2

I have a signed PDF. I would like to show this signature in the document. I can add a new signature field, this way:

Stamper.addSignature("My Signature", 1, 20f, 10f, 100f, 100f);

But I can't find a way to associate it with the signature that is already in the document.

How can I associate it?

Tunaki
  • 132,869
  • 46
  • 340
  • 423
user5918204
  • 41
  • 1
  • 3
  • 1
    *I have a signed PDF. I would like to show this signature in the document.* - if the PDF is signed, its signature already shows, doesn't it? I don't understand what you try to achieve. – mkl May 04 '16 at 14:29
  • Hi, The sign is showed when you open the Signature Panel, but i would like the sign would be showed in the document filling a field. Something like this: [link(]http://www.isunshare.com/blog/wp-content/uploads/2015/02/open-signed-pdf-document.png) – user5918204 May 05 '16 at 08:58

1 Answers1

8

The OP wants to attach an in-document signature visualization to an existing signature.

First of all, this obviously is not allowed if your documents has been certified with no changes allowed

Quite surprisingly, though, it does seem to be allowed for signed but uncertified documents (which the sample file is which I worked with).

Indeed, unless a document has been certified with no changes allowed, you always are allowed to fill in forms and (except for documents certified with form fill-in and digital signatures allowed) even to modify annotations, cf. this answer for an overview.

As PDF signatures are form field values and visualizations of form fields are special annotations, changing signature visualizations is allowed as form fill-in or at least as annotation modification.

Doing it in iText 5

The OP tried to implement this by adding a new signature field:

Stamper.addSignature("My Signature", 1, 20f, 10f, 100f, 100f);

This does not help, though, because one has to change the existing signature field, not create a new one.

Using iText 5.x this can be done using the generic form field manipulation API:

PdfReader pdfReader = new PdfReader(resource);
PdfStamper pdfStamper = new PdfStamper(pdfReader, result, '\0', true);

AcroFields acroFields = pdfStamper.getAcroFields();
for (String signatureName : acroFields.getSignatureNames())
{
    Item field = acroFields.getFieldItem(signatureName);
    field.writeToAll(PdfName.RECT, new PdfArray(new int[]{100,100,200,200}), Item.WRITE_WIDGET);
    field.markUsed(acroFields, Item.WRITE_WIDGET);

    PdfAppearance appearance = PdfAppearance.createAppearance(pdfStamper.getWriter(), 100, 100);
    appearance.setColorStroke(BaseColor.RED);
    appearance.moveTo(0, 0);
    appearance.lineTo(99, 99);
    appearance.moveTo(0, 99);
    appearance.lineTo(99, 0);
    appearance.stroke();

    PdfDictionary appDict = new PdfDictionary();
    appDict.put(PdfName.N, appearance.getIndirectReference());
    field.writeToAll(PdfName.AP, appDict, Item.WRITE_WIDGET);
}

pdfStamper.close();

(ChangeSignatureAppearance.java method testChangeAppearances)

This code creates a new signature appearance for each integrated PDF signature, in this case a red cross located at 100, 100 and sized 100x100, but you can create any appearance you like there.

Beware: this code assumes the invisible signature to be associated with some document page already. For invisible signatures which are not associated with a page yet, an association will have to be established. This probably might turn out to be a change which is not allowed, at least it is no mere form fill-in anymore because also the form structure is changed, not only its entries.


The OP indicated in a comment

But i would like retrieve the name of the sign and write it instead of a red cross

For this your merely need to slightly change the above code:

PdfReader pdfReader = new PdfReader(resource);
PdfStamper pdfStamper = new PdfStamper(pdfReader, result, '\0', true);

AcroFields acroFields = pdfStamper.getAcroFields();
for (String signatureName : acroFields.getSignatureNames())
{
    PdfPKCS7 pkcs7 = acroFields.verifySignature(signatureName);
    X509Certificate signerCert = (X509Certificate) pkcs7.getSigningCertificate();
    String signerName = CertificateInfo.getSubjectFields(signerCert).getField("CN");

    Item field = acroFields.getFieldItem(signatureName);
    field.writeToAll(PdfName.RECT, new PdfArray(new int[]{100,100,200,200}), Item.WRITE_WIDGET);
    field.markUsed(acroFields, Item.WRITE_WIDGET);

    PdfAppearance appearance = PdfAppearance.createAppearance(pdfStamper.getWriter(), 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.getIndirectReference());
    field.writeToAll(PdfName.AP, appDict, Item.WRITE_WIDGET);
}

pdfStamper.close();

(ChangeSignatureAppearance.java method testChangeAppearancesWithName)

In case of the sample document BouncyCastle has to be registered as security provider.

And the warning from above still applies.

Doing it in iText 7

As iText 7 recently has been released, the code above can be ported to it like this:

try (   PdfReader pdfReader = new PdfReader(resource);
        PdfWriter pdfWriter = new PdfWriter(result);
        PdfDocument pdfDocument = new PdfDocument(pdfReader, pdfWriter, new StampingProperties().useAppendMode()))
{
    SignatureUtil signatureUtil = new SignatureUtil(pdfDocument);
    PdfAcroForm acroForm = PdfAcroForm.getAcroForm(pdfDocument, false);

    for (String name : signatureUtil.getSignatureNames())
    {
        PdfFormField field = acroForm.getField(name);
        field.setModified();
        for (PdfWidgetAnnotation pdfWidgetAnnotation : field.getWidgets())
        {
            pdfWidgetAnnotation.setRectangle(new PdfArray(new int[]{100, 100, 200, 200}));

            PdfFormXObject form = new PdfFormXObject(new Rectangle(100, 100));
            PdfCanvas canvas = new PdfCanvas(form, pdfDocument);
            canvas.setStrokeColor(Color.RED);
            canvas.moveTo(0, 0);
            canvas.lineTo(99, 99);
            canvas.moveTo(0, 99);
            canvas.lineTo(99, 0);
            canvas.stroke();

            pdfWidgetAnnotation.setNormalAppearance(form.getPdfObject());
        }
    }
}

(ChangeSignatureAppearance.java method testChangeAppearances)

This code requires the iText 7 artifacts kernel, forms, and sign.

The same warning as in case of the iText 5 code above applies:

Beware: this code assumes the invisible signature to be associated with some document page already.


The variant with the subject's name looks like this:

try (   PdfReader pdfReader = new PdfReader(resource);
        PdfWriter pdfWriter = new PdfWriter(result);
        PdfDocument pdfDocument = new PdfDocument(pdfReader, pdfWriter, new StampingProperties().useAppendMode()))
{
    SignatureUtil signatureUtil = new SignatureUtil(pdfDocument);
    PdfAcroForm acroForm = PdfAcroForm.getAcroForm(pdfDocument, false);

    for (String name : signatureUtil.getSignatureNames())
    {
        PdfPKCS7 pkcs7 = signatureUtil.verifySignature(name);
        X509Certificate signerCert = (X509Certificate) pkcs7.getSigningCertificate();
        String signerName = CertificateInfo.getSubjectFields(signerCert).getField("CN");
        PdfFormField field = acroForm.getField(name);
        field.setModified();
        for (PdfWidgetAnnotation pdfWidgetAnnotation : field.getWidgets())
        {
            pdfWidgetAnnotation.setRectangle(new PdfArray(new int[]{100, 100, 200, 200}));

            PdfFormXObject form = new PdfFormXObject(new Rectangle(100, 100));
            Canvas canvas = new Canvas(form, pdfDocument);
            canvas.add(new Paragraph().setItalic().add("Signed by:"));
            canvas.add(new Paragraph().setBold().add(signerName));

            pdfWidgetAnnotation.setNormalAppearance(form.getPdfObject());
        }
    }
}

(ChangeSignatureAppearance.java method testChangeAppearancesWithName)

This code additionally uses the iText 7 artifact layout. Furthermore, in case of the sample document BouncyCastle has to be registered as security provider just like for the iText 5 code above.

And again the warning from above still applies.

Looking at it in Adobe Acrobat Reader DC

I tested this in Adobe Acrobat Reader DC with an invisibly signed blank document BLANK-signed.pdf:

Screen shot of original blank signed PDF

Having manipulated the file using the code above, I got:

Screen shot of blank signed PDF with added appearance

The warning concerning unsigned changes is correct, but after signing again, even that warning vanishes:

Screen shot of blank signed PDF with added appearance and signed again


The variant with signer name looks like this:

Screen shot of blank signed PDF with added appearance with signer name

Appendix: Multiple appearances of a signature

The OP asked in a comment

This method only stampt it in the first page of the document. How can I stamp it in all pages in the document ?

The methods above in general actually don't stamp on the first page but on the page associated with the signature. As invisible signatures often are associated with the first page, though, it is understandable why it appeared so.

Furthermore, multiple appearances of a single signature field are not universally supported (while not actually being forbidden by ISO 32000-1), and they will be forbidden by the upcoming ISO 32000-2. Thus, it is not the best idea to go that way.

If there is no way around it, though, you can try something like this in iText 7:

try (   PdfReader pdfReader = new PdfReader(resource);
        PdfWriter pdfWriter = new PdfWriter(result);
        PdfDocument pdfDocument = new PdfDocument(pdfReader, pdfWriter, new StampingProperties().useAppendMode()))
{
    SignatureUtil signatureUtil = new SignatureUtil(pdfDocument);
    PdfAcroForm acroForm = PdfAcroForm.getAcroForm(pdfDocument, false);

    for (String name : signatureUtil.getSignatureNames())
    {
        PdfPKCS7 pkcs7 = signatureUtil.verifySignature(name);
        X509Certificate signerCert = (X509Certificate) pkcs7.getSigningCertificate();
        String signerName = CertificateInfo.getSubjectFields(signerCert).getField("CN");
        PdfFormField field = acroForm.getField(name);
        field.setModified();

        Rectangle rectangle = new Rectangle(100, 100);
        PdfFormXObject form = new PdfFormXObject(rectangle);
        Canvas canvas = new Canvas(form, pdfDocument);
        canvas.add(new Paragraph().setItalic().add("Signed by:"));
        canvas.add(new Paragraph().setBold().add(signerName));

        for (PdfWidgetAnnotation pdfWidgetAnnotation : field.getWidgets())
        {
            PdfDictionary pageObject = pdfWidgetAnnotation.getPageObject();
            PdfPage page = pdfDocument.getPage(pageObject);
            page.removeAnnotation(pdfWidgetAnnotation);

            pdfWidgetAnnotation.releaseFormFieldFromWidgetAnnotation();
        }

        for (int pageNumber = 1; pageNumber <= pdfDocument.getNumberOfPages(); pageNumber++)
        {
            PdfPage pdfPage = pdfDocument.getPage(pageNumber);
            PdfWidgetAnnotation pdfWidgetAnnotation = new PdfWidgetAnnotation(rectangle);
            pdfWidgetAnnotation.setNormalAppearance(form.getPdfObject());
            pdfWidgetAnnotation.setPage(pdfPage);
            field.addKid(pdfWidgetAnnotation);
            pdfPage.addAnnotation(pdfWidgetAnnotation);
        }
    }
}

(ChangeSignatureAppearance.java method testChangeAppearancesWithNameAllPages)

This first removes any existing annotations of the signature field and then adds new ones to all fields.

Just like above this will make a warning about unsigned changes appear which is true after all.

As an aside, if one takes a signed document with a visualization on one page and changes the code above not to remove the original annotation, one can easily add copies of that annotation to all pages, and the current Adobe Acrobat Reader does not even show a warning! The Reader code for checking for changes in a signed document is weird indeed...

Community
  • 1
  • 1
mkl
  • 90,588
  • 15
  • 125
  • 265
  • Thank you mkl! It works. But i would like retrieve the name of the sign and write it instead of a red cross? – user5918204 May 09 '16 at 09:31
  • *the name of the sign* - you mean the subject from the signer certificate? Should be no problem, shouldn't it? – mkl May 09 '16 at 09:43
  • If i write the signatureName shows "Signature1". I would like get the text appears after of "Signed by" (exceed Signature Service in your sample), but i don´t know get this value.Thank you mkl – user5918204 May 09 '16 at 09:48
  • I have try to do this, but it doesnt works: The second line (acroFields.verifySignature() throws an exception: java.lang.NoClassDefFoundError. I dont know if this is the way to get it: Item field = acroFields.getFieldItem(signatureName); PdfPKCS7 pk = acroFields.verifySignature(signatureName); appearance.showText("Subject: " + CertificateInfo.getSubjectFields(pk.getSigningCertificate())); appearance.endText(); – user5918204 May 09 '16 at 10:19
  • *acroFields.verifySignature() throws an exception: java.lang.NoClassDefFoundError* - please also look at the exception message, not only at the exception class. The message will tell you which class. I would assume BouncyCastle is amiss. By the way, I just added iText 7 code adding the signer's common name; things are a bit tidier there... – mkl May 09 '16 at 10:58
  • Yes. I have add in the Project Java built path the bcprov-jdk15on-1.47.jar and bcmail-jdk15on-154 with itext-5.5.9. But i get the next error: Exception in thread "main" java.lang.NoClassDefFoundError: org/bouncycastle/tsp/TimeStampTokenInfo at com.itextpdf.text.pdf.AcroFields.verifySignature(AcroFields.java:2420). – user5918204 May 09 '16 at 12:19
  • *bcprov-jdk15on-1.47.jar and bcmail-jdk15on-154* - **a** BouncyCastle artifact versions must match. **b** iText requires bcprov and bcpkix but not bcmail. **c** iText 5.5.9 requires BouncyCastle artifacts in version 1.49. Cf. the iText pom file. – mkl May 09 '16 at 12:29
  • Now i´m refrencing bcpkix-jdk15on-1.49 and bcprov-jdk15on-1.49. But in this point: PdfPKCS7 pkcs7 = acroFields.verifySignature(signatureName); It´s throw this exception Exception in thread "main" ExceptionConverter: java.security.InvalidKeyException: Key must not be null – user5918204 May 09 '16 at 15:00
  • That's why I wrote *BouncyCastle has to be registered as security provider* - without doing that I get the same exception. – mkl May 09 '16 at 15:29
  • @user5918204 Great! In that case, please mark the answer as accepted (click the tick at its upper left.) – mkl May 11 '16 at 14:34
  • Another question. This method only stampt it in the first page of the document. How can I stamp it in all pages in the document ? – user5918204 May 26 '16 at 11:26
  • *How can I stamp it in all pages in the document* - While it is possible to add appearances on all pages for PDF 1.x, PDF 2.x will forbid this. Thus, you should consider whether you really want that as it will make your application outdated before it even is published. – mkl May 30 '16 at 10:22
  • @user5918204 I added an "Appendix: Multiple appearances of a signature" to the answer. – mkl May 30 '16 at 13:22
  • *"one can easily add copies of that annotation to all pages, and the current Adobe Acrobat Reader does not even show a warning!"* - This has changed, meanwhile Adobe Reader has started showing a "Form Fields with Property Changes" warning. – mkl Apr 09 '18 at 14:14