Document Signing Service With Grails and iText

I’ve posted examples of document signing using iText and Grails previously. Here’s the latest iteration of the service and methods that we’re using in our production digital signature process. It works in conjunction with the KeyStoreService posted in the previous article.

This code is basically just a refactoring of the examples provided in iText in Action, 2nd Edition, Chapter 12, with a few accomodations for Groovy, Grails, and a SafeNet Luna HSM. If you are serious about using iText for digital signatures, I would definitely recommend that you buy the book.

package com.geekcredential.document

import com.geekcredential.common.output.PdfUtil
import com.geekcredential.crypto.KeyStoreService
import com.itextpdf.text.DocumentException
import com.itextpdf.text.pdf.AcroFields
import com.itextpdf.text.pdf.OcspClientBouncyCastle
import com.itextpdf.text.pdf.PdfDate
import com.itextpdf.text.pdf.PdfDictionary
import com.itextpdf.text.pdf.PdfName
import com.itextpdf.text.pdf.PdfPKCS7
import com.itextpdf.text.pdf.PdfReader
import com.itextpdf.text.pdf.PdfSignature
import com.itextpdf.text.pdf.PdfSignatureAppearance
import com.itextpdf.text.pdf.PdfStamper
import com.itextpdf.text.pdf.PdfString
import com.itextpdf.text.pdf.PdfWriter
import com.itextpdf.text.pdf.TextField
import com.itextpdf.text.pdf.TSAClient
import com.itextpdf.text.pdf.TSAClientBouncyCastle
import java.security.MessageDigest

class DocumentService {

    boolean transactional = false

    KeyStoreService keyStoreService

    private static final List<Integer> PDF_PERMISSIONS = [
            PdfWriter.ALLOW_PRINTING,
            PdfWriter.ALLOW_SCREENREADERS
    ]

    def encryptFile(File file) {
        PdfUtil.encryptFile(file, PDF_PERMISSIONS, keyStoreService.pdfOwnerPassword)
    }

    def encryptFile(byte[] content) {
        PdfUtil.encrypt(content, PDF_PERMISSIONS, keyStoreService.pdfOwnerPassword)
    }

    def applySignatures(String documentPath, String signedDocumentPath, Map signatureMap,
                        String reason, String location) {
        PdfReader reader = new PdfReader(documentPath, keyStoreService.pdfOwnerPassword.bytes)
        File signed = new File(signedDocumentPath)
        FileOutputStream fout = new FileOutputStream(signed)
        // Groovy thinks '\0' is a GString, so we have to be explicit and force it to char.
        PdfStamper stamper = PdfStamper.createSignature(reader, fout, '\0'.toCharacter().charValue(), null, true)

        // Write the 'signatures' entered by applicants to the appropriate fields in the document.
        // These aren't digital signature fields, they're just text fields.
        applyNames(stamper, signatureMap)

        // Apply digital signature
        Map credentials = keyStoreService.credentials
        PdfSignatureAppearance appearance = setAppearance(stamper, credentials, reason, location)
        setCryptoDictionary(appearance)
        // Estimate how much room the signed content will take up and reserve some space for it.
        int contentEstimated = 15000
        preClose(appearance, contentEstimated)
        byte[] encodedSig = generateDigitalSignature(appearance, credentials)
        if (contentEstimated + 2 < encodedSig.length)
            throw new DocumentException("Document signature failed: Not enough space reserved for content in the signed file")
        insertSignedContent(contentEstimated, encodedSig, appearance)
    }

    private def applyNames(PdfStamper stamper, Map signatureMap) {
        AcroFields form = stamper.getAcroFields()
        /*
        Understand form field caching: Setting up a cache for field appearances can improve iText's performance when
        filling PDFs that have a lot of fields.
        (See http://api.itextpdf.com/itext/com/itextpdf/text/pdf/AcroFields.html#setFieldCache(java.util.Map))
        */
        form.setFieldCache(new HashMap<String, TextField>())
        signatureMap.each { String field, String signature ->
            form.setField(field, signature)
        }
        // 'Flattening' eliminates the form fields and merges the contents directly into the rest of the PDF stream.
        stamper.setFormFlattening(true)
        stamper.setFullCompression()
    }

    private PdfSignatureAppearance setAppearance(
            PdfStamper stamper,
            Map credentials,
            String reason,
            String location
    ) {
        PdfSignatureAppearance appearance = stamper.getSignatureAppearance()
        appearance.setCrypto(credentials.privateKey, credentials.certificateChain, null, PdfSignatureAppearance.WINCER_SIGNED)
        appearance.setReason(reason)
        appearance.setLocation(location)
        appearance.setContact(keyStoreService.contactEmail)
        appearance.setCertificationLevel(PdfSignatureAppearance.CERTIFIED_NO_CHANGES_ALLOWED)
        return appearance
    }

    TSAClient getTimeStampAuthorityClient() {
        TSAClient tsc = new TSAClientBouncyCastle(keyStoreService.timestampUrl)
        return tsc
    }

    private byte[] getOcsp() {
        Map credentials = keyStoreService.credentials
        OcspClientBouncyCastle ocspClient = new OcspClientBouncyCastle(
                credentials.certificateChain[0],
                credentials.certificateChain[credentials.certificateChain.length - 1],
                keyStoreService.ocspUrl)
        int attempts = 0
        byte[] result = null
        // OCSP is optional, and a failure to get a response from the certificate status service should not halt the
        // signature from proceeding. Nevertheless, we give our best effort. If we see these failure messages coming
        // up in the logs frequently, then we need to complain to our service provider about their uptime.
        while (!result && attempts < 3) {
            try {
                attempts++
                result = ocspClient.getEncoded()
            } catch (Throwable t) {
                log.warn("The OCSP service at ${keyStoreService.ocspUrl} could not be contacted. Attempt #${attempts}", t)
            }
        }
        if (!result) {
            log.warn("The OCSP service at ${keyStoreService.ocspUrl} could not be contacted after 3 attempts. Document signing will proceed without confirming that our certificate is still valid")
        }
        return result
    }

    private def setCryptoDictionary(PdfSignatureAppearance appearance) {
        PdfSignature dic = new PdfSignature(PdfName.ADOBE_PPKLITE, new PdfName("adbe.pkcs7.detached"))
        dic.setReason(appearance.reason)
        dic.setLocation(appearance.location)
        dic.setContact(appearance.contact)
        dic.setDate(new PdfDate(appearance.getSignDate()))
        appearance.setCryptoDictionary(dic)
    }

    private byte[] getDigest(PdfSignatureAppearance appearance) {
        InputStream data = appearance.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()
        return hash
    }

    private void preClose(PdfSignatureAppearance appearance, int contentEstimated) {
        HashMap<PdfName, Integer> exc = new HashMap<PdfName, Integer>()
        exc.put(PdfName.CONTENTS, new Integer(contentEstimated * 2 + 2))
        appearance.preClose(exc)
    }

    private byte[] generateDigitalSignature(
            PdfSignatureAppearance appearance,
            Map credentials
    ) {
        byte[] hash = getDigest(appearance)
        if (!hash) log.error("Digital signature failure: digest could not be obtained from the PDF")
        Calendar cal = Calendar.getInstance()
        // Check certificate revocation status using OCSP
        byte[] ocsp = getOcsp()
        // Create the signature
        PdfPKCS7 sgn = new PdfPKCS7(credentials.privateKey, credentials.certificateChain, null, "SHA1", null, false)
        if (!sgn) log.error("Digital signature failure: unable to obtain credentials from hardware storage")
        byte[] sh = sgn.getAuthenticatedAttributeBytes(hash, cal, ocsp)
        if (!sh) log.error("Digital signature failure: could not generate authenticated attribute bytes")
        sgn.update(sh, 0, sh.length)
        TSAClient tsc = getTimeStampAuthorityClient()
        return sgn.getEncodedPKCS7(hash, cal, tsc, ocsp)
    }

    private void insertSignedContent(int contentEstimated, byte[] encodedSig, PdfSignatureAppearance appearance) {
        byte[] paddedSig = new byte[contentEstimated]
        System.arraycopy(encodedSig, 0, paddedSig, 0, encodedSig.length)
        PdfDictionary signedDictionary = new PdfDictionary()
        signedDictionary.put(PdfName.CONTENTS, new PdfString(paddedSig).setHexWriting(true))
        appearance.close(signedDictionary)
    }

}
package com.geekcredential.common.output

import com.itextpdf.text.pdf.PdfEncryptor
import com.itextpdf.text.pdf.PdfReader
import com.itextpdf.text.pdf.PdfWriter

class PdfUtil {

    static int sumPermissions(List<Integer> permissions){
      // Get bitwise sum of permissions
      int sum = (Integer) permissions[0]
      permissions.each {p ->
          sum = sum | p
      }
      return sum
    }

    /**
     * Encrypts a PDF and then returns it as a byte array for writing to http response.
     * @param byte[] content (PDF contents)
     * @param int[] permissions (an array of permission constants (see PdfWriter))
     * @param ownerPassword password to be applied
     */
    static byte[] encrypt(byte[] content, List<Integer> permissions, String ownerPassword) {
        ByteArrayOutputStream bout = new ByteArrayOutputStream()
        /* The userPassword and the ownerPassword can be null or have
           zero length. In this case the ownerPassword is replaced by
           a random string. */
        PdfEncryptor.encrypt(new PdfReader(content),
                             bout,
                             PdfWriter.STANDARD_ENCRYPTION_128,
                             null,
                             ownerPassword,
                             sumPermissions(permissions))
        return bout.toByteArray()
    }

    /**
     * Encrypts a PDF and then saves it.
     * @param File file
     * @param int[] permissions (an array of permission constants (see PdfWriter))
     * @param ownerPassword password to be applied
     */
    static void encryptFile(File file, List<Integer> permissions, String ownerPassword) {
        file.setBytes(encrypt(file.readBytes(), permissions, ownerPassword))
    }

}
Advertisements

#digital-signature, #electronic-signature, #grails, #hsm, #itext, #pdf

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

Signing a PDF with iText and a Luna HSM

After obtaining a trial of a Certificate for Adobe CDS, which is only delivered on a hardware storage module (HSM) or a USB key, I had some difficulties adapting the iText examples for signing a PDF to work with the API for the HSM. The code below is the result of trial-and-error. It is based on the examples provided for iText at http://itextpdf.com/book/examples.php#chapter12.

I have been told that there is a naming convention for certificates on the HSM that, if followed, makes the KeyStore.getCertificateChain() method usable: The labels for each certificate should all start the same way (in this case, it would be ‘mycert’) and followed by the suffix ‘–cert#’, where # is the number of the certificate in the chain. In this instance, it would be something like this:

  • mycert–cert0 (Adobe CDS certificate)
  • mycert–cert1 (GlobalSign CA for Adobe certificate)
  • mycert–cert2 (Adobe Root CA certificate)

If it works, that should make it unnecessary to create the certificate chan manually, as is done in this example.

This example uses Groovy, but it should translate almost directly to Java.


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 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()
sap.setCrypto(cert.key, cert.chain, null, PdfSignatureAppearance.WINCER_SIGNED)
// 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)
sap.setReason(reason)
sap.setLocation(location)
sap.setContact(contact)
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)
stp.close()

tm.Logout()

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

#adobe-cds, #groovy, #hsm, #itext, #luna, #pdf

Split a PDF into pages with PdfStamper (iText)

More help from the iText Questions List:

Finding signature fields in a split page

#groovy, #itext, #pdf

Some questions for the iText community

ArrayIndexOutOfBoundsException when signing Documaker PDF

Certificate vendors for digital signatures

#certificate, #digital-signature, #itext, #pdf

How to sign a PDF using iText and Groovy

Paulo Soares has posted some very good examples of how to sign a PDF using iText. (iText is an open source library for working with PDFs.) I ran into a few hiccups trying to implement his example in Groovy, so I thought I’d share my fixes.

Here is a class with a method for signing PDFs. It includes the imports! This example presumes that the PDF is already encrypted to require a password for editing and that a signature field has already been placed in the PDF.

package com.geekcredential.common.output

import java.security.KeyStore
import java.security.PrivateKey
import java.security.cert.Certificate
import java.security.MessageDigest
import com.itextpdf.text.pdf.PdfReader
import com.itextpdf.text.pdf.PdfStamper
import com.itextpdf.text.pdf.PdfSignatureAppearance
import com.itextpdf.text.pdf.PdfSigGenericPKCS
import com.itextpdf.text.pdf.PdfLiteral
import com.itextpdf.text.pdf.PdfPKCS7
import com.itextpdf.text.pdf.PdfDictionary
import com.itextpdf.text.pdf.PdfString
import com.itextpdf.text.pdf.PdfName
import org.apache.log4j.Logger

/**
 * Utilities for working with PDF files.
 */

public class PdfUtil {

    /**
     * @param String keyFile (filepath to PKCS12 certificate)
     * @param String keyfilePassword
     * @param String inFile (filepath to PDF to be signed)
     * @param String ownerPassword (password for changing encrypted PDF)
     * @param String reason (text of Reason field in signature)
     * @param String location (text of Location field in signature)
     * @param String sigFieldName (name of signature field in PDF)
     * @param String outFile (desired name of signed file)
     */
    static String sign(
        String keyFile,
        String keyFilePassword,
        String inFile,
        String ownerPassword,
        String reason,
        String location,
        String sigFieldName,
        String outFile
    ) {
        Logger log = Logger.getLogger("com.geekcredential.common.output.PdfUtil")
        log.debug("Entering sign()")
        try {
            // Load the certificate to be used for signing
            KeyStore ks = KeyStore.getInstance("pkcs12")
            ks.load(new FileInputStream(keyFile), keyFilePassword.toCharArray())
            // Defaults here to first alias found in keyfile. We could change this
            // to specify a named alias.
            String alias = (String)ks.aliases().nextElement()
            PrivateKey key = (PrivateKey)ks.getKey(alias, keyFilePassword.toCharArray())
            Certificate[] chain = ks.getCertificateChain(alias)

            PdfReader reader = new PdfReader(inFile, ownerPassword.getBytes())
            FileOutputStream fout = new FileOutputStream(outFile)
            // Third param is PDF revision (char). Fifth param (boolean) enables append without
            // incrementing revision number.
            // Groovy thinks '\0' is a GString, so we have to be explicit and force it to char.
            PdfStamper stp = PdfStamper.createSignature(reader, fout, '\0'.toCharacter().charValue(), null, true)
            PdfSignatureAppearance sap = stp.getSignatureAppearance()
            sap.setCrypto(key, chain, null, PdfSignatureAppearance.SELF_SIGNED)
            sap.setReason(reason)
            sap.setLocation(location)
            sap.setVisibleSignature(sigFieldName)
            stp.close()
            return outFile
        } catch (Throwable t) {
            log.fatal("Failure signing PDF", t)
            throw t
        } finally {
            log.debug("Exiting sign()")
        }
    }
}

Note: iText supports three certificate signing modes: SELF_SIGNED, WINCER_SIGNED and VERISIGN_SIGNED. I could find no trace of the Verisign Adobe Reader plugin that is supposed to require VERISIGN_SIGNED on Verisign’s website, so I presume that this is old technology, discontinued. However, our Verisign test certificate works just fine with WINCER_SIGNED, and as Paulo Soares points out, WINCER_SIGNED mode works with any certificate. So, in practice, it seems that there are really only two modes: SELF_SIGNED or WINCER_SIGNED.

#digital-signature, #groovy, #itext, #pdf