在 java 中验证证书会引发异常 - 无法找到请求目标的有效证书路径

Posted

技术标签:

【中文标题】在 java 中验证证书会引发异常 - 无法找到请求目标的有效证书路径【英文标题】:Validating a certificate in java throws an exception - unable to find valid certificate path to requested target 【发布时间】:2012-05-11 18:58:36 【问题描述】:

我有一个 Web 应用程序需要客户端发送它的证书,并且服务器必须验证证书(即查看颁发者是否是有效的颁发者并存在于服务器的信任库中)。这是代码:

FileInputStream fin=new FileInputStream("C:/trustedca");
    KeyStore anchors = KeyStore.getInstance("JKS","SUN");
    anchors.load(fin, "server".toCharArray());
    X509CertSelector target = new X509CertSelector();
    FileInputStream fin1=new FileInputStream("C:/client.crt");
    CertificateFactory cf=CertificateFactory.getInstance("X.509");
    X509Certificate cert=null;
    while (fin1.available() > 0) 
    
     System.out.println("in while---------");
     cert =(X509Certificate) cf.generateCertificate(fin1);
    
    target.setCertificate(cert);
    PKIXBuilderParameters params = new PKIXBuilderParameters(anchors, target);

    CertPathBuilder builder = (CertPathBuilder) CertPathBuilder.getInstance("PKIX").build(params);
    PKIXCertPathBuilderResult r = (PKIXCertPathBuilderResult) builder.build((CertPathParameters)params);<br>

但我得到一个例外:

sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid
 certification path to requested target<br>

注意: 此处客户端发送的证书是 client.crt,用于签署 client.crt 证书的证书是密钥库“trustedca”中存在的 ca.crt。那为什么会出现这个异常呢?

【问题讨论】:

值得一提的是,通过查看这段代码,无法判断为什么 PKIX builder 无法构建路径。但是将 -Djava.security.debug=all 添加到 java 选项将提供构建器的调试输出,并且可以阐明问题。 【参考方案1】:

可能由于缺少某些中间证书,无法构建有效路径。您加载证书的循环会丢弃除最后一个之外的所有证书。相反,请保存所有这些证书,并将它们传递给 CertPathBuilder 以帮助构建路径。

另一个常见问题是默认执行吊销检查,这有利于安全。如果您不了解如何获取 CRL 或使用 OCSP,您可以降低安全性并禁用吊销检查。这也显示在下面的示例中。

...
CertificateFactory fac = CertificateFactory.getInstance("X.509");
FileInputStream is = new FileInputStream("client.crt");
Collection<? extends Certificate> intermediate;
try 
  intermediate = fac.generateCertificates(is);
 finally 
  is.close();

X509Certificate client = null;
for (Certificate c : intermediate)
  client = (X509Certificate) c;
if (client == null)
  throw new IllegalArgumentException("Empty chain.");
X509CertSelector t = new X509CertSelector();
t.setCertificate(client);
PKIXBuilderParameters params = new PKIXBuilderParameters(anchors, t);
CertStoreParameters store = new CollectionCertStoreParameters(intermediate);
params.addCertStore(CertStore.getInstance("Collection", store));
params.setRevocationEnabled(false);
...

了解您是如何获取“client.crt”文件以及预期其内容是什么会有所帮助。和响应者一样,我想知道为什么您不能使用 JSSE 的内置工具来执行此验证。

【讨论】:

基于this duplicate问题,证书来自html表单。目前尚不清楚这如何与与该证书中的公钥匹配的私钥持有者联系起来。除此之外,尚不清楚应检查其应用程序的哪些属性(例如密钥使用)。 @erickson :没有中间证书。就像我说的 client.crt 是由客户端上传的。出于测试目的,我创建了一个 ca 并使用 ca.crt 签署了 client.csr 以提供 client.crt。我将 ca.crt 文件添加到“trustedca”,并从客户端机器上传了 client.crt 文件。所以你看到没有中间证书。密钥库“trustedca”中存在的 ca 证书。我已将此证书和目标证书添加到 PKIXBuilderParameters @Ashwin,这不仅仅是中间证书,您还需要添加要检查的最终实体证书。 X509CertSelector 只是一个选择器,当您将证书添加到选择器时,您实际上并没有将其添加到要使用的证书集合中。再读一遍this section,或者看看埃里克森的代码是如何初始化intermediate的。【参考方案2】:

如果您需要客户端证书,请让 JSSE 为您完成所有这些工作。如果您想对特定连接使用自己的信任库,请配置 JSSE 以使用它。检查参考文档中的Customizing JSSE 部分。

这是一个使用自定义信任存储构建SSLContext 的简短示例。 (其他更复杂的X509TrustManagers 也可以使用,但你很少需要它。)

TrustManagerFactory tmf = TrustManagerFactory
    .getInstance(TrustManagerFactory.getDefaultAlgorithm());
KeyStore ks = KeyStore.getInstance("JKS");
FileInputStream fis = new FileInputStream("/.../example.jks");
ks.load(fis, null);
// or ks.load(fis, "thepassword".toCharArray());
fis.close();

tmf.init(ks);

SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, tmf.getTrustManagers(), null);

如果您使用现有的应用程序服务器,如何通过配置这一切将取决于服务器以及它预期的配置方式。 为此使用 JSSE 还将确保关键使用属性是适当的。

如果您通过其他方式获得证书并想要对其进行验证,则需要使用PKI API。如果你关注Example of Validating a Certification Path using the PKIX algorithm,你应该会得到这样的结果:

X509Certificate certToVerify = ...

CertificateFactory cf = CertificateFactory.getInstance("X.509");
CertPath cp = cf.generateCertPath(Arrays
    .asList(new X509Certificate[]  certToVerify ));

TrustAnchor trustAnchor = new TrustAnchor(caCert, null);

CertPathValidator cpv = CertPathValidator.getInstance("PKIX");
PKIXParameters pkixParams = new PKIXParameters(
    Collections.singleton(trustAnchor));
pkixParams.setRevocationEnabled(false);
    
cpv.validate(cp, pkixParams);

检查 validate 的结果(当然,它没有抛出验证异常)。在这里,我禁用了撤销检查以简化。您还可以设置PKIXParameters 的其他方面以进行策略检查。这可能会变得相当复杂(以及为什么最好让默认的 JSSE 管理器为您做这件事)。


您还在 Security.SE 上提出的另一个问题的背景下询问了所有这些问题:What is the actual value of a certificate fingerprint?

假设您有两个X509Certificates:serverCertcaCert,您想要验证serverCert 是否由(与公钥匹配的私钥)caCert 签名。

最简单的方法:

serverCert.verify(caCert.getPublicKey());

如果您想更手动地执行此操作,请使用 Signature API:

System.out
     .println("Signature algorithm: " + serverCert.getSigAlgName());
Signature sig = Signature.getInstance(serverCert.getSigAlgName());
sig.initVerify(caCert.getPublicKey());
sig.update(serverCert.getTBSCertificate());
System.out
    .println("Verified? " + sig.verify(serverCert.getSignature()));

假设算法是SHA1withRSA,你也可以计算摘要:

MessageDigest digest = MessageDigest.getInstance("SHA-1");
digest.reset();
digest.update(serverCert.getTBSCertificate());
byte[] digestBytes = digest.digest();

Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.DECRYPT_MODE, caCert.getPublicKey());
byte[] cipherText = cipher.doFinal(serverCert.getSignature());

摘要本身只是使用Cipher的结果的一部分:你从serverCert.getSignature()得到的实际上是一个更复杂的ASN.1结构,其中包括摘要算法标识符,在这种情况下,@ 987654344@ 应该以 like this 为前缀:

SHA-1:   (0x)30 21 30 09 06 05 2b 0e 03 02 1a 05 00 04 14 || H.

(如果您想正确分析 ASN.1 结构,BouncyCastle 可能很有用。)

请注意,这些都不能验证时间有效性或任何其他属性。 PKIX 合规性不仅仅是检查签名(参见 RFC 3820 和 5820)。

【讨论】:

查看“使用 PKIX 算法构建证书路径的示例”:您需要将 CertStore 添加到包含所有相关证书的参数中。【参考方案3】:

即查看颁发者是否是有效的颁发者并存在于服务器的 信任库

JSSE 已经做到了这一切。您无需执行任何此操作,除非验证对等证书尚未过期。

【讨论】:

@suraj 您没有得到什么? 当您将证书添加到您的信任库时,您是在告诉信任库信任该证书。如果您不信任它,请不要添加它。 @suraj 如果它是由授权 CA 签名的,则根本不需要添加它。随 Java 分发的信任库已经信任所有公认的 CA。这就是整个想法。 @suraj 您的信任库最初应包含随 JDK 分发的信任库中的所有内容,或者至少包含您准备信任的那些 CA 证书。这种机制完全避免必须将客户端证书加载到信任库中。这就是信任库机制乃至整个 X.509 机制的意义所在。 @suraj 如果您将自签名证书放入您的信任库,则您信任它。这就是手术的意义。如果您不信任它,请不要添加它。我已经说了这么多。您已经说过您的信任库不是默认的 Java 信任库。我的问题是为什么不呢?在我看来,你做这一切都是错的。您根本不必为证书获取编写代码,因为它应该是一个具有独立验证的离线过程。否则你所做的就是破坏 SSL 内置的安全性。 @suraj 和 Ashwin,您不再是 SO 新手。你需要know what you ask,试试not to redirect the question completely in comments,避免重复here和here。

以上是关于在 java 中验证证书会引发异常 - 无法找到请求目标的有效证书路径的主要内容,如果未能解决你的问题,请参考以下文章

异常无法在 Spring MVC 中验证目标的证书

抛出异常:根据验证程序,远程证书无效

每次打开OUTLOOK提示无法验证您连接的服务器的安全证书。怎么解决

如何编一个Java应用读取XP数字证书库中的cer证书中的信息

OUTLOOK 无法验证安全证书问题

无效的凭据引发身份验证突变异常