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

Java tutorial

Introduction

Here is the source code for org.signserver.module.tsa.TimeStampSignerTest.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.ByteArrayInputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.math.BigInteger;
import java.security.cert.Certificate;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.*;

import org.apache.log4j.Logger;
import org.bouncycastle.asn1.ASN1Boolean;
import org.bouncycastle.asn1.ASN1Encodable;
import org.bouncycastle.asn1.ASN1EncodableVector;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.ASN1OctetString;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.ASN1TaggedObject;
import org.bouncycastle.asn1.cmp.PKIFailureInfo;
import org.bouncycastle.asn1.cmp.PKIStatus;
import org.bouncycastle.asn1.cms.Attribute;
import org.bouncycastle.asn1.cms.AttributeTable;
import org.bouncycastle.asn1.cms.CMSAttributes;
import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import org.bouncycastle.asn1.x509.ExtendedKeyUsage;
import org.bouncycastle.asn1.x509.GeneralName;
import org.bouncycastle.asn1.x509.KeyPurposeId;
import org.bouncycastle.asn1.x509.X509Extension;
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
import org.bouncycastle.cert.jcajce.JcaX509CertificateHolder;
import org.bouncycastle.cms.SignerInformation;
import org.bouncycastle.cms.SignerInformationVerifier;
import org.bouncycastle.cms.jcajce.JcaSimpleSignerInfoVerifierBuilder;
import org.bouncycastle.tsp.*;
import org.ejbca.util.Base64;
import org.junit.After;
import org.junit.FixMethodOrder;
import org.junit.runners.MethodSorters;
import org.signserver.common.*;
import org.signserver.statusrepo.IStatusRepositorySession;
import org.signserver.statusrepo.common.StatusName;
import org.signserver.test.utils.builders.CertBuilder;
import org.signserver.test.utils.builders.CertExt;
import org.signserver.testutils.ModulesTestCase;
import org.signserver.testutils.TestUtils;
import org.signserver.testutils.TestingSecurityManager;

import org.junit.Before;
import org.junit.Test;
import org.signserver.ejb.interfaces.IWorkerSession;

/**
 * Tests for the TimeStampSigner.
 *
 * @version $Id: TimeStampSignerTest.java 5761 2015-02-23 14:16:28Z malu9369 $
 */
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class TimeStampSignerTest extends ModulesTestCase {

    /** Logger for class. */
    private static final Logger LOG = Logger.getLogger(TimeStampSignerTest.class);

    /** The status repository session. */
    private static IStatusRepositorySession.IRemote repository;

    /** Worker ID for test worker. */
    private static final int WORKER1 = 8901;

    /** Worker ID for test worker. */
    private static final int WORKER2 = 8902;

    /** Worker ID for test worker. */
    private static final int WORKER3 = 8903;

    /** Worker ID for test worker. */
    private static final int WORKER4 = 8904;

    /** Worker ID for test worker. */
    private static final int WORKER20 = 8920;

    /** BASE64-encoded cert for WORKER1 */
    private static String CERTSTRING = "MIIEkTCCAnmgAwIBAgIIeCvAS5OwAJswDQYJKoZIhvcNAQELBQAwTTEXMBUGA1UEAwwORFNTIFJvb3QgQ0EgMTAxEDAOBgNVBAsMB1Rlc3RpbmcxEzARBgNVBAoMClNpZ25TZXJ2ZXIxCzAJBgNVBAYTAlNFMB4XDTExMDUyNzEyMTU1NVoXDTIxMDUyNDEyMTU1NVowSjEUMBIGA1UEAwwLVFMgU2lnbmVyIDExEDAOBgNVBAsMB1Rlc3RpbmcxEzARBgNVBAoMClNpZ25TZXJ2ZXIxCzAJBgNVBAYTAlNFMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnT38GG8i/bGnuFMwnOdg+caHMkdPBacRdBaIggwMPfE50SOZ2TLrDEHJotxYda7HS0+tX5dIcalmEYCls/ptHzO5TQpqdRTuTqxp5cMA379yhD0OqTVNAmHrvPj9IytktoAtB/xcjwkRTHagaCmg5SWNcLKyVUct7nbeRA5yDSJQsCAEGHNZbJ50vATg1DQEyKT87GKfSBsclA0WIIIHMt8/SRhpsUZxESayU6YA4KCxVtexF5x+COLB6CzzlRG9JA8WpX9yKgIMsMDAscsJLiLPjhET5hwAFm5ZRfQQG9LI06QNTGqukuTlDbYrQGAUR5ZXW00WNHfgS00CjUCu0QIDAQABo3gwdjAdBgNVHQ4EFgQUOF0FflO2G+IN6c92pCNlPoorGVwwDAYDVR0TAQH/BAIwADAfBgNVHSMEGDAWgBQgeiHe6K27Aqj7cVikCWK52FgFojAOBgNVHQ8BAf8EBAMCB4AwFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwgwDQYJKoZIhvcNAQELBQADggIBADELkeIO9aiKjS/GaBUUhMr+k5UbVeK69WapU+7gTsWwa9D2vAOhAkfQ1OcUJoZaminv8pcNfo1Ey5qLtxBCmUy1fVomVWOPl6u1w8B6uYgE608hi2bfx28uIeksqpdqUX0Qf6ReUyl+FOh4xNrsyaF81TrIKt8ekq0iD+YAtT/jqgv4bUvs5fgIms4QOXgMUzNAP7cPU44KxcmR5I5Uy/Ag82hGIz64hZmeIDT0X59kbQvlZqFaiZvYOikoZSFvdM5kSVfItMgp7qmyLxuM/WaXqJWp6Mm+8ZZmcECugd4AEpE7xIiB7M/KEe+X4ItBNTKdAoaxWa+yeuYS7ol9rHt+Nogelj/06ZRQ0x03UqC7uKpgYAICjQEXIjcZofWSTh9KzKNfS1sQyIQ6yNTT2VMdYW9JC2OLKPV4AEJuBw30X8HOciJRRXOq9KRrIA2RSiaC5/3oAYscWuo31Fmj8CWQknXAIb39gPuZRwGOJbi1tUu2zmRsUNJfAe3hnvk+uxhnyp2vKB2KN5/VQgisx+8doEK/+Nbj/PPG/zASKimWG++5m0JNY4chIfR43gDDcF+4INof/8V84wbvUF+TpvP/mYM8wC9OkUyRvzqv9vjWOncCdbdjCuqPxDItwm9hhr+PbxsMaBes9rAiV9YT1FnpA++YpCufveFCQPDbCTgJ";

    /** Dummy OID used for testing an invalid hashing algorithm */
    private static String DUMMY_OID = "1.42.42.42.42";

    /** Expected values in the signingCertificate CMS attribute */
    private static final String SIGNING_CERT_OID = "1.2.840.113549.1.9.16.2.12";
    private static final String CN_OID = "2.5.4.3";
    private static final String OU_OID = "2.5.4.11";
    private static final String O_OID = "2.5.4.10";
    private static final String C_OID = "2.5.4.6";
    private static final String CN = "DSS Root CA 10";
    private static final String OU = "Testing";
    private static final String O = "SignServer";
    private static final String C = "SE";

    /**
     * Base64 encoded request with policy 1.2.3.5.
     * <pre>
     * Version: 1
     *  Hash Algorithm: sha1
     *  Message data:
     *      0000 - 32 a0 61 7a ab 4c 9f e7-25 f1 b5 bc 44 12 91 18
     *      0010 - 0a d2 5b 73
     *  Policy OID: 1.2.3.5
     *  Nonce: unspecified
     *  Certificate required: no
     *  Extensions:
     *  </pre>
     */
    private static final String REQUEST_WITH_POLICY1235 = "MCsCAQEwITAJBgUrDgMCGgUABBQyoGF6q0yf5yXxtbxEEpEYCtJbcwYDKgMF";

    private static String signserverhome;
    private static int moduleVersion;

    private Random random = new Random(4711);

    private final IWorkerSession workerSession = getWorkerSession();

    @Before
    public void setUp() throws Exception {
        SignServerUtil.installBCProvider();

        repository = ServiceLocator.getInstance().lookupRemote(IStatusRepositorySession.IRemote.class);
    }

    /* (non-Javadoc)
     * @see junit.framework.TestCase#tearDown()
     */
    @After
    public void tearDown() throws Exception {
        TestingSecurityManager.remove();
    }

    @Test
    public void test00SetupDatabase() throws Exception {
        addTimeStampSigner(WORKER1, "TestTSA1", true);
        workerSession.setWorkerProperty(WORKER1, "DEFAULTTSAPOLICYOID", "1.2.3");
        workerSession.reloadConfiguration(WORKER1);

        addTimeStampSigner(WORKER2, "TestTSA2", true);
        workerSession.setWorkerProperty(WORKER2, "DEFAULTTSAPOLICYOID", "1.2.3");
        workerSession.setWorkerProperty(WORKER2, "ACCEPTEDPOLICIES", "1.2.3");
        workerSession.reloadConfiguration(WORKER2);

        addTimeStampSigner(WORKER3, "TestTSA3", true);
        workerSession.setWorkerProperty(WORKER3, "DEFAULTTSAPOLICYOID", "1.2.3");
        workerSession.setWorkerProperty(WORKER3, "TIMESOURCE", "org.signserver.server.NullTimeSource");
        workerSession.reloadConfiguration(WORKER3);

        addTimeStampSigner(WORKER4, "TestTSA4", true);
        workerSession.setWorkerProperty(WORKER4, "DEFAULTTSAPOLICYOID", "1.2.3");
        workerSession.setWorkerProperty(WORKER4, "TIMESOURCE",
                "org.signserver.server.StatusReadingLocalComputerTimeSource");
        workerSession.reloadConfiguration(WORKER4);
    }

    @Test
    public void test01BasicTimeStamp() throws Exception {
        // Test signing
        final TimeStampResponse response = assertSuccessfulTimestamp(WORKER1, true);

        // Test that it is using the right algorithm
        final TimeStampToken token = response.getTimeStampToken();
        final SignerInformation si = (SignerInformation) token.toCMSSignedData().getSignerInfos().getSigners()
                .iterator().next();
        assertEquals("sha1withrsa", "1.2.840.113549.1.1.1", si.getEncryptionAlgOID());
    }

    /**
     * Test successfully doing a TSA request.
     * 
     * @param worker Worker ID
     * @param includeSigningTime If the signingTime signed CMS attribute is expected or not
     * @return Time stamp response
     * @throws Exception
     */
    private TimeStampResponse assertSuccessfulTimestamp(int worker, final boolean includeSigningTime)
            throws Exception {
        int reqid = random.nextInt();

        TimeStampRequestGenerator timeStampRequestGenerator = new TimeStampRequestGenerator();
        TimeStampRequest timeStampRequest = timeStampRequestGenerator.generate(TSPAlgorithms.SHA1, new byte[20],
                BigInteger.valueOf(100));
        byte[] requestBytes = timeStampRequest.getEncoded();

        GenericSignRequest signRequest = new GenericSignRequest(reqid, requestBytes);

        final GenericSignResponse res = (GenericSignResponse) workerSession.process(worker, signRequest,
                new RequestContext());

        assertTrue(reqid == res.getRequestID());

        Certificate signercert = res.getSignerCertificate();

        assertNotNull(signercert);

        final TimeStampResponse timeStampResponse = new TimeStampResponse((byte[]) res.getProcessedData());
        timeStampResponse.validate(timeStampRequest);

        assertEquals("Token granted", PKIStatus.GRANTED, timeStampResponse.getStatus());
        assertNotNull("Got timestamp token", timeStampResponse.getTimeStampToken());

        // Validate the signature of the token
        try {
            final SignerInformationVerifier infoVerifier = new JcaSimpleSignerInfoVerifierBuilder()
                    .setProvider("BC").build((X509Certificate) signercert);
            timeStampResponse.getTimeStampToken().validate(infoVerifier);
        } catch (TSPValidationException ex) {
            LOG.error("Token validation failed", ex);
            fail("Token validation failed: " + ex.getMessage());
        }

        // check the signingTime signed attribute
        final AttributeTable attrs = timeStampResponse.getTimeStampToken().getSignedAttributes();
        final Attribute attr = attrs.get(CMSAttributes.signingTime);

        if (includeSigningTime) {
            assertNotNull("Should contain signingTime signed attribute", attr);
        } else {
            assertNull("Should not contain signingTime signed attribute", attr);
        }

        return timeStampResponse;
    }

    /**
     * Return raw data of a TSA request's response.
     * 
     * @param worker
     * @return
     * @throws Exception
     */
    private byte[] getResponseData(int worker) throws Exception {
        int reqid = random.nextInt();

        TimeStampRequestGenerator timeStampRequestGenerator = new TimeStampRequestGenerator();
        TimeStampRequest timeStampRequest = timeStampRequestGenerator.generate(TSPAlgorithms.SHA1, new byte[20],
                BigInteger.valueOf(100));
        byte[] requestBytes = timeStampRequest.getEncoded();

        GenericSignRequest signRequest = new GenericSignRequest(reqid, requestBytes);

        final GenericSignResponse res = (GenericSignResponse) workerSession.process(worker, signRequest,
                new RequestContext());

        return res.getProcessedData();
    }

    /**
     * Tests the status returned by the worker.
     */
    @Test
    public void test02GetStatus() throws Exception {
        StaticWorkerStatus stat = (StaticWorkerStatus) workerSession.getStatus(8901);
        assertEquals("token status", WorkerStatus.STATUS_ACTIVE, stat.getTokenStatus());
        assertEquals("ALLOK: " + stat.getFatalErrors(), 0, stat.getFatalErrors().size());
    }

    /**
     * Test that a timestamp token is not granted for an policy not listed in
     * ACCEPTEDPOLICIES and that a proper resoonse is sent back.
     * @throws Exception in case of exception
     */
    @Test
    public void test03NotAcceptedPolicy() throws Exception {
        // WORKER2 has ACCEPTEDPOLICIES=1.2.3
        // Create an request with another policy (1.2.3.5 != 1.2.3)
        final TimeStampRequest timeStampRequest = new TimeStampRequest(
                Base64.decode(REQUEST_WITH_POLICY1235.getBytes()));

        final byte[] requestBytes = timeStampRequest.getEncoded();

        final GenericSignRequest signRequest = new GenericSignRequest(13, requestBytes);

        final GenericSignResponse res = (GenericSignResponse) workerSession.process(WORKER2, signRequest,
                new RequestContext());

        final TimeStampResponse timeStampResponse = new TimeStampResponse((byte[]) res.getProcessedData());
        timeStampResponse.validate(timeStampRequest);

        LOG.info("Response: " + timeStampResponse.getStatusString());

        assertEquals("Token rejected", PKIStatus.REJECTION, timeStampResponse.getStatus());
    }

    /**
     * Tests that the timestamp signer returnes a time stamp response with
     * the timeNotAvailable status if the Date is null.
     * @throws Exception in case of exception
     */
    @Test
    public void test04timeNotAvailable() throws Exception {
        assertTimeNotAvailable(WORKER3);
    }

    /**
     * Tests that the timestamp is only granted when the INSYNC property
     * is set.
     * @throws Exception in case of exception
     */
    @Test
    public void test05ReadingStatusTimeSource() throws Exception {
        // Test with insync
        repository.update(StatusName.TIMESOURCE0_INSYNC.name(), "true");
        assertSuccessfulTimestamp(WORKER4, true);

        // Test without insync
        repository.update(StatusName.TIMESOURCE0_INSYNC.name(), "");
        assertTimeNotAvailable(WORKER4);
    }

    /**
     * Utility method to return the hash length for the hash types we're testing against
     * 
     * @param hashType
     * @return
     */
    private int getHashLength(ASN1ObjectIdentifier hashType) {
        if (TSPAlgorithms.SHA1.equals(hashType)) {
            return 20;
        } else if (TSPAlgorithms.SHA256.equals(hashType)) {
            return 32;
        } else if (TSPAlgorithms.SHA512.equals(hashType)) {
            return 64;
        } else if (TSPAlgorithms.RIPEMD160.equals(hashType)) {
            return 20;
        } else {
            LOG.info("Trying to use an unknow hash algorithm, using dummy length");
            // return the length of a SHA1 hash as a dummy value to allow passing
            // invalid hash algo names for testing
            return 20;
        }
    }

    private int testWithHash(final ASN1ObjectIdentifier hashAlgo) throws Exception {
        int reqid = random.nextInt();
        TimeStampRequestGenerator timeStampRequestGenerator = new TimeStampRequestGenerator();
        final TimeStampRequest timeStampRequest = timeStampRequestGenerator.generate(hashAlgo,
                new byte[getHashLength(hashAlgo)], BigInteger.valueOf(100));

        byte[] requestBytes = timeStampRequest.getEncoded();

        GenericSignRequest signRequest = new GenericSignRequest(reqid, requestBytes);

        final GenericSignResponse res = (GenericSignResponse) workerSession.process(WORKER1, signRequest,
                new RequestContext());

        final CertificateFactory factory = CertificateFactory.getInstance("X.509");
        final X509Certificate cert = (X509Certificate) factory
                .generateCertificate(new ByteArrayInputStream(Base64.decode(CERTSTRING.getBytes())));

        TimeStampResponse timeStampResponse = null;
        try {
            // check response
            timeStampResponse = new TimeStampResponse((byte[]) res.getProcessedData());
            timeStampResponse.validate(timeStampRequest);

            if (timeStampResponse.getStatus() != PKIStatus.GRANTED) {
                // return early and don't attempt to get a token
                return timeStampResponse.getStatus();
            }

            // check the hash value from the response
            TimeStampToken token = timeStampResponse.getTimeStampToken();
            AlgorithmIdentifier algo = token.getTimeStampInfo().getHashAlgorithm();
            assertEquals("Timestamp response is using incorrect hash algorithm", hashAlgo, algo.getAlgorithm());

            Collection signerInfos = token.toCMSSignedData().getSignerInfos().getSigners();

            // there should be one SignerInfo
            assertEquals("There should only be one signer in the timestamp response", 1, signerInfos.size());

            for (Object o : signerInfos) {
                SignerInformation si = (SignerInformation) o;

                // test the response signature algorithm
                assertEquals("Timestamp used unexpected signature algorithm", TSPAlgorithms.SHA1.toString(),
                        si.getDigestAlgOID());
                assertEquals("Timestamp is signed with unexpected signature encryption algorithm",
                        "1.2.840.113549.1.1.1", si.getEncryptionAlgOID());

                final AttributeTable attrs = si.getSignedAttributes();
                final ASN1EncodableVector scAttrs = attrs.getAll(PKCSObjectIdentifiers.id_aa_signingCertificate);

                assertEquals("Should contain a signingCertificate signed attribute", 1, scAttrs.size());

                TestUtils.checkSigningCertificateAttribute(ASN1Sequence.getInstance(scAttrs.get(0)), cert);
            }

        } catch (TSPException e) {
            fail("Failed to verify response");
        } catch (IOException e) {
            fail("Failed to verify response");
        }

        final TimeStampToken token = timeStampResponse.getTimeStampToken();

        try {

            token.validate(cert, "BC");

        } catch (TSPException e) {
            fail("Failed to validate response token");
        }

        return timeStampResponse.getStatus();
    }

    /**
     * Tests requesting a timetamp with SHA256 as the hash algorithm
     * verify the hash and signature algortithms of the respons token
     * 
     * @throws Exception
     */
    @Test
    public void test06HashSHA256() throws Exception {
        testWithHash(TSPAlgorithms.SHA256);
    }

    /**
     * Test requesting a timestamp with SHA512 as the hash algorithm
     * 
     * @param worker
     * @throws Exception
     */
    @Test
    public void test07HashSHA512() throws Exception {
        testWithHash(TSPAlgorithms.SHA512);
    }

    /**
     * Test requesting a timestamp with RIPEMD160 as the hash algorithm
     * 
     * @param worker
     * @throws Exception
     */
    @Test
    public void test08HashRIPE160() throws Exception {
        testWithHash(TSPAlgorithms.RIPEMD160);
    }

    /**
     * Test requesting a timestamp with a hash algorithm not included in the accepted
     * algorithms list
     * 
     * @param worker
     * @throws Exception
     */
    @Test
    public void test09HashWithNotAllowedAlgorithm() throws Exception {
        // set accepted algorithms to SHA1
        workerSession.setWorkerProperty(WORKER1, TimeStampSigner.ACCEPTEDALGORITHMS, "SHA1");
        workerSession.reloadConfiguration(WORKER1);

        int status = testWithHash(TSPAlgorithms.SHA256);
        assertEquals("Should return status REJECTION", PKIStatus.REJECTION, status);
    }

    /**
     * Test request a timestamp using a made-up dummy hash algorithm name
     * 
     * @param worker
     * @throws Exception
     */
    @Test
    public void test10HashWithIllegalAlgorithm() throws Exception {
        // reset accepted algorithms
        workerSession.removeWorkerProperty(WORKER1, TimeStampSigner.ACCEPTEDALGORITHMS);
        workerSession.reloadConfiguration(WORKER1);

        ASN1ObjectIdentifier oid = new ASN1ObjectIdentifier(DUMMY_OID);
        int status = testWithHash(oid);

        assertEquals("Should not accept an invalid hash algorithm", PKIStatus.REJECTION, status);
    }

    /**
     * Test setting ACCEPTEDALGORITHMS and sign using that hash algorithm
     * 
     * @param worker
     * @throws Exception
     */
    @Test
    public void test11HashWithAllowedAlgorithm() throws Exception {
        // set accepted algorithms to SHA1
        workerSession.setWorkerProperty(WORKER1, TimeStampSigner.ACCEPTEDALGORITHMS, "SHA1");
        workerSession.reloadConfiguration(WORKER1);

        int status = testWithHash(TSPAlgorithms.SHA1);
        assertEquals("Should return status GRANTED", PKIStatus.GRANTED, status);
    }

    private void assertTimeNotAvailable(int worker) throws Exception {
        final int reqid = random.nextInt();

        final TimeStampRequestGenerator timeStampRequestGenerator = new TimeStampRequestGenerator();
        final TimeStampRequest timeStampRequest = timeStampRequestGenerator.generate(TSPAlgorithms.SHA1,
                new byte[20], BigInteger.valueOf(114));
        final byte[] requestBytes = timeStampRequest.getEncoded();

        final GenericSignRequest signRequest = new GenericSignRequest(reqid, requestBytes);

        final GenericSignResponse res = (GenericSignResponse) workerSession.process(worker, signRequest,
                new RequestContext());

        assertTrue(reqid == res.getRequestID());

        final TimeStampResponse timeStampResponse = new TimeStampResponse((byte[]) res.getProcessedData());
        timeStampResponse.validate(timeStampRequest);

        LOG.info("Response: " + timeStampResponse.getStatusString());

        assertEquals("Token not granted", PKIStatus.REJECTION, timeStampResponse.getStatus());

        assertEquals("PKIFailureInfo.timeNotAvailable", new PKIFailureInfo(PKIFailureInfo.timeNotAvailable),
                timeStampResponse.getFailInfo());

        assertNull("No timestamp token", timeStampResponse.getTimeStampToken());
    }

    /**
     * Check that we either include the signer certificate if it is missing or
     * otherwise fails the request.
     * 
     * In addition Health check should also report an error for this.
     * 
     * RFC#3161 2.4.1:
     * "If the certReq field is present and set to true, the TSA's public key
     *  certificate that is referenced by the ESSCertID identifier inside a
     *  SigningCertificate attribute in the response MUST be provided by the
     *  TSA in the certificates field from the SignedData structure in that
     *  response.  That field may also contain other certificates."
     */
    @Test
    public void test09SignerCertificateMustBeIncluded() throws Exception {
        List<Certificate> chain = workerSession.getSignerCertificateChain(WORKER2);
        final X509Certificate subject = (X509Certificate) chain.get(0);
        X509Certificate issuer = (X509Certificate) chain.get(1);

        // Now, don't include the signer certificate in the chain
        // For some reason we need to upload the signer certificate again :S
        workerSession.uploadSignerCertificate(WORKER2, subject.getEncoded(), GlobalConfiguration.SCOPE_GLOBAL);
        workerSession.uploadSignerCertificateChain(WORKER2, Arrays.asList(issuer.getEncoded()),
                GlobalConfiguration.SCOPE_GLOBAL);
        workerSession.reloadConfiguration(WORKER2);

        if (!subject.equals(workerSession.getSignerCertificate(WORKER2))) {
            LOG.info("Subject: " + subject);
            LOG.info("Signer: " + workerSession.getSignerCertificate(WORKER2));
            throw new Exception("Something is fishy. Test assumed the signer certificate to be present");
        }
        // Test the status of the worker
        WorkerStatus actualStatus = workerSession.getStatus(WORKER2);
        assertEquals("should be error as signer certificate is not included in chain", 1,
                actualStatus.getFatalErrors().size());
        assertTrue("error should talk about missing signer certificate: " + actualStatus.getFatalErrors(),
                actualStatus.getFatalErrors().get(0).contains("ertificate"));

        // Send a request including certificates
        TimeStampRequestGenerator timeStampRequestGenerator = new TimeStampRequestGenerator();
        timeStampRequestGenerator.setCertReq(true);
        TimeStampRequest timeStampRequest = timeStampRequestGenerator.generate(TSPAlgorithms.SHA1, new byte[20],
                BigInteger.valueOf(100));
        byte[] requestBytes = timeStampRequest.getEncoded();
        GenericSignRequest signRequest = new GenericSignRequest(123124, requestBytes);
        try {
            final GenericSignResponse res = (GenericSignResponse) workerSession.process(WORKER2, signRequest,
                    new RequestContext());

            final TimeStampResponse timeStampResponse = new TimeStampResponse((byte[]) res.getProcessedData());
            timeStampResponse.validate(timeStampRequest);

            if (PKIStatus.GRANTED == timeStampResponse.getStatus()) {
                fail("Should have failed as the signer is miss-configured");
            }
        } catch (CryptoTokenOfflineException ex) {
            assertTrue("message should talk about missing signer certificate",
                    ex.getMessage().contains("igner certificate"));
        } finally {
            // Restore
            workerSession.uploadSignerCertificate(WORKER2, subject.getEncoded(), GlobalConfiguration.SCOPE_GLOBAL);
            workerSession.uploadSignerCertificateChain(WORKER2, asListOfByteArrays(chain),
                    GlobalConfiguration.SCOPE_GLOBAL);
            workerSession.reloadConfiguration(WORKER2);
        }
    }

    /**
     * Tests that status is not OK and that an failure is generated when trying
     * to sign when the right signer certificate is not configured.
     *
     */
    @Test
    public void test10WrongSignerCertificate() throws Exception {
        final List<Certificate> chain = workerSession.getSignerCertificateChain(WORKER2);
        final X509Certificate subject = (X509Certificate) workerSession.getSignerCertificate(WORKER2);

        // Any other certificate that will no match the key-pair
        final X509Certificate other = new JcaX509CertificateConverter()
                .getCertificate(new CertBuilder().setSubject("CN=Other")
                        .addExtension(new CertExt(org.bouncycastle.asn1.x509.X509Extension.extendedKeyUsage, true,
                                new ExtendedKeyUsage(KeyPurposeId.id_kp_timeStamping)))
                        .build());

        try {
            // Use the other certificate which will not match the key + the right cert in chain        
            workerSession.uploadSignerCertificate(WORKER2, other.getEncoded(), GlobalConfiguration.SCOPE_GLOBAL);
            workerSession.uploadSignerCertificateChain(WORKER2, Arrays.asList(subject.getEncoded()),
                    GlobalConfiguration.SCOPE_GLOBAL);
            workerSession.reloadConfiguration(WORKER2);

            // Test the status of the worker
            WorkerStatus actualStatus = workerSession.getStatus(WORKER2);
            assertEquals("should be error as the right signer certificate is not configured", 2,
                    actualStatus.getFatalErrors().size());
            assertTrue("error should talk about incorrect signer certificate: " + actualStatus.getFatalErrors(),
                    actualStatus.getFatalErrors().get(0).contains("ertificate"));

            // Send a request including certificates
            TimeStampRequestGenerator timeStampRequestGenerator = new TimeStampRequestGenerator();
            timeStampRequestGenerator.setCertReq(true);
            TimeStampRequest timeStampRequest = timeStampRequestGenerator.generate(TSPAlgorithms.SHA1, new byte[20],
                    BigInteger.valueOf(100));
            byte[] requestBytes = timeStampRequest.getEncoded();
            GenericSignRequest signRequest = new GenericSignRequest(123124, requestBytes);
            try {
                final GenericSignResponse res = (GenericSignResponse) workerSession.process(WORKER2, signRequest,
                        new RequestContext());

                final TimeStampResponse timeStampResponse = new TimeStampResponse((byte[]) res.getProcessedData());
                timeStampResponse.validate(timeStampRequest);

                if (PKIStatus.GRANTED == timeStampResponse.getStatus()) {
                    fail("Should have failed as the signer is miss-configured");
                }
            } catch (CryptoTokenOfflineException ex) {
                assertTrue("message should talk about incorrect signer certificate",
                        ex.getMessage().contains("igner certificate"));
            }
        } finally {
            // Restore
            workerSession.uploadSignerCertificate(WORKER2, subject.getEncoded(), GlobalConfiguration.SCOPE_GLOBAL);
            workerSession.uploadSignerCertificateChain(WORKER2, asListOfByteArrays(chain),
                    GlobalConfiguration.SCOPE_GLOBAL);
            workerSession.reloadConfiguration(WORKER2);
        }
    }

    /**
     * Tests that status is not OK and that an failure is generated when trying
     * to sign when the right signer certificate is not configured in the 
     * certificate chain property.
     *
     */
    @Test
    public void test10WrongSignerCertificate_InChain() throws Exception {
        final List<Certificate> chain = workerSession.getSignerCertificateChain(WORKER2);
        final X509Certificate subject = (X509Certificate) workerSession.getSignerCertificate(WORKER2);

        // Any other certificate that will no match the key-pair
        final X509Certificate other = new JcaX509CertificateConverter()
                .getCertificate(new CertBuilder().setSubject("CN=Other").build());

        try {
            // Use the right certificate but the other in the certificate chain
            workerSession.uploadSignerCertificate(WORKER2, subject.getEncoded(), GlobalConfiguration.SCOPE_GLOBAL);
            workerSession.uploadSignerCertificateChain(WORKER2, Arrays.asList(other.getEncoded()),
                    GlobalConfiguration.SCOPE_GLOBAL);
            workerSession.reloadConfiguration(WORKER2);

            // Test the status of the worker
            WorkerStatus actualStatus = workerSession.getStatus(WORKER2);
            assertEquals("should be error as the right signer certificate is not configured", 1,
                    actualStatus.getFatalErrors().size());
            assertTrue("error should talk about incorrect signer certificate: " + actualStatus.getFatalErrors(),
                    actualStatus.getFatalErrors().get(0).contains("ertificate"));

            // Send a request including certificates
            TimeStampRequestGenerator timeStampRequestGenerator = new TimeStampRequestGenerator();
            timeStampRequestGenerator.setCertReq(true);
            TimeStampRequest timeStampRequest = timeStampRequestGenerator.generate(TSPAlgorithms.SHA1, new byte[20],
                    BigInteger.valueOf(100));
            byte[] requestBytes = timeStampRequest.getEncoded();
            GenericSignRequest signRequest = new GenericSignRequest(123124, requestBytes);
            try {
                final GenericSignResponse res = (GenericSignResponse) workerSession.process(WORKER2, signRequest,
                        new RequestContext());

                final TimeStampResponse timeStampResponse = new TimeStampResponse((byte[]) res.getProcessedData());
                timeStampResponse.validate(timeStampRequest);

                if (PKIStatus.GRANTED == timeStampResponse.getStatus()) {
                    fail("Should have failed as the signer is miss-configured");
                }
            } catch (CryptoTokenOfflineException ex) {
                assertTrue("message should talk about incorrect signer certificate",
                        ex.getMessage().contains("igner certificate"));
            }
        } finally {
            // Restore
            workerSession.uploadSignerCertificate(WORKER2, subject.getEncoded(), GlobalConfiguration.SCOPE_GLOBAL);
            workerSession.uploadSignerCertificateChain(WORKER2, asListOfByteArrays(chain),
                    GlobalConfiguration.SCOPE_GLOBAL);
            workerSession.reloadConfiguration(WORKER2);
        }
    }

    private Collection<byte[]> asListOfByteArrays(List<Certificate> chain) throws CertificateEncodingException {
        ArrayList results = new ArrayList(chain.size());
        for (Certificate c : chain) {
            results.add(c.getEncoded());
        }
        return results;
    }

    /**
     * Tests that if REQUIREVALIDCHAIN=true is specified only the signer certificate
     * and its issuer (and its issuer and so on...) is allowed in the chain.
     * Also tests that the default is to not do this check.
     */
    @Test
    public void test11RequireValidChain() throws Exception {

        // First make sure we don't have this property set
        workerSession.removeWorkerProperty(WORKER1, "REQUIREVALIDCHAIN");

        // Setup an invalid chain
        final List<Certificate> chain = workerSession.getSignerCertificateChain(WORKER1);
        final X509Certificate subject = (X509Certificate) workerSession.getSignerCertificate(WORKER1);

        // Any other certificate that will no match the key-pair
        final X509Certificate other = new JcaX509CertificateConverter()
                .getCertificate(new CertBuilder().setSubject("CN=Other cert").build());

        try {
            // An not strictly valid chain as it contains an additional certificate at the end
            // (In same use cases this might be okey but now we are testing the 
            //  strict checking with the REQUIREVALIDCHAIN property set)
            List<Certificate> ourChain = new LinkedList<Certificate>();
            ourChain.addAll(chain);
            ourChain.add(other);
            workerSession.uploadSignerCertificate(WORKER1, subject.getEncoded(), GlobalConfiguration.SCOPE_GLOBAL);
            workerSession.uploadSignerCertificateChain(WORKER1, asListOfByteArrays(ourChain),
                    GlobalConfiguration.SCOPE_GLOBAL);
            workerSession.reloadConfiguration(WORKER1);

            // Test the status of the worker: should be ok as we aren't doing strict checking
            WorkerStatus actualStatus = workerSession.getStatus(WORKER1);
            assertEquals("should be okey as aren't doing strict checking", 0, actualStatus.getFatalErrors().size());
            // Test signing: should also be ok
            assertTokenGranted(WORKER1);

            // Now change to strict checking
            workerSession.setWorkerProperty(WORKER1, "REQUIREVALIDCHAIN", "true");
            workerSession.reloadConfiguration(WORKER1);

            // Test the status of the worker: should be offline as we don't have a valid chain
            actualStatus = workerSession.getStatus(WORKER1);
            assertEquals("should be offline as we don't have a valid chain", 1,
                    actualStatus.getFatalErrors().size());
            // Test signing: should give error
            assertTokenNotGranted(WORKER1);

        } finally {
            // Restore
            workerSession.uploadSignerCertificate(WORKER1, subject.getEncoded(), GlobalConfiguration.SCOPE_GLOBAL);
            workerSession.uploadSignerCertificateChain(WORKER1, asListOfByteArrays(chain),
                    GlobalConfiguration.SCOPE_GLOBAL);
            workerSession.reloadConfiguration(WORKER1);
        }

    }

    /**
     * Tests that status is not OK and that an failure is generated when trying
     * to sign when the right signer certificate is not configured.
     *
     */
    @Test
    public void test12WrongEkuInSignerCertificate() throws Exception {

        final List<Certificate> chain = workerSession.getSignerCertificateChain(WORKER2);
        final X509Certificate subject = (X509Certificate) workerSession.getSignerCertificate(WORKER2);

        // Certifiate without id_kp_timeStamping
        final X509Certificate certNoEku = new JcaX509CertificateConverter().getCertificate(
                new CertBuilder().setSubject("CN=Without EKU").setSubjectPublicKey(subject.getPublicKey()).build());

        // Certificate with non-critical id_kp_timeStamping
        boolean critical = false;
        final X509Certificate certEku = new JcaX509CertificateConverter().getCertificate(
                new CertBuilder().setSubject("CN=With non-critical EKU").setSubjectPublicKey(subject.getPublicKey())
                        .addExtension(new CertExt(X509Extension.extendedKeyUsage, critical,
                                new ExtendedKeyUsage(KeyPurposeId.id_kp_timeStamping)))
                        .build());

        // OK: Certificate with critical id_kp_timeStamping
        critical = true;
        final X509Certificate certCritEku = new JcaX509CertificateConverter().getCertificate(
                new CertBuilder().setSubject("CN=With critical EKU").setSubjectPublicKey(subject.getPublicKey())
                        .addExtension(new CertExt(X509Extension.extendedKeyUsage, critical,
                                new ExtendedKeyUsage(KeyPurposeId.id_kp_timeStamping)))
                        .build());

        // Certificate with additional extended key usage, besides id_kp_timeStamping
        final X509Certificate certCritEkuAndAdditional = new JcaX509CertificateConverter().getCertificate(
                new CertBuilder().setSubject("CN=With critical EKU").setSubjectPublicKey(subject.getPublicKey())
                        .addExtension(new CertExt(X509Extension.extendedKeyUsage, critical,
                                new ExtendedKeyUsage(new KeyPurposeId[] { KeyPurposeId.id_kp_timeStamping,
                                        KeyPurposeId.id_kp_emailProtection })))
                        .build());

        try {
            // Fail: No id_kp_timeStamping
            workerSession.uploadSignerCertificate(WORKER2, certNoEku.getEncoded(),
                    GlobalConfiguration.SCOPE_GLOBAL);
            workerSession.uploadSignerCertificateChain(WORKER2, Arrays.asList(certNoEku.getEncoded()),
                    GlobalConfiguration.SCOPE_GLOBAL);
            workerSession.reloadConfiguration(WORKER2);
            WorkerStatus actualStatus = workerSession.getStatus(WORKER2);
            List<String> errors = actualStatus.getFatalErrors();
            String errorsString = errors.toString();
            // should be error as the signer certificate is missing id_kp_timeStamping and EKU is not critical
            LOG.info("errorsString: " + errorsString);
            assertEquals(2, errors.size());
            assertTrue("error should talk about missing extended key usage timeStamping: " + errorsString,
                    errorsString.contains("timeStamping")); // Will need adjustment if language changes
            assertTrue("error should talk about missing critical extension: " + errorsString,
                    errorsString.contains("critical")); // Will need adjustment if language changes

            // Ok: Certificate with critical id_kp_timeStamping
            workerSession.uploadSignerCertificate(WORKER2, certCritEku.getEncoded(),
                    GlobalConfiguration.SCOPE_GLOBAL);
            workerSession.uploadSignerCertificateChain(WORKER2, Arrays.asList(certCritEku.getEncoded()),
                    GlobalConfiguration.SCOPE_GLOBAL);
            workerSession.reloadConfiguration(WORKER2);
            actualStatus = workerSession.getStatus(WORKER2);
            assertEquals(0, actualStatus.getFatalErrors().size());

            // Fail: No non-critical id_kp_timeStamping
            workerSession.uploadSignerCertificate(WORKER2, certEku.getEncoded(), GlobalConfiguration.SCOPE_GLOBAL);
            workerSession.uploadSignerCertificateChain(WORKER2, Arrays.asList(certEku.getEncoded()),
                    GlobalConfiguration.SCOPE_GLOBAL);
            workerSession.reloadConfiguration(WORKER2);
            actualStatus = workerSession.getStatus(WORKER2);
            errorsString = errors.toString();
            // should be error as the signer certificate is missing id_kp_timeStamping
            assertEquals(1, actualStatus.getFatalErrors().size());
            // error should talk about missing critical EKU
            assertTrue("errorString: " + errorsString, errorsString.contains("critical")); // Will need adjustment if language changes

            // Fail: Additional EKU
            workerSession.uploadSignerCertificate(WORKER2, certCritEkuAndAdditional.getEncoded(),
                    GlobalConfiguration.SCOPE_GLOBAL);
            workerSession.uploadSignerCertificateChain(WORKER2,
                    Arrays.asList(certCritEkuAndAdditional.getEncoded()), GlobalConfiguration.SCOPE_GLOBAL);
            workerSession.reloadConfiguration(WORKER2);
            actualStatus = workerSession.getStatus(WORKER2);
            // should be error as the signer certificate is missing id_kp_timeStamping
            assertEquals(1, actualStatus.getFatalErrors().size());
            errorsString = actualStatus.getFatalErrors().toString();
            // error should talk about missing critical EKU
            assertTrue("Should mention additional extended key usages: " + errorsString,
                    errorsString.contains("No other extended key usages than timeStamping is allowed")); // Will need adjustment if language changes

        } finally {
            // Restore
            workerSession.uploadSignerCertificate(WORKER2, subject.getEncoded(), GlobalConfiguration.SCOPE_GLOBAL);
            workerSession.uploadSignerCertificateChain(WORKER2, asListOfByteArrays(chain),
                    GlobalConfiguration.SCOPE_GLOBAL);
            workerSession.reloadConfiguration(WORKER2);
        }
    }

    /** Tests issuance of time-stamp token when an EC key is specified. */
    @Test
    public void test20BasicTimeStampECDSA() throws Exception {
        final int workerId = WORKER20;
        try {
            // Setup signer
            final File keystore = new File(getSignServerHome(), "res/test/dss10/dss10_signer5ec.p12");
            if (!keystore.exists()) {
                throw new FileNotFoundException(keystore.getAbsolutePath());
            }
            addP12DummySigner(TimeStampSigner.class.getName(), workerId, "TestTimeStampP12ECDSA", keystore,
                    "foo123", "signerec");
            workerSession.setWorkerProperty(workerId, "DEFAULTTSAPOLICYOID", "1.2.3");
            workerSession.setWorkerProperty(workerId, "SIGNATUREALGORITHM", "SHA1WithECDSA");
            workerSession.reloadConfiguration(workerId);

            // Test signing
            TimeStampResponse response = assertSuccessfulTimestamp(WORKER20, true);

            // Test that it is using the right algorithm
            TimeStampToken token = response.getTimeStampToken();
            SignerInformation si = (SignerInformation) token.toCMSSignedData().getSignerInfos().getSigners()
                    .iterator().next();
            assertEquals("sha1withecdsa", "1.2.840.10045.4.1", si.getEncryptionAlgOID());

            // Test with SHA256WithECDSA
            workerSession.setWorkerProperty(workerId, "SIGNATUREALGORITHM", "SHA256WithECDSA");
            workerSession.reloadConfiguration(workerId);

            // Test signing
            response = assertSuccessfulTimestamp(WORKER20, true);

            // Test that it is using the right algorithm
            token = response.getTimeStampToken();
            si = (SignerInformation) token.toCMSSignedData().getSignerInfos().getSigners().iterator().next();
            assertEquals("sha256withecdsa", "1.2.840.10045.4.3.2", si.getEncryptionAlgOID());

        } finally {
            removeWorker(workerId);
        }
    }

    /** Tests issuance of time-stamp token when an DSA key is specified. */
    @Test
    public void test21BasicTimeStampDSA() throws Exception {
        final int workerId = WORKER20;
        try {
            // Setup signer
            final File keystore = new File(getSignServerHome(), "res/test/dss10/dss10_tssigner6dsa.jks");
            if (!keystore.exists()) {
                throw new FileNotFoundException(keystore.getAbsolutePath());
            }
            addJKSDummySigner(TimeStampSigner.class.getName(), workerId, "TestTimeStampJKSDSA", keystore, "foo123",
                    "mykey");
            workerSession.setWorkerProperty(workerId, "DEFAULTTSAPOLICYOID", "1.2.3");
            workerSession.setWorkerProperty(workerId, "SIGNATUREALGORITHM", "SHA1WithDSA");
            workerSession.reloadConfiguration(workerId);

            // Test signing
            TimeStampResponse response = assertSuccessfulTimestamp(WORKER20, true);

            // Test that it is using the right algorithm
            TimeStampToken token = response.getTimeStampToken();
            SignerInformation si = (SignerInformation) token.toCMSSignedData().getSignerInfos().getSigners()
                    .iterator().next();
            assertEquals("sha1withdsa", "1.2.840.10040.4.3", si.getEncryptionAlgOID());
        } finally {
            removeWorker(workerId);
        }
    }

    /**
     * Test with requestData of zero length. Shall give an IllegalRequestException.
     * @throws Exception
     */
    @Test
    public void test22EmptyRequest() throws Exception {
        int reqid = random.nextInt();
        byte[] requestBytes = new byte[0];

        GenericSignRequest signRequest = new GenericSignRequest(reqid, requestBytes);

        try {
            final GenericSignResponse res = (GenericSignResponse) workerSession.process(WORKER1, signRequest,
                    new RequestContext());
        } catch (IllegalRequestException e) {
            // expected
        } catch (Exception e) {
            fail("Unexpected exception thrown: " + e.getClass().getName());
        }
    }

    /**
     * Test with an invalid requestData. Shall give an IllegalRequestException.
     * @throws Exception
     */
    @Test
    public void test23BogusRequest() throws Exception {
        int reqid = random.nextInt();
        byte[] requestBytes = "bogus request".getBytes();

        GenericSignRequest signRequest = new GenericSignRequest(reqid, requestBytes);

        try {
            final GenericSignResponse res = (GenericSignResponse) workerSession.process(WORKER1, signRequest,
                    new RequestContext());
        } catch (IllegalRequestException e) {
            // expected
        } catch (Exception e) {
            fail("Unexpected exception thrown: " + e.getClass().getName());
        }
    }

    /**
     * Test with setting requestData to null. Shall give an IllegalRequestException.
     * @throws Exception
     */
    @Test
    public void test24NullRequest() throws Exception {
        int reqid = random.nextInt();
        byte[] requestBytes = "bogus request".getBytes();

        GenericSignRequest signRequest = new GenericSignRequest(reqid, requestBytes);

        try {
            final GenericSignResponse res = (GenericSignResponse) workerSession.process(WORKER1, signRequest,
                    new RequestContext());
        } catch (IllegalRequestException e) {
            // expected
        } catch (Exception e) {
            fail("Unexpected exception thrown: " + e.getClass().getName());
        }
    }

    /**
     * Test that the default behavior is to include the status string in the TSA response.
     * @throws Exception
     */
    @Test
    public void test25StatusStringIncluded() throws Exception {
        // Test signing
        final TimeStampResponse response = assertSuccessfulTimestamp(WORKER1, true);

        assertEquals("Operation Okay", response.getStatusString());
    }

    /**
     * Test that setting the INCLUDESTATUSSTRING property to false results in no status string
     * in the TSA response.
     * @throws Exception
     */
    @Test
    public void test26StatusStringExcluded() throws Exception {
        workerSession.setWorkerProperty(WORKER1, TimeStampSigner.INCLUDESTATUSSTRING, "FALSE");
        workerSession.reloadConfiguration(WORKER1);

        final TimeStampResponse response = assertSuccessfulTimestamp(WORKER1, true);

        assertNull(response.getStatusString());
    }

    /**
     * Test that the default behavior on rejection is to include a status string.
     * @throws Exception
     */
    @Test
    public void test27StatusStringIncludedFailure() throws Exception {
        // WORKER2 has ACCEPTEDPOLICIES=1.2.3
        // Create an request with another policy (1.2.3.5 != 1.2.3)
        final TimeStampRequest timeStampRequest = new TimeStampRequest(
                Base64.decode(REQUEST_WITH_POLICY1235.getBytes()));

        final byte[] requestBytes = timeStampRequest.getEncoded();

        final GenericSignRequest signRequest = new GenericSignRequest(13, requestBytes);

        final GenericSignResponse res = (GenericSignResponse) workerSession.process(WORKER2, signRequest,
                new RequestContext());

        final TimeStampResponse timeStampResponse = new TimeStampResponse((byte[]) res.getProcessedData());

        assertNotNull(timeStampResponse.getStatusString());
    }

    /**
     * Test that setting the INCLUDESTATUSSTRING property to false results in no status string
     * on rejection.
     * @throws Exception
     */
    @Test
    public void test28StatusStringExcludedFailure() throws Exception {
        workerSession.setWorkerProperty(WORKER2, TimeStampSigner.INCLUDESTATUSSTRING, "FALSE");
        workerSession.reloadConfiguration(WORKER2);
        // WORKER2 has ACCEPTEDPOLICIES=1.2.3
        // Create an request with another policy (1.2.3.5 != 1.2.3)
        final TimeStampRequest timeStampRequest = new TimeStampRequest(
                Base64.decode(REQUEST_WITH_POLICY1235.getBytes()));

        final byte[] requestBytes = timeStampRequest.getEncoded();

        final GenericSignRequest signRequest = new GenericSignRequest(13, requestBytes);

        final GenericSignResponse res = (GenericSignResponse) workerSession.process(WORKER2, signRequest,
                new RequestContext());

        final TimeStampResponse timeStampResponse = new TimeStampResponse((byte[]) res.getProcessedData());

        assertNull(timeStampResponse.getStatusString());
    }

    /**
     * Test that omitting a default policy OID results in the correct fatal error.
     * @throws Exception
     */
    @Test
    public void test29NoDefaultPolicyOID() throws Exception {
        workerSession.removeWorkerProperty(WORKER1, TimeStampSigner.DEFAULTTSAPOLICYOID);
        workerSession.reloadConfiguration(WORKER1);

        final WorkerStatus status = workerSession.getStatus(WORKER1);
        final List<String> errors = status.getFatalErrors();

        assertTrue("Should mention missing default policy OID: " + errors,
                errors.contains("No default TSA policy OID has been configured, or is invalid"));

        // restore
        workerSession.setWorkerProperty(WORKER1, TimeStampSigner.DEFAULTTSAPOLICYOID, "1.2.3");
        workerSession.reloadConfiguration(WORKER1);
    }

    /**
     * Test that setting an invalid default policy OID results in the correct fatal error.
     * @throws Exception
     */
    @Test
    public void test30BogusDefaultPolicyOID() throws Exception {
        workerSession.setWorkerProperty(WORKER1, TimeStampSigner.DEFAULTTSAPOLICYOID, "foobar");
        workerSession.reloadConfiguration(WORKER1);

        final WorkerStatus status = workerSession.getStatus(WORKER1);
        final List<String> errors = status.getFatalErrors();

        assertTrue("Should mention missing default policy OID: " + errors,
                errors.contains("No default TSA policy OID has been configured, or is invalid"));

        // restore
        workerSession.setWorkerProperty(WORKER1, TimeStampSigner.DEFAULTTSAPOLICYOID, "1.2.3");
        workerSession.reloadConfiguration(WORKER1);
    }

    /**
     * Test that the default behavior is to not include the TSA field.
     * @throws Exception
     */
    @Test
    public void test31NoTSAName() throws Exception {
        // Test signing
        final TimeStampResponse response = assertSuccessfulTimestamp(WORKER1, true);

        assertNull("No TSA set", response.getTimeStampToken().getTimeStampInfo().getTsa());
    }

    /**
     * Test setting the TSA worker property.
     * @throws Exception
     */
    @Test
    public void test32ExplicitTSAName() throws Exception {
        workerSession.setWorkerProperty(WORKER1, TimeStampSigner.TSA, "CN=test");
        workerSession.reloadConfiguration(WORKER1);

        final TimeStampResponse response = assertSuccessfulTimestamp(WORKER1, true);
        final GeneralName name = response.getTimeStampToken().getTimeStampInfo().getTsa();
        final GeneralName expectedName = new GeneralName(new X500Name("CN=test"));

        assertEquals("TSA included", expectedName, name);

        // restore
        workerSession.removeWorkerProperty(WORKER1, TimeStampSigner.TSA);
        workerSession.reloadConfiguration(WORKER1);
    }

    /**
     * Test using the TSA_FROM_CERT property to set the TSA name from
     * the signing cert.
     * 
     * @throws Exception
     */
    @Test
    public void test34TSANameFromCert() throws Exception {
        workerSession.setWorkerProperty(WORKER1, TimeStampSigner.TSA_FROM_CERT, "true");
        workerSession.reloadConfiguration(WORKER1);

        final TimeStampResponse response = assertSuccessfulTimestamp(WORKER1, true);
        final GeneralName name = response.getTimeStampToken().getTimeStampInfo().getTsa();
        final GeneralName expectedName = new GeneralName(
                new X500Name("CN=TS Signer 1,OU=Testing,O=SignServer,C=SE"));

        assertEquals("TSA included", expectedName, name);

        final GeneralName certName = new GeneralName(
                new JcaX509CertificateHolder((X509Certificate) workerSession.getSignerCertificate(WORKER1))
                        .getSubject());
        assertTrue("TSA name content equals cert", Arrays.equals(certName.getEncoded(), name.getEncoded()));

        // restore
        workerSession.removeWorkerProperty(WORKER1, TimeStampSigner.TSA_FROM_CERT);
        workerSession.reloadConfiguration(WORKER1);
    }

    /**
     * Test setting both the TSA and TSA_FROM_CERT property, should result in an error.
     * @throws Exception
     */
    @Test
    public void test35TSANameExplicitAndFromCert() throws Exception {
        workerSession.setWorkerProperty(WORKER1, TimeStampSigner.TSA, "CN=test");
        workerSession.setWorkerProperty(WORKER1, TimeStampSigner.TSA_FROM_CERT, "true");
        workerSession.reloadConfiguration(WORKER1);

        final WorkerStatus status = workerSession.getStatus(WORKER1);
        final List<String> errors = status.getFatalErrors();

        assertTrue("Should mention conflicting TSA properties: " + errors,
                errors.contains("Can not set " + TimeStampSigner.TSA_FROM_CERT + " to true and set "
                        + TimeStampSigner.TSA + " worker property at the same time"));

        // restore
        workerSession.removeWorkerProperty(WORKER1, TimeStampSigner.TSA);
        workerSession.removeWorkerProperty(WORKER1, TimeStampSigner.TSA_FROM_CERT);
        workerSession.reloadConfiguration(WORKER1);
    }

    /**
     * Test that excluding signingTime signed CMS attribute works.
     * @throws Exception
     */
    @Test
    public void test36noSigningTimeAttribute() throws Exception {
        workerSession.setWorkerProperty(WORKER1, TimeStampSigner.INCLUDESIGNINGTIMEATTRIBUTE, "false");
        workerSession.reloadConfiguration(WORKER1);

        assertSuccessfulTimestamp(WORKER1, false);
    }

    /**
     * Test that explicitly including the signingTime signed attribute works.
     * @throws Exception
     */
    @Test
    public void test37explicitlyIncludeSigningTime() throws Exception {
        workerSession.setWorkerProperty(WORKER1, TimeStampSigner.INCLUDESIGNINGTIMEATTRIBUTE, "true");
        workerSession.reloadConfiguration(WORKER1);

        assertSuccessfulTimestamp(WORKER1, true);
    }

    /**
     * Return the ASN1Sequence encapsulated in the tSTInfo structure.
     * 
     * @param res TSA response data.
     * @return The encoded sequence in TSTInfo (see the TSTInfo class).
     */
    private ASN1Sequence extractTstInfoSeq(final byte[] res) {
        final ASN1Sequence seq1 = ASN1Sequence.getInstance(res);
        final ASN1Sequence signedData = ASN1Sequence.getInstance(seq1.getObjectAt(1));
        final ASN1TaggedObject tag = ASN1TaggedObject.getInstance(signedData.getObjectAt(1));
        final ASN1Sequence seq2 = ASN1Sequence.getInstance(tag.getObject());
        final ASN1Sequence seq3 = ASN1Sequence.getInstance(seq2.getObjectAt(2));
        final ASN1TaggedObject tag2 = ASN1TaggedObject.getInstance(seq3.getObjectAt(1));
        final ASN1OctetString data = ASN1OctetString.getInstance(tag2.getObject());
        final ASN1Sequence result = ASN1Sequence.getInstance(data.getOctets());

        return result;
    }

    /**
     * Test that ordering is not included by default.
     * 
     * @throws Exception
     */
    @Test
    public void test38orderingDefault() throws Exception {
        // reset ORDERING property
        workerSession.removeWorkerProperty(WORKER1, TimeStampSigner.ORDERING);
        workerSession.reloadConfiguration(WORKER1);

        final byte[] res = getResponseData(WORKER1);
        final ASN1Sequence seq = extractTstInfoSeq(res);

        try {
            ASN1Boolean.getInstance(seq.getObjectAt(5));
        } catch (IllegalArgumentException e) {
            // expected
        } catch (Exception e) {
            fail("Unexpected exception: " + e.getClass().getName() + ": " + e.getMessage());
        }
    }

    /**
     * Test that setting ordering to "true" results in a correct tstInfo sequence.
     * 
     * @throws Exception
     */
    @Test
    public void test39orderingTrue() throws Exception {
        // reset ORDERING property
        workerSession.setWorkerProperty(WORKER1, TimeStampSigner.ORDERING, "true");
        workerSession.reloadConfiguration(WORKER1);

        final byte[] res = getResponseData(WORKER1);
        final ASN1Sequence seq = extractTstInfoSeq(res);
        final ASN1Encodable o = seq.getObjectAt(5);

        try {
            // when ordering isn't included, the 6:th element in the tstInfo sequence should be the nonce
            final ASN1Boolean b = ASN1Boolean.getInstance(o);
            assertEquals("Ordering should be set to true", ASN1Boolean.TRUE, b);
        } catch (IllegalArgumentException e) {
            fail("Ordering should be included");
        } catch (Exception e) {
            fail("Unexpected exception");
        } finally {
            workerSession.removeWorkerProperty(WORKER1, TimeStampSigner.ORDERING);
            workerSession.reloadConfiguration(WORKER1);
        }
    }

    /**
     * Test that by default (when not setting INCLUDEORDERING to "true") ordering is not
     * included ordering is set to "false".
     * 
     * @throws Exception
     */
    @Test
    public void test40orderingFalse() throws Exception {
        // reset ORDERING property
        workerSession.setWorkerProperty(WORKER1, TimeStampSigner.ORDERING, "false");
        workerSession.reloadConfiguration(WORKER1);

        final byte[] res = getResponseData(WORKER1);
        final ASN1Sequence seq = extractTstInfoSeq(res);
        final ASN1Encodable o = seq.getObjectAt(5);

        try {
            // when ordering isn't included, the 6:th element in the tstInfo sequence should be the nonce
            final ASN1Boolean b = ASN1Boolean.getInstance(o);
            fail("Ordering shouldn't included when ORDERING = false");
        } catch (IllegalArgumentException e) {
            // expected
        } catch (Exception e) {
            fail("Unexpected exception");
        } finally {
            workerSession.removeWorkerProperty(WORKER1, TimeStampSigner.ORDERING);
            workerSession.reloadConfiguration(WORKER1);
        }
    }

    /**
     * Test that the ordering field is included when ORDERING == false and INCLUDEORDERING == true.
     * 
     * @throws Exception
     */
    @Test
    public void test41IncludeOrderingOrderingFalse() throws Exception {
        // reset ORDERING property
        workerSession.setWorkerProperty(WORKER1, TimeStampSigner.ORDERING, "false");
        workerSession.setWorkerProperty(WORKER1, TimeStampSigner.INCLUDEORDERING, "true");
        workerSession.reloadConfiguration(WORKER1);

        final byte[] res = getResponseData(WORKER1);
        final ASN1Sequence seq = extractTstInfoSeq(res);
        final ASN1Encodable o = seq.getObjectAt(5);

        try {
            final ASN1Boolean b = ASN1Boolean.getInstance(o);
            assertEquals("Ordering should be set to false", ASN1Boolean.FALSE, b);
        } catch (IllegalArgumentException e) {
            fail("Ordering should be included");
        } catch (Exception e) {
            fail("Unexpected exception");
        } finally {
            workerSession.removeWorkerProperty(WORKER1, TimeStampSigner.ORDERING);
            workerSession.removeWorkerProperty(WORKER1, TimeStampSigner.INCLUDEORDERING);
            workerSession.reloadConfiguration(WORKER1);
        }
    }

    /**
     * Test that the ordering field is included when ORDERING == true and INCLUDEORDERING == true.
     * 
     * @throws Exception
     */
    @Test
    public void test42IncludeOrderingOrderingTrue() throws Exception {
        // reset ORDERING property
        workerSession.setWorkerProperty(WORKER1, TimeStampSigner.ORDERING, "true");
        workerSession.setWorkerProperty(WORKER1, TimeStampSigner.INCLUDEORDERING, "true");
        workerSession.reloadConfiguration(WORKER1);

        final byte[] res = getResponseData(WORKER1);
        final ASN1Sequence seq = extractTstInfoSeq(res);
        final ASN1Encodable o = seq.getObjectAt(5);

        try {
            final ASN1Boolean b = ASN1Boolean.getInstance(o);
            assertEquals("Ordering should be set to true", ASN1Boolean.TRUE, b);
        } catch (IllegalArgumentException e) {
            fail("Ordering should be included");
        } catch (Exception e) {
            fail("Unexpected exception");
        } finally {
            workerSession.removeWorkerProperty(WORKER1, TimeStampSigner.ORDERING);
            workerSession.removeWorkerProperty(WORKER1, TimeStampSigner.INCLUDEORDERING);
            workerSession.reloadConfiguration(WORKER1);
        }
    }

    /**
     * Test that the ordering field is not included when ORDERING == false and INCLUDEORDERING == false.
     * 
     * @throws Exception
     */
    @Test
    public void test43NotIncludeOrderingOrderingFalse() throws Exception {
        // reset ORDERING property
        workerSession.setWorkerProperty(WORKER1, TimeStampSigner.ORDERING, "false");
        workerSession.setWorkerProperty(WORKER1, TimeStampSigner.INCLUDEORDERING, "false");
        workerSession.reloadConfiguration(WORKER1);

        final byte[] res = getResponseData(WORKER1);
        final ASN1Sequence seq = extractTstInfoSeq(res);
        final ASN1Encodable o = seq.getObjectAt(5);

        try {
            final ASN1Boolean b = ASN1Boolean.getInstance(o);
            fail("Ordering should not be included");
        } catch (IllegalArgumentException e) {
            // expected
        } catch (Exception e) {
            fail("Unexpected exception");
        } finally {
            workerSession.removeWorkerProperty(WORKER1, TimeStampSigner.ORDERING);
            workerSession.removeWorkerProperty(WORKER1, TimeStampSigner.INCLUDEORDERING);
            workerSession.reloadConfiguration(WORKER1);
        }
    }

    /**
     * Test that setting INCLUDEORDERING false and ORDERING true results in a configuration errror.
     * @throws Exception
     */
    @Test
    public void test44NotIncludeOrderingOrderingTrue() throws Exception {
        workerSession.setWorkerProperty(WORKER1, TimeStampSigner.ORDERING, "true");
        workerSession.setWorkerProperty(WORKER1, TimeStampSigner.INCLUDEORDERING, "false");
        workerSession.reloadConfiguration(WORKER1);

        final List<String> errors = workerSession.getStatus(WORKER1).getFatalErrors();
        assertTrue("Should mention incompatible configuration values.",
                errors.contains("INCLUDEORDERING can not be set to \"false\" when ORDERING is set to \"true\""));
    }

    /**
     * Test that setting INCLUDE_CERTIFICATE_LEVELS to 0 is not supported.
     * 
     * @throws Exception
     */
    @Test
    public void test45IncludeCertificateLevels0NotAllowed() throws Exception {
        try {
            workerSession.setWorkerProperty(WORKER1, WorkerConfig.PROPERTY_INCLUDE_CERTIFICATE_LEVELS, "0");
            workerSession.reloadConfiguration(WORKER1);

            final List<String> errors = workerSession.getStatus(WORKER1).getFatalErrors();

            assertTrue("Should contain configuration error", errors.contains("Illegal value for property "
                    + WorkerConfig.PROPERTY_INCLUDE_CERTIFICATE_LEVELS + ". Only numbers >= 1 supported."));
        } finally {
            workerSession.removeWorkerProperty(WORKER1, WorkerConfig.PROPERTY_INCLUDE_CERTIFICATE_LEVELS);
            workerSession.reloadConfiguration(WORKER1);
        }
    }

    private void assertTokenGranted(int workerId) throws Exception {
        TimeStampRequestGenerator timeStampRequestGenerator = new TimeStampRequestGenerator();
        timeStampRequestGenerator.setCertReq(true);
        TimeStampRequest timeStampRequest = timeStampRequestGenerator.generate(TSPAlgorithms.SHA1, new byte[20],
                BigInteger.valueOf(100));
        byte[] requestBytes = timeStampRequest.getEncoded();
        GenericSignRequest signRequest = new GenericSignRequest(123124, requestBytes);
        try {
            final GenericSignResponse res = (GenericSignResponse) workerSession.process(workerId, signRequest,
                    new RequestContext());

            final TimeStampResponse timeStampResponse = new TimeStampResponse((byte[]) res.getProcessedData());
            timeStampResponse.validate(timeStampRequest);

            assertEquals(PKIStatus.GRANTED, timeStampResponse.getStatus());
        } catch (CryptoTokenOfflineException ex) {
            fail(ex.getMessage());
        }
    }

    private void assertTokenNotGranted(int workerId) throws Exception {
        TimeStampRequestGenerator timeStampRequestGenerator = new TimeStampRequestGenerator();
        timeStampRequestGenerator.setCertReq(true);
        TimeStampRequest timeStampRequest = timeStampRequestGenerator.generate(TSPAlgorithms.SHA1, new byte[20],
                BigInteger.valueOf(100));
        byte[] requestBytes = timeStampRequest.getEncoded();
        GenericSignRequest signRequest = new GenericSignRequest(123124, requestBytes);
        try {
            final GenericSignResponse res = (GenericSignResponse) workerSession.process(workerId, signRequest,
                    new RequestContext());

            final TimeStampResponse timeStampResponse = new TimeStampResponse((byte[]) res.getProcessedData());
            timeStampResponse.validate(timeStampRequest);

            assertFalse(PKIStatus.GRANTED == timeStampResponse.getStatus());
        } catch (CryptoTokenOfflineException ignored) { //NOPMD
            // OK
        }
    }

    @Test
    public void test99TearDownDatabase() throws Exception {
        removeWorker(WORKER1);
        removeWorker(WORKER2);
        removeWorker(WORKER3);
        removeWorker(WORKER4);
    }

}