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

时间:2012-05-02 09:54:09

标签: java certificate x509certificate keystore pkix

我有一个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。那为什么要给出这个例外?

3 个答案:

答案 0 :(得分:12)

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

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

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?

假设您有两个X509Certificate s: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结构,其中包括摘要算法标识符,在此例如,digestBytes应该以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)。

答案 1 :(得分:4)

可能无法构造有效路径,因为缺少某些中间证书。您加载证书的循环会丢弃除最后一个之外的所有内容。相反,保存所有这些证书,并将它们传递给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的内置工具来执行这个验证。

答案 2 :(得分:1)

  

即查看发行人是否是有效的发行人并出现在服务器中   信任

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