Certified Document when Approval Signature exist - java

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.

Related

PDFBox Add Validation Information on Certification Signature

how to add validation information on PDF during signing if signature use Certification
SigUtils.setMDPPermission(doc, signature, 1);
cause warning message on function tell addValidationInformation.validateSignature(inPath, outFile) :
PDF is certified to forbid changes, some readers may report the document as invalid despite that the PDF specification allows DSS additions
i call addValidationInformation function after signing doc, signing.signPDF();
what i have tried with this function :
private void makeLTV() {
try {
COSDictionary catalogDict = doc.getDocumentCatalog().getCOSObject();
catalogDict.setNeedToBeUpdated(true);
byte[][] certs = new byte[certificateChain.length][];
for (int i = 0; i < certificateChain.length; i++) {
certs[i] = certificateChain[i].getEncoded();
}
// Assign byte array for storing certificate in DSS Store.
List<CRL> crlList = new ArrayList<CRL>();
List<OCSPResp> ocspList = new ArrayList<OCSPResp>();
for (int i = 0; i < certificateChain.length; i++) {
X509Certificate cert = (X509Certificate) certificateChain[i];
if (!cert.getIssuerDN().equals(cert.getSubjectDN())) {
X509Certificate issuerCert = (X509Certificate) certificateChain[i + 1];
if (issuerCert != null) {
OCSPResp ocspResp;
ocspResp = new GetOcspResp().getOcspResp(cert, issuerCert);
if (ocspResp != null) {
ocspList.add(ocspResp);
}
}
crlList.addAll(new DssHelper().readCRLsFromCert(cert));
}
}
byte[][] crls = new byte[crlList.size()][];
for (int i = 0; i < crlList.size(); i++) {
crls[i] = ((X509CRL) crlList.get(i)).getEncoded();
LogSystem.info("set CRL data");
}
byte[][] ocsps = new byte[ocspList.size()][];
for (int i = 0; i < ocspList.size(); i++) {
ocsps[i] = ocspList.get(i).getEncoded();
}
Iterable<byte[]> certifiates = Arrays.asList(certs);
COSDictionary dss = new DssHelper().createDssDictionary(certifiates, Arrays.asList(crls),
Arrays.asList(ocsps));
catalogDict.setItem(COSName.getPDFName("DSS"), dss);
} catch (Exception e) {
// TODO Auto-generated catch block
LogSystem.error(e.toString());
e.printStackTrace();
}
before doc.addSignature(signature, signatureInterface, signatureOptions);
PDF is certified to forbid changes, some readers may report the document as invalid despite that the PDF specification allows DSS additions
Well, the LTV level addition is indeed allowed to PDF documents even with restricted MDP permissions. See "Table 257 — Entries in the DocMDP transform parameters dictionary" of ISO 32000-2:
P number (Optional) The access permissions granted for this document. Changes to a PDF that are incremental updates which include only the data necessary to add DSS’s 12.8.4.3, "Document Security Store (DSS)" and/or document timestamps 12.8.5, "Document timestamp (DTS) dictionary" to the document shall not be considered as changes to the document as defined in the choices below.
So technically speaking you are allowed to add validation data to a PDF signature.
However, you need to take into account the practical aspect of this change. For example, the most commonly used application for reading electronic signatures Adobe, more likely, will invalidate such change. The problem here, that the "extension" of a signature with a validation data and/or a timestamp may involve other changes within a PDF document, which may be not considered as part of the allowed changes, as there is no formal guidance on either how to create such signature nor as validate them.
For your information, ETSI standardization group is currently working on a new set of standards that should provide a formal guidance for validation of different AdES signature formats. So maybe in the future this part will be clarified.
For the implementation part of the LTV level addition, I would recommend to add validation data to your signature after the production time of a first timestamp (signature-time-stamp or a PDF document timestamp), that will ensure that the revocation data is fresh and can be considered during the validation process (see "5.2.5 Revocation freshness checker" of EN 319 102-1).
For this you will need to add the revocation data to a signed document within a new incremental update (revision). For this you will need to use the method
pdDocument.saveIncremental(...)
executed on a signed PDDocument.

PDFBox lock signature still can add signature with valid status

refer my question about prevent adding another approval/signing after lock Add lock Dictionary on Certify Signature PDFBox
i have succeed to certify then lock, but after lock when i try to signing/adding another signature/approval is still valid, what i expected is after lock if another sign the document becomes invalid
rev 1 -> rev 2 -> certified -> lock -> then rev 4 (come and still valid signature)
this what i'm try:
//Create empty signature field
PDDocument docEx=PDDocument.load(inputFile);
PDAcroForm acroFormField = docEx.getDocumentCatalog().getAcroForm();
acroFormField.getCOSObject().setNeedToBeUpdated(true);
COSObject fields = acroFormField.getCOSObject().getCOSObject(COSName.FIELDS);
if (fields != null)
fields.setNeedToBeUpdated(true);
acroFormField.setSignaturesExist(true);
acroFormField.setAppendOnly(true);
acroFormField.getCOSObject().setDirect(true);
PDPage pageField = docEx.getPage(page);
// Create empty signature field, it will get the name "Signature1"
PDSignatureField signatureField = new PDSignatureField(acroFormField);
PDAnnotationWidget widget = signatureField.getWidgets().get(0);
PDRectangle rectField = new PDRectangle((float)humanRect.getX(), (float)humanRect.getY(), (float)humanRect.getWidth(), (float)humanRect.getHeight());
widget.setRectangle(rectField);
widget.getCOSObject().setNeedToBeUpdated(true);
widget.setPage(pageField);
pageField.getAnnotations().add(widget);
pageField.getCOSObject().setNeedToBeUpdated(true);
acroFormField.getFields().add(signatureField);
setLock(signatureField, acroFormField);
docEx.getDocumentCatalog().getCOSObject().setNeedToBeUpdated(true);
docEx.saveIncremental(fos);
fos.close();
//Close and replace with created empty field signature
PDDocument doc = PDDocument.load(signedFile);
FileOutputStream fosSeal = new FileOutputStream(signedFile);
PDSignature signatureLock = new PDSignature();
PDSignatureField signatureFieldLoad = (PDSignatureField) doc.getDocumentCatalog().getAcroForm().getField("Signature1");
LogSystem.info("signatureFieldLoad " + signatureFieldLoad.getValueAsString());
LogSystem.info("signaturelock " + signatureLock);
signatureFieldLoad.setValue(signatureLock);
COSBase lock = signatureFieldLoad.getCOSObject().getDictionaryObject(COS_NAME_LOCK);
if (lock instanceof COSDictionary)
{
COSDictionary lockDict = new COSDictionary();
lockDict.setItem(COS_NAME_ACTION, COS_NAME_ALL);
lockDict.setItem(COSName.TYPE, COS_NAME_SIG_FIELD_LOCK);
COSDictionary transformParams = new COSDictionary(lockDict);
transformParams.setItem(COSName.TYPE, COSName.getPDFName("TransformParams"));
transformParams.setItem(COSName.V, COSName.getPDFName("1.2"));
transformParams.setInt(COSName.P, 1);
transformParams.setDirect(true);
transformParams.setNeedToBeUpdated(true);
COSDictionary sigRef = new COSDictionary();
sigRef.setItem(COSName.TYPE, COSName.getPDFName("SigRef"));
sigRef.setItem(COSName.getPDFName("TransformParams"), transformParams);
sigRef.setItem(COSName.getPDFName("TransformMethod"), COSName.getPDFName("FieldMDP"));
sigRef.setItem(COSName.getPDFName("Data"), doc.getDocumentCatalog());
sigRef.setDirect(true);
COSArray referenceArray = new COSArray();
referenceArray.add(sigRef);
signatureLock.getCOSObject().setItem(COSName.getPDFName("Reference"), referenceArray);
LogSystem.info("LOCK DICTIONARY");
final Predicate<PDField> shallBeLocked;
final COSArray fieldsLock = lockDict.getCOSArray(COSName.FIELDS);
final List<String> fieldNames = fieldsLock == null ? Collections.emptyList() :
fieldsLock.toList().stream().filter(c -> (c instanceof COSString)).map(s -> ((COSString)s).getString()).collect(Collectors.toList());
final COSName action = lockDict.getCOSName(COSName.getPDFName("Action"));
if (action.equals(COSName.getPDFName("Include"))) {
shallBeLocked = f -> fieldNames.contains(f.getFullyQualifiedName());
} else if (action.equals(COSName.getPDFName("Exclude"))) {
shallBeLocked = f -> !fieldNames.contains(f.getFullyQualifiedName());
} else if (action.equals(COSName.getPDFName("All"))) {
shallBeLocked = f -> true;
} else { // unknown action, lock nothing
shallBeLocked = f -> false;
}
lockFields(doc.getDocumentCatalog().getAcroForm().getFields(), shallBeLocked);
setMDPPermission(doc, signatureLock, 2);
// default filter
signatureLock.setFilter(PDSignature.FILTER_ADOBE_PPKLITE);
// subfilter for basic and PAdES Part 2 signatures
signatureLock.setSubFilter(PDSignature.SUBFILTER_ADBE_PKCS7_DETACHED);
signatureLock.setName(name);
// the signing date, needed for valid signature
signatureLock.setSignDate(getDate());
// do not set SignatureInterface instance, if external signing used
SignatureInterface signatureInterface = isExternalSigning() ? null : this;
doc.addSignature(signatureLock, signatureInterface, signatureOptions);
doc.getDocumentCatalog().getAcroForm().getField("Signature1").setPartialName(SignatureField);
if (isExternalSigning()) {
this.tsaUrl=tsaUrl;
ExternalSigningSupport externalSigning = doc.saveIncrementalForExternalSigning(fosSeal);
// invoke external signature serviceUsing fallback
byte[] cmsSignature =IOUtils.toByteArray(externalSigning.getContent());
String sgn=null;
try {
sgn = signingProcess(cmsSignature);
}catch(Exception e)
{
LogSystem.error(e.toString());
e.printStackTrace();
doc.close();
Telegram tele = new Telegram();
tele.Send(e.toString());
return false;
}
// set signature bytes received from the service
if (sgn != null)
{
externalSigning.setSignature(attachSignature(sgn));
}
}
any suggest ? or anyone can tell me how it work if i want to prevent the document changes ? thank you

PAdES Signature Level - Adobe Acrobat

I am creating a PADES signature using pdfbox 3.0.0 RC, my code works using the example to create the digital signature. However, I am unable to see the signature level in Adobe Acrobat when I open the document with this tool although it is able to validate my signature.
I am not creating the VRI so I am guessing that this might be an issue but then if this is necessary to validate my signature I don't understand why the signature is displayed as valid?
Adobe Acrobat Signature:
/**
* Service for automatically signing a document as part of a workflow. In this instance no user information is
* gathered
*
* #param taskID
* #param processName which will be added to the document
* #param keyID the ID for the key used to sign the PDF document
* #return the signed PDF document as a base 64 Encoded String
*/
#Transactional
public String signPDFService(String processID,
String processName,
String keyID,
ObjectData signatureImage,
String creator)
{
try {
ByteArrayOutputStream ostream = new ByteArrayOutputStream();
//Optional<ObjectData> pdfDocumentProcessInstance = userDataRepository.findById(documents.get(0).getProcessID());
/*List<Task> tasks = taskService.createTaskQuery()
.taskId(taskID)
.list();
// Optional<ObjectData> pdfDocumentProcessInstance = userDataRepository.findById(documents.get(0).getProcessID());
List<PDFDocument> documents = tasks.stream()
.map(task -> {
String processID = task.getProcessInstanceId();
Map<String, Object> variables = taskService.getVariables(task.getId());
PDFDocument document;
try {
document = new PDFDocument(
(ArrayList) variables.get("assigneeList"),
(String) variables.get("unsignedPDFDocument"),
task.getProcessInstanceId(),
task.getId(),
(String) variables.get("name"),
(String) variables.get("description")
);
document.setHistory((ArrayList) variables.get("history"));
return document;
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
return null;
} catch (CMSException e) {
// TODO Auto-generated catch block
e.printStackTrace();
return null;
}
})
.collect(Collectors.toList());*/
//Optional<ObjectData> pdfDocumentProcessInstance = userDataRepository.findById(documents.get(0).getProcessID());
Optional<ObjectData> pdfDocumentProcessInstance = userDataRepository.findById(processID);
if(pdfDocumentProcessInstance.isEmpty())
throw new IOException("No process found");
String pdfDocumentBase64String = pdfDocumentProcessInstance.get().getAttributes().get("PDFDocument");
String extractedPDFString = pdfDocumentBase64String.replaceAll("data:application/pdf;base64,", "").replaceAll("data:;base64,", "").replaceAll("data:application/octet-stream", "");
//String extractedPDFString = base64PDF.replaceAll("data:application/pdf;base64,", "").replaceAll("data:;base64,", "");
InputStream stream = new ByteArrayInputStream(Base64.getDecoder().decode(extractedPDFString.getBytes()));
//Create the date object to sign the document
Date date = new Date();
Calendar calendar = Calendar.getInstance();
calendar.setTime(date);
//Retrieve certificate chain for the PDF Signer
String certChainPEM = kmsService.getCertChainPEM(keyID);
X509Certificate pdfSignerCertificate = X509Utils.readCertificateChain(certChainPEM).get(0).getCertificate();
//Create the CMS Signing Object
ExternalSignatureCMSSignedDataGenerator cmsGenerator = new ExternalSignatureCMSSignedDataGenerator();
ExternalSignatureSignerInfoGenerator signerGenerator = new ExternalSignatureSignerInfoGenerator(CMSSignedDataGenerator.DIGEST_SHA256, "1.2.840.10045.4.3.2");
signerGenerator.setCertificate(pdfSignerCertificate);
ExternalSigningSupport externalSigningSupport;
PDDocument pdDocument = Loader.loadPDF(stream);
//Create the PDFBox Signature Object
PDSignature pdSignature = new PDSignature();
pdSignature.setFilter(PDSignature.FILTER_ADOBE_PPKLITE);
pdSignature.setSubFilter(PDSignature.SUBFILTER_ETSI_CADES_DETACHED);
pdSignature.setLocation("Remote IS Blocks Signer");
pdSignature.setName("IS Blocks Signer");
pdSignature.setReason(processName);
pdDocument.setDocumentId(calendar.getTimeInMillis());
pdSignature.setSignDate(calendar);
// Optional: Certify the first time signature
// 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.
int accessPermissions = SigUtils.getMDPPermission(pdDocument);
if (pdDocument.getVersion() >= 1.5f && accessPermissions == 0 && processName.contains("Document Certifying Key"))
{
logger.debug("Certifying Document");
SigUtils.setMDPPermission(pdDocument, pdSignature, 3);
}
if(signatureImage != null) {
String data = signatureImage.getAttributes().get("data").replaceAll("data:application/pdf;base64,", "").replaceAll("data:;base64,", "").replaceAll("data:image/png;base64,", "");
int pageNumber = Integer.parseInt(signatureImage.getAttributes().get("page"));
float x = Float.parseFloat(signatureImage.getAttributes().get("x"));
float y = Float.parseFloat(signatureImage.getAttributes().get("y"));
float width = Float.parseFloat(signatureImage.getAttributes().get("width"));
float height = Float.parseFloat(signatureImage.getAttributes().get("height"));
SignatureOptions signatureOptions;
// register signature dictionary and sign interface
signatureOptions = new SignatureOptions();
PDFVisibleSignature pdfVisibleSignature = new PDFVisibleSignature();
signatureOptions.setVisualSignature(pdfVisibleSignature.createVisualSignatureTemplate(
x,
y,
width,
height,
pdDocument,
pageNumber,
pdSignature,
Base64.getDecoder().decode(data.getBytes("UTF-8"))));
signatureOptions.setPage(pageNumber);
pdDocument.addSignature(pdSignature, null, signatureOptions);
} else {
pdDocument.addSignature(pdSignature);
}
externalSigningSupport = pdDocument.saveIncrementalForExternalSigning(ostream);
//Create the message digest of the pre-signed PDF
MessageDigest digest = MessageDigest.getInstance("SHA-256");
byte[] bytes = org.apache.commons.io.IOUtils.toByteArray(externalSigningSupport.getContent());
byte[] hashBytes = digest.digest(bytes);
//CMS Signature
InputStream isBytes = new ByteArrayInputStream(bytes);
CMSProcessable input = new CMSProcessableInputStream(isBytes);
Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
MessageDigest messageDigest1 = MessageDigest.getInstance("SHA-256");
byte[] hash = messageDigest1.digest(bytes);
byte[] bytesToSign = signerGenerator.getBytesToSign(PKCSObjectIdentifiers.data, hash, new Date(),
"BC");
String encodedData = Base64.getEncoder().encodeToString(bytesToSign);
logger.debug("Bytes to Sign:" + (Base64.getEncoder().encodeToString(bytesToSign)));
logger.debug("Hash:" + Base64.getEncoder().encodeToString(hash));
//Create the signature using the keyID
//At this time only ECDSAWithSHA256 is supported
Map<String, String> signature = kmsService.sign(keyID, encodedData);
byte[] signedBytes = Base64.getDecoder().decode(signature.get("signature"));
X509Certificate[] chain;
signerGenerator.setCertificate(pdfSignerCertificate);
signerGenerator.setSignedBytes(signedBytes);
cmsGenerator.addSignerInf(signerGenerator);
cmsGenerator.addCertificatesAndCRLs(X509Utils.getCertStore(signature.get("certificateChain")));
CMSSignedData signedData = cmsGenerator.generate(new CMSProcessableByteArray(hash), false);
//Add a RFC3161 Time Stamp
ValidationTimeStamp validation = new ValidationTimeStamp("https://freetsa.org/tsr");
signedData = validation.addSignedTimeStamp(signedData);
ContentSigner nonSigner = new ContentSigner() {
#Override
public byte[] getSignature() {
return signedBytes;
}
#Override
public OutputStream getOutputStream() {
return new ByteArrayOutputStream();
}
#Override
public AlgorithmIdentifier getAlgorithmIdentifier() {
return new DefaultSignatureAlgorithmIdentifierFinder().find( "SHA256WithECDSA" );
}
};
CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
JcaSignerInfoGeneratorBuilder sigb = new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder().build());
gen.addCertificate(new X509CertificateHolder(pdfSignerCertificate.getEncoded()));
sigb.setDirectSignature( true );
gen.addSignerInfoGenerator(sigb.build(nonSigner, new X509CertificateHolder(pdfSignerCertificate.getEncoded())));
CMSTypedData msg = new CMSProcessableInputStream( new ByteArrayInputStream( "not used".getBytes() ) );
CMSSignedData signedData1 = gen.generate((CMSTypedData)msg, false);
signedData1.getEncoded();
externalSigningSupport.setSignature(signedData.getEncoded());
//documents.get(0).addHistoricEvent("Signed " + processName);
//ArrayList<String> history = documents.get(0).getHistory();
//Post Signature
String signedPDFDocument = Base64.getEncoder().encodeToString(ostream.toByteArray());
PDDocument newPdf1;
newPdf1 = Loader.loadPDF(ostream.toByteArray());
byte[] fileContent = ostream.toByteArray();
List<PDSignature> pdfSignatures;
pdfSignatures = newPdf1.getSignatureDictionaries();
byte[] signatureAsBytes;
signatureAsBytes = newPdf1.getLastSignatureDictionary().getContents( fileContent );
byte[] signedContentAsBytes;
signedContentAsBytes = newPdf1.getLastSignatureDictionary().getSignedContent( fileContent );
// Now we construct a PKCS #7 or CMS.
CMSProcessable cmsProcessableInputStream = new CMSProcessableByteArray(signedContentAsBytes);
CMSSignedData cmsSignedData;
cmsSignedData = new CMSSignedData(cmsProcessableInputStream, signatureAsBytes);
Store certificatesStore = cmsSignedData.getCertificates();
Collection<SignerInformation> signers = cmsSignedData.getSignerInfos().getSigners();
SignerInformation signerInformation = signers.iterator().next();
Collection matches = certificatesStore.getMatches(signerInformation.getSID());
X509CertificateHolder certificateHolder = (X509CertificateHolder) matches.iterator().next();
ObjectMapper mapper = new ObjectMapper();
ArrayList<String> signatures = null;
//signatures = documents.get(0).getSignatures();
for(int iCount = 0; iCount < pdfSignatures.size(); iCount++) {
PDFSignature pdfSignature = new PDFSignature(
pdfSignatures.get(iCount).getName(),
pdfSignatures.get(iCount).getLocation(),
pdfSignatures.get(iCount).getSignDate().getDisplayName(Calendar.LONG_FORMAT, java.util.Calendar.LONG, Locale.UK),
pdfSignatures.get(iCount).getReason(),
certificateHolder.getSubject().toString(),
certificateHolder.getIssuer().toString(),
Base64.getEncoder().encodeToString(certificateHolder.getEncoded()));
//signatures.add(mapper.writeValueAsString(pdfSignature));
logger.info("Signature" + mapper.writeValueAsString(pdfSignature));
}
Map<String, Object> variables = new HashMap<String, Object>();
// variables.put("history", history);
//variables.put("unsignedPDFDocument", signedPDFDocument);
//variables.put("signatures", signatures);
//variables.put("status", value)
Map<String, String> pdfDocumentProcessInstanceAttributes = pdfDocumentProcessInstance.get().getAttributes();
pdfDocumentProcessInstanceAttributes.put("PDFDocument", signedPDFDocument);
ObjectData newpdfProcessInstance = pdfDocumentProcessInstance.get();
newpdfProcessInstance.setAttributes(pdfDocumentProcessInstanceAttributes);
userDataRepository.save(newpdfProcessInstance);
newpdfProcessInstance.getHistory().add(new Date() + "Signed by:" + creator);
System.out.println(newpdfProcessInstance.getId() + " " + newpdfProcessInstance.toString());
newPdf1.close();
ostream.close();
} catch (Exception e){
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
While analyzing the file document-with signingTime.pdf you provided in a comment, I recognized an issue in it. Being aware of that issue I re-checked your original document-17 21.08.14.pdf and also recognized that issue therein, so maybe this issue causes the validation problem you're here to solve. Thus, ...
Both your example files (document-17 21.08.14.pdf and document-with signingTime.pdf) contain each actually two concatenated copies of the same, multi-revision PDF with a single signature Signature1, merely the second copy has a changed ID entry. Added to them are incremental updates with a signature Signature2.
%PDF-1.4
...
15 0 obj
<<
/FT /Sig
...
/T (Signature1)
...
>>
endobj
...
/ID [<1952AB9C134E46B58251246E985D5C15> <7F887303DDC0ED7C37AE77403E30DFB0>]
...
%%EOF
%PDF-1.4
...
15 0 obj
<<
/FT /Sig
...
/T (Signature1)
...
>>
endobj
...
/ID [<1952AB9C134E46B58251246E985D5C15> <A57CD5B87222756EC4A096125C7E8A42>]
...
%%EOF
...
35 0 obj
<<
/FT /Sig
...
/T (Signature2)
...
>>
...
%%EOF
This structure is broken, even though it does not give rise to wrong cross references (as the first copy and the second copy are identical except for the ID, the cross references and the startxref offsets point to the respective positions in the first copy). Adobe Reader signature validation can react quite sensitive to such issues.
Thus, you should remove the second copy here to get ahead.
Furthermore, as already mentioned in a comment the SignerInfo of your CMS signature container contains a 1.2.840.113549.1.9.5 signingTime signed attribute. This is forbidden for PAdES BASELINE profiles.

add custom value for signature file using pdfbox java

How i will add my custom value as like (xyz123) when adding signature.Because when i adding signature that time i am not able to add custom file for signature. the field "signature1" automatically added inside the document.
My output file screenshot attached bello:
Instead of "signature1", I want to add my custom value as (xyz123)
throws IOException {
PDSignature pdSignature = new PDSignature();
pdSignature.setFilter(PDSignature.FILTER_ADOBE_PPKLITE);
pdSignature.setSubFilter(PDSignature.SUBFILTER_ADBE_PKCS7_DETACHED);
pdSignature.setName("jvmfy");
pdSignature.setReason("Learn how to sign pdf with jvmfy.com!");
pdSignature.setLocation("location");
// the signing date, needed for valid signature
pdSignature.setSignDate(Calendar.getInstance());
// register signature dictionary and sign interface
document.addSignature(pdSignature, signature);
// write incremental (only for signing purpose)
// use saveIncremental to add signature, using plain save method may break up a
// document
document.saveIncremental(output);
}
private void signDetached(SignatureInterface signature, PDDocument document, OutputStream output)
throws IOException {
PDSignature pdSignature = new PDSignature();
pdSignature.setFilter(PDSignature.FILTER_ADOBE_PPKLITE);
pdSignature.setSubFilter(PDSignature.SUBFILTER_ADBE_PKCS7_DETACHED);
pdSignature.setSignDate(Calendar.getInstance());
PDAcroForm acroForm = document.getDocumentCatalog().getAcroForm();
if (acroForm == null)
{
acroForm = new PDAcroForm(document);
document.getDocumentCatalog().setAcroForm(acroForm);
}
PDSignatureField signatureField = new PDSignatureField(acroForm);
signatureField.setPartialName("xyz123");
signatureField.setValue(pdSignature);
signatureField.getWidgets().get(0).setPage(document.getPage(0));
document.addSignature(pdSignature, signature);
document.saveIncremental(output);
}```
You need your PDSignatureField object... when you have it, do this:
signatureField.setPartialName("xyz123");
If the code doesn't create its own PDSignatureField object (as in the example for invisible signature fields), PDFBox does it for you. You can get all PDSignatureField objects by calling PDDocument.getSignatureFields().
If you created the file yourself, then there is only one such field. If you are signing existing files, then it's more tricky, then I'd recommend that you compare the field names or the results of getCOSObject() (i.e. create two sets). Don't assume that the last one is the right one (in some cases it isn't).
Or you create the field yourself:
PDAcroForm acroForm = document.getDocumentCatalog().getAcroForm(null);
if (acroForm == null)
{
acroForm = new PDAcroForm(document);
document.getDocumentCatalog().setAcroForm(acroForm);
}
PDSignatureField signatureField = new PDSignatureField(acroForm);
signatureField.setValue(signature);
signatureField.getWidgets().get(0).setPage(document.getPage(0));
acroForm.getFields().add(signatureField);
// page annotation, only needed if PDF/A
document.getPage(0).getAnnotations().add(signatureField.getWidgets().get(0));
document.getPage(0).getCOSObject().setNeedToBeUpdated(true);

Why is my form being flattened without calling the flattenFields method?

I am testing my method with this form https://help.adobe.com/en_US/Acrobat/9.0/Samples/interactiveform_enabled.pdf
It is being called like so:
Pdf.editForm("./src/main/resources/pdfs/interactiveform_enabled.pdf", "./src/main/resources/pdfs/FILLEDOUT.pdf"));
where Pdf is just a worker class and editForm is a static method.
The editForm method looks like this:
public static int editForm(String inputPath, String outputPath) {
try {
PdfDocument pdf = new PdfDocument(new PdfReader(inputPath), new PdfWriter(outputPath));
PdfAcroForm form = PdfAcroForm.getAcroForm(pdf, true);
Map<String, PdfFormField> m = form.getFormFields();
for (String s : m.keySet()) {
if (s.equals("Name_First")) {
m.get(s).setValue("Tristan");
}
if (s.equals("BACHELORS DEGREE")) {
m.get(s).setValue("Off"); // On or Off
}
if (s.equals("Sex")) {
m.get(s).setValue("FEMALE");
}
System.out.println(s);
}
pdf.close();
logger.info("Completed");
} catch (IOException e) {
logger.error("Unable to fill form " + outputPath + "\n\t" + e);
return 1;
}
return 0;
}
Unfortunately the FILLEDOUT.pdf file is no longer a form after calling this method. Am I doing something wrong?
I was using this resource for guidance. Notice how I am not calling the form.flattenFields(). If I do call that method however, I get an error of java.lang.IllegalArgumentException.
Thank you for your time.
Your form is Reader-enabled, i.e. it contains a usage rights digital signature by a key and certificate issued by Adobe to indicate to a regular Adobe Reader that it shall activate a number of additional features when operating on that very PDF.
If you stamp the file as in your original code, the existing PDF objects will get re-arranged and slightly changed. This breaks the usage rights signature, and Adobe Reader, recognizing that, disclaims "The document has been changed since it was created and use of extended features is no longer available."
If you stamp the file in append mode, though, the changes are appended to the PDF as an incremental update. Thus, the signature still correctly signs its original byte range and Adobe Reader does not complain.
To activate append mode, use StampingProperties when you create your PdfDocument:
PdfDocument pdf = new PdfDocument(new PdfReader(inputPath), new PdfWriter(outputPath), new StampingProperties().useAppendMode());
(Tested with iText 7.1.1-SNAPSHOT and Adobe Acrobat Reader DC version 2018.009.20050)
By the way, Adobe Reader does not merely check the signature, it also tries to determine whether the changes in the incremental update don't go beyond the scope of the additional features activated by the usage rights signature.
Otherwise you could simply take a small Reader-enabled PDF and in append mode replace all existing pages by your own content of choice. This of course is not in Adobe's interest...
The filled in PDF is still an AcroForm, otherwise the example below would result in the same PDF twice.
public class Main {
public static final String SRC = "src/main/resources/interactiveform_enabled.pdf";
public static final String DEST = "results/filled_form.pdf";
public static final String DEST2 = "results/filled_form_second_time.pdf";
public static void main(String[] args) throws Exception {
File file = new File(DEST);
file.getParentFile().mkdirs();
Main main = new Main();
Map<String, String> data1 = new HashMap<>();
data1.put("Name_First", "Tristan");
data1.put("BACHELORS DEGREE", "Off");
main.fillPdf(SRC, DEST, data1, false);
Map<String, String> data2 = new HashMap<>();
data2.put("Sex", "FEMALE");
main.fillPdf(DEST, DEST2, data2, false);
}
private void fillPdf(String src, String dest, Map<String, String> data, boolean flatten) {
try {
PdfDocument pdf = new PdfDocument(new PdfReader(src), new PdfWriter(dest));
PdfAcroForm form = PdfAcroForm.getAcroForm(pdf, true);
//Delete print field from acroform because it is defined in the contentstream not in the formfields
form.removeField("Print");
Map<String, PdfFormField> m = form.getFormFields();
for (String d : data.keySet()) {
for (String s : m.keySet()) {
if(s.equals(d)){
m.get(s).setValue(data.get(d));
}
}
}
if(flatten){
form.flattenFields();
}
pdf.close();
System.out.println("Completed");
} catch (IOException e) {
System.out.println("Unable to fill form " + dest + "\n\t" + e);
}
}
}
The issue you are facing has to do with the 'reader enabled forms'.
What it boils down to is that the PDF file that is initially fed to your program is reader enabled. Hence you can open the PDF in Adobe Reader and fill in the form. This allows Acrobat users to extend the behaviour of Adobe Reader.
Once the PDF is filled in and closed using iText it saves the PDF as 'not reader-extended'.
This makes it so that the AcroForm can still be filled using iText but when you open the PDF using Adobe Reader the extended functionality you see in the original PDF is gone. But this does not mean the form is flattened.
iText cannot make a form reader enabled, as a matter of fact, the only way to create a reader enabled form is using Acrobat Professional. This is how Acrobat and Adobe Reader interact and it is not something iText can imitate or solve. You can find some more info and a possible solution on this link.
The IllegalArgumentException you get when you call the form.flattenFields() method is because of the way the PDF document was constructed.
The "Print form" button should have been defined in the AcroForm, yet it is defined in the contentstream of the PDF, meaning the button in the AcroForm has an empty text value, and this is what causes the exception.
You can fix this by removing the print field from the AcroForm before you flatten.
IllegalArgumentException issue has been fixed in iText 7.1.5.

Categories