Signature With Timestamp Using iText and Luna HSM


import java.security.KeyStore
import java.security.MessageDigest
import java.security.PrivateKey
import java.security.cert.Certificate
import com.itextpdf.text.pdf.PdfReader
import com.itextpdf.text.pdf.PdfStamper
import com.itextpdf.text.pdf.PdfSignatureAppearance
import com.itextpdf.text.pdf.PdfName
import com.itextpdf.text.Image
import com.itextpdf.text.Rectangle
import com.itextpdf.text.pdf.PdfEncryptor
import com.itextpdf.text.pdf.PdfWriter
import com.itextpdf.text.pdf.PdfSignature
import com.itextpdf.text.pdf.PdfDate
import com.itextpdf.text.pdf.TSAClient
import com.itextpdf.text.pdf.TSAClientBouncyCastle
import com.itextpdf.text.pdf.PdfPKCS7
import com.itextpdf.text.DocumentException
import com.itextpdf.text.pdf.PdfDictionary
import com.itextpdf.text.pdf.PdfString
import com.chrysalisits.crypto.LunaTokenManager

…

String hsmPartitionLabel = "Luna_partition_name"
String hsmPassword = "partition_password"
String hsmKeyLabel = "private_key_alias"
String hsmCertLabel = "certificate_alias"
String hsmCALabel = "CA_certificate_alias"
String timestampUrl = "URL_of_timestamp_server"
String ownerPassword = "PDF_owner_password"
String inFile = "path_to_unsigned_PDF"
String reason = "signature_reason"
String location = "signature_location"
String contact = "signature_contact_email"
String sealPath = "path_to_an_image_to_be_used_with_signature"
String outFile = "path_to_put_signed_PDF"

// Login to HSM
LunaTokenManager tm = LunaTokenManager.getInstance()
tm.Login(hsmPartitionLabel, hsmPassword)

// Dynamically load security providers
Class providerClass = Class.forName("com.chrysalisits.crypto.LunaJCAProvider")
java.security.Provider provider = (java.security.Provider)providerClass.newInstance()
java.security.Security.removeProvider(provider.getName())
java.security.Security.insertProviderAt(provider, 2)
providerClass = Class.forName("com.chrysalisits.cryptox.LunaJCEProvider")
provider = (java.security.Provider) providerClass.newInstance()
java.security.Security.removeProvider(provider.getName())
java.security.Security.insertProviderAt(provider, 3)

// This syntax gets an instance of a LunaKeystore
KeyStore ks = KeyStore.getInstance("Luna")
ks.load(null, null)
PrivateKey key = (PrivateKey) ks.getKey(hsmKeyLabel, null)
// We need to assemble the certificate chain manually because the HSM doesn't support the
// getCertificateChain method.
Certificate[] chain = new Certificate[2]
chain[0] = ks.getCertificate(hsmCertLabel)
chain[1] = ks.getCertificate(hsmCALabel)

// It seems necessary to load the file into the PdfReader this way to
// avoid a java.io.IOException in sun.nio.ch.FileChannelImpl on AIX.
byte[] content = new File(inFile).readBytes()
PdfReader reader = new PdfReader(content, ownerPassword.getBytes())
FileOutputStream fout = new FileOutputStream(outFile)
// Third param is PDF revision (char).
// Groovy thinks '' is a GString, so we have to be explicit and force it to char.
PdfStamper stp = PdfStamper.createSignature(reader, fout, ''.toCharacter().charValue(), null, true)
PdfSignatureAppearance sap = stp.getSignatureAppearance()
// Instead of reason and location, a graphic image will be rendered. Reason and
// location will still be shown in the signature properties.
sap.setRenderingMode(PdfSignatureAppearance.RenderingMode.GRAPHIC)
Image image = Image.getInstance(sealPath)
sap.setSignatureGraphic(image)
// sap.setVisibleSignature(new Rectangle(x-len, y-len, x-loc, y-loc), page, null for new fieldname)
// Coordinates begin from lower left. Units are 1/72 of an inch. 8.5 x 11 in == 612 x 792
sap.setVisibleSignature(new Rectangle(36, 36, 100, 100), 1, null)
sap.setCrypto(key, chain, null, PdfSignatureAppearance.WINCER_SIGNED)

PdfSignature dic = new PdfSignature(PdfName.ADOBE_PPKLITE, new PdfName("adbe.pkcs7.detached"))
dic.setReason(reason)
dic.setLocation(location)
dic.setContact(contact)
dic.setDate(new PdfDate(sap.getSignDate()))
sap.setCryptoDictionary(dic)

// This is estimated space for the signature itself.
int contentEstimated = 15000
HashMap exc = new HashMap()
exc.put(PdfName.CONTENTS, new Integer(contentEstimated * 2 + 2))
sap.preClose(exc)

// make the digest
InputStream data = sap.getRangeStream()
MessageDigest messageDigest = MessageDigest.getInstance("SHA1")
byte[] buf = new byte[8192]
int n
while ((n = data.read(buf)) > 0) {
messageDigest.update(buf, 0, n)
}
byte[] hash = messageDigest.digest()
Calendar cal = Calendar.getInstance()

// If we add a time stamp:
TSAClient tsc = null
String tsa_url = timestampUrl
// Our provider does not use userid and password; use
// TSAClientBouncyCastle(tsa_url, tsa_userid, tsa_password)
// if yours does.
tsc = new TSAClientBouncyCastle(tsa_url)

byte[] ocsp = null

// Create the signature
PdfPKCS7 sgn = new PdfPKCS7(cert.key, cert.chain, null, "SHA1", null, false)
byte[] sh = sgn.getAuthenticatedAttributeBytes(hash, cal, ocsp)
sgn.update(sh, 0, sh.length)
byte[] encodedSig = sgn.getEncodedPKCS7(hash, cal, tsc, ocsp)

if (contentEstimated + 2 < encodedSig.length)
throw new DocumentException("Not enough space")

byte[] paddedSig = new byte[contentEstimated]
System.arraycopy(encodedSig, 0, paddedSig, 0, encodedSig.length)
// Replace the contents
PdfDictionary dic2 = new PdfDictionary()
dic2.put(PdfName.CONTENTS, new PdfString(paddedSig).setHexWriting(true))
sap.close(dic2)

tm.Logout()

println "Signed PDF saved as ${outFile}."

#adobe-cds, #digital-signature, #groovy, #hsm, #itext, #luna, #pdf, #timestamp