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 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)
            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