结合验证PKCS#7签名所需的所有任务

Bro*_*oks 5 java cryptography bouncycastle digital-signature

我一直在用这个问题敲打墙头约20个小时,我可能会错过一些简单的事情.但是,我已经到了我认为需要帮助的地步.我已经阅读了几十个关于如何解决问题的不同部分的解释,但我无法弄清楚如何将它们整合在一起.

我有一个DER编码的分离式PKCS#7数字签名.签名符合RFC 3852(加密消息语法).对于我的项目,我需要逐步完成验证签名所需的每个步骤,并能够确定验证失败的步骤.我在Java中使用BouncyCastle.

据我了解,验证数字签名需要六个基本步骤

  1. 验证根证书是否为可信证书
  2. 验证从根证书到签名证书的证书链
  3. 验证签名者的姓名是您期望的名称
  4. 验证证书是否已过期
  5. 验证证书是否未出现在CRL上(为简单起见,假设已在本地下载CRL)
  6. 验证签名中的摘要是否与文档的摘要匹配

编辑:几条评论要求在列表中添加OSCP检查.

在BouncyCastle测试代码中,我能够找到以下示例.它似乎完成了2/6,然而,它并不清楚它是否完成了任何任务.如果有人能指出我正确的方向完成剩下的任务,我们将不胜感激.

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());
Run Code Online (Sandbox Code Playgroud)

Jcs*_*Jcs 6

CMS签名数据验证中最复杂的是X.509验证部分.对于签名数据中的每个签名者,步骤如下:

1.找到签名者证书

SignerInformation signerInfo = (SignerInformation)it.next();
Collection certCollection = certStore.getMatches(signerInfo.getSID());
Iterator certIt = certCollection.iterator();
X509CertificateHolder signerCertificateHolder = (X509CertificateHolder)certIt.next();
Run Code Online (Sandbox Code Playgroud)

我假设这里只有一个匹配的证书.

2.查找从签署者证书到受信任根证书的证书链

在这里,您要检查证书Store,以便找到证书链.使用主题DN /颁发者DN和主题密钥标识符/授权密钥标识符匹配来构建该链.

3.验证链

此步骤对链中的每个证书进行递归验证.对于每个证书,从直接在根证书下的证书开始,您需要检查:

  1. 带有颁发者公钥的证书签名
  2. 证书的有效期
  3. 证书的撤销状态

希望对于步骤2和3,您可以使用内置的PKIX证书路径构建器和验证机制:

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 */
Run Code Online (Sandbox Code Playgroud)

为了便于阅读,我省略java(x).crypto了Bouncycastle类型之间的对象转换.

4.使用公钥验证SignerInfo签名

JcaSimpleSignerInfoVerifierBuilder builder = new JcaSimpleSignerInfoVerifierBuilder();
SignerInformationVerifier verifier = builder.build(signerCertificateHolder);
assertTrue(signerInfo.verify(verifier));
Run Code Online (Sandbox Code Playgroud)

5.验证文档摘要是否与签名摘要匹配

byte[] contentDigest = computeDigest(originalDoc, signerInfo.getDigestAlgOID());
assertArrayEquals(contentDigest, signer.getContentDigest());
Run Code Online (Sandbox Code Playgroud)