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

匿名 (未验证) 提交于 2019-12-03 00:53:01

问题:

I'm getting an Org.BouncyCastle.Security.InvalidKeyException with error message Public key presented not for certificate signature when validating a pdf with LtvVerifier.

This problem has arisen after circumventing an issue with CRL LDAP URIs. The code used to perform the verification is the same as the previous post:

   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;    }

Auxiliary function that builds a List of X509Certificates from the certificate chain:

    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();     }

A custom verifier, just copied from sample:

 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>();     } }

I have re-implemented LtvVerifier and CrlVerifier as explained in the previous question. The CRL validation is done ok.

The certificate chain includes the certificate that was used to sign the PDF and the CA root certificate. The function CrlVerifier.Verify is throwing the mentioned exception when calling the next line:

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

And this is the relevant stack trace of Org.BouncyCastle.Security.InvalidKeyException:

   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

And a link to the pdf that I try to validate

回答1:

After some debugging it turns out that

iText(Sharp) 5.5.10 LtvVerifier fails in the observed manner when verifying certificates with certificate chains not ending in a self-signed certificate.

The cause

The reason is pretty simple: LtvVerifier establishes a sequence of Verifier instances (OcspVerifier, CrlVerifier, RootStoreVerifier, CertificateVerifier; the last one chained via base class calls). Then it requests the certificate chain of the signing certificate of the signature in question and for each certificate of the chain calls the Verifier sequence for the certificate couple consisting of this certificate and its issuer; in case of the final certificate in the chain, null is forwarded as issuer certificate.

Unfortunately the final Verifier, the CertificateVerifier, assumes in case of a null issuer certificate that the certificate to verify is self-signed:

// 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()); } 

(from CertificateVerifier method Verify)

If the certificate chain the LtvVerifier initially requested does not end in a self-signed certificate, that final test correctly results in the observed

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

The OP's example

In the case at hand we have a document timestamp issued by

cn=AUTORIDAD DE SELLADO DE TIEMPO FNMT-RCM, ou=CERES, o=FNMT-RCM, c=ES

issued by

cn=AC Administración Pública, serialNumber=Q2826004J, ou=CERES, o=FNMT-RCM, c=ES

issued by

ou=AC RAIZ FNMT-RCM, o=FNMT-RCM, c=ES

which is self-signed.

In this case already the intermediary certificate, AC Administración Pública, is on the European trusted list (cf. the TL manager for Spain, "Trust Service Provider", "Fábrica Nacional de Moneda y Timbre - Real Casa de la Moneda (FNMT-RCM)", "Trust Service", "Certificados reconocidos para su uso en el ámbito de... ", "Digital Identity").

Thus, one does not need more than the first two certificates to establish trust, the self signed root certificate is not needed. As a consequence only these first two certificates are embedded in the time stamp and returned as certificate chain to the LtvVerifier, not the self signed root.

The result is the observed error in the LtvVerifier.

What to do?

Well, as we already started creating our own copies of the involved classes in the previous question, changing them a bit more should be an option.

In this case one should additionally change the RootStoreVerifier. Its Verify method looks like this:

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);     } }

We merely have to remove the marked line

                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;

in the inner try block. As we here have just established that the certificate signCert is signed by a trust anchor, there is no need for the base.Verify anyways.



标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!