Thursday, December 13, 2007

Getting Started With iText - How to Self-Sign a PDF

In my last entry I showed How to Sign a PDF in ColdFusion, using a java example by Paul Soares. Another of his great examples demonstrates How to self-sign a PDF.

If you would like to experiment with signing, but do not have a certificate, you can generate your own certificate for self-signing. There are also companies that offer free personal certificates, but since this entry is about self-signing I will leave you to explore that option on your own.

First I generated the keys using the java keytool command. As you can see I selected a very imaginative alias: "SelfCertTest1". After entering the password the new keys were successfully generated and added to my keystore, which was created in the current directory. I was then ready to sign my PDF.


C:\Program Files\Java\jre1.5.0_11\bin> keytool -genkey -keyalg RSA -alias SelfCertTest1 -keypass ThePrivateKeyPassword -keystore keystore.ks -dname "cn=localhost,C=US"



Requirements


Here is what you will need to run this example

- iText-2.0.7.jar available at sourceforge.net. The code may work with other jar versions but was only tested with 2.0.7

- JavaLoader.cfc available at javaloader.riaforge.org/.

- (Bouncy castle jar) bcprov-jdk14-138.jar. You may not need this jar. In my testing it was needed to resolve a runtime error. See my previous entry for a full description.


A note on circumventing the blogger


Due to an annoying blogger bug, you cannot run the code below "as is". I had to add a padding space to prevent the blogger from hiding one section of the code. Unfortunately it also breaks the working code. So before you run it, remove the extra space in the URLDecode(..) function below. Then you should be "good to go".


// cfSearching: Tip from http://www.schwabe.net/blog/1/2006/05/Using-NULL-characters-in-a-ColdFusion-string.cfm
// cfSearching: must remove the space between % and 00
// cfSearching: its used to circumvent a blogger bug that distorts the code
pdfVersion = javacast("string", URLDecode('% 00')).charAt(0);


UPDATE: The code sample below creates a new instance of the JavaLoader on each request. It is recommended that you store the javaLoader in the server scope instead to avoid memory leaks caused by a bug in ColdFusion. To find out more about this issue read Mark Mandel's article Using a Java URLClassLoader in CFMX Can Cause a Memory Leak.



Source: How to generate the keys for the self signed mode


<h1>How to Self-Sign a PDF</h1>
<cfscript>

fullPathToOriginalFile = ExpandPath("./HelloWorld.pdf");
fullPathToSignedFile = ExpandPath("./HelloWorldSelfSigned.pdf");
fullPathToKeyStore = "C:\Program Files\Java\jre1.5.0_11\bin\keystore.ks";

// cfSearching: explicitly creating java string objects
keystorePassword = javacast("string", "TheKeystorePassword");
selfSignedKeyPassword = javacast("string", "ThePrivateKeyPassword");

// cfSearching: My javaloader amd iText.jar are located. Your paths may be different
// cfSearching: C:\CFusionMX7\wwwroot\com\javaloader\JavaLoader.cfc
// cfSearching: C:\CFusionMX7\wwwroot\iText\iText-2.0.7.jar
dotNotationPathToJavaLoader = "com.javaloader.JavaLoader";
fullPathToITextJar = ExpandPath("./iText-2.0.7.jar");
fullPathToExtraJar = ExpandPath("./bcprov-jdk14-138.jar");


pathsForJavaLoader = arrayNew(1);
arrayAppend(pathsForJavaLoader, fullPathToITextJar);
arrayAppend(pathsForJavaLoader, fullPathToExtraJar);

// cfSearching: See "Using a Java URLClassLoader in CFMX Can Cause a Memory Leak"
// cfSearching: http://www.compoundtheory.com/?action=displayPost&ID=212
javaLoader = createObject('component', dotNotationPathToJavaLoader).init(pathsForJavaLoader);

// cfSearching: create the object first so we have access to the static methods
keyStore = createObject("java","java.security.KeyStore");
ks = keyStore.getInstance(KeyStore.getDefaultType());

// cfSearching: load the keystore file
inputStream = createObject("java","java.io.FileInputStream").init(fullPathToKeyStore);
ks.load( inputStream, keystorePassword.toCharArray());

alias = ks.aliases().nextElement().toString();
privateKey = ks.getKey(alias, selfSignedKeyPassword.toCharArray());
chainArray = ks.getCertificateChain(alias);

pdfReader = javaLoader.create("com.lowagie.text.pdf.PdfReader").init( fullPathToOriginalFile );
outStream = createObject("java","java.io.FileOutputStream").init( fullPathToSignedFile );
pdfStamper = javaLoader.create("com.lowagie.text.pdf.PdfStamper");
// cfSearching: must remove the space between % and 00
// cfSearching: its used to circumvent a blogger bug that distorts the code
pdfVersion = javacast("string", URLDecode('% 00')).charAt(0);
stamper = pdfStamper.createSignature(pdfReader, outStream, pdfVersion.charValue());

signatureAppearance = stamper.getSignatureAppearance();
PdfSignatureAppearance = javaLoader.create("com.lowagie.text.pdf.PdfSignatureAppearance");
signatureAppearance.setCrypto( privateKey, chainArray, javacast("null", 0),
PdfSignatureAppearance.SELF_SIGNED);
signatureAppearance.setReason("I'm the author");
signatureAppearance.setLocation("Lisbon");

// cfSearching: Commented out the next (2) two lines to have an invisible sinature
rectangle = javaLoader.create("com.lowagie.text.Rectangle").init(
javacast("float", 100),
javacast("float", 100),
javacast("float", 200),
javacast("float", 200) );
signatureAppearance.setVisibleSignature( rectangle, javacast("int", 1), javacast("null", "") );

// cfSearching: always close the stamper and output file
stamper.close();
outStream.close();

WriteOutput("Finished!");
</cfscript>

2 comments:

Anonymous,  May 14, 2009 at 8:30 AM  

trying to produce dynamic PDF form that includes a "signature" field for a user to sign and then have the form saved somewhere upon signing.

cfSearching May 15, 2009 at 6:55 AM  

@Anonymous,

Have a look at the Forms and Digital Signatures example on the iText site:
http://itext.ugent.be/articles/eid-pdf/index.php?page=3

  © Blogger templates The Professional Template by Ourblogtemplates.com 2008

Header image adapted from atomicjeep