【问题标题】:Get server certificate from failed connection attempt with HttpClient 4.2从与 HttpClient 4.2 的失败连接尝试中获取服务器证书
【发布时间】:2015-01-20 18:12:06
【问题描述】:

我目前正在使用 Apache HttpClient(特别是 4.2.3 版)向我的应用程序添加 https 下载功能。

如果证书验证失败,我想获得服务器证书链。抛出的异常 SSLPeerUnverifiedException 没有提供任何信息的字段。

try {
    HttpResponse response = client.execute(get);
} catch (SSLPeerUnverifiedException e) {
    // retrieve server certificate here
}

有一种方法是将TrustManager(用于捕获证书)注入SSLContext,并为每个请求重新创建SSLContextSSLSocketFactoryHttpClient。但是,我希望能够为多个可能的并行请求重用这些实例。

【问题讨论】:

  • 您随后通过SSLSocket 描述的方式是我知道的唯一方式。如果HttpClient 以某种方式提供了这种能力,我不知道。基本上我创建了一个虚拟TrustManager,它保留了checkServerTrusted 内的证书链,然后如果握手引发证书验证错误,它会处理获得的证书链。如果你愿意,我可以在答案中显示一些代码,但看起来你真的已经知道如何做到这一点,并希望通过 HttpClient 来做到这一点。
  • 谢谢,我已经用这种方式实现了。我只是想知道这是否可以通过重用 HttpClient 或至少 SSLContext、SSLSocketFactory 和 TrustManager 来完成。这样做的原因是为了在 httpclient 上获得连接重用/缓存等功能。

标签: java ssl certificate httpclient apache-httpclient-4.x


【解决方案1】:

我在此示例中使用了 HC 4.3,但应该与 HC 4.2 完全相同,尽管我建议升级

public static void main(final String[] args) throws Exception {

    TrustManagerFactory tmfactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
    tmfactory.init((KeyStore) null);
    TrustManager[] tms = tmfactory.getTrustManagers();
    if (tms != null) {
        for (int i = 0; i < tms.length; i++) {
            final TrustManager tm = tms[i];
            if (tm instanceof X509TrustManager) {
                tms[i] = new TrustManagerDelegate((X509TrustManager) tm);
            }
        }
    }
    SSLContext sslContext = SSLContext.getInstance("TLS");
    sslContext.init(null, tms, null);

    CloseableHttpClient httpClient = HttpClients.custom()
            .setSslcontext(sslContext)
            .build();
    try {
        CloseableHttpResponse response = httpClient.execute(new HttpGet("https://google.com/"));
        try {
            // do something usefull
        } finally {
            response.close();
        }
    } catch (SSLException ex) {
        Throwable cause = ex.getCause();
        if (cause instanceof MyCertificateException) {
            X509Certificate[] chain = ((MyCertificateException) cause).getChain();
            for (X509Certificate cert: chain) {
                System.out.println(cert);
            }
        }
    }
}

static class TrustManagerDelegate implements X509TrustManager {

    private final X509TrustManager trustManager;

    TrustManagerDelegate(final X509TrustManager trustManager) {
        super();
        this.trustManager = trustManager;
    }

    @Override
    public void checkClientTrusted(
            final X509Certificate[] chain, final String authType) throws CertificateException {
        this.trustManager.checkClientTrusted(chain, authType);
    }

    @Override
    public void checkServerTrusted(
            final X509Certificate[] chain, final String authType) throws CertificateException {
        try {
            this.trustManager.checkServerTrusted(chain, authType);
        } catch (CertificateException ex) {
            throw new MyCertificateException(chain, ex);
        }
    }

    @Override
    public X509Certificate[] getAcceptedIssuers() {
        return this.trustManager.getAcceptedIssuers();
    }

}

static class MyCertificateException extends CertificateException {

    private final X509Certificate[] chain;

    MyCertificateException(final X509Certificate[] chain, final CertificateException ex) {
        super(ex);
        this.chain = chain;
    }

    public X509Certificate[] getChain() {
        return chain;
    }

}

【讨论】:

  • 哇,我应该自己想出来的;)谢谢@oleg
  • 只是对此的补充。这是依赖于平台的行为。正确设置原因取决于内部 SSL 实现。例如。它不适用于 Android 4.4。
  • 您可能想说这种行为依赖于 JSSE 提供程序,而不是依赖于平台。此外,不传播根本原因的 Android 提供商听起来对我来说也是一个缺陷,而不是“依赖于平台的行为”。可以通过使用自定义连接工厂和 HttpContext 以任何方式将证书链传播给调用者
猜你喜欢
  • 1970-01-01
  • 2010-10-27
  • 2020-07-22
  • 2019-04-24
  • 1970-01-01
  • 2016-06-04
  • 2016-08-23
  • 2011-07-07
相关资源
最近更新 更多