编辑我意识到这个答案很久以前就被接受了,并且也被投票了 3 次,但它(至少部分)不正确,所以这里有更多关于这个例外的信息。造成的不便,深表歉意。
javax.net.ssl.SSLPeerUnverifiedException:对等体未通过身份验证
这通常是远程服务器根本没有发送证书时抛出的异常。但是,有一个边缘情况,在使用 Apache HTTP Client 时会遇到,因为它在这个版本中的实现方式,也因为sun.security. ssl.SSLSocketImpl.getSession()
实现方式。
使用 Apache HTTP Client 时,远程证书不受信任时也会抛出此异常,通常会抛出“ sun.security.validator.ValidatorException: PKIX path building failed
”。
SSLSession
发生这种情况的原因是因为 Apache HTTP 客户端在执行任何其他操作之前尝试获取和对等证书。
提醒一下,有3 种使用 启动握手的方法SSLSocket
:
- 调用显式开始握手的 startHandshake,或
- 任何在此套接字上读取或写入应用程序数据的尝试都会导致隐式握手,或者
- 如果当前没有有效会话,则调用 getSession 会尝试建立会话,并完成隐式握手。
以下是 3 个示例,均针对具有不受信任证书的主机(使用javax.net.ssl.SSLSocketFactory
,而不是 Apache 的)。
示例 1:
SSLSocketFactory ssf = (SSLSocketFactory) sslContext.getSocketFactory();
SSLSocket sslSocket = (SSLSocket) ssf.createSocket("untrusted.host.example",
443);
sslSocket.startHandshake();
这会抛出“ javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed
”(如预期的那样)。
示例 2:
SSLSocketFactory ssf = (SSLSocketFactory) sslContext.getSocketFactory();
SSLSocket sslSocket = (SSLSocket) ssf.createSocket("untrusted.host.example",
443);
sslSocket.getInputStream().read();
这也会抛出“ javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed
”(如预期的那样)。
示例 3:
SSLSocketFactory ssf = (SSLSocketFactory) sslContext.getSocketFactory();
SSLSocket sslSocket = (SSLSocket) ssf.createSocket("untrusted.host.example",
443);
SSLSession sslSession = sslSocket.getSession();
sslSession.getPeerCertificates();
但是,这会抛出javax.net.ssl.SSLPeerUnverifiedException: peer not authenticated
.
这是Apache HTTP 客户端AbstractVerifier
org.apache.http.conn.ssl.SSLSocketFactory
在 4.2.1 版本中使用的逻辑。更高版本根据问题 HTTPCLIENT-1346中的报告对进行显式调用startHandshake()
。
这最终似乎来自 的实现sun.security. ssl.SSLSocketImpl.getSession()
,它捕获IOException
了调用startHandshake(false)
(内部方法)时抛出的潜在 s,而不是进一步抛出它。这可能是一个错误,尽管这不会产生巨大的安全影响,因为SSLSocket
无论如何它仍然会被关闭。
示例 4:
SSLSocketFactory ssf = (SSLSocketFactory) sslContext.getSocketFactory();
SSLSocket sslSocket = (SSLSocket) ssf.createSocket("untrusted.host.example",
443);
SSLSession sslSession = sslSocket.getSession();
// sslSession.getPeerCertificates();
sslSocket.getInputStream().read();
javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed
值得庆幸的是,每当您实际尝试使用它时,它仍然会抛出“ ” SSLSocket
(通过获取会话而不获取对等证书没有漏洞)。
如何解决这个问题
与不受信任的证书的任何其他问题一样,问题在于确保您使用的信任存储包含必要的信任锚(即颁发您要验证的链的 CA 证书,或者可能是实际的服务器特殊情况的证明)。
要解决此问题,您应该将 CA 证书(或者可能是服务器证书本身)导入您的信任库。你可以这样做:
- 在您的 JRE 信任存储中,通常是
cacerts
文件(这不一定是最好的,因为这会影响使用该 JRE 的所有应用程序),
- 在您的信任库的本地副本中(您可以使用
-Djavax.net.ssl.trustStore=...
选项进行配置),
- 通过为该连接创建一个特定
SSLContext
的(如本答案中所述)。(有些人建议使用什么都不做的信任管理器,但这会使您的连接容易受到 MITM 攻击。)
初步答案
javax.net.ssl.SSLPeerUnverifiedException:对等体未通过身份验证
这与信任证书或您必须创建自定义无关SSLContext
:这是因为服务器根本没有发送任何证书。
此服务器明显未配置为正确支持 TLS。这失败了(您不会获得远程证书):
openssl s_client -tls1 -showcerts -connect appserver.gtportalbase.com:443
但是,SSLv3 似乎可以工作:
openssl s_client -ssl3 -showcerts -connect appserver.gtportalbase.com:443
如果您知道谁在运行此服务器,则值得与他们联系以解决此问题。服务器至少现在应该真正支持 TLSv1。
同时,解决此问题的一种方法是创建自己的org.apache.http.conn.ssl.SSLSocketFactory
并将其用于与 Apache Http 客户端的连接。
这个工厂需要SSLSocket
像往常一样创建一个,sslSocket.setEnabledProtocols(new String[] {"SSLv3"});
在返回该套接字之前使用,以禁用 TLS,否则默认情况下会启用。