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:
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.
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:
-
Start Eclipse.
-
Click
File > New > Project
.
-
Specify a name for the project in the
Project name
box.
-
Click
Finish
.
To add required JAR files to your project:
-
In the
Project Explorer
window,
right-click your project and select
Properties
.
-
Click
Java build path
.
-
Click the
Libraries
tab.
-
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 ("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");
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.
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();
}
}
}
|
|
|