#java #ssl #bouncycastle #tls1.2
#java #ssl #bouncycastle #tls1.2
Вопрос:
Я столкнулся с проблемой при разработке HTTP-клиента с использованием библиотек BouncyCastle. Целевые версии (но ошибка также воспроизводится в Java 1.8.0_91 с той же версией BouncyCastle.)
- JRE 1.6.0_45-b06
- BouncyCastle jdk15to18 167 (bcprov-jdk15to18-167.jar , bcpkix-jdk15to18-167.jar , bctls-jdk15to18-167.jar )
Код HttpClient:
String strURL = "https://www.<WEBSITE>.com";
// CODE to set default TrustStore, KeyStore to be used
// setup BC as SecurityProvider and SSLSocketFactoryProvider
/*
Security.insertProviderAt(new BouncyCastleProvider(), 1);
Security.insertProviderAt(new BouncyCastleJsseProvider(), 2);
Security.setProperty("ssl.KeyManagerFactory.algorithm", "PKIX");
Security.setProperty("ssl.TrustManagerFactory.algorithm", "PKIX");
Security.setProperty("ssl.SocketFactory.provider", "org.bouncycastle.jsse.provider.SSLSocketFactoryImpl");
System.setProperty("jdk.tls.trustNameService", "true");
*/
URL url = new URL( strURL );
HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
conn.setDoOutput(true);
conn.setRequestMethod("GET");
InputStream is = null;
if (conn.getResponseCode() == HttpURLConnection.HTTP_OK) {
System.out.println("OK");
is = conn.getInputStream();
} else if (conn.getResponseCode() == HttpURLConnection.HTTP_INTERNAL_ERROR) {
System.out.println("ERROR");
is = conn.getErrorStream();
} else {
System.out.println( conn.getResponseCode() );
System.out.println( conn.getResponseMessage() );
}
if (is != null) {
System.out.println( readFullyAsString(is, "UTF-8") );
}
conn.disconnect();
Доступ к данным на веб-сайте с TLS 1.2 без client_auth работает нормально (есть некоторые проблемы с определенными сайтами, которые возвращают handshake_failure (40), но, к счастью, не в нашем случае). Но когда требуется client_auth, код завершается ошибкой со следующей (немного загадочной) ошибкой:
nov. 27, 2020 5:23:10 PM org.bouncycastle.jsse.provider.ProvTlsClient notifyAlertRaised
WARNING: Client raised fatal(2) internal_error(80) alert: Failed to read record
java.net.SocketException: Connection reset
at java.net.SocketInputStream.read(Unknown Source)
at java.net.SocketInputStream.read(Unknown Source)
at org.bouncycastle.tls.RecordStream$Record.fillTo(RecordStream.java:429)
at org.bouncycastle.tls.RecordStream$Record.readHeader(RecordStream.java:468)
at org.bouncycastle.tls.RecordStream.readRecord(RecordStream.java:201)
at org.bouncycastle.tls.TlsProtocol.safeReadRecord(TlsProtocol.java:768)
at org.bouncycastle.tls.TlsProtocol.readApplicationData(TlsProtocol.java:731)
at org.bouncycastle.jsse.provider.ProvSSLSocketDirect$AppDataInput.read(ProvSSLSocketDirect.java:603)
at java.io.BufferedInputStream.fill(Unknown Source)
at java.io.BufferedInputStream.read1(Unknown Source)
at java.io.BufferedInputStream.read(Unknown Source)
at sun.net.www.http.HttpClient.parseHTTPHeader(Unknown Source)
at sun.net.www.http.HttpClient.parseHTTP(Unknown Source)
at sun.net.www.protocol.http.HttpURLConnection.getInputStream0(Unknown Source)
at sun.net.www.protocol.http.HttpURLConnection.getInputStream(Unknown Source)
at java.net.HttpURLConnection.getResponseCode(Unknown Source)
at sun.net.www.protocol.https.HttpsURLConnectionImpl.getResponseCode(Unknown Source)
at HttpsClient.main(HttpsClient.java:109)
nov. 27, 2020 5:23:10 PM org.bouncycastle.jsse.provider.ProvTlsClient notifyAlertRaised
WARNING: Client raised fatal(2) internal_error(80) alert: Failed to read record
java.net.SocketException: Connection reset
at java.net.SocketInputStream.read(Unknown Source)
at java.net.SocketInputStream.read(Unknown Source)
at org.bouncycastle.tls.RecordStream$Record.fillTo(RecordStream.java:429)
at org.bouncycastle.tls.RecordStream$Record.readHeader(RecordStream.java:468)
at org.bouncycastle.tls.RecordStream.readRecord(RecordStream.java:201)
at org.bouncycastle.tls.TlsProtocol.safeReadRecord(TlsProtocol.java:768)
at org.bouncycastle.tls.TlsProtocol.readApplicationData(TlsProtocol.java:731)
at org.bouncycastle.jsse.provider.ProvSSLSocketDirect$AppDataInput.read(ProvSSLSocketDirect.java:603)
at java.io.BufferedInputStream.fill(Unknown Source)
at java.io.BufferedInputStream.read1(Unknown Source)
at java.io.BufferedInputStream.read(Unknown Source)
at sun.net.www.http.HttpClient.parseHTTPHeader(Unknown Source)
at sun.net.www.http.HttpClient.parseHTTP(Unknown Source)
at sun.net.www.http.HttpClient.parseHTTP(Unknown Source)
at sun.net.www.protocol.http.HttpURLConnection.getInputStream0(Unknown Source)
at sun.net.www.protocol.http.HttpURLConnection.getInputStream(Unknown Source)
at java.net.HttpURLConnection.getResponseCode(Unknown Source)
at sun.net.www.protocol.https.HttpsURLConnectionImpl.getResponseCode(Unknown Source)
at HttpsClient.main(HttpsClient.java:109)
java.net.SocketException: Connection reset
at java.net.SocketInputStream.read(Unknown Source)
at java.net.SocketInputStream.read(Unknown Source)
at org.bouncycastle.tls.RecordStream$Record.fillTo(RecordStream.java:429)
at org.bouncycastle.tls.RecordStream$Record.readHeader(RecordStream.java:468)
at org.bouncycastle.tls.RecordStream.readRecord(RecordStream.java:201)
at org.bouncycastle.tls.TlsProtocol.safeReadRecord(TlsProtocol.java:768)
at org.bouncycastle.tls.TlsProtocol.readApplicationData(TlsProtocol.java:731)
at org.bouncycastle.jsse.provider.ProvSSLSocketDirect$AppDataInput.read(ProvSSLSocketDirect.java:603)
at java.io.BufferedInputStream.fill(Unknown Source)
at java.io.BufferedInputStream.read1(Unknown Source)
at java.io.BufferedInputStream.read(Unknown Source)
at sun.net.www.http.HttpClient.parseHTTPHeader(Unknown Source)
at sun.net.www.http.HttpClient.parseHTTP(Unknown Source)
at sun.net.www.http.HttpClient.parseHTTP(Unknown Source)
at sun.net.www.protocol.http.HttpURLConnection.getInputStream0(Unknown Source)
at sun.net.www.protocol.http.HttpURLConnection.getInputStream(Unknown Source)
at java.net.HttpURLConnection.getResponseCode(Unknown Source)
at sun.net.www.protocol.https.HttpsURLConnectionImpl.getResponseCode(Unknown Source)
at HttpsClient.main(HttpsClient.java:109)
Как и ожидалось, код отлично работает в Java 1.8 без BC в качестве поставщика безопасности… но в Java 1.6 BC необходим для поддержки TLS 1.2.
Я просмотрел пакеты с помощью Wireshark, но у меня недостаточно знаний, чтобы проанализировать, что происходит (сбой) с операцией установления связи SSL с сервером.
Чего мне здесь не хватает? Или это просто известное ограничение BC? Спасибо за любые предложения.
Комментарии:
1. 80 — это internal_error и может быть почти чем угодно, включая что-то совершенно законное. Лучший подход — выяснить из журналов сервера, в чем, по его мнению, проблема, возможно, после установки «подробного» или «отладочного» ведения журнала или аналогичного. Следующий шаг — выяснить, какую реализацию SSL / TLS и данные использует сервер, и реплицировать их в контролируемой среде. Последним было бы получить подробные сведения о TLS и просмотреть сообщения протокола Bouncy по сравнению со стандартным-JSSE и, возможно, другими рабочими клиентами, чтобы увидеть, что отличается, даже если это не незаконно или неправильно.
2. @dave_thompson_085 спасибо за ваши идеи: мы попытаемся воспроизвести его в нашей контролируемой среде, поскольку у нас нет доступа к серверу. Я опубликую наши результаты (и, надеюсь, решение)
Ответ №1:
После обширных исследований и тестирования (мы настроили нашу собственную среду разработки, которая реплицировала конечную точку, к которой мы пытались подключиться с помощью сертификата клиента), мы нашли решение (опубликовано ниже), которое работает для нас в нашей среде разработки, но не может быть настроено на конечной точке. Вот почему мы перешли на plan B — TLS termination proxy, и в нашем случае это сработало как чудо, когда у нас не было контроля над серверной средой.
Решение для IIS
В нашем случае веб-служба, требующая авторизации клиента, была запущена в Microsoft IIS. Как указано «К сожалению, наши библиотеки TLS не поддерживают повторное согласование (и мы не планируем его добавлять, хотя есть соответствующие функции TLS 1.3, которые мы будем)». в этой открытой проблеме (https://github.com/bcgit/bc-java/issues/593 ). После прочтения документации IIS и некоторого тестирования мы успешно настроили среду таким образом, чтобы сеансы TLS могли устанавливаться с помощью библиотеки BC TLS:
Шаг 1:
- Создайте сайт IIS с настройками по умолчанию
- Запустите IISCrypto с шаблоном «Лучшие практики»:
Шаг 2:
- Включить SSLAlwaysNegoClientCert
Сохраните следующий текст в файл с именем «Enable_SSL_Renegotiate.js »
var vdirObj=GetObject("IIS://localhost/W3svc/1");
// replace 1 on this line with the number of the web site you wish to configure
WScript.Echo("Value of SSLAlwaysNegoClientCert Before: " vdirObj.SSLAlwaysNegoClientCert);
vdirObj.Put("SSLAlwaysNegoClientCert", true);
vdirObj.SetInfo();
WScript.Echo("Value of SSLAlwaysNegoClientCert After: " vdirObj.SSLAlwaysNegoClientCert);
Выполните следующую команду из командной строки с повышенными правами / администратора:
cscript.exe enable_ssl_renegotiate.js
Я опубликовал тот же ответ на BouncyCastle Github — # 847