/************************************************************************* * * * 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.ASN1ObjectIdentifier; import org.bouncycastle.asn1.cmp.PKIFailureInfo; import org.bouncycastle.asn1.cmp.PKIStatus; import org.bouncycastle.asn1.x500.RDN; 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.TestingSecurityManager; import static org.junit.Assert.*; import org.junit.Before; import org.junit.Test; /** * Tests for the TimeStampSigner. * * @version $Id: TimeStampSignerTest.java 3465 2013-05-01 10:24:46Z netmackan $ */ @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"; /** * Base64 encoded request with policy 1.2.3.5. *
     * 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:
     *  
*/ private static final String REQUEST_WITH_POLICY1235 = "MCsCAQEwITAJBgUrDgMCGgUABBQyoGF6q0yf5yXxtbxEEpEYCtJbcwYDKgMF"; private static String signserverhome; private static int moduleVersion; private Random random = new Random(4711); @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 { setProperties(new File(getSignServerHome(), "modules/SignServer-Module-TSA/src/conf/junittest-part-config.properties")); workerSession.reloadConfiguration(WORKER1); workerSession.reloadConfiguration(WORKER2); workerSession.reloadConfiguration(WORKER3); workerSession.reloadConfiguration(WORKER4); } @Test public void test01BasicTimeStamp() throws Exception { // Test signing final TimeStampResponse response = assertSuccessfulTimestamp(WORKER1); // 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()); } private TimeStampResponse assertSuccessfulTimestamp(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()); 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()); } return timeStampResponse; } /** * Tests the status returned by the worker. */ @Test public void test02GetStatus() throws Exception { SignerStatus stat = (SignerStatus) workerSession.getStatus(8901); assertEquals("token status", SignerStatus.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); // 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()); 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()); } } catch (TSPException e) { fail("Failed to verify response"); } catch (IOException e) { fail("Failed to verify response"); } final TimeStampToken token = timeStampResponse.getTimeStampToken(); try { final CertificateFactory factory = CertificateFactory.getInstance("X.509"); final X509Certificate cert = (X509Certificate) factory.generateCertificate(new ByteArrayInputStream(Base64.decode(CERTSTRING.getBytes()))); 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 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 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 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 asListOfByteArrays(List 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 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 ourChain = new LinkedList(); 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 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()); 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 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 } 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"); workerSession.setWorkerProperty(workerId, "DEFAULTTSAPOLICYOID", "1.2.3"); workerSession.setWorkerProperty(workerId, "SIGNATUREALGORITHM", "SHA1WithECDSA"); workerSession.reloadConfiguration(workerId); // Test signing TimeStampResponse response = assertSuccessfulTimestamp(WORKER20); // 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); // 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"); workerSession.setWorkerProperty(workerId, "DEFAULTTSAPOLICYOID", "1.2.3"); workerSession.setWorkerProperty(workerId, "SIGNATUREALGORITHM", "SHA1WithDSA"); workerSession.reloadConfiguration(workerId); // Test signing TimeStampResponse response = assertSuccessfulTimestamp(WORKER20); // 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); 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); 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 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 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); 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); 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); 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 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); } 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); } }