Wednesday, 9 October 2013

Java Certificate Path Validation Weirdness

I had to create some code that would validate a PKCS#7 signedData structure using a set of pre-configured trust points. We also need to verify the signer certificate has not been revoked and we wanted to support both OCSP based revocation checking (based on embedded AIA extension) and CRL Distribution Points for retrieving CRLs from HTTP locations.

I wrote some code in Java to do this based on sample code on the Web. Essentially I used a Certficate Path Builder which would both locate a path based on a bucket of certificates and set of trust anchors (trusted certificates) and validate the path (including checking the revocation status).

            Set<TrustAnchor> trustAnchors = buildAnchors();

            X509CertSelector selector = new X509CertSelector();
            selector.setCertificate(certificate);
            
            PKIXBuilderParameters params = new                                   PKIXBuilderParameters(
                   trustAnchors, selector ); 

            params.addCertStore(
                makeAdditionlCertStore(
                     certificate,additionalCerts));

            params.setDate(messageSigningDate);

            //
            //  Enable OCSP lookup
            //
            Security.setProperty("ocsp.enable","true");
            
            //
            //  Enable Certificate Revocation List Distribution 
            //  Points (CRLDP) support
            //
            System.setProperty(
                 "com.sun.security.enableCRLDP","true");
            
            CertPathBuilder builder = 
                CertPathBuilder.getInstance("PKIX");
        
            return builder.build(params);

This seemed to be working fine. I tested this with a SSL path issued by a public CA and it was able to get the revocation status and all was well. I disabled OCSP and it still fetched the revocation status using a CRLDP extension in the certificate.

What I didn't initially check was that if I disabled CRLDP it failed even though the path I was testing had an AIA extension pointing at an OCSP server which was accessible. I did some Googling on this and found this which seems to indicate OCSP is not supported via the path builder before JDK 1.8!

So then I re-wrote the code to use the certificate path validator and again this didn't work but this time I got a wrong key usage exception. Well according to this there is a bug in 1.7 update 6 through 10 where OCSP fails with this error!

I updated to update 25 and sure enough the problem goes away.

The problem with the path validator is it literally does what the name implies and so unless you have a path already built for you then you either have to use the path builder or do it yourself. With PKCS#7 structures the ordering of the additional certificates is arbitrary (well they are ordered lexicographically on their encoding according to DER as they are sent as part of a SET) so you can't assume they will be in path order.

My solution in the end was to use the path builder to build the path but turn off revocation checking and then once I have a path to validate it with the path validator. This is going to be pretty inefficient but then what choice have I got until I can upgrade to 1.8?

        public void validateCertificate( 
            X509Certificate       certificate,
            List<X509Certificate> additionalCerts )
                    {
            Set<TrustAnchor> trustAnchors = 
                buildAnchors();

            CertPath cp = 
                 makeCertificatePath(
                     trustAnchors,
                     certificate, 
                     additionalCerts,
                     messageSigningDate);
            
            PKIXParameters params = 
                 new PKIXParameters(trustAnchors);

            params.setDate(messageSigningDate);
            params.setRevocationEnabled(true);
            params.setSigProvider("BC");
            
            //
            //  Enable OCSP lookup
            //
            Security.setProperty("ocsp.enable","true");
            
            //
            //  Enable Certificate Revocation List Distribution 
            //  Points (CRLDP) support
            //
            System.setProperty(
                "com.sun.security.enableCRLDP","true");
            
            CertPathValidator cpv = 
                CertPathValidator.getInstance("PKIX");

            return cpv.validate(cp, params);
        }

    private CertPath makeCertificatePath(
            Set<TrustAnchor>        trustAnchors,
            X509Certificate         certificate,
            List<X509Certificate>   additionalCerts, 
            Date                    messageSigningDate) 
                    throws  CertificateException, 
                            InvalidAlgorithmParameterException, 
                            NoSuchAlgorithmException, 
                            CertPathBuilderException
    {
        X509CertSelector selector = new X509CertSelector();
        selector.setCertificate(certificate);
        
        PKIXBuilderParameters params = new 
            PKIXBuilderParameters(trustAnchors, selector );
        params.addCertStore(
            makeAdditionlCertStore(certificate,additionalCerts));

        params.setDate(messageSigningDate);
        params.setRevocationEnabled(false);
        params.setSigProvider("BC");

        //
        //  Throws if path building/validation fails
        //
        CertPathBuilder builder = 
            CertPathBuilder.getInstance("PKIX");
        
        CertPathBuilderResult result = 
           builder.build(params);
        return result.getCertPath();
    }



No comments:

Post a Comment