繁体   English   中英

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

[英]Pdf signature invalidates existing signature in Acrobat Reader

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

当我签署“常规”pdf(例如通过 word 创建)时,我可以应用多个签名,并且所有签名在 adobe acrobat 阅读器中均显示为有效。

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

应用第一个签名后在 Adobe Reader 中的文档: 在此处输入图像描述

应用第二个签名后在 Adobe Reader 中的文档: 在此处输入图像描述

同一文档的签名在福昕阅读器中显示为有效。

我在 stackoverflow 上发现了一个类似的问题( 多个签名使 iTextSharp pdf 签名中的第一个签名无效),但它使用的是 iText 5,我不确定这是同一个问题。

问题:如何才能使两个签名在 Acrobat Reader 中保持有效?

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

两次签名的无效文档: 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

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

分析问题

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

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

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

在您的示例文档的情况下,您可以简单地确定第一次检查必须肯定地验证第一个签名:只有一个(有效)签名的文件构成了具有两个签名的文件的字节起始部分。 所以这里没有任何东西被破坏。

因此,第二种检查类型,变化无常的类型,在手头的情况下一定是错误的。

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

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

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

解决方法

知道这似乎是一个 Adobe Acrobat 错误,这一切都很好,但归根结底,现在可能需要一种方法来多次签署此类文档,而当前的 Adobe Acrobat 不会将其称为无效。

可以让 iText 相信没有结构树,也不需要更新结构树。 这可以通过使文档标记结构的初始化失败来完成。 为此,我们重写了PdfDocument方法TryInitTagStructure 由于 iText PdfSigner在内部创建其文档 object,因此我们在PdfSigner方法InitDocument的覆盖中执行此操作。

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

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 将不再添加标记,因此不会让 Adobe Acrobat 抱怨结构树中的新条目。

Java 中的相同解决方法

由于我在 Java 中工作感觉更舒服,我对此进行了分析并测试了 Java 中的变通方法。

这里可以把它变成一个更封闭的形式(也许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;博士

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM