Android HttpsUrlConnection javax.net.ssl.SSLException Соединение закрыто из-за ошибки однорангового рукопожатия при использовании локального хранилища доверенных сертификатов

У меня возникли проблемы с тем, чтобы заставить Android подключиться к простому серверу OpenSSL с помощью объекта HttpsUrlConnection (я просмотрел StackOverflow и кучу онлайн-руководств и следовал примерам в значительной степени строка за строкой, и я до сих пор не могу понять почему мой не работает, когда я использую свой локальный доверенный магазин).

В настоящее время у меня есть активность Android, которая пытается подключиться к простому OpenSSL server (я могу подключиться к своему серверу с помощью клиента OpenSSL), после вызова HttpsUrlConnection.connect() я получаю сообщение «javax.net.ssl.SSLException: Connection closed by peer" error during the SSL handshake. Возможно, я неправильно настраиваю свой образец сервера?

Что следует отметить:

  • на данный момент нет авторизации клиента
  • я могу подключиться к https://www.google.com при загрузке хранилища доверия по умолчанию
  • я не могу подключиться к серверу на локальном хосте с самоподписанным сертификатом
  • не хочу доверять всем сертификатам
  • не хотите использовать Apache HttpClient
  • хотите использовать только локальное хранилище доверенных сертификатов
  • создал местный трастовый магазин с надувным замком
  • я могу правильно загрузить доверенное хранилище в
  • за прокси-брандмауэром прокси-сервер установлен на моем виртуальном устройстве Android
  • AVD установлен на Android 4.1 API 16.

Вещи, которые я уже пробовал:

  • подключение к обоим 127.0.0.1 and 10.0.2.2
  • используя новый SecureRandom() with the SSLContext.init()
  • создание URL с 'URL u = new URL("https", "10.0.2.2", 443, "/");'
  • using TrustManagerFactory.getDefaultAlgorithms() instead of the "X509"
    • gives "Unexpected response code error 503" instead of "Connection closed by peer"

Заранее благодарю Вас за то, что нашли время рассмотреть мой вопрос!

Простой сервер запускается командой:

$ sudo openssl s_server -accept 443 -cert server-cert.pem -key server-key.pem -pass file:passphrase.txt -state -www -verify 0

Соединение с клиентом проверено командой:

$ openssl s_client -connect 127.0.0.1:443 

Код действия Android (отредактировано, чтобы удалить полный рабочий код для упрощения — пожалуйста, дайте мне знать, если потребуется дополнительный код) — вывод ошибки находится под кодом.

    try {
        TrustManagerFactory tmf;

        // local trust store
        tmf = TrustManagerFactory.getInstance("X509");
        tmf.init(loadLocalKeyStore(getApplicationContext()));

        // default trust store - works for https://www.google.com
        // tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
        // tmf.init((KeyStore) null);

        SSLContext context = SSLContext.getInstance("TLS");
        context.init(null, tmf.getTrustManagers(), null);

        HostnameVerifier hostnameVerifier = org.apache.http.conn.ssl.SSLSocketFactory.STRICT_HOSTNAME_VERIFIER;
        URL u = new URL("https://10.0.2.2");

        HttpsURLConnection urlConnection = (HttpsURLConnection) u.openConnection();

        urlConnection.setSSLSocketFactory(context.getSocketFactory());
        urlConnection.setHostnameVerifier(hostnameVerifier);
        urlConnection.connect();

        System.out.println("Response Code: " + urlConnection.getResponseCode());
        System.out.println("Response Code: " + urlConnection.getCipherSuite());
    } 

    ...

    private KeyStore loadLocalKeyStore(Context context) {
        InputStream in = context.getResources().openRawResource(R.raw.newserverkeystore);
        KeyStore trusted = null;
        try {
           trusted = KeyStore.getInstance("BKS");
           trusted.load(in, "thisisasecret".toCharArray());
        } finally {
           in.close();
        }
       return trusted;
    }

Вывод при правильном подключении к https://www.google.com:

09-09 21:58:09.947: I/System.out(669): Response Code: 200
09-09 21:58:09.947: I/System.out(669): Response Code: TLS_ECDHE_RSA_WITH_RC4_128_SHA

Вывод при попытке подключения к моему серверу с самоподписанным сертификатом:

09-09 22:03:23.377: D/HttpsProxy(717): Https Request error
09-09 22:03:23.377: D/HttpsProxy(717): javax.net.ssl.SSLException: Connection closed by peer
09-09 22:03:23.377: D/HttpsProxy(717):  at org.apache.harmony.xnet.provider.jsse.NativeCrypto.SSL_do_handshake(Native Method)
09-09 22:03:23.377: D/HttpsProxy(717):  at org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.java:395)
09-09 22:03:23.377: D/HttpsProxy(717):  at libcore.net.http.HttpConnection.setupSecureSocket(HttpConnection.java:210)
09-09 22:03:23.377: D/HttpsProxy(717):  at libcore.net.http.HttpsURLConnectionImpl$HttpsEngine.makeSslConnection(HttpsURLConnectionImpl.java:478)
09-09 22:03:23.377: D/HttpsProxy(717):  at libcore.net.http.HttpsURLConnectionImpl$HttpsEngine.connect(HttpsURLConnectionImpl.java:442)
09-09 22:03:23.377: D/HttpsProxy(717):  at libcore.net.http.HttpEngine.sendSocketRequest(HttpEngine.java:289)
09-09 22:03:23.377: D/HttpsProxy(717):  at libcore.net.http.HttpEngine.sendRequest(HttpEngine.java:239)
09-09 22:03:23.377: D/HttpsProxy(717):  at libcore.net.http.HttpURLConnectionImpl.connect(HttpURLConnectionImpl.java:80)
09-09 22:03:23.377: D/HttpsProxy(717):  at libcore.net.http.HttpsURLConnectionImpl.connect(HttpsURLConnectionImpl.java:165)
09-09 22:03:23.377: D/HttpsProxy(717):  at com.example.myfirstapp.HttpsUrlConnectionActivity$3.doInBackground(HttpsUrlConnectionActivity.java:257)
09-09 22:03:23.377: D/HttpsProxy(717):  at com.example.myfirstapp.HttpsUrlConnectionActivity$3.doInBackground(HttpsUrlConnectionActivity.java:1)
09-09 22:03:23.377: D/HttpsProxy(717):  at android.os.AsyncTask$2.call(AsyncTask.java:287)
09-09 22:03:23.377: D/HttpsProxy(717):  at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:305)
09-09 22:03:23.377: D/HttpsProxy(717):  at java.util.concurrent.FutureTask.run(FutureTask.java:137)
09-09 22:03:23.377: D/HttpsProxy(717):  at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:230)
09-09 22:03:23.377: D/HttpsProxy(717):  at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1076)
09-09 22:03:23.377: D/HttpsProxy(717):  at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:569)
09-09 22:03:23.377: D/HttpsProxy(717):  at java.lang.Thread.run(Thread.java:856)

Спасибо еще раз!!


person aspergillusOryzae    schedule 10.09.2012    source источник
comment
Я забыл упомянуть, что я не вижу никакой активности сервера, когда пытаюсь подключиться к эмулятору Android, только с моим клиентом OpenSSL.   -  person aspergillusOryzae    schedule 10.09.2012
comment
очень сложно отформатировать этот вопрос ;-)   -  person Sandip Armal Patil    schedule 10.09.2012
comment
Извините, есть ли способ упростить понимание, прежде чем голосовать за вопрос? Я пытался объяснить методы, которые я взял из других сообщений, прежде чем меня перенаправят к ним.   -  person aspergillusOryzae    schedule 10.09.2012
comment
попробуйте сократить этот вопрос... просто покажите, что важно, а не всю программу....   -  person Sandip Armal Patil    schedule 10.09.2012


Ответы (2)


Я решил свою проблему - мне нужно было использовать сертификат с 10.0.2.2 в качестве общего имени (CN), чтобы он соответствовал IP-адресу локального хоста Android 10.0.2.2 вместо «localhost» или «127.0.0.1».

Изменить: вы, вероятно, могли бы создать сертификат с локальным хостом в качестве CN и «127.0.0.1» и «10.0.2.2» в качестве альтернативных имен субъекта (SAN).

Как только я создал файлы сертификата 10.0.2.2 и PEM-файлы закрытого ключа, я смог запустить свой сервер с помощью следующей команды:

openssl s_server -accept 8888 -cert 10.0.2.2-cert.pem -key 10.0.2.2-key.pem  -state -www

Если вы хотите, чтобы клиент предоставил сертификат (хотя он не будет проверяться), добавьте флаг -Verify 1 в приведенную выше команду.

Чтобы протестировать сервер в командной строке, вы можете использовать следующее (обратите внимание, что openssl может подключаться через 127.0.0.1):

openssl s_client -connect 127.0.0.1:8888

И чтобы добавить клиентский сертификат, если он требуется серверу, добавьте флаги -cert client-cert.pem -key client-key.pem

В моем клиенте для Android я использовал следующий код для подключения (проверка ошибок удалена):

// use local trust store (CA)
TrustManagerFactory tmf;
KeyStore trustedStore = null;
InputStream in = context.getResources().openRawResource(R.raw.mycatruststore); // BKS in res/raw
trustedStore = KeyStore.getInstance("BKS");
trustedStore.load(in, "insertBksPasswordHere".toCharArray());
tmf = TrustManagerFactory.getInstance("X509");
tmf.init(trustedStore);

// load client certificate
KeyStore clientKeyStore = loadClientKeyStore(getApplicationContext());
KeyManagerFactory kmf = KeyManagerFactory.getInstance("X509");
kmf.init(clientKeyStore, "insertPasswordHere".toCharArray());

SSLContext context = SSLContext.getInstance("TLS");

// provide client cert - if server requires client cert this will pass
context.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
HostnameVerifier hostnameVerifier = org.apache.http.conn.ssl.SSLSocketFactory.STRICT_HOSTNAME_VERIFIER;

// connect to url
URL u = new URL("https://10.0.2.2:8888/");
HttpsURLConnection urlConnection = (HttpsURLConnection) u.openConnection();
urlConnection.setSSLSocketFactory(context.getSocketFactory());
urlConnection.setHostnameVerifier(hostnameVerifier);
urlConnection.connect();
System.out.println("Response Code: " + urlConnection.getResponseCode());

Вы должны получить код ответа 200 и можете анализировать ответ оттуда.

Вот код для загрузки учетных данных клиента, который идентичен загрузке хранилища ключей сервера, но с другим именем файла ресурса и паролем:

private KeyStore loadClientKeyStore(Context context) {
    InputStream in = context.getResources().openRawResource(R.yourKeyStoreFile);
    KeyStore trusted = null;
    trusted = KeyStore.getInstance("BKS");
    trusted.load(in, "yourClientPassword".toCharArray());
    in.close();
    return trusted;
}
person aspergillusOryzae    schedule 03.10.2012

Я потратил 6-7 часов на исправление этой проблемы, и, наконец, она заработала.

public void URLConnection(String webUrl) throws IOException, NoSuchAlgorithmException, KeyManagementException {
        //TLSSocketFactory objTlsSocketFactory = new TLSSocketFactory();
        URL url = new URL(webUrl);
        HttpsURLConnection urlConnection = (HttpsURLConnection)url.openConnection();
        urlConnection.setRequestMethod("GET");
        //urlConnection.setSSLSocketFactory(objTlsSocketFactory);

        int responseCode = urlConnection.getResponseCode();
        System.out.println("\nSending 'GET' request to URL : " + url);
        System.out.println("Response Code : " + responseCode);

        BufferedReader in = new BufferedReader(
                new InputStreamReader(urlConnection.getInputStream()));
        String inputLine;
        StringBuffer response = new StringBuffer();

        while ((inputLine = in.readLine()) != null) {
            response.append(inputLine);
        }
        in.close();

        //print result
        System.out.println(response.toString());
    }

И это сработало!!!!!!

person sidhanshu    schedule 22.02.2016