Search code examples
javacertificatebouncycastlepkcs#7

Verifying PKCS#7 certificates in Java


Need some help with crypto routines in Java.

Given a PKCS#7 signature, I want to verify all certificates it contains against a trusted store. I assume that all certificates contained in signature are in the correct order to form a valid certificate path (or chain, whatever), so that

  • topmost (#0) is a signing certificate;
  • next one (#1) is an intermediate certificate, used to sign #0;
  • next one (#2) is another intermediate certificate, used to sign #1;
  • and so on.

The last certificate (#N) is signed by CA.

That's what I've managed to hack so far:

// Exception handling skipped for readability

//byte[] signature = ...
pkcs7 = new PKCS7(signature); // `sun.security.pkcs.PKCS7;`

// *** Checking some PKCS#7 parameters here

X509Certificate prevCert = null; // Previous certificate we've found
X509Certificate[] certs = pkcs7.getCertificates(); // `java.security.cert.X509Certificate`
for (int i = 0; i < certs.length; i++) {
    // *** Checking certificate validity period here

    if (cert != null) {
        // Verify previous certificate in chain against this one
        prevCert.verify(certs[i].getPublicKey());
    }
    prevCert = certs[i];
}

//String keyStorePath = ...
KeyStore keyStore = KeyStore.getInstance("JKS"); // `java.security.KeyStore`
keyStore.load(new FileInputStream(keyStorePath), null);

// Get trusted VeriSign class 1 certificate
Certificate caCert = keyStore.getCertificate("verisignclass1ca"); // `java.security.cert.Certificate`

// Verify last certificate against trusted certificate
cert.verify(caCert.getPublicKey());

So the question is -- how can this be done using standard Java classes like CertPath and friends? I have a strong feeling I'm re-inventing a bicycle. Or, if someone has an example with BouncyCastle library, that would also be fine.

Bonus question: how to verify a certificate against a trusted store so that root certificate is selected automatically?


Solution

  • Found the solution myself. So, here's how one can extract and validate a certificate chain against the trusted store (exception handling skipped for readability):

    CertificateFactory cf = CertificateFactory.getInstance("X.509");
    
    // Get ContentInfo
    //byte[] signature = ... // PKCS#7 signature bytes
    InputStream signatureIn = new ByteArrayInputStream(signature);
    DERObject obj = new ASN1InputStream(signatureIn).readObject();
    ContentInfo contentInfo = ContentInfo.getInstance(obj);
    
    // Extract certificates
    SignedData signedData = SignedData.getInstance(contentInfo.getContent());
    Enumeration certificates = signedData.getCertificates().getObjects();
    
    // Build certificate path
    List certList = new ArrayList();
    while (certificates.hasMoreElements()) {
        DERObject certObj = (DERObject) certificates.nextElement();
        InputStream in = new ByteArrayInputStream(certObj.getDEREncoded());
        certList.add(cf.generateCertificate(in));
    }
    CertPath certPath = cf.generateCertPath(certList);
    
    // Load key store
    //String keyStorePath = ...
    KeyStore keyStore = KeyStore.getInstance("JKS");
    keyStore.load(new FileInputStream(keyStorePath), null);
    
    // Set validation parameters
    PKIXParameters params = new PKIXParameters(keyStore);
    params.setRevocationEnabled(false); // to avoid exception on empty CRL
    
    // Validate certificate path
    CertPathValidator validator = CertPathValidator.getInstance("PKIX");
    CertPathValidatorResult result = validator.validate(certPath, params);
    

    validate() will throw an exception if validation fails.

    Docs: ASN1Set, ContentInfo, SignedData. All other exotic names and related docs can be found in java.security.cert.

    No SUN-dependencies here, only BouncyCastle provider library is needed.

    This question (and especially an answer) may help too.