Apache HTTP服务器发生间歇性SSL握手错误

问题描述:

我发现Java客户端和浏览器通过Apache HTTP服务器访问站点时发生间歇性SSL握手错误。这种情况很少发生,但它每天都会破坏构建并影响用户体验。服务器配置为建立安全连接,但不需要来自客户端的证书。Apache HTTP服务器发生间歇性SSL握手错误

我已经在Java测试客户端和服务器上开启了SSL调试输出(见下文)。我所观察到的是,无论何时发生握手异常,服务器似乎都发送了客户端证书的请求。我不明白为什么它会这样做,为什么这只会偶尔发生。当它发送证书请求时,几乎总是发生错误,但是我也在成功的情况下捕获了请求(可能有1%的时间,99%的失败)。

客户端使用Java 8(1.8.0_31-b13),Apache HTTP服务器版本为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)客户端日志(severly略)

*** 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 6时,Java客户端从不显示错误。 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中存在一些已知问题”)。我将尽力至少检查在Java客户端中禁用TLSv1.1/2是否会导致问题消失。

是什么让服务器发送证书请求,导致客户端输出“*** CertificateRequest”?

在服务器上调用SSL_CTX_set_client_CA_list。它是一个可分辨名称的列表。另请参阅SSL_CTX_set_client_CA_list man page

服务器还需要拨打SSL_CTX_load_verify_locations在CA中建立信任。另见SSL_CTX_load_verify_locations man page


是设置 “SSLVerifyClient无” 不会阻止证书请求?

可能。从SSL_CTX_set_verify man page开始,您需要SSL_VERIFY_PEER并可能需要SSL_VERIFY_FAIL_IF_NO_PEER_CERT


什么就看未来有什么建议?

您应该显示相关的Java代码。

您应该声明服务器和客户端使用的OpenSSL版本。

+0

它在CertificateRequest中发送给客户端。没有理由发送它,否则。 – EJP

+0

@EJP - 啊,你说得对。对于那个很抱歉。 – jww

+0

增加了Java示例代码,OpenSSL不在那里使用。我无法访问实际的机器。系统管理员告诉我“安装了OpenSSL 1.0.1h 2014年6月5日,扩展3.0.16.0”。我假设Apache使用共享库。 – JanDasWiesel

是什么让服务器发送证书请求,导致客户端输出“*** CertificateRequest”?

某处你有SSLVerifyClient设置为“可选”或更高,可能在.htaccess文件,这就是为什么你不能在配置文件中找到它。这将覆盖全局设置的“SSLVerifyClient none”。

+0

系统管理员告诉我周围没有.htaccess文件。 “SSLVerifyClient none”仅在部分配置。将其移入httpd.conf(全局) - 看起来很有前途,但我明天才会知道更多。仍然困惑我为什么这个问题应该偶尔发生。 – JanDasWiesel

+0

某处或其他地方的配置比'无'高。否则,证书请求将不会被发送。对此没有任何疑问。 – EJP

+0

非常感谢您的帮助!由于我无法控制的情况,进展缓慢。我要求配置mod_info,以便配置可以在运行时显示;也许它会显示一个意外的SSLVerifyClient设置。我通过启动新的JVM并反复访问相同的URL来生成零星的证书请求。这些调用的一小部分显示证书请求并失败。任何想法为什么这只会偶尔发生?据我所知,证书请求是服务器SSL状态的一部分,为什么它会对同一请求做出不同反应? – JanDasWiesel

经过漫长而乏味的调试和分析,我想报告调查结果。该问题与客户端发送的TLS握手的第一个ClientHello消息有关。

Java 7和Java 8客户端(也可能是所有现代浏览器)都使用“服务器名称指示”(SNI)扩展名。只有使用此扩展时才会出现此问题。 Java 6客户端不会发送它,并且问题从未发生过。 TLSv1和TLSv1.2出现问题 - 我们从未观察到TLSv1.1请求,因此我无法发表评论。

对于Java 7和8客户端来说,我们可以设置-Djsse.enableSNIExtension=false来阻止客户端发送SNI扩展。

我们将尝试更改httpd.conf配置(以及包含的文件)以查看我们是否可以使服务器正常运行。如果我们成功,我可能会发布信息,例如,如果有服务器端解决方法。上面提到的Java开关是一种客户端解决方法,它至少足以满足我们每晚的构建。

+0

我在jre 8的tomcat 8.5上有这个确切的问题。不幸的是,禁用SNI并不能解决它(至少在我的情况下)。 – spy