iTextSharp中的Pades LTVvalidation会引发公钥,而不是根CA证书的证书签名
我收到一个带有错误消息的Org.BouncyCastle.Security.InvalidKeyException
当使用LtvVerifier
validationpdf时, 公钥不会出现在证书签名中 。
在绕过CRL LDAP URI的问题之后出现此问题。 用于执行validation的代码与上一篇文章相同:
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 { }; ltvVerifier.Verify(ltvResult); if (!ltvResult.Any()) throw new Exception("Ltv verification failed"); } return true; }
从证书链构建X509Certificates列表的辅助function:
private static X509.X509Certificate[] GetChain(X509Certificate2 myCert) { var x509Chain = new X509Chain(); x509Chain.Build(myCert); var chain = new List(); foreach(var cert in x509Chain.ChainElements) { chain.Add( DotNetUtilities.FromX509Certificate(cert.Certificate) ); } return chain.ToArray(); }
自定义validation程序,仅从示例中复制:
class MyVerifier : CertificateVerifier { public MyVerifier(CertificateVerifier verifier) : base(verifier) { } override public List Verify( X509.X509Certificate signCert, X509.X509Certificate issuerCert, DateTime signDate) { Console.WriteLine(signCert.SubjectDN + ": ALL VERIFICATIONS DONE"); return new List(); } }
我已经重新实现了LtvVerifier
和CrlVerifier
如前一个问题所述。 CRLvalidation完成。
证书链包括用于对PDF和CA根证书进行签名的证书。 函数CrlVerifier.Verify
在调用下一行时抛出提到的exception:
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
的相关堆栈跟踪:
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
以及我尝试validation的pdf链接
经过一些调试后发现它
iText(夏普)5.5.10 LtvVerifier
在validation证书链未以自签名证书结尾的证书时以观察到的方式失败。
原因
原因很简单: LtvVerifier
建立一系列Verifier
实例( OcspVerifier
, CrlVerifier
, RootStoreVerifier
, CertificateVerifier
;最后一个通过基类调用链接)。 然后,它请求有关签名的签名证书的证书链,并且对于链的每个证书,调用由该证书及其颁发者组成的证书对的Verifier
序列; 如果链中有最终证书,则null
作为颁发者证书转发。
不幸的是,最终的Verifier
, CertificateVerifier
,假设null
发行者证书,要validation的证书是自签名的:
// 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()); }
(来自CertificateVerifier
方法Verify
)
如果LtvVerifier
最初请求的证书链没有以自签名证书结束,则最终测试正确导致观察到
带有错误消息的
Org.BouncyCastle.Security.InvalidKeyException
显示的公钥不适用于证书签名
OP的例子
在手头的情况下,我们有一个文档时间戳发布
cn = AUTORIDAD DE SELLADO DE TIEMPO FNMT-RCM,ou = CERES,o = FNMT-RCM,c = ES
由…发出
cn =ACAdministraciónPública,serialNumber = Q2826004J,ou = CERES,o = FNMT-RCM,c = ES
由…发出
ou = AC RAIZ FNMT-RCM,o = FNMT-RCM,c = ES
这是自签名的。
在这种情况下,中间证书ACAdministraciónPública已经在欧洲可信列表中(参见西class牙TL经理 ,“信托服务提供商”,“FábricaNacionalde Moneda y Timbre – Real Casa de la Moneda(FNMT-RCM) )“,”信托服务“,”Certificados reconocidos para su uso enelámbitode…“,”Digital Identity“)。
因此,建立信任不需要超过前两个证书,不需要自签名根证书。 因此,只有前两个证书嵌入时间戳中,并作为证书链返回给LtvVerifier
,而不是自签名根。
结果是LtvVerifier
观察到的错误。
该怎么办?
好吧,因为我们已经开始在上一个问题中创建我们自己的相关类的副本,所以更改它们应该是一个选项。
在这种情况下,还应该更改RootStoreVerifier
。 它的Verify
方法如下所示:
override public List 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 result = new List (); // 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); } }
我们只需要删除标记的行
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
。