Validating a certificate in java throws an exception - unable to find valid certificate path to requested target

Ashwin picture Ashwin · May 2, 2012 · Viewed 8.8k times · Source

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>

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?

Answer

Bruno picture Bruno · May 2, 2012

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.

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

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.

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

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


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?.

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.

The simplest way:

serverCert.verify(caCert.getPublicKey());

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

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

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.

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

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