JCE:验证 X509 自签名证书时出现异常

JCE: Exception while verifying X509 self-signed certificate

我们为 SSL/TLS 开发了自定义 JCE 安全提供程序。

我们的一位用户在客户端遇到服务器证书验证失败。这是常见的 "unable to find valid certification path to requested target" 错误。 (是的,证书在信任库中。)

注意:虽然我们正在实施自定义提供程序,但我们依赖于信任管理器的标准 JCE 提供程序,在 TLS 期间使用 javax.net.ssl.X509TrustManager.checkServerTrusted(X509Certificate[] chain, String)握手。

sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
        at sun.security.validator.PKIXValidator.doBuild(PKIXValidator.java:387)
        at sun.security.validator.PKIXValidator.engineValidate(PKIXValidator.java:292)
        at sun.security.validator.Validator.validate(Validator.java:260)
        at sun.security.ssl.X509TrustManagerImpl.validate(X509TrustManagerImpl.java:324)
        at sun.security.ssl.X509TrustManagerImpl.checkTrusted(X509TrustManagerImpl.java:229)
        at sun.security.ssl.X509TrustManagerImpl.checkServerTrusted(X509TrustManagerImpl.java:105)
        [snip]
Caused by: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
        at sun.security.provider.certpath.SunCertPathBuilder.build(SunCertPathBuilder.java:146)
        at sun.security.provider.certpath.SunCertPathBuilder.engineBuild(SunCertPathBuilder.java:131)
        at java.security.cert.CertPathBuilder.build(CertPathBuilder.java:280)
        at sun.security.validator.PKIXValidator.doBuild(PKIXValidator.java:382)
        ...

与我们所有的用户一样,他们安装了无限的安全策略 jar。

服务器证书是自签名的(不是 CA)。他们使用 127.0.0.1 作为主机名,因为他们只是连接到后端进程。

JRE/JDK 的默认安装有效(使用 Sun/Oracle 安全提供程序)。使用 javax.net.debug 输出,我已确认这些成功的连接使用相同的自签名证书。

然而,当我将一些代码拼凑在一起以简单地建立连接时,它可以毫无问题地在同一台机器上使用相同的信任库、密钥库、证书和 JDK。这使用相同的验证功能,该功能使用相同的证书和相同的 authType 字符串对 X509TrustManager.checkServerTrusted() 进行相同的调用。我一辈子都无法解释为什么 checkServerTrusted() 在这种情况下验证了证书,但在用户的情况下却失败了。

是否可以通过某种方式调整 JCE,使 X509TrustManager 无法验证此证书?也许是因为它是自签名的,或者因为公用名是服务的名称而不是域名?我在他们的 JVM 参数或安全属性中看不到任何表明这一点的内容。但也许他们正在进行一些 JCE 调用我不知道哪个修改了 X509TrustManager 的行为?

证书的 javax.net.debug 输出如下。通用名称只是我们采用者服务的名称。这对我来说似乎可疑。但是,它适用于默认安全提供程序。

***
Found trusted certificate:
[
[
  Version: V3
  Subject: CN=[snip], OU=[snip], O=[snip], L=Bangalore, ST=[snip], C=IN
  Signature Algorithm: SHA256withRSA, OID = 1.2.840.113549.1.1.11

  Key:  Sun RSA public key, 2048 bits
  modulus: [snip]
  public exponent: 65537
  Validity: [From: Wed Feb 17 14:45:40 IST 2016,
               To: Thu Nov 20 14:45:40 IST 2070]
  Issuer: CN=[snip], OU=[snip], O=[snip], L=Bangalore, ST=[snip], C=IN
  SerialNumber: [    5cf68160]

Certificate Extensions: 1
[1]: ObjectId: 2.5.29.14 Criticality=false
SubjectKeyIdentifier [
KeyIdentifier [
[snip]
]
]

]
  Algorithm: [SHA256withRSA]
  Signature:
[snip]

]

添加一些额外的日志后,很明显无论出于何种原因,接受的颁发者的填充方式都不同。在失败的情况下,从 [trust-manager].getAcceptedIssuers[] 返回大量已接受的发行者;他们不包括有问题的证书。在通过的情况下,只有有问题的证书包含在接受的颁发者中。

[编辑 1] 更正了证书。

[编辑 2] 更正了粗体问题中的通用名称。

[edit 3] 添加了接受的发行人段落

事实证明这是缓存信任管理器的问题。

使用默认提供程序或我们的自定义提供程序,信任管理器最初是使用 cacerts 信任库实例化的。

使用默认提供程序,稍后使用 javax.net.ssl.trustStore 指定的信任库实例化新的信任管理器。我们的自定义提供程序只是重新使用之前实例化的 cacerts 信任管理器。

解决方案:实例化一个新的信任管理器,它将尊重javax.net.ssl.trustStore。