我正在尝试将现有代码库迁移到 itext 7,它负责签署 pdf 文件(ti 的延迟签名)。迁移到新的 api 后,文档确实得到了签名,但出现错误,表明文档在签名后已被修改:
我想我可能错误地计算了为签名保留的大小,但我不确定我在哪里搞砸了。这是我当前的代码:
TSAClientBouncyCastle tsaClient = new("https://freetsa.org/tsr");
// crl list for revocation
// userCertificationChain is a IEnumerable<x509> with the certificate chain used for signimng
List<ICrlClient> crlClients = new() {new CrlClientOnline(userCertificatesChain.ToArray())};
// added ocsp client
OcspClientBouncyCastle ocspClient = new(null);
PdfSignerHelper sgn = new PdfSignerHelper(userCertificatesChain.ToArray( ));
PdfSigningManager pdfSigner = new(userCertificatesChain,
sgn,
crlClients: crlClients,
ocspClient: ocspClient,
tsaClient: tsaClient);
HashesForSigning hashInformation = pdfSigner.CreateTemporaryPdfForSigning(
new SigningInformation(pdfToBeSigned,
temporaryPdf,
Reason: "Because yes",
Location: "Funchal",
Logo: logo));
// signinginformation is a simple record for passing info between objects
CreateTemporaryPdfForSigning
看起来像这样:
public HashesForSigning CreateTemporaryPdfForSigning(SigningInformation signingInformation) {
StampingProperties properties = new();
properties.UseAppendMode();
PdfSigner pdfSigner = new(new PdfReader(signingInformation.PathToPdf),
new FileStream(signingInformation.PathToIntermediaryPdf, FileMode.Create),
properties);
pdfSigner.SetFieldName(_signatureFieldname);
SignatureFieldAppearance appearance = new(pdfSigner.GetFieldName());
if(signingInformation.Logo is null) {
appearance.SetContent(BuildVisibleInformation(signingInformation.Reason,
signingInformation.Location));
}
else {
appearance.SetContent(BuildVisibleInformation(signingInformation.Reason,
signingInformation.Location),
signingInformation.Logo);
}
appearance.SetFontSize(6f);
pdfSigner.SetSignatureAppearance(appearance);
pdfSigner.SetPageNumber(signingInformation.PageNumber)
.SetPageRect(new Rectangle(10, 750, 150,50));
IList<byte[]>? crlBytesList = GetCrlByteList();
IList<byte[]>? ocspBytesList = GetOcspBytesList();
int estimatedSize = EstimateContainerSize(crlBytesList);
Console.WriteLine($"Estimated size: {estimatedSize}");
PrepareForAmaSigningContainer container = new(_sgn,
crlBytesList,
ocspBytesList);
pdfSigner.SignExternalContainer(container, estimatedSize); // add size for timestamp in signature
return new HashesForSigning(container.HashToBeSignedByAma, container.NakedHash);
}
EstimateContainerSize
方法负责计算签名的估计大小。顺便说一句,计算方法如下:
private int EstimateContainerSize(IEnumerable<byte[]>? crlBytesList) {
int estimatedSize = 8192 + //initial base container size
( _ocspClient != null ? 4192 : 0 ) +
( _tsaClient != null ? 4600 : 0 );
if(crlBytesList != null) {
estimatedSize += crlBytesList.Sum(crlBytes => crlBytes.Length + 10);
}
return estimatedSize;
}
编辑:根据@mkl的建议,我更新了代码,以便在需要时使用相同的
PdfPKCS7
实例。它被封装在一个新的类型中,如下所示:
public sealed class PdfSignerHelper {
private const string _signaturePolicyUri = "https://www.autenticacao.gov.pt/documents/20126/0/POL%2316.PolAssQual_signed_signed.pdf";
private readonly PdfPKCS7 _sgn;
public PdfSignerHelper(IEnumerable<IX509Certificate> certificates) {
_sgn = new(null,
certificates.ToArray( ),
DigestAlgorithms.SHA256,
false);
// set signature policy information
MemoryStream policyIdByteMs = new(Encoding.ASCII.GetBytes("2.16.620.2.1.2.2"), false);
byte[]? policyIdByte = DigestAlgorithms.Digest(policyIdByteMs, DigestAlgorithms.SHA256);
SignaturePolicyInfo spi = new("2.16.620.2.1.2.2", policyIdByte, DigestAlgorithms.SHA256, _signaturePolicyUri);
_sgn.SetSignaturePolicy(spi);
}
public PdfPKCS7 Signer => _sgn;
}
此类型被注入到其他几种现有类型中,并保存在名为
_sgn
的字段中(例如,您将在 CreateTemporaryPdfForSigning' method being passed to the constructor of
PrepareForAmaSigningContainer` 上看到它)
PrepareForAmaSigningContainer
是一个简单的类,它扩展了 ExternalBlankSignatureContainer
用于创建“空白”签名:
public class PrepareForAmaSigningContainer : ExternalBlankSignatureContainer {
private static readonly byte[] _sha256SigPrefix = {
0x30, 0x31, 0x30, 0x0d, 0x06, 0x09,
0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01,
0x05, 0x00, 0x04, 0x20
};
private readonly IEnumerable<byte[]>? _crlBytesCollection;
private readonly IEnumerable<byte[]>? _ocspBytes;
private readonly PdfSignerHelper _sgn;
/// <summary>
/// Creates a new instance of the container
/// </summary>
/// <param name="certificates">User certficate chain</param>
/// <param name="crlBytesCollection">Collection of CRL bytes for revocation check</param>
/// <param name="ocspBytes">Collection od OCSP bytes for revocation</param>
public PrepareForAmaSigningContainer(PdfSignerHelper sgn,
IEnumerable<byte[]>? crlBytesCollection,
IEnumerable<byte[]>? ocspBytes) : base(PdfName.Adobe_PPKLite, PdfName.Adbe_pkcs7_detached) {
_crlBytesCollection = crlBytesCollection;
_ocspBytes = ocspBytes;
_sgn = sgn;
}
/// <summary>
/// Returns the hash that must be send for signing
/// </summary>
public byte[] HashToBeSignedByAma { get; private set; } = new byte[0];
/// <summary>
/// Original naked hash of the document (used for injecting the signature when it's retrieved from AMA)
/// </summary>
public byte[] NakedHash { get; private set; } = new byte[0];
/// <summary>
/// Method that will be called during the signing process
/// </summary>
/// <param name="data">Stream with the doc data that should be used in the hasing process</param>
/// <returns></returns>
public override byte[] Sign(Stream data) {
// get hash for document bytes
NakedHash = DigestAlgorithms.Digest(data, DigestAlgorithms.SHA256);
// get attributes
byte[]? docBytes = _sgn.Signer.GetAuthenticatedAttributeBytes(NakedHash,
PdfSigner.CryptoStandard.CMS,
_ocspBytes?.ToList( ),
_crlBytesCollection?.ToList( ));
// hash it again
using MemoryStream hashMemoryStream = new(docBytes, false);
byte[]? docBytesHash = DigestAlgorithms.Digest(hashMemoryStream, DigestAlgorithms.SHA256);
//prepend sha256 prefix
byte[] totalHash = new byte[_sha256SigPrefix.Length + docBytesHash.Length];
_sha256SigPrefix.CopyTo(totalHash, 0);
docBytesHash.CopyTo(totalHash, _sha256SigPrefix.Length);
HashToBeSignedByAma = totalHash;
Console.WriteLine($"size hash to be signed ama: {totalHash.Length}");
return Array.Empty<byte>();
}
}
返回的
HashesForSigning
然后传递给外部服务,该服务返回一个带有签名的字节数组,该签名将附加到中间文档中:
byte[] signature = GetExternalSignature();
pdfSigner.SignIntermediatePdf(new SignatureInformation(temporaryPdf,
finalPdf,
signature,
hashInformation.NakedHash));
这是将签名附加到中间 pdf 以获得签名 pdf 的辅助方法:
public void SignIntermediatePdf(SignatureInformation signatureInformation) {
PdfDocument document = new(new PdfReader(signatureInformation.PathToIntermediaryPdf));
using FileStream writer = new(signatureInformation.pathToSignedPdf, FileMode.Create);
IList<byte[]>? crlBytesList = GetCrlByteList();
IList<byte[]>? ocspBytesList = GetOcspBytesList();
Console.WriteLine($"pdf signing manager najed hash interm: {signatureInformation.NakedHashFromIntermediaryPdf.Length}");
InjectAmaSignatureContainer container = new(signatureInformation.Signature,
_sgn,
signatureInformation.NakedHashFromIntermediaryPdf,
crlBytesList,
ocspBytesList,
_tsaClient);
PdfSigner.SignDeferred(document, _signatureFieldname, writer, container);
}
最后,
InjectAmaSignatureContainer
是实现IExternalSignatureContainer
的类,看起来像这样:
public class InjectAmaSignatureContainer : IExternalSignatureContainer {
private readonly IEnumerable<IX509Certificate> _certificates;
private readonly IEnumerable<byte[]>? _crlBytesCollection;
private readonly byte[] _documentHash;
private readonly IEnumerable<byte[]>? _ocspBytes;
private readonly byte[] _signature;
private readonly ITSAClient? _tsaClient;
private readonly PdfSignerHelper _sgn;
private const string _signaturePolicyUri = "https://www.autenticacao.gov.pt/documents/20126/0/POL%2316.PolAssQual_signed_signed.pdf";
/// <summary>
/// Creates a new instance of the external container
/// </summary>
/// <param name="signature">Byte array with AMA's generated signature for specified doc</param>
/// <param name="certificates">User's certificate chain</param>
/// <param name="documentHash">Naked document hash used during preparation phase</param>
/// <param name="crlBytesCollection">CRL information that should be embedded</param>
/// <param name="ocspBytes">OCSP information that should be embedded</param>
/// <param name="tsaClient">TSA client that should be used for timestamping the document</param>
public InjectAmaSignatureContainer(byte[] signature,
PdfSignerHelper sgn,
byte[] documentHash,
IEnumerable<byte[]>? crlBytesCollection,
IEnumerable<byte[]>? ocspBytes,
ITSAClient? tsaClient = null) {
_signature = signature;
_sgn = sgn;
_documentHash = documentHash;
_crlBytesCollection = crlBytesCollection;
_ocspBytes = ocspBytes;
_tsaClient = tsaClient;
}
/// <summary>
/// Append signature and optional data to the temporary PDF document
/// </summary>
/// <param name="data"></param>
/// <returns></returns>
public byte[] Sign(Stream data) {
// set the signature bytes
_sgn.Signer.SetExternalSignatureValue(_signature,
null,
"RSA");
// call GetEncoded with the same parameters as the original GetAuthenticatedAtt...
byte[]? encodedSig = _sgn.Signer.GetEncodedPKCS7(_documentHash,
PdfSigner.CryptoStandard.CMS,
_tsaClient,
_ocspBytes?.ToList(),
_crlBytesCollection?.ToList());
return encodedSig;
}
public void ModifySigningDictionary(PdfDictionary signDic) {
}
}
编辑:添加辅助方法
GetCrlByteList
和GetOcspBytesList
的代码,因为我获取信息的方式似乎有问题:
private IList<byte[]>? GetCrlByteList() => _crlClients == null
? null
: _userCertificateChain.Select(x509 => GetCrlClientBytesList(x509))
.SelectMany(crlBytes => crlBytes)
.ToList();
private IList<byte[]>? GetCrlClientBytesList(IX509Certificate certificate) {
List<byte[]>? crls = _crlClients?.Select(crlClient => crlClient.GetEncoded(certificate, null))
.Where(encoded => encoded != null)
.SelectMany(bytes => bytes)
.ToList();
return crls;
}
private IList<byte[]>? GetOcspBytesList() {
if(_userCertificateChain.Count <= 1 || _ocspClient is null) {
return null;
}
List<byte[]> list = new();
for(int i = 0; i < _userCertificateChain.Count - 1; i++) {
byte[]? encoded = _ocspClient.GetEncoded(_userCertificateChain[i], _userCertificateChain[i + 1], null);
if(encoded != null) {
list.Add(encoded);
}
}
return list;
}
我使用的代码肯定有问题,因为当我没有设置 ocsp 或 clr 信息时,pdf 被认为是有效的...
这最终生成了签名的pdf,但不幸的是,它给出了之前的错误。关于我缺少什么的任何线索吗?
谢谢。
在
PrepareForAmaSigningContainer
中,您在检索经过身份验证的属性之前设置 PdfPKCS7 sgn
的策略值:
PdfPKCS7 sgn = new(null,
_certificates.ToArray(),
DigestAlgorithms.SHA256,
false);
// set signature policy information
MemoryStream policyIdByteMs = new(Encoding.ASCII.GetBytes("2.16.620.2.1.2.2"), false);
byte[]? policyIdByte = DigestAlgorithms.Digest(policyIdByteMs, DigestAlgorithms.SHA256);
SignaturePolicyInfo spi = new("2.16.620.2.1.2.2", policyIdByte, DigestAlgorithms.SHA256, _signaturePolicyUri);
sgn.SetSignaturePolicy(spi);
// get hash for document bytes
NakedHash = DigestAlgorithms.Digest(data, DigestAlgorithms.SHA256);
// get attributes
byte[]? docBytes = sgn.GetAuthenticatedAttributeBytes(NakedHash,
PdfSigner.CryptoStandard.CMS,
_ocspBytes?.ToList(),
_crlBytesCollection?.ToList());
因此,经过身份验证的属性包括策略属性,并且为属性包括该策略创建 AMA 签名。
但是在
InjectAmaSignatureContainer
中,您创建了 PdfPKCS7 sgn
而不设置策略:
PdfPKCS7 sgn = new(null,
_certificates.ToArray(),
DigestAlgorithms.SHA256,
false);
// set the signature bytes
sgn.SetExternalSignatureValue(_signature,
null,
"RSA");
// call GetEncoded with the same parameters as the original GetAuthenticatedAtt...
byte[]? encodedSig = sgn.GetEncodedPKCS7(_documentHash,
PdfSigner.CryptoStandard.CMS,
_tsaClient,
_ocspBytes?.ToList(),
_crlBytesCollection?.ToList());
因此,您在其中创建并最终嵌入到 PDF 中的编码 CMS 容器具有一个属性集(不带策略条目)和针对属性集(带策略条目)计算的签名值。因此,存在不匹配。
最好的选择是仅创建 PdfPKCS7
实例一次并保留并重复使用它,因此保证以相同的初始化形式使用它。
如果您确实需要删除原始
PdfPKCS7
并稍后重新创建它,请确保使用相同的参数化重新创建它。考虑将 PdfPKCS7
的创建和重新创建放入您从两个
IExternalSignatureContainer
实现中调用的中心方法中。