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

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

I have a web app that requires a client to send it's certificate and the server has to validate the certificate(i.e see if the issuer is a valid issuer and present in the server's truststore). Here is the code :

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>

但我得到一个例外:

But I get an exception :

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

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

NOTE :
Here the certificate sent by the client is client.crt and the cerificate used to sign the client.crt certificate is the ca.crt which is present in the keystore "trustedca". Then why is it giving this exception?

推荐答案

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

If you're expecting a client certificate, let the JSSE do all of this for you. If you want to use your own trust store for a particular connection, configure the JSSE to use it. Check the Customizing JSSE section in the reference documentation.

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

Here is a short example for building an SSLContext with a custom trust store. (Other, more complex X509TrustManagers can also be used, but you rarely need that.)

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 还将确保关键使用属性是适当的.

If you're using an existing application server, how to pass configure all this will depend on the server and how it expects to be configured. Using the JSSE for this will also make sure that the key usage attributes are appropriate.

如果您通过其他方式获得证书并希望对其进行验证,则需要使用 PKI API.如果您遵循 示例使用 PKIX 算法验证证书路径,您应该得到这样的结果:

If you get the certificate via some other means and want to validate it, you need to use the PKI API. If you follow the Example of Validating a Certification Path using the PKIX algorithm, you should get to something like this:

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 管理器为您做这件事).

Check the result from validate (and that it hasn't thrown a validation exception, of course). Here, I've disabled revocation checks to simplify. You can also set other aspects of the PKIXParameters for policy checks. This can get quite complex (and why it's better to let the default JSSE managers do that for you).

您还在 Security.SE 上提出的另一个问题的上下文中询问了所有这些:什么是证书指纹的实际值吗?.

You were also asking about all this in the context of this other question you asked on Security.SE: What is the actual value of a certificate fingerprint?.

假设您有两个 X509Certificate:serverCertcaCert,您要在其中验证 serverCert 是否存在签名者(与公钥匹配的私钥)caCert.

Suppose you have two X509Certificates: serverCert and caCert, where you want to verify that serverCert was signed by (the private key matching the public key in) caCert.

最简单的方法:

serverCert.verify(caCert.getPublicKey());

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

If you want to do this a bit more manually, use the 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,你也可以计算摘要:

Assuming the algorithm is SHA1withRSA, you could also compute the digest:

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 结构,其中包括摘要算法标识符,在这种情况下,digestBytes 应以 像这样:

The digest itself will only be part of the result from using Cipher: what you get from serverCert.getSignature() is in fact a more complex ASN.1 structure, which includes the digest algorithm identifier, in this case, the digestBytes should be prefixed with something like this:

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

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

(BouncyCastle may be useful if you want to analyse the ASN.1 structure properly.)

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

Note that none of this verifies the time validity or any other attributes. PKIX compliance is far more than checking the signature (see RFC 3820 and 5820).

相关文章