【问题标题】:Apache HttpClient 4.2.3 - SSLException: hostname in certificate didn't matchApache HttpClient 4.2.3 - SSLException:证书中的主机名不匹配
【发布时间】:2013-04-04 09:20:57
【问题描述】:

我遇到了这么大的问题,在谷歌上找不到任何答案,所以我试着写在这里。

我正在开发一个 Web 服务客户端。要连接到 Web 服务,我必须创建一个带有客户端身份验证的 ssl 通道并使用代理(必须从列入白名单的 IP 进行调用)。为此,我在 WebSphere 6.1 环境下使用 HttpClient 4.2.3 库和 Java 1.5(与 1.4 的兼容模式)。

由于工作相关,我删除了对我正在调用的实际 Web 服务的所有引用,因此您会在这里和那里发现一些奇怪的变量/主机/类名称 :)

我已经这样设置了连接

    // Costruzione del proxy
    Resources res = new Resources();
    String proxyHost = res.get(PROXY_HOST);
    int proxyPort = Integer.parseInt(res.get(PROXY_PORT));

    HttpHost proxy = new HttpHost(proxyHost, proxyPort);


    // Costruzione del post method
    String serviceUrl = "https://" + someHost + meth.getServiceURL();
    HttpPost post = new HttpPost(serviceUrl);
    post.addHeader("Content-Type", "text/xml");

    String soapAction = "https://" + someHost + meth.getAction();
    post.addHeader("SOAPAction", soapAction);

    AbstractHttpEntity postBody = new StringEntity(soapEnvelope);
    post.setEntity(postBody);


    // Costruzione dell'ambiente di chiamata HTTPS
    SSLSocketFactory sslSocketFactory = new SSLSocketFactory(keyStore, res.get(KEY_STORE_PASS_CONF), trustStore);
    Scheme httpsScheme = new Scheme("https", HTTPS_PORT, sslSocketFactory);

    Scheme httpScheme = new Scheme("http", HTTP_PORT, PlainSocketFactory.getSocketFactory());

    final SchemeRegistry schemeRegistry = new SchemeRegistry();
    schemeRegistry.register(httpScheme);
    schemeRegistry.register(httpsScheme);

    PoolingClientConnectionManager connManager = new PoolingClientConnectionManager(schemeRegistry);

    HttpParams params = new BasicHttpParams();

    HttpClient client = new DefaultHttpClient(connManager, params);
    client.getParams().setParameter(ConnRoutePNames.DEFAULT_PROXY, proxy);


    // CHIAMATA A MEF
    HttpResponse resp = client.execute(post);

Keystore 和 Truststore 已从文件加载

File ksFile = new File(res.get(KEY_STORE_PATH_CONF));
File tsFile = new File(res.get(TRUST_STORE_PATH_CONF));
InputStream ksInput, tsInput;

try {
    ksInput = new FileInputStream(ksFile);
    tsInput = new FileInputStream(tsFile);
} catch ... {}

KeyStore keyStore, trustStore;
try {
    String ksPassword = res.get(KEY_STORE_PASS_CONF);
    String tsPassword = res.get(TRUST_STORE_PASS_CONF);

    keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
    keyStore.load(ksInput, ksPassword.toCharArray());   

    trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
    trustStore.load(tsInput, tsPassword.toCharArray());
} catch ... {}

当我尝试调用 client.execute 方法时,它给了我这个异常

javax.net.ssl.SSLException: hostname in certificate didn't match: <right.host.it> != <unknown.host.it>
at org.apache.http.conn.ssl.AbstractVerifier.verify(AbstractVerifier.java)
at org.apache.http.conn.ssl.BrowserCompatHostnameVerifier.verify(BrowserCompatHostnameVerifier.java)
at org.apache.http.conn.ssl.AbstractVerifier.verify(AbstractVerifier.java)
at org.apache.http.conn.ssl.AbstractVerifier.verify(AbstractVerifier.java)
at org.apache.http.conn.ssl.SSLSocketFactory.createLayeredSocket(SSLSocketFactory.java:628)
at org.apache.http.impl.conn.DefaultClientConnectionOperator.updateSecureConnection(DefaultClientConnectionOperator.java:232)
at org.apache.http.impl.conn.ManagedClientConnectionImpl.layerProtocol(ManagedClientConnectionImpl.java:401)
at org.apache.http.impl.client.DefaultRequestDirector.establishRoute(DefaultRequestDirector.java:842)
at org.apache.http.impl.client.DefaultRequestDirector.tryConnect(DefaultRequestDirector.java:649)
at org.apache.http.impl.client.DefaultRequestDirector.execute(DefaultRequestDirector.java:480)
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)
at my.class.package.MyClass.callWs(MyWebService.java)

我注意到了一些奇怪的事情:

  • 异常消息中的未知主机来自正确主机的同一域,因此它必然来自该公司的证书
  • 我已尝试调试该调用,但它似乎需要其他证书。 unknown.host.it 地址取自 x509 证书的 CN(在调试中可见),并且密钥库文件中的两个证书都包含其他 CN。此外,我的密钥库中有两个证书,当调用第一个 AbstractVerifier.verify 方法时,调用参数中有三个证书,其中一个包含 CN 中的 unknown.host.it 字符串
  • 实际上,我在我拥有的任何证书中都找不到 unknown.host.it 字符串

问题可能是我不知道 SSL 究竟是如何工作的,所以如果你在我所说的中发现任何完全错误的东西,这可能是解决我问题的关键。

你能帮我什么忙吗? 非常感谢!

#

更新 1:后天 :) 因为我想先收到答案(公司必须测试软件),所以我决定强制软件接受每个主机名。我想稍后更改它,但现在我只想让它工作。所以我在我的代码中添加了这个(已弃用:))行

    SSLSocketFactory sslSocketFactory = new SSLSocketFactory(keyStore, res.get(KEY_STORE_PASS_CONF), trustStore);
    sslSocketFactory.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);

现在它通过了主机名验证(惊喜:))但仍然给我一个 403-Forbidden 错误。服务所有者说我是从列入白名单的 IP 呼叫的,所以我想问题在于服务器无法识别我。他们说我没有出示任何证书。

我添加证书的方法是在 SSLSocketFactory 构造函数中,我在其中传递带有我的证书的密钥库。我是否必须向 HttpClient 指定我想在握手时向服务器自我识别?

谢谢:)

【问题讨论】:

  • 我通过激活可信赖的主机名验证器(即 SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER)找到了一个临时解决方案。我知道这不是正确的解决方案,但我会有时间解决这个问题。问题是 HTTP 不断向我返回 403 禁止错误。我打电话给服务支持,他们说我是从列入白名单的 IP 调用的(所以代理可以工作),但由于某种原因,相互身份验证不起作用。

标签: ssl apache-httpclient-4.x websphere-6.1 java-5 jks


【解决方案1】:

您的服务器很可能使用服务器名称指示 (SNI) 为同一 IP 地址(和端口)上的两个主机名提供服务。

您可以通过明确指定服务器名称来使用openssl 进行检查。例如,这两个命令将连接到同一个 IP 地址,但请求不同的主机名并提供不同的证书:

openssl s_client -connect www.google.co.uk:443 -servername www.google.co.uk

openssl s_client -connect www.google.co.uk:443 -servername www.google.com

Java 目前仅在客户端支持 SNI,并且仅从 Java 7 开始。

【讨论】:

  • 我正在使用 Java 5 :) 但它实际上工作过一次......它只是停止这样做,我仍然想知道为什么......
  • 你真的应该升级。也许服务器上发生了一些变化?
  • 我同意,但是...我不是决定者。和所有大公司一样,我的公司有点不太可能升级技术:)
  • 谢谢你。不知道是什么导致了错误。
  • 从 OpenSSL 1.1.1 开始,s_client 工具自动配置服务器名,因此 SNI 标头使用 -noservername 开关避免在 TLS 握手中发送主机名信息。添加此注释是因为它在使用 openssl 测试 SNI 时很有用。来源:feistyduck.com/library/openssl-cookbook/online/…
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-12-16
  • 1970-01-01
  • 1970-01-01
  • 2018-06-18
  • 1970-01-01
相关资源
最近更新 更多