Поступила мне задачка: Написать приложение под Андройд, которое ходит с клиентским сертификатом через https соединение. Вроде, что может быть проще! Снабжать свои приложения возможностью выхода в сеть я научилась еще давно. И думала, что легко будет сделать тоже самое, только еще и с сертификатом. Но не тут-то было. Долго искала примеры, читала различные доки. Простое https-соединение у меня получалось. Как и думала - нет ничего проще. Но заставить приложение использовать клиентский сертификат....оказалось сложнее. Это сейчас я знаю, что все на самом деле легко. Надо googl'у задавать правильные вопросы и в различных примерах убирать все ненужные " красявости". В самом начале моего поиска сбила с толку меня статья где-то на каком-то зарубежном сайте. Там говорилось, что необходимо взять сертификат формата *.pem, импортировать его в файл-хранилище, например типа bks,закинуть на Android-устройство и им пользоваться. И вот тут-то я свернула с верного пути. И потратила много времени на поиск и на различные тестовые реализации. Но как говорится - "Если долго мучиться что-нибудь получится" и мой кропотливый труд наконец-то был вознагражден! Спасибо, конечно, некоторым прекрасным лицам на форуме и на работе, которые помогали и подсказывали мне. И конечно спасибо удаче, которая посетила меня вчера. В итоге весь мой полуторонедельный труд уложился вот в этот кусочек кода:
private void onClickConnect(String path,String password,String https_url) { //Создание файл хранилища для сертификата формата p12 //https_url - то, куда будем ходить //path - путь к файлу на андройд-устройстве //password - пароль от сертификата. На многих сайтах пишут, //чтобы пароль от файл-хранилища был такой же как и у сертификата. //Иначе будет ошибка. InputStream in = null; try { in = new FileInputStream(path); } catch (FileNotFoundException e1) { e1.printStackTrace(); } //Фабрика клиентских сертификатов KeyManagerFactory mgrFact = null; try { mgrFact = KeyManagerFactory.getInstance("X509"); } catch (NoSuchAlgorithmException e1) { e1.printStackTrace(); } KeyStore clientStore = null; try { clientStore = KeyStore.getInstance("PKCS12"); } catch (KeyStoreException e1) { e1.printStackTrace(); } try { clientStore.load(in, password.toCharArray()); } catch (NoSuchAlgorithmException e1) { e1.printStackTrace(); } catch (CertificateException e1) { e1.printStackTrace(); } catch (IOException e1) { e1.printStackTrace(); } try { mgrFact.init(clientStore, password.toCharArray()); } catch (UnrecoverableKeyException e1) { e1.printStackTrace(); } catch (KeyStoreException e1) { e1.printStackTrace(); } catch (NoSuchAlgorithmException e1) { e1.printStackTrace(); } //Фабрика управления доверительными соединениями //она будет использоваться для проверки сертифкатов //всех https соединений final TrustManager[] trustAllCerts = new TrustManager[] {new X509TrustManager() { public X509Certificate[] getAcceptedIssuers() { System.out.println("getAcceptedIssuers"); return null; } public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { System.out.println("Сведения о сертификате : " + chain[0].getIssuerX500Principal().getName() + " Тип авторизации : " + authType); } public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { System.out.println("checkClientTrusted : " + authType); } } }; //Далее создаем sslContext SSLContext sslContext = null; try { sslContext = SSLContext.getInstance("TLS"); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } //Настраиваем его с помощью наших фабрик try { sslContext.init(mgrFact.getKeyManagers(),trustAllCerts, new java.security.SecureRandom()); } catch (KeyManagementException e) { e.printStackTrace(); } final javax.net.ssl.SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory(); //Создаем соединение HttpURLConnection conn = null; try { conn = (HttpURLConnection) new URL(https_url).openConnection(); } catch (MalformedURLException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } //Настраиваем его try { conn.setRequestMethod("GET"); conn.setUseCaches(false); conn.setDoInput(true); conn.setReadTimeout(10000 ); conn.setConnectTimeout(15000); } catch (ProtocolException e) { e.printStackTrace(); } //encodeBase64 - функция преобразует строку в Base64 //это если еще требуется авторизация conn.setRequestProperty("Authorization", "Basic "+encodeBase64("user:password") ); conn.setRequestProperty("Connection", "close"); conn.setRequestProperty("Accept-Charset", "cp1251"); сonn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); ((HttpsURLConnection) conn).setSSLSocketFactory(sslSocketFactory); //Проверка имени хоста. Но проверку мы не делаем, а подтверждаем любой хост. //Кому надо, могут сделать проверку на что-нибудь ((HttpsURLConnection) conn).setHostnameVerifier(new HostnameVerifier() { public boolean verify(String arg0, SSLSession arg1) { return true; } }); // Устанавливаем соединение try { if (conn.getResponseCode() != HttpURLConnection.HTTP_OK) { System.out.println("Ошибка соединения"+ conn.getResponseCode()); }else{ System.out.println("Соединение состоялось "+conn.getResponseCode()+ " ["+conn.getResponseMessage()+"]"); StringBuffer buf = new StringBuffer(); InputStreamReader in = new InputStreamReader(conn.getInputStream(), "cp1251"); int ch; while ((ch = in.read()) != -1) { buf.append((char)c); } in.close(); String response = buf.toString(); System.out.println("response "+response); //вывод различной информации по сертифкату, если надо. System.out.println("Cipher Suite : " + conn.getCipherSuite()); Certificate[] certs = conn.getServerCertificates(); System.out.println("n"); for(Certificate cert : certs){ System.out.println("Cert Type : " + cert.getType()); System.out.println("Cert Hash Code : " + cert.hashCode()); System.out.println("Cert Public Key Algorithm : " + cert.getPublicKey().getAlgorithm()); System.out.println("Cert Public Key Format : " + cert.getPublicKey().getFormat()); } } } catch (IOException e) { e.printStackTrace(); } catch (Exception e) { e.printStackTrace(); } }
Это написано все в обработчике кнопки. Но желательно вынести все в отдельные функции или даже класс, что я реализую для себя сегодня )
Спасибо Дмитрию и obrazel за их помощь, а так же моим коллегам.
Полезные статьи:
http://my-it-notes.com/2011/09/how-to-approve-ssl-certificate-on-android/ ,
http://www.cyberforum.ru/android-dev/thread613608-page2.html ,
http://www.javadocexamples.com/java/security/KeyStore/getInstance%28String%20type%29.html - вот эта полезная статья, которая наконец-то пнула меня в верном направлении и дала завершить проект!
Если будут какие-то замечания по коду или предложения - буду рада услышать и узнать что-то новенькое!