Сбой рукопожатия Android SSLSocket в Android 6 и выше

Я написал сервер на основе 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 в папку необработанных ресурсов.

На 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


person John Ericksen    schedule 23.09.2017    source источник
comment
вы используете открытый SSL?   -  person chandrakant sharma    schedule 25.09.2017
comment
Пробовали ли вы установить альтернативное имя субъекта в сертификатах, как показано в заголовке stackoverflow.com/questions/8744607/   -  person sapensadler    schedule 25.09.2017
comment
@chandrakantsharma: Нет   -  person John Ericksen    schedule 25.09.2017
comment
@sapensadler: я могу попробовать, но не уверен, что это изменит ситуацию.   -  person John Ericksen    schedule 25.09.2017
comment
Единственная причина, по которой я это предлагаю, заключается в том, что chrome 58 больше не использует общее имя, а использует SAN для идентификации сертификатов. Я предполагаю, что, поскольку они оба являются Google, Android, возможно, последовал их примеру. support.google.com/chrome/a/answer/7391219?hl= ru Я могу ошибаться, но попробовать стоит. Это также могло бы объяснить, почему это работает в предыдущих версиях, но не после 6, если поведение изменилось.   -  person sapensadler    schedule 25.09.2017


Ответы (1)


Я думал, что это будет простое решение. Кажется, мне нужно было указать алгоритм ключа в самой первой команде keytool:

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

Это генерирует 2048-битный ключ RSA, который совместим с версиями Android ‹ 6 и >= 6.

person John Ericksen    schedule 26.09.2017