8

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?

Ashwin
  • 12,691
  • 31
  • 118
  • 190
  • It's worth mentioning that by looking at this code it's impossible to tell why PKIX builder can't build the path. But adding -Djava.security.debug=all to java options will provide the debugging output from the builder and can shed some light on the problem. – Pawel Veselov Aug 29 '15 at 04:00

3 Answers3

14

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

Community
  • 1
  • 1
Bruno
  • 119,590
  • 31
  • 270
  • 376
  • 1
    Look at the "*Example of Building a Certification Path using the PKIX algorithm*": you need to add a CertStore to your params with all the certificates involved. – Bruno May 02 '12 at 14:45
4

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.

erickson
  • 265,237
  • 58
  • 395
  • 493
  • Based on [this duplicate](http://stackoverflow.com/q/10413767/372643) question, the certificate comes from an HTML form. It's not clear how this is then tied to the holder of the private key matching the public key in that certificate. Besides this, it's not clear what attributes should be checked (e.g. key usage) for their application. – Bruno May 02 '12 at 16:36
  • @erickson : There are no intermediate certificates. Like I said the client.crt is uploaded by the client. For test purposes I have created a ca and signed the client.csr using ca.crt to give client.crt. I added the ca.crt file to the "trustedca" and from the client machine uploaded the client.crt file. So you see there are no intermediate certificates. The ca certificate in present in the the keystore "trustedca". I have added this and the target certificate to the the PKIXBuilderParameters – Ashwin May 03 '12 at 04:38
  • @Ashwin, it's not just the intermediate certificate, you need to add the End Entity Certificate you want to check too. `X509CertSelector` is just a selector, you're not actually adding it to the collection of certificate to use when you add the cert to the selector. Read [this section](http://docs.oracle.com/javase/6/docs/technotes/guides/security/certpath/CertPathProgGuide.html#PKIXBuilderParameters) again, or look at how erickson's code is initialising `intermediate`. – Bruno May 03 '12 at 08:59
1

i.e see if the issuer is a valid issuer and present in the server's truststore

JSSE already does all that. You don't have to do any of this, except maybe verify that the peer certificate hasn't expired.

user207421
  • 305,947
  • 44
  • 307
  • 483
  • I am not doing this for ssl. I want to store the client certificate in my database. But before that I want to verify that it has been signed by a valid ca. The client uploads his certificate in a html form field. – Ashwin May 02 '12 at 10:00
  • @EJP I dint get you. Actually I also have same issue. When I added a certificate from my client into my truststore, it just added it though the CA certificate which signed the Client's certificate was not available in my system – suraj May 02 '12 at 10:02
  • 1
    @suraj You didn't get *what?* When you add a certificate into your truststore, you are telling the truststore to trust that certificate. If you don't trust it, don't add it. – user207421 May 02 '12 at 10:06
  • @EJP Yea but before adding I want to make sure that the certificate is signed by a authorised CA right? – suraj May 02 '12 at 10:07
  • 1
    @suraj If it is signed by an authorized CA you don't have to add it at all. The truststore distributed with Java already trusts all the recognized CAs. That's the whole idea. – user207421 May 02 '12 at 10:08
  • @EJP : I am not using the truststore distributed with java. I am using my own truststore in which the trusted certificate authorities are present. I want to check whether the client certificate is signed by anyone of these authorities or not. – Ashwin May 02 '12 at 10:11
  • @EJP Suppose u r a client. You are uploading ur certificate to me(I am server). In server, your certificate must be added into truststore. How to achieve that u tell me? – suraj May 02 '12 at 10:11
  • 1
    @suraj Your truststore should initially contain everything that is in the truststore distributed with the JDK, or at least those CA certificates that you are prepared to trust. This mechanism *avoids* having to load client certificates into the truststore at all. This is what the truststore mechanism and indeed the entire X.509 mechanism is all about. – user207421 May 02 '12 at 10:12
  • @EJP One of my client uploaded a self signed certificate but unfortunately that certificate was entered into truststore without showing any errors. Any suggestions on that? – suraj May 02 '12 at 10:15
  • @EJP my truststore is not default java truststore – suraj May 02 '12 at 10:16
  • 1
    @suraj If you put a self-signed certificate into your truststore, you trust it. That's what the operation means. If you don't trust it, don't add it. I've already said all that. You already said that your truststore isn't the default Java truststore. My question is why not? It sounds to me like you are doing this all wrong. You shouldn't have to write code for certificate takeon at all, as it should be an offline process with independent verification. Otherwise all you are doing is breaking the security built in to SSL. – user207421 May 02 '12 at 10:17
  • @EJP Actually I will use this client certificate in future to extract its public key for encryption. So I must store the certificate into my store. – suraj May 02 '12 at 10:19
  • @EJP This is not being used for ssl my dear friend. I want the certificates(Only trusted) to be stored into my database for future reference. – suraj May 02 '12 at 10:21
  • 1
    @suraj Please stop posting two comments for every one of mine. I am finding it quite impossible to keep up. – user207421 May 02 '12 at 10:23
  • @EJP Ok i will post one at a time – suraj May 02 '12 at 10:27
  • 1
    @EJP See the certificate used for ssl is nowhere related to certificate he is uploading.Both are different. Point no.2 I have all the trusted CA's certificate into my trust store. I just wanna check the certificate that client is uploading is signed by any of these' CA? – suraj May 02 '12 at 11:10
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/10784/discussion-between-suraj-and-ejp) – suraj May 02 '12 at 11:13
  • 3
    @suraj and Ashwin, you're no longer new to SO. You need to [know what you ask](http://stackoverflow.com/q/10245473/372643), try [not to redirect the question completely in comments](http://security.stackexchange.com/q/14330/2435), and avoid duplicates [here](http://stackoverflow.com/q/10413767/372643) and [here](http://stackoverflow.com/q/10384669/372643). – Bruno May 02 '12 at 13:40