Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
188 views
in Technique[技术] by (71.8m points)

c# - Is it possible to change the appearance of the signature within the document after signing it?

Before calculating the hash of the document for signing I am adding the TextField in my document using the below code. as I am following this link Changing signature appearance after signing pdf file with iTextSharp Here is a code that adds signature on all pages and adds a text field on the first page. the purpose of the text field is to extract the "IssuedTo" from the certificate and display it on the signature appearance.

Before esign open pdf in update mode:

 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);
                        }
                    }

Code that add text to temp pdf

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

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

First of all, as has been discussed in comments to the question and to Bharat's answer:

The need to update signature appearances after applying the signature indicates a bad architecture of the signing solution.

In the case at hand this bad architecture appears to be a result of the requirements ("appearances must include certificate information" in combination with "certificate is not available before signing"). Nonetheless this is a bad architecture and should be improved after reviewing and revising the requirements.

But it indeed is possible under benign circumstances to update signature appearances: If the existing signatures allow for "form fill-ins and annotation changes" and do not completely lock the respective signature fields, the appearances of signatures can be updated in an incremental update without invalidating the signatures (validators may warn about a change, though).

Updating generic PDF signatures

The PDF specification does not specifically define a structure for the appearance of a signature field, a generic solution simply has to replace the appearance stream of each signature field widget annotation with a new one. This can be done like this using iText 5.5.x for .Net:

using (PdfReader pdfReader = new PdfReader(SRC))
using (PdfStamper pdfStamper = new PdfStamper(pdfReader, new FileStream(DEST, FileMode.Create, FileAccess.Write), '', 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);
    }
}

As you can see the code extracts the subject's common name from the signer certificate and writes it (prefixed by a "Signed by:" line) to the new appearance. If you need other data in your your replacement appearance, simply change the data added to the columnText and/or the appearance accordingly.

Furthermore, the code replace all appearances with new ones which are 100×100 in size. You of course can adapt this to your requirements, too.

This essentially is a port of code from this answer to C#.

Updating PDF signatures using the Adobe specific layers

Adobe Acrobat Reader uses a specific scheme to build its signature appearances and even adds a certain functionality to signatures built according to an older version of this scheme. As mentioned above, the PDF specification does not prescribe any such scheme; actually it even forbids such a functionality, cf. this answer.

Nonetheless many stack overflow questions in particular from India seem to indicate that signatures following that obsolete scheme are often required there by clients.

If one follows this scheme, the appearance itself is constructed as a hierarchy of form XObjects, in particular a set of so called "layers" n0 through n4 from which n2 is the layer onto which a signer is expected to apply its identity.

The generic solution above can be adapted as follows to comply with this scheme:

using (PdfReader pdfReader = new PdfReader(SRC))
using (PdfStamper pdfStamper = new PdfStamper(pdfReader, new FileStream(DEST, FileMode.Create, FileAccess.Write), '', 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);
        }
    }
}

making use of the following helper method:

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;
}

(Beware: This code assumes the signatures to follow the Adobe scheme; if you are not sure that your input does, add some sanity checks and default to the generic solution above.)


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...