Вопрос

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

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

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

Это было полезно?

Решение

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

Другие советы

Как правильно прокомментировал 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();
    }
}

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

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

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

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

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

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

Вам также понадобится самозаверяющий сертификат, я предполагаю, что он называется 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 (сертификаты.bks это просто имя файла;вы можете использовать любое имя, какое захотите)

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

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

    <resources>
    ...
        <string name="store_pass">*STOREPASS*</string>
    ...
    </resources>
  1. Создайте класс this, который наследует 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) - добавить дополнительный доверенный якорь, которому нужно доверять в процессе проверки сертификата. Оба включают жесткое кодирование сертификата доверенного якоря в ваше приложение и добавление его к любым доверенным якорям, которые предоставляет ОС (иначе вы не сможете подключиться к своему сайту, если получите настоящий сертификат).

Мне известны два способа сделать это:

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

  • Создайте пользовательский экземпляр 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 - опубликовать это как ответ, так как я не могу добавлять комментарии (пока). Мне интересно, если ваш подход должен работать при использовании веб-просмотра. Я не могу заставить это сделать на Android 2.3 - вместо этого я просто получаю белый экран.

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

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

    Лицензировано под: CC-BY-SA с атрибуция
    Не связан с StackOverflow
    scroll top