本文介绍了Apache HTTPClient SSLPeerUnverifiedException的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

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

访问受 SSL 保护的登录表单时出现异常:

从 https://appserver.gtportalbase.com/agileBase/AppController.servlet?return=blank 获取库项目javax.net.ssl.SSLPeerUnverifiedException:对等方未通过身份验证关闭http连接在 sun.security.ssl.SSLSessionImpl.getPeerCertificates(SSLSessionImpl.java:397)在 org.apache.http.conn.ssl.AbstractVerifier.verify(AbstractVerifier.java:128)在 org.apache.http.conn.ssl.SSLSocketFactory.connectSocket(SSLSocketFactory.java:572)在 org.apache.http.impl.conn.DefaultClientConnectionOperator.openConnection(DefaultClientConnectionOperator.java:180)在 org.apache.http.impl.conn.ManagedClientConnectionImpl.open(ManagedClientConnectionImpl.java:294)在 org.apache.http.impl.client.DefaultRequestDirector.tryConnect(DefaultRequestDirector.java:640)在 org.apache.http.impl.client.DefaultRequestDirector.execute(DefaultRequestDirector.java:479)在 org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:906)在 org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:805)在 org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:784)

据我所知,证书很好(查看堆栈跟踪前的 URL),没有过期 - 浏览器不会抱怨.

我试过将证书导入我的密钥库中

如何使用 Apache HttpClient 处理无效的 SSL 证书?

没有变化.我相信您可以创建一个自定义 SSLContext 来强制 Java 忽略错误,但我宁愿修复根本原因,因为我不想打开任何安全漏洞.

有什么想法吗?

解决方案

EDIT 我意识到这个答案很久以前就被接受了,并且也被投票了 3 次,但它是(至少部分) 不正确,所以这里有更多关于这个例外的信息.给您带来的不便深表歉意.

javax.net.ssl.SSLPeerUnverifiedException:对等方未通过身份验证

这是通常当远程服务器根本没有发送证书时抛出的异常.但是,有一个边缘情况,在使用 Apache HTTP Client 时会遇到,因为它在这个版本中的实现方式,以及 sun.security 的方式.ssl.SSLSocketImpl.getSession() 已实现.

使用 Apache HTTP Client 时,远程证书不受信任时也会抛出此异常,通常会抛出sun.security.validator.ValidatorException: PKIX path building failed".

发生这种情况的原因是 Apache HTTP Client 在执行任何其他操作之前尝试获取 SSLSession 和对等证书.

提醒一下,有使用 SSLSocket 发起握手的 3 种方式:

  • 调用 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 路径构建失败"(如预期).

示例 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 版本中.更高版本 根据 startHandshake()-1346" rel="nofollow noreferrer">问题 HTTPCLIENT-1346.

这最终似乎来自于sun.security.ssl.SSLSocketImpl.getSession(),捕获调用startHandshake(false)时抛出的潜在IOException(内部方法),不抛出更远.这可能是一个错误,尽管这不应该对安全产生巨大影响,因为 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(如 此答案 中所述).(有些人建议使用什么都不做的信任管理器,但这会使您的连接容易受到 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,否则默认启用.

Using Apache HttpClient 4.2.1. Using code copied from the form based login example

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

I get an exception when accessing an SSL protected login form:

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)

The certificate as far as I can tell is fine (see the URL before the stack trace), not expired - browsers don't complain.

I've tried importing the certificate into my keystore a la

How to handle invalid SSL certificates with Apache HttpClient?

with no change. I believe you can create a custom SSLContext to force Java to ignore the error but I'd rather fix the root cause as I don't want to open up any security holes.

Any ideas?

解决方案

EDIT I realise this answer was accepted a long time ago and has also been upvoted 3 times, but it was (at least partly) incorrect, so here is a bit more about this exception. Apologies for the inconvenience.

This is usually an exception thrown when the remote server didn't send a certificate at all. However, there is an edge case, which is encountered when using Apache HTTP Client, because of the way it was implemented in this version, and because of the way sun.security. ssl.SSLSocketImpl.getSession() is implemented.

When using Apache HTTP Client, this exception will also be thrown when the remote certificate isn't trusted, which would more often throw "sun.security.validator.ValidatorException: PKIX path building failed".

The reasons this happens is because Apache HTTP Client tries to get the SSLSession and the peer certificate before doing anything else.

Just as a reminder, there are 3 ways of initiating the handshake with an SSLSocket:

Here are 3 examples, all against a host with a certificate that isn't trusted (using javax.net.ssl.SSLSocketFactory, not the Apache one).

Example 1:

    SSLSocketFactory ssf = (SSLSocketFactory) sslContext.getSocketFactory();
    SSLSocket sslSocket = (SSLSocket) ssf.createSocket("untrusted.host.example",
            443);
    sslSocket.startHandshake();

This throws "javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed" (as expected).

Example 2:

    SSLSocketFactory ssf = (SSLSocketFactory) sslContext.getSocketFactory();
    SSLSocket sslSocket = (SSLSocket) ssf.createSocket("untrusted.host.example",
            443);
    sslSocket.getInputStream().read();

This also throws "javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed" (as expected).

Example 3:

    SSLSocketFactory ssf = (SSLSocketFactory) sslContext.getSocketFactory();
    SSLSocket sslSocket = (SSLSocket) ssf.createSocket("untrusted.host.example",
            443);
    SSLSession sslSession = sslSocket.getSession();
    sslSession.getPeerCertificates();

This, however, throws javax.net.ssl.SSLPeerUnverifiedException: peer not authenticated.

This is the logic implemented in Apache HTTP Client's AbstractVerifier used by its org.apache.http.conn.ssl.SSLSocketFactory in version 4.2.1. Later versions make an explicit call to startHandshake(), based on reports in issue HTTPCLIENT-1346.

This ultimately seems to come from the implementation of sun.security. ssl.SSLSocketImpl.getSession(), which catches potential IOExceptions thrown when calling startHandshake(false) (internal method), without throwing it further. This might be a bug, although this shouldn't have a massive security impact, since the SSLSocket will still be closed anyway.

Example 4:

    SSLSocketFactory ssf = (SSLSocketFactory) sslContext.getSocketFactory();
    SSLSocket sslSocket = (SSLSocket) ssf.createSocket("untrusted.host.example",
            443);
    SSLSession sslSession = sslSocket.getSession();
    // sslSession.getPeerCertificates();
    sslSocket.getInputStream().read();

Thankfully, this will still throw "javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed", whenever you actually try to use that SSLSocket (no loophole there by getting the session without getting the peer certificate).


How to fix this

Like any other issue with certificates that are not trusted, it's a matter of making sure the trust store you're using contains the necessary trust anchors (i.e. the CA certificates that issued the chain you're trying to verify, or possibly the actual server certificate for exceptional cases).

To fix this, you should import the CA certificate (or possibly the server certificate itself) into your trust store. You can do this:

  • in your JRE trust store, usually the cacerts file (that's not necessarily the best, because that would affect all applications using that JRE),
  • in a local copy of your trust store (which you can configure using the -Djavax.net.ssl.trustStore=... options),
  • by creating a specific SSLContext for that connection (as described in this answer). (Some suggest to use a trust manager that does nothing, but this would make your connection vulnerable to MITM attacks.)

Initial answer

This has nothing to do with trusting certificates or you having to create a custom SSLContext: this is due to the fact that the server isn't sending any certificate at all.

This server is visibly not configured to support TLS properly. This fails (you won't get a remote certificate):

However, SSLv3 seems to work:

If you know who's running this server, it would be worth contacting them to fix this problem. Servers should really support TLSv1 at least nowadays.

Meanwhile, one way to fix this problem would be to create your own org.apache.http.conn.ssl.SSLSocketFactory and use it for this connection with Apache Http client.

This factory would need to create an SSLSocket as usual, use sslSocket.setEnabledProtocols(new String[] {"SSLv3"}); before returning that socket, to disable TLS, which would otherwise be enabled by default.

这篇关于Apache HTTPClient SSLPeerUnverifiedException的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

08-04 05:56