关于c#:在iTextSharp中的Pades LTV验证引发的公钥不是针对根CA证书的证书签名

Pades LTV verification in iTextSharp throws Public key presented not for certificate signature for root CA certificate

我得到了一个Org.BouncyCastle.Security.InvalidKeyException,在用LtvVerifier验证PDF时,出现了错误消息public key而不是用于证书签名。

此问题是在绕过了crl ldap uris的问题之后出现的。用于执行验证的代码与上一个日志相同:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
   public static bool Validate(byte[] pdfIn, X509Certificate2 cert)
    {
        using (var reader = new PdfReader(pdfIn))
        {
            var fields = reader.AcroFields;
            var signames = fields.GetSignatureNames();

            if (!signames.Any(n => fields.SignatureCoversWholeDocument(n)))
                throw new Exception("None signature covers all document");

            var verifications = signames.Select(n => fields.VerifySignature(n));

            var invalidSignature = verifications.Where(v => !v.Verify());
            var invalidTimeStamp = verifications.Where(v => !v.VerifyTimestampImprint());

            if (invalidSignature.Any())
                throw new Exception("Invalid signature found");
        }

        using (var reader = new PdfReader(pdfIn))
        {
            var ltvVerifier = new LtvVerifier(reader)
            {
                OnlineCheckingAllowed = false,
                CertificateOption = LtvVerification.CertificateOption.WHOLE_CHAIN,
                Certificates = GetChain(cert).ToList(),
                VerifyRootCertificate = false,
                Verifier = new MyVerifier(null)
            };

            var ltvResult = new List<VerificationOK> { };
            ltvVerifier.Verify(ltvResult);

            if (!ltvResult.Any())
                throw new Exception("Ltv verification failed");
        }
        return true;
   }

从证书链构建X509证书列表的辅助功能:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
    private static X509.X509Certificate[] GetChain(X509Certificate2 myCert)
    {
        var x509Chain = new X509Chain();
        x509Chain.Build(myCert);

        var chain = new List<X509.X509Certificate>();
        foreach(var cert in x509Chain.ChainElements)
        {
            chain.Add(
                DotNetUtilities.FromX509Certificate(cert.Certificate)
                );
        }

        return chain.ToArray();
    }

自定义验证器,刚从示例复制:

1
2
3
4
5
6
7
8
9
10
11
 class MyVerifier : CertificateVerifier
{
    public MyVerifier(CertificateVerifier verifier) : base(verifier) { }

    override public List<VerificationOK> Verify(
        X509.X509Certificate signCert, X509.X509Certificate issuerCert, DateTime signDate)
    {
        Console.WriteLine(signCert.SubjectDN +": ALL VERIFICATIONS DONE");
        return new List<VerificationOK>();
    }
}

如前一问题所述,我已经重新实施了LtvVerifierCrlVerifier。CRL验证完成正常。

证书链包括用于签署PDF和CA根证书的证书。函数CrlVerifier.Verify在调用下一行时引发了上述异常:

1
2
3
4
 if (verifier != null)
                result.AddRange(verifier.Verify(signCert, issuerCert, signDate));
            // verify using the previous verifier in the chain (if any)
            return result;

这是Org.BouncyCastle.Security.InvalidKeyException的相关堆栈跟踪:

1
2
3
4
5
6
7
8
9
   in Org.BouncyCastle.X509.X509Certificate.CheckSignature(AsymmetricKeyParameter publicKey, ISigner signature)
   in Org.BouncyCastle.X509.X509Certificate.Verify(AsymmetricKeyParameter key)
   in iTextSharp.text.pdf.security.CertificateVerifier.Verify(X509Certificate signCert, X509Certificate issuerCert, DateTime signDate)
   in iTextSharp.text.pdf.security.RootStoreVerifier.Verify(X509Certificate signCert, X509Certificate issuerCert, DateTime signDate)
   in PdfCommon.CrlVerifierSkippingLdap.Verify(X509Certificate signCert, X509Certificate issuerCert, DateTime signDate) in c:\Projects\digit\Fuentes\PdfCommon\CrlVerifierSkippingLdap.cs:line 76
   in iTextSharp.text.pdf.security.OcspVerifier.Verify(X509Certificate signCert, X509Certificate issuerCert, DateTime signDate)
   in PdfCommon.LtvVerifierSkippingLdap.Verify(X509Certificate signCert, X509Certificate issuerCert, DateTime sigDate) in c:\Projects\digit\Fuentes\PdfCommon\LtvVerifierSkippingLdap.cs:line 221
   in PdfCommon.LtvVerifierSkippingLdap.VerifySignature() in c:\Projects\digit\Fuentes\PdfCommon\LtvVerifierSkippingLdap.cs:line 148
   in PdfCommon.LtvVerifierSkippingLdap.Verify(List`1 result) in c:\Projects\digit\Fuentes\PdfCommon\LtvVerifierSkippingLdap.cs:line 112

以及一个链接,指向我试图验证的PDF


经过调试,结果是

IText(sharp)5.5.10 LtvVerifier在使用不以自签名证书结尾的证书链验证证书时,以观察到的方式失败。

原因

原因很简单:LtvVerifier建立了Verifier实例的序列(OcspVerifierCrlVerifierRootStoreVerifierCertificateVerifier;最后一个实例通过基类调用链接)。然后,它请求所述签名证书的证书链,对于链的每个证书,调用由该证书及其颁发者组成的证书对的Verifier序列;对于链中的最终证书,EDOCX1〔8]作为颁发者证书转发。

不幸的是,最终的VerifierCertificateVerifier假设在null发行人证书的情况下,要验证的证书是自签名的:

1
2
3
4
5
6
7
8
// Check if the signature is valid
if (issuerCert != null) {
    signCert.Verify(issuerCert.GetPublicKey());
}
// Also in case, the certificate is self-signed
else {
    signCert.Verify(signCert.GetPublicKey());
}

(来源于CertificateVerifierVerify)

如果最初请求的LtvVerifier证书链没有以自签名证书结束,则最终测试正确地导致

Org.BouncyCastle.Security.InvalidKeyException with error message Public key presented not for certificate signature

OP的例子

在手头的情况下,我们有一个由

cn=autoridad de sellado de tiempo fnmt-rcm,ou=ceres,o=fnmt-rcm,c=es

发布

c n=ac administraci_n p_blica,序列号=q2826004j,ou=ceres,o=fnmt-rcm,c=es

发布

ou=ac raiz fnmt-rcm,o=fnmt-rcm,c=es

它是自签名的。

在这种情况下,中介机构证书ac administraci_n p_blica已经在欧洲受信任名单上(参见西班牙的TL经理,"信托服务提供商","F_brica nacional de moneda y timbre-real casa de la moneda(fnmt-rcm)","信托服务","certificados reconocidos para su uso"我要去……""数字标识")。

因此,一个不需要超过前两个证书来建立信任,不需要自签名根证书。因此,只有前两个证书嵌入到时间戳中,并作为证书链返回到LtvVerifier,而不是自签名根。

结果是观察到的LtvVerifier中的误差。

怎么办?

好吧,因为我们已经开始在前面的问题中创建所涉及类的自己的副本,所以多更改它们应该是一个选项。

在这种情况下,应该另外更改RootStoreVerifier。其Verify方法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
override public List<VerificationOK> Verify(X509Certificate signCert, X509Certificate issuerCert, DateTime signDate) {
    LOGGER.Info("Root store verification:" + signCert.SubjectDN);
    // verify using the CertificateVerifier if root store is missing
    if (certificates == null)
        return base.Verify(signCert, issuerCert, signDate);
    try {
        List<VerificationOK> result = new List<VerificationOK>();
        // loop over the trusted anchors in the root store
        foreach (X509Certificate anchor in certificates) {
            try {
                signCert.Verify(anchor.GetPublicKey());
                LOGGER.Info("Certificate verified against root store");
                result.Add(new VerificationOK(signCert, this,"Certificate verified against root store."));
                result.AddRange(base.Verify(signCert, issuerCert, signDate));
                return result;
            } catch (GeneralSecurityException) {}
        }
        result.AddRange(base.Verify(signCert, issuerCert, signDate));
        return result;
    } catch (GeneralSecurityException) {
        return base.Verify(signCert, issuerCert, signDate);
    }
}

我们只需去掉标记线

1
2
3
4
5
6
7
                signCert.Verify(anchor.GetPublicKey());
                LOGGER.Info("Certificate verified against root store");
                result.Add(new VerificationOK(signCert, this,"Certificate verified against root store."));
                // vvv remove
                result.AddRange(base.Verify(signCert, issuerCert, signDate));
                // ^^^ remove
                return result;

在内try区块。由于我们在此刚刚确定证书signCert由一个信任锚签署,因此不需要base.Verify