diff --git a/imixs-archive-signature/README.md b/imixs-archive-signature/README.md
index 6b2b201f..10639011 100644
--- a/imixs-archive-signature/README.md
+++ b/imixs-archive-signature/README.md
@@ -83,21 +83,15 @@ The SignatureAdapter does throw a PluginException in case not certificate for th
#### Configuration
The adapter creates a new certificate (autocreate=true) or signs the document with the root certificate if no user certificate exists (rootsignature=true)
-
* The service expects the existence of a valid X509 certificate stored in the @@ -134,13 +132,12 @@ public class SigningService { keytool -storepass 123456 -storetype PKCS12 -keystore file.p12 -genkey -alias client -keyalg RSA } * - * @param documentFile - File to be signed - * @param alias - the alias used to sign the document. The alias should - * be listed in the keystore. - * - * - * @param signatureImage - image of visible signature - * @return signed FileData object + * @param inputFileData A byte array containing the source PDF document. + * @param certAlias Certificate alias name to be used for signing + * @param certPassword optional private key password + * @param externalSigning optional boolean flag to trigger an external signing + * process + * @return A byte array containing the singed PDF document * * @throws SigningException * @throws CertificateVerificationException @@ -148,25 +145,19 @@ public class SigningService { * * */ - public FileData signPDF(FileData inputFileData, String certAlias, String certPassword, File signatureImage) + public byte[] signPDF(byte[] inputFileData, String certAlias, String certPassword, boolean externalSigning) throws CertificateVerificationException, SigningException { - logger.info("......signPDF '" + inputFileData.getName() + "' ..."); - - String name = inputFileData.getName(); - String substring = name.substring(0, name.lastIndexOf('.')); - String signedFileName = substring + "_signed.pdf"; - // Set the signature rectangle // Although PDF coordinates start from the bottom, humans start from the top. // So a human would want to position a signature (x,y) units from the // top left of the displayed page, and the field has a horizontal width and a // vertical height // regardless of page rotation. - Rectangle2D humanRect = new Rectangle2D.Float(50, 660, 170, 50); + // Rectangle2D humanRect = new Rectangle2D.Float(50, 660, 170, 50); - FileData signedFileData = createSignedPDF(inputFileData, signedFileName, certAlias, certPassword, humanRect, - "Signature1", signatureImage, false); + byte[] signedFileData = signPDF(inputFileData, certAlias, certPassword, externalSigning, null, "Signature1", + null); return signedFileData; @@ -175,27 +166,28 @@ public FileData signPDF(FileData inputFileData, String certAlias, String certPas /** * Sign pdf file and create new file that ends with "_signed.pdf". * - * @param inputFile The source pdf document file. - * @param signedFile The file to be signed. - * @param alias Certificate alias name to be used for signing + * @param inputFileData A byte array containing the source PDF document. + * @param certAlias Certificate alias name to be used for signing + * @param certPassword optional private key password + * @param externalSigning optional boolean flag to trigger an external + * signing process * @param humanRect rectangle from a human viewpoint (coordinates start * at top left) * @param tsaUrl optional TSA url * @param signatureFieldName optional name of an existing (unsigned) signature * field - * @param externalSigning optional boolean flag to trigger an external - * signing process + * @return A byte array containing the singed PDF document * @throws CertificateVerificationException * @throws SigningException */ - private FileData createSignedPDF(FileData inputFileData, String signedFileName, String certAlias, - String certPassword, Rectangle2D humanRect, String signatureFieldName, File imageFile, - boolean externalSigning) throws CertificateVerificationException, SigningException { + public byte[] signPDF(byte[] inputFileData, String certAlias, String certPassword, boolean externalSigning, + Rectangle2D humanRect, String signatureFieldName, byte[] imageFile) + throws CertificateVerificationException, SigningException { SignatureOptions signatureOptions = null; byte[] signedContent = null; - if (inputFileData == null || inputFileData.getContent().length == 0) { + if (inputFileData == null || inputFileData.length == 0) { throw new SigningException("empty file data"); } @@ -203,8 +195,7 @@ private FileData createSignedPDF(FileData inputFileData, String signedFileName, // ByteArrayOutputStream // try (FileOutputStream fos = new FileOutputStream(signedFile); PDDocument doc // = PDDocument.load(inputFileData.getContent())) { - try (ByteArrayOutputStream bos = new ByteArrayOutputStream(); - PDDocument doc = PDDocument.load(inputFileData.getContent())) { + try (ByteArrayOutputStream bos = new ByteArrayOutputStream(); PDDocument doc = PDDocument.load(inputFileData)) { int accessPermissions = SigUtils.getMDPPermission(doc); if (accessPermissions == 1) { throw new SigningException( @@ -220,12 +211,18 @@ private FileData createSignedPDF(FileData inputFileData, String signedFileName, PDAcroForm acroForm = doc.getDocumentCatalog().getAcroForm(); PDRectangle rect = null; - // sign a PDF with an existing empty signature, as created by the - // CreateEmptySignatureForm example. + // If the PDF contains an existing empty signature, as created by the + // CreateEmptySignatureForm example we can reuse it here if (acroForm != null) { - pdSignature = findExistingSignature(acroForm, signatureFieldName); - if (pdSignature != null) { - rect = acroForm.getField(signatureFieldName).getWidgets().get(0).getRectangle(); + try { + pdSignature = findExistingSignature(acroForm, signatureFieldName); + if (pdSignature != null) { + rect = acroForm.getField(signatureFieldName).getWidgets().get(0).getRectangle(); + } + } catch (IllegalStateException ise) { + // we can not use this signature field + logger.warning("signature " + signatureFieldName + " already exists: " + ise.getMessage()); + signatureFieldName = signatureFieldName + ".1"; } } @@ -234,7 +231,7 @@ private FileData createSignedPDF(FileData inputFileData, String signedFileName, pdSignature = new PDSignature(); } - if (rect == null) { + if (rect == null && humanRect != null) { rect = createSignatureRectangle(doc, humanRect); } @@ -310,12 +307,12 @@ private FileData createSignedPDF(FileData inputFileData, String signedFileName, // register signature dictionary and sign interface signatureOptions = new SignatureOptions(); - // create visual signature if imageFile exists.... - if (imageFile.exists()) { + // create visual signature if a signing rect object exists.... + if (rect != null) { signatureOptions.setVisualSignature( createVisualSignatureTemplate(doc, 0, rect, pdSignature, imageFile, certificateChain)); } else { - logger.warning("Signature Image '" + imageFile.getPath() + "' does not exist. No Image will be added!"); + logger.info("...Signature Image not provided, no VisualSignature will be added!"); } signatureOptions.setPage(0); doc.addSignature(pdSignature, signature, signatureOptions); @@ -348,9 +345,8 @@ private FileData createSignedPDF(FileData inputFileData, String signedFileName, IOUtils.closeQuietly(signatureOptions); } - // return the new singed FileData object - FileData signedFileData = new FileData(signedFileName, signedContent, "application/pdf", null); - return signedFileData; + // return the new singed content + return signedContent; } private PDRectangle createSignatureRectangle(PDDocument doc, Rectangle2D humanRect) { @@ -395,7 +391,7 @@ private PDRectangle createSignatureRectangle(PDDocument doc, Rectangle2D humanRe // create a template PDF document with empty signature and return it as a // stream. private InputStream createVisualSignatureTemplate(PDDocument srcDoc, int pageNum, PDRectangle rect, - PDSignature signature, File imageFile, Certificate[] certificateChain) throws IOException { + PDSignature signature, byte[] imageFile, Certificate[] certificateChain) throws IOException { try (PDDocument doc = new PDDocument()) { PDPage page = new PDPage(srcDoc.getPage(pageNum).getMediaBox()); doc.addPage(page); @@ -468,8 +464,13 @@ private InputStream createVisualSignatureTemplate(PDDocument srcDoc, int pageNum // save and restore graphics if the image is too large and needs to be scaled cs.saveGraphicsState(); cs.transform(Matrix.getScaleInstance(0.25f, 0.25f)); - PDImageXObject img = PDImageXObject.createFromFileByExtension(imageFile, doc); + // PDImageXObject img = PDImageXObject.createFromFileByExtension(imageFile, + // doc); + // create image form image byte array + // if (imageFile != null) { + PDImageXObject img = PDImageXObject.createFromByteArray(doc, imageFile, null); cs.drawImage(img, 0, 0); + // } cs.restoreGraphicsState(); } @@ -511,8 +512,17 @@ private InputStream createVisualSignatureTemplate(PDDocument srcDoc, int pageNum } } - // Find an existing signature (assumed to be empty). You will usually not need - // this. + /** + * This method verifies if for a given sigFieldName a signature already exists. + * If so, the method throws a IllegalStateException. In that case, the + * signatureField can not be used for another signature and a new empty + * signatureField have to be created. + * + * @see singPDF + * @param acroForm + * @param sigFieldName + * @return a PDSignature if exits + */ private PDSignature findExistingSignature(PDAcroForm acroForm, String sigFieldName) { PDSignature signature = null; PDSignatureField signatureField; diff --git a/imixs-archive-signature/src/main/java/org/imixs/archive/signature/workflow/SignatureAdapter.java b/imixs-archive-signature/src/main/java/org/imixs/archive/signature/workflow/SignatureAdapter.java index db7b0080..b59dd7db 100644 --- a/imixs-archive-signature/src/main/java/org/imixs/archive/signature/workflow/SignatureAdapter.java +++ b/imixs-archive-signature/src/main/java/org/imixs/archive/signature/workflow/SignatureAdapter.java @@ -1,6 +1,6 @@ package org.imixs.archive.signature.workflow; -import java.io.File; +import java.awt.geom.Rectangle2D; import java.io.IOException; import java.security.InvalidKeyException; import java.security.KeyStoreException; @@ -75,7 +75,7 @@ public class SignatureAdapter implements SignalAdapter { @Inject WorkflowService workflowService; - + @Inject X509ProfileHandler x509ProfileHandler; @@ -118,37 +118,46 @@ public ItemCollection execute(ItemCollection document, ItemCollection event) thr if (filePatternMatcher.matcher(fileName).find()) { // yes! start signing.... - // compute alias validate existence of certificate - String certAlias = workflowService.getUserName(); - logger.info("......signing " + fileName + " by '" + certAlias + "'..."); - // we assume an empty password for certificate String certPassword = ""; - - // test if a certificate exits.... - if (!caService.existsCertificate(certAlias)) { - if (autocreate) { - // lookup the x509 data form the x509ProfileHandler - ItemCollection x509Profile= x509ProfileHandler.findX509Profile(certAlias); - // create new certificate.... - caService.createCertificate(certAlias,x509Profile); - } else { - // try to fetch the root certificate - if (rootsignature && rootCertAlias.isPresent()) { - certAlias = rootCertAlias.get(); - // set SIGNATURE_ROOTCERT_PASSWORD - if (rootCertPassword.isPresent()) { - certPassword = rootCertPassword.get(); - } - } else { - throw new CertificateVerificationException("certificate for alias '" + certAlias - + "' not found. Missing default certificate alias (SIGNATURE_KEYSTORE_DEFAULT_ALIAS)!"); - } + String certAlias = null; + + // Test if the a signature with the root certificate is requested + if (rootsignature && rootCertAlias.isPresent()) { + certAlias = rootCertAlias.get(); + // set SIGNATURE_ROOTCERT_PASSWORD + if (rootCertPassword.isPresent()) { + certPassword = rootCertPassword.get(); } + // test existence of default certificate if (!caService.existsCertificate(certAlias)) { throw new ProcessingErrorException(this.getClass().getSimpleName(), "SIGNING_ERROR", - "No certificate exists for user '" + certAlias + "'"); + "Root certificate '" + certAlias + "' does not exist!"); + } + logger.info("......signing " + fileName + " with root certificate '" + certAlias + "'..."); + } else { + // signature with user certificate.... + // compute alias validate existence of certificate + certAlias = workflowService.getUserName(); + logger.info("......signing " + fileName + " by '" + certAlias + "'..."); + + // test if a certificate exits.... + if (!caService.existsCertificate(certAlias)) { + if (autocreate) { + // lookup the x509 data form the x509ProfileHandler + ItemCollection x509Profile = x509ProfileHandler.findX509Profile(certAlias); + // create new certificate.... + caService.createCertificate(certAlias, x509Profile); + } else { + throw new CertificateVerificationException( + "certificate for alias '" + certAlias + "' not found."); + } + // test existence of default certificate + if (!caService.existsCertificate(certAlias)) { + throw new ProcessingErrorException(this.getClass().getSimpleName(), "SIGNING_ERROR", + "No certificate exists for user '" + certAlias + "'"); + } } } @@ -162,16 +171,24 @@ public ItemCollection execute(ItemCollection document, ItemCollection event) thr sourceContent = fileData.getContent(); } - // Path path = Paths.get(fileName); - // Files.write(path, sourceContent); - // File filePDFSource = new File(fileName); - File fileSignatureImage = new File("/opt/imixs-keystore/" + certAlias + ".jpg"); - FileData signedFileData = signatureService.signPDF(fileData, certAlias, certPassword, - fileSignatureImage); + byte[] signedContent=null; + // in case of a rootsignature we do not generate a signature visual! + if (rootsignature) { + signedContent = signatureService.signPDF(sourceContent, certAlias, certPassword,false); + } else { + Rectangle2D humanRect = new Rectangle2D.Float(50, 660, 170, 50); + + // create signature withvisual + //File fileSignatureImage = new File("/opt/imixs-keystore/" + certAlias + ".jpg"); + signedContent = signatureService.signPDF(sourceContent, certAlias, certPassword,false,humanRect,"Signature1", + null); + } // ad the signed pdf file to the workitem + FileData signedFileData = new FileData(fileName, signedContent,"application/pdf", null); + document.addFileData(signedFileData); - logger.info("......signing " + fileName + " completed!"); + logger.info("......signing " + fileName + " completed!"); } } }