签名后是否可以更改签名在文档中的外观?

时间:2019-05-03 06:07:54

标签: c# pdf itext digital-signature x509certificate

在计算文档的哈希值以进行签名之前,我正在使用以下代码在文档中添加TextField。当我关注此链接时 Changing signature appearance after signing pdf file with iTextSharp 这是一个在所有页面上添加签名并在第一页上添加文本字段的代码。 文本字段的目的是从证书中提取“ IssuedTo”并将其显示在签名外观上。

在以更新方式分配pdf之前:

 XmlNodeList nodeList = xmlDoc.GetElementsByTagName("Signatures");

                    string signature = nodeList[0].FirstChild.InnerText;

                    string src = Server.MapPath("~/ESignFiles/" + file_withoutExtn + "_temp.pdf");
                    string dest = Server.MapPath("~/ESignFiles/" + file_withoutExtn + "_multiple_signed.pdf");
                    ///add text
                    AddText(src, dest);
                    ///add text
                    using (PdfReader reader = new PdfReader(src))
                    {
                        using (FileStream os = new FileStream(dest, FileMode.Create))
                        {
                            byte[] encodedSignature = Convert.FromBase64String(signature);

                            IExternalSignatureContainer external = new MyExternalSignatureContainer(encodedSignature);
                            MakeSignature.SignDeferred(reader, "sign1", os, external);
                        }
                    }

将文本添加到临时pdf的代码

 public void AddText(String src, String dest) {
                PdfReader reader = new PdfReader(src);
                PdfStamper stamper = new PdfStamper(reader, new FileStream(dest, FileMode.Create), '\0', true);
                ColumnText.ShowTextAligned(stamper.GetOverContent(1), Element.ALIGN_LEFT, new Phrase("client name"), 200, 380, 0);
                stamper.Close();
            }

2 个答案:

答案 0 :(得分:2)

首先,正如对该问题和Bharat's answer的评论中所讨论的:

在应用签名后 需要更新签名外观,这表明签名解决方案的体系结构很差。

在这种情况下,这种不良体系结构似乎是由于要求而导致的(“外观必须包含证书信息”与“在签名之前不可用证书”结合使用)。但是,这是一个糟糕的体系结构,应在审查和修订要求之后对其进行改进。

但是在良性情况下确实可以更新签名外观:如果现有签名允许“表单填写和注释更改”并且没有完全锁定相应的签名字段,则外观可以在不使签名无效的情况下以增量更新方式更新签名集(不过,验证者可能会警告更改)。

更新通用PDF签名

PDF规范没有专门为签名字段的外观定义一种结构,一种通用解决方案只需要用一个新的替换每个签名字段小部件注释的外观流。可以使用.Net的iText 5.5.x来做到这一点:

using (PdfReader pdfReader = new PdfReader(SRC))
using (PdfStamper pdfStamper = new PdfStamper(pdfReader, new FileStream(DEST, FileMode.Create, FileAccess.Write), '\0', true))
{
    AcroFields acroFields = pdfStamper.AcroFields;
    foreach (String signatureName in acroFields.GetSignatureNames())
    {
        PdfPKCS7 pkcs7 = acroFields.VerifySignature(signatureName);
        X509Certificate signerCert = pkcs7.SigningCertificate;
        String signerName = CertificateInfo.GetSubjectFields(signerCert).GetField("CN");

        PdfAppearance appearance = PdfAppearance.CreateAppearance(pdfStamper.Writer, 100, 100);
        ColumnText columnText = new ColumnText(appearance);
        Chunk chunk = new Chunk();
        chunk.SetSkew(0, 12);
        chunk.Append("Signed by:");
        columnText.AddElement(new Paragraph(chunk));
        chunk = new Chunk();
        chunk.SetTextRenderMode(PdfContentByte.TEXT_RENDER_MODE_FILL_STROKE, 1, BaseColor.BLACK);
        chunk.Append(signerName);
        columnText.AddElement(new Paragraph(chunk));
        columnText.SetSimpleColumn(0, 0, 100, 100);
        columnText.Go();

        PdfDictionary appDict = new PdfDictionary();
        appDict.Put(PdfName.N, appearance.IndirectReference);

        AcroFields.Item field = acroFields.GetFieldItem(signatureName);
        for (int i = 0; i < field.Size; i++)
        {
            PdfDictionary widget = field.GetWidget(i);
            PdfArray rect = widget.GetAsArray(PdfName.RECT);
            float x = Math.Min(rect.GetAsNumber(0).FloatValue, rect.GetAsNumber(0).FloatValue);
            float y = Math.Min(rect.GetAsNumber(1).FloatValue, rect.GetAsNumber(3).FloatValue);
            widget.Put(PdfName.RECT, new PdfArray(new float[] { x, y, x + 100, y + 100 }));
        }
        field.WriteToAll(PdfName.AP, appDict, AcroFields.Item.WRITE_WIDGET);
        field.MarkUsed(acroFields, AcroFields.Item.WRITE_WIDGET);
    }
}

如您所见,该代码从签署者证书中提取主题的通用名称,并将其(以"Signed by:"行开头)写入新外观。如果您的替换外观中需要其他数据,只需更改添加到columnText和/或appearance的数据即可。

此外,该代码用尺寸为100×100的新外观替换了所有外观。当然,您也可以根据自己的需求进行调整。

这实质上是从this answer到C#的代码移植。

使用Adobe特定的图层更新PDF签名

Adob​​e Acrobat Reader使用特定的方案来构建其签名外观,甚至为根据该方案的较旧版本构建的签名添加某些功能。如上所述,PDF规范没有规定任何此类方案;实际上,甚至禁止这样的功能,请参见。 this answer

尽管如此,许多堆栈溢出问题(尤其是来自印度的堆栈溢出问题)似乎表明,客户经常需要遵循该过时方案的签名。

如果遵循此方案,则外观本身将被构造为XObjects形式的层次结构,特别是一组所谓的“层” n0 n4 n2 是希望签名者在其上应用其身份的层。

上述通用解决方案可以进行以下调整以符合此方案:

using (PdfReader pdfReader = new PdfReader(SRC))
using (PdfStamper pdfStamper = new PdfStamper(pdfReader, new FileStream(DEST, FileMode.Create, FileAccess.Write), '\0', true))
{
    AcroFields acroFields = pdfStamper.AcroFields;
    foreach (String signatureName in acroFields.GetSignatureNames())
    {
        PdfPKCS7 pkcs7 = acroFields.VerifySignature(signatureName);
        X509Certificate signerCert = pkcs7.SigningCertificate;
        String signerName = CertificateInfo.GetSubjectFields(signerCert).GetField("CN");

        AcroFields.Item field = acroFields.GetFieldItem(signatureName);
        for (int i = 0; i < field.Size; i++)
        {
            PdfDictionary widget = field.GetWidget(i);
            Rectangle rect = PdfReader.GetNormalizedRectangle(widget.GetAsArray(PdfName.RECT));

            PdfAppearance appearance = PdfAppearance.CreateAppearance(pdfStamper.Writer, rect.Width, rect.Height);
            ColumnText columnText = new ColumnText(appearance);
            Chunk chunk = new Chunk();
            chunk.SetSkew(0, 12);
            chunk.Append("Signed by:");
            columnText.AddElement(new Paragraph(chunk));
            chunk = new Chunk();
            chunk.SetTextRenderMode(PdfContentByte.TEXT_RENDER_MODE_FILL_STROKE, 1, BaseColor.BLACK);
            chunk.Append(signerName);
            columnText.AddElement(new Paragraph(chunk));
            columnText.SetSimpleColumn(0, 0, rect.Width, rect.Height - 15);
            columnText.Go();

            PdfDictionary xObjects = GetAsDictAndMarkUsed((PdfStamperImp)pdfStamper.Writer, widget, PdfName.AP, PdfName.N, PdfName.RESOURCES, PdfName.XOBJECT, PdfName.FRM, PdfName.RESOURCES, PdfName.XOBJECT);
            xObjects.Put(PdfName.N2, appearance.IndirectReference);
        }
    }
}

使用以下辅助方法:

PdfDictionary GetAsDictAndMarkUsed(PdfStamperImp writer, PdfDictionary dictionary, params PdfName[] names)
{
    PRIndirectReference reference = null;
    foreach (PdfName name in names)
    {
        if (dictionary != null)
        {
            dictionary = dictionary.GetDirectObject(name) as PdfDictionary;
            if (dictionary != null)
            {
                if (dictionary.IndRef != null)
                    reference = dictionary.IndRef;
            }
        }
    }
    if (reference != null)
        writer.MarkUsed(reference);

    return dictionary;
}

(请注意:此代码假定签名遵循Adobe方案;如果不确定输入是否正确,请添加一些健全性检查并将其默认设置为上述通用解决方案。)

答案 1 :(得分:0)

由于外观是计算要签名的哈希时文档的一部分,因此更改外观将更改哈希并使已完成的签名无效。