7

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

4

3 回答 3

13

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

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

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。如果您遵循使用 PKIX 算法验证证书路径的示例,您应该会得到这样的结果:

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 上提出的另一个问题的上下文中询问了所有这些:证书指纹的实际价值是什么?.

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

最简单的方法:

serverCert.verify(caCert.getPublicKey());

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

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());

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

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)。

于 2012-05-02T13:27:23.593 回答
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 的内置工具来执行此验证。

于 2012-05-02T15:44:17.517 回答
1

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

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

于 2012-05-02T09:58:32.687 回答