Самоподписанный SSL-прием на Android

Как принять самозаверяющий сертификат в Java на Android?

Образец кода был бы идеальным.

Я смотрел всюду в Интернете, и, хотя некоторые люди утверждают, что нашли решение, оно либо не работает, либо нет образца кода для его резервного копирования.

У меня есть эта функция в exchangeIt, которая подключается к Microsoft Exchange через WebDav. Вот какой код для создания HttpClient, который будет подключаться к самостоятельно подписанному сертификату через SSL:

SchemeRegistry schemeRegistry = new SchemeRegistry(); // http scheme schemeRegistry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80)); // https scheme schemeRegistry.register(new Scheme("https", new EasySSLSocketFactory(), 443)); HttpParams params = new BasicHttpParams(); params.setParameter(ConnManagerPNames.MAX_TOTAL_CONNECTIONS, 30); params.setParameter(ConnManagerPNames.MAX_CONNECTIONS_PER_ROUTE, new ConnPerRouteBean(30)); params.setParameter(HttpProtocolParams.USE_EXPECT_CONTINUE, false); HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1); ClientConnectionManager cm = new ThreadSafeClientConnManager(params, schemeRegistry); 

Здесь находится EasySSLSocketFactory, и здесь находится EasyX509TrustManager.

Код для exchangeIt является открытым исходным кодом и размещен на googlecode здесь , если у вас есть какие-либо проблемы. Я больше не работаю над этим, но код должен работать.

Обратите внимание, что с Android 2.2 процесс немного изменился, поэтому проверьте это, чтобы сделать код выше.

Как правильно прокомментировал EJP: «Читатели должны заметить, что этот метод радикально небезопасен. SSL небезопасен, если не проверено хотя бы одно одноранговое соединение. См. RFC 2246.”

Сказав это, вот еще один способ, без каких-либо дополнительных занятий:

 import java.security.SecureRandom; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import javax.net.ssl.HostnameVerifier; import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSession; import javax.net.ssl.X509TrustManager; private void trustEveryone() { try { HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier(){ public boolean verify(String hostname, SSLSession session) { return true; }}); SSLContext context = SSLContext.getInstance("TLS"); context.init(null, new X509TrustManager[]{new X509TrustManager(){ public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {} public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {} public X509Certificate[] getAcceptedIssuers() { return new X509Certificate[0]; }}}, new SecureRandom()); HttpsURLConnection.setDefaultSSLSocketFactory( context.getSocketFactory()); } catch (Exception e) { // should never happen e.printStackTrace(); } } 

Вчера я столкнулся с этим вопросом, перенося API RESTful нашей компании на HTTPS, но используя самоподписанные сертификаты SSL.

Я смотрю повсюду, но все «правильные» отмеченные ответы, которые я нашел, состояли в отключении проверки сертификатов, явно переопределяющем весь смысл SSL.

Я, наконец, пришел к решению:

  1. Создание локального хранилища ключей

    Чтобы ваше приложение могло проверить ваши самозаверяющие сертификаты, вам необходимо предоставить настраиваемое хранилище ключей сертификатами таким образом, чтобы Android мог доверять вашей конечной точке.

Формат таких пользовательских хранилищ – «BKS» от BouncyCastle , поэтому вам нужна версия BouncyCastleProvider версии 1.46, которую вы можете скачать здесь .

Вам также нужен ваш самозаверяющий сертификат, я предполагаю, что он называется self_cert.pem .

Теперь команда создания вашего хранилища ключей:

  $ keytool -import -v -trustcacerts -alias 0 \ -file *PATH_TO_SELF_CERT.PEM* \ -keystore *PATH_TO_KEYSTORE* \ -storetype BKS \ -provider org.bouncycastle.jce.provider.BouncyCastleProvider \ -providerpath *PATH_TO_bcprov-jdk15on-146.jar* \ -storepass *STOREPASS* 

PATH_TO_KEYSTORE указывает на файл, в котором будет создано ваше хранилище ключей. Он НЕ ДОЛЖЕН СУЩЕСТВОВАТЬ .

PATH_TO_bcprov-jdk15on-146.jar.JAR – это путь к загружаемому .jar libary.

STOREPASS – ваш вновь созданный пароль хранилища ключей.

  1. Включить KeyStore в приложение

Скопируйте новое созданное хранилище ключей из PATH_TO_KEYSTORE в res/raw/certs.bks ( certs.bks – это просто имя файла, вы можете использовать любое имя)

Создайте ключ в res/values/strings.xml с помощью

   ... *STOREPASS* ...  
  1. Создайте этот class, который наследует DefaultHttpClient

     import android.content.Context; import android.util.Log; import org.apache.http.conn.scheme.PlainSocketFactory; import org.apache.http.conn.scheme.Scheme; import org.apache.http.conn.scheme.SchemeRegistry; import org.apache.http.conn.ssl.SSLSocketFactory; import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.params.HttpParams; import java.io.IOException; import java.io.InputStream; import java.security.*; public class MyHttpClient extends DefaultHttpClient { private static Context appContext = null; private static HttpParams params = null; private static SchemeRegistry schmReg = null; private static Scheme httpsScheme = null; private static Scheme httpScheme = null; private static String TAG = "MyHttpClient"; public MyHttpClient(Context myContext) { appContext = myContext; if (httpScheme == null || httpsScheme == null) { httpScheme = new Scheme("http", PlainSocketFactory.getSocketFactory(), 80); httpsScheme = new Scheme("https", mySSLSocketFactory(), 443); } getConnectionManager().getSchemeRegistry().register(httpScheme); getConnectionManager().getSchemeRegistry().register(httpsScheme); } private SSLSocketFactory mySSLSocketFactory() { SSLSocketFactory ret = null; try { final KeyStore ks = KeyStore.getInstance("BKS"); final InputStream inputStream = appContext.getResources().openRawResource(R.raw.certs); ks.load(inputStream, appContext.getString(R.string.store_pass).toCharArray()); inputStream.close(); ret = new SSLSocketFactory(ks); } catch (UnrecoverableKeyException ex) { Log.d(TAG, ex.getMessage()); } catch (KeyStoreException ex) { Log.d(TAG, ex.getMessage()); } catch (KeyManagementException ex) { Log.d(TAG, ex.getMessage()); } catch (NoSuchAlgorithmException ex) { Log.d(TAG, ex.getMessage()); } catch (IOException ex) { Log.d(TAG, ex.getMessage()); } catch (Exception ex) { Log.d(TAG, ex.getMessage()); } finally { return ret; } } } 

Теперь просто используйте экземпляр **MyHttpClient** как и с **DefaultHttpClient** чтобы сделать ваши HTTPS-запросы, и он будет корректно использовать и правильно проверять ваши самоподписанные SSL-сертификаты.

 HttpResponse httpResponse; HttpPost httpQuery = new HttpPost("https://yourserver.com"); ... set up your query ... MyHttpClient myClient = new MyHttpClient(myContext); try{ httpResponse = myClient.(peticionHttp); // Check for 200 OK code if (httpResponse.getStatusLine().getStatusCode() == HttpURLConnection.HTTP_OK) { ... do whatever you want with your response ... } }catch (Exception ex){ Log.d("httpError", ex.getMessage()); } 

Если я что-то пропустил, другие ответы на этой странице ОПАСНЫ, и они функционально эквивалентны тому, чтобы не использовать SSL вообще. Если вы доверяете самозаверяющим сертификатам без дополнительных проверок, чтобы убедиться, что сертификаты являются теми, которые вы ожидаете, каждый может создать самозаверяющий сертификат и может претендовать на роль вашего сервера. В этот момент у вас нет реальной безопасности.

Единственный законный способ сделать это (без написания полного стека SSL) – добавить дополнительный доверенный якорь, которому нужно доверять во время процесса проверки сертификата. Оба include жесткое кодирование доверенного сертификата привязки в ваше приложение и добавление его к любым доверенным якорям, предоставляемым ОС (иначе вы не сможете подключиться к вашему сайту, если получите реальный сертификат).

Я знаю два способа сделать это:

  1. Создайте собственное хранилище доверия, как описано в http://www.ibm.com/developerworks/java/library/j-customssl/#8.

  2. Создайте собственный экземпляр X509TrustManager и переопределите метод getAcceptedIssuers, чтобы вернуть массив, содержащий ваш сертификат:

     public X509Certificate[] getAcceptedIssuers() { X509Certificate[] trustedAnchors = super.getAcceptedIssuers(); /* Create a new array with room for an additional trusted certificate. */ X509Certificate[] myTrustedAnchors = new X509Certificate[trustedAnchors.length + 1]; System.arraycopy(trustedAnchors, 0, myTrustedAnchors, 0, trustedAnchors.length); /* Load your certificate. Thanks to http://stackoverflow.com/questions/11857417/x509trustmanager-override-without-allowing-all-certs for this bit. */ InputStream inStream = new FileInputStream("fileName-of-cert"); CertificateFactory cf = CertificateFactory.getInstance("X.509"); X509Certificate cert = (X509Certificate)cf.generateCertificate(inStream); inStream.close(); /* Add your anchor cert as the last item in the array. */ myTrustedAnchors[trustedAnchors.length] = cert; return myTrustedAnchors; } 

Обратите внимание, что этот код полностью не проверен и может даже не компилироваться, но должен по крайней мере направлять вас в правильном направлении.

Ответ Брайана Ярджера также работает в Android 2.2, если вы измените большую загрузку метода createSocket следующим образом. Мне потребовалось некоторое время, чтобы заставить работать самоподписанные SSL.

 public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException, UnknownHostException { return getSSLContext().getSocketFactory().createSocket(socket, host, port, autoClose); } 

На Android HttpProtocolParams принимает ProtocolVersion а не HttpVersion .

 ProtocolVersion pv = new ProtocolVersion("HTTP", 1, 1); HttpProtocolParams.setVersion(params, pv); 

@Chris – Опубликовать это как ответ, так как я не могу добавлять комментарии (пока). Мне интересно, должен ли ваш подход работать при использовании webView. Я не могу заставить его делать это на Android 2.3 – вместо этого я просто получаю белый экран.

После некоторого поиска я наткнулся на это простое исправление для обработки ошибок SSL в webView, который работал для меня как шарм.

В обработчике я проверяю, есть ли я в специальном режиме dev и вызываю handler.proceed (), иначе я вызываю handler.cancel (). Это позволяет мне делать разработку против самозаверяющего сертификата на локальном веб-сайте.

Давайте будем гением компьютера.