Самозаверяющее принятие SSL на Android - PullRequest
52 голосов
/ 01 августа 2009

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

Пример кода будет идеальным.

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

Ответы [ 7 ]

37 голосов
/ 22 октября 2009

Как правильно прокомментировал 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();
    }
}
37 голосов
/ 01 августа 2009

У меня есть эта функциональность в exchangeIt, которая подключается к бирже Microsoft через 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 процесс немного изменился, поэтому отметьте this , чтобы приведенный выше код работал.

35 голосов
/ 20 марта 2013

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

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

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

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

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

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

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

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

<!-- language: lang-sh -->

    $ 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-библиотеке.

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

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

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

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

<!-- language: lang-xml -->

    <resources>
    ...
        <string name="store_pass">*STOREPASS*</string>
    ...
    </resources>
  1. Создать этот класс, который наследует 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());
}
15 голосов
/ 20 октября 2012

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

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

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

  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;
    }
    

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

4 голосов
/ 04 ноября 2010

Ответ Брайана Яргера работает и в 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);
}
0 голосов
/ 09 января 2013

@ Крис - Публикация этого ответа, так как я не могу добавлять комментарии (пока). Мне интересно, если ваш подход должен работать при использовании веб-просмотра. Я не могу этого сделать на Android 2.3 - вместо этого у меня просто белый экран.

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

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

0 голосов
/ 26 мая 2011

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

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