【问题标题】:iText 7 pdf signature invalid when created manually手动创建时 iText 7 pdf 签名无效
【发布时间】:2021-08-18 08:31:57
【问题描述】:

我想使用 iText 7 对 pdf 文档进行数字签名。签名由仅返回 PKCS1 签名的外部服务创建。然后我必须创建并应用 PKCS7。

iText 提供了一个很好的文档:https://kb.itextpdf.com/home/it7kb/examples/how-to-use-a-digital-signing-service-dss-such-as-globalsign-with-itext-7

示例应用程序

我创建了一个示例应用程序,它通过本地证书签署 pdf 文档。此示例应用程序可以从https://github.com/suntsu42/PdfSignSamplePkcs1 克隆。在这个示例应用程序中,有两种不同的方法来创建 PKCS7。一次手动,一次通过 IExternalSignature(PrivateKeySignature) 实现。

对于这两种情况,必须签名的 pdf 摘要以相同的方式创建。唯一的区别是 PKCS7 的创建方式。

github (https://github.com/suntsu42/PdfSignSamplePkcs1) 上的项目是完整且自包含的。在资源文件夹中是用于创建签名的私钥文件 (pfx) 以及根证书。为了运行该示例,只需更改 resourcePath 变量的值以适应您的本地系统就足够了。

可以通过更改 createSignatureViaPlainPkcs1

的值来切换签名创建
using iText.Kernel.Pdf;
using iText.Signatures;
using System;
using System.IO;

namespace PdfSignSamplePkcs1
{
    class Program
    {
        static void Main(string[] args)
        {
            // TODO >> Change this path based on your local system
            var resourcePath = @"c:\project\github\PdfSignSamplePkcs1\Resources\";
            var pdfToSignPath = Path.Combine(resourcePath, "test.pdf");
            var signedPdfPath = Path.Combine(resourcePath, "signedPdf.pdf");
            var privateKey = Path.Combine(resourcePath, "SignTest.pfx"); // not critical, self signed certificate
            var privateKeyPassword = "test";

            // ############
            // Change value in order to create the PKCS7
            // either manually or via Itext
            // ############
            bool createSignatureViaPlainPkcs1 = false;

            //delete signed file if it exists
            if (System.IO.File.Exists(signedPdfPath))
                System.IO.File.Delete(signedPdfPath);
            var pdfToSign = System.IO.File.ReadAllBytes(pdfToSignPath);
            byte[] pdfDigest = null;


            //#1 Prepare pdf for signing
            var SignatureAttributeName = $"SignatureAttributeName_{DateTime.Now:yyyyMMddTHHmmss}";
            byte[] preparedToSignPdf = null;
            using (MemoryStream input = new MemoryStream(pdfToSign))
            {
                using (var reader = new PdfReader(input))
                {
                    StampingProperties sp = new StampingProperties();
                    sp.UseAppendMode();
                    using (MemoryStream baos = new MemoryStream())
                    {
                        var signer = new PdfSigner(reader, baos, sp);
                        signer.SetCertificationLevel(PdfSigner.NOT_CERTIFIED);

                        signer.SetFieldName(SignatureAttributeName);

                        DigestCalcBlankSigner external = new DigestCalcBlankSigner(PdfName.Adobe_PPKLite, PdfName.Adbe_pkcs7_detached);
                        signer.SignExternalContainer(external, 32000);

                        //get digest to be signed
                        pdfDigest = external.PdfDigest;
                        preparedToSignPdf = baos.ToArray();
                    }
                }
            }

            //#2 Create PKCS7
            SignService ss = new SignService(pdfDigest, privateKey, privateKeyPassword);
            byte[] signatureAsPkcs7 = null;
            if (createSignatureViaPlainPkcs1)
                signatureAsPkcs7 = ss.CreatePKCS7ViaPkcs1(); // >> Creates invalid pdf signature
            else
                signatureAsPkcs7 = ss.CreatePKCS7(); // Creates valid pdf signature

            //#3 apply cms(PKCS7) to prepared pdf
            ReadySignatureSigner extSigContainer = new ReadySignatureSigner(signatureAsPkcs7);
            using (MemoryStream preparedPdfStream = new MemoryStream(preparedToSignPdf))
            {
                using (var pdfReader = new PdfReader(preparedPdfStream))
                {
                    using (PdfDocument docToSign = new PdfDocument(pdfReader))
                    {
                        using (MemoryStream outStream = new MemoryStream())
                        {
                            PdfSigner.SignDeferred(docToSign, SignatureAttributeName, outStream, extSigContainer);
                            System.IO.File.WriteAllBytes(signedPdfPath, outStream.ToArray());
                        }
                    }
                }
            }
        }
    }


}

手动创建 pkcs7 签名

在此示例中,首先使用本地证书创建 PKCS1 签名。然后通过 SetExternalDigest

将创建的 PKCS1 签名应用于 PdfPKCS7 容器

这样创建的pdf无效。

        public byte[] CreatePKCS7ViaPkcs1()
        {
            //Load the certificate used for signing
            signCertificatePrivateKey = LoadCertificateFromFile();

            // create sha256 message digest
            // This is from https://kb.itextpdf.com/home/it7kb/examples/how-to-use-a-digital-signing-service-dss-such-as-globalsign-with-itext-7
            // Not sure if this is required, but the created signature is invalid either way
            using (SHA256 sha256 = SHA256.Create())
            {
                Digest = sha256.ComputeHash(Digest);
            }

            //Create pkcs1 signature
            byte[] signature = null;
            using (var key = signCertificatePrivateKey.GetRSAPrivateKey())
            {
                signature = key.SignData(Digest, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
            }

            Org.BouncyCastle.X509.X509Certificate cert = DotNetUtilities.FromX509Certificate(signCertificatePrivateKey);
            PdfPKCS7 sgn = new PdfPKCS7(null, new[] { cert }, "SHA256", false);
            sgn.SetExternalDigest(signature, null, "RSA");

            //Return the complete PKCS7 CMS 
            return sgn.GetEncodedPKCS7(Digest, PdfSigner.CryptoStandard.CMS, null, null, null);
        }

使用 PrivateKeySignature 实现创建 PKCS7 签名

在此示例中,PKCS7 是使用 iText PrivateKeySignature 创建的。签名是使用与另一个示例中相同的摘要和相同的私钥创建的。

此处创建的 pdf 是有效的。但是由于这种方法不允许使用外部服务来创建签名,所以我不能使用它。

        public byte[] CreatePKCS7ViaPkcs1()
        {
            //Load the certificate used for signing
            signCertificatePrivateKey = LoadCertificateFromFile();

            // create sha256 message digest
            // This is from https://kb.itextpdf.com/home/it7kb/examples/how-to-use-a-digital-signing-service-dss-such-as-globalsign-with-itext-7
            // Not sure if this is required, but the created signature is invalid either way
            using (SHA256 sha256 = SHA256.Create())
            {
                Digest = sha256.ComputeHash(Digest);
            }

            //Create pkcs1 signature using RSA
            byte[] signature = null;
            using (var key = signCertificatePrivateKey.GetRSAPrivateKey())
            {
                signature = key.SignData(Digest, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
            }

            Org.BouncyCastle.X509.X509Certificate cert = DotNetUtilities.FromX509Certificate(signCertificatePrivateKey);
            PdfPKCS7 sgn = new PdfPKCS7(null, new[] { cert }, "SHA256", false);
            sgn.SetExternalDigest(signature, null, "RSA");

            //Return the complete PKCS7 CMS 
            return sgn.GetEncodedPKCS7(Digest, PdfSigner.CryptoStandard.CMS, null, null, null);
        }

备注(编辑)

我认为问题的原因是我没有使用 GetAuthenticatedAttributeBytes 来获取要签名的哈希。但我不能使用这种方法。时间戳、ocsp 和 CLR 作为服务调用的一部分返回。由于 GetAuthenticatedAttributeBytes 的参数必须与应用签名时相同,我想我不能使用此功能。

问题

通过 RSA 创建的签名在生成的 pdf 中无效的原因是什么? 编辑:更具体地说:当签名服务返回 PKCS1、Timestamp、Ocsp 和 CRL 时,我如何创建有效的 pkcs7 容器。在这种情况下究竟必须签署什么?

【问题讨论】:

    标签: c# pdf itext digital-signature itext7


    【解决方案1】:

    一个错误是相当明显的:

    CreatePKCS7 中,您对包含文档摘要 (Digest) 的签名容器 (GetAuthenticatedAttributeBytes) 的待签名属性进行签名:

    var sh = sgn.GetAuthenticatedAttributeBytes(Digest, PdfSigner.CryptoStandard.CMS, null, null);
    byte[] extSignature = signature.Sign(sh);
    

    CreatePKCS7ViaPkcs1 中,您签署文档摘要 (Digest) 本身:

    //Create pkcs1 signature using RSA
    byte[] signature = null;
    using (var key = signCertificatePrivateKey.GetRSAPrivateKey())
    {
        signature = key.SignData(Digest, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
    }
    

    在这两种情况下,您都可以继续将返回的签名注入PdfPKCS7

    sgn.SetExternalDigest(extSignature, null, signature.GetEncryptionAlgorithm());
    

    sgn.SetExternalDigest(signature, null, "RSA");
    

    分别。

    第一个变体有效,是一个指示符,告诉您SetExternalDigest 期望作为第一个参数的是待签名属性的签名(外部签名摘要),而不是文档摘要的签名直接。

    因此,在CreatePKCS7ViaPkcs1 中,您只是签署了错误的字节!

    您可以通过(就像在CreatePKCS7 中一样)在创建PdfPKCS7 实例并从中接收待签名属性(使用GetAuthenticatedAttributeBytes)来修复CreatePKCS7ViaPkcs1。您可能需要也可能不需要在签名之前对其结果进行哈希处理 - 我不太精通 .NET 加密 API。

    【讨论】:

    • 我想我确实理解您的观点(我还编辑了问题以反映这一点)。我的问题是,如果使用 GetAuthenticatedAttributeBytes,我需要设置 OCSP 和 CRL。但我只有 OCSP 和 CRL 以及来自签名服务的签名。
    • @Manuel “但我只有 OCSP 和 CRL 以及来自签名服务的签名。” - 不幸的是:在旧的 ISO 32000-1 样式签名中(其中您显然想要这样做 - 毕竟您使用的是PdfName.Adbe_pkcs7_detached) OCSP 和 CRL 在 签名之前需要,因为它们存储在待签名属性中,所以没有机会。如果更新的 ISO 32000-2 样式签名结构也可以,您可以在单独的第二步中添加 OCSP 和 CRL 数据。无论哪种方式,您都应该首先让您的代码在没有 OCSP 和 CRL 的情况下工作。
    • @Manuel 顺便说一句,有一些签名服务会返回完整的 CMS 容器,包括用于 PDF 的签名属性中的 CRL 和/或 OCSP 响应。瑞士电信公司用来运营此类服务的 IIRC。
    • 如果服务按照 ISO 32000-2 实施,它必须返回一个完整的 PKCS7 容器,还是在所有部分单独返回时也可以工作?
    • 另一个旁注:我使用 Adbe_pkcs7_detached 因为这是我在文档中找到的,我认为这是正确的。
    猜你喜欢
    • 1970-01-01
    • 2020-03-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-02-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多