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
793 views
in Technique[技术] by (71.8m points)

itext - How to associate a previous signature in a new signature field

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?

See Question&Answers more detail:os

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

1 Reply

0 votes
by (71.8m points)

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, '', 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, '', 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 = (X50

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

...