Я написал сервер на базе 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
Кроме того, я делаю это совместимым с android, создавая доверительный магазин, используя bouncycastle:
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 в папку raw resource.
В 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, чтобы помочь диагностировать эту проблему здесь: