PDFOne (for Java)
Create, edit, view, print & enhance PDF documents and forms in Java SE/EE
Compatibility
J2SE J2EE Windows Linux Mac (OS X)

How To Digitally Sign A PDF Document Using USB token In Java

Using PDFOne (for Java).
By Santhanam L

In PDFOne (for Java) Version 5.5, we introduced support for digital signing using USB token. In this article, you will see how to sign PDF documents using the certificate installed in the USB token.

A USB token is a password-protected physical device used to store digital signature certificates. To sign PDF documents using an USB Token, you need a digital signature certificate that is installed on an USB token. USB token based certificates are an implementation of PKCS#11, one of the Public-Key Cryptography Standards. Digital signature certificates are issued by a Certificate Authority (CA). To access the connected USB token using Java you need the device driver library path of the token. For instance, when SafeNet USB token device is installed on Windows, the USB token’s library file "eTPKCS11.dll" is installed at the directory "C:\WINDOWS\system32\". In order to load the certificates installed on the USB token into the Java KeyStore object, you need a valid password/PIN to access the USB token.

Signing a PDF document using USB token

You can load an existing document and sign straightaway using PdfDocument.addSignature() method. PDFOne will take care of adding the signature field. The code example below shows you how to use PdfDocument.addSignature() method.

import java.io.IOException;

import javax.security.auth.callback.CallbackHandler;

import com.gnostice.pdfone.PdfDocument;
import com.gnostice.pdfone.PdfException;
import com.gnostice.pdfone.PdfRect;


public class AddSignatureUsingUSBToken
{
  public static void main(String[] args) throws IOException,
      PdfException
  {
    String inputFileName = "InputDocument.pdf";
    String outputFileName = "OutputDocument.pdf";
    
    // Load an existing PDF document
    PdfDocument pdfDoc = new PdfDocument();
    pdfDoc.load(inputFileName);
    
    /*
     * path of the library that has the PKCS11 implementation of
     * the USB token.
     */
    String pathToPKCS11ImplLibOfUSBToken = "libFileName";
    
    // PIN/password to access the certificates in USB token.
    String passwordToAccessKeyStore = "password";
    
    /*
     * specify callback handler if you do not want to supply the
     * password directly to Gnostice API.
     */
    CallbackHandler callbackHandlerToGetPassword = null;
    
    /*
     * when the token is not available in the default slot then
     * attempts are made from slot number '0' to this
     * maxSlotNumber to access the token. If the value specified
     * for maxSlotNumber is '-1', then only the default slot will
     * be attempted with, to access the token.
     */
    int maxSlotNumber = -1;
    
    /*
     * Supply the parameters required to access the USB token, and
     * other details such as Reason, Location, and ContactInfo.
     */
    pdfDoc.addSignature(
      pathToPKCS11ImplLibOfUSBToken,
      passwordToAccessKeyStore,
      callbackHandlerToGetPassword,
      maxSlotNumber,
      "Signer name",  // signer's name
      "Signing this document", // reason
      "Bangalore, India", // location
      "test@example.com", // contact info
      1, // page number
      "sigField1", // field name
      new PdfRect(10, 20, 90, 40)); // location on page
    
    pdfDoc.save(outputFileName);
    pdfDoc.close();
  }
}

The following code snippets demonstrate various digital signing scenarios.

Scenario 1: I want to load an existing document and sign it. Then, when I view the output document in consumer applications such as Adobe Reader, I should see the validation result such as check mark over the signature field. NOTE: Usage of this feature is a violation of PAdES standard.

String inputFileName = "InputDocument.pdf";
String outputFileName = "OutputDocument.pdf";

// Load an existing PDF document
PdfDocument pdfDoc = new PdfDocument();
pdfDoc.load(inputFileName);

/*
 * Supply the parameters required to access the USB token, and
 * other details such as Reason, Location, and ContactInfo.
 */
PdfSignature pdfSignature = new PdfSignature(
  "libraryPath", // path to PKCS11 implementation library
  "password", // password to access KeyStore 
  null, // issuer Common name
  null, // certificate Serial number
  null, // callbackHandler to get password
  -1, // maxSlotNumber
  "Signer name",  // signer's name
  "Signing this document", // reason
  "Bangalore, India", // location
  "test@example.com", // contact info
  1); // page number

PdfFormSignatureField signatureField = new PdfFormSignatureField(
  new PdfRect(10, 10, 90, 40));
signatureField.setName("sigfld");
signatureField.fill(pdfSignature);

// specify whether validation result appearance should be included
signatureField.setIncludeValidationResultAppearance(true);

/*
 * specify if validation result appearance text should not be
 * shown when IncludeValidationResultAppearance is true.
 */
//signatureField.setIncludeValidationResultAppearanceText(false);

pdfDoc.addFormField(signatureField, 1);

pdfDoc.save(outputFileName);
pdfDoc.close();

Scenario 2: I do not want to supply the PIN to access the USB token to the PdfDocument.addSignature() method. Instead, I want to use javax.security.auth.callback.CallbackHandler to supply the PIN.

String inputFileName = "InputDocument.pdf";
String outputFileName = "OutputDocument.pdf";

// Load an existing PDF document
PdfDocument pdfDoc = new PdfDocument();
pdfDoc.load(inputFileName);

/*
 * specify callback handler if you do not want to supply the
 * password directly to Gnostice API.
 */
CallbackHandler callbackHandlerToGetPassword = new CallbackHandler()
{
  public void handle(Callback[] callbacks)
    throws IOException, UnsupportedCallbackException
  
  {
    for (int i = 0; i < callbacks.length; i++)
    {
      Callback callback = (Callback) callbacks[i];
      if (callback instanceof PasswordCallback)
      {
        PasswordCallback passwordCallback = (PasswordCallback) callback;
        
        // security provider shows a password prompt to get a password
        // passwordCallback.getPrompt();
        
        // directly supply the password
        passwordCallback.setPassword("password".toCharArray());
      }
    }
  }
};

/*
 * Supply the parameters required to access the USB token, and
 * other details such as Reason, Location, and ContactInfo.
 */
pdfDoc.addSignature("librayPath", // path to PKCS11 implementation library
  null, // password to access KeyStore 
  callbackHandlerToGetPassword,
  -1, // maxSlotNumber
  "Signer name",  // signer's name
  "Signing this document", // reason
  "Bangalore, India", // location
  "test@example.com", // contact info
  1, // page number
  "sigField1", // field name
  new PdfRect(10, 20, 100, 50)); // location on page

pdfDoc.save(outputFileName);
pdfDoc.close();

Scenario 3: I want to load a document and fill an existing blank signature form field. Also, I want to specify that the signature is detached (i.e., the original signed message digest over the document’s byte range shall be incorporated as the normal CMS SignedData field).

String inputFileName = "InputDocument.pdf";
String outputFileName = "OutputDocument.pdf";

// Load an existing PDF document
PdfDocument pdfDoc = new PdfDocument();
pdfDoc.load(inputFileName);

/*
 * Supply the parameters required to access the USB token, and
 * other details such as Reason, Location, and ContactInfo.
 */
PdfSignature pdfSignature = new PdfSignature(
  "libraryPath", // path to PKCS11 implementation library
  "password", // password to access KeyStore
  null, // issuerCommonName
  null, // certificateSerialNumber
  null, // callbackHandler to get password
  -1, // maxSlotNumber
  "Signer name",  // signer's name
  "Signing this document", // reason
  "Bangalore, India", // location
  "test@example.com", // contact info
  1); // page number

// specify whether the signature is detached
pdfSignature.setDetached(true);

// Retrieve a list of all signature fields
List fields = pdfDoc.getAllFormFieldsOnPage(0, PdfFormField.TYPE_SIGNATURE);

PdfFormSignatureField signatureField;
// Iterate the list and fill the signature fields
for (int i = 0; i < fields.size(); i++)
{
  signatureField = (PdfFormSignatureField) fields.get(i);
  if (signatureField.isUnsigned())
  {
    signatureField.fill(pdfSignature);
    break;
  }
}

pdfDoc.save(outputFileName);
pdfDoc.close();

Scenario 4: According to our security policies we can not supply the USB token credentials to PDFOne API. Instead I want to connect to the device and get the required certificate at my end and supply required certificate object to PDFOne API to sign the document.

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.PrivateKey;
import java.security.Provider;
import java.security.Security;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.util.Enumeration;

import com.gnostice.pdfone.PdfDocument;
import com.gnostice.pdfone.PdfException;
import com.gnostice.pdfone.PdfRect;
import com.gnostice.pdfone.PdfSignature;


public class AddSignatureUsingUSBToken_RetrieveCertAtUserEnd
{
  public static void main(String[] args) throws IOException,
      PdfException
  {
    // Start of block - User connects to the USB token at his end and reads the certificate
    Certificate[] certificateChain = null;
    PrivateKey privateKey = null;
    
    try
    {
      // Specify the configuration to access the USB token.
      String tokenName = "eToken";
      String pathToPKCS11ImplLibOfUSBToken = "libraryPath";
      String passwordToAccessKeyStore = "password";
      
      boolean useFirstCertificateFromToken = true;
      String issuerCommonName = "MyName";
      String certificateSerialNumber = "-398666017398154847914207";
      
      String pkcs11Config = "name=" + tokenName + System.getProperty("line.separator") 
          + "library=" + pathToPKCS11ImplLibOfUSBToken;
      
      byte[] pkcs11ConfigBytes = pkcs11Config.getBytes();
      InputStream pkcs11ConfigStream = new ByteArrayInputStream(pkcs11ConfigBytes);
      
      // Create a Provider for accessing the USB token by supplying the configuration.
      Provider pkcs11Provider = new sun.security.pkcs11.SunPKCS11(pkcs11ConfigStream);
      Security.addProvider(pkcs11Provider);
      
      // The USB device requires a PIN to access the certficates in the device.
      char[] pin = passwordToAccessKeyStore.toCharArray();
      
      // Create the Keystore for accessing certificates in the USB device by supplying the PIN.
      KeyStore smartCardKeyStore = KeyStore.getInstance("PKCS11");
      smartCardKeyStore.load(null, pin);
      
      // System.out.println("Keystore size: " + smartCardKeyStore.size());
      
      // Enumerate the certificates in the keystore
      Enumeration aliasesEnum = smartCardKeyStore.aliases();
      
      if (aliasesEnum.hasMoreElements())
      {
        while(aliasesEnum.hasMoreElements())
        {
          // choose the required certificate using the alias
          String alias = (String) aliasesEnum.nextElement();
          
          X509Certificate cert = (X509Certificate) smartCardKeyStore
            .getCertificate(alias);
          
          // System.out.println(cert);
          // System.out.println("Serial Number: " + cert.getSerialNumber());
          // System.out.println("IssuerDN: " + cert.getIssuerDN());
          
          // Always read first certificate
          if (useFirstCertificateFromToken)
          {
            certificateChain = smartCardKeyStore.getCertificateChain(alias);
            privateKey = (PrivateKey) smartCardKeyStore.getKey(alias, null);
            break;
          }
          else
          {
            // Look for the matching certificate serial number and issuer common name
            if (cert.getIssuerDN().getName().indexOf("CN=" + issuerCommonName) != -1
                && cert.getSerialNumber().toString().equals(certificateSerialNumber))
            {
              // Get the certificate chain of the required certificate and get its private key.
              certificateChain = smartCardKeyStore.getCertificateChain(alias);
              privateKey = (PrivateKey) smartCardKeyStore.getKey(alias, null);
              
              break;
            }
          }
        }
      }
      else
      {            
        throw new KeyStoreException("Keystore is empty");
      }
    }
    catch(Exception e)
    {
      e.printStackTrace();
    }
      
    if (certificateChain == null
        || privateKey == null)
    {
      return;
    }
    // End of block - User connects to the USB token at his end and reads the certificate
    
    // Load the document to be signed.
    PdfDocument pdfDoc = new PdfDocument();
    pdfDoc.load("InputDocument.pdf");
    
    /*
     * Supply Certificate[] array and PrivateKey objects retrieved
     * from the KeyStrore of USB token.
     */
    PdfSignature pdfSignature = new PdfSignature(
      certificateChain,
      privateKey,
      "Signer name",  // signer's name
      "Signing this document", // reason
      "Bangalore, India", // location
      "test@example.com", // contact info
      1); // page number
    
    pdfSignature.setFieldName("sigfld");
    pdfSignature.setFieldRect(new PdfRect(10, 10, 100, 50));
    
    // add the signature to the document
    pdfDoc.addSignature(pdfSignature);
    
    pdfDoc.save("OutputDocument.pdf");
    pdfDoc.close();
  }
}

Scenario 5: I have connected more than one USB token and sometimes a token has more than one certificate installed in it. I want to sign the document using a specific certificate in the USB token. You can use the code snippet demonstrated in Scenario 4 to iterate the certificates of a token at your end and identify the issuer name and certificate serial number of the certificate you want to use.

String inputFileName = "InputDocument.pdf";
String outputFileName = "OutputDocument.pdf";

// Load an existing PDF document
PdfDocument pdfDoc = new PdfDocument();
pdfDoc.load(inputFileName);

/*
 * when the token is not available in the default slot then
 * attempts are made from slot number '0' to this
 * maxSlotNumber to access the token. If the value specified
 * for maxSlotNumber is '-1', then only the default slot will
 * be attempted with, to access the token.
 */
int maxSlotNumber = 10;

/*
 * Supply the parameters required to access the USB token, and
 * other details such as Reason, Location, and ContactInfo.
 */
PdfSignature pdfSignature = new PdfSignature(
  "libraryPath", // path to PKCS11 implementation library
  "password", // password to access KeyStore 
  "MyName", // issuer Common name
  "-3c6404190057ff9cfa39", // certificate Serial number
  null, // callbackHandler to get password
  maxSlotNumber, // maxSlotNumber
  "Signer name",  // signer's name
  "Signing this document", // reason
  "Bangalore, India", // location
  "test@example.com", // contact info
  1); // page number
pdfSignature.setFieldName("sigfld");
pdfSignature.setFieldRect(new PdfRect(10, 10, 100, 50));

pdfDoc.addSignature(pdfSignature);

pdfDoc.save(outputFileName);
pdfDoc.close();

---o0O0o---

Our .NET Developer Tools
Gnostice Document Studio .NET

Multi-format document-processing component suite for .NET developers.

PDFOne .NET

A .NET PDF component suite to create, edit, view, print, reorganize, encrypt, annotate, and bookmark PDF documents in .NET applications.

Our Delphi/C++Builder developer tools
Gnostice Document Studio Delphi

Multi-format document-processing component suite for Delphi/C++Builder developers, covering both VCL and FireMonkey platforms.

eDocEngine VCL

A Delphi/C++Builder component suite for creating documents in over 20 formats and also export reports from popular Delphi reporting tools.

PDFtoolkit VCL

A Delphi/C++Builder component suite to edit, enhance, view, print, merge, split, encrypt, annotate, and bookmark PDF documents.

Our Java developer tools
XtremeDocumentStudio (for Java)

Multi-format document-processing component suite for Java developers.

PDFOne (for Java)

A Java PDF component suite to create, edit, view, print, reorganize, encrypt, annotate, bookmark PDF documents in Java applications.

Our Platform-Agnostic Cloud and On-Premises APIs
StarDocs

Cloud-hosted and On-Premises REST-based document-processing and document-viewing APIs

Privacy | Legal | Feedback | Newsletter | Blog | Resellers © 2002-2019 Gnostice Information Technologies Private Limited. All rights reserved.