使用本地信任库时,Android HttpsUrlConnection javax.net.ssl.SSLException 连接因对等握手错误而关闭
Posted
技术标签:
【中文标题】使用本地信任库时,Android HttpsUrlConnection javax.net.ssl.SSLException 连接因对等握手错误而关闭【英文标题】:Android HttpsUrlConnection javax.net.ssl.SSLException Connection closed by peer handshake error when using local truststore 【发布时间】:2012-09-03 00:08:05 【问题描述】:我无法让 android 使用 HttpsUrlConnection
对象连接到简单的 OpenSSL
服务器(我已经梳理了 *** 和一堆在线教程,并且几乎逐行遵循示例当我使用本地信任库时,我仍然无法弄清楚为什么我的会损坏)。
我目前有一个 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 只想使用本地信任库 使用充气城堡创建本地信任库 能够正确地将信任库加载到 在代理防火墙后面,我的安卓虚拟设备上设置了代理 AVD 设置为Android 4.1 API 16
。
我已经尝试过的事情:
同时连接到127.0.0.1 and 10.0.2.2
使用新的SecureRandom() with the SSLContext.init()
使用'URL u = new URL("https", "10.0.2.2", 443, "/");'
创建 URL
使用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$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)
再次感谢!!
【问题讨论】:
我忘了提到,当我尝试连接 Android 模拟器时,我没有看到任何服务器活动,只有我的 OpenSSL 客户端。 很难格式化这个问题;-) 抱歉,在否决问题之前,有什么方法可以让我更容易理解吗?在我被重定向到其他帖子之前,我正在尝试解释我从其他帖子中获取的方法。 尽量减少这个问题...只显示重要的内容而不是整个程序.... 【参考方案1】:我解决了我的问题 - 我需要使用 10.0.2.2 作为通用名称 (CN) 的证书,因此它匹配 Android localhost ip 地址 10.0.2.2 而不是“localhost”或“127.0.0.1”。
编辑:您可能会创建一个证书,其中 localhost 作为 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;
【讨论】:
【参考方案2】:我浪费了 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());
成功了!!!!!!
【讨论】:
以上是关于使用本地信任库时,Android HttpsUrlConnection javax.net.ssl.SSLException 连接因对等握手错误而关闭的主要内容,如果未能解决你的问题,请参考以下文章