Периодическая ошибка рукопожатия SSL с HTTP-сервером Apache

Я вижу периодически возникающие ошибки рукопожатия SSL, когда клиенты Java и браузеры обращаются к сайту через HTTP-сервер Apache. Это случается редко, но каждый день ломает сборки и влияет на пользовательский опыт. Сервер настроен на установку безопасного соединения, но не требует сертификат от клиента.

Я включил вывод отладки SSL как на тестовом клиенте Java, так и на сервере (см. ниже). Что я наблюдаю, так это то, что всякий раз, когда возникает исключение рукопожатия, сервер, похоже, отправляет запрос на сертификат клиента. Я не понимаю, почему это когда-либо могло произойти, и почему это происходит только время от времени. Когда он отправляет запрос на сертификат, ошибка возникает почти всегда, однако я также перехватывал запросы, в которых это удалось (возможно, в 1% случаев, с ошибкой 99%).

Клиент использует Java 8 (1.8.0_31-b13), версия HTTP-сервера Apache — 2.2.19.

Вот фрагменты из журналов:

1) Выдержка из конфигурации Apache

SSLProtocol ALL -SSLv2 -SSLv3
SSLCertificateFile <path-to-pem1>
SSLCertificateChainFile <path-to-pem2>
SSLCACertificateFile <path-to-pem3>
SSLVerifyDepth 10
SSLVerifyClient none

Файлы .pem доступны для чтения всем.

2) Журнал клиента (сильно сокращенный)

*** ClientHello, TLSv1.2
***
*** ServerHello, TLSv1
***
*** Certificate chain
***
*** Diffie-Hellman ServerKeyExchange
*** CertificateRequest
*** ServerHelloDone
*** Certificate chain
***
*** ClientKeyExchange, DH
*** Finished
***
...
main, WRITE: TLSv1 Handshake, length = 48
main, waiting for close_notify or alert: state 1
main, Exception while waiting for close java.net.SocketException: Software caused connection abort: recv failed
main, handling exception: java.net.SocketException: Software caused connection abort: recv failed
%% Invalidated:  [Session-1, TLS_DHE_RSA_WITH_AES_128_CBC_SHA]
main, SEND TLSv1 ALERT:  fatal, description = unexpected_message
...
main, WRITE: TLSv1 Alert, length = 32
main, Exception sending alert: java.net.SocketException: Software caused connection abort: socket write error
main, called closeSocket()

3) Журнал сервера (сокращенно)

[info] [client x.x.x.x] Connection to child 1 established (server XXX:443)
[info] Seeding PRNG with 0 bytes of entropy
[debug] ssl_engine_kernel.c(1866): OpenSSL: Handshake: start
[debug] ssl_engine_kernel.c(1874): OpenSSL: Loop: before/accept initialization
[debug] ssl_engine_io.c(1897): OpenSSL: read 11/11 bytes from BIO#82f6820 [mem: 82c5290] (BIO dump follows)
[debug] ssl_engine_io.c(1830): +-------------------------------------------------------------------------+
[debug] ssl_engine_io.c(1869): | 0000: XX XX XX XX XX XX XX XX-XX XX XX                 ...........      |
[debug] ssl_engine_io.c(1875): +-------------------------------------------------------------------------+
[debug] ssl_engine_io.c(1897): OpenSSL: read 245/245 bytes from BIO#82f6820 [mem: 82c529b] (BIO dump follows)
[debug] ssl_engine_io.c(1830): +-------------------------------------------------------------------------+
[debug] ssl_engine_io.c(1869): | 0000: XX XX XX XX XX XX XX XX-XX XX XX XX XX XX XX XX  ................ |
...
[debug] ssl_engine_io.c(1869): | 00f0: XX XX XX XX XX                                   .....            |
[debug] ssl_engine_io.c(1875): +-------------------------------------------------------------------------+
[debug] ssl_engine_kernel.c(1987): [client x.x.x.x] SSL virtual host for servername XXX found
[debug] ssl_engine_kernel.c(1874): OpenSSL: Loop: SSLv3 read client hello A
[debug] ssl_engine_kernel.c(1874): OpenSSL: Loop: SSLv3 write server hello A
[debug] ssl_engine_kernel.c(1874): OpenSSL: Loop: SSLv3 write certificate A
[debug] ssl_engine_kernel.c(1274): [client x.x.x.x] handing out temporary 1024 bit DH key
[debug] ssl_engine_kernel.c(1874): OpenSSL: Loop: SSLv3 write key exchange A
[debug] ssl_engine_kernel.c(1874): OpenSSL: Loop: SSLv3 write certificate request A
[debug] ssl_engine_kernel.c(1874): OpenSSL: Loop: SSLv3 flush data
[debug] ssl_engine_io.c(1897): OpenSSL: read 5/5 bytes from BIO#82f6820 [mem: 82c5290] (BIO dump follows)
[debug] ssl_engine_io.c(1830): +-------------------------------------------------------------------------+
[debug] ssl_engine_io.c(1869): | 0000: 16 03 01 00 8d                                   .....            |
[debug] ssl_engine_io.c(1875): +-------------------------------------------------------------------------+
[debug] ssl_engine_io.c(1897): OpenSSL: read 141/141 bytes from BIO#82f6820 [mem: 82c5295] (BIO dump follows)
[debug] ssl_engine_io.c(1830): +-------------------------------------------------------------------------+
[debug] ssl_engine_io.c(1869): | 0000: XX XX XX XX XX XX XX XX-XX XX XX XX XX XX XX XX  ................ |
...
[debug] ssl_engine_io.c(1869): | 0080: XX XX XX XX XX XX XX XX-XX XX XX XX XX           .............    |
[debug] ssl_engine_io.c(1875): +-------------------------------------------------------------------------+
[debug] ssl_engine_kernel.c(1884): OpenSSL: Write: SSLv3 read client certificate B
[debug] ssl_engine_kernel.c(1903): OpenSSL: Exit: error in SSLv3 read client certificate B
[debug] ssl_engine_kernel.c(1903): OpenSSL: Exit: error in SSLv3 read client certificate B
[info] [client x.x.x.x] SSL library error 1 in handshake (server XXX:443)
[info] SSL Library Error: 336105671 error:140890C7:SSL routines:SSL3_GET_CLIENT_CERTIFICATE:peer did not return a certificate No CAs known to server for verification?
[info] [client x.x.x.x] Connection closed to child 1 with abortive shutdown (server XXX:443)

В хороших случаях мы постоянно не видим «*** CertificateRequest» на клиенте и выводим на сервере вот так.

[debug] ssl_engine_kernel.c(1987): [client x.x.x.x] SSL virtual host for servername XXX found
[debug] ssl_engine_kernel.c(1874): OpenSSL: Loop: SSLv3 read client hello A
[debug] ssl_engine_kernel.c(1874): OpenSSL: Loop: SSLv3 write server hello A
[debug] ssl_engine_kernel.c(1874): OpenSSL: Loop: SSLv3 write certificate A
[debug] ssl_engine_kernel.c(1274): [client x.x.x.x] handing out temporary 1024 bit DH key
[debug] ssl_engine_kernel.c(1874): OpenSSL: Loop: SSLv3 write key exchange A
[debug] ssl_engine_kernel.c(1874): OpenSSL: Loop: SSLv3 write server done A
[debug] ssl_engine_kernel.c(1874): OpenSSL: Loop: SSLv3 flush data
[debug] ssl_engine_io.c(1897): OpenSSL: read 5/5 bytes from BIO#82d82d8 [mem: 82c8790] (BIO dump follows)
[debug] ssl_engine_io.c(1830): +-------------------------------------------------------------------------+
[debug] ssl_engine_io.c(1869): | 0000: 16 03 01 00 86                                   .....            |
[debug] ssl_engine_io.c(1875): +-------------------------------------------------------------------------+
[debug] ssl_engine_io.c(1897): OpenSSL: read 134/134 bytes from BIO#82d82d8 [mem: 82c8795] (BIO dump follows)
[debug] ssl_engine_io.c(1830): +-------------------------------------------------------------------------+
[debug] ssl_engine_io.c(1869): | 0000: XX XX XX XX XX XX XX XX-XX XX XX XX XX XX XX XX  ................ |
...
[debug] ssl_engine_io.c(1869): | 0080: XX XX XX XX XX XX                                ......           |
[debug] ssl_engine_io.c(1875): +-------------------------------------------------------------------------+
[debug] ssl_engine_kernel.c(1874): OpenSSL: Loop: SSLv3 read client key exchange A

Схема кода клиента Java:

URL url = new URL("https://...");
HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
connection.setRequestProperty("Authorization", "Basic " + DatatypeConverter.printBase64Binary((user + ":" + pass).getBytes(Charset.forName("UTF-8"))));
connection.setReadTimeout(60*1000);
connection.setUseCaches(false);
connection.connect();

Бежать с

java -Djavax.net.ssl.trustStore=... -Djavax.net.ssl.trustStoreType=jks -Djavax.net.ssl.trustStorePassword=... -Djavax.net.debug=all ...

Вопросы:

  1. Что заставляет сервер отправлять запрос сертификата, вызывая вывод «*** CertificateRequest» на клиенте?
  2. Параметр «SSLVerifyClient none» не препятствует запросу сертификата?
  3. Любые предложения о том, что посмотреть дальше?

Обновление: я заметил, что клиент Java никогда не показывает ошибку при использовании Java 6. Проблема возникает с Java 7 и 8, а также в Chrome, Internet Explorer и Firefox. Похоже, это указывает на проблему с TLSv1.1 или TLSv1.2 (см. также https://serverfault.com/questions/513961/how-to-disable-tls-1-1-1-2).-in-apache "OpenSSL v1.0.1 имеет некоторые известные проблемы с TLSv1.2"). Я попытаюсь хотя бы проверить, устранит ли проблема отключение TLSv1.1/2 в Java-клиенте.


person JanDasWiesel    schedule 28.06.2016    source источник


Ответы (3)


Что заставляет сервер отправлять запрос сертификата, вызывая вывод «*** CertificateRequest» на клиенте?

Где-то у вас SSLVerifyClient установлено значение «необязательно» или выше, возможно, в файле .htaccess, поэтому вы не можете найти его в файлах конфигурации. Это отменяет глобальный набор SSLVerifyClient none.

person user207421    schedule 29.06.2016
comment
Сисадмин говорит мне, что файлов .htaccess нет. Однако SSLVerifyClient none был настроен только в разделе ‹VirtualHost›. Переместил его в httpd.conf (глобально) - выглядит многообещающе, но я узнаю больше только завтра. Меня все еще озадачивает, почему проблема возникает только время от времени. - person JanDasWiesel; 29.06.2016
comment
Где-то он настроен выше, чем «нет». В противном случае CertificateRequest не будет отправлен. В этом нет места сомнениям. - person user207421; 02.07.2016
comment
Ваша помощь очень ценится! По независящим от меня обстоятельствам прогресс мучительно медленный. Я попросил настроить mod_info, чтобы конфигурацию можно было отображать во время выполнения; возможно, он покажет неожиданную настройку SSLVerifyClient. Я произвожу спорадические запросы сертификатов, запуская новую JVM и снова и снова обращаясь к одному и тому же URL-адресу. Часть этих вызовов показывает запрос сертификата и терпит неудачу. Любая идея, почему это происходит только время от времени? Насколько я понимаю, запрос сертификата является частью состояния SSL сервера, почему он по-разному реагирует на один и тот же запрос? - person JanDasWiesel; 04.07.2016
comment
Конфигурация не показывает, что для SSLVerifClient установлено что-либо иное, кроме none. Я думаю, что мы видим проблему в mod_ssl или в реализации openssl. - person JanDasWiesel; 06.07.2016

Что заставляет сервер отправлять запрос сертификата, вызывая вывод «*** CertificateRequest» на клиенте?

Вызов SSL_CTX_set_client_CA_list на сервере. Это список выдающихся имен. Также см. SSL_CTX_set_client_CA_list справочную страницу.

Серверу также потребуется вызвать SSL_CTX_load_verify_locations, чтобы установить доверие в центрах сертификации. См. также SSL_CTX_load_verify_locations справочную страницу.


Параметр «SSLVerifyClient none» не препятствует запросу сертификата?

Возможно. На SSL_CTX_set_verify справочной странице вам потребуется SSL_VERIFY_PEER и, возможно, SSL_VERIFY_FAIL_IF_NO_PEER_CERT.


Любые предложения о том, что посмотреть дальше?

Вы должны показать свой соответствующий код Java.

Вы должны указать версию OpenSSL, используемую сервером и клиентом.

person jww    schedule 28.06.2016
comment
Он отправляется клиенту в CertificateRequest. В противном случае нет причин отправлять его. - person user207421; 29.06.2016
comment
Добавлен пример кода Java, OpenSSL там не используется. У меня нет доступа к реальной машине. Системный администратор сообщает мне, что OpenSSL 1.0.1h 5 июня 2014 г., установлены расширения ‹3rdpartycompanyname› 3.0.16.0. Я предполагаю, что Apache использует общие библиотеки. - person JanDasWiesel; 29.06.2016

После долгой и утомительной отладки и анализа я хотел бы сообщить о результатах. Проблема связана с первым сообщением ClientHello рукопожатия TLS, отправленным клиентом.

Клиенты Java 7 и Java 8 (и, вероятно, все современные браузеры) используют расширение «Индикация имени сервера» (SNI). Проблема возникает только при использовании этого расширения. Клиенты Java 6 не отправляют его, и проблема никогда не возникала. Проблема возникла с TLSv1 и TLSv1.2 — мы никогда не наблюдали запросы TLSv1.1, поэтому я не могу комментировать.

Для клиентов Java 7 и 8, чтобы предотвратить проблему, мы можем установить -Djsse.enableSNIExtension=false, который остановит клиент, отправляющий расширение SNI.

Мы попробуем изменить конфигурацию httpd.conf (и включенные файлы), чтобы увидеть, сможем ли мы заставить сервер работать хорошо. Я могу опубликовать информацию, если мы добьемся успеха, то есть если для этого есть обходной путь на стороне сервера. Вышеупомянутый переключатель Java — это обходной путь на стороне клиента, который, по крайней мере, достаточно хорош для наших ночных сборок.

person JanDasWiesel    schedule 06.07.2016
comment
у меня именно эта проблема на tomcat 8.5 с jre 8. К сожалению, отключение SNI не решает ее (по крайней мере, в моем случае). - person spy; 09.03.2018