Skip to content

Commit

Permalink
refactoring, documentation
Browse files Browse the repository at this point in the history
Issue #134
  • Loading branch information
rsoika committed Nov 30, 2020
1 parent d53919d commit 11f445d
Show file tree
Hide file tree
Showing 4 changed files with 140 additions and 97 deletions.
31 changes: 21 additions & 10 deletions imixs-archive-signature/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)

<signature name="autocreate">true</signature>
<signature name="rootsignature">false</signature>
<signature name="filepattern">order.pdf</signature>

**autocreate**

If autocreate=true than in case no certificate for the current user exists, the SignatureAdaper will create a certificate on the fly.
**filepattern**

**rootsignature**
A regular expression to filter the attachments do be signed by the plugin.

If no no certificate for the current user exists, a document will be signed with the root certificate.
<signature name="filepattern">order.pdf</signature>

**filepattern**

A regular expression to filter the attachments do be signed by the plugin. If no file pattern is set, only PDF files will be signed
If no file pattern is set, only PDF files will be signed

(^.+\\.([pP][dD][fF])$).

Expand All @@ -107,6 +101,23 @@ The following example will sign all pdf files with the sufix 'order.pdf'

You can find general details about how to use an Adapter with Imixs-Workflow [here](https://www.imixs.org/doc/core/adapter-api.html).


**rootsignature**

The document will be signed with the root certificate.

<signature name="rootsignature">true</signature>

If not set, a signature based on the current user will be generated.

**autocreate**

If autocreate=true than in case no certificate for the current user exists, the SignatureAdaper will create a certificate on the fly.

<signature name="autocreate">true</signature>




## The CA Service

Expand Down
15 changes: 10 additions & 5 deletions imixs-archive-signature/docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,16 +59,20 @@ After the successful DNS challenge you will find the chain and key files at:

/etc/letsencrypt/archive/foo.com/

**3. Import the Root Certificate**
Read the next section to import the root certificate.

### Import a Root Certificate

If you have obtained a valid certificate you can import the certificate into an existing keystore using the keytool. The following section explains the procedure.

The keytool does not allow to import multiple public and private .pem certificates directly. So first you’ll need to add all .pem files to a PKCS12 archive. This can be done with the OpenSSL tool:

$ sudo openssl pkcs12 -export \
-in /etc/letsencrypt/live/foo.com/cert.pem \
-inkey /etc/letsencrypt/live/foo.com/privkey.pem \
-in foo.com/cert.pem \
-inkey foo.com/privkey.pem \
-out foo.com.p12 \
-name foo.com \
-CAfile /etc/letsencrypt/live/foo.com/fullchain.pem \
-CAfile foo.com/fullchain.pem \
-caname "Let's Encrypt Authority X3" \
-password pass:changeit

Expand All @@ -87,6 +91,7 @@ Next you can import the certificates into your keystore:
-destkeystore imixs.jks \
-alias foo.com

Replace the password 'changeit' with password of your keystore!

To verify the content of the keystore run:

Expand All @@ -95,7 +100,7 @@ To verify the content of the keystore run:



### Create Certificate with the Root Certificate
### Createing a Certificate from a Root Certificate

If you have imported a root certificate into your keystore you can create new keypairs signed by the root CA.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@
import java.awt.geom.Rectangle2D;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.security.KeyStoreException;
Expand Down Expand Up @@ -78,12 +77,11 @@
import org.imixs.archive.signature.pdf.cert.CertificateVerificationException;
import org.imixs.archive.signature.pdf.cert.SigningException;
import org.imixs.archive.signature.pdf.util.SigUtils;
import org.imixs.workflow.FileData;

/**
* The SignatureService provides methods to sign a PDF document on a X509
* certificate. The service adds a digital signature and also a visual signature.
* The method 'signPDF' expects a Imixs FileData object containing
* certificate. The service adds a digital signature and also a visual
* signature. The method 'signPDF' expects a Imixs FileData object containing
* the content of the PDF file to be signed.
* <p>
* The service expects the existence of a valid X509 certificate stored in the
Expand Down Expand Up @@ -134,39 +132,32 @@ 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
*
*
*
*/
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;

Expand All @@ -175,36 +166,36 @@ 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");
}

// = Loader.loadPDF(inputFile)) {
// 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(
Expand All @@ -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";
}
}

Expand All @@ -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);
}

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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();
}

Expand Down Expand Up @@ -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;
Expand Down
Loading

0 comments on commit 11f445d

Please sign in to comment.