Validating a certificate in Java throws an exception – a valid certificate path for the requested target cannot be found
I have a web application that requires the client to send its certificate. The server must verify the certificate (that is, check whether the issuer is a valid issuer and appears in the server's trust store) This 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 got an exception:
sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target<br>
Note: the certificate sent by the client here is client CRT, used to sign client The certificate of the CRT certificate is the ca.crt existing in the keystore "trustedca" So why give this exception?
Solution
If you expect to get a client certificate, please let JSSE do all this for you If you want to use a specific connection for your own truststore, configure JSSE to use it Check the customizing JSSE section in the reference documentation
The following is a short example of using a custom truststore to build sslcontext (other more complex x509 trust managers can also be used, but you rarely need it.)
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);
If you are using an existing application server, how it is delivered through configuration will depend on the server and how it is expected to be configured Using JSSE for this will also ensure that the key usage properties are appropriate
If you obtain a certificate by other means and want to verify it, you need to use PKI API If you follow the example of validating a certification path using the PKIX algorithm, you should get 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);
Check the result from validate (of course, it doesn't throw a validation exception) Here, I have disabled undo checking to simplify You can also set other aspects of pkixparameters for policy checking This can become very complicated (why it's best to let the default JSSE manager do it for you)
You are still in security SE:What is the actual value of a certificate fingerprint? All these questions were asked in the context of another question raised on
Suppose you have two x509certificates: servercert and cacert, where you want to verify whether servercert is signed by cacert (the private key matching the public key)
The simplest way:
serverCert.verify(caCert.getPublicKey());
If you want to do this 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()));
Assuming that the algorithm is sha1withrsa, you can also calculate the summary:
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());
The summary itself is only part of the result of using cipher: you get it from servercert What getsignature () gets is actually a more complex ASN 1 structure, including the digest algorithm identifier. In this case, 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.
(bouncy castle may be useful if you want to correctly analyze the ASN. 1 structure.)
Please note that none of these will verify time validity or any other properties PKIX compliance is more than just checking signatures (see RFC 3820 and 5820)