Android HttpsUrlConnection javax.сеть.ssl.Соединение SSLException закрыто ошибкой однорангового рукопожатия при использовании локального truststore
у меня возникли проблемы с получением Android для подключения к простому OpenSSL
сервер с помощью HttpsUrlConnection
объект (я прочесал StackOverflow и кучу онлайн-учебников и следовал примерам в значительной степени строка за строкой, и я все еще не могу понять, почему мой сломан, когда я использую свой локальный truststore).
в настоящее время у меня есть Android-активность, которая пытается подключиться к простому OpenSSL server
(Я могу подключиться к своему серверу с помощью клиента OpenSSL), как только HttpsUrlConnection.connect()
is вызванный я получаю "javax.net.ssl.SSLException: Connection closed by peer" error during the SSL handshake.
возможно, я неправильно настраиваю свой пример сервера?
Примечание:
- на данный момент нет авторизации клиента
- я могу подключиться к https://www.google.com при загрузке хранилища доверия по умолчанию
- не могу подключиться к серверу на localhost с самозаверяющим сертификатом
- не хочу доверять всем сертификатам
- не хотите использовать Apache С помощью HttpClient
- хотите использовать только локальный truststore
- создан местный truststore с надувной замок
- я могу правильно загрузить truststore в
- за брандмауэром прокси-сервера прокси установлен на моем виртуальном устройстве 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, "/");'
- используя
TrustManagerFactory.getDefaultAlgorithms()
вместо "X509"- дает
"Unexpected response code error 503"
вместо "соединение закрыто сервером"
- дает
заранее спасибо, что нашли время, чтобы рассмотреть мой вопрос!
простой сервер начался с команды:
$ 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.doInBackground(HttpsUrlConnectionActivity.java:257)
09-09 22:03:23.377: D/HttpsProxy(717): at com.example.myfirstapp.HttpsUrlConnectionActivity.doInBackground(HttpsUrlConnectionActivity.java:1)
09-09 22:03:23.377: D/HttpsProxy(717): at android.os.AsyncTask.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.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)
2 ответов
я решил свою проблему - мне нужно было использовать сертификат с 10.0.2.2 в качестве общего имени (CN), чтобы он соответствовал ip-адресу Android localhost 10.0.2.2 вместо "localhost" или "127.0.0.1".
Edit: вы, вероятно, можете создать сертификат с localhost как CN и "127.0.0.1" и "10.0.2.2" в качестве альтернативных имен субъекта (SAN).
как только я создал 10.0.2.2 cert и 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;
}
Я потратил свои 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());
}
и это сработало !!!!!!