PDF 签名使 Acrobat 中的现有签名无效 Reader

Pdf signature invalidates existing signature in Acrobat Reader

我正在使用 iText 7.1.15 和 SignDeferred 将签名应用于 pdf 文档。 SignDeferred 是必需的,因为签名是创建 PKCS11 硬件令牌(USB 密钥)。

当我签署“常规”pdf 时,例如通过 word 创建,我可以应用多个签名,并且所有签名在 adobe acrobat 中都显示为有效 reader.

如果 pdf 是通过将多个 pdf 文档与 adobe DC 合并创建的,则第一个签名有效,但在应用第二个签名后立即失效。

应用第一个签名后 Adob​​e reader 中的文档:

应用第二个签名后 Adob​​e reader 中的文档:

同一个文档的签名在foxit中显示为有效reader。

我在 Whosebug () 上发现了类似的问题,但它使用的是 iText 5,我不确定这是不是同一个问题。

问题:我该怎么做才能使 Acrobat 中的两个签名都有效 Reader?

第一个签名无效的未签名 Pdf 文档: https://github.com/suntsu42/iTextDemoInvalidSecondSignature/blob/master/test.pdf

两次签名的文件无效: https://github.com/suntsu42/iTextDemoInvalidSecondSignature/blob/master/InvalidDocumentSignedTwice.pdf

用于签名的代码

  //Step #1 >> prepare pdf for signing (Allocate space for the signature and calculate hash)
            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);
             

                        //Has to be NOT_CERTIFIED since otherwiese a pdf cannot be signed multiple times
                        signer.SetCertificationLevel(PdfSigner.NOT_CERTIFIED);

                        if (visualRepresentation != null)
                        {
                            try
                            {
                                PdfSignatureAppearance appearance = signer.GetSignatureAppearance();
                                base.SetPdfSignatureAppearance(appearance, visualRepresentation);
                            }
                            catch (Exception ex)
                            {
                                throw new Exception("Unable to set provided signature image", ex);
                            }
                        }

                        //Make the SignatureAttributeName unique
                        SignatureAttributeName = $"SignatureAttributeName_{DateTime.Now:yyyyMMddTHHmmss}";
                        signer.SetFieldName(SignatureAttributeName);
                        DigestCalcBlankSigner external = new DigestCalcBlankSigner(PdfName.Adobe_PPKLite, PdfName.Adbe_pkcs7_detached);

                        signer.SignExternalContainer(external, EstimateSize);
                        hash = external.GetDocBytesHash();
                        tmpPdf = baos.ToArray();
                    }
                }

                //Step #2 >> Create the signature based on the document hash
                // This is the part which accesses the HSM via PCKS11
                byte[] signature = null;
                if (LocalSigningCertificate == null)
                {
                    signature = CreatePKCS7SignatureViaPKCS11(hash, pin);
                }
                else
                {
                    signature = CreatePKCS7SignatureViaX509Certificate(hash);
                }

                //Step #3 >> Apply the signature to the document
                ReadySignatureSigner extSigContainer = new ReadySignatureSigner(signature);
                using (MemoryStream preparedPdfStream = new MemoryStream(tmpPdf))
                {
                    using (var pdfReader = new PdfReader(preparedPdfStream))
                    {
                        using (PdfDocument docToSign = new PdfDocument(pdfReader))
                        {
                            using (MemoryStream outStream = new MemoryStream())
                            {
                                PdfSigner.SignDeferred(docToSign, SignatureAttributeName, outStream, extSigContainer);
                                return outStream.ToArray();
                            }
                        }
                    }
                }

            }

示例项目

我创建了一个使用本地证书进行签名的工作示例项目。我也确实将 iText 更新到 7.2 版,但结果相同。 它还包含无法签名两次的文档 (test.pdf) https://github.com/suntsu42/iTextDemoInvalidSecondSignature/tree/master

编辑

我已经将 MKL 提供的解决方案应用于 github 上的示例项目。

作为第二个说明,也可以使用 PdfSigner,但在这种情况下,必须删除原始文档的书签。

正如评论中已经提到的,示例文档“InvalidDocumentSignedTwice.pdf”的签名未在增量更新中应用,因此很明显,以前的签名会被破坏。但这不是 OP 示例项目的问题。因此,问题的处理着眼于示例项目的实际输出。

分析问题

在验证签名的 PDF 时,Adobe Acrobat 会执行两种类型的检查:

  • 它会检查签名本身以及它所涵盖的 PDF 的修订版是否未受影响。
  • (如果在签名涵盖的修订后对 PDF 进行了添加:)它检查增量更新中应用的更改是否仅包含允许的更改。

前一个检查非常稳定和标准,但第二个检查非常异想天开,容易出现不正确的负面验证结果。就像你的情况一样...

对于您的示例文档,可以简单地确定第一次检查必须肯定地验证第一个签名:只有一个(有效!)签名的文件构成文件的 byte-wise 起始部分,其中有两个签名,所以这里没有任何东西可以被破坏。

因此,第二种检查,变化无常的类型,在本案中肯定会出错。

要找出有什么变化,需要分析签名期间所做的更改。一个有用的事实是,使用 iText 5 执行相同的操作不会产生问题;因此,触发检查的更改必须是 iText 7 与此处的 iText 5 不同的地方。在此上下文中的主要区别在于 iText 7 具有比 iText 5 更全面的标记支持,因此,还将对新签名字段的引用添加到文档结构树中。

这本身还不会触发异想天开的检查,不过,它只会在这里触发,因为一个大纲元素将更改的父结构树元素引用为其 结构元素 (SE)。显然,Adobe Acrobat 将关联结构元素中的更改视为大纲的更改 link,因此,将其视为由第一个签名签署的文档修订行为的(不允许的)更改。

这是 iText 错误(向结构树添加条目)还是 Adob​​e Acrobat 错误(抱怨添加)?那么,在 标记的 PDF 中(并且您的 PDF 将相应的 Marked 条目设置为 true)内容 包括注释和表单字段 预计会被标记。因此,对于新增的签名字段及其外观,增加结构树条目不仅应该允许,而且实际上是推荐甚至要求的!所以这似乎是 Adob​​e Acrobat 的错误。

一个Work-Around

知道这似乎是一个 Adob​​e Acrobat 错误很好,但在一天结束时,现在可能需要一种方法来多次签署此类文档,而当前的 Adob​​e Acrobat 不会调用该错误。

可以让iText相信没有结构树并且不需要更新结构树。这可以通过使文档标签结构的初始化失败来完成。为此,我们覆盖 PdfDocument 方法 TryInitTagStructure。由于 iText PdfSigner 在内部创建其文档对象,我们在 PdfSigner 方法 InitDocument.

的重写中执行此操作

即而不是 PdfSigner 我们使用 class MySigner 定义如下:

public class MySigner : PdfSigner
{
    public MySigner(PdfReader reader, Stream outputStream, StampingProperties properties) : base(reader, outputStream, properties)
    {
    }

    override protected PdfDocument InitDocument(PdfReader reader, PdfWriter writer, StampingProperties properties)
    {
        return new MyDocument(reader, writer, properties);
    }
}

public class MyDocument : PdfDocument
{
    public MyDocument(PdfReader reader, PdfWriter writer, StampingProperties properties) : base(reader, writer, properties)
    {

    }

    override protected void TryInitTagStructure(PdfDictionary str)
    {
        structTreeRoot = null;
        structParentIndex = -1;
    }
}

使用 MySigner 签署文档 iText 将不再添加标签,因此不会让 Adob​​e Acrobat 抱怨结构树中的新条目。

Java

中的相同 Work-Around

因为我觉得在 Java 中工作更舒服,所以我对此进行了分析并在 Java 中测试了 work-around。

这里可以写成更封闭的形式(也许C#也可以,我不知道),而不是像这样初始化签名者

PdfSigner pdfSigner = new PdfSigner(pdfReader, os, new StampingProperties().useAppendMode());

我们这样做:

PdfSigner pdfSigner = new PdfSigner(pdfReader, os, new StampingProperties().useAppendMode()) {
    @Override
    protected PdfDocument initDocument(PdfReader reader, PdfWriter writer, StampingProperties properties) {
        return new PdfDocument(reader, writer, properties) {
            @Override
            protected void tryInitTagStructure(PdfDictionary str) {
                structTreeRoot = null;
                structParentIndex = -1;
            }
        };
    }
};

(MultipleSignaturesAndTagging 测试 testSignTestManuelTwiceNoTag)

TL;DR

iText 7 在签名期间将新签名字段的结构元素添加到文档结构树中。但是,如果此新节点的父节点被引用为大纲元素的关联结构元素,Adobe Acrobat 会错误地认为这是不允许的更改。作为 work-around 可以调整 iText 签名以不添加结构元素。

我在上次更新 Adob​​e Reader 后遇到了类似的签名问题。我在他们的社区写了一篇post,但他们仍然没有回复我。

看看: https://community.adobe.com/t5/acrobat-reader-discussions/invalid-signatures-after-adobe-reader-update-2022-001-20085/td-p/12892048

我正在使用 iText v.5.5.5 生成 pdf。我以单一方法签署和认证 pdf 文档。此外,foxit reader 表明签名是有效的。我相信这是一个 Adob​​e 错误,很快就会修复 :) 关键是一个日志。