Creating Signature Handlers

You can create a custom signature handler for the Signature service that provides custom functionality when signing, certifying, or verifying PDF documents. For example, normally you must reference a security credential that is located in a LiveCycle trust store when digitally signing or certifying a document. However, when using a signature provider, you can reference third-party security credentials that are stored in locations accessible over a network as opposed to the LiveCycle trust store.

The Signature service provides an extensible Java SPI that lets you create a signature handler. This section explains how to use the Signature service Java SPI to create a signature handler for the Signature service. (See LiveCycle API Reference .)

Note: Before creating a signature handler, it is recommended that you be familiar with the Signature service. (See Digitally Signing and Certifying Documents .)
Note: The ability to create a signature handler was introduced in LiveCycle ES 8.2.

To implement a signature handler, create a Java interface that uses Java data types located in the com.adobe.livecycle.signatures.client.spi package, such as SignRequest and SignResponse .

After you create the application logic, you must deploy your signature handler as a LiveCycle component. You can then use your component when signing or certifying a PDF document using a process created in Workbench, or by using the Signature service Java or web service API. (See Creating Your First Component .)

Understanding the signature handler Java SPI

To successfully create a signature handler, it is recommended that you be familiar with the signature handler Java SPI. Although there are many classes in this Java library, the following are the main classes:

  • SignRequest : Represents an object that is sent to the signature handler when a client application attempts to sign or certify a PDF document. This object contains information such as the key name element, which specifies a string value that can be used by the signer to communicate a key identifier to the recipient. Typically, the key name value contains an identifier related to the key pair used to sign the message, but it may contain other protocol-related information that indirectly identifies a key pair. Common uses of the key name value include simple string names for keys, a key index, a distinguished name (DN), and an email address.

  • SignResponse : Represents the response to the sign request. This object contains information such as the status of the request and the timestamp value if the sign request was successful.

  • InputDocument : Represents the PDF document to digitally sign. The document is xml or base64 encoded. This type also contains the document’s hash value.

  • DocumentBytes : Represents the bytes that make up the document.

  • SignatureObject: Represents the signature that is used to digitally sign the PDF document. This object contains information such as the algorithm used to generate the signature. The following are valid signature types:

    • XML Signature

    • XMLTimeStampToken

    • RFC 3161 TimeStampToken

    • PKCS#7

    • PKCS#1

    • DSA Signatures

Sample files

This section describes using Java classes that correspond to sample .JAVA files that are located in the [install directory] \Adobe\Adobe_Experience_Manager_forms\sdk\samples\Signatures-spi folder, where [install directory] is the LiveCycle installation location. As you read through this section, it is recommended that you also refer to the sample JAVA files. This section contains application logic that is based on the sample JAVA files.

Note: This section creates a signature handler that signs a PDF document. As a result, only the Sign operation is implemented. You can refer to the sample .JAVA files to see how other operations such as the verify operation are implemented.
The recommended way to create a signature handler is to create a Java project and then copy and paste the code located in this section to your project. Follow the steps in this section to deploy and test the signature handler.

Summary of steps

To develop a signature handler, perform the following steps:

  1. Set up your development environment. (See Setting up your development environment .)

  2. Define your application logic. (See Defining application logic for the signature handler .)

  3. Define the component XML file. (See Defining the component XML file for the signature handler .)

  4. Package the signature handler. (See Packaging the signature handler .)

  5. Deploy and test the signature handler. (See Testing the signature handler .)

Setting up your development environment

To create a signature handler, create a new Eclipse Java project. The supported Eclipse version is 3.2.1 or later. The JAR files that you add to your Eclipse project depend upon your implementation. The signature handler that is created in this section requires the following JAR files:

  • sdk/spi/adobe-signatures-spi.jar

  • sdk/client-libs/common/adobe-livecycle-client.jar

  • sdk/client-libs/common/adobe-signatures-client.jar

  • sdk/spi/adobe-signatures-spi.jar

  • sdk/client-libs/common/adobe-usermanager-client.jar

  • sdk/client-libs/jboss/adobe-utilities.jar

  • sdk/client-libs/thirdparty/xalan.jar

  • sdk/client-libs/thirdparty/xercesImpl.jar

  • sdk/client-libs/thirdparty/log4j.jar

  • sdk/client-libs/thirdparty/commons-fileupload-1.2.jar

  • sdk/client-libs/thirdparty/commons-codec-1.3.jar

Additional JAR files

In addition to the JAR files specified in the previous list, the following JAR files are also required:

  • commons-io-1.4.jar

  • bcmail-jdk15-135.jar

  • bcprov-jdk15-135.jar

  • jsafeFIPS.jar

  • jsafeJCEFIPS.jar

  • certjFIPS.jar

Note: These JAR files can be downloaded from the internet.

To create a new Java project:

  1. Start Eclipse.

  2. Click File > New > Project .

  3. Specify a name for the project in the Project name box.

  4. Click Finish .

To add required JAR files to your project:

  1. In the Project Explorer window, right-click your project and select Properties .

  2. Click Java build path .

  3. Click the Libraries tab.

  4. Click Add External JARs and select the JAR files to include.

Defining application logic for the signature handler

To create a signature handler, create a class that implements com.adobe.livecycle.externalpkiprovider.client.ExternalPKIServiceClientInterface interface. This interface contains Signature service operations that you can customize and is defined in the adobe-signatures-spi.jar file. (See Setting up your development environment .)

The com.adobe.livecycle.externalpkiprovider.client.ExternalPKIServiceClientInterface contains the following four method declarations:

  • public Document embedSignedContent (HashWithReference href, byte[] signedContent)

  • public HashWithReference getHash(Document inPDFDoc, String signatureFieldName, Credential credential, HashAlgorithm hashAlgorithm, String reason,String location, String contactInfo, PDFSignatureAppearanceOptionSpec appearanceOptionSpec, Boolean embedRevocationInfo, OCSPOptionSpec ocspOptionSpec,CRLOptionSpec crlOptionSpec, TSPOptionSpec tspOptionSpec, Integer exepectedSize)throws PDFOperationException, SigningException,PermissionsException, InvalidArgumentException,SeedValueValidationException, MissingSignatureFieldException,SignatureFieldSignedException, CredentialLoginException,SignaturesOtherException, FIPSComplianceException;

  • public SPIVerificationInfo verifyCertificate(SPICertificateInfo spiCertInfo, Date verificationTime, String revoStyle, int trustLevel, HashMap<String, byte[]> spiProperties);

  • public SignResponse sign(SignRequest inSignRequest) throws SigningException;

To create a signature handler, this document creates the following classes:

  • ExternalPKIService - Extends the com.adobe.livecycle.externalpkiprovider.client.ExternalPKIServiceClientInterface interface and contains application logic for the signature handler operations. For example, application logic that is invoked when the Signature service signs a PDF document is specified within this class. This class belongs to the com.adobe.livecycle.externalpkiprovider.service.impl package.

  • epkiUtil - Contains additional business logic that performs operations peformed by the signature handler. For example, this class retrieves configuration values and prepares the certificate used to sign the document. This class belongs to the com.adobe.livecycle.externalpkiprovider.util package.

  • FileReadAction - A utility class that reads data from a file. This class belongs to the com.adobe.livecycle.externalpkiprovider.util package.

  • FileWriteAction - A utility class that writes data from a file. This class belongs to the com.adobe.livecycle.externalpkiprovider.util package.

  • SignatureAttributes - A utility class that helps define attributes used by the signature handler. This class belongs to the com.adobe.livecycle.externalpkiprovider.util package.

Creating the ExternalPKIService class

The ExternalPKIService class is the main class for the signature handler and contains the implementation logic for the operations exposed by the signature handler. For example, application logic that is invoked when a PDF document is signed is located in this class. This class implements the ExternalPKIServiceClientInterface interface. (See Defining application logic for the signature handler .)

When a PDF document is signed, the sign operation is invoked by the Signature service, which passes a com.adobe.livecycle.signatures.client.spi.types.SignRequest instance as an input parameter. This occurs when the Signature service is configured to use the signature handler by invoking the setSpiName method that belongs to the com.adobe.livecycle.signatures.client.types.Credential class. (See Testing the signature handler .)

Assume, for example, that a Java client application uses the Signature service Java API to sign a document. The Signature service invokes the signature handler by calling the sign method located in the ExternalPKIService class.

The rest of this section explains the Java application logic located in the sign method. When the sign method is invoked, an epkiUtil object is created and the following configuration values are set:

BaseDir = C:\\serverCredentials 
KeyPreFix = testKey 
CertPreFix = testCert 
KeyExt = .der 
CertExt = .der 
MinIndex = 1 
MaxIndex = 1 
SPIName = SamplePKIProviderServiceEx

These configuration values are located in a configuration file named server.orig.properties that is located in the following LiveCycle SDK sample directory: [ LiveCycle Install Directory ]\Adobe\Adobe_Experience_Manager_forms\sdk\samples\Signatures-spi\conf\server.orig.properties

When testing the signature handler, ensure that this configuration file is located in this folder. The configuration file is loaded in a method named loadConf that is located in the epkiUtil class.

The sign method calls a private method located in the ExternalPKIService class named signAndPrepareResponse , which returns a SignReponse object. The signAndPrepareResponse method performs the following tasks:

  • Determines the signature type by invoking a static method in the epkiUtil class named getSignatureTypeFromString . This method accepts a string value that specifies the signature type. A string value that specifies the signature type can be obtained by invoking the SignRequest object’s getDigestEmbedType method. This method returns an integer value that specifies the signature type.

  • Retrieves a java.security.PrivateKey object by invoking a method in the epkiUtil class named getPrivateKeyFromCache . This method accepts a byte array that represents the credential. The byte array is obtained by invoking the SignRequest.getCredentialIdentifier.getX509Data.getX509Certificate method.

  • Retrieves a com.adobe.livecycle.signatures.pki.client.types.common.HashAlgorithm object by invoking the private DigestAlgoToHashAlgo method. This method accepts a java.net.URI object and returns a HashAlgorithm object. The java.net.URI object can be obtained by invoking the SignRequest.getInputDocument.getDocumentHash.getDigestAlgorithm method.

  • Retrieves the digest value of the input document. This byte array value is obtained by invoking the SignRequest.getInputDocument.getDocumentHash.getDigestValue method.

  • Retrieves a SignParameters object by invoking the private createSignParams method. This method accepts a SignRequest object, an integer value that specifies the signature type, an epkiUtil object, and a Boolean value that specifies whether to use an embedded time stamp.

  • Signs the document by invoking the pkiSign method. This method accepts an integer value that specifies the signature type, a java.security.PrivateKey object, a HashAlgorithm object, a byte array that specifies the digest value, a SignParameters object, and an epkiUtil object. The pkiSign method returns a byte array that represents the signed document.

The signAndPrepareResponse method returns a com.adobe.livecycle.signatures.client.spi.types.SignResponse object that specifies whether the operation was successful. Finally, the sign method returns the SignResponse object.

Although this document provides some details about how the code works, the best way to learn the Java code is to load the classes located in this section in a Java IDE and read through the Java code.

The following code represents the ExternalPKIService class.

package com.adobe.livecycle.externalpkiprovider.service.impl; 
 
import java.net.URI; 
import java.net.URISyntaxException; 
import java.security.PrivateKey; 
import java.security.cert.CertStore; 
import java.security.cert.CertificateFactory; 
import java.security.cert.CollectionCertStoreParameters; 
import java.security.cert.X509CRL; 
import java.security.cert.X509Certificate; 
import java.lang.InterruptedException; 
 
import java.util.ArrayList; 
import java.util.Date; 
import java.util.HashMap; 
import java.util.Map; 
import java.io.ByteArrayInputStream; 
import java.io.ByteArrayOutputStream; 
import java.io.FileInputStream; 
import java.io.FileNotFoundException; 
import java.io.IOException; 
import java.io.InputStream; 
import java.io.ObjectInputStream; 
import java.io.PrintStream; 
import java.util.Properties; 
import java.util.UUID; 
import java.lang.Integer; 
 
import org.bouncycastle.asn1.cms.CMSAttributes; 
import com.adobe.livecycle.externalpkiprovider.util.epkiUtil; 
 
import com.adobe.idp.Document; 
import com.adobe.idp.dsc.InvocationRequest; 
import com.adobe.idp.dsc.InvocationResponse; 
import com.adobe.idp.dsc.clientsdk.ServiceClient; 
import com.adobe.idp.dsc.clientsdk.ServiceClientFactory; 
 
import com.adobe.livecycle.signatures.client.spi.types.SignRequest; 
import com.adobe.livecycle.signatures.client.spi.types.SignResponse; 
import com.adobe.livecycle.signatures.client.spi.types.SignatureObject; 
import com.adobe.livecycle.signatures.client.spi.types.Result; 
import com.adobe.livecycle.signatures.client.spi.types.exceptions.SigningException; 
import com.adobe.livecycle.signatures.client.spi.types.exceptions.ExceptionMsgIds; 
 
import com.adobe.livecycle.signatures.client.spi.types.SPICertificateInfo; 
import com.adobe.livecycle.signatures.client.spi.types.SPIVerificationInfo; 
import com.adobe.livecycle.signatures.client.spi.types.HashWithReference; 
import com.adobe.livecycle.signatures.client.types.CRLOptionSpec; 
import com.adobe.livecycle.signatures.client.types.Credential; 
import com.adobe.livecycle.signatures.client.types.OCSPOptionSpec; 
import com.adobe.livecycle.signatures.client.types.PDFSignatureAppearanceOptionSpec; 
import com.adobe.livecycle.signatures.client.types.TSPOptionSpec; 
import com.adobe.livecycle.signatures.client.types.exceptions.SignaturesBaseException; 
 
 
import com.adobe.livecycle.signatures.pki.client.types.common.HashAlgorithm; 
import com.adobe.livecycle.externalpkiprovider.util.SignatureAttributes; 
import com.adobe.livecycle.externalpkiprovider.client.ExternalPKIServiceClientInterface; 
 
 
import com.adobe.logging.AdobeLogger; 
import com.rsa.asn1.ASN1; 
import com.rsa.asn1.ASN1Container; 
import com.rsa.asn1.ASN_Exception; 
import com.rsa.asn1.EndContainer; 
import com.rsa.asn1.OctetStringContainer; 
import com.rsa.asn1.SetContainer; 
 
/** 
 * The ExternalPKIService interface is an 
 * implementation of the Signatures SPI Interface.  
 */ 
 
public class ExternalPKIService implements ExternalPKIServiceClientInterface  { 
     
    protected String m_fileSep; 
    protected String m_userHome; 
    protected static HashMap<UUID, TableEntry> tableOfThreads = null; 
     
    private static AdobeLogger logger = AdobeLogger.getAdobeLogger(ExternalPKIService.class.getName()); 
    public ExternalPKIService() { 
        tableOfThreads = new HashMap<UUID, TableEntry>(); 
        m_fileSep = System.getProperty("file.separator"); 
        m_userHome = System.getProperty("user.home"); 
    } 
     
        public SignResponse sign(SignRequest sreq) throws SigningException 
    { 
        SignResponse sresp = null; 
        epkiUtil epki = new epkiUtil(null, false, false); 
        try { 
            logger.info("SampleExternalPKIProvider: *******Signing using the Signature Handler*********"); 
            URI u1 = new URI("Success"); 
            Result r = new Result(u1, null, "Success"); 
            Properties prop = Credential.hashMapToProperties(sreq.getSPIGenericProperties()); 
            boolean embedTimeStamp = new Boolean(prop.getProperty("embedTimeStamp")); 
            sresp = signAndPrepareResponse(sreq, r, epki, embedTimeStamp); 
             
        } catch (Exception e) { 
            logException(e); 
            throw new SigningException(e); 
        } 
        return sresp; 
    } 
         
     
    public SPIVerificationInfo verifyCertificate(SPICertificateInfo spiCertInfo, 
            Date verificationTime, String revoStyle, int trustLevel, HashMap spiProperties) { 
 
        return null; 
    } 
     
    private void logException(Exception e) { 
        ByteArrayOutputStream bas = new ByteArrayOutputStream(); 
        PrintStream pst = new PrintStream(bas); 
        e.printStackTrace(pst); 
        logger.warning(e.getMessage() + "\n" + bas.toString()); 
    } 
 
    private HashAlgorithm DigestAlgoToHashAlgo(java.net.URI ha) { 
         
        try { 
            if(ha.equals(new java.net.URI("http://www.w3.org/2000/09/xmldsig#sha1"))) { 
                logger.info("Hash Algo is SHA1"); 
                return HashAlgorithm.SHA1;                 
            } 
            if(ha.equals(new java.net.URI("http://www.w3.org/2001/04/xmldsig-more#md5"))){ 
                logger.info("Hash Algo is MD5"); 
                return HashAlgorithm.MD5; 
            } 
            logger.info("Hash Algo is " + HashAlgorithm.getValueFromString(ha.toString()).toString());             
            return HashAlgorithm.getValueFromString(ha.toString()); 
        }catch (URISyntaxException urise) { 
            logException(urise); 
        } 
        return null; 
    } 
 
    private SignResponse signAndPrepareResponse(SignRequest sreq, Result r, epkiUtil epki, boolean embedTimeStamp) throws SigningException, Exception{ 
        int stype = epkiUtil.getSignatureTypeFromString(sreq.getDigestEmbedType()); 
        PrivateKey key = epki.getPrivateKeyFromCache(sreq.getCredentialIdentifier().getX509Data().getX509Certificate()); 
        HashAlgorithm hashAlgo = DigestAlgoToHashAlgo(sreq.getInputDocument().getDocumentHash().getDigestAlgorithm()); 
        byte[] data = sreq.getInputDocument().getDocumentHash().getDigestValue(); 
         
        SignParameters sp = createSignParams(sreq, stype, epki, embedTimeStamp); 
        byte pkcs7Bytes[] = null; 
        if(key == null) { 
            logger.warning("PrivateKey is null"); 
        } 
        logger.info("Calling pkiSign with params:" + stype +":"+ key+":"+ hashAlgo+":"+ data+":"+sp); 
        pkcs7Bytes = pkiSign(stype, key, hashAlgo, data, sp, epki); 
        logger.info("The length of signature is " + pkcs7Bytes.length); 
        r.setInternationalizedResultMessage("signed successfully"); 
        return createSignResponse(sreq, r, pkcs7Bytes); 
    } 
     
    private SignResponse createSignResponse (SignRequest sreq, Result r, byte[] pkcs7Bytes) { 
        SignatureObject so = new SignatureObject(null, null, pkcs7Bytes); 
        int length = so.getSignatureBytes().length; 
        SignResponse sresp = new SignResponse(r, so, sreq.getRequestID(), length); 
        return sresp; 
    } 
     
     
    class SignParameters { 
        protected int signatureType; 
        protected CertStore certsAndCRLs; 
        protected X509Certificate signerCert; 
        protected HashMap<String, byte[]> authAttr; 
        protected SignatureAttributes unAuthAttrGenerator; 
        protected HashMap<String, byte[]> spiProperties; 
        protected String contentTypeOID; 
         
        public String getContentTypeOID(){ 
            return contentTypeOID; 
        } 
        public void setContentTypeOID(String oid){ 
            this.contentTypeOID = oid; 
        } 
         
        public int getSignatureType() { 
            return signatureType; 
        } 
        public void setSignatureType(int signatureType) { 
            this.signatureType = signatureType; 
        } 
        public CertStore getCertsAndCRLs() { 
            return certsAndCRLs; 
        } 
        public void setCertsAndCRLs(CertStore certsAndCRLs) { 
            this.certsAndCRLs = certsAndCRLs; 
        } 
        public X509Certificate getSignerCert() { 
            return signerCert; 
        } 
        public void setSignerCert(X509Certificate signerCert) { 
            this.signerCert = signerCert; 
        } 
        public HashMap<String, byte[]> getAuthAttr() { 
            return authAttr; 
        } 
        public void setAuthAttr(HashMap<String, byte[]> authAttr) { 
            this.authAttr = authAttr; 
        } 
        public SignatureAttributes getUnAuthAttrGenerator() { 
            return unAuthAttrGenerator; 
        } 
        public void setUnAuthAttrGenerator(SignatureAttributes unAuthAttr) { 
            this.unAuthAttrGenerator = unAuthAttr; 
        } 
        public HashMap<String, byte[]> getSpiProperties() { 
            return spiProperties; 
        } 
        public void setSpiProperties(HashMap<String, byte[]> spiProperties) { 
            this.spiProperties = spiProperties; 
        } 
    } 
     
    private SignParameters createSignParams (SignRequest sreq, int stype, epkiUtil epki, boolean embedTimeStamp) throws SigningException, Exception{ 
        SignParameters cmsParams = new SignParameters(); 
        populateCertsAndCRLs(sreq, cmsParams, epki); 
        cmsParams.setAuthAttr(sreq.getSignedSignatureProperties()); 
        HashMap<String, byte[]> unAuthAttrs = sreq.getUnsignedSignatureProperties(); 
        if(embedTimeStamp){ 
            SignatureAttributes ag = new SignatureAttributes(unAuthAttrs); 
            cmsParams.setUnAuthAttrGenerator(ag); 
        } else { 
            cmsParams.setUnAuthAttrGenerator(null); 
        } 
        return cmsParams; 
    } 
     
    private void populateCertsAndCRLs(SignRequest sreq, SignParameters params, epkiUtil epki) throws SigningException  { 
        try { 
            ArrayList<java.lang.Object> cout = new ArrayList<java.lang.Object>(); 
             
            HashMap<String, byte[]> spiProps = (HashMap<String, byte[]>)sreq.getSPIGenericProperties(); 
            byte[] certsByte = spiProps.get(new String("Certs")); 
            byte[] crlsByte = spiProps.get(new String("CRLs")); 
             
            if(certsByte != null) { 
                ByteArrayInputStream certStream = new  ByteArrayInputStream(certsByte); 
                ObjectInputStream certObjectStream = new ObjectInputStream(certStream); 
                ArrayList<X509Certificate> certs = (ArrayList<X509Certificate>) certObjectStream.readObject(); 
                for(java.security.cert.X509Certificate crtInfo: certs){ 
                    cout.add(crtInfo); 
                } 
            } 
             
            if(crlsByte != null) { 
                ByteArrayInputStream crlStream = new ByteArrayInputStream(crlsByte); 
                ObjectInputStream crlObjectStream = new ObjectInputStream(crlStream); 
                ArrayList<X509CRL> crls = (ArrayList<X509CRL>) crlObjectStream.readObject(); 
                for(java.security.cert.X509CRL crlrevInfo: crls){ 
                    cout.add(crlrevInfo); 
                } 
            } 
             
            CertStore cst = CertStore.getInstance("Collection", new CollectionCertStoreParameters(cout), 
                    epki.getProviderName()); 
             
            byte[] certBytes = sreq.getCredentialIdentifier().getX509Data().getX509Certificate(); 
            InputStream inStream = new ByteArrayInputStream(certBytes); 
            CertificateFactory cf = CertificateFactory.getInstance("X.509"); 
            X509Certificate signerCert = (X509Certificate)cf.generateCertificate(inStream); 
            inStream.close(); 
 
            params.setSignerCert(signerCert); 
            params.setCertsAndCRLs(cst); 
             
        } catch (Exception e) { 
                throw new SigningException(ExceptionMsgIds.SignerPKCS7Embed_Exn, null, e); 
        } 
    } 
 
    private byte[] pkiSign(int type, PrivateKey key, 
            HashAlgorithm hashAlgo, byte[] data, SignParameters params, epkiUtil epki) throws SigningException { 
 
        if (hashAlgo == null ||  key == null || params == null 
                || data == null || params.getSignerCert() == null) 
            throw new SigningException("Invalid sign parameters"); 
 
        if (type == epkiUtil.PKCS7_DATA) { 
            // Data cannot be digested if we have to embed it in the signature 
            //certificateDB; 
            return epki.sign(key, hashAlgo.toString(), data, params.getSignerCert(), 
                    params.getCertsAndCRLs(), params.getUnAuthAttrGenerator(), 
                    params.getAuthAttr(), params.getContentTypeOID(), false); 
        } else if (type == epkiUtil.PKCS7_DETACHED) { 
            Map<String, byte[]> authAttrs = params.getAuthAttr(); 
            if (authAttrs == null) 
                authAttrs = new HashMap<String, byte[]>(); 
            addDigestToAttr(data, authAttrs); 
            params.setAuthAttr((HashMap<String, byte[]>)authAttrs); 
            data = null; 
            return epki.sign(key, hashAlgo.toString(), data, params.getSignerCert(), 
                    params.getCertsAndCRLs(), params.getUnAuthAttrGenerator(), 
                    params.getAuthAttr(), params.getContentTypeOID(), true); 
        } 
        return null; 
    } 
     
    private void addDigestToAttr(byte[] data, Map<String, byte[]> authAttrs) 
        throws SigningException { 
         
    try { 
        OctetStringContainer encCont = new OctetStringContainer( 
                ASN1.NO_SPECIAL, true, ASN1.NO_TAG, data, 0, data.length); 
        authAttrs.put(CMSAttributes.messageDigest.getId(), 
                ASN1.derEncode(new ASN1Container[] { 
                                new SetContainer(ASN1.NO_SPECIAL, true, 
                                        ASN1.NO_TAG), encCont, 
                                new EndContainer() })); 
        } catch (ASN_Exception e) { 
            throw new SigningException("Unable to encode digest in a set", e); 
        } 
    } 
     
    class getHashRun implements Runnable { 
        protected Document mInPDFDoc; 
        protected String mSignatureFieldName; 
        protected Credential mCredential; 
        protected HashAlgorithm mHashAlgorithm; 
        protected String mReason; 
        protected String mLocation; 
        protected String mContactInfo; 
        protected PDFSignatureAppearanceOptionSpec mAppearanceOptionSpec; 
        protected Boolean mEmbedRevocationInfo; 
        protected OCSPOptionSpec mOcspOptionSpec; 
        protected CRLOptionSpec mCrlOptionSpec; 
        protected TSPOptionSpec mTspOptionSpec; 
                 
        protected ServiceClientFactory m_serviceFactory; 
        //protected ComponentRegistry m_componentRegistry; 
        //protected ServiceRegistry m_serviceRegistry; 
        protected ServiceClient m_serviceClient; 
         
        protected TableEntry mTableE; 
         
           protected Properties loadServerProperties(){ 
                Properties prop = new Properties(); 
                java.net.URL u = this.getClass ().getResource ("/server.properties"); 
                String propFileName = null; 
                 
                InputStream is = null; 
                try { 
                    if(u!=null) { 
                        is  = u.openStream(); 
                    } else { 
                        propFileName = "server.properties"; 
                        is = new FileInputStream(propFileName); 
                    } 
                    prop.load(is); 
                    return prop; 
                } catch (FileNotFoundException fnfe) { 
                    logger.warning("File Not Found either " + propFileName); 
                    logger.warning(fnfe.getMessage()); 
                } catch (IOException ioe) { 
                    logger.warning("File format not correct " + propFileName); 
                    logger.warning(ioe.getMessage()); 
                } catch (Exception e) { 
                    logger.warning("Exception loading properties:" + propFileName + e.getMessage()); 
                } 
                return null; 
           } 
 
           public getHashRun(Document inPDFDoc, String signatureFieldName, 
                Credential credential, HashAlgorithm hashAlgorithm, String reason, 
                String location, String contactInfo, 
                PDFSignatureAppearanceOptionSpec appearanceOptionSpec, 
                Boolean embedRevocationInfo, OCSPOptionSpec ocspOptionSpec, 
                CRLOptionSpec crlOptionSpec, TSPOptionSpec tspOptionSpec, TableEntry te){ 
            mInPDFDoc = inPDFDoc; 
            mSignatureFieldName = signatureFieldName; 
            mCredential = credential; 
            mHashAlgorithm = hashAlgorithm; 
            mReason = reason; 
            mLocation = location; 
            mContactInfo = contactInfo;; 
            mAppearanceOptionSpec = appearanceOptionSpec; 
            mEmbedRevocationInfo = embedRevocationInfo; 
            mOcspOptionSpec = ocspOptionSpec; 
            mCrlOptionSpec = crlOptionSpec; 
            mTspOptionSpec = tspOptionSpec; 
            mTableE = te; 
        } 
         
        public void run() { 
            Properties serverProps = loadServerProperties(); 
             
            Properties connectProps = new Properties(); 
             
            connectProps.setProperty("DSC_CREDENTIAL_USERNAME", serverProps.getProperty("DSC_CREDENTIAL_USERNAME")); 
            connectProps.setProperty("DSC_CREDENTIAL_PASSWORD", serverProps.getProperty("DSC_CREDENTIAL_PASSWORD")); 
            connectProps.setProperty("DSC_SERVER_TYPE", serverProps.getProperty("DSC_SERVER_TYPE")); 
                         
            m_serviceFactory = ServiceClientFactory.createInstance(connectProps); 
            m_serviceClient = m_serviceFactory.getServiceClient(); 
             
            int i =0; 
             
            String args[] = new String[12]; 
            Map inputParams = new HashMap(); 
 
            String serviceName = "SignatureService"; 
            String outputParam = "outPDFDoc"; 
            String opern = "sign"; 
             
            args[0] = "inPDFDoc"; 
            args[1] = "signatureFieldName"; 
            args[2] = "credential"; 
            args[3] = "hashAlgorithm"; 
            args[4] = "reason"; 
            args[5] = "location"; 
            args[6] = "contactInfo"; 
            args[7] = "appearanceOptionSpec"; 
            args[8] = "embedRevocationInfo"; 
            args[9] = "ocspOptionSpec"; 
            args[10] = "crlOptionSpec"; 
            args[11] = "tspOptionSpec"; 
             
            Properties prop = Credential.hashMapToProperties(mCredential.getSPIProperties()); 
            prop.setProperty("TRANSACTIONID", mTableE.getTransactionId()); 
            mCredential.setSPIProperties(Credential.propertiesToHashMap(prop)); 
 
            // create the request 
            inputParams.put(args[0], mInPDFDoc); 
            inputParams.put(args[1], mSignatureFieldName); 
            inputParams.put(args[2], mCredential); 
            inputParams.put(args[3], mHashAlgorithm); 
            inputParams.put(args[4], mReason); 
            inputParams.put(args[5], mLocation); 
            inputParams.put(args[6], mContactInfo); 
            inputParams.put(args[7], mAppearanceOptionSpec); 
            inputParams.put(args[8], mEmbedRevocationInfo); 
            inputParams.put(args[9], mOcspOptionSpec); 
            inputParams.put(args[10], mCrlOptionSpec); 
            inputParams.put(args[11], mTspOptionSpec); 
            InvocationRequest request = m_serviceFactory.createInvocationRequest(serviceName, 
                    opern, inputParams, true); 
         
            // invoke the operation 
            try { 
                ExternalPKIService.logger.info("ExternalPKIService.getHashRun.run(): Calling sign on DSS SignatureService"); 
                InvocationResponse response = m_serviceClient.invoke(request); 
                Document pdfout = (Document) (response.getOutputParameter(outputParam)); 
                mTableE.setDoc(pdfout); 
                mTableE.NotifyServiceThread(); 
            } catch (Exception fault) { 
                if (fault.getCause() instanceof SignaturesBaseException) { 
                    SignaturesBaseException de = (SignaturesBaseException) fault 
                            .getCause(); 
                    System.out.println(de.getMessage()); 
//                    /fail("No Exception Expected while Signing!"); 
                } else { 
                    fault.printStackTrace(); 
                    //assertTrue(false); 
                } 
                mTableE.setException(fault); 
                mTableE.NotifyServiceThread(); 
            } 
        } 
    } 
         
    class SynchroPrimitive  { 
        public SynchroPrimitive() { 
            //super(); 
        } 
        synchronized public void Wait() throws InterruptedException{ 
            wait(); 
        } 
        synchronized public void Notify() { 
            notify(); 
        } 
    } 
     
    class TableEntry { 
        protected byte[] signedContent; 
        protected byte[] hash; 
        protected Document doc; 
        protected UUID transactionId; 
        protected int signatureSize; 
        protected int state; //experimental, may not be consisent 
        protected Exception exception; 
        protected Thread workerThread; 
        protected SynchroPrimitive serviceSynPrim; 
        protected SynchroPrimitive workerSynPrim; 
         
        public TableEntry(Document pdoc, int expectedSize){ 
            ExternalPKIService.logger.info("TableEntry: Creating new object"); 
            signatureSize = expectedSize; 
            signedContent=null; 
            hash = null; 
            doc = pdoc; 
            transactionId=UUID.randomUUID(); 
            workerThread=null; 
            exception = null; 
            serviceSynPrim = new SynchroPrimitive(); 
            workerSynPrim =  new SynchroPrimitive(); 
        } 
 
        public Document getDoc() { 
            ExternalPKIService.logger.info("TableEntry: Returning Document"); 
            return doc; 
        } 
 
        public void setDoc(Document doc) { 
            ExternalPKIService.logger.info("TableEntry: Setting Document"); 
            this.doc = doc; 
        } 
         
        public int getSignatureSize() { 
            ExternalPKIService.logger.info("TableEntry: returning size=" + signatureSize); 
            return signatureSize; 
        } 
        public void setSignatureSize(int size) { 
            ExternalPKIService.logger.info("TableEntry: setting size=" + size); 
            signatureSize = size; 
        } 
 
        byte[] getHash() { 
            ExternalPKIService.logger.info("TableEntry: Returning Hash"); 
            return hash; 
        } 
 
                public void setHash(byte[] hash) { 
            ExternalPKIService.logger.info("TableEntry: Setting hash"); 
            this.hash = hash; 
        } 
 
                public byte[] getSignedContent() { 
            ExternalPKIService.logger.info("TableEntry: Returning SignedContent"); 
            return signedContent; 
        } 
 
        public void setSignedContent(byte[] signedContent) { 
            ExternalPKIService.logger.info("TableEntry: Setting Signed Content"); 
            this.signedContent = signedContent; 
        } 
 
        public int getState() { 
            ExternalPKIService.logger.info("TableEntry: Returning State"); 
            return state; 
        } 
 
        public void setState(int state) { 
            this.state = state; 
        } 
 
        public String getTransactionId() { 
            ExternalPKIService.logger.info("TableEntry: Returning TrasactionId" + transactionId.toString()); 
            return transactionId.toString(); 
        } 
         
        public void setException(Exception e) { 
            this.exception = e; 
        } 
        public Exception getException() { 
            return this.exception; 
        } 
 
        public void setTransactionId(String sTransactionId) { 
            ExternalPKIService.logger.info("TableEntry: Setting TransactionId:" + sTransactionId); 
            this.transactionId = UUID.fromString(sTransactionId); 
        } 
 
        public Thread getWorkerThread() { 
            ExternalPKIService.logger.info("TableEntry: Returning Worker thread"); 
            return workerThread; 
        } 
 
        public void setWorkerThread(Thread workerThread) { 
            ExternalPKIService.logger.info("TableEntry: Setting Worker thread"); 
            this.workerThread = workerThread; 
        } 
 
        public UUID getUUIDTransactionId() { 
            return transactionId; 
        } 
         
        public void WaitServiceThread() throws InterruptedException { 
            ExternalPKIService.logger.info("TableEntry: Sending current(service) thread to wait"); 
            if(Thread.currentThread().equals(workerThread)) { 
                ExternalPKIService.logger.info("TableEntry: Trying to cause service thread to wait while in worker thread, Failed"); 
            } else { 
                serviceSynPrim.Wait(); 
            } 
        } 
        public void WaitWorkerThread() throws InterruptedException { 
            ExternalPKIService.logger.info("TableEntry: Sending Worker thread to wait"); 
            if(Thread.currentThread().equals(workerThread)) { 
                workerSynPrim.Wait(); //Current thread should wait on the object workerThread 
            } else { 
                ExternalPKIService.logger.info("TableEntry: Trying to cause worker thread to wait while in service thread, Failed"); 
            } 
        } 
        public void NotifyWorkerThread() { 
            ExternalPKIService.logger.info("TableEntry: Notifying Worker thread"); 
            if(Thread.currentThread().equals(workerThread)) { 
                ExternalPKIService.logger.info("TableEntry: Trying to notify worker thread while in worker thread, Failed"); 
            } else { 
                workerSynPrim.Notify(); //current thread notifies anyone waiting on WorkerThread object 
            } 
        } 
        public void NotifyServiceThread() { 
            ExternalPKIService.logger.info("TableEntry: Notifying service thread"); 
 
            if(Thread.currentThread().equals(workerThread)) { 
                serviceSynPrim.Notify(); 
            } else { 
                ExternalPKIService.logger.info("TableEntry: Trying to notify service thread while in service thread, Failed"); 
            } 
 
        } 
    } 
     
    synchronized static void addThrToTable(TableEntry te) { 
        if(tableOfThreads!=null) 
            tableOfThreads.put(te.getUUIDTransactionId(), te); 
    } 
    synchronized static TableEntry getRefFromTable(TableEntry te) { 
        if(tableOfThreads!=null) 
            te = tableOfThreads.get(te.getUUIDTransactionId()); 
        return te; 
    } 
     
    TableEntry updateSignedContentInTableAndReturnThread(HashWithReference href, byte[] signedContent){ 
        TableEntry te = tableOfThreads.get(UUID.fromString(href.getReference())); 
        te.setSignedContent(signedContent); 
        return te; 
    } 
     
    public HashWithReference getHash(Document inPDFDoc, String signatureFieldName, 
            Credential credential, HashAlgorithm hashAlgorithm, String reason, 
            String location, String contactInfo, 
            PDFSignatureAppearanceOptionSpec appearanceOptionSpec, 
            Boolean embedRevocationInfo, OCSPOptionSpec ocspOptionSpec, 
            CRLOptionSpec crlOptionSpec, TSPOptionSpec tspOptionSpec, Integer expectedSize) 
            throws SigningException { 
        String hash = null; 
        String reference = "this is reference"; 
        String state = "InGetHash"; 
         
        TableEntry te = new TableEntry(inPDFDoc, expectedSize.intValue()); 
        getHashRun ghr = new getHashRun(inPDFDoc, signatureFieldName, 
                credential, hashAlgorithm, reason, location, contactInfo, 
                appearanceOptionSpec, embedRevocationInfo, ocspOptionSpec, 
                crlOptionSpec, tspOptionSpec, te); 
         
        Thread thr = new Thread(ghr); 
        te.setWorkerThread(thr); 
        addThrToTable(te); 
         
        thr.start(); 
        while(te.getHash() == null && te.getException()== null){ 
            try { 
                te.WaitServiceThread(); //monitor is acquired in addthrToTable call 
                }catch (InterruptedException iterExcept) { 
                logger.warning("InterruptedException in Service thread wait2, continuing in wait:"+iterExcept.getMessage()); 
            } 
        } 
        if (te.getException()!= null) 
            throw new SigningException(te.getException().getMessage(), te.getException()); 
         
        HashWithReference href = new HashWithReference(te.getHash(), te.getTransactionId()); 
        return href; 
    } 
     
     
    public Document embedSignedContent (HashWithReference href, byte[] signedContent) { 
        TableEntry te = updateSignedContentInTableAndReturnThread(href, signedContent); 
        te.NotifyWorkerThread(); 
        try { 
            te.WaitServiceThread(); 
        }catch (InterruptedException iterExcept) { 
            logger.warning("InterruptedException in Service thread wait3, continuing in wait"); 
        } 
        Document doc = te.getDoc(); 
        return doc; 
    } 
}

Creating the epkiUtil class

The epkiUtil class contains additional application logic that is used to sign or certify a PDF document. For example, this class contain application logic that signs a document and retrieves configuration values. By default, the configuration file named server.orig.properties is located in the following LiveCycle SDK sample directory: [ LiveCycle Install Directory ]\Adobe\Adobe_Experience_Manager_forms\skd\samples\Signatures-spi\conf\server.orig.properties

The following code represents the epkiUtil class.

package com.adobe.livecycle.externalpkiprovider.util; 
 
import java.io.ByteArrayInputStream; 
import java.io.ByteArrayOutputStream; 
import java.io.FileInputStream; 
import java.io.FileNotFoundException; 
import java.io.IOException; 
import java.io.InputStream; 
import java.io.ObjectInputStream; 
import java.io.PrintStream; 
 
import java.io.UnsupportedEncodingException; 
import java.security.AccessController; 
import java.security.InvalidAlgorithmParameterException; 
import java.security.KeyFactory; 
import java.security.KeyStore; 
import java.security.KeyStoreException; 
import java.security.NoSuchAlgorithmException; 
import java.security.NoSuchProviderException; 
import java.security.PrivateKey; 
import java.security.Provider; 
import java.security.Security; 
import java.security.SecureRandom; 
 
import java.security.UnrecoverableKeyException; 
import java.security.cert.CertStore; 
import java.security.cert.Certificate; 
import java.security.cert.CertStoreException; 
import java.security.cert.CertificateException; 
import java.security.cert.CertificateFactory; 
import java.security.cert.CollectionCertStoreParameters; 
import java.security.cert.X509Certificate; 
import java.security.cert.X509CRL; 
import java.security.spec.InvalidKeySpecException; 
import java.security.spec.PKCS8EncodedKeySpec; 
import java.util.ArrayList; 
import java.util.Enumeration; 
import java.util.HashMap; 
import java.util.Hashtable; 
import java.util.Iterator; 
import java.util.List; 
import java.util.Map; 
import java.util.Properties; 
 
import org.apache.commons.codec.binary.Base64; 
 
import org.bouncycastle.asn1.ASN1Object; 
import org.bouncycastle.asn1.ASN1Set; 
import org.bouncycastle.asn1.DERObjectIdentifier; 
import org.bouncycastle.asn1.cms.Attribute; 
import org.bouncycastle.asn1.cms.AttributeTable; 
import org.bouncycastle.asn1.cms.CMSAttributes; 
import org.bouncycastle.cms.CMSProcessable; 
import org.bouncycastle.cms.CMSProcessableByteArray; 
import org.bouncycastle.cms.CMSSignedData; 
import org.bouncycastle.cms.CMSSignedDataGenerator; 
import org.bouncycastle.cms.CMSException; 
import org.bouncycastle.cms.SignerInformation; 
import org.bouncycastle.cms.SignerInformationStore; 
import org.bouncycastle.jce.provider.BouncyCastleProvider; 
import com.rsa.jsafe.provider.JsafeJCE; 
 
import org.ietf.jgss.Oid; 
import org.ietf.jgss.GSSException; 
 
import com.adobe.idp.dsc.InvocationRequest; 
import com.adobe.idp.dsc.InvocationResponse; 
import com.adobe.idp.dsc.clientsdk.ServiceClient; 
import com.adobe.idp.dsc.clientsdk.ServiceClientFactory; 
 
import com.adobe.livecycle.externalpkiprovider.util.FileWriteAction; 
 
import com.adobe.livecycle.externalpkiprovider.util.FileReadAction; 
import com.adobe.livecycle.signatures.client.spi.types.SignRequest; 
import com.adobe.livecycle.signatures.client.spi.types.exceptions.SigningException; 
import com.adobe.livecycle.signatures.pki.client.types.common.HashAlgorithm; 
import com.adobe.logging.AdobeLogger; 
import com.rsa.asn1.ASN1; 
import com.rsa.asn1.ASN1Container; 
import com.rsa.asn1.ASN_Exception; 
import com.rsa.asn1.EndContainer; 
import com.rsa.asn1.OIDContainer; 
import com.rsa.asn1.OIDList; 
import com.rsa.asn1.OctetStringContainer; 
import com.rsa.asn1.SetContainer; 
 
public class epkiUtil { 
    private AdobeLogger logger = AdobeLogger.getAdobeLogger(getClass()); 
     
    public static final int PKCS7_DETACHED = 1; 
    public static final int PKCS7_DATA = 2; 
    public static final int XML_SIGNATURE = 3; 
    public static final int PKCS1_DATA = 4; 
     
    protected String mNonFIPsProviderName; 
    protected Provider mNonFIPsProvider; 
    protected String mFIPsProviderName; 
    protected Provider mFIPsProvider; 
    protected String mProviderName; 
    protected Provider mProvider; 
     
    boolean mIsFIPsMode = false; 
     
    protected String m_userHome; 
    protected String m_fileSep; 
     
    protected InvocationRequest m_request; 
    protected InvocationResponse m_response; 
    protected ServiceClientFactory m_serviceFactory; 
    protected ServiceClient m_serviceClient; 
     
    protected static SecureRandom randomGen = null; 
     
    protected String mPDFName; 
    protected Properties m_keyProps; 
    protected String m_baseDir; 
    protected String mSignedContent; 
    protected boolean mDone=false; 
    protected boolean mIsApplet=false; 
    protected String mP12File; 
     
    private void logException(Exception e) { 
        ByteArrayOutputStream bas = new ByteArrayOutputStream(); 
        PrintStream pst = new PrintStream(bas); 
        e.printStackTrace(pst); 
        logger.warning(e.getMessage() + "\n" + bas.toString()); 
    } 
 
    public epkiUtil(String p12File, boolean isFipsMode, boolean isApplet) { 
     
        m_fileSep = System.getProperty("file.separator"); 
        m_userHome = System.getProperty("user.home"); 
        mP12File = p12File; 
        mIsFIPsMode = isFipsMode; 
        mNonFIPsProviderName = "BC"; 
        mIsApplet = isApplet; 
 
        mNonFIPsProvider = Security.getProvider(mNonFIPsProviderName); 
        if (mNonFIPsProvider == null) { 
            mNonFIPsProvider = new BouncyCastleProvider(); 
            Security.addProvider(mNonFIPsProvider); 
        } 
         
        mFIPsProviderName = "JSafeJCE"; 
        mFIPsProvider = Security.getProvider(mFIPsProviderName); 
        if (mFIPsProvider == null) { 
            mFIPsProvider = new JsafeJCE(); 
            Security.addProvider(mFIPsProvider); 
        } 
 
        if (randomGen == null) { 
            try { 
                randomGen = SecureRandom.getInstance("FIPS186PRNG", mFIPsProvider); 
                randomGen.setSeed(randomGen.generateSeed(256)); 
            } catch (NoSuchAlgorithmException e) { 
                logException(e); 
            } 
        } 
 
        m_keyProps = new Properties(); 
        if(mIsFIPsMode) { 
         mProviderName = mFIPsProviderName; 
         mProvider = mFIPsProvider; 
        } else { 
         mProviderName = mNonFIPsProviderName; 
         mProvider = mNonFIPsProvider; 
        } 
        initializeKeyCache(); 
    } 
     
    public static int getSignatureTypeFromString(String stype){ 
        int ret = 0; 
        if ("PKCS7 Detached".equalsIgnoreCase(stype)) { 
            ret = PKCS7_DETACHED; 
        } else if ("PKCS7 Signed Data".equalsIgnoreCase(stype)) { 
            ret = PKCS7_DATA; 
        } else if ("XML Signature".equalsIgnoreCase(stype)){ 
            ret = XML_SIGNATURE; 
        } else if ("PKCS1 Signed Data".equalsIgnoreCase(stype)){ 
            ret = PKCS1_DATA; 
        } 
        return ret; 
    } 
     
    public void setIsFipsMode(boolean isFipsMode) { 
        mIsFIPsMode = isFipsMode; 
        if(mIsFIPsMode) { 
         mProviderName = mFIPsProviderName; 
         mProvider = mFIPsProvider; 
        } else { 
         mProviderName = mNonFIPsProviderName; 
         mProvider = mNonFIPsProvider; 
        } 
    } 
 
    public boolean getIsFipsMode() { 
        return mIsFIPsMode; 
    } 
     
    public PrivateKey getPrivateKeyFromCache(byte[] certBytes) throws SigningException{ 
        try { 
            X509Certificate cert = loadCertificate(certBytes); 
            return getPrivateKeyFromCache(cert); 
        } catch (Exception ure) { 
         throw new SigningException("Unable to find desired key:" + ure.getMessage()); 
        } 
    } 
     
    public PrivateKey getPrivateKeyFromCache(X509Certificate cert) throws SigningException{ 
        try { 
            String certSerialNIssuer=cert.getIssuerDN().toString() + cert.getSerialNumber().toString(10); 
            PrivateKey privKey = (PrivateKey)m_keyProps.get(certSerialNIssuer); 
            return privKey; 
        } catch (Exception ure) { 
         throw new SigningException("Unable to find desired key:" + ure.getMessage()); 
        } 
    } 
     
    public String signBase64(String hashStr, String base64Cert, int sType, 
            HashAlgorithm hashAlgo, HashMap<String, byte[]> authAttr, 
            HashMap<String, byte[]> unauthAttr) throws SigningException { 
        String retVal="0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345670123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678"; 
        logger.warning("Logger Initialized"); 
         
        byte[] certBytes = Base64.decodeBase64(base64Cert.getBytes()); 
        byte[] hashBytes = Base64.decodeBase64(hashStr.getBytes()); 
        try { 
                byte[] pkcs7Bytes = signRawBytes(hashBytes, certBytes, sType, hashAlgo, authAttr, unauthAttr, 
                        null); 
                retVal = new String(Base64.encodeBase64(pkcs7Bytes), "UTF8"); 
                logger.info("Bytes of signature is " + retVal); 
        } catch (UnsupportedEncodingException se){ 
            logException(se); 
        } 
        return retVal; 
    } 
     
    public byte[] signRawBytes(byte[] hashBytes, byte[] certBytes, int sType, HashAlgorithm hashAlgo, HashMap<String, byte[]> authAttr, 
         HashMap<String, byte[]> unAuthAttr, HashMap<String, byte[]> spiProperties) throws SigningException{ 
     
        if (hashAlgo == null || hashBytes == null ) 
            throw new SigningException("hashalgorithm or hashBytes are null"); 
         
        try { 
            ByteArrayInputStream inStream = new ByteArrayInputStream(certBytes); 
            CertificateFactory cf = CertificateFactory.getInstance("X.509"); 
            X509Certificate cert = (X509Certificate)cf.generateCertificate(inStream); 
            inStream.close(); 
             
            PrivateKey key = getPrivateKeyFromCache(cert); 
            CertStore certsAndCRLs = null; 
            boolean isDetached = false; 
             
            String digestOID = new Oid(ASN1.derEncode(new ASN1Container[] { new OIDContainer( 
                    ASN1.NO_SPECIAL, true, ASN1.NO_TAG, hashAlgo.toString(), 
                    OIDList.DIGEST_OID) })).toString(); 
     
             
            if(spiProperties != null && spiProperties.get(new String("Certs"))!= null) 
                certsAndCRLs = getCertsAndCRLs(spiProperties); 
            else 
                certsAndCRLs = getCertsAndCRLs(cert); 
     
            if (sType == PKCS7_DATA) { 
                // Data cannot be digested if we have to embed it in the signature 
                CMSProcessable content = null; 
                if (hashBytes != null) 
                    content = new CMSProcessableByteArray(hashBytes); 
     
                CMSSignedData  cmsData = createSignedData(key, certsAndCRLs, null, cert, 
                        digestOID, getTableFromMap(authAttr), getTableFromMap(unAuthAttr), content, isDetached, mProviderName); 
     
                return cmsData.getEncoded(); 
            } else if (sType == PKCS7_DETACHED) { 
                isDetached = true; 
                if (authAttr == null) 
                    authAttr = new HashMap<String, byte[]>(); 
                addDigestToAttr(hashBytes, authAttr); 
                hashBytes = null; 
                CMSProcessable content = null; 
                if (hashBytes != null) 
                    content = new CMSProcessableByteArray(hashBytes); 
                CMSSignedData cmsData = createSignedData(key, certsAndCRLs, null, cert, 
                        digestOID, getTableFromMap(authAttr), getTableFromMap(unAuthAttr), content, isDetached, mProviderName); 
                return cmsData.getEncoded(); 
            } 
        } catch (CertificateException ce) { 
            logException(ce); 
        } catch (IOException ioe) { 
            logException(ioe); 
        } catch (ASN_Exception asse) { 
            logException(asse); 
        } catch (GSSException gsse) { 
            logException(gsse); 
        } 
        return null; 
    } 
     
    public String getProviderName() { 
        return mProviderName; 
    } 
     
    public byte[] sign(PrivateKey key, String digestAlg, byte[] data, 
            X509Certificate signerCert, CertStore inCertsAndCRLs, SignatureAttributes unAuthAttrGenerator,  
            Map<String, byte[]> authAttr, String contentOID, boolean isDetached) 
            throws  SigningException{ 
         
        byte[] signature = null; 
        try { 
            CMSProcessable content = null; 
            if (data != null) 
                content = new CMSProcessableByteArray(data); 
            CMSSignedData signedData = null; 
            String digestOID = new Oid(ASN1.derEncode(new ASN1Container[] { new OIDContainer( 
ASN1.NO_SPECIAL, true, ASN1.NO_TAG, digestAlg, 
OIDList.DIGEST_OID) })).toString(); 
             
            CertStore certsAndCRLs = trimCertificates(signerCert, inCertsAndCRLs); 
            AttributeTable authAttrTable = getTableFromMap(authAttr); 
            AttributeTable unAuthAttrGenTable = unAuthAttrGenerator != null  
                                                ? getTableFromMap(unAuthAttrGenerator.getDummyAttributes()) 
                                                : null; 
             
            signedData = (CMSSignedData)createSignedData(key, certsAndCRLs ,contentOID , signerCert, 
                    digestOID,authAttrTable,unAuthAttrGenTable,content,isDetached, mProviderName); 
             
            if (unAuthAttrGenerator != null) { 
                List<SignerInformation> list = new ArrayList<SignerInformation>(); 
                for (Iterator<SignerInformation> it = signedData 
                        .getSignerInfos().getSigners().iterator(); it.hasNext();) { 
                    SignerInformation info = it.next(); 
                    list 
                            .add(SignerInformation.replaceUnsignedAttributes( 
                                    info, getTableFromMap(unAuthAttrGenerator 
                                            .getActualAttributes(info 
                                                    .getSignature())))); 
                    // We know that we have only one signer, so we break the 
                    // first time 
                    break; 
                } 
 
                signedData = CMSSignedData.replaceSigners(signedData, 
                        new SignerInformationStore(list)); 
            } 
            signature = signedData.getEncoded(); 
        } catch (Exception e) { 
            throw new SigningException("PKCS7 data", e); 
        } 
         
        return signature; 
    } 
 
    protected CertStore getCertsAndCRLs(X509Certificate cert) { 
        try { 
                ArrayList<Certificate> list = new ArrayList<Certificate>(); 
                list.add(cert); 
                return CertStore.getInstance("Collection", new CollectionCertStoreParameters(list), mProviderName); 
        } catch(NoSuchProviderException e){ 
            logger.warning("populateCertsAndCRLs:NoSuchProviderException" + e.getMessage()); 
        } catch(InvalidAlgorithmParameterException e){ 
            logger.warning("populateCertsAndCRLs:InvalidAlgorithmParameterException" + e.getMessage()); 
        } catch (NoSuchAlgorithmException e) { 
            logger.warning("populateCertsAndCRLs:NoSuchAlgorithmException" + e.getMessage()); 
        } 
        return null; 
    } 
 
    protected CertStore getCertsAndCRLs(HashMap<String, byte[]> spiProperties) { 
        ArrayList<java.lang.Object> cout = new ArrayList<java.lang.Object>(); 
         
        byte[] certsByte = spiProperties.get(new String("Certs")); 
        byte[] crlsByte = spiProperties.get(new String("CRLs")); 
         
        try { 
            if(certsByte != null) { 
                ByteArrayInputStream certStream = new  ByteArrayInputStream(certsByte); 
                ObjectInputStream certObjectStream = new ObjectInputStream(certStream); 
                ArrayList<X509Certificate> certs = (ArrayList<X509Certificate>) certObjectStream.readObject(); 
                for(X509Certificate crtInfo: certs){ 
                    cout.add(crtInfo); 
                } 
            } 
             
            if(crlsByte != null) { 
                ByteArrayInputStream crlStream = new ByteArrayInputStream(crlsByte); 
                ObjectInputStream crlObjectStream = new ObjectInputStream(crlStream); 
                ArrayList<X509CRL> crls = (ArrayList<X509CRL>) crlObjectStream.readObject(); 
                for(java.security.cert.X509CRL crlrevInfo: crls){ 
                    cout.add(crlrevInfo); 
                } 
            } 
             
            CertStore cst = CertStore.getInstance("Collection", new CollectionCertStoreParameters(cout), 
                    mProviderName); 
            return cst; 
        } catch (IOException ioe) { 
            logException (ioe); 
        } catch (InvalidAlgorithmParameterException iape) { 
            logException (iape); 
        } catch(ClassNotFoundException cnfe) { 
            logException (cnfe); 
        } catch (NoSuchAlgorithmException nsae) { 
            logException (nsae); 
        } catch (NoSuchProviderException nspe) { 
            logException (nspe); 
        } 
        return null; 
         
    } 
 
protected Properties loadConf(boolean isApplet){ 
        Properties prop = new Properties(); 
        try { 
            //Load configuration values from server.orig.properties 
            InputStream is  = new FileInputStream("C:\Adobe\Adobe_Experience_Manager_forms\sdk\samples\Signatures-spi\\conf\server.orig.properties"); 
            prop.load(is); 
         return prop; 
     
           } catch (Exception e) { 
         logger.warning("Exception loading properties:" + e.getMessage()); 
           } 
           return null; 
      } 
 
    protected void initializeKeyCache(){ 
        int i=0; 
        Properties prop = loadConf(mIsApplet); 
     
        m_baseDir = prop.getProperty("BaseDir", "c:\\serverCredentials"); 
        String keyPreFix = prop.getProperty("KeyPreFix", "privKey"); 
        String certPreFix = prop.getProperty("CertPreFix", "pubCert"); 
        String minIndexStr = prop.getProperty("MinIndex", "1"); 
        String maxIndexStr = prop.getProperty("MaxIndex", "1"); 
        String keyExt = prop.getProperty("KeyExt", ".der"); 
        String certExt = prop.getProperty("CertExt", ".der"); 
         
        int minIndex = new Integer(minIndexStr); 
        int maxIndex = new Integer(maxIndexStr); 
         
        String keyBase = m_baseDir + "\\" + keyPreFix; 
        String certBase = m_baseDir + "\\" + certPreFix; 
        PrivateKey privKey = null; 
        X509Certificate  cert = null; 
         
        for(i=minIndex;i<=maxIndex;i++) { 
            String keyFileName = keyBase + String.valueOf(i) + keyExt; 
            String certFileName = certBase + String.valueOf(i) + certExt; 
            try { 
                privKey = getPrivateKey(keyFileName); 
                cert = loadCertificate(getFileAsBytes(certFileName)); 
                String certSerialNIssuer=cert.getIssuerDN().toString() + cert.getSerialNumber().toString(10); 
                m_keyProps.put(certSerialNIssuer, privKey); 
            } catch (FileNotFoundException fnfe) { 
                logger.warning("File Not Found either " + keyFileName + " or " + certFileName); 
                logger.warning(fnfe.getMessage()); 
            } catch (IOException ioe) { 
                logger.warning("File format not correct " + keyFileName + " or " + certFileName); 
                logger.warning(ioe.getMessage()); 
            } catch (NoSuchAlgorithmException nsae) { 
                logger.warning(nsae.getMessage()); 
            } catch (InvalidKeySpecException ikse) { 
                logger.warning("File format not correct " + keyFileName); 
                logger.warning(ikse.getMessage()); 
            } catch (CertificateException ce) { 
                logger.warning("File format not correct " + certFileName); 
                logger.warning(ce.getMessage()); 
            } 
        } 
    } 
 
    protected byte[] getFileAsBytes(String fileName){ 
        byte[] retVal = null; 
        retVal = (byte[])AccessController.doPrivileged(new FileReadAction(fileName)); 
        logger.info("getFileAsBytes:read file "+ fileName + ",size = " + retVal.length); 
        return retVal; 
    } 
 
    protected void writeToFile(byte[] b, String s){ 
        Boolean retVal = (Boolean)AccessController.doPrivileged(new FileWriteAction(s, b)); 
    } 
 
    protected PrivateKey getPrivateKey(String keyFileName) throws FileNotFoundException, IOException, NoSuchAlgorithmException, InvalidKeySpecException { 
        byte encodeKeyBytes[] = getFileAsBytes(keyFileName); 
        PKCS8EncodedKeySpec pkcs8enckey = new PKCS8EncodedKeySpec(encodeKeyBytes); 
         
        KeyFactory kf = KeyFactory.getInstance("RSA"); 
        PrivateKey privKey = kf.generatePrivate(pkcs8enckey); 
        return privKey; 
    } 
     
    protected PrivateKey getPrivateKeyFromP12Store(X509Certificate cert, String password) { 
        try { 
            Provider myProvider = new org.bouncycastle.jce.provider.BouncyCastleProvider(); 
            Security.addProvider(myProvider); 
            logger.info("Added provider"); 
            KeyStore ks = KeyStore.getInstance("PKCS12"); 
             
            ByteArrayInputStream bis = new ByteArrayInputStream(getFileAsBytes(m_userHome + m_fileSep + "test_credentials" + m_fileSep + mP12File)); 
             
            logger.info("loading keystore, available bytes are:" + bis.available()); 
            ks.load(bis, password.toCharArray()); 
            String alias = ks.getCertificateAlias(cert); 
            logger.info("Found alias in keystore:" + alias); 
            PrivateKey ke =(PrivateKey) ks.getKey(alias, password.toCharArray()); 
             
            if(ke == null) { 
                logger.warning("Null Key returned for alias" + alias); 
                Enumeration e = ks.aliases(); 
                int i=0; 
                for ( ; e.hasMoreElements() ;) { 
                    String elem = (String) e.nextElement(); 
                    logger.info(elem); 
                    if(ks.isCertificateEntry(elem)){ 
                 Certificate cert1 = ks.getCertificate(elem); 
                 logger.info("writing to file" + elem); 
                 writeToFile(cert1.getEncoded(), "c:\\ltmp\certs" + i + ".cer"); 
                 i++; 
                    } 
                 } 
            } else { 
                logger.info("PrivateKey is:" + ke.toString()); 
            } 
            return ke; 
        } catch (KeyStoreException kse){ 
            logger.warning("KeyStoreException:"+ kse.getMessage()); 
        //} catch (NoSuchProviderException nspe){ 
        //    logger.warning("NoSuchProviderException:"+ nspe.getMessage()); 
        } catch (NoSuchAlgorithmException nsa) { 
            logger.warning("NoSuchAlgorithmException:"+ nsa.getMessage()); 
        } catch (UnrecoverableKeyException urke) { 
            logger.warning("UnrecoverableKeyException:"+ urke.getMessage()); 
        } catch (IOException ioe) { 
            logger.warning("IOException:"+ ioe.getMessage()); 
        } catch (CertificateException ce) { 
            logger.warning("CertificateException:"+ ce.getMessage()); 
        } 
        return null; 
    } 
 
    private X509Certificate loadCertificate(String certFile) throws FileNotFoundException, CertificateException, IOException { 
        InputStream inStream = new FileInputStream(certFile); 
        CertificateFactory cf = CertificateFactory.getInstance("X.509"); 
        X509Certificate cert = (X509Certificate)cf.generateCertificate(inStream); 
        inStream.close(); 
        return cert; 
    } 
    private X509Certificate loadCertificate(byte[] certBytes) throws CertificateException, IOException { 
        InputStream inStream = new ByteArrayInputStream(certBytes); 
        CertificateFactory cf = CertificateFactory.getInstance("X.509"); 
        X509Certificate cert = (X509Certificate)cf.generateCertificate(inStream); 
        inStream.close(); 
        return cert; 
    } 
 
     
    private PrivateKey getPrivKeyFromSignRequest(SignRequest sreq) throws SigningException { 
        try { 
            X509Certificate cert = loadCertificate(sreq.getCredentialIdentifier().getX509Data().getX509Certificate()); 
            String certSerialNIssuer=cert.getIssuerDN().toString() + cert.getSerialNumber().toString(10); 
            PrivateKey privKey = (PrivateKey)m_keyProps.get(certSerialNIssuer); 
            return privKey; 
         } catch (Exception ure) { 
             throw new SigningException("Unable to find desired key:" + ure.getMessage()); 
         } 
    } 
     
     
    protected CMSSignedData createSignedData(PrivateKey key, CertStore certsAndCRLs,String contentOID, X509Certificate cert, 
            String digestOID, AttributeTable authAttrTable, AttributeTable unAuthAttrGenTable,  
            CMSProcessable content, boolean isDetached, String providerName) { 
        try { 
            CMSSignedDataGenerator signer = new CMSSignedDataGenerator(); 
            signer.addCertificatesAndCRLs(certsAndCRLs); 
            signer.addSigner(key, cert, digestOID, authAttrTable, unAuthAttrGenTable); 
     
            if (contentOID != null) 
                return signer.generate(contentOID,content, !isDetached, providerName); 
            else 
                return signer.generate(content, !isDetached, providerName); 
        }catch (CertStoreException cse) { 
                logException(cse); 
        } catch (CMSException cmse) { 
            logException(cmse); 
        } catch (NoSuchProviderException nspe) { 
            logException(nspe); 
        } catch (NoSuchAlgorithmException nsae) { 
            logException(nsae); 
        } 
        return null; 
    } 
     
    private CertStore trimCertificates(X509Certificate signerCert, CertStore store) { 
        try { 
            List<Object> certsAndCRLs = new ArrayList<Object>(); 
            for (Iterator it = store.getCertificates(null).iterator(); it.hasNext();) { 
                X509Certificate cert = (X509Certificate)it.next(); 
                if (!cert.equals(signerCert)  
                        && cert.getIssuerX500Principal().equals(signerCert.getIssuerX500Principal()) 
                        && cert.getSerialNumber().equals(signerCert.getSerialNumber())) 
                    continue; 
                certsAndCRLs.add(cert); 
            } 
            certsAndCRLs.addAll(store.getCRLs(null)); 
            return CertStore.getInstance("Collection", new CollectionCertStoreParameters(certsAndCRLs), mProviderName); 
        } catch (CertStoreException e) { 
            logException(e); 
        } catch (InvalidAlgorithmParameterException e) { 
            logException(e); 
        } catch (NoSuchAlgorithmException e) { 
            logException(e); 
        } catch (NoSuchProviderException nspe) { 
            logException(nspe); 
        } 
        return null; 
    }     
 
    private AttributeTable getTableFromMap(Map<String, byte[]> map) { 
        if (map == null) 
            return null; 
        Hashtable<DERObjectIdentifier, Attribute> table = new Hashtable<DERObjectIdentifier, Attribute>(); 
        for (Iterator<String> it = map.keySet().iterator(); it.hasNext();) { 
            String oidString = it.next(); 
            try { 
                DERObjectIdentifier oid = new DERObjectIdentifier(oidString); 
                Attribute attr = new Attribute(oid, ASN1Set 
                        .getInstance(ASN1Object.fromByteArray(map 
                                .get(oidString)))); 
                table.put(oid, attr); 
            } catch (IOException e) { 
            } 
        } 
        return new AttributeTable(table); 
    } 
     
    private void addDigestToAttr(byte[] data, Map<String, byte[]> authAttrs) 
        throws SigningException { 
    try { 
        OctetStringContainer encCont = new OctetStringContainer( 
                ASN1.NO_SPECIAL, true, ASN1.NO_TAG, data, 0, data.length); 
        authAttrs.put(CMSAttributes.messageDigest.getId(), 
                ASN1.derEncode(new ASN1Container[] { 
                                new SetContainer(ASN1.NO_SPECIAL, true, 
                                        ASN1.NO_TAG), encCont, 
                                new EndContainer() })); 
        } catch (ASN_Exception e) { 
            throw new SigningException("Unable to encode digest in a set", e); 
        } 
    } 
}

Creating the FileReadAction class

The FileReadAction class reads data from a specified file. The following code represents the FileReadAction class.

package com.adobe.livecycle.externalpkiprovider.util; 
 
import java.io.FileInputStream; 
import java.io.FileNotFoundException; 
import java.io.IOException; 
import java.security.PrivilegedAction; 
 
public class FileReadAction implements PrivilegedAction<byte[]> { 
    String mFileName; 
    public FileReadAction(String fileName) { 
        mFileName = fileName; 
    } 
    public byte[] run()  { 
        try { 
            FileInputStream fi = new FileInputStream(mFileName); 
            byte[] fileBytes = new byte[fi.available()]; 
            fi.read(fileBytes); 
            return fileBytes; 
        } catch(FileNotFoundException fnfe){ 
            System.out.println("FileNotFoundException:" + fnfe.getMessage()); 
        } catch(IOException ioe){ 
            System.out.println("IOException:" + ioe.getMessage()); 
        } 
        return null; 
    } 
}

Creating the FileWriteAction class

The FileWriteAction class writes data to the specified file. The following code represents the FileWriteAction class.

package com.adobe.livecycle.externalpkiprovider.util; 
 
import java.io.FileNotFoundException; 
import java.io.FileOutputStream; 
import java.io.IOException; 
import java.security.PrivilegedAction; 
 
public class FileWriteAction implements PrivilegedAction<Boolean> { 
    String mFileName; 
    byte[] mBytes; 
    public FileWriteAction(String fileName, byte[] b) { 
        mFileName = fileName; 
        mBytes = b; 
    } 
    public Boolean run()  { 
        try { 
            FileOutputStream fi = new FileOutputStream(mFileName); 
            fi.write(mBytes); 
            return true; 
        } catch(FileNotFoundException fnfe){ 
            System.out.println("FileNotFoundException:" + fnfe.getMessage()); 
        } catch(IOException ioe){ 
            System.out.println("IOException:" + ioe.getMessage()); 
        } 
        return false; 
    } 
}

Creating the SignatureAttributes class

The following code represents the SignatureAttributes class. This class defines additional attributes for the signature, such as the timestamp.

package com.adobe.livecycle.externalpkiprovider.util; 
 
import com.rsa.asn1.ASN1; 
import com.rsa.asn1.ASN1Container; 
import com.rsa.asn1.ASN_Exception; 
import com.rsa.asn1.EncodedContainer; 
import com.rsa.asn1.EndContainer; 
import com.rsa.asn1.SetContainer; 
 
import java.util.HashMap; 
import java.util.Map; 
 
 
public class SignatureAttributes  { 
    protected HashMap<String, byte[]> mAtribs; 
    public SignatureAttributes(HashMap<String, byte[]> atribs) { 
        mAtribs = atribs ; 
    } 
    public Map<String, byte[]> getDummyAttributes() throws Exception { 
        return mAtribs; 
    } 
    public Map<String, byte[]> getActualAttributes(byte[] signature) throws Exception { 
        String ts = new String ("MIINFgYJKoZIhvcNAQcCoIINBzCCDQMCAQMxCzAJBgUrDgMCGgUAMIIBDAYLKoZIhvcNAQkQAQSg" + 
                "gfwEgfkwgfYCAQEGAikCMCEwCQYFKw4DAhoFAAQUyQY4k242wSJYfz69SjkHvZ2hLXACAwbpeRgP" + 
                "MjAwODA0MTQxMTUwMjRaMAMCATwBAf8CECVlFf8wEtrWL5qZ0MuKOeCggZmkgZYwgZMxCzAJBgNV" + 
                "BAYTAklOMQswCQYDVQQIEwJVUDEOMAwGA1UEBxMFTm9pZGExHDAaBgNVBAoTE0Fkb2JlIFN5c3Rl" + 
                "bXMgSW5kaWExDTALBgNVBAsTBEVEQlUxFjAUBgNVBAMTDURTU1RTQVN2cjc3NzcxIjAgBgkqhkiG" + 
                "9w0BCQEWE2Rzcy1xZS1pbkBhZG9iZS5jb22gggjPMIIEMjCCAxqgAwIBAgIBWDANBgkqhkiG9w0B" + 
                "AQQFADCBjTELMAkGA1UEBhMCSU4xCzAJBgNVBAgTAlVQMQ4wDAYDVQQHEwVOb2lkYTEcMBoGA1UE" + 
                "ChMTQWRvYmUgU3lzdGVtcyBJbmRpYTENMAsGA1UECxMERURCVTEQMA4GA1UEAxMHRFNTVFNDQTEi" + 
                "MCAGCSqGSIb3DQEJARYTZHNzLXFlLWluQGFkb2JlLmNvbTAeFw0wNjA1MzEwNzQ0MjBaFw0yNjA1" + 
                "MjYwNzQ0MjBaMIGTMQswCQYDVQQGEwJJTjELMAkGA1UECBMCVVAxDjAMBgNVBAcTBU5vaWRhMRww" + 
                "GgYDVQQKExNBZG9iZSBTeXN0ZW1zIEluZGlhMQ0wCwYDVQQLEwRFREJVMRYwFAYDVQQDEw1EU1NU" + 
                "U0FTdnI3Nzc3MSIwIAYJKoZIhvcNAQkBFhNkc3MtcWUtaW5AYWRvYmUuY29tMIIBIjANBgkqhkiG" + 
                "9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwFRT9TVfy5GeMs+w7uyTMq6XmZ7dGKTLKc10WGZGfEZEs0Y1" + 
                "4uUAEAGuv+l94IoTubBmbGGjQ3T1+VZ4+TR451sRgm6x07nI+OQLwLyak5ZY+Ou23PkO4sFz3SNi" + 
                "TtYp+Ne5u5vv4P1w0wQWG2aumWYxyyVGaEyVUrgOLogyTYogjYMNI+WKvKftdqSQqw37hNr1DK1y" + 
                "cHaeHW2jcRc7cAvT14/WInY55/++do3WwnEPUD8Sk8QopzqRt60Pwh93XulIA4bczwW62BOlHCX2" + 
                "o+9F4ZmLhziGbTdxJLpUFv1ByBhX4svoKHX09Lsiahe1s3r5AloITDopFOwIO2vs8QIDAQABo4GU" + 
                "MIGRMAkGA1UdEwQCMAAwLAYJYIZIAYb4QgENBB8WHU9wZW5TU0wgR2VuZXJhdGVkIENlcnRpZmlj" + 
                "YXRlMB0GA1UdDgQWBBTf6uUdg4nG9qlpO0nrduDPu1bmkjAfBgNVHSMEGDAWgBRSlq6kjjJBzsZ9" + 
                "S4/+y0hauLhh1TAWBgNVHSUBAf8EDDAKBggrBgEFBQcDCDANBgkqhkiG9w0BAQQFAAOCAQEAaI9M" + 
                "cs1p7FlXgkgYMJgTDYsdsIsNsA2LLnne+hkr8VSW1Sij80DjuV6cLAgziFKZn2mX3iqhLvftvIBv" + 
                "KKGQcSQA3WM16hv3nj/h6vipQ/T3NnxL14HSE2WUplOhAStcuMBHMrtp1EpIIJHIT8kJXAetxJJa" + 
                "Qj9Y2soKPuEWyVto/WVP9Oxy0WnMWBxeB6UM8uYF9gycjl5aHJc+DZTTmwOnN6A6BU1w8bu8B4Nk" + 
                "Kbcop39WfvHtTu9db0JwXszhZtwx15OieG8k6LQIdw7OkTP2SxyrvO6nUyhYEaMBcdGLcz2bJHTk" + 
                "xleA+OCwQ53xCx7WsSXTCU481+xMW7IkSDCCBJUwggN9oAMCAQICCQC32lkcjLdBsDANBgkqhkiG" + 
                "9w0BAQUFADCBjTELMAkGA1UEBhMCSU4xCzAJBgNVBAgTAlVQMQ4wDAYDVQQHEwVOb2lkYTEcMBoG" + 
                "A1UEChMTQWRvYmUgU3lzdGVtcyBJbmRpYTENMAsGA1UECxMERURCVTEQMA4GA1UEAxMHRFNTVFND" + 
                "QTEiMCAGCSqGSIb3DQEJARYTZHNzLXFlLWluQGFkb2JlLmNvbTAeFw0wNjA1MzEwNDEzMzJaFw0y" + 
                "NjA1MjYwNDEzMzJaMIGNMQswCQYDVQQGEwJJTjELMAkGA1UECBMCVVAxDjAMBgNVBAcTBU5vaWRh" + 
                "MRwwGgYDVQQKExNBZG9iZSBTeXN0ZW1zIEluZGlhMQ0wCwYDVQQLEwRFREJVMRAwDgYDVQQDEwdE" + 
                "U1NUU0NBMSIwIAYJKoZIhvcNAQkBFhNkc3MtcWUtaW5AYWRvYmUuY29tMIIBIjANBgkqhkiG9w0B" + 
                "AQEFAAOCAQ8AMIIBCgKCAQEA2dIbzYblBsg+ysmV8ZOgqa5t0PHaOMXDEk08wT2/29f7i5vTOe5c" + 
                "29NP0TcnnbNvr5hunZiVnsAH1X+IwepXfId11Jo/adFcJ16gT9z4T6uOSRx1ftliqilB5ExM9Xc+" + 
                "w/WChCAuXeF88jIKALPUDPdFtLqMnE4KZXjM/rd4+u0BhZdFBA7rsPxmY1k4BIdIuvAy2fpMy1VN" + 
                "TGe82yFiptryKzUAPRo2pP9iqTwLDsfPsC9+Xndsw5XM0dQqBn7N5MGH1tv5wwqIlwSTuaeWF89B" + 
                "F3tU9z+zB0IHX0xYHx18SrI+CyISAdyafBblkfMEfbTqkfjw7fnlcDaBrEAjSQIDAQABo4H1MIHy" + 
                "MB0GA1UdDgQWBBRSlq6kjjJBzsZ9S4/+y0hauLhh1TCBwgYDVR0jBIG6MIG3gBRSlq6kjjJBzsZ9" + 
                "S4/+y0hauLhh1aGBk6SBkDCBjTELMAkGA1UEBhMCSU4xCzAJBgNVBAgTAlVQMQ4wDAYDVQQHEwVO" + 
                "b2lkYTEcMBoGA1UEChMTQWRvYmUgU3lzdGVtcyBJbmRpYTENMAsGA1UECxMERURCVTEQMA4GA1UE" + 
                "AxMHRFNTVFNDQTEiMCAGCSqGSIb3DQEJARYTZHNzLXFlLWluQGFkb2JlLmNvbYIJALfaWRyMt0Gw" + 
                "MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAC9SdZIFYp7au0BpKXkI129qGOPZIpbv" + 
                "fSMTNhOtybIZR/b8ABTxg65PW7DtWoyZSkRotYLj9mQ1vc65EYvFNfAWD94R0R7GIUbKGZn3ylGe" + 
                "JAnsT9J0G3rgTkj8tgCEqn1gAdn0TiIdgKdI9j+o9Q3Ql96XSFAl9uZg6cT0Ud1s5i3rfRAIosZL" + 
                "oABbeMO8790lDmtyDkpQk/DsNh8o9xc4wpQrU092JOg8P9v/Vpj6lIflmjUMGMpvTxO4Megg2SpE" + 
                "Nb+lyvX6jMWEWSvk5EGw4oUOc1qtvlj8+gPJupMLzuB/AdWzOQoXocMvu+l9qBDOSlF4tp3WpaCt" + 
                "zRlbmlIxggMMMIIDCAIBATCBkzCBjTELMAkGA1UEBhMCSU4xCzAJBgNVBAgTAlVQMQ4wDAYDVQQH" + 
                "EwVOb2lkYTEcMBoGA1UEChMTQWRvYmUgU3lzdGVtcyBJbmRpYTENMAsGA1UECxMERURCVTEQMA4G" + 
                "A1UEAxMHRFNTVFNDQTEiMCAGCSqGSIb3DQEJARYTZHNzLXFlLWluQGFkb2JlLmNvbQIBWDAJBgUr" + 
                "DgMCGgUAoIIBTTAaBgkqhkiG9w0BCQMxDQYLKoZIhvcNAQkQAQQwHAYJKoZIhvcNAQkFMQ8XDTA4" + 
                "MDQxNDExNTAyNFowIwYJKoZIhvcNAQkEMRYEFNKEkqfnvWHPaxgJztfx4zhT22yRMIHrBgsqhkiG" + 
                "9w0BCRACDDGB2zCB2DCB1TAWBBSxWcniVHQIOB+p7QWTBLDOTRU6HjCBugQU3wfxuD/9pRkkCzqG" + 
                "v0I2CpsaNrwwgaEwgZOkgZAwgY0xCzAJBgNVBAYTAklOMQswCQYDVQQIEwJVUDEOMAwGA1UEBxMF" + 
                "Tm9pZGExHDAaBgNVBAoTE0Fkb2JlIFN5c3RlbXMgSW5kaWExDTALBgNVBAsTBEVEQlUxEDAOBgNV" + 
                "BAMTB0RTU1RTQ0ExIjAgBgkqhkiG9w0BCQEWE2Rzcy1xZS1pbkBhZG9iZS5jb20CCQC32lkcjLdB" + 
                "sDANBgkqhkiG9w0BAQEFAASCAQCrt/hgJiomcKbVDqr+fgrm87kZLobellTb/ZafJRf1DwkpKNe0" + 
                "JwQ9SuMGZyPNpveKj0r75JBWfO9FuzV7iLYmDXKSvxlIsYj0FdbueZVSfYx3hOY0zScsnPKGPupg" + 
                "kw/Nc/cG82A375sdEdrnKW6a3IZEQnIO+YPrSP7tplQuj1s/r9VWJZm024CxtooCV2q5ySfa2SYe" + 
                "LJ9BPW1duM9zxSRbIsGymfRsjCbJeFuRipKd6fD4ooIumk6DE18p1MTgvXlp8UlKe4oWgWjRfyPU" + 
                "TqGDRlHha/CbDnrjKuhE5BWL402bO+5IyO3b0GH/DCgh6oI5QqqB+DUX0HB5tFcQ"); 
        byte[] timestamp = new sun.misc.BASE64Decoder().decodeBuffer(ts); 
        if(mAtribs == null) 
            mAtribs = new HashMap<String, byte[]>(); 
        mAtribs.put("1.2.840.113549.1.9.16.2.14", wrapDataInSet(timestamp)); 
        return mAtribs; 
    } 
    private static byte[] wrapDataInSet(byte[] data) throws ASN_Exception { 
        EncodedContainer encCont = new EncodedContainer(ASN1.ANY, 
                true, ASN1.NO_SPECIAL, data, 0, 
                data.length); 
        return ASN1.derEncode(new ASN1Container[] { 
                new SetContainer(ASN1.NO_SPECIAL, true, ASN1.NO_TAG), 
                encCont, new EndContainer() }); 
    } 
}

Defining the component XML file for the signature handler

You must define a component XML file to deploy the signature handler, which is a LiveCycle component. A component XML file exists for each component and provides metadata about the component. For more information about the component XML file, see the Component XML Reference .

When creating the component XML file for the signature handler, you can use the import-packages element to specify the packages that must be referenced by the signature handler. The following XML data specifies the import-packages that must be specified within the component XML file.

<import-packages> 
<package>com.adobe.livecycle.signatures.client.spi.types</package>  
<package>com.adobe.livecycle.signatures.client.spi.types.exceptions</package>  
<package>com.adobe.livecycle.signatures.client</package>  
<package>com.adobe.livecycle.signatures.client.types</package>  
<package>com.adobe.livecycle.signatures.client.types.exceptions</package>  
</import-packages>
Note: The Signatures JAR file (adobe-signatures-client.jar) must be referenced in the component XML file’s class-path element in order to avoid classloading errors.

The following component.xml file is used for the signature handler. Notice that the service name is SamplePKIProviderServiceEx and the operation that this service exposes is sign .

Defining the component XML file for the signature handler

<component xmlns="http://adobe.com/idp/dsc/component/document"> 
    <component-id>com.adobe.livecycle.externalpkiprovider</component-id> 
       <version>1.0</version> 
    <class-path>adobe-livecycle-client.jar adobe-usermanager-client.jar adobe-utilities.jar commons-io-1.4.jar xalan.jar xercesImpl.jar log4j.jar commons-fileupload-1.2.jar commons-codec-1.3.jar bcmail-jdk15-135.jar bcprov-jdk15-135.jar jsafeFIPS.jar jsafeJCEFIPS.jar certjFIPS.jar</class-path> 
    <import-packages> 
        <package>com.adobe.livecycle.signatures.client.spi.types</package>  
         <package>com.adobe.livecycle.signatures.client.spi.types.exceptions</package>  
         <package>com.adobe.livecycle.signatures.client</package>  
         <package>com.adobe.livecycle.signatures.client.types</package>  
        <package>com.adobe.livecycle.signatures.client.types.exceptions</package>  
    </import-packages> 
    <services> 
        <service name="SamplePKIProviderServiceEx"> 
            <implementation-class>com.adobe.livecycle.externalpkiprovider.service.impl.ExternalPKIService</implementation-class> 
            <!--*****--> 
            <supported-connectors>default</supported-connectors>     
            <!--**************************************************************--> 
            <operations> 
                <!--AddInvisibleSignature Operation--> 
                <operation name="sign" title="Sign the Hash" method="sign" orchestrateable="true"> 
                    <description>Returns a PKCS7 packet in return of hash provided.</description> 
                        <input-parameter name="SignRequest" type="com.adobe.livecycle.signatures.client.spi.types.SignRequest" title="Sign Request" required="false"> 
                             <description>This structure contains hash which is to be encrypted to create a pkcs7 parameter and all the options to generate the pkcs7 packet</description> 
                        </input-parameter> 
                    <output-parameter name="signResp" 
                        type="com.adobe.livecycle.signatures.client.spi.types.SignResponse" title="Sign Response"> 
                        <description>the data structure containing the result as well as the status of Signing operation</description> 
                    </output-parameter> 
                    <faults> 
                        <fault name = "SigningException" type = "com.adobe.livecycle.signatures.client.spi.types.exceptions.SigningException"/> 
                    </faults>                     
                </operation> 
            </operations> 
        </service> 
    </services> 
</component> 
 

Packaging the signature handler

Before deploying the signature handler to LiveCycle, you must package your Eclipse project into a JAR file. Also, ensure that the component XML file and the external JAR files on which the signature handler’s business logic depends are present. The component.xml file and external JAR files must be located at the root of the JAR file.

The following illustration shows the Eclipse project’s content that is packaged into the signature handler’s JAR file.

View full size graphic
A.
External JAR files required by the component

B.
JAVA file

Package the signature handler into a JAR file named adobe-signature-spi-dsc.jar. In the previous illustration, notice that .JAVA files are listed. After you package the signature handler into a JAR file, you can deploy the component to LiveCycle. (See Deploying your component .)

You can also programmatically deploy a component. (See Programmatically Deploying Components .)

Testing the signature handler

You can test the signature handler by invoking the Signature service’s Sign operation using the Signature service Java API and by defining specific run-time options. The signature handler is not invoked directly by a client application. For information about using the Signature service Java API to digitally sign a PDF document, see Digitally Signing PDF Documents .

Note: You can also invoke the Signature service’s Sign operation by creating a process using Workbench. (See workbench Help .)

To ensure that a signature handler is invoked when signing a PDF document, you set run-time options by using a com.adobe.livecycle.signatures.client.types.Credential object. After you create a Credential object by using its constructor, you specify the following run-time options:

  • The name of the signature handler by invoking the Credential object’s setSpiName method.

  • The type of credential to use by invoking the Credential object’s setCredentialType method. Specify Credential.CERT_NONE_KEY .

  • The location of the credential to use by invoking the Credential object’s setCertificate method. For example, you can reference a credential that is located on the local file system.

Passing additional properties to the signature handler

You can pass optional properties to the signature handler when using the Signature service Java API. By passing these properties, you can influence the signature that is applied to the PDF document. For example, you can embed a timestamp in the signature.

These properties are passed as key value pairs to the signature handler. To pass properties, invoke the com.adobe.livecycle.signatures.client.types.Credential object’s setSPIProperties method and pass a java.util.HashMap object that contains the properties. The following list specifies the properties that you can pass to the signature handler.

  • embedRevocationInfo : A java.lang.Boolean object indicating the value passed by the user in the signing API call.

  • embedTimeStamp : A primitive Boolean indicating whether the signature handler needs to be embedded in the timestamp in the signature.

The following code shows a Credential object being created and the run-time options required to invoke the signature handler being set.

//Create a Credential object based on the SamplePKIProviderServiceEx SPI 
Credential myCred = new Credential(); 
     
//Specify the name of the signature provider 
myCred.setSpiName("SamplePKIProviderServiceEx"); 
 
//Get the credential from the local file system 
FileInputStream credFile = new FileInputStream("C:\Adobe\Adobe_Experience_Manager_forms\sdk\samples\Signatures-spi\\credentials\client\testCert2.der"); 
int len = credFile.available(); 
byte[] credBytes = new byte[len]; 
credFile.read(credBytes); 
myCred.setCertificate(credBytes)  ; 
 
//Set the Credential type 
myCred.setCredentialType(Credential.CERT_NONE_KEY);

The following Java code example digitally signs a PDF document that is based on a PDF file named LoanSig.pdf . The signature handler named SamplePKIProviderServiceEx is used to sign the PDF document. The credential is based on a file named testCert2.der. The signed document is saved as a PDF file named LoanSigned.pdf.

/* 
    * This Java Quick Start uses the following JAR files 
    * 1. adobe-signatures-client.jar 
    * 2. adobe-livecycle-client.jar 
    * 3. adobe-usermanager-client.jar 
    * 4. adobe-utilities.jar 
    * 5. jbossall-client.jar (use a different JAR file if LiveCycle is not deployed 
    * on JBoss) 
    *  
    * These JAR files are located in the following path: 
    * [LiveCycleES root]/sdk/client-libs 
    *  
    * For complete details about the location of these JAR files,  
    * see "Including LiveCycle library files" in Programming  
    * with LiveCycle 
    */ 
import java.util.*; 
import java.io.File; 
import java.io.FileInputStream; 
import com.adobe.livecycle.signatures.client.*; 
import com.adobe.livecycle.signatures.client.types.*; 
import com.adobe.livecycle.signatures.pki.client.types.common.HashAlgorithm; 
import com.adobe.idp.Document; 
import com.adobe.idp.dsc.clientsdk.ServiceClientFactory; 
import com.adobe.idp.dsc.clientsdk.ServiceClientFactoryProperties; 
 
public class SignDocumentSPI { 
 
public static void main(String[] args) { 
         
    try 
    { 
      //Set connection properties required to invoke LiveCycle                                 
      Properties connectionProps = new Properties(); 
      connectionProps.setProperty(ServiceClientFactoryProperties.DSC_DEFAULT_EJB_ENDPOINT, "jnp://localhost:1099"); 
     connectionProps.setProperty(ServiceClientFactoryProperties.DSC_TRANSPORT_PROTOCOL,ServiceClientFactoryProperties.DSC_EJB_PROTOCOL);           
      connectionProps.setProperty(ServiceClientFactoryProperties.DSC_SERVER_TYPE, "JBoss"); 
      connectionProps.setProperty(ServiceClientFactoryProperties.DSC_CREDENTIAL_USERNAME, "administrator"); 
         connectionProps.setProperty(ServiceClientFactoryProperties.DSC_CREDENTIAL_PASSWORD, "password"); 
     
      //Create a ServiceClientFactory instance 
      ServiceClientFactory myFactory = ServiceClientFactory.createInstance(connectionProps); 
 
      //Create a SignatureServiceClient object 
      SignatureServiceClient signClient = new SignatureServiceClient(myFactory); 
         
      //Specify a PDF document to sign 
      FileInputStream fileInputStream = new FileInputStream("C:\\Adobe\LoanSig.pdf");     
      Document credDoc = new Document (fileInputStream); 
     
      //Specify the name of the signature field 
      String fieldName = "SignatureField1"; 
 
      //Create a Credential object based on the SamplePKIProviderServiceEx SPI 
      Credential myCred = new Credential(); 
     
      myCred.setSpiName("SamplePKIProviderServiceEx"); 
     
      //Get the credential from the local file system 
      FileInputStream credFile = new FileInputStream("C:\Adobe\Adobe_Experience_Manager_forms\sdk\samples\Signatures-spi\\credentials\client\testCert2.der"); 
     
      int len = credFile.available(); 
      byte[] credBytes = new byte[len]; 
      credFile.read(credBytes); 
     
      myCred.setCertificate(credBytes)  ; 
      myCred.setCredentialType(Credential.CERT_NONE_KEY); 
     
      //Specify the reason to sign the document 
      String reason = "The document was reviewed"; 
 
      //Specify the location of the signer 
      String location  = "New York HQ";  
 
      //Specify contact information 
      String contactInfo = "Tony Blue"; 
 
      //Create a PDFSignatureAppearanceOptions object  
      //and show date information 
      PDFSignatureAppearanceOptionSpec appear = new  PDFSignatureAppearanceOptionSpec();  
      appear.setShowDate(true); 
      appear.setShowReason(true); 
 
      //Set revocation checking to false 
      java.lang.Boolean revCheck = new Boolean(false); 
     
      //Create objects to pass to the sign method 
           OCSPOptionSpec ocspSpec = new OCSPOptionSpec(); 
           CRLOptionSpec crlSpec = new CRLOptionSpec(); 
           TSPOptionSpec tspSpec = new TSPOptionSpec(); 
 
      //Sign the PDF document 
      Document signedDoc = signClient.sign( 
         credDoc, 
         fieldName, 
         myCred, 
         HashAlgorithm.SHA256, 
         reason, 
         location, 
         contactInfo, 
         appear, 
         revCheck, 
        ocspSpec, 
             crlSpec, 
             tspSpec); 
 
      //Save the signed PDF document 
      File outFile = new File("C:\\Adobe\LoanSigned.pdf"); 
      signedDoc.copyToFile (outFile); 
      } 
     
    catch (Exception ee) 
      { 
        ee.printStackTrace(); 
      } 
      } 
}

// Ethnio survey code removed