org.signserver.module.tsa.TimeStampSigner.java Source code

Java tutorial

Introduction

Here is the source code for org.signserver.module.tsa.TimeStampSigner.java

Source

/*************************************************************************
 *                                                                       *
 *  SignServer: The OpenSource Automated Signing Server                  *
 *                                                                       *
 *  This software is free software; you can redistribute it and/or       *
 *  modify it under the terms of the GNU Lesser General Public           *
 *  License as published by the Free Software Foundation; either         *
 *  version 2.1 of the License, or any later version.                    *
 *                                                                       *
 *  See terms of license at gnu.org.                                     *
 *                                                                       *
 *************************************************************************/
package org.signserver.module.tsa;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.math.BigInteger;
import java.security.*;
import java.security.cert.*;
import java.security.cert.Certificate;
import java.text.SimpleDateFormat;
import java.util.*;
import javax.persistence.EntityManager;
import org.apache.log4j.Logger;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.cmp.PKIStatus;
import org.bouncycastle.asn1.oiw.OIWObjectIdentifiers;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import org.bouncycastle.asn1.x509.GeneralName;
import org.bouncycastle.asn1.x509.KeyPurposeId;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.jcajce.JcaX509CertificateHolder;
import org.bouncycastle.cms.SignerInfoGenerator;
import org.bouncycastle.cms.jcajce.JcaSignerInfoGeneratorBuilder;
import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.operator.DigestCalculator;
import org.bouncycastle.operator.DigestCalculatorProvider;
import org.bouncycastle.operator.OperatorCreationException;
import org.bouncycastle.operator.bc.BcDigestCalculatorProvider;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
import org.signserver.module.tsa.bc.TimeStampRequest;
import org.signserver.module.tsa.bc.TimeStampResponseGenerator;
import org.signserver.module.tsa.bc.TimeStampTokenGenerator;
import org.bouncycastle.tsp.TSPAlgorithms;
import org.bouncycastle.tsp.TSPException;
import org.bouncycastle.tsp.TimeStampResponse;
import org.bouncycastle.tsp.TimeStampToken;
import org.ejbca.util.Base64;
import org.signserver.common.*;
import org.signserver.server.ITimeSource;
import org.signserver.server.WorkerContext;
import org.signserver.server.archive.Archivable;
import org.signserver.server.archive.DefaultArchivable;
import org.signserver.server.cryptotokens.ICryptoInstance;
import org.signserver.server.cryptotokens.ICryptoToken;
import org.signserver.server.log.IWorkerLogger;
import org.signserver.server.log.LogMap;
import org.signserver.server.signers.BaseSigner;

/**
 * A Signer signing Time-stamp request according to RFC 3161 using the
 * BouncyCastle TimeStamp API.
 *
 * Implements a ISigner and have the following properties:
 *
 * <table border="1">
 *  <tr>
 *      <td>TIMESOURCE</td>
 *      <td>
 *          property containing the classpath to the ITimeSource implementation
 *          that should be used. (default LocalComputerTimeSource)
 *      </td>
 *  </tr>
 *  <tr>
 *      <td>ACCEPTEDALGORITHMS</td>
 *      <td>
 *          A ';' separated string containing accepted algorithms, can be null
 *          if it shouldn't be used. (OPTIONAL)
 *      </td>
 *  </tr>
 *  <tr>
 *      <td>ACCEPTEDPOLICIES</td>
 *      <td>
 *          A ';' separated string containing accepted policies, can be null if
 *          it shouldn't be used. (OPTIONAL)
 *      </td>
 * </tr>
 *  <tr>
 *      <td>ACCEPTEDEXTENSIONS</td>
 *      <td>
 *          A ';' separated string containing accepted extensions, can be null
 *          if it shouldn't be used. (OPTIONAL)
 *      </td>
 * </tr>
 *  <tr>
 *      <td>DIGESTOID</td>
 *      <td>
 *          The Digenst OID to be used in the timestamp
 *      </td>
 * </tr>
 *  <tr>
 *      <td>DEFAULTTSAPOLICYOID</td>
 *      <td>
 *          The default policy ID of the time stamp authority
 *      </td>
 * </tr>
 *  <tr>
 *      <td>ACCURACYMICROS</td>
 *      <td>
 *          Accuraty in micro seconds, Only decimal number format, only one of
 *          the accuracy properties should be set (OPTIONAL)
 *      </td>
 * </tr>
 *  <tr>
 *      <td>ACCURACYMILLIS</td>
 *      <td>
 *          Accuraty in milli seconds, Only decimal number format, only one of
 *          the accuracy properties should be set (OPTIONAL)
 *      </td>
 * </tr>
 *  <tr>
 *      <td>ACCURACYSECONDS</td>
 *      <td>
 *          Accuraty in seconds. Only decimal number format, only one of the
 *          accuracy properties should be set (OPTIONAL)
 *      </td>
 * </tr>
 *  <tr>
 *      <td>ORDERING</td>
 *      <td>
 *          The ordering (OPTIONAL), default false.
 *      </td>
 * </tr>
 *  <tr>
 *      <td>TSA</td>
 *      <td>
 *          General name of the Time Stamp Authority.
 *      </td>
 *  </tr>
 * <tr>
 *      <td>REQUIREVALIDCHAIN</td>
 *      <td>
 *          Set to true to perform an extra check that the SIGNERCERTCHAIN only 
 *          contains certificates in the chain of the signer certificate.
 *          (OPTIONAL), default false.
 *      </td>
 * </tr>
 *
 * </table>
 * 
 * Specifying a signer certificate (normally the SIGNERCERT property) is required 
 * as information from that certificate will be used to indicate which signer
 * signed the time-stamp token.
 * 
 * The SIGNERCERTCHAIN property contains all certificates included in the token 
 * if the client requests the certificates. The RFC specified that the signer 
 * certificate MUST be included in the list returned.
 * 
 *
 * @author philip
 * @version $Id: TimeStampSigner.java 5977 2015-03-27 10:30:50Z netmackan $
 */
public class TimeStampSigner extends BaseSigner {

    /** Log4j instance for actual implementation class. */
    private static final Logger LOG = Logger.getLogger(TimeStampSigner.class);

    /** Random generator algorithm. */
    private static String algorithm = "SHA1PRNG";

    /** Random generator. */
    private transient SecureRandom random;

    /** MIME type for the request data. **/
    private static final String REQUEST_CONTENT_TYPE = "application/timestamp-query";

    /** MIME type for the response data. **/
    private static final String RESPONSE_CONTENT_TYPE = "application/timestamp-reply";

    // Property constants
    public static final String TIMESOURCE = "TIMESOURCE";
    public static final String SIGNATUREALGORITHM = "SIGNATUREALGORITHM";
    public static final String ACCEPTEDALGORITHMS = "ACCEPTEDALGORITHMS";
    public static final String ACCEPTEDPOLICIES = "ACCEPTEDPOLICIES";
    public static final String ACCEPTEDEXTENSIONS = "ACCEPTEDEXTENSIONS";
    //public static final String DEFAULTDIGESTOID    = "DEFAULTDIGESTOID";
    public static final String DEFAULTTSAPOLICYOID = "DEFAULTTSAPOLICYOID";
    public static final String ACCURACYMICROS = "ACCURACYMICROS";
    public static final String ACCURACYMILLIS = "ACCURACYMILLIS";
    public static final String ACCURACYSECONDS = "ACCURACYSECONDS";
    public static final String ORDERING = "ORDERING";
    public static final String INCLUDEORDERING = "INCLUDEORDERING";
    public static final String TSA = "TSA";
    public static final String TSA_FROM_CERT = "TSA_FROM_CERT";
    public static final String REQUIREVALIDCHAIN = "REQUIREVALIDCHAIN";
    public static final String MAXSERIALNUMBERLENGTH = "MAXSERIALNUMBERLENGTH";
    public static final String INCLUDESTATUSSTRING = "INCLUDESTATUSSTRING";
    public static final String INCLUDESIGNINGTIMEATTRIBUTE = "INCLUDESIGNINGTIMEATTRIBUTE";

    private static final String DEFAULT_WORKERLOGGER = DefaultTimeStampLogger.class.getName();

    private static final String DEFAULT_TIMESOURCE = "org.signserver.server.LocalComputerTimeSource";
    private static final int DEFAULT_MAXSERIALNUMBERLENGTH = 8;

    private static final String[] ACCEPTEDALGORITHMSNAMES = { "GOST3411", "MD5", "SHA1", "SHA224", "SHA256",
            "SHA384", "SHA512", "RIPEMD128", "RIPEMD160", "RIPEMD256" };

    private static final ASN1ObjectIdentifier[] ACCEPTEDALGORITHMSOIDS = { TSPAlgorithms.GOST3411,
            TSPAlgorithms.MD5, TSPAlgorithms.SHA1, TSPAlgorithms.SHA224, TSPAlgorithms.SHA256, TSPAlgorithms.SHA384,
            TSPAlgorithms.SHA512, TSPAlgorithms.RIPEMD128, TSPAlgorithms.RIPEMD160, TSPAlgorithms.RIPEMD256 };

    private static final HashMap<String, ASN1ObjectIdentifier> ACCEPTEDALGORITHMSMAP = new HashMap<String, ASN1ObjectIdentifier>();
    private static final HashMap<ASN1ObjectIdentifier, String> ACCEPTEDALGORITHMSREVERSEMAP = new HashMap<ASN1ObjectIdentifier, String>();

    static {
        for (int i = 0; i < ACCEPTEDALGORITHMSNAMES.length; i++) {
            ACCEPTEDALGORITHMSMAP.put(ACCEPTEDALGORITHMSNAMES[i], ACCEPTEDALGORITHMSOIDS[i]);
            ACCEPTEDALGORITHMSREVERSEMAP.put(ACCEPTEDALGORITHMSOIDS[i], ACCEPTEDALGORITHMSNAMES[i]);
        }
    }

    private static final String DEFAULT_SIGNATUREALGORITHM = "SHA1withRSA";
    private static final String DEFAULT_ORDERING = "FALSE";
    //private static final String DEFAULT_DIGESTOID   = TSPAlgorithms.SHA1;

    private final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss z");

    private ITimeSource timeSource = null;
    private String signatureAlgorithm;
    private Set<ASN1ObjectIdentifier> acceptedAlgorithms = null;
    private Set<String> acceptedPolicies = null;
    private Set<String> acceptedExtensions = null;

    //private String defaultDigestOID = null;
    private ASN1ObjectIdentifier defaultTSAPolicyOID = null;

    private boolean validChain = true;

    private int maxSerialNumberLength;
    private String serialNumberError;

    // we restrict the allowed serial number size limit to between 64 and 160 bits
    // note: the generated serial number will always be positive
    private static final int MAX_ALLOWED_MAXSERIALNUMBERLENGTH = 20;
    private static final int MIN_ALLOWED_MAXSERIALNUMBERLENGTH = 8;

    private boolean includeStatusString;

    private String tsaName;
    private boolean tsaNameFromCert;
    private boolean includeSigningTimeAttribute;

    private boolean ordering;
    private boolean includeOrdering;

    private List<String> configErrors;

    @Override
    public void init(final int signerId, final WorkerConfig config, final WorkerContext workerContext,
            final EntityManager workerEntityManager) {
        super.init(signerId, config, workerContext, workerEntityManager);

        // Overrides the default worker logger to be this worker
        //  implementation's default instead of the WorkerSessionBean's
        if (config.getProperty("WORKERLOGGER") == null) {
            config.setProperty("WORKERLOGGER", DEFAULT_WORKERLOGGER);
        }

        // Check that the timestamp server is properly configured
        try {
            timeSource = getTimeSource();
            if (LOG.isDebugEnabled()) {
                LOG.debug("TimeStampSigner[" + signerId + "]: " + "Using TimeSource: "
                        + timeSource.getClass().getName());
            }
        } catch (SignServerException e) {
            LOG.error("Could not create time source: " + e.getMessage());
        }

        // Get the signature algorithm
        signatureAlgorithm = config.getProperty(SIGNATUREALGORITHM, DEFAULT_SIGNATUREALGORITHM);

        /* defaultDigestOID =
        config.getProperties().getProperty(DEFAULTDIGESTOID);
        if (defaultDigestOID == null) {
        defaultDigestOID = DEFAULT_DIGESTOID;
        }*/

        final String policyId = config.getProperties().getProperty(DEFAULTTSAPOLICYOID);

        try {
            if (policyId != null) {
                defaultTSAPolicyOID = new ASN1ObjectIdentifier(policyId);
            } else {
                LOG.error("Error: No default TSA Policy OID have been configured");
            }
        } catch (IllegalArgumentException iae) {
            LOG.error("Error: TSA Policy OID " + policyId + " is invalid");
        }

        if (LOG.isDebugEnabled()) {
            LOG.debug("bctsp version: " + TimeStampResponseGenerator.class.getPackage().getImplementationVersion()
                    + ", " + TimeStampRequest.class.getPackage().getImplementationVersion());
        }

        // Validate certificates in signer certificate chain
        final String requireValidChain = config.getProperty(REQUIREVALIDCHAIN, Boolean.FALSE.toString());
        if (Boolean.parseBoolean(requireValidChain)) {
            validChain = validateChain();
        }

        maxSerialNumberLength = DEFAULT_MAXSERIALNUMBERLENGTH;
        final String maxSerialNumberLengthProp = config.getProperty(MAXSERIALNUMBERLENGTH);

        if (maxSerialNumberLengthProp != null) {
            try {
                maxSerialNumberLength = Integer.parseInt(maxSerialNumberLengthProp);
            } catch (NumberFormatException e) {
                maxSerialNumberLength = -1;
                serialNumberError = "Maximum serial number length specified is invalid: \""
                        + maxSerialNumberLengthProp + "\"";
            }

            if (serialNumberError == null) {
                if (maxSerialNumberLength > MAX_ALLOWED_MAXSERIALNUMBERLENGTH) {
                    serialNumberError = "Maximum serial number length specified is too large: "
                            + maxSerialNumberLength;
                } else if (maxSerialNumberLength < MIN_ALLOWED_MAXSERIALNUMBERLENGTH) {
                    serialNumberError = "Maximum serial number length specified is too small: "
                            + maxSerialNumberLength;
                }
            }
        }

        includeStatusString = Boolean.parseBoolean(config.getProperty(INCLUDESTATUSSTRING, "true"));

        tsaName = config.getProperty(TSA);
        tsaNameFromCert = Boolean.parseBoolean(config.getProperty(TSA_FROM_CERT, "false"));

        if (tsaName != null && tsaNameFromCert) {
            LOG.error("Error: Can not set " + TSA_FROM_CERT + " to true and set " + TSA
                    + " worker property at the same time");
        }

        includeSigningTimeAttribute = Boolean.valueOf(config.getProperty(INCLUDESIGNINGTIMEATTRIBUTE, "true"));

        ordering = Boolean.parseBoolean(config.getProperty(ORDERING, "false"));
        includeOrdering = Boolean.parseBoolean(config.getProperty(INCLUDEORDERING, "false"));

        configErrors = new LinkedList<String>();

        if (hasSetIncludeCertificateLevels && includeCertificateLevels == 0) {
            configErrors.add("Illegal value for property " + WorkerConfig.PROPERTY_INCLUDE_CERTIFICATE_LEVELS
                    + ". Only numbers >= 1 supported.");
        }
    }

    /**
     * The main method performing the actual timestamp operation.
     * Expects the signRequest to be a GenericSignRequest contining a
     * TimeStampRequest
     *
     * @param signRequest
     * @param requestContext
     * @return the sign response
     * @see org.signserver.server.IProcessable#processData(org.signserver.common.ProcessRequest, org.signserver.common.RequestContext)
     */
    @Override
    public ProcessResponse processData(final ProcessRequest signRequest, final RequestContext requestContext)
            throws IllegalRequestException, CryptoTokenOfflineException, SignServerException {

        // Log values
        final LogMap logMap = LogMap.getInstance(requestContext);

        final ISignRequest sReq = (ISignRequest) signRequest;

        // Check that the request contains a valid TimeStampRequest object.
        if (!(signRequest instanceof GenericSignRequest)) {
            final IllegalRequestException exception = new IllegalRequestException(
                    "Recieved request wasn't a expected GenericSignRequest. ");
            throw exception;
        }

        if (!((sReq.getRequestData() instanceof TimeStampRequest) || (sReq.getRequestData() instanceof byte[]))) {
            final IllegalRequestException exception = new IllegalRequestException(
                    "Recieved request data wasn't a expected TimeStampRequest. ");
            throw exception;
        }

        if (!validChain) {
            LOG.error("Certificate chain not correctly configured");
            throw new CryptoTokenOfflineException("Certificate chain not correctly configured");
        }

        final ITimeSource timeSrc = getTimeSource();
        if (LOG.isDebugEnabled()) {
            LOG.debug("TimeSource: " + timeSrc.getClass().getName());
        }
        final Date date = timeSrc.getGenTime();
        final BigInteger serialNumber = getSerialNumber();

        // Log values
        logMap.put(ITimeStampLogger.LOG_TSA_TIME, date == null ? null : String.valueOf(date.getTime()));
        logMap.put(ITimeStampLogger.LOG_TSA_SERIALNUMBER, serialNumber.toString(16));
        logMap.put(ITimeStampLogger.LOG_TSA_TIMESOURCE, timeSrc.getClass().getSimpleName());

        GenericSignResponse signResponse = null;
        ICryptoInstance crypto = null;
        try {
            crypto = acquireCryptoInstance(ICryptoToken.PURPOSE_SIGN, signRequest, requestContext);
            final byte[] requestbytes = (byte[]) sReq.getRequestData();

            if (requestbytes == null || requestbytes.length == 0) {
                LOG.error("Request must contain data");
                throw new IllegalRequestException("Request must contain data");
            }

            final TimeStampRequest timeStampRequest = new TimeStampRequest(requestbytes);

            // Log values for timestamp request
            logMap.put(ITimeStampLogger.LOG_TSA_TIMESTAMPREQUEST_CERTREQ,
                    String.valueOf(timeStampRequest.getCertReq()));
            logMap.put(ITimeStampLogger.LOG_TSA_TIMESTAMPREQUEST_CRITEXTOIDS,
                    String.valueOf(timeStampRequest.getCriticalExtensionOIDs()));
            logMap.put(ITimeStampLogger.LOG_TSA_TIMESTAMPREQUEST_ENCODED,
                    new String(Base64.encode(requestbytes, false)));
            logMap.put(ITimeStampLogger.LOG_TSA_TIMESTAMPREQUEST_NONCRITEXTOIDS,
                    String.valueOf(timeStampRequest.getNonCriticalExtensionOIDs()));
            logMap.put(ITimeStampLogger.LOG_TSA_TIMESTAMPREQUEST_NOUNCE,
                    String.valueOf(timeStampRequest.getNonce()));
            logMap.put(ITimeStampLogger.LOG_TSA_TIMESTAMPREQUEST_VERSION,
                    String.valueOf(timeStampRequest.getVersion()));
            logMap.put(ITimeStampLogger.LOG_TSA_TIMESTAMPREQUEST_MESSAGEIMPRINTALGOID,
                    timeStampRequest.getMessageImprintAlgOID().getId());
            logMap.put(ITimeStampLogger.LOG_TSA_TIMESTAMPREQUEST_MESSAGEIMPRINTDIGEST,
                    new String(Base64.encode(timeStampRequest.getMessageImprintDigest(), false)));

            final TimeStampTokenGenerator timeStampTokenGen = getTimeStampTokenGenerator(crypto, timeStampRequest,
                    logMap);

            final TimeStampResponseGenerator timeStampResponseGen = getTimeStampResponseGenerator(
                    timeStampTokenGen);

            final TimeStampResponse timeStampResponse = timeStampResponseGen.generate(timeStampRequest,
                    serialNumber, date, includeStatusString);

            final TimeStampToken token = timeStampResponse.getTimeStampToken();
            final byte[] signedbytes = timeStampResponse.getEncoded();

            // Log values for timestamp response
            if (LOG.isDebugEnabled()) {
                LOG.debug("Time stamp response status: " + timeStampResponse.getStatus() + ": "
                        + timeStampResponse.getStatusString());
            }
            logMap.put(ITimeStampLogger.LOG_TSA_PKISTATUS, String.valueOf(timeStampResponse.getStatus()));
            if (timeStampResponse.getFailInfo() != null) {
                logMap.put(ITimeStampLogger.LOG_TSA_PKIFAILUREINFO,
                        String.valueOf(timeStampResponse.getFailInfo().intValue()));
            }
            logMap.put(ITimeStampLogger.LOG_TSA_TIMESTAMPRESPONSE_ENCODED,
                    new String(Base64.encode(signedbytes, false)));
            logMap.put(ITimeStampLogger.LOG_TSA_PKISTATUS_STRING, timeStampResponse.getStatusString());

            final String archiveId;
            if (token == null) {
                archiveId = serialNumber.toString(16);
            } else {
                archiveId = token.getTimeStampInfo().getSerialNumber().toString(16);
            }

            final Collection<? extends Archivable> archivables = Arrays.asList(
                    new DefaultArchivable(Archivable.TYPE_REQUEST, REQUEST_CONTENT_TYPE, requestbytes, archiveId),
                    new DefaultArchivable(Archivable.TYPE_RESPONSE, RESPONSE_CONTENT_TYPE, signedbytes, archiveId));

            if (signRequest instanceof GenericServletRequest) {
                signResponse = new GenericServletResponse(sReq.getRequestID(), signedbytes,
                        getSigningCertificate(signRequest, requestContext), archiveId, archivables,
                        RESPONSE_CONTENT_TYPE);
            } else {
                signResponse = new GenericSignResponse(sReq.getRequestID(), signedbytes,
                        getSigningCertificate(signRequest, requestContext), archiveId, archivables);
            }

            // Put in log values
            if (date == null) {
                logMap.put(ITimeStampLogger.LOG_TSA_EXCEPTION, "timeSourceNotAvailable");
            }

            // We were able to fulfill the request so the worker session bean
            // can go on and charge the client
            if (timeStampResponse.getStatus() == PKIStatus.GRANTED) {
                // The client can be charged for the request
                requestContext.setRequestFulfilledByWorker(true);
            } else {
                logMap.put(IWorkerLogger.LOG_PROCESS_SUCCESS, String.valueOf(false));
            }

        } catch (InvalidAlgorithmParameterException e) {
            final IllegalRequestException exception = new IllegalRequestException(
                    "InvalidAlgorithmParameterException: " + e.getMessage(), e);
            LOG.error("InvalidAlgorithmParameterException: ", e);
            logMap.put(ITimeStampLogger.LOG_TSA_EXCEPTION, exception.getMessage());
            throw exception;
        } catch (NoSuchAlgorithmException e) {
            final IllegalRequestException exception = new IllegalRequestException(
                    "NoSuchAlgorithmException: " + e.getMessage(), e);
            LOG.error("NoSuchAlgorithmException: ", e);
            logMap.put(ITimeStampLogger.LOG_TSA_EXCEPTION, exception.getMessage());
            throw exception;
        } catch (NoSuchProviderException e) {
            final IllegalRequestException exception = new IllegalRequestException(
                    "NoSuchProviderException: " + e.getMessage(), e);
            LOG.error("NoSuchProviderException: ", e);
            logMap.put(ITimeStampLogger.LOG_TSA_EXCEPTION, exception.getMessage());
            throw exception;
        } catch (CertStoreException e) {
            final IllegalRequestException exception = new IllegalRequestException(
                    "CertStoreException: " + e.getMessage(), e);
            LOG.error("CertStoreException: ", e);
            logMap.put(ITimeStampLogger.LOG_TSA_EXCEPTION, exception.getMessage());
            throw exception;
        } catch (IOException e) {
            final IllegalRequestException exception = new IllegalRequestException("IOException: " + e.getMessage(),
                    e);
            LOG.error("IOException: ", e);
            logMap.put(ITimeStampLogger.LOG_TSA_EXCEPTION, exception.getMessage());
            throw exception;
        } catch (TSPException e) {
            final IllegalRequestException exception = new IllegalRequestException(e.getMessage(), e);
            LOG.error("TSPException: ", e);
            logMap.put(ITimeStampLogger.LOG_TSA_EXCEPTION, exception.getMessage());
            throw exception;
        } catch (OperatorCreationException e) {
            final SignServerException exception = new SignServerException(e.getMessage(), e);
            LOG.error("OperatorCreationException: ", e);
            logMap.put(ITimeStampLogger.LOG_TSA_EXCEPTION, exception.getMessage());
            throw exception;
        } finally {
            releaseCryptoInstance(crypto, requestContext);
        }

        return signResponse;
    }

    /**
     * @return a time source interface expected to provide accurate time
     */
    private ITimeSource getTimeSource() throws SignServerException {
        if (timeSource == null) {
            try {
                String classpath = this.config.getProperties().getProperty(TIMESOURCE);
                if (classpath == null) {
                    classpath = DEFAULT_TIMESOURCE;
                }

                final Class<?> implClass = Class.forName(classpath);
                final Object obj = implClass.newInstance();
                timeSource = (ITimeSource) obj;
                timeSource.init(config.getProperties());

            } catch (ClassNotFoundException e) {
                throw new SignServerException("Class not found", e);
            } catch (IllegalAccessException iae) {
                throw new SignServerException("Illegal access", iae);
            } catch (InstantiationException ie) {
                throw new SignServerException("Instantiation error", ie);
            }
        }

        return timeSource;
    }

    @SuppressWarnings("unchecked")
    private Set<ASN1ObjectIdentifier> getAcceptedAlgorithms() {
        if (acceptedAlgorithms == null) {
            final String nonParsedAcceptedAlgorihms = this.config.getProperties().getProperty(ACCEPTEDALGORITHMS);
            if (nonParsedAcceptedAlgorihms == null) {
                acceptedAlgorithms = TSPAlgorithms.ALLOWED;
            } else {
                final String[] subStrings = nonParsedAcceptedAlgorihms.split(";");
                if (subStrings.length > 0) {
                    acceptedAlgorithms = new HashSet();
                    for (int i = 0; i < subStrings.length; i++) {
                        final ASN1ObjectIdentifier acceptAlg = ACCEPTEDALGORITHMSMAP.get(subStrings[i]);
                        if (acceptAlg != null) {
                            acceptedAlgorithms.add(acceptAlg);
                        } else {
                            LOG.error("Error, signer " + workerId
                                    + " configured with incompatible acceptable algorithm : " + subStrings[i]);
                        }
                    }
                }
            }
        }

        return acceptedAlgorithms;
    }

    private Set<String> getAcceptedPolicies() {
        if (acceptedPolicies == null) {
            final String nonParsedAcceptedPolicies = this.config.getProperties().getProperty(ACCEPTEDPOLICIES);
            acceptedPolicies = makeSetOfProperty(nonParsedAcceptedPolicies);
        }

        return acceptedPolicies;

    }

    private Set<String> getAcceptedExtensions() {
        if (acceptedExtensions == null) {
            final String nonParsedAcceptedExtensions = this.config.getProperties().getProperty(ACCEPTEDEXTENSIONS);
            acceptedExtensions = makeSetOfProperty(nonParsedAcceptedExtensions);
        }

        return acceptedExtensions;
    }

    /**
     * Help method taking a string and creating a java.util.Set of the
     * strings using ';' as a delimiter.
     * If null is used as and argument then will null be returned by the method.
     * @param nonParsedPropery Semicolon separated strings
     * @return Set of Strings
     */
    private Set<String> makeSetOfProperty(final String nonParsedPropery) {
        Set<String> retval = new HashSet<String>();
        if (nonParsedPropery != null) {
            final String[] subStrings = nonParsedPropery.split(";");
            for (String oid : subStrings) {
                oid = oid.trim();
                if (!oid.isEmpty()) {
                    retval.add(oid);
                }
            }
        }
        return retval;
    }

    private TimeStampTokenGenerator getTimeStampTokenGenerator(final ICryptoInstance crypto,
            final TimeStampRequest timeStampRequest, final LogMap logMap) throws IllegalRequestException,
            CryptoTokenOfflineException, InvalidAlgorithmParameterException, NoSuchAlgorithmException,
            NoSuchProviderException, CertStoreException, OperatorCreationException, SignServerException {

        TimeStampTokenGenerator timeStampTokenGen = null;
        try {
            ASN1ObjectIdentifier tSAPolicyOID = timeStampRequest.getReqPolicy();
            if (tSAPolicyOID == null) {
                tSAPolicyOID = defaultTSAPolicyOID;
            }
            logMap.put(ITimeStampLogger.LOG_TSA_POLICYID, tSAPolicyOID.getId());

            final X509Certificate signingCert = (X509Certificate) getSigningCertificate(crypto);
            if (signingCert == null) {
                throw new CryptoTokenOfflineException("No certificate for this signer");
            }

            DigestCalculatorProvider calcProv = new BcDigestCalculatorProvider();
            DigestCalculator calc = calcProv.get(new AlgorithmIdentifier(TSPAlgorithms.SHA1));

            ContentSigner cs = new JcaContentSignerBuilder(signatureAlgorithm).setProvider(crypto.getProvider())
                    .build(crypto.getPrivateKey());
            JcaSignerInfoGeneratorBuilder sigb = new JcaSignerInfoGeneratorBuilder(calcProv);
            X509CertificateHolder certHolder = new X509CertificateHolder(signingCert.getEncoded());

            // set signed attribute table generator based on property
            sigb.setSignedAttributeGenerator(
                    new OptionalSigningTimeSignedAttributeTableGenerator(includeSigningTimeAttribute));

            SignerInfoGenerator sig = sigb.build(cs, certHolder);

            timeStampTokenGen = new TimeStampTokenGenerator(calc, sig, tSAPolicyOID);

            if (config.getProperties().getProperty(ACCURACYMICROS) != null) {
                timeStampTokenGen
                        .setAccuracyMicros(Integer.parseInt(config.getProperties().getProperty(ACCURACYMICROS)));
            }

            if (config.getProperties().getProperty(ACCURACYMILLIS) != null) {
                timeStampTokenGen
                        .setAccuracyMillis(Integer.parseInt(config.getProperties().getProperty(ACCURACYMILLIS)));
            }

            if (config.getProperties().getProperty(ACCURACYSECONDS) != null) {
                timeStampTokenGen
                        .setAccuracySeconds(Integer.parseInt(config.getProperties().getProperty(ACCURACYSECONDS)));
            }

            timeStampTokenGen.setOrdering(ordering);
            timeStampTokenGen.setIncludeOrdering(includeOrdering);

            if (tsaName != null) {
                final X500Name x500Name = new X500Name(tsaName);
                timeStampTokenGen.setTSA(new GeneralName(x500Name));
            } else if (tsaNameFromCert) {
                final X500Name x500Name = new JcaX509CertificateHolder(signingCert).getSubject();
                timeStampTokenGen.setTSA(new GeneralName(x500Name));
            }

            timeStampTokenGen
                    .addCertificates(getCertStoreWithChain(signingCert, getSigningCertificateChain(crypto)));

        } catch (IllegalArgumentException e) {
            LOG.error("IllegalArgumentException: ", e);
            throw new IllegalRequestException(e.getMessage());
        } catch (TSPException e) {
            LOG.error("TSPException: ", e);
            throw new IllegalRequestException(e.getMessage());
        } catch (CertificateEncodingException e) {
            LOG.error("CertificateEncodingException: ", e);
            throw new IllegalRequestException(e.getMessage());
        } catch (IOException e) {
            LOG.error("IOException: ", e);
            throw new IllegalRequestException(e.getMessage());
        }

        return timeStampTokenGen;
    }

    private TimeStampResponseGenerator getTimeStampResponseGenerator(TimeStampTokenGenerator timeStampTokenGen) {

        return new TimeStampResponseGenerator(timeStampTokenGen, this.getAcceptedAlgorithms(),
                this.getAcceptedPolicies(), this.getAcceptedExtensions());
    }

    /**
     * Help method that generates a serial number using SecureRandom.
     * Uses the configured length of the signer. This is public to allow using directly from
     * unit test.
     */
    public BigInteger getSerialNumber() throws SignServerException {
        BigInteger serialNumber = null;

        if (maxSerialNumberLength < MIN_ALLOWED_MAXSERIALNUMBERLENGTH
                || maxSerialNumberLength > MAX_ALLOWED_MAXSERIALNUMBERLENGTH) {
            throw new SignServerException("Maximum serial number length is not in allowed range");
        }

        try {
            serialNumber = getSerno(maxSerialNumberLength);
        } catch (Exception e) {
            LOG.error("Error initiating Serial Number generator, SEVERE ERROR.", e);
        }
        return serialNumber;
    }

    /**
     * Generates a number of serial number bytes. The number returned should
     * be a positive number.
     *
     * @param maxLength the maximum number of octects of the generated serial number
     * @return a BigInteger with a new random serial number.
     */
    public BigInteger getSerno(int maxLength) {
        if (random == null) {
            try {
                random = SecureRandom.getInstance(algorithm);
            } catch (NoSuchAlgorithmException e) {
                LOG.error(e);
            }
        }

        final byte[] sernobytes = new byte[maxLength];
        random.nextBytes(sernobytes);
        BigInteger serno = new BigInteger(sernobytes).abs();

        return serno;
    }

    private static class SHA1DigestCalculator implements DigestCalculator {
        private ByteArrayOutputStream bOut = new ByteArrayOutputStream();
        private MessageDigest digest;

        public SHA1DigestCalculator() {
            try {
                this.digest = MessageDigest.getInstance("SHA1");
            } catch (NoSuchAlgorithmException e) {

            }
        }

        public AlgorithmIdentifier getAlgorithmIdentifier() {
            return new AlgorithmIdentifier(OIWObjectIdentifiers.idSHA1);
        }

        public OutputStream getOutputStream() {
            return bOut;
        }

        public byte[] getDigest() {
            byte[] bytes = digest.digest(bOut.toByteArray());

            bOut.reset();

            return bytes;
        }
    }

    /**
     * @return True if each certificate in the certificate chain can be verified 
     * by the next certificate (if any). This does not check that the last 
     * certificate is a trusted certificate as the root certificate is normally 
     * not included.
     */
    private boolean validateChain() {
        boolean result = true;
        try {
            final List<Certificate> signingCertificateChain = getSigningCertificateChain();
            if (signingCertificateChain != null) {
                List<Certificate> chain = (List<Certificate>) signingCertificateChain;
                for (int i = 0; i < chain.size(); i++) {
                    Certificate subject = chain.get(i);

                    // If we have the issuer we can validate the certificate
                    if (chain.size() > i + 1) {
                        Certificate issuer = chain.get(i + 1);
                        try {
                            subject.verify(issuer.getPublicKey(), "BC");
                        } catch (CertificateException ex) {
                            if (LOG.isDebugEnabled()) {
                                LOG.debug("Certificate could not be verified: " + ex.getMessage() + ": " + subject);
                            }
                            result = false;
                        } catch (NoSuchAlgorithmException ex) {
                            if (LOG.isDebugEnabled()) {
                                LOG.debug("Certificate could not be verified: " + ex.getMessage() + ": " + subject);
                            }
                            result = false;
                        } catch (InvalidKeyException ex) {
                            if (LOG.isDebugEnabled()) {
                                LOG.debug("Certificate could not be verified: " + ex.getMessage() + ": " + subject);
                            }
                            result = false;
                        } catch (NoSuchProviderException ex) {
                            if (LOG.isDebugEnabled()) {
                                LOG.debug("Certificate could not be verified: " + ex.getMessage() + ": " + subject);
                            }
                            result = false;
                        } catch (SignatureException ex) {
                            if (LOG.isDebugEnabled()) {
                                LOG.debug("Certificate could not be verified: " + ex.getMessage() + ": " + subject);
                            }
                            result = false;
                        }
                    }
                }
            } else {
                // This would be a bug
                LOG.error("Certificate chain was not an list!");
                result = false;
            }
        } catch (CryptoTokenOfflineException ex) {
            if (LOG.isDebugEnabled()) {
                LOG.debug("Unable to get signer certificate or chain: " + ex.getMessage());
            }
            result = false;
        }
        return result;
    }

    @Override
    protected List<String> getFatalErrors() {
        final List<String> result = new LinkedList<String>();
        result.addAll(super.getFatalErrors());
        result.addAll(configErrors);

        try {
            // Check signer certificate chain if required
            if (!validChain) {
                result.add("Not strictly valid chain and " + REQUIREVALIDCHAIN + " specified");
                if (LOG.isDebugEnabled()) {
                    LOG.debug("Signer " + workerId + ": " + REQUIREVALIDCHAIN
                            + " specified but the chain was not found valid");
                }
            }

            // Check if certificat has the required EKU
            final Certificate certificate = getSigningCertificate();
            try {
                if (certificate instanceof X509Certificate) {
                    final X509Certificate cert = (X509Certificate) certificate;
                    final List<String> ekus = cert.getExtendedKeyUsage();

                    if (ekus == null || !ekus.contains(KeyPurposeId.id_kp_timeStamping.getId())) {
                        result.add("Missing extended key usage timeStamping");
                    }
                    if (cert.getCriticalExtensionOIDs() == null || !cert.getCriticalExtensionOIDs()
                            .contains(org.bouncycastle.asn1.x509.X509Extension.extendedKeyUsage.getId())) {
                        result.add("The extended key usage extension must be present and marked as critical");
                    }
                    // if extended key usage contains timeStamping and also other
                    // usages
                    if (ekus != null && ekus.contains(KeyPurposeId.id_kp_timeStamping.getId()) && ekus.size() > 1) {
                        result.add("No other extended key usages than timeStamping is allowed");
                    }
                } else {
                    result.add("Unsupported certificate type");
                }
            } catch (CertificateParsingException ex) {
                result.add("Unable to parse certificate");
                if (LOG.isDebugEnabled()) {
                    LOG.debug("Signer " + workerId + ": Unable to parse certificate: " + ex.getMessage());
                }
            }
        } catch (CryptoTokenOfflineException ex) {
            result.add("No signer certificate available");
            if (LOG.isDebugEnabled()) {
                LOG.debug("Signer " + workerId + ": Could not get signer certificate: " + ex.getMessage());
            }
        }

        // check time source
        if (timeSource.getGenTime() == null) {
            result.add("Time source not available");
            if (LOG.isDebugEnabled()) {
                LOG.debug("Signer " + workerId + ": time source not available");
            }
        }

        final String serialNumberError = getSerialNumberError();

        if (serialNumberError != null) {
            result.add(serialNumberError);
        }

        // check default policy
        if (defaultTSAPolicyOID == null) {
            result.add("No default TSA policy OID has been configured, or is invalid");
        }

        // check TSA naming properties conflict
        if (tsaName != null && tsaNameFromCert) {
            result.add("Can not set " + TSA_FROM_CERT + " to true and set " + TSA
                    + " worker property at the same time");
        }

        if (ordering && !includeOrdering) {
            result.add("INCLUDEORDERING can not be set to \"false\" when ORDERING is set to \"true\"");
        }

        return result;
    }

    /**
     * Get serial number error
     * We run this stand-alone from the unit test
     * 
     */
    protected String getSerialNumberError() {
        return serialNumberError;
    }

}