Working with java use Apache PDFBox to sign and certified, invalid certified if signature exist, with JsignPDF was able to certified when approval exist, the process is sign after that do certified (seal) document
Signature with JsignPDF
document after certified with JsignPDF :
the certified invalid with PDFBox
the function from PDFBox with some editing was to try :
public void signPDF(File inputFile, File signedFile, Rectangle2D humanRect, String tsaUrl, int page, String SignatureField, boolean isWithQR, String name, UserSignature userSignature) throws IOException, CertificateEncodingException, NoSuchAlgorithmException, OperatorCreationException, CMSException {
if (inputFile == null || !inputFile.exists()) {
throw new IOException("Document for signing does not exist");
}
setTsaUrl(tsaUrl);
// creating output document and prepare the IO streams.
// try (FileOutputStream fos = new FileOutputStream(signedFile);
// PDDocument doc = Loader.loadPDF(inputFile)) {
// creating output document and prepare the IO streams.
FileOutputStream fos = new FileOutputStream(signedFile);
// load document
PDDocument doc = PDDocument.load(inputFile);
int accessPermissions = SigUtils.getMDPPermission(doc);
LogSystem.info("Document permission " + accessPermissions);
if (accessPermissions == 1) {
throw new IllegalStateException("No changes to the document are permitted due to DocMDP transform parameters dictionary");
}
// Note that PDFBox has a bug that visual signing on certified files with permission 2
// doesn't work properly, see PDFBOX-3699. As long as this issue is open, you may want to
// be careful with such files.
PDSignature signature = null;
PDAcroForm acroForm = doc.getDocumentCatalog().getAcroForm(null);
PDRectangle rect = null;
// sign a PDF with an existing empty signature, as created by the CreateEmptySignatureForm example.
if (acroForm != null) {
signature = findExistingSignature(acroForm, SignatureField);
if (signature != null) {
rect = acroForm.getField(SignatureField).getWidgets().get(0).getRectangle();
}
}
if (signature == null) {
// create signature dictionary
signature = new PDSignature();
}
if (rect == null) {
rect = createSignatureRectangle(doc, humanRect);
}
// Optional: certify
// can be done only if version is at least 1.5 and if not already set
// doing this on a PDF/A-1b file fails validation by Adobe preflight (PDFBOX-3821)
// PDF/A-1b requires PDF version 1.4 max, so don't increase the version on such files.
// if (doc.getVersion() >= 1.5f && accessPermissions == 0)
// {
// SigUtils.setMDPPermission(doc, signature, 2);
// }
if (acroForm != null && acroForm.getNeedAppearances()) {
// PDFBOX-3738 NeedAppearances true results in visible signature becoming invisible
// with Adobe Reader
if (acroForm.getFields().isEmpty()) {
// we can safely delete it if there are no fields
acroForm.getCOSObject().removeItem(COSName.NEED_APPEARANCES);
// note that if you've set MDP permissions, the removal of this item
// may result in Adobe Reader claiming that the document has been changed.
// and/or that field content won't be displayed properly.
// ==> decide what you prefer and adjust your code accordingly.
} else {
System.out.println("/NeedAppearances is set, signature may be ignored by Adobe Reader");
}
}
// default filter
signature.setFilter(PDSignature.FILTER_ADOBE_PPKLITE);
// subfilter for basic and PAdES Part 2 signatures
signature.setSubFilter(PDSignature.SUBFILTER_ADBE_PKCS7_DETACHED);
signature.setName("Name");
signature.setLocation("Location");
if(userSignature.getType().equals("sign"))
{
signature.setReason("Reason");
if (accessPermissions == 0) {
COSDictionary sigDict = signature.getCOSObject();
// DocMDP specific stuff
COSDictionary transformParameters = new COSDictionary();
transformParameters.setItem(COSName.TYPE, COSName.TRANSFORM_PARAMS);
transformParameters.setInt(COSName.P, 0);
transformParameters.setNeedToBeUpdated(true);
COSDictionary referenceDict = new COSDictionary();
referenceDict.setItem(COSName.TYPE, COSName.SIG_REF);
referenceDict.setItem(COSName.TRANSFORM_METHOD, COSName.DOCMDP);
referenceDict.setItem(COSName.DIGEST_METHOD, COSName.getPDFName("SHA256"));
referenceDict.setItem(COSName.TRANSFORM_PARAMS, transformParameters);
referenceDict.setNeedToBeUpdated(true);
COSArray referenceArray = new COSArray();
referenceArray.add(referenceDict);
sigDict.setItem(COSName.REFERENCE, referenceArray);
referenceArray.setNeedToBeUpdated(true);
COSDictionary catalogDict = doc.getDocumentCatalog().getCOSObject();
COSDictionary permsDict = new COSDictionary();
catalogDict.setItem(COSName.PERMS, permsDict);
permsDict.setItem(COSName.DOCMDP, signature);
catalogDict.setNeedToBeUpdated(true);
permsDict.setNeedToBeUpdated(true);
}
}
if(userSignature.getType().equals("seal"))
{
signature.setReason(userSignature.getQrText());
// try {
// if (doc.getVersion() >= 1.5f && accessPermissions == 0)
// {
// SigUtils.setMDPPermission(doc, signature, 2);
// }
// }catch(Exception e)
// {
// e.printStackTrace();
// }
if (accessPermissions == 0) {
// SigUtils.setMDPPermission(document, signature, 1);
COSDictionary sigDict = signature.getCOSObject();
// DocMDP specific stuff
COSDictionary transformParameters = new COSDictionary();
transformParameters.setItem(COSName.TYPE, COSName.TRANSFORM_PARAMS);
transformParameters.setInt(COSName.P, 2);
transformParameters.setNeedToBeUpdated(true);
COSDictionary referenceDict = new COSDictionary();
referenceDict.setItem(COSName.TYPE, COSName.SIG_REF);
referenceDict.setItem(COSName.TRANSFORM_METHOD, COSName.DOCMDP);
referenceDict.setItem(COSName.DIGEST_METHOD, COSName.getPDFName("SHA256"));
referenceDict.setItem(COSName.TRANSFORM_PARAMS, transformParameters);
referenceDict.setNeedToBeUpdated(true);
COSArray referenceArray = new COSArray();
referenceArray.add(referenceDict);
sigDict.setItem(COSName.REFERENCE, referenceArray);
referenceArray.setNeedToBeUpdated(true);
COSDictionary catalogDict = doc.getDocumentCatalog().getCOSObject();
COSDictionary permsDict = new COSDictionary();
catalogDict.setItem(COSName.PERMS, permsDict);
permsDict.setItem(COSName.DOCMDP, signature);
catalogDict.setNeedToBeUpdated(true);
permsDict.setNeedToBeUpdated(true);
}
}
// the signing date, needed for valid signature
signature.setSignDate(Calendar.getInstance());
// do not set SignatureInterface instance, if external signing used
SignatureInterface signatureInterface = isExternalSigning() ? null : this;
// register signature dictionary and sign interface
signatureOptions = new SignatureOptions();
signatureOptions.setVisualSignature(createVisualSignatureTemplate(doc, 0, rect, signature, isWithQR, name, userSignature.getDescOnly(), userSignature.getType(), userSignature.isVisible()));
signatureOptions.setPage(page);
doc.addSignature(signature, signatureInterface, signatureOptions);
doc.getDocumentCatalog().getAcroForm().getField("Signature1").setPartialName(SignatureField);
if (isExternalSigning()) {
this.tsaUrl=tsaUrl;
// ExternalSigningSupport externalSigning = doc.saveIncrementalForExternalSigning(fos);
// // invoke external signature service
// byte[] cmsSignature = sign(externalSigning.getContent());
ExternalSigningSupport externalSigning = doc.saveIncrementalForExternalSigning(fos);
// invoke external signature service
byte[] cmsSignature =IOUtils.toByteArray(externalSigning.getContent());
String sgn= signingProcess(cmsSignature);
// set signature bytes received from the service
externalSigning.setSignature(attachSignature(sgn));
} else {
// write incremental (only for signing purpose)
doc.saveIncremental(fos);
}
doc.close();
// } catch (CertificateEncodingException e) {
// e.printStackTrace();
// } catch (NoSuchAlgorithmException e) {
// e.printStackTrace();
// } catch (OperatorCreationException e) {
// e.printStackTrace();
// } catch (CMSException e) {
// e.printStackTrace();
// }
// Do not close signatureOptions before saving, because some COSStream objects within
// are transferred to the signed document.
// Do not allow signatureOptions get out of scope before saving, because then the COSDocument
// in signature options might by closed by gc, which would close COSStream objects prematurely.
// See https://issues.apache.org/jira/browse/PDFBOX-3743
IOUtils.closeQuietly(signatureOptions);
}
set setMDPermission to certified the document :
if (accessPermissions == 0) {
COSDictionary sigDict = signature.getCOSObject();
// DocMDP specific stuff
COSDictionary transformParameters = new COSDictionary();
transformParameters.setItem(COSName.TYPE, COSName.TRANSFORM_PARAMS);
transformParameters.setInt(COSName.P, 1);
transformParameters.setNeedToBeUpdated(true);
COSDictionary referenceDict = new COSDictionary();
referenceDict.setItem(COSName.TYPE, COSName.SIG_REF);
referenceDict.setItem(COSName.TRANSFORM_METHOD, COSName.DOCMDP);
referenceDict.setItem(COSName.DIGEST_METHOD, COSName.getPDFName("SHA256"));
referenceDict.setItem(COSName.TRANSFORM_PARAMS, transformParameters);
referenceDict.setNeedToBeUpdated(true);
COSArray referenceArray = new COSArray();
referenceArray.add(referenceDict);
sigDict.setItem(COSName.REFERENCE, referenceArray);
referenceArray.setNeedToBeUpdated(true);
COSDictionary catalogDict = doc.getDocumentCatalog().getCOSObject();
COSDictionary permsDict = new COSDictionary();
catalogDict.setItem(COSName.PERMS, permsDict);
permsDict.setItem(COSName.DOCMDP, signature);
catalogDict.setNeedToBeUpdated(true);
permsDict.setNeedToBeUpdated(true);
}
Signature with the parameter "sign" and Certified with parameter "seal"
*Update, on PDFBox the first existing signature always be certified, not second signature ? can the last sign be certified and swap the first one ?
any suggest ?
This answer essentially is a more detailed version of the comments, essentially finding that what you want to do is not possible.
You ask for a way to
Certify Document when Approval Signature exist
According to the current PDF specification:
ISO 32000-2:2020 subsection 12.8.1 "General" of 12.8 "Digital signatures" |
---|
A PDF document may contain the following standard types of signatures: [...] One or more approval signatures (also known as recipient signatures). These shall follow the certification signature if one is present. |
Thus, you cannot validly add a certification signature to a PDF which already has approval signatures.
(This does not automatically mean that all validators will detect the issue if you add a certification signature after approval signatures, let alone correctly display the cause. Many validators only do a subset of the checks that strictly speaking are necessary...)
More question if the certification signature come first then next is approval like 3 signature approval, can the last the certification setMDPPermission with 1 ? so at the end the document can't add more approval signature
setMDPPermission
adds a DocMDP transform to the signature, and such a transform makes the signature a certification signature. Thus, using this method when signing a document that already has an approval signature, will create an invalid PDF or fail entirely.
You can lock a PDF document with an approval signature, though, if you add a Lock dictionary to the signature field with a P entry of 1. Beware, though, this is a ISO 32000-2 feature originally introduced as a feature of an Adobe Extension to ISO 32000-1. Not all PDF viewers support ISO 32000-2 yet, so some viewers may not respect this entry.