首页 > 解决方案 > C# Itext7 签名的 pdf 签名在 Foxit PDF Reqader 中无效,但在 Acrobat 阅读器中有效

问题描述

我找不到为什么 Foxit PDF Reader 显示我的签名文件签名无效。首先,我将空白签名容器插入 pdf

public class BlankSignatureContainer2: IExternalSignatureContainer
{
    private readonly PdfName filter;
    private readonly PdfName subFilter;
    private byte[] docBytesHash;
    private byte[] docDigest;
    private const String HASH_ALGORITHM = DigestAlgorithms.SHA256;
    private X509Certificate[] chain;

    public BlankSignatureContainer2(PdfName filter, PdfName subFilter, X509Certificate[] chain)
    {
        this.filter = filter;
        this.subFilter = subFilter;
        this.chain = chain;
    }

    public virtual byte[] GetDocBytesHash()
    {
        return docBytesHash;
    }

    public virtual byte[] GetDocBytesDigest()
    {
        return docDigest;
    }

    public virtual byte[] Sign(Stream docBytes)
    {
        PdfPKCS7 sgn = new PdfPKCS7(null, chain, HASH_ALGORITHM, false);
        docDigest = DigestAlgorithms.Digest(docBytes, DigestAlgorithms.GetMessageDigest(HASH_ALGORITHM));
        docBytesHash = sgn.GetAuthenticatedAttributeBytes(docDigest, PdfSigner.CryptoStandard.CADES, null, null);
        using (SHA256 sha256 = SHA256.Create())
        {
            docBytesHash = sha256.ComputeHash(docBytesHash);
        }

        return new byte[0];
    }

    public virtual void ModifySigningDictionary(PdfDictionary signDic)
    {
        signDic.Put(PdfName.Filter, filter);
        signDic.Put(PdfName.SubFilter, subFilter);
    }
}

docBytesHash我发送到外部网络服务以使用合格的签名进行签名。

using (MemoryStream stream = new MemoryStream())
using (MemoryStream ms = new MemoryStream(Convert.FromBase64String(value.pdfFile))) //pdf file to sign
using (PdfReader pdfReader = new PdfReader(ms))
{
    //Get signers and CA certificates
    X509Certificate[] chain = await _signPdf.GetMidChain(certificate.Cert);

    // creating empty signature container, getting file digest and hash
    var blankSignature = new BlankSignatureContainer2(PdfName.Adobe_PPKLite, PdfName.ETSI_CAdES_DETACHED, chain);
    var stamper = new PdfSigner(pdfReader, stream, new StampingProperties().UseAppendMode());
    var appearance = stamper.GetSignatureAppearance();

    //Method to add signature appearance    
    var sigAppearance = new SigAppearance("stamp1.svg", chain[0].SubjectDN.GetValueList(X509Name.CN).Cast<string>().FirstOrDefault() ?? "", DateTime.Now, "Sign", stamper.GetDocument());

    appearance.SetPageRect(new iText.Kernel.Geom.Rectangle(sigAppearance.GetPosition()));
    stamper.SetFieldName(new GetSignatureToSign(stamper.GetDocument()).GetSignatureName());
    new PdfCanvas(appearance.GetLayer2(), stamper.GetDocument()).AddXObjectFittedIntoRectangle(sigAppearance.GetXObject(), sigAppearance.GetRectangle());
    stamper.SignExternalContainer(blankSignature, 8192*2+2);

    //Call external webservice to sign blankSignature.GetDocBytesHash()
    var msrp = await _clientMID.SignAsync(new MidSignReqModel() {
        NationalIdentityNumber = value.personalCode,
        PhoneNumber = value.phone,
        Language = value.language,
        DisplayText = value.ShortMessage(),
        DisplayTextFormat = "UCS-2",
        Hash = Convert.ToBase64String(blankSignature.GetDocBytesHash()),
        HashType = "SHA256"
    });

接下来,我使用网络服务的结果签署了 pdf。这是外部签名容器:

public class ExternalSignatureContainer2: IExternalSignatureContainer
{
    private X509Certificate[] chain;
    private byte[] signature;
    private string hashAlgorithm;

    public ExternalSignatureContainer2(X509Certificate[] chain, byte[] signature, string hashAlgorithm)
    {
        this.chain = chain;
        this.signature = signature;
        this.hashAlgorithm = hashAlgorithm;
    }

    public void ModifySigningDictionary(PdfDictionary signDic)
    {
        //throw new NotImplementedException();
    }

    public byte[] Sign(Stream data)
    {
        //create TimeStamp Client
        ITSAClient tsa = new TSAClientBouncyCastle(@"http://demo.sk.ee/tsa/");

        string hashAlgorithm = "SHA256";
        PdfPKCS7 sgn = new PdfPKCS7(null, chain, hashAlgorithm, false);
        byte[] digest = DigestAlgorithms.Digest(data, DigestAlgorithms.GetMessageDigest(hashAlgorithm));
        sgn.SetExternalDigest(signature, null, "ECDSA");
        return sgn.GetEncodedPKCS7(digest, PdfSigner.CryptoStandard.CADES, tsa, null, null);
    }
}

这是签名代码:

public async Task<byte[]> Sign2(string file, byte[] signature, X509Certificate[] chain, string hashAlgorithm)
{
    using (var tempStream = new MemoryStream(File.ReadAllBytes(Path.Combine(Directory.GetCurrentDirectory(), @"drafts", file))))
    using (var destination = new MemoryStream())
    {
        var tempReader = new PdfReader(tempStream);
        var gsContainer = new ExternalSignatureContainer2(chain, signature, hashAlgorithm);
        var signer = new PdfSigner(tempReader, destination, new StampingProperties().UseAppendMode());
        signer.SetCertificationLevel(PdfSigner.CERTIFIED_FORM_FILLING);
        PdfSigner.SignDeferred(signer.GetDocument(), new GetSignatureToSign(signer.GetDocument()).GetSignatureName(), destination, gsContainer);

        using (MemoryStream LTV = new MemoryStream())
        using (var newSource = new MemoryStream(destination.ToArray()))
        {
            PdfDocument pdfDoc = new PdfDocument(new PdfReader(newSource), new PdfWriter(LTV), new StampingProperties().UseAppendMode());

            LtvVerification v = new LtvVerification(pdfDoc);
            SignatureUtil signatureUtil = new SignatureUtil(pdfDoc);

            IList<string> names = signatureUtil.GetSignatureNames();
            String sigName = names[(names.Count - 1)];

            PdfPKCS7 pkcs7 = signatureUtil.ReadSignatureData(sigName);

            if (pkcs7.IsTsp())
            {
                v.AddVerification(sigName, new OcspClientBouncyCastle(null), new CrlClientOnline(), LtvVerification.CertificateOption.WHOLE_CHAIN,
                    LtvVerification.Level.OCSP_CRL, LtvVerification.CertificateInclusion.YES);
            }
            else
            {
                foreach (String name in names)
                {
                    v.AddVerification(name, new OcspClientBouncyCastle(null), new CrlClientOnline(), LtvVerification.CertificateOption.WHOLE_CHAIN,
                        LtvVerification.Level.OCSP_CRL, LtvVerification.CertificateInclusion.YES);
                }
            }

            v.Merge();
            pdfDoc.Close();
            return await Task.FromResult( LTV.ToArray());
        }
    }
}

结果是:

爱看阅读器

福昕PDF阅读器

找不到我做错了什么。

标签: c#pdfitextsignaturesign

解决方案


您的问题与此问题有关,例如在这种情况下,ECDSA 签名格式存在问题。因此,有关更多详细信息,请阅读那里的答案

正如在回答上述问题时所解释的那样,有两种主要格式可以对 ECDSA 签名值进行编码,

  • 作为SEQUENCE两个INTEGER值的 TLV 和
  • 作为具有固定长度的两个整数的串联。

根据我的经验,Adobe Reader 支持这两种格式,而 Foxit 显然只支持前一种格式。

您嵌入的签名值使用后一种格式。因此,福昕不接受它。

要使您的签名与 Foxit(以及 iText 自己的验证)一起使用,您应该切换到前一种格式。检查您的 Web 服务是否可以改为返回 TLV 格式的签名。或者,您必须在代码中从纯格式转换为 TLV 格式。


实际上在这里人们可以读到福昕声称与底层操作系统紧密集成的数字签名。虽然这确实可能具有使操作系统识别的任何证书都能被 Foxit 使用的优点,正如那里所解释的那样,它的缺点是,由不依赖于操作系统的签名软件生成的签名可能存在问题,甚至与福昕在其他操作系统上生成或成功验证的签名...


顺便说一句:就像上述问题一样,您的签名容器中还有另一个问题:在签名算法字段中,它具有 ECDSA 公钥的 OID,而不是实际签名算法的 OID。虽然 Adob​​e Reader 和 Foxit 都接受这一点,但其他不那么宽松的验证者会拒绝这一点。

使用这个错误的 OID 是当前 iText 版本中的一个已知问题。目前,您可以使用 BouncyCastle 或 .Net 安全 API 类而不是 iTextPdfPKCS7来创建 CMS 签名容器。


推荐阅读