Android 6及以上版本中Android SSLSocket握手失败

14

我基于Java的SSLServerSocket编写了一个服务器,它接受连接并通过自定义二进制协议与Android应用程序通信:

ServerSocket serverSocket = SSLServerSocketFactory.getDefault().createServerSocket(1234);
while (true) {
    Socket socket = serverSocket.accept();
    ...
}

我使用以下参数运行服务器:

-Djavax.net.ssl.keyStore=keystore.jks
-Djavax.net.ssl.keyStorePassword=<PASSWORD>

证书是使用以下教程生成的,该教程构建了一组公钥和私钥:http://judebert.com/progress/archives/425-Using-SSL-in-Java,-Part-2.html

keytool -genkeypair -keystore keystore.jks -alias keyname
keytool -export -alias keyname -file keyname.crt -keystore keystore.jks 
keytool -importcert -file keyname.crt -keystore truststore.jks

同时,我通过使用bouncycastle构建一个truststore,使其与安卓系统兼容:

keytool -importkeystore -srckeystore truststore.jks -srcstoretype JKS -srcstorepass <PASSWORD> -destkeystore truststore.bks -deststoretype BKS -deststorepass <PASSWORD> -provider org.bouncycastle.jce.provider.BouncyCastleProvider -providerpath bcprov-ext-jdk15on-1.58.jar

在此处下载bouncycastle提供程序:https://www.bouncycastle.org/latest_releases.html

然后将生成的truststore.bks移动到原始资源文件夹中。

在Android上,我使用以下代码构建SSLSocketFactory,它允许我导入生成的bouncycastle证书,从而使我被服务器认证:

KeyStore trustStore = KeyStore.getInstance("BKS");
InputStream trustStoreStream = context.getResources().openRawResource(R.raw.truststore);
trustStore.load(trustStoreStream, "<PASSWORD>".toCharArray());

TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(trustStore);

SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, trustManagerFactory.getTrustManagers(), null);

Socket socket = sslContext.getSocketFactory().createSocket("ip", 1234);
... use socket

这在 Android 6 版本以下的设备上运行良好。我的问题出现在版本 6 及更高版本中,当尝试使用套接字时会抛出异常:

 Shutting down connection Socket[address=/ip,port=1234,localPort=321321] due to exception Handshake failed
 javax.net.ssl.SSLHandshakeException: Handshake failed
    at com.android.org.conscrypt.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.java:429)
    at com.example.Client.connect(Client.java:97)
    at com.example.Client.start(Client.java:60)
    at com.example.BackendServiceFactory$2.call(BackendServiceFactory.java:136)
    at com.example.BackendServiceFactory$2.call(BackendServiceFactory.java:130)
    ...
 Caused by: javax.net.ssl.SSLProtocolException: SSL handshake terminated: ssl=0xe69ec900: Failure in SSL library, usually a protocol error
 error:10000410:SSL routines:OPENSSL_internal:SSLV3_ALERT_HANDSHAKE_FAILURE (external/boringssl/src/ssl/s3_pkt.c:641 0xe2d10880:0x00000001)
 error:1000009a:SSL routines:OPENSSL_internal:HANDSHAKE_FAILURE_ON_CLIENT_HELLO (external/boringssl/src/ssl/s3_clnt.c:800 0xe6ea5af3:0x00000000)
    at com.android.org.conscrypt.NativeCrypto.SSL_do_handshake(Native Method)
    at com.android.org.conscrypt.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.java:357)
    ... 24 more

我不确定这里发生了什么。处理客户端证书的过程中似乎出现了错误,这可能是密码套件不匹配引起的吗?

我已经准备了一个包括Java服务器、Java客户端和Android客户端的最小示例来帮助诊断此问题:

https://github.com/johncarl81/androidCA


你是否在使用OpenSSL? - chandrakant sharma
你尝试过像 https://dev59.com/7moy5IYBdhLWcg3wJKvR 上所示的设置证书主题备用名称吗? - sapensadler
@chandrakantsharma:不。 - John Ericksen
@sapensadler:我可以尝试一下,但我不确定这会有什么区别。 - John Ericksen
我建议这样做的唯一原因是,Chrome 58不再使用通用名称(Common Name),而是使用SAN来识别证书。我猜想,由于它们都是谷歌公司,Android可能也采用了同样的做法。https://support.google.com/chrome/a/answer/7391219?hl=en 我可能完全错了,但值得一试。如果行为已经改变,那么这也可以解释为什么它在之前的版本上可以工作,但在6之后不能工作。 - sapensadler
1个回答

3

我想这应该是一个简单的修复问题。似乎我需要在第一个keytool命令中指定密钥算法:

keytool -genkeypair -keystore keystore.jks -alias keyname -keyalg RSA

这将生成一个2048位的RSA密钥,与Android版本 < 6 和 >= 6 兼容。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接