使用本地信任库时,Android HttpsUrlConnection javax.net.ssl.SSLException 连接被对等握手错误关闭

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/12346368/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-20 10:27:18  来源:igfitidea点击:

Android HttpsUrlConnection javax.net.ssl.SSLException Connection closed by peer handshake error when using local truststore

androidsslandroid-emulatoropensslhttpsurlconnection

提问by aspergillusOryzae

I'm having trouble with getting Android to connect to a simple OpenSSLserver using the HttpsUrlConnectionobject (I've combed through StackOverflow and a bunch of online tutorials, and followed the examples pretty much line for line and I still can't figure out why mine is broken when I use my local truststore).

我在OpenSSL使用HttpsUrlConnection对象让 Android 连接到一个简单的服务器时遇到了麻烦(我已经梳理了 StackOverflow 和一堆在线教程,并且几乎一行一行地遵循了示例,但我仍然无法弄清楚为什么我的当我使用本地信任库时已损坏)。

I currently have an Android activity that attempts to connect to a simple OpenSSL server(I can connect to my server using a OpenSSL client), once the HttpsUrlConnection.connect()is called I receive a "javax.net.ssl.SSLException: Connection closed by peer" error during the SSL handshake.Perhaps I am setting up my sample server incorrectly?

我目前有一个 Android 活动,它尝试连接到一个简单的OpenSSL server(我可以使用 OpenSSL 客户端连接到我的服务器),一旦HttpsUrlConnection.connect()被调用,我收到一个“javax.net.ssl.SSLException: Connection closed by peer" error during the SSL handshake.也许我设置的示例服务器不正确?

Things to note:

注意事项:

  • no client authorization at the moment
  • am able to connect to https://www.google.comwhen loading default trust store
  • am not able to connect to server on localhost with self-signed certificate
  • do not want to trust all certificates
  • do not want to use Apache HttpClient
  • want to use local truststore only
  • created local truststore with bouncy castle
  • am able to correctly load truststore into
  • behind a proxy firewall, proxy is set on my android virtual device
  • AVD set to Android 4.1 API 16.
  • 目前没有客户授权
  • 加载默认信任存储时能够连接到https://www.google.com
  • 无法使用自签名证书连接到本地主机上的服务器
  • 不想信任所有证书
  • 不想使用 Apache HttpClient
  • 只想使用本地信任库
  • 使用充气城堡创建本地信任库
  • 能够正确地将信任库加载到
  • 在代理防火墙后面,我的 android 虚拟设备上设置了代理
  • AVD 设置为Android 4.1 API 16.

Things I have already tried:

我已经尝试过的事情:

  • connecting to both 127.0.0.1 and 10.0.2.2
  • using a new SecureRandom() with the SSLContext.init()
  • creating the URL with '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"
  • 连接到两者 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"而不是“对等方关闭的连接”

Thank you in advance for taking the time to review my question!

预先感谢您抽出时间来查看我的问题!

Simple server started with command:

使用以下命令启动的简单服务器:

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

Client connection tested with command:

使用命令测试的客户端连接:

$ openssl s_client -connect 127.0.0.1:443 

Android activity code (edited to remove complete running code for simplification - please let me know if more code is needed) - error output is below the code.

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

Output when connecting correctly to https://www.google.com:

正确连接到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

Output when trying to connect to my server with self-signed certificate:

尝试使用自签名证书连接到我的服务器时的输出:

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)

Thanks again!!

再次感谢!!

采纳答案by aspergillusOryzae

I solved my problem - I needed to use a certificate with 10.0.2.2 as the common name (CN) so it matched Android localhost ip address of 10.0.2.2 instead of 'localhost' or '127.0.0.1'.

我解决了我的问题 - 我需要使用 10.0.2.2 作为通用名称 (CN) 的证书,因此它匹配 Android localhost ip 地址 10.0.2.2 而不是“localhost”或“127.0.0.1”。

Edit: you could probably create a certificate with localhost as the CN and '127.0.0.1' and '10.0.2.2' as Subject Alternative Names (SAN).

编辑:您可能会创建一个以 localhost 作为 CN、“127.0.0.1”和“10.0.2.2”作为主题备用名称 (SAN) 的证书。

Once I created 10.0.2.2 cert and private key pem files, I was able to hit my server running with the following command:

一旦我创建了 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

If you want to force the client to provide a certificate (though it won't be checked), add the flag -Verify 1to the command above.

如果您想强制客户端提供证书(尽管它不会被检查),请将标志添加-Verify 1到上面的命令中。

To test the server at the command line you can use the following (note openssl is able to connect via 127.0.0.1):

要在命令行测试服务器,您可以使用以下命令(注意 openssl 能够通过 127.0.0.1 连接):

openssl s_client -connect 127.0.0.1:8888

And to add a client cert if the server requires it, add the flags -cert client-cert.pem -key client-key.pem

并在服务器需要时添加客户端证书,添加标志 -cert client-cert.pem -key client-key.pem

In my Android client I used the following code to connect (error checking removed):

在我的 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());

You should get a response code of 200, and can dissect the response from there.

您应该得到 200 的响应代码,并且可以从那里剖析响应。

Here's the code to load the client credentials, which is identical to loading the server key store but with a different resource filename and password:

这是加载客户端凭据的代码,它与加载服务器密钥库相同,但具有不同的资源文件名和密码:

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

回答by sidhanshu

I wasted my 6 - 7 hours fixed this problem and finally it worked with

我浪费了 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());
    }

And it worked !!!!!!

它奏效了!!!!!!