I want to sign PDF which has signature fields in it already. I need to add new signature field retaining existing unsigned signature fields. After signing such PDFs, I see that new signature field added by code is always invalid. Says 'document has been altered'.
Below code being used to compute hash of the document:
private DocumentSignatureStructure createSignatureStructureAndComputeHash(byte[] inputFile, File tempFile,
SignatureProperties sigProperties)
throws IOException, NoSuchAlgorithmException {
try (FileOutputStream fos = new FileOutputStream(tempFile);
PDDocument doc = PDDocument.load(inputFile);
SignatureOptions signatureOptions = new SignatureOptions();) {
signatureOptions.setPreferredSignatureSize(SignatureOptions.DEFAULT_SIGNATURE_SIZE * 2);
signatureOptions.setPage(sigProperties.getPage() - 1);
if (sigProperties.isVisibleSignature()) {
PDRectangle rect = createSignatureRectangle(doc, sigProperties);
signatureOptions.setVisualSignature(createVisualSignatureTemplate(doc, rect, sigProperties));
}
PDSignature signature = new PDSignature();
signature.setFilter(PDSignature.FILTER_ADOBE_PPKLITE);
signature.setSubFilter(PDSignature.SUBFILTER_ADBE_PKCS7_DETACHED);
signature.setSignDate(Calendar.getInstance());
doc.addSignature(signature, signatureOptions);
ExternalSigningSupport externalSigning = doc.saveIncrementalForExternalSigning(fos);
MessageDigest digest = MessageDigest.getInstance(sigProperties.getHashAlgorithm().getAlgoName());
byte[] hashBytes = digest.digest(IOUtils.toByteArray(externalSigning.getContent()));
String base64Hash = Base64.toBase64String(hashBytes);
externalSigning.setSignature(new byte[0]);
int offset = signature.getByteRange()[1] + 1;
IOUtils.closeQuietly(signatureOptions);
return DocumentSignatureStructure.builder().offset(offset)
.hashValue(base64Hash)
.build();
}
}
Embedding signature code:
byte[] originalDocumentByte = docBlob.getBytes(1L, (int) docBlob.length());
File file = new File(getTempFolderPath(), getTempFileName("signed"));
try (FileOutputStream fos = new FileOutputStream(file);) {
fos.write(originalDocumentByte);
}
try (RandomAccessFile raf = new RandomAccessFile(file, "rw")) {
raf.seek(documentSignatureStructure.getOffset());
raf.write(Hex.getBytes(Base64.decode(encodedSignature)));
}
Blob signedAndLtvBlob;
try (PDDocument doc = PDDocument.load(file);
FileOutputStream fos = new FileOutputStream(file);
FileInputStream fis = new FileInputStream(file)) {
if (createDss) {
log.info("Adding revocation information to DSS dictionary of PDF");
makeLtv(doc, revocationData);
}
doc.saveIncremental(fos);
}
It did not work with above code.
Googled up and saw few solutions where COSObject 'NeedToBeUpdated' flag needs to be set to true. Added below code block before adding new signature fields in above code.
//..
if (sigProperties.isVisibleSignature()) {
PDRectangle rect = createSignatureRectangle(doc, sigProperties);
signatureOptions.setVisualSignature(createVisualSignatureTemplate(doc, rect, sigProperties));
}
PDAcroForm acroForm = doc.getDocumentCatalog().getAcroForm();
COSDictionary catalogDictionary = doc.getDocumentCatalog().getCOSObject();
catalogDictionary.setNeedToBeUpdated(true);
COSDictionary acroFormDictionary = (COSDictionary) catalogDictionary.getDictionaryObject(COSName.ACRO_FORM);
acroFormDictionary.setNeedToBeUpdated(true);
COSArray array = (COSArray) acroFormDictionary.getDictionaryObject(COSName.FIELDS);
array.setNeedToBeUpdated(true);
for (PDField field : acroForm.getFieldTree()) {
if (field instanceof PDSignatureField) {
COSDictionary fieldDictionary = field.getCOSObject();
COSDictionary dictionary = (COSDictionary) fieldDictionary.getDictionaryObject(COSName.AP);
dictionary.setNeedToBeUpdated(true);
COSStream stream = (COSStream) dictionary.getDictionaryObject(COSName.N);
stream.setNeedToBeUpdated(true);
while (fieldDictionary != null)
{
fieldDictionary.setNeedToBeUpdated(true);
fieldDictionary = (COSDictionary) fieldDictionary.getDictionaryObject(COSName.PARENT);
}
}
}
PDSignature signature = new PDSignature();
signature.setFilter(PDSignature.FILTER_ADOBE_PPKLITE);
signature.setSubFilter(PDSignature.SUBFILTER_ADBE_PKCS7_DETACHED);
//..
Even this did not work.
Resulting PDF shows signature is invalid:

PDF used for signing with signature fields:

Whats the piece I am missing here?
PDF file : https://drive.google.com/file/d/1-vu9_WIfFo198v6AxoBMxCuyX1rE2FOS/view?usp=share_link
Signed PDF (invalid): https://drive.google.com/file/d/1DD0aKVkonH9a_CfGrj9mACe6DBt4Ijsj/view?usp=share_link
Something indeed damaged your file, and it's apparently not the code you show.
Comparing your signed file "Formulier DSS-01 - DC+QV Onboarding Checklist-signed.pdf" with your original "Formulier DSS-01 - DC+QV Onboarding Checklist.pdf", one sees that the former is not simply the latter with incremental updates as one would expect but that some regions in that file part have been overwritten with zeros.
The regions in question are from 0x2c000-0x2cfff and 0x40000-0x40fff.
After fixing this issue by copying the contents from those regions of the original file into the signed file, Adobe Acrobat validates the signature positively.
So you should try and find out what zeroed those two 4KB regions in your file.