Multiple esign using pdfbox 2.0.12 java?

前端 未结 1 1184
清歌不尽
清歌不尽 2021-01-27 12:48

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 leas

相关标签:
1条回答
  • 2021-01-27 13:38

    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.
    0 讨论(0)
提交回复
热议问题