Apache HTTPClient SSLPeerUnverifiedException

14

使用 Apache HttpClient 4.2.1。使用从基于表单的登录示例复制的代码。

http://hc.apache.org/httpcomponents-client-ga/examples.html

访问一个 SSL 受保护的登录表单时,我会收到一个异常:

Getting library items from https://appserver.gtportalbase.com/agileBase/AppController.servlet?return=blank
javax.net.ssl.SSLPeerUnverifiedException: peer not authenticated
Closing http connection
at sun.security.ssl.SSLSessionImpl.getPeerCertificates(SSLSessionImpl.java:397)
at org.apache.http.conn.ssl.AbstractVerifier.verify(AbstractVerifier.java:128)
at org.apache.http.conn.ssl.SSLSocketFactory.connectSocket(SSLSocketFactory.java:572)
at org.apache.http.impl.conn.DefaultClientConnectionOperator.openConnection(DefaultClientConnectionOperator.java:180)
at org.apache.http.impl.conn.ManagedClientConnectionImpl.open(ManagedClientConnectionImpl.java:294)
at org.apache.http.impl.client.DefaultRequestDirector.tryConnect(DefaultRequestDirector.java:640)
at org.apache.http.impl.client.DefaultRequestDirector.execute(DefaultRequestDirector.java:479)
at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:906)
at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:805)
at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:784)
据我所知,证书是好的(查看堆栈跟踪之前的URL),没有过期-浏览器也不会发出警告。我已经尝试使用如何处理Apache HttpClient中无效的SSL证书?将证书导入到我的密钥库中,但没有任何改变。我相信您可以创建一个自定义的SSLContext来强制Java忽略错误,但我宁愿修复根本原因,因为我不想打开任何安全漏洞。您有什么想法吗?
2个回答

24

编辑 我意识到这个答案很久以前就被接受了,并且也获得了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();
    // sslSession.getPeerCertificates();
    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。

嗯,我使用两者都可以获取远程证书。将尝试从几个不同的客户端进行测试。 - Oliver Kohll
从OSX,两者都可以工作,从Linux,只有ssl3。将查看服务器配置。 - Oliver Kohll
2
@OliverKohll 那个信任管理器只是实现了默认行为,即使用JRE中的信任存储。如果您想使用不同的信任存储,只需设置-Djavax.net.ssl.trustStore=xxx即可,您不需要代码。 - user207421
使用那个信任管理器不应该改变任何东西。请注意,在初始化SSLContext时使用TLS或SSLv3并不控制协议(如果您使用了这种方式)。您的服务器仍然存在某些密码套件的问题。我认为ECDHE-RSA-AES256-SHA是有问题的,因为openssl s_client -tls1 -connect appserver.gtportalbase.com:443 -cipher 'DEFAULT:!ECDHE-RSA-AES256-SHA'可以工作(当然这取决于您的默认列表)。您在测试中是否从Java 6和7更改过(因为首选项顺序不同)? - Bruno
不,理论上Java 7是默认版本,但机器上有多个版本(最新的Oracle Java 7几天前发布)。我会检查密码,谢谢提醒。 - Oliver Kohll
显示剩余2条评论

0
创建自定义上下文,以便您可以记录证书无效的原因。或者直接进行调试。

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