0

I was trying to add multiple signatures in a single pdf on stamper. I am able to add multiple stampers. In my case on one, I was getting the error

at least one signature is invalid. Also need to make valid all signature.

I want to add multiple valid signs in a single PDF. Please help me. In image only one sign is valid other signs are invalid, so let me what I'm doing wrong

My code snapshot below

public void getSignOnPdf(Map<Integer, byte[]> PdfSigneture1, List<Long> documentIds, List<String> calTimeStamp,
        String originalPdfReadServerPath, String tickImagePath, int serverTime, int pageNumberToInsertStamp,
        String name, String location, String reasonForSign, int xCo_ordinates, int yCo_ordinates,
        int signatureWidth, int signatureHeight, String pdfPassword, String internal_outputFinalPdfPath)
        throws Exception {
    String pdfReadServerPath = null;
    String l_slash = new String();
    String originalPDFPath = new String(originalPdfReadServerPath.trim());
    
    boolean isCorrectPDFOutputPath = false;
    String aspOutputPdfServerPath = null;
    synchronized (this) {
        if ((internal_outputFinalPdfPath != null) && (!internal_outputFinalPdfPath.trim().isEmpty())) {
            System.out.println("[" + EsignCommonFuntion.generateTimeStampForLog()
                    + "] :1-->  outputFinalPdfPath is: " + internal_outputFinalPdfPath);
            if (!(new File(internal_outputFinalPdfPath)).isFile()) {
                isCorrectPDFOutputPath = true;
                aspOutputPdfServerPath = internal_outputFinalPdfPath;
            } else {
                System.out.println("1--> Please provide directory path for outputFinalPdfPath: "
                        .concat(String.valueOf(internal_outputFinalPdfPath)));
            }
        } else {
            System.out.println(" 1--> outputFinalPdfPath is empty or null: "
                    .concat(String.valueOf(internal_outputFinalPdfPath)));
        }
    }
    boolean isPasswordPresent = false;
    String pdfPasswordForEncryption;
    synchronized (this) {
        if ((pdfPassword != null) && (!pdfPassword.trim().isEmpty())) {
            pdfPasswordForEncryption = pdfPassword.trim();
            isPasswordPresent = true;
        } else {
            pdfPasswordForEncryption = null;
        }
        String pdfOriginalName = (new File(originalPDFPath)).getName();
        String pdfAbsolutePath = originalPDFPath.substring(0, originalPDFPath.lastIndexOf(l_slash));
        if (isPasswordPresent) {
            pdfAbsolutePath = getEncryptedPdfName(originalPDFPath, pdfAbsolutePath + l_slash,
                    pdfPasswordForEncryption, pdfOriginalName);
            pdfReadServerPath = new String(pdfAbsolutePath);
        } else {
            pdfReadServerPath = originalPDFPath;
        }
    }
    ArrayList<String> unSignedFilesList = new ArrayList<String>();

    Map<Integer, byte[]> l_PdfSigneture = PdfSigneture1;

    int actual_pageNumForStamp = 1;

    String pdfFileName = (new File(pdfReadServerPath)).getName();

    FileOutputStream fos = null;

    String nameToShowInSignature = name;
    String locationToShowInSignature = location;
    String reasonForSignatureSign = reasonForSign;

    PDDocument documentFinal = null;
    try {
        pdfReadServerPath = pdfReadServerPath.substring(0, pdfReadServerPath.lastIndexOf(l_slash));
        System.out.println("inside getSignOnMethod pdfAbsolutePath:".concat(String.valueOf(pdfReadServerPath)));
        unSignedFilesList.add(pdfFileName);
        System.out.println("inside getSignOnMethod pdfFileName:".concat(String.valueOf(pdfFileName)));
        
        String PDFpath = pdfReadServerPath + l_slash + (String) (unSignedFilesList).get(0);

        System.out.println("Inside for PDFpath: ".concat(String.valueOf(PDFpath)));

        String finalOutputPdfName = ((String) (unSignedFilesList).get(0)).substring(0,
                ((String) (unSignedFilesList).get(0)).lastIndexOf(".")) + "_signedFinal.pdf";

        File outFile2 = null;

        if (isCorrectPDFOutputPath) {
            System.out.println("if condition Final signed PDF ouptut Path: " + aspOutputPdfServerPath + l_slash
                    + finalOutputPdfName);
            outFile2 = new File(aspOutputPdfServerPath + l_slash + finalOutputPdfName);
            fos = new FileOutputStream(outFile2);
        } else {
            outFile2 = new File(pdfReadServerPath + l_slash + outFile2);
            fos = new FileOutputStream(outFile2);
        }

        documentFinal = PDDocument.load(new File(PDFpath));
            
        for (int i = 1; i < 4; i++) {
            FileInputStream image2 = new FileInputStream(tickImagePath);
            
            PDSignature pdsignature = new PDSignature();
            pdsignature.setFilter(PDSignature.FILTER_ADOBE_PPKLITE);
            pdsignature.setSubFilter(PDSignature.SUBFILTER_ADBE_PKCS7_DETACHED);

            Calendar cal = GregorianCalendar.getInstance();
            SimpleDateFormat l_simpleDateFormater = new SimpleDateFormat("yyyyMMdd_HHmmss");
            String timeStamp = (String) calTimeStamp.get(i - 1);

            try {
                cal.setTime(l_simpleDateFormater.parse(timeStamp));
            } catch (ParseException ex) {
                ex.printStackTrace();
            }

            cal.add(12, serverTime);
            pdsignature.setSignDate(cal);
            documentFinal.setDocumentId((Long) documentIds.get(i - 1));

            String dateToShowInSignature = cal.getTime().toString();
            
            Float saveIncrementalObj1 = null;
            saveIncrementalObj1 = new Float((float) xCo_ordinates, (float) yCo_ordinates, (float) signatureWidth,
                    (float) signatureHeight);

            PDRectangle rect = getPDRectangle(documentFinal, saveIncrementalObj1, i);
            PDVisibleSignDesigner visibleSig;
            (visibleSig = new PDVisibleSignDesigner(documentFinal, image2, i)).xAxis(xCo_ordinates)
                    .yAxis(yCo_ordinates).zoom(-95.0F).signatureFieldName("signature");
            
            PDVisibleSigProperties visibleSignatureProp = new PDVisibleSigProperties();
            
            visibleSignatureProp.signerName("name").signerLocation("location").signatureReason("Security")
                    .preferredSize(0).page(i - 1).visualSignEnabled(true).setPdVisibleSignature(visibleSig)
                    .buildSignature();
            try {
                PdfSigneture = new TreeMap<>();
                // PdfSigneture.clear();
                PdfSigneture = l_PdfSigneture;
                
                if (visibleSignatureProp.isVisualSignEnabled()) {
                    this.options = new SignatureOptions();
                    this.options.setVisualSignature(visibleSignatureProp);
                    this.options.setPage(visibleSignatureProp.getPage());
                    this.options.setVisualSignature(
                            getInputStream(documentFinal, i, rect, tickImagePath, nameToShowInSignature,
                                    locationToShowInSignature, dateToShowInSignature, reasonForSignatureSign));
                    documentFinal.addSignature(pdsignature, this, this.options);
                } else {
                    documentFinal.addSignature(pdsignature, this);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        
        synchronized (this) {
            SaveIncrementalSignObject saveIncrementalSignObject;
            (saveIncrementalSignObject = new SaveIncrementalSignObject()).setFos(fos);
            saveIncrementalSignObject.setPDDocumentFromFile(documentFinal);
            
            saveIncrementalForSign(saveIncrementalSignObject);
        }
    } catch (Exception localException2) {
        System.out.println("Insidemethod -- Exception block" + localException2.getMessage());
        return;
    } finally {
        fos.flush();
        if (fos != null) {
            fos.close();
        }
        documentFinal.close();
    }
}

public static synchronized void saveIncrementalForSign(SaveIncrementalSignObject p_SaveIncrementalObj) {
    PDDocument documentFinal = null;
    try {
        (documentFinal = p_SaveIncrementalObj.getPDDocumentFromFile())
                .saveIncremental(p_SaveIncrementalObj.getFos());
    } catch (Exception e) {
        e.printStackTrace();
        try {
        //              documentFinal.close();
            return;
        } catch (Exception eX) {
            eX.printStackTrace();
            return;
        }
    }
}
rak
  • 108
  • 1
  • 12
  • Please mentioned the PDFBox version, add the code for saveIncrementalForSign, and the code for signing. If possible share the signed PDF. – Tilman Hausherr Oct 16 '18 at 08:39
  • Yes I have add saveincremental code & my pdf box version is 2.0.12 – rak Oct 16 '18 at 09:09
  • Please also share a sample signed PDF. There is so much code it most likely is easier to analyze the PDF first to know what is wrong. – mkl Oct 16 '18 at 10:55
  • Thanks! The problem immediately was obvious when inspecting the PDF. – mkl Oct 16 '18 at 11:24
  • Your recent edit is not really an improvement. – mkl Aug 21 '21 at 06:18

1 Answers1

4

In a comment you clarified what you want to achieve:

I tried to applying one signature to multiple place.

As discussed in the first section below this is not what your code does: your code attempts to apply multiple signatures to one place each in a single revision which is impossible as also explained there.

Applying a single signature to multiple places in a single revision, on the other hand, is not desired by the PDF specification team and some approaches to implement this have been made invalid by the specification, but it is possible as explained in the second section below.

Your approach, and why it cannot work

You appear to try to apply multiple signatures in one pass:

if (isPasswordPresent) {
    documentFinal = PDDocument.load(new File(PDFpath), pdfPasswordForEncryption);
} else {
    documentFinal = PDDocument.load(new File(PDFpath));
}

for (int i = 1; i < 4; i++) {
    FileInputStream image2 = new FileInputStream(tickImagePath);

    PDSignature pdsignature = new PDSignature();

    [...]

    try {
        [...]

        if (visibleSignatureProp.isVisualSignEnabled()) {
            [...]
            documentFinal.addSignature(pdsignature, this, this.options);
        } else {
            documentFinal.addSignature(pdsignature, this);
        }
    } catch (Exception e) {
        System.out.println("Inside getSignOnPdf sub exception block at addSignature:" + e + "error :" + e.getMessage());
        e.printStackTrace();
    }
}

synchronized (this) {
    [...]
    saveIncrementalForSign(saveIncrementalSignObject);
}

This cannot work.

In PDFs multiple signatures are applied one after the other in separate PDF revisions, not all in parallel in the same revision:

image

You can find some backgrounds in this answer and the documents referenced from there.

Thus, in pseudo code what you have to do instead is:

for (int i = 1; i < 4; i++) {
    load current version of the PDF;
    apply the i'th signature;
    save and sign as new current version of the PDF;
}

The method name PDDocument.addSignature might be a little misleading here as it might be assumed to imply that multiple signatures may be added. This is not the case; all signatures will be created as signature fields with their widgets but only the field of the last added PDSignature will actually be signed, so only this last added signature field will actually have a sensible value.

@Tilman - there probably should be a test in PDDocument.addSignature throwing an exception if a signature already has been added since loading the document.

A discussion of your actual task

The path of PDF objects from a signature visualization on a PDF page to the actual signature (the CMS signature container in case of CMS based subfilters) is not immediate. Instead we have

  • the PDF page, in its annotations referencing
  • the signature field widget (the signature visualization) belonging to
  • the signature field referencing
  • the signature value dictionary into which the CMS signature container is embedded.

For the implementation of your actual task,

applying one signature to multiple places,

therefore, there appear to be a number of options to get from multiple pages with signature appearances to the single signature container:

  1. All pages with signature visualizations pointing to the same single widget annotation of the single signature field with the value dictionary containing the signature container.
  2. Each page with signature visualizations pointing to their own widget, but all widgets belonging to the same single signature field with the value dictionary containing the signature container.
  3. Each page with signature visualizations pointing to their own widget, each widget belonging to a separate signature field, but all of them pointing to the same value dictionary containing the signature container.

Let's now look at the PDF specification ISO 32000-2. First of all it warns against having single signatures with multiple visualizations:

The location of a signature within a document can have a bearing on its legal meaning. [...]

If more than one location is associated with a signature, the meaning can become ambiguous.

(ISO 32000-2, section 12.7.5.5 "Signature fields")

Consequentially, the specification attempts to forbid single signatures with multiple visualizations:

A given annotation dictionary shall be referenced from the Annots array of only one page.

(ISO 32000-2, section 12.5.2 "Annotation dictionaries")

This forbids option 1 above.

signature fields shall never refer to more than one annotation

(ISO 32000-2, section 12.7.5.5 "Signature fields")

This forbids option 2.

Apparently, though, option 3 is not explicitly forbidden. For generic form fields value object sharing is even explicitly allowed as the form field value is inheritable!

Thus, strictly speaking creating signatures with multiple visualizations is possible using option 3.

Please be aware, though, that it clearly was not intended by the PDF specification team to allow them, it most likely was an oversight. Thus, you have to reckon that some upcoming corrigenda to the specification will eventually forbid option 3, too.

If you want to try nonetheless, it should be possible to tweak or patch PDFBox to create single signatures with multiple visualizations using the approach of option 3.

It has already proven possible for e.g. iText, cf. this answer.

Furthermore, the sample document you shared makes use of this option.

A proof of concept

As it turns out, it is pretty easy to create a multi-visualization PDF signature using PDFBox along the lines of option 3. In particular it is easier than doing this with iText, cf. the answer referenced above, because the signature value dictionary here is an object one creates and handles oneself while in iText it is created under the hood and just in time.

All one has to do is to create one PDSignature object and generate one signature with it normally (using PDDocument.addSignature) and then add as many other signature fields as one wants, setting the signature value properties of those fields to the single PDSignature object create at the start.

E.g. you can use a method like this to add additional signature fields:

void addSignatureField(PDDocument pdDocument, PDPage pdPage, PDRectangle rectangle, PDSignature signature) throws IOException {
    PDAcroForm acroForm = pdDocument.getDocumentCatalog().getAcroForm();
    List<PDField> acroFormFields = acroForm.getFields();

    PDSignatureField signatureField = new PDSignatureField(acroForm);
    signatureField.setSignature(signature);
    PDAnnotationWidget widget = signatureField.getWidgets().get(0);
    acroFormFields.add(signatureField);

    widget.setRectangle(rectangle);
    widget.setPage(pdPage);

    // from PDVisualSigBuilder.createHolderForm()
    PDStream stream = new PDStream(pdDocument);
    PDFormXObject form = new PDFormXObject(stream);
    PDResources res = new PDResources();
    form.setResources(res);
    form.setFormType(1);
    PDRectangle bbox = new PDRectangle(rectangle.getWidth(), rectangle.getHeight());
    float height = bbox.getHeight();

    form.setBBox(bbox);
    PDFont font = PDType1Font.HELVETICA_BOLD;

    // from PDVisualSigBuilder.createAppearanceDictionary()
    PDAppearanceDictionary appearance = new PDAppearanceDictionary();
    appearance.getCOSObject().setDirect(true);
    PDAppearanceStream appearanceStream = new PDAppearanceStream(form.getCOSObject());
    appearance.setNormalAppearance(appearanceStream);
    widget.setAppearance(appearance);

    try (PDPageContentStream cs = new PDPageContentStream(pdDocument, appearanceStream))
    {
        // show background (just for debugging, to see the rect size + position)
        cs.setNonStrokingColor(Color.yellow);
        cs.addRect(-5000, -5000, 10000, 10000);
        cs.fill();

        float fontSize = 10;
        float leading = fontSize * 1.5f;
        cs.beginText();
        cs.setFont(font, fontSize);
        cs.setNonStrokingColor(Color.black);
        cs.newLineAtOffset(fontSize, height - leading);
        cs.setLeading(leading);
        cs.showText("Signature text");
        cs.newLine();
        cs.showText("some additional Information");
        cs.newLine();
        cs.showText("let's keep talking");
        cs.endText();
    }

    pdPage.getAnnotations().add(widget);
    
    COSDictionary pageTreeObject = pdPage.getCOSObject(); 
    while (pageTreeObject != null) {
        pageTreeObject.setNeedToBeUpdated(true);
        pageTreeObject = (COSDictionary) pageTreeObject.getDictionaryObject(COSName.PARENT);
    }
}

(CreateMultipleVisualizations helper method)

(This method actually is based on the CreateVisibleSignature2.createVisualSignatureTemplate method from the pdfbox examples artifact but severely simplified and now used to create the actual signature fields, not merely a template to copy from.)

Used like this

try (   InputStream resource = PDF_SOURCE_STREAM;
        OutputStream result = PDF_TARGET_STREAM;
        PDDocument pdDocument = PDDocument.load(resource)   )
{
    PDAcroForm acroForm = pdDocument.getDocumentCatalog().getAcroForm();
    if (acroForm == null) {
        pdDocument.getDocumentCatalog().setAcroForm(acroForm = new PDAcroForm(pdDocument));
    }
    acroForm.setSignaturesExist(true);
    acroForm.setAppendOnly(true);
    acroForm.getCOSObject().setDirect(true);

    PDRectangle rectangle = new PDRectangle(100, 600, 300, 100);
    PDSignature signature = new PDSignature();
    signature.setFilter(PDSignature.FILTER_ADOBE_PPKLITE);
    signature.setSubFilter(PDSignature.SUBFILTER_ADBE_PKCS7_DETACHED);
    signature.setName("Example User");
    signature.setLocation("Los Angeles, CA");
    signature.setReason("Testing");
    signature.setSignDate(Calendar.getInstance());
    pdDocument.addSignature(signature, this);

    for (PDPage pdPage : pdDocument.getPages()) {
        addSignatureField(pdDocument, pdPage, rectangle, signature);
    }

    pdDocument.saveIncremental(result);
}

(CreateMultipleVisualizations test testCreateSignatureWithMultipleVisualizations)

one retrieves a PDF with a signature visualization on each page of the result document (and an extra invisible one because I was a bit lazy) but only a single actual signature value (given that this implements SignatureInterface with the byte[] sign(InputStream) method).

Beware, though:

  • The PDSignatureField method setSignature has been deprecated in PDFBox 3.0.0-SNAPSHOT. You might eventually have to inject the PDSignature object using more low-level techniques.
  • This kind of multi-visualization signature is not wanted by the PDF specification teams. Chances are that they eventually will be forbidden.
Community
  • 1
  • 1
mkl
  • 90,588
  • 15
  • 125
  • 265
  • I tried to applying one signature to multiple place. Is it possible?? – rak Oct 16 '18 at 12:24
  • *"hi mkl wishify.in/signed_one.pdf"* - That PDF has a hybrid cross references structure (MS Word style). Please first try with a different PDF with only a cross reference table or only a cross reference stream. These hybrids can make PDFBox work incorrectly. – mkl Oct 16 '18 at 12:29
  • 1
    *"I tried to applying one signature to multiple place. Is it possible??"* - It's not desired by the PDF specification teams. There are tricks valid in PDF 1.x but forbidden in PDF 2.0. You should not do that. – mkl Oct 16 '18 at 12:30
  • @RahulKharade he wrote it. For each signature, you need to open the pdf, sign, then do an incremental save, close. Create a class that does all this, but only for ONE signature. Then call this class for each signature. The two example classes in the PDFBox source code download do this. – Tilman Hausherr Oct 18 '18 at 10:40
  • 1
    @Rahul as mentioned before, there can only be a single signature per revision. Your output image might be the result of applying a trick which most likely is not allowed anymore in pdf 2. For further analysis please share the pdf. – mkl Oct 18 '18 at 13:54
  • @Tilman , I don't under the above link which you shared, can to help me to explain. – rak Oct 20 '18 at 06:12
  • 1
    The link won't help you much, it was for @mkl to show that his suggestion was put into work. The fix in 2.0.13 is that you'll get an exception if you are trying to call `addSignature` twice, and the javadoc also warns against doing this. I looked at your PDF... the signature contents are the same, which is of course not correct, but is a result of you trying to sign several times. So please sign only once. Then save incremental, close, reopen the signed PDF etc if you wan to sign more than once. – Tilman Hausherr Oct 20 '18 at 06:27
  • Signature not validating using above method @mkl – rak Nov 06 '18 at 06:32
  • @Rahul i tested that code and the signature was validating. Can you share a copy of your result pdf file analysis? – mkl Nov 06 '18 at 08:19
  • @RahulKharade Your example PDF was not signed at all. Thus, I ran it through the signing test code described and referenced in my answer and the result validates ok: [screen shot](https://i.stack.imgur.com/nvK5I.png). Thus, if your output does not, you in some relevant way deviate from what I describe in my answer. – mkl Nov 12 '18 at 10:29
  • @Rahul Usually there is a way to adapt the `sign` method to use arbitrary signature creation APIs. How exactly you should do this, depends on the signature creation API used. Thus, please supply details in that regard. And please do so in a new question, this one already is pretty loaded. – mkl Nov 13 '18 at 09:14
  • @mkl I want to remove that invisible signature , https://stackoverflow.com/questions/53295461/how-to-remove-extra-invisible-signature-in-pdfbox, I am new in pdf related development – rak Nov 14 '18 at 08:55
  • @rak Look at the code in `testCreateSignatureWithMultipleVisualizations`. It is based on the standard samples for generating an invisible signature. You simply have to use the standard sample code for a visible signature as base instead and add the `for` loop there (with one less iteration as the base code already creates one visible signature). – mkl Nov 14 '18 at 09:14
  • In my case I'm using a JPG file as the signature picture so the example code wasn't useful for me, but the concept itself was. This answer was really helpful. I basically replicated the "addSignature" method removing some code so it only added a PDSignatureField, and called it for every page. As a sidenote this results in multiple signatures appearing in the signatures tab when opening the PDF file in Acrobat Reader, but they all points to the same revision so I think this is the desired outcome. – Daniel Facciabene Aug 05 '22 at 21:33
  • Thanks @mkl , Using the approach 3 mentioned above, I was able to add multiple approval signature visualisations (with corresponding different fields) on different pages where all of them pointing to same signature dictionary. But when I tried the same for Certification signatures (by adding DocMDP), All signatures except the last one are shown as unverified in acrobat for all kinds of DocMDP permissions. Is it happening due to the fact that "there can be only one certification signature which should be the first one in the document"? – Qazazaz May 24 '23 at 08:07
  • @Qazazaz *《Is it happening due to the fact that "there can be only one certification signature which should be the first one in the document"?》* - that's probable. Adobe Acrobat is not open source, so I cannot be sure, though. – mkl May 24 '23 at 11:10