编辑 我意识到这个答案很久以前就被接受了,并且也获得了3次赞,但它(至少部分)是不正确的,所以在这里提供更多关于此异常的信息。对于给您带来的不便,我们深表歉意。
javax.net.ssl.SSLPeerUnverifiedException: 对等体未经身份验证
通常情况下,当远程服务器根本没有发送证书时,会抛出此异常。然而,在使用Apache HTTP Client时,由于其实现方式以及sun.security.ssl.SSLSocketImpl.getSession()
的实现方式,会遇到一种边缘情况。
使用Apache HTTP Client时,如果远程证书不受信任,也会抛出此异常,这通常会抛出“sun.security.validator.ValidatorException: PKIX路径构建失败
”。
发生这种情况的原因是因为Apache HTTP Client尝试在执行任何其他操作之前获取SSLSession
和对等证书。
提醒一下,使用
SSLSocket
握手有
3种方式:
- 调用startHandshake明确开始握手;或者
- 任何读写应用程序数据的尝试都会导致隐式握手;或者
- 如果没有当前有效的会话,则调用getSession尝试建立会话,并进行隐式握手。
以下是三个示例,都针对一个未受信任证书的主机(使用
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 Client 的逻辑实现,其使用版本为 4.2.1 中的 org.apache.http.conn.ssl.SSLSocketFactory
的 显式调用 startHandshake()
,基于 问题 HTTPCLIENT-1346 的报告。
这似乎最终源于
sun.security.ssl.SSLSocketImpl.getSession()
的实现。该实现在调用
startHandshake(false)
(内部方法)时捕获潜在的
IOException
异常,而不会将其进一步抛出。这可能是一个 bug,尽管这不应该对安全造成巨大影响,因为
SSLSocket
仍然会被关闭。
示例 4:
SSLSocketFactory ssf = (SSLSocketFactory) sslContext.getSocketFactory();
SSLSocket sslSocket = (SSLSocket) ssf.createSocket("untrusted.host.example",
443);
SSLSession sslSession = sslSocket.getSession();
sslSocket.getInputStream().read();
感谢您,即使您尝试使用
SSLSocket
(没有通过获取对等证书来获取会话的漏洞),仍然会引发“
javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed
”异常。
如何修复这个问题
像所有其他不受信任证书的问题一样,这只是确保您使用的信任存储包含必要的信任锚点的问题(即发出您正在尝试验证的链的CA证书,或者可能是实际的服务器证书,用于特殊情况)。
为了解决这个问题,您应该将CA证书(或可能是服务器证书本身)导入到您的信任存储中。您可以这样做:
- 在您的JRE信任存储中,通常是
cacerts
文件(这不一定是最好的,因为这会影响使用该JRE的所有应用程序),
- 在您的信任存储的本地副本中(您可以使用
-Djavax.net.ssl.trustStore = ...
选项进行配置),
- 通过为该连接创建特定的
SSLContext
(如this answer中所述)。 (有人建议使用什么都不做的信任管理器,但这会使您的连接容易受到中间人攻击。)
初始答案
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。