我收到一个带有错误消息的Org.BouncyCastle.Security.InvalidKeyException当使用LtvVerifier验证pdf时,公钥不会出现在证书签名中.
此问题在circumventing an issue with CRL LDAP URIs之后出现.用于执行验证的代码与上一篇文章相同:
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;
}
从证书链构建X509Certificates列表的辅助功能:
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();
}
自定义验证程序,仅从示例中复制:
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>();
}
}
我已经重新实现了LtvVerifier和CrlVerifier,如前一个问题所述. CRL验证完成.
证书链包括用于签署PDF和CA根证书的证书.函数CrlVerifier.Verify在调用下一行时抛出提到的异常:
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
而且是link to the pdf that I try to validate
最佳答案 经过一些调试后发现它
iText(夏普)5.5.10 LtvVerifier在验证证书链未以自签名证书结尾的证书时以观察到的方式失败.
原因
原因很简单:LtvVerifier建立一系列Verifier实例(OcspVerifier,CrlVerifier,RootStoreVerifier,CertificateVerifier;最后一个通过基类调用链接).然后它请求有关签名的签名证书的证书链,并且对于链的每个证书,调用由该证书及其发行者组成的证书对的Verifier序列;在链中的最终证书的情况下,null作为颁发者证书转发.
不幸的是,最终的Verifier,CertificateVerifier,假设在空发行者证书的情况下,要验证的证书是自签名的:
// 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方法验证)
如果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
由…发出
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已经在欧洲可信列表中(参见the TL manager for Spain,“信托服务提供商”,“FábricaNacionalde Moneda y Timbre – Real Casa de la Moneda(FNMT-RCM)”,“信托服务“,”Certificados reconocidos para su uso enelámbitode…“,”数字身份“).
因此,建立信任不需要超过前两个证书,不需要自签名根证书.因此,只有前两个证书嵌入时间戳中,并作为证书链返回给LtvVerifier,而不是自签名根.
结果是LtvVerifier中观察到的错误.
该怎么办?
好吧,因为我们已经开始在previous question中创建我们自己的相关类的副本,所以更改它们应该是一个选项.
在这种情况下,还应该更改RootStoreVerifier.它的Verify方法如下所示:
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);
}
}
我们只需要删除标记的行
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;
在内部尝试块.由于我们刚刚建立了证书signCert由信任锚签名,因此无需base.Verify.