Combining All of the Tasks Needed to Verify a PKCS

2020-07-11 10:04发布

问题:

I've been banging my head against the wall with this problem for about 20 hours now and I am probably missing something easy. However, I've gotten to the point where I think I need help. I have read dozens of explanations for how to do different parts of the problem, but I cannot figure out how to bring them all together.

I have a DER-encoded detached PKCS#7 digital signature. The signature conforms to RFC 3852 (Cryptographic Message Syntax). For my project I need to step through each of the steps needed to verify a signature and be able to tell which step the verification failed on. I am using BouncyCastle in Java.

To my understanding there are six basic steps needed to verify a digital signature

  1. Verify that the root certificate is a trusted certificate
  2. Verify the certificate chain from the root certificate to the signing certificate
  3. Verify that the signer's name is who you would expect it to be
  4. Verify that the certificates are not expired
  5. Verify that the certificates do not appear on a CRL (for simplicity assume that the CRL is already downloaded locally)
  6. Verify the digest in the signature matches the digest of the document

EDIT: Several comments requested adding an OSCP check to the list.

In the BouncyCastle test code, I was able to find the following example. It seems to accomplish 2/6, however, it is not clear that it does any of the of the tasks. If anyone could point me in the right direction for the rest of the tasks it would be greatly appreciated.

CMSSignedData s = ...
byte[] contentDigest = ...

Store certStore = s.getCertificates();
Store crlStore = s.getCRLs();
SignerInformationStore  signers = s.getSignerInfos();

Collection c = signers.getSigners();
Iterator it = c.iterator();

while (it.hasNext())
{
    SignerInformation   signer = (SignerInformation)it.next();
    Collection certCollection = certStore.getMatches(signer.getSID());

    Iterator certIt = certCollection.iterator();
    X509CertificateHolder cert = (X509CertificateHolder)certIt.next();

    assertEquals(true, signer.verify(new JcaSimpleSignerInfoVerifierBuilder().setProvider(BC).build(cert)));

    if (contentDigest != null)
    {
        assertTrue(MessageDigest.isEqual(contentDigest, signer.getContentDigest()));
    }
}

Collection certColl = certStore.getMatches(null);
Collection crlColl = crlStore.getMatches(null);

assertEquals(certColl.size(), s.getCertificates().getMatches(null).size());
assertEquals(crlColl.size(), s.getCRLs().getMatches(null).size());

回答1:

The most complex in a CMS signed data validation is the X.509 validation part. For each signer in the signed data The steps are as follows:

1. Find the signer certificate

SignerInformation signerInfo = (SignerInformation)it.next();
Collection certCollection = certStore.getMatches(signerInfo.getSID());
Iterator certIt = certCollection.iterator();
X509CertificateHolder signerCertificateHolder = (X509CertificateHolder)certIt.next();

I assume here there is only one matching certificate.

2. Find a certificate chain from the signer certificate to a trusted root certificate

Here you are to inspect the certificates in the Store in order to find the certificate chain. Use the subject DN/Issuer DN and Subject Key Identifier/Authority Key Identifier matches to build that chain.

3. Validate the chain

This step a recursive validation of every certificate in the chain. For each certificate, starting from the certificate directly under the root certificate you need to check:

  1. the certificate signature with the issuer public key
  2. the validity date of the certificate
  3. the revocation status of the certificate

Hopefully for steps 2 & 3 you can use the built-in PKIX certificate path builder and validation mechanisms:

KeyStore trustAnchors = getTrustAnchors();
X509CertSelector target = new X509CertSelector();
target.setCertificate(signerCertificate);
PKIXBuilderParameters params = new PKIXBuilderParameters(anchors, target);
CertStoreParameters additionalCerts = new CollectionCertStoreParameters(allOtherCerts)
params.addCertStore(CertStore.getInstance("Collection", additionalCerts));
CertStoreParameters revocationObjects = new CollectionCertStoreParameters(allCRLs);
params.addCertStore(CertStore.getInstance("Collection", revocationObjects));
CertPathBuilder builder = CertPathBuilder.getInstance("PKIX");
PKIXCertPathBuilderResult r = (PKIXCertPathBuilderResult) builder.build(params);
/* if the build method returns without exception, the certificate chain is valid */

For the sake of readability I omited the convertion of objects between java(x).crypto an Bouncycastle types.

4. Validate the SignerInfo signature with the public key

JcaSimpleSignerInfoVerifierBuilder builder = new JcaSimpleSignerInfoVerifierBuilder();
SignerInformationVerifier verifier = builder.build(signerCertificateHolder);
assertTrue(signerInfo.verify(verifier));

5. Validate that the document digest matches the signed digest

byte[] contentDigest = computeDigest(originalDoc, signerInfo.getDigestAlgOID());
assertArrayEquals(contentDigest, signer.getContentDigest());