Java SSLContextImpl$TLS10Context引发的随机“对等体未经认证”异常

50

使用SSL连接到一个HAProxy服务器时,我会随机地出现连接失败。我已确认这些故障发生在JDK版本1.7.0_21和1.7.0_25上,而不是1.7.0_04或1.6.0_38。

异常情况是

 Exception in thread "main" javax.net.ssl.SSLPeerUnverifiedException: peer not authenticated
    at sun.security.ssl.SSLSessionImpl.getPeerCertificates(SSLSessionImpl.java:397)
    at SSLTest2.main(SSLTest2.java:52)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:601)
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:120)

只有在使用TLS SSL上下文时才会发生这些故障,而默认上下文则不会。以下代码在循环中运行一千次,在循环完成之前就会出现故障(大约2%的连接失败):

SSLContext sslcontext = SSLContext.getInstance("TLS");   
sslcontext.init(null, null, null);
SSLSocketFactory factory = sslcontext.getSocketFactory(); 
SSLSocket socket = (SSLSocket)factory.createSocket("myserver", 443);

//socket.startHandshake();
SSLSession session = socket.getSession();
session.getPeerCertificates();
socket.close();

然而,如果我以这种方式创建SSL上下文,我就不会在我提到的任何Java版本上遇到连接故障:

SSLSocketFactory factory = (SSLSocketFactory)SSLSocketFactory.getDefault();
第一种方法使用 SSLContextImpl$TLS10Context,而后一种方法使用SSLContextImpl$DefaultSSLContext。从代码上看,我没有发现任何会导致异常发生的差异。
我为什么会出现故障,并且使用getDefault()调用有什么优缺点?
注意:这些异常最初是使用Apache HttpClient(版本4)发现的。这段代码是重现与HttpClient一起看到的问题的最小子集。
这是我添加-Djavax.net.debug=ssl时看到的错误:
main, READ: TLSv1 Alert, length = 2
main, RECV TLSv1 ALERT:  fatal, bad_record_mac
%% Invalidated:  [Session-101, SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA]
main, called closeSocket()
main, handling exception: javax.net.ssl.SSLException: Received fatal alert:   bad_record_mac
main, IOException in getSession():  javax.net.ssl.SSLException: Received fatal alert: bad_record_mac

另外一个信息是,如果我在代理服务器上关闭Diffie-Hellman,则不会发生错误。


3
请使用-Djavax.net.debug=ssl,handshake重新运行,并提供一个失败运行的输出结果。 - user207421
再次以调试选项运行。已更新问题并提供此信息。有人有任何想法吗? - user2687486
你为什么更喜欢四行代码而不是一行? - user207421
4
看起来这是提到的Java版本中的一个错误。当我在Eclipse(subclipse)中从SVN服务器连接时,经常会出现这个错误。但是,使用最新的JVM 1.7.0_51,在此处执行上面的代码1000次不会触发任何异常。 - Robert
您可能想阅读https://dev59.com/-GTWa4cB1Zd3GeqPB0tW中的评论。 - David H. Bennett
显示剩余2条评论
2个回答

1
根据症状,我猜测这与浏览器使用 TLS false start有关,这是Google引入的客户端技巧,旨在 减少TLS中的来回传输

False Start在很大程度上由浏览器控制,并通过将官方SSL规范中描述的两个往返数据传递减少到一个往返数据传递来工作。它通过指示客户端在单个调度中发送Finished和第一个ApplicationData消息而不是将它们放入两个不同的包中并仅在从服务器获得确认后才发送第二个来实现这一点。

Google提出False Start作为官方标准,以使目前发现它太昂贵而无法提供的网站更容易接受SSL。通过缩短协商加密密钥和其他变量所需的握手时间,False Start旨在降低许多人认为使用该协议会带来的性能惩罚。

来自 Mozilla Firefox 中提出的相关问题:(重点在于我)

到目前为止,已知与 False Start 相容性问题存在的产品列表(据我所知)包括:F5、A10、Microsoft TMG、Cisco ASA、ServerIron ADX、ESET、NetNanny、Java 的 SSL 服务器实现的某些配置


连接在 OP 的情况和我的情况下都是由一个用 JAVA 编写的客户端和一个服务器(代理或 http)之间建立的。这里没有浏览器... - Lorenzo Dematté
@LorenzoDematté 啊,当然,你是对的。我简直不敢相信我错过了那个。 - JvR

-3

javax.net.ssl.SSLPeerUnverifiedException只会因为HTTP安全性而引起,您必须将连接配置为HTTPS,否则请按照以下代码操作。

            SSLContext ctx = SSLContext.getInstance("TLS");
    ctx.init(null, new TrustManager[]{tm}, null);
    SSLSocketFactory ssf = new SSLSocketFactory(ctx);
    ssf.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
    ClientConnectionManager ccm = client.getConnectionManager();
    SchemeRegistry sr = ccm.getSchemeRegistry();
    sr.register(new Scheme("https", ssf, 443));
    return new DefaultHttpClient(ccm, client.getParams());

请使用这个。我希望它能帮到你。

3
你只是通过使用ALLOW_ALL_HOSTNAME_VERIFIER引入了一个安全漏洞,这并没有帮助到你。 - Bruno
这完全不是因为HTTP安全的原因而出现。它与HTTPS无关,在TLS层发生。 - user207421

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接