在 iTextSharp 中 Pades LTV 验证抛出 Public 密钥不用于根 CA 证书的证书签名
Pades LTV verification in iTextSharp throws Public key presented not for certificate signature for root CA certificate
在使用 LtvVerifier
验证 pdf 时,我收到 Org.BouncyCastle.Security.InvalidKeyException
错误消息 Public key presented not for certificate signature。
这个问题是在之后出现的。用于执行验证的代码和前面一样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;
}
从证书链构建 X509 证书列表的辅助函数:
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
调试后发现
iText(Sharp) 5.5.10 LtvVerifier
在验证证书链未以 self-signed 证书结尾的证书时,以观察到的方式失败。
原因
原因很简单:LtvVerifier
建立了 Verifier
个实例的序列(OcspVerifier
、CrlVerifier
、RootStoreVerifier
、CertificateVerifier
; 最后一个通过 base class 调用链接)。然后它请求相关签名的签名证书的证书链,并为链中的每个证书调用由该证书及其颁发者组成的证书对的 Verifier
序列;如果是链中的最终证书,null
将作为颁发者证书转发。
不幸的是,最终 Verifier
,即 CertificateVerifier
,假设 null
颁发者证书要验证的证书是 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());
}
(来自CertificateVerifier
方法Verify
)
如果 LtvVerifier
最初请求的证书链没有以 self-signed 证书结尾,则最终测试正确地导致观察到的
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=AC Administración Pública, serialNumber=Q2826004J, ou=CERES, o=FNMT-RCM, c=ES
发布者
ou=AC RAIZ FNMT-RCM, o=FNMT-RCM, c=ES
即self-signed。
在这种情况下,中间证书 AC Administración Pública 已经在欧洲信任列表中(参见 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").
因此,建立信任不需要超过前两个证书,不需要自签名根证书。因此,只有前两个证书嵌入时间戳中并作为证书链返回给 LtvVerifier
,而不是自签名根。
结果是 LtvVerifier
中观察到的错误。
怎么办?
好吧,由于我们已经开始在 中创建我们自己的相关 classes 副本,因此可以选择对它们进行更多更改。
在这种情况下,应该另外更改 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;
在内部 try
块中。由于我们刚刚确定证书 signCert
是由信任锚签署的,因此无论如何都不需要 base.Verify
。
在使用 LtvVerifier
验证 pdf 时,我收到 Org.BouncyCastle.Security.InvalidKeyException
错误消息 Public key presented not for certificate signature。
这个问题是在
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 证书列表的辅助函数:
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
调试后发现
iText(Sharp) 5.5.10 LtvVerifier
在验证证书链未以 self-signed 证书结尾的证书时,以观察到的方式失败。
原因
原因很简单:LtvVerifier
建立了 Verifier
个实例的序列(OcspVerifier
、CrlVerifier
、RootStoreVerifier
、CertificateVerifier
; 最后一个通过 base class 调用链接)。然后它请求相关签名的签名证书的证书链,并为链中的每个证书调用由该证书及其颁发者组成的证书对的 Verifier
序列;如果是链中的最终证书,null
将作为颁发者证书转发。
不幸的是,最终 Verifier
,即 CertificateVerifier
,假设 null
颁发者证书要验证的证书是 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());
}
(来自CertificateVerifier
方法Verify
)
如果 LtvVerifier
最初请求的证书链没有以 self-signed 证书结尾,则最终测试正确地导致观察到的
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=AC Administración Pública, serialNumber=Q2826004J, ou=CERES, o=FNMT-RCM, c=ES
发布者
ou=AC RAIZ FNMT-RCM, o=FNMT-RCM, c=ES
即self-signed。
在这种情况下,中间证书 AC Administración Pública 已经在欧洲信任列表中(参见 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").
因此,建立信任不需要超过前两个证书,不需要自签名根证书。因此,只有前两个证书嵌入时间戳中并作为证书链返回给 LtvVerifier
,而不是自签名根。
结果是 LtvVerifier
中观察到的错误。
怎么办?
好吧,由于我们已经开始在
在这种情况下,应该另外更改 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;
在内部 try
块中。由于我们刚刚确定证书 signCert
是由信任锚签署的,因此无论如何都不需要 base.Verify
。