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?
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 X509TrustManager
s 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 X509Certificate
s: 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).
Maybe a valid path can't be constructed because some intermediate certificates are missing. Your loop to load certificates discards all but the last. Instead, save all of those certificates, and pass them to the CertPathBuilder
to aid in path construction.
Another common problem is that revocation checks are performed by default, which is good for security. If you don't understand how to obtain a CRL, or utilize OCSP, you can diminish your security and disable revocation checks. This is also shown in the example below.
...
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);
...
It would help to know how you are obtaining the "client.crt" file and what its contents are expected to be. Like the responders, I wonder why you can't use the built-in facilities of JSSE to perform this validation.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With