itext8:迁移破坏了外部签名

问题描述 投票:0回答:1

我正在尝试将现有代码库迁移到 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,但不幸的是,它给出了之前的错误。关于我缺少什么的任何线索吗?

谢谢。

c# itext7
1个回答
0
投票

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
实现中调用的中心方法中。
    

© www.soinside.com 2019 - 2024. All rights reserved.