PDFBox - opening and saving a signed pdf invalidat

2020-02-13 02:27发布

问题:

I am trying to learn to use Apache's pdfBox to deal with digitaly signed documents for work. During testing, I created a completely empty pdf document.

I then signed the document through Adobe reader using the sign with certificate function.

I tried to open, save and close the signed file with pdfBox without any modifications. However once I open the file in Adobe the files are no longer valid.

Adobe tells me: "There are errors in the formatting or information contained in this signature (support information: SigDict/Contents illegal data)"

Since I have not modified the content of the file, intuitively there should not have been any problems and the signature should be still valid, however this is not the case and I don't know what the solutions are (googling yielded no results).

How I create the document:

@Test
public void createEmptyPDF() throws IOException {
    String path = "path to file";
    PDDocument document = new PDDocument();
    PDPage page = new PDPage();
    document.addPage(page);
    document.save(path);
    document.close();
}

I then sign it with adobe and pass it through this:

 @Test
public void copySignedDocument() throws IOException {
    String path = "path to file";
    File file = new File(path);
    PDDocument document = PDDocument.load(file);
    document.save(file);
    document.close();

    //just opening and saving the file invalidates the signatures
}

I am truly at a loss as to why this does not work. Any help would be great!

EDIT:

So I did some digging around and it seems that updating an existing signed document (either adding annotations or filling forms) is not yet implemented in PDFBox 2.0.1 and is scheduled to come in versions 2.1 (however no release date has been specified). More information here and here.

However it seems possible to add annotations on signed documents with IText without invalidating the signature using PDFStamper, from this question

EDIT 2: Code to add a stamp to a document and save it incrementally:

 @Test
public void stampSignedDocument() throws IOException {
    File file = new File("path to file");
    PDDocument document = PDDocument.load(file);
    File image = new File("path to image to be added to annotation");
    PDPage page = document.getPage(0);
    List<PDAnnotation> annotations = page.getAnnotations();
    PDImageXObject ximage = PDImageXObject.createFromFileByContent(image, document);

    //stamp
    PDAnnotationRubberStamp stamp = new PDAnnotationRubberStamp();
    stamp.setName("testing rubber stamp");
    stamp.setContents("this is a test");
    stamp.setLocked(true);
    stamp.setReadOnly(true);
    stamp.setPrinted(true);

    PDRectangle rectangle = createRectangle(100, 100, 100, 100, 100, 100);
    PDFormXObject form = new PDFormXObject(document);
    form.setResources(new PDResources());
    form.setBBox(rectangle);
    form.setFormType(1);

    form.getResources().add(ximage);
    PDAppearanceStream appearanceStream = new PDAppearanceStream(form.getCOSObject());
    PDAppearanceDictionary appearance = new PDAppearanceDictionary(new COSDictionary());
    appearance.setNormalAppearance(appearanceStream);
    stamp.setAppearance(appearance);
    stamp.setRectangle(rectangle);
    PDPageContentStream stream = new PDPageContentStream(document, appearanceStream);
    Matrix matrix = new Matrix(100, 0, 0, 100, 100, 100);
    stream.drawImage(ximage, matrix);
    stream.close();
    //close and save   
    annotations.add(stamp);
    page.getCOSObject().setNeedToBeUpdated(true);
    OutputStream os = new FileOutputStream(file);
    document.saveIncremental(os);
    document.close();
    os.close();
}

The above code doesn't invalidate my signature but doesn't save the annotation that I have added.

As suggested I've set the NeedToBeUpdated flag to true for the added annotation, page and annotations list (I hope I did the last one correctly):

    stamp.getCOSObject().setNeedToBeUpdated(true);
    COSArrayList<PDAnnotation> list = (COSArrayList<PDAnnotation>) annotations;
    COSArrayList.converterToCOSArray(list).setNeedToBeUpdated(true);
    page.getCOSObject().setNeedToBeUpdated(true);
    document.getDocumentCatalog().getCOSObject().setNeedToBeUpdated(true);

The annotation is still not saved so I'm obviously missing something.

EDIT 3:

This is my current method to add an annotation:

    @Test
public void stampSignedDocument() throws IOException {
    File file = new File(
            "E:/projects/eSign/g2digitalsignature/G2DigitalSignatureParent/G2DigitalSignatureTest/src/test/resources/pdfBoxTest/empty.pdf");
    PDDocument document = PDDocument.load(file);
    File image = new File(
            "E:/projects/eSign/g2digitalsignature/G2DigitalSignatureParent/G2DigitalSignatureTest/src/test/resources/pdfBoxTest/digitalSign.png");
    PDPage page = document.getPage(0);
    List<PDAnnotation> annotations = page.getAnnotations();
    PDImageXObject ximage = PDImageXObject.createFromFileByContent(image, document);

    //stamp
    PDAnnotationRubberStamp stamp = new PDAnnotationRubberStamp();
    stamp.setName("testing rubber stamp");
    stamp.setContents("this is a test");
    stamp.setLocked(true);
    stamp.setReadOnly(true);
    stamp.setPrinted(true);

    PDRectangle rectangle = createRectangle(100, 100, 100, 100, 100, 100);
    PDFormXObject form = new PDFormXObject(document);
    form.setResources(new PDResources());
    form.setBBox(rectangle);
    form.setFormType(1);

    form.getResources().getCOSObject().setNeedToBeUpdated(true);
    form.getResources().add(ximage);
    PDAppearanceStream appearanceStream = new PDAppearanceStream(form.getCOSObject());
    PDAppearanceDictionary appearance = new PDAppearanceDictionary(new COSDictionary());
    appearance.setNormalAppearance(appearanceStream);
    stamp.setAppearance(appearance);
    stamp.setRectangle(rectangle);
    PDPageContentStream stream = new PDPageContentStream(document, appearanceStream);
    Matrix matrix = new Matrix(100, 0, 0, 100, 100, 100);
    stream.drawImage(ximage, matrix);
    stream.close();
    //close and save   
    annotations.add(stamp);

    appearanceStream.getCOSObject().setNeedToBeUpdated(true);
    appearance.getCOSObject().setNeedToBeUpdated(true);
    rectangle.getCOSArray().setNeedToBeUpdated(true);
    stamp.getCOSObject().setNeedToBeUpdated(true);
    form.getCOSObject().setNeedToBeUpdated(true);
    COSArrayList<PDAnnotation> list = (COSArrayList<PDAnnotation>) annotations;
    COSArrayList.converterToCOSArray(list).setNeedToBeUpdated(true);
    document.getPages().getCOSObject().setNeedToBeUpdated(true);
    page.getCOSObject().setNeedToBeUpdated(true);
    document.getDocumentCatalog().getCOSObject().setNeedToBeUpdated(true);

    OutputStream os = new FileOutputStream(file);
    document.saveIncremental(os);
    document.close();
    os.close();

}

When I add an annotation using it on a non signed document, the annotation gets added and is visible. However when using it on a signed document, the annotation does not appear.

I have opened the pdf file in notepad++ and have found that the annotation seems to have been added since I found this as well as the rest of the code pertaining to the annotation:

<<
/Type /Annot
/Subtype /Stamp
/Name /testing#20rubber#20stamp
/Contents (this is a test)
/F 196
/AP 29 0 R
/Rect [100.0 100.0 200.0 200.0]
>>

However it does not appear when I open the document in adobe reader. Perhaps this has more to do with the appearance streams than the annotation itself?

回答1:

The problem is that using PDDocument.save() creates a new document and thus invalidates the signature.

Using PDDocument.saveIncremental(...) does not invalidate the signature, however it will not update any changes to the document (such as annotations or form filling), it is only used to save a signature.

Updating a signed PDF document with annotations or form filling is not yet possible with PDFBox 2.0 but should be possible once PDFBox 2.1 rolls out.

Information on the problem : here and here

Using IText's PDFStamper however solve the problem of adding annotations to a signed document without invalidating the signature as answered here.



回答2:

From what I can gather from some of Adobe's documentation, there is a timestamp which is included as part of the signature. I would guess by saving the document (even without any content changes) would modify this timestamp value, hence invalidating the signature (which would have been created using the original timestamp).

This isn't an authoritative, mind you - this is just what I've been able glean from a quick glance at Adobe's documentation on the matter.