【问题标题】:Is it possible to add an additional subdomain to a existing SSL certificate for HttpClient 4.4?是否可以为 HttpClient 4.4 的现有 SSL 证书添加额外的子域?
【发布时间】:2015-03-26 11:10:20
【问题描述】:

我们正在使用 HttpClient 4.4 与一些外部服务器(server1.company.com、server2.company.com、server3.company.com)进行通信。他们最近添加了一个额外的环境 (server4.company.com),它使用与其他 3 个相同的证书。有问题的证书将前 3 个服务器列为“证书主题备用名称”,但未提及 server4。

我是否可以告诉 keytool 该证书对其他 SAN 有效?或者有没有其他方法可以告诉 HttpClient 为一些额外的域“信任”这个证书?是否有其他选择,或者我必须返回 company.com 并要求他们获得新证书?

【问题讨论】:

  • 告诉他们修复证书当然是最好的选择。如果您的代码可能受到损害,摆弄信任策略会使您陷入问题(并可能承担责任)。您应该得到一份书面声明,说明他们要么承担风险,要么更新证书。

标签: java ssl apache-httpclient-4.x keytool apache-httpcomponents


【解决方案1】:

证书验证有两个方面(一般来说):

  1. 验证证书是否真实且由您信任的人颁发(这是 PKI 方面)。
  2. 验证它是否属于您要连接的主机名(即主机名验证)。

(如果您需要类比,也许this question, about libcurl 可能会感兴趣。)

根据您的说法,该特定证书对于其他主机名是可信且有效的。因此,它将通过 PKI 验证(TrustStrategy 实现的内容)。

您需要构建一个例外情况,仅针对该特定证书,用于主机名验证方面。

我不记得它是如何与 Apache HTTP Client 4.4 一起使用的,但您应该使用自己的验证器而不是 DefaultHostnameVerifier。

实现的方法有verify(String hostYouAreAfter, SSLSession sessionYouActuallyGet)和verify(String hostYouAreAfter, X509Certificate certYouActuallyGet)。

您可以按照以下方式提供自己的实现:

public verify(String hostYouAreAfter, X509Certificate certYouActuallyGet) {
    if (certYouActuallyGet.equals(referenceCertificate)) {
         if ("server4.company.com".equalsIgnoreCase(hostYouAreAfter)) {
             // All good, don't fail and throw an exception.
         } else {
             super.verify(hostYouAreAfter, certYouActuallyGet);
         }
    } else {
         super.verify(hostYouAreAfter, certYouActuallyGet);
    }
}

您可以对verify(String,SSLSession) 执行相同操作,并从SSLSession 的对等链(位置0)获取X509Certificate。逻辑是一样的,只是需要返回true/false,而不是抛出异常。

在这里,我假设您已从具有该特定证书的参考位置加载 referenceCertificate。例如,您可以从已知的密钥库中加载它,或者使用应用程序中配置的参考 PEM 文件中的 CertificateFactory 加载它。


与 TrustStrategy 有两个关键区别,您可以将 isTrusted(final X509Certificate[] chain, final String authType) 实现为 return "nice guy".equalsIgnoreCase(issuerDN.getName());:

  • 实际上,您只是为该证书创建此例外情况,而不是为任何其他可能与您所追求的名称发生问题的证书。
  • 它只影响您希望连接到该特定主机(而不是其他主机)的连接。您确实可以访问HostnameVerifier.verify(...) 的第一个String 参数,这是您打算连接到的主机名。至少您可以将其用于与您获得的证书进行比较,这是 TrustStrategy 所没有的。

【讨论】:

  • 感谢您花时间写这篇文章。这听起来更像是我正在寻找的解决方案。
  • 请注意,与许多这些变通方法一样,代码维护的长期成本可能高于为特定主机颁发的有效证书(特别是如果有一天您改变主意或您需要更新该参考证书)。
【解决方案2】:

人们可以通过使用自定义 TrustStrategy 来信任某些精选证书

SSLContext sslcontext = SSLContexts.custom()
  .loadTrustMaterial(new TrustStrategy() {
    @Override
    public boolean isTrusted(final X509Certificate[] chain, final String authType) 
        throws CertificateException {
      X509Certificate x509Certificate = chain[0];
      Principal issuerDN = x509Certificate.getIssuerDN();
      return "nice guy".equalsIgnoreCase(issuerDN.getName());
    }
  }).build();
CloseableHttpClient client = HttpClients.custom()
  .setSslcontext(sslcontext)
  .build();

【讨论】:

  • (A) 当然应该是主题 DN,而不是发行者 DN。 (B) 这实际上是不安全的:您会接受具有正确主题 DN(或您的示例中的颁发者 DN)的任何证书,而无需检查它是否真的是真实的。 (基本上,一个自称“好人”的坏话会被接受......)
  • @Bruno 同意它不安全,但整个情况从一开始就是不安全的。唯一正确的答案是获得新证书。 Oleg 的回答略比只相信一切要好,这是我目前唯一可行的选择。
  • @布鲁诺。请查看问题以及首先提出的问题。我并不是说这是有道理的,或者是正确的做法。我是说如果这是最后的手段,该怎么做。
  • 如果服务器提供的证书是由同一个 CA 颁发的,HostnameVerifier 会是一个更好的注入点。然而,这在最初的问题中并不清楚。问题是关于“信任”证书,这只能通过自定义 TrustStrategy 来完成。
  • 我认为从这个问题来看,我们正在谈论一个现有的有效证书,对 3 个名称有效,但不是第 4 个,这是例外情况。更一般地说,就TrustStrategy 而言,最好将连接尝试使用的主机名传达给它的verify 方法,以便能够根据主机处理异常情况(就像Firefox 那样,对于例子)。不过,这是一个稍微不同的话题。
猜你喜欢
  • 1970-01-01
  • 2018-10-13
  • 2021-02-11
  • 1970-01-01
  • 1970-01-01
  • 2011-01-03
  • 1970-01-01
  • 1970-01-01
  • 2019-01-13
相关资源
最近更新 更多