我有一个需要客户端发送它的证书和服务器验证证书的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证书cerificate是其存在于密钥库“trustedca”的ca.crt。 那为什么给这个例外?
如果你期待一个客户端证书,让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);
检查从验证结果(和它没有抛出验证异常,当然)。 在这里,我已禁用吊销检查,以简化。 您还可以设置等方面PKIXParameters
的策略检查。 这可以得到相当复杂的(以及为什么它是更好地让默认JSSE经理为你做的)。
你还问了关于这一切,你问的这个Security.SE其他问题的背景: 什么是证书指纹的实际价值? 。
假设你有两个X509Certificate
S: serverCert
和caCert
,要验证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
应该前缀有一些这样的 :
SHA-1: (0x)30 21 30 09 06 05 2b 0e 03 02 1a 05 00 04 14 || H.
( BouncyCastle的 ,如果你想正确地分析ASN.1结构可能是有用的。)
需要注意的是这一切都不验证时间有效性或任何其他属性。 PKIX遵守远远超过检查签名(见RFC 3820和5820)。
也许一个有效的路径无法建造,因为一些中间证书丢失。 你的循环加载证书丢弃所有,但最后一次。 相反,保存所有的证书,并将其传递给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的内置工具来执行此验证。
即看,如果发行人是在服务器的信任有效的发行和现在
JSSE已经做了这一切。 你不必做任何的这个,也许除了验证对方的证书尚未过期。
文章来源: Validating a certificate in java throws an exception - unable to find valid certificate path to requested target