/* Sign a jar file.
*
* Copyright 2004 - 2007 University of Cardiff.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ptolemy.copernicus.applet;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.KeyFactory;
import java.security.KeyStore;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.SignatureException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.spec.DSAPrivateKeySpec;
import java.security.spec.KeySpec;
import java.security.spec.RSAPrivateKeySpec;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.jar.Attributes;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.JarOutputStream;
import java.util.jar.Manifest;
import java.util.Properties;
import java.util.zip.ZipFile;
import ptolemy.util.StringUtilities;
import ptolemy.util.StreamExec;
import sun.misc.BASE64Encoder;
import sun.security.util.ManifestDigester;
/**
* Sign a Jar file.
* <p>From <a href="https://svn.cs.cf.ac.uk/projects/whip/trunk/whip-core/src/main/java/org/whipplugin/data/bundle/JarSigner15.java">https://svn.cs.cf.ac.uk/projects/whip/trunk/whip-core/src/main/java/org/whipplugin/data/bundle/JarSigner15.java</a>.
* See also <a href="http://www.onjava.com/pub/a/onjava/2001/04/12/signing_jar.html?page=1">http://www.onjava.com/pub/a/onjava/2001/04/12/signing_jar.html?page=1</a>.
* @author Andrew Harrison, Contributor: Christopher Brooks.
* @since Ptolemy II 7.1
* @version $Id$
*/
public class JarSigner {
/** Construct a jar signer.
* @param alias The alias for the signing key.
* @param privateKey The private key to sign with.
* @param certChain The certificate chain.
*/
public JarSigner(String alias, PrivateKey privateKey,
X509Certificate[] certChain) {
_alias = alias;
_privateKey = privateKey;
// Findbugs: EI2 May expose internal representation by incorporating
// reference to immutable object
_certChain = new X509Certificate[certChain.length];
System.arraycopy(certChain, 0, _certChain, 0, certChain.length);
}
/** JarSigner test driver.
*
* <p>This method uses the <code>$PTII/ptKeystore</code> certificate. To create that file:
* <pre>
* cd $PTII
* make ptKeystore
* make jnlp_list
* </pre>
* <p>Usage:
* <pre>
* java -classpath $PTII ptolemy.copernicus.applet.JarSigner JNLPApplication.jar JNLPSignedApplication.jar
* </pre>
* To verify the signed jar, run:
* <pre>
* jarsigner -verify -verbose -certs JNLPSignedApplication.jar
* </pre>
* @param args An array of two arguments, the first element is the name of the jar file
* to be read in, the second is the name of the signed jar file to be created.
*/
public static void main(String args[]) {
if (args.length != 2) {
System.err
.println("Usage: java -classpath $PTII ptolemy.copernicus.applet.JarSigner JNLPApplication.jar JNLPSignedApplication.jar");
}
// $PTII/ptKeystore is generated by running (cd $PTII; make ptKeystore)
String keystoreFileName = /*System.getProperty("PTII")*/"/Users/cxh/ptII"
+ File.separator + "ptKeystore";
String storePassword = "this.is.the.storePassword,change.it";
String keyPassword = "this.is.the.keyPassword,change.it";
String alias = "ptolemy";
String keystorePropertiesFileName = StringUtilities
.getProperty("ptolemy.ptII.dir")
+ File.separator + "ptKeystore.properties";
Properties properties = new Properties();
try {
FileInputStream fileInputStream = null;
try {
fileInputStream = new FileInputStream(keystorePropertiesFileName);
properties.load(fileInputStream);
String property = null;
if ((property = properties.getProperty("keystoreFileName")) != null) {
keystoreFileName = property;
}
storePassword = properties.getProperty("storePassword");
keyPassword = properties.getProperty("keyPassword");
alias = properties.getProperty("alias");
} finally {
if (fileInputStream != null) {
fileInputStream.close();
}
}
} catch (IOException ex) {
System.out
.println("Warning: failed to read \""
+ keystorePropertiesFileName
+ "\", using default store password, key password and alias:"
+ ex);
}
System.out.println("About to sign \"" + args[0]
+ "\" and create \"" + args[1] + "\""
+ " using keystore: \"" + keystoreFileName + "\""
+ " and alias: \"" + alias + "\"");
try {
sign(args[0], args[1], keystoreFileName, alias, storePassword.toCharArray(),
keyPassword.toCharArray());
} catch (Throwable ex) {
ex.printStackTrace();
}
}
/** Sign a jar file.
* @param jarFileName The name of the jar file to be signed.
* @param signedJarFileName The name of the signed jar file to be created.
* @param keystoreFileName The name of the keystore file. To create a keystore file, run
* <pre>
* cd $PTII
* make ptKeystore
* make jnlp_list
* <pre>
* @param alias The alias of the certificate. This is the string used when the key is created.
* @param storePassword The password of the key store.
* @param keyPassword The password of the key store.
* @exception Exception If there is a problem open or closing files, or a problem signing
* the jar file.
*/
public static void sign(String jarFileName, String signedJarFileName,
String keystoreFileName, String alias, char[] storePassword,
char[] keyPassword) throws Exception {
FileInputStream fileIn = null;
OutputStream outStream = null;
try {
fileIn = new FileInputStream(keystoreFileName);
KeyStore keyStore = KeyStore.getInstance("JKS");
keyStore.load(fileIn, storePassword);
// Get the certificate chain
Certificate[] chain = keyStore.getCertificateChain(alias);
if (chain == null) {
throw new Exception(
"Could not get certificate chain from alias \"" + alias
+ "\" from keystore \"" + keystoreFileName
+ "\"");
}
X509Certificate certChain[] = new X509Certificate[0];
certChain = new X509Certificate[chain.length];
CertificateFactory cf = CertificateFactory.getInstance("X.509");
for (int count = 0; count < chain.length; count++) {
ByteArrayInputStream certIn = new ByteArrayInputStream(chain[0]
.getEncoded());
X509Certificate cert = (X509Certificate) cf
.generateCertificate(certIn);
certChain[count] = cert;
}
Key key = keyStore.getKey(alias, keyPassword);
if (key == null) {
throw new Exception("Could not get key from alias \"" + alias
+ "\" from keystore \"" + keystoreFileName + "\"");
}
KeyFactory keyFactory = KeyFactory.getInstance(key.getAlgorithm());
KeySpec keySpec = null;
try {
keySpec = keyFactory.getKeySpec(key,
DSAPrivateKeySpec.class);
} catch (java.security.spec.InvalidKeySpecException ex) {
System.out.println("Using RSA");
keySpec = keyFactory.getKeySpec(key,
RSAPrivateKeySpec.class);
}
PrivateKey privateKey = keyFactory.generatePrivate(keySpec);
JarSigner jarSigner = new JarSigner(alias, privateKey,
certChain);
JarFile jarFile = null;
try {
jarFile = new JarFile(jarFileName);
outStream = new FileOutputStream(signedJarFileName);
jarSigner._signJarFile(jarFile, outStream);
} finally {
if (jarFile != null) {
jarFile.close();
}
}
} finally {
if (fileIn != null) {
try {
fileIn.close();
} catch (IOException ex) {
if (outStream != null) {
outStream.close();
}
throw ex;
}
}
if (outStream != null) {
outStream.close();
}
}
// FIXME: The problem here is that if we create jar files for Web Start,
// then the cert chain is not included in the PTOLEMY.RSA file.
// One can see this by running without the code below and creating
// a jar file and then running
// jarsigner -verify -certs -verbose signed_V2V.jar.bad
// and then running with the code below
// jarsigner -verify -certs -verbose signed_V2V.jar
//
// In the good file, we will have lines like
// [certificate is valid from 2/27/09 4:00 PM to 4/9/12 4:59 PM]
// X.509, CN=VeriSign Class 3 Code Signing 2004 CA, OU=Terms of use at https://www.verisign.com/rpa (c)04, OU=VeriSign Trust Network, O="VeriSign, Inc.", C=US
// [certificate is valid from 7/15/04 5:00 PM to 7/15/14 4:59 PM]
// X.509, OU=Class 3 Public Primary Certification Authority, O="VeriSign, Inc.", C=US
// [certificate is valid from 1/28/96 4:00 PM to 8/1/28 4:59 PM]
//
// If we don't have those lines, then we get an error about how
// the "JAR resources in JNLP file are not signed by same certificate"
System.out.println("Working around bug where the chain of certs "
+ "is not included in the .RSA file");
List commands = new LinkedList();
commands.add("jarsigner -keystore \"" +
keystoreFileName +
"\" -keypass \"" + new String(keyPassword) +
"\" -storepass \"" + new String(storePassword) +
"\" \"" + signedJarFileName + "\" \"" +
alias + "\"");
final StreamExec exec = new StreamExec();
exec.setCommands(commands);
exec.start();
}
///////////////////////////////////////////////////////////////////
//// private methods ////
/** Make sure that the manifest entries are ready for the signed
* JAR manifest file. if we already have a manifest, then we
* make sure that all the elements are valid. if we do not
* have a manifest, then we create a new signed JAR manifest
* file by adding the appropriate headers
*/
private static Map _createEntries(Manifest manifest, JarFile jarFile)
throws IOException {
Map entries = null;
if (manifest.getEntries().size() > 0) {
entries = _pruneManifest(manifest, jarFile);
} else {
// if there are no pre-existing entries in the manifest,
// then we put a few default ones in
Attributes attributes = manifest.getMainAttributes();
attributes.putValue(Attributes.Name.MANIFEST_VERSION.toString(),
"1.0");
attributes.putValue("Created-By", System
.getProperty("java.version")
+ " (" + System.getProperty("java.vendor") + ")");
entries = manifest.getEntries();
}
return entries;
}
/** Create a signature file object out of the manifest and the
* message digest.
*/
private/*static*/SignatureFile _createSignatureFile(Manifest manifest,
MessageDigest messageDigest) throws IOException,
IllegalAccessException, NoSuchMethodException,
InvocationTargetException, InstantiationException,
ClassNotFoundException {
// Findbugs: SIC: Should be a static inner class. However,
// this method references this._alias and returns a new object.
// construct the signature file and the signature block for
// this manifest
ManifestDigester manifestDigester = new ManifestDigester(
_serialiseManifest(manifest));
return new SignatureFile(new MessageDigest[] { messageDigest },
manifest, manifestDigester, this._alias, true);
}
private static Constructor _findConstructor(Class c, Class... argTypes)
throws NoSuchMethodException {
Constructor ct = c.getDeclaredConstructor(argTypes);
if (ct == null) {
throw new RuntimeException(c.getName());
}
ct.setAccessible(true);
return ct;
}
private static Method _findMethod(Class c, String methodName,
Class... argTypes) throws NoSuchMethodException {
Method m = c.getDeclaredMethod(methodName, argTypes);
if (m == null) {
throw new RuntimeException(c.getName());
}
m.setAccessible(true);
return m;
}
/** Retrieve the manifest from a jar file -- this will either
* load a pre-existing META-INF/MANIFEST.MF, or create a new
* one.
*/
private static Manifest _getManifestFile(JarFile jarFile)
throws IOException {
JarEntry je = jarFile.getJarEntry("META-INF/MANIFEST.MF");
if (je != null) {
Enumeration entries = jarFile.entries();
while (entries.hasMoreElements()) {
je = (JarEntry) entries.nextElement();
if ("META-INF/MANIFEST.MF".equalsIgnoreCase(je.getName())) {
break;
} else {
je = null;
}
}
}
// create the manifest object
Manifest manifest = new Manifest();
if (je != null) {
manifest.read(jarFile.getInputStream(je));
}
return manifest;
}
/** Given a manifest file and given a jar file, make sure that
* the contents of the manifest file is correct and return a
* map of all the valid entries from the manifest
*/
private static Map _pruneManifest(Manifest manifest, JarFile jarFile)
throws IOException {
Map map = manifest.getEntries();
Iterator elements = map.keySet().iterator();
while (elements.hasNext()) {
String element = (String) elements.next();
if (jarFile.getEntry(element) == null) {
elements.remove();
}
}
return map;
}
/** A small helper function that will convert a manifest into an
* array of bytes.
*/
private static byte[] _serialiseManifest(Manifest manifest)
throws IOException {
ByteArrayOutputStream baos = null;
try {
baos = new ByteArrayOutputStream();
manifest.write(baos);
baos.flush();
} finally {
if (baos != null) {
baos.close();
}
}
return baos.toByteArray();
}
/** The actual JAR signing method. This is the method which
* will be called by those wrapping the JARSigner class.
* @param jarFile The jar file to be read in and signed.
* @param outputStream The stream to which the signed jar file should be written.
* @exception NoSuchAlgorithmException If the SHA1 algorithm cannot be found or there
* is a problem generating the block.
* @exception InvalidKeyException If the certificate key is not valid.
* @exception SignatureException If there is a problem with the signature.
* @exception IOException If there is a problem reading or writing a file.
* @exception IllegalAccessException If there is a problem getting the metaname from the
* the signature file.
* @exception InvocationTargetException If there is a problem creating the signature file
* or getting the metaname from the signature.
* @exception NoSuchMethodException If thrown while creating the signature file.
* @exception CertificateException If there is a problem generating the block.
* @exception InstantiationException If thrown while creating the signature file.
* @exception ClassNotFoundException If thrown while generating the signature block.
*/
public void _signJarFile(JarFile jarFile, OutputStream outputStream)
throws NoSuchAlgorithmException, InvalidKeyException,
SignatureException, IOException, IllegalAccessException,
InvocationTargetException, NoSuchMethodException,
CertificateException, InstantiationException,
ClassNotFoundException {
// calculate the necessary files for the signed jAR
// get the manifest out of the jar and verify that
// all the entries in the manifest are correct
Manifest manifest = _getManifestFile(jarFile);
Map entries = _createEntries(manifest, jarFile);
// create the message digest and start updating the
// the attributes in the manifest to contain the SHA1
// digests
MessageDigest messageDigest = MessageDigest.getInstance("SHA1");
_updateManifestDigest(manifest, jarFile, messageDigest, entries);
// construct the signature file object and the
// signature block objects
SignatureFile signatureFile = _createSignatureFile(manifest,
messageDigest);
SignatureFile.Block block = signatureFile.generateBlock(_privateKey,
_certChain, true, jarFile);
// start writing out the signed JAR file
// write out the manifest to the output jar stream
String manifestFileName = "META-INF/MANIFEST.MF";
JarOutputStream jos = new JarOutputStream(outputStream);
JarEntry manifestFile = new JarEntry(manifestFileName);
jos.putNextEntry(manifestFile);
byte manifestBytes[] = _serialiseManifest(manifest);
jos.write(manifestBytes, 0, manifestBytes.length);
jos.closeEntry();
// write out the signature file -- the signatureFile
// object will name itself appropriately
String signatureFileName = signatureFile.getMetaName();
JarEntry signatureFileEntry = new JarEntry(signatureFileName);
jos.putNextEntry(signatureFileEntry);
signatureFile.write(jos);
jos.closeEntry();
// write out the signature block file -- again, the block
// will name itself appropriately
String signatureBlockName = block.getMetaName();
JarEntry signatureBlockEntry = new JarEntry(signatureBlockName);
jos.putNextEntry(signatureBlockEntry);
block.write(jos);
jos.closeEntry();
// commit the rest of the original entries in the
// META-INF directory. if any of their names conflict
// with one that we created for the signed JAR file, then
// we simply ignore it
Enumeration metaEntries = jarFile.entries();
while (metaEntries.hasMoreElements()) {
JarEntry metaEntry = (JarEntry) metaEntries.nextElement();
if (metaEntry.getName().startsWith("META-INF")
&& !(manifestFileName.equalsIgnoreCase(metaEntry.getName())
|| signatureFileName.equalsIgnoreCase(metaEntry
.getName()) || signatureBlockName
.equalsIgnoreCase(metaEntry.getName()))) {
_writeJarEntry(metaEntry, jarFile, jos);
}
}
// now write out the rest of the files to the stream
Enumeration allEntries = jarFile.entries();
while (allEntries.hasMoreElements()) {
JarEntry entry = (JarEntry) allEntries.nextElement();
//System.out.println("JarSigner: entry: " + entry);
if (!entry.getName().startsWith("META-INF")) {
_writeJarEntry(entry, jarFile, jos);
}
}
// finish the stream that we have been writing to
jos.flush();
jos.finish();
}
/** Helper function to update the digest.
* The inputStream is always closed upon exit.
*/
private static String _updateDigest(MessageDigest digest,
InputStream inputStream) throws IOException {
try {
byte[] buffer = new byte[2048];
int read = 0;
while ((read = inputStream.read(buffer)) > 0) {
digest.update(buffer, 0, read);
}
} finally {
inputStream.close();
}
return _b64Encoder.encode(digest.digest());
}
/** Update the attributes in the manifest to have the
* appropriate message digests. we store the new entries into
* the entries Map and return it (we do not compute the digests
* for those entries in the META-INF directory)
*/
private static Map _updateManifestDigest(Manifest manifest,
JarFile jarFile, MessageDigest messageDigest, Map entries)
throws IOException {
Enumeration jarElements = jarFile.entries();
while (jarElements.hasMoreElements()) {
JarEntry jarEntry = (JarEntry) jarElements.nextElement();
if (jarEntry.getName().startsWith("META-INF")) {
continue;
} else if (manifest.getAttributes(jarEntry.getName()) != null) {
// update the digest and record the base 64 version of
// it into the attribute list
Attributes attributes = manifest.getAttributes(jarEntry
.getName());
attributes.putValue("SHA1-Digest", _updateDigest(messageDigest,
jarFile.getInputStream(jarEntry)));
} else if (!jarEntry.isDirectory()) {
// store away the digest into a new Attribute
// because we don't already have an attribute list
// for this entry. we do not store attributes for
// directories within the JAR
Attributes attributes = new Attributes();
attributes.putValue("SHA1-Digest", _updateDigest(messageDigest,
jarFile.getInputStream(jarEntry)));
entries.put(jarEntry.getName(), attributes);
}
}
return entries;
}
/** A helper function that can take entries from one jar file and
* write it to another jar stream.
*
* @param jarEntry The entry in the jar file to be added to the
* jar output stream.
* @param jarFile The jar file that contains the jarEntry.
* @param jarOutputStream The output stream that the jarEntry from
* the jarFile to which to write.
* @exception IOException If there is a problem reading or writing
* the jarEntry.
*/
protected static void _writeJarEntry(JarEntry jarEntry, JarFile jarFile,
JarOutputStream jarOutputStream) throws IOException {
jarOutputStream.putNextEntry(jarEntry);
byte[] buffer = new byte[2048];
int read = 0;
try {
InputStream inputStream = jarFile.getInputStream(jarEntry);
while ((read = inputStream.read(buffer)) > 0) {
jarOutputStream.write(buffer, 0, read);
}
} finally {
jarOutputStream.closeEntry();
}
}
///////////////////////////////////////////////////////////////////
//// private inner classes ////
private class SignatureFile {
private Object sigFile;
private Class JDKsfClass;
private Method getMetaNameMethod;
private Method writeMethod;
private static final String JDK_SIGNATURE_FILE = "sun.security.tools.SignatureFile";
private static final String GETMETANAME_METHOD = "getMetaName";
private static final String WRITE_METHOD = "write";
public SignatureFile(MessageDigest digests[], Manifest mf,
ManifestDigester md, String baseName, boolean signManifest)
throws ClassNotFoundException, NoSuchMethodException,
InstantiationException, IllegalAccessException,
InvocationTargetException {
JDKsfClass = Class.forName(JDK_SIGNATURE_FILE);
Constructor constructor = _findConstructor(JDKsfClass,
MessageDigest[].class, Manifest.class,
ManifestDigester.class, String.class, Boolean.TYPE);
sigFile = constructor.newInstance(digests, mf, md, baseName,
signManifest);
getMetaNameMethod = _findMethod(JDKsfClass, GETMETANAME_METHOD);
writeMethod = _findMethod(JDKsfClass, WRITE_METHOD,
OutputStream.class);
}
public Block generateBlock(PrivateKey privateKey,
X509Certificate[] certChain, boolean externalSF, ZipFile zipFile)
throws NoSuchAlgorithmException, InvalidKeyException,
IOException, SignatureException, CertificateException,
ClassNotFoundException, NoSuchMethodException,
InstantiationException, IllegalAccessException,
InvocationTargetException {
return new Block(this, privateKey, certChain, externalSF, zipFile);
}
public Class getJDKSignatureFileClass() {
return JDKsfClass;
}
public Object getJDKSignatureFile() {
return sigFile;
}
public String getMetaName() throws IllegalAccessException,
InvocationTargetException {
return (String) getMetaNameMethod.invoke(sigFile);
}
public void write(OutputStream os) throws IllegalAccessException,
InvocationTargetException {
writeMethod.invoke(sigFile, os);
}
private class Block {
private Object block;
private static final String JDK_BLOCK = JDK_SIGNATURE_FILE
+ "$Block";
private static final String JDK_CONTENT_SIGNER = "com.sun.jarsigner.ContentSigner";
private Method getMetaNameMethod;
private Method writeMethod;
public Block(SignatureFile sfg, PrivateKey privateKey,
X509Certificate[] certChain, boolean externalSF,
ZipFile zipFile) throws ClassNotFoundException,
NoSuchMethodException, InstantiationException,
IllegalAccessException, InvocationTargetException {
Class blockClass = Class.forName(JDK_BLOCK);
Class contentSignerClass = Class.forName(JDK_CONTENT_SIGNER);
Constructor constructor = null;
try {
// Java 1.5
constructor = _findConstructor(blockClass, sfg
.getJDKSignatureFileClass(), PrivateKey.class,
X509Certificate[].class, Boolean.TYPE,
String.class, X509Certificate.class,
contentSignerClass, String[].class, ZipFile.class);
block = constructor.newInstance(sfg.getJDKSignatureFile(), /* explicit argument on the constructor */
privateKey, certChain, externalSF, null, null, null, null,
zipFile);
} catch (NoSuchMethodException ex) {
// Java 1.6
// In Java 1.6, SignatureFile$Block() takes a new argument,
// String sigAlg, which can be null. See the source code
// for JarSigner at
// http://www.java2s.com/Open-Source/Java-Document/6.0-JDK-Modules-sun/security/sun/security/tools/JarSigner.java.htm
// and see
// http://www.docjar.com/docs/api/sun/security/tools/SignatureFile.html
constructor = _findConstructor(blockClass, sfg
.getJDKSignatureFileClass(), PrivateKey.class,
/* Is this the only difference between 1.5 and 1.6?*/
/* signatureAlgorithm */String.class,
X509Certificate[].class, Boolean.TYPE,
String.class, X509Certificate.class,
contentSignerClass, String[].class, ZipFile.class);
block = constructor.newInstance(sfg.getJDKSignatureFile(), /* explicit argument on the constructor */
privateKey,
/*signatureAlgorithm*/null, certChain, externalSF, null,
null, null, null, zipFile);
}
getMetaNameMethod = _findMethod(blockClass, GETMETANAME_METHOD);
writeMethod = _findMethod(blockClass, WRITE_METHOD,
OutputStream.class);
}
public String getMetaName() throws IllegalAccessException,
InvocationTargetException {
return (String) getMetaNameMethod.invoke(block);
}
public void write(OutputStream os) throws IllegalAccessException,
InvocationTargetException {
writeMethod.invoke(block, os);
}
}
}
///////////////////////////////////////////////////////////////////
//// private variables ////
/** The alias for the signing key. */
private String _alias;
private static BASE64Encoder _b64Encoder = new BASE64Encoder();
/** The private key to sign with. */
private PrivateKey _privateKey;
/** The certificate chain. */
private X509Certificate[] _certChain;
}