Java tutorial
/************************************************************************* * * * 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.server.cryptotokens; import java.io.File; import java.io.FileOutputStream; import java.math.BigInteger; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.KeyStore; import java.security.cert.Certificate; import java.security.cert.X509Certificate; import java.util.Arrays; import java.util.Date; import java.util.HashSet; import java.util.List; import java.util.Set; import javax.security.auth.x500.X500Principal; import static junit.framework.TestCase.assertEquals; import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; import org.apache.log4j.Logger; import org.bouncycastle.cert.X509v3CertificateBuilder; import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder; import org.bouncycastle.operator.ContentSigner; import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; import org.signserver.common.CryptoTokenOfflineException; import org.signserver.common.GlobalConfiguration; import org.signserver.common.SignServerUtil; import org.signserver.common.TokenOutOfSpaceException; /** * System tests for the KeystoreCryptoToken. * * @author Markus Kils * @version $Id: KeystoreCryptoTokenTest.java 6158 2015-08-18 08:57:16Z malu9369 $ */ public class KeystoreCryptoTokenTest extends KeystoreCryptoTokenTestBase { /** Logger for this class. */ private static final Logger LOG = Logger.getLogger(KeystoreCryptoTokenTest.class); private static final int WORKER_CMS = 30003; private static final int CRYPTO_TOKEN = 30103; private static final String SIGN_KEY_ALIAS = "p12signkey1234"; private static final String TEST_KEY_ALIAS = "p12testkey1234"; private static final String KEYSTORE_NAME = "p12testkeystore1234"; private File keystoreFile; @Override protected void setUp() throws Exception { super.setUp(); SignServerUtil.installBCProvider(); } @Override protected void tearDown() throws Exception { super.tearDown(); } private void setCMSSignerPropertiesCombined(final int workerId, boolean autoActivate) throws Exception { // Create keystore keystoreFile = File.createTempFile(KEYSTORE_NAME, ".p12"); FileOutputStream out = null; try { KeyStore ks = KeyStore.getInstance("PKCS12", "BC"); ks.load(null, null); out = new FileOutputStream(keystoreFile); ks.store(out, pin.toCharArray()); } finally { IOUtils.closeQuietly(out); } // Setup worker globalSession.setProperty(GlobalConfiguration.SCOPE_GLOBAL, "WORKER" + workerId + ".CLASSPATH", "org.signserver.module.cmssigner.CMSSigner"); globalSession.setProperty(GlobalConfiguration.SCOPE_GLOBAL, "WORKER" + workerId + ".SIGNERTOKEN.CLASSPATH", KeystoreCryptoToken.class.getName()); workerSession.setWorkerProperty(workerId, "NAME", "CMSSignerP12"); workerSession.setWorkerProperty(workerId, "KEYSTORETYPE", "PKCS12"); workerSession.setWorkerProperty(workerId, "AUTHTYPE", "NOAUTH"); workerSession.setWorkerProperty(workerId, "KEYSTOREPATH", keystoreFile.getAbsolutePath()); workerSession.setWorkerProperty(workerId, "DEFAULTKEY", SIGN_KEY_ALIAS); if (autoActivate) { workerSession.setWorkerProperty(workerId, "KEYSTOREPASSWORD", pin); } else { workerSession.removeWorkerProperty(workerId, "KEYSTOREPASSWORD"); } } private void setCMSSignerPropertiesSeparateToken(final int workerId, final int tokenId, boolean autoActivate) throws Exception { // Create keystore keystoreFile = File.createTempFile(KEYSTORE_NAME, ".p12"); FileOutputStream out = null; try { KeyStore ks = KeyStore.getInstance("PKCS12", "BC"); ks.load(null, null); out = new FileOutputStream(keystoreFile); ks.store(out, pin.toCharArray()); } finally { IOUtils.closeQuietly(out); } // Setup crypto token globalSession.setProperty(GlobalConfiguration.SCOPE_GLOBAL, "WORKER" + tokenId + ".CLASSPATH", "org.signserver.server.signers.CryptoWorker"); globalSession.setProperty(GlobalConfiguration.SCOPE_GLOBAL, "WORKER" + tokenId + ".SIGNERTOKEN.CLASSPATH", KeystoreCryptoToken.class.getName()); workerSession.setWorkerProperty(tokenId, "NAME", "TestCryptoTokenP12"); workerSession.setWorkerProperty(tokenId, "KEYSTORETYPE", "PKCS12"); workerSession.setWorkerProperty(tokenId, "KEYSTOREPATH", keystoreFile.getAbsolutePath()); workerSession.setWorkerProperty(tokenId, "DEFAULTKEY", SIGN_KEY_ALIAS); if (autoActivate) { workerSession.setWorkerProperty(tokenId, "KEYSTOREPASSWORD", pin); } else { workerSession.removeWorkerProperty(workerId, "KEYSTOREPASSWORD"); } // Setup worker globalSession.setProperty(GlobalConfiguration.SCOPE_GLOBAL, "WORKER" + workerId + ".CLASSPATH", "org.signserver.module.cmssigner.CMSSigner"); workerSession.setWorkerProperty(workerId, "NAME", "CMSSignerP12"); workerSession.setWorkerProperty(workerId, "AUTHTYPE", "NOAUTH"); workerSession.setWorkerProperty(workerId, "CRYPTOTOKEN", "TestCryptoTokenP12"); workerSession.setWorkerProperty(workerId, "DEFAULTKEY", SIGN_KEY_ALIAS); } /** * Tests setting up a CMS Signer, giving it a certificate and sign a file. * Using a worker with its own token. */ public void testSigning() throws Exception { LOG.info("testSigning"); final int workerId = WORKER_CMS; try { setCMSSignerPropertiesCombined(workerId, true); workerSession.reloadConfiguration(workerId); workerSession.generateSignerKey(workerId, "RSA", "1024", SIGN_KEY_ALIAS, pin.toCharArray()); workerSession.reloadConfiguration(workerId); cmsSigner(workerId); } finally { FileUtils.deleteQuietly(keystoreFile); removeWorker(workerId); } } /** * Tests setting up a CMS Signer, giving it a certificate and sign a file. * Using a worker referencing a crypto token. */ public void testSigning_separateToken() throws Exception { LOG.info("testSigning_separateToken"); final int workerId = WORKER_CMS; final int tokenId = CRYPTO_TOKEN; try { setCMSSignerPropertiesSeparateToken(workerId, tokenId, true); workerSession.reloadConfiguration(tokenId); workerSession.reloadConfiguration(workerId); workerSession.generateSignerKey(tokenId, "RSA", "1024", SIGN_KEY_ALIAS, pin.toCharArray()); workerSession.reloadConfiguration(tokenId); cmsSigner(workerId); } finally { FileUtils.deleteQuietly(keystoreFile); removeWorker(workerId); removeWorker(tokenId); } } public void testGenerateKey() throws Exception { LOG.info("testGenerateKey"); final int workerId = WORKER_CMS; try { setCMSSignerPropertiesCombined(workerId, true); workerSession.reloadConfiguration(workerId); // Add a reference key workerSession.generateSignerKey(workerId, "RSA", "1024", "somekey123", pin.toCharArray()); // Check available aliases Set<String> aliases1 = getKeyAliases(workerId); if (aliases1.isEmpty()) { throw new Exception("getKeyAliases is not working or the slot is empty"); } // If the key already exists, try to remove it first if (aliases1.contains(TEST_KEY_ALIAS)) { workerSession.removeKey(workerId, TEST_KEY_ALIAS); aliases1 = getKeyAliases(workerId); } if (aliases1.contains(TEST_KEY_ALIAS)) { throw new Exception("Pre-condition failed: Key with alias " + TEST_KEY_ALIAS + " already exists and removing it failed"); } // Generate a testkey workerSession.generateSignerKey(workerId, "RSA", "1024", TEST_KEY_ALIAS, pin.toCharArray()); // Now expect the new TEST_KEY_ALIAS Set<String> expected = new HashSet<String>(aliases1); expected.add(TEST_KEY_ALIAS); Set<String> aliases2 = getKeyAliases(workerId); assertEquals("new key added", expected, aliases2); } finally { FileUtils.deleteQuietly(keystoreFile); removeWorker(workerId); } } /** * Tests that key generation is not allowed when the number of keys has * reached the KEYGENERATIONLIMIT. * Also checks that when allowing for one more keys, the next key can be * generated. */ @SuppressWarnings("ThrowableResultIgnored") public void testKeyGenerationLimit() throws Exception { LOG.info("testKeyGenerationLimit"); final int workerId = WORKER_CMS; try { setCMSSignerPropertiesCombined(workerId, true); workerSession.reloadConfiguration(workerId); // Add a reference key workerSession.generateSignerKey(workerId, "RSA", "1024", "somekey123", pin.toCharArray()); // Check available aliases final int keys = getKeyAliases(workerId).size(); // Set the current number of keys as maximum workerSession.setWorkerProperty(workerId, "KEYGENERATIONLIMIT", String.valueOf(keys)); workerSession.reloadConfiguration(workerId); // Key generation should fail try { workerSession.generateSignerKey(workerId, "RSA", "1024", TEST_KEY_ALIAS, pin.toCharArray()); fail("Should have failed because of no space in token"); } catch (TokenOutOfSpaceException expected) { // NOPMD // OK } // Allow for one more keys to be created workerSession.setWorkerProperty(workerId, "KEYGENERATIONLIMIT", String.valueOf(keys + 1)); workerSession.reloadConfiguration(workerId); // Generate a new key try { workerSession.generateSignerKey(workerId, "RSA", "1024", TEST_KEY_ALIAS, pin.toCharArray()); } catch (CryptoTokenOfflineException ex) { fail("Should have worked but got: " + ex.getLocalizedMessage()); } final int keys2 = getKeyAliases(workerId).size(); assertEquals("one more key", keys + 1, keys2); // Key generation should fail try { workerSession.generateSignerKey(workerId, "RSA", "1024", TEST_KEY_ALIAS, pin.toCharArray()); fail("Should have failed because of no space in token"); } catch (TokenOutOfSpaceException expected) { // NOPMD // OK } } finally { FileUtils.deleteQuietly(keystoreFile); removeWorker(workerId); } } /** * Tests that a worker just set up with a key store containing a new * key-pair and is activated manually gets status ACTIVE. * @throws Exception */ public void testActivateWithNewKeystore() throws Exception { LOG.info("testActivateWithNewKeystore"); final boolean autoActivate = false; final int workerId = WORKER_CMS; try { setCMSSignerPropertiesCombined(workerId, autoActivate); // Create a key-pair and certificate in the keystore FileOutputStream out = null; try { KeyStore ks = KeyStore.getInstance("PKCS12", "BC"); ks.load(null, null); // Generate key and issue certificate final KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA", "BC"); kpg.initialize(1024); final KeyPair keyPair = kpg.generateKeyPair(); X509Certificate[] chain = new X509Certificate[1]; chain[0] = getSelfCertificate("CN=TestActivateWithNewKeystore" + ", C=SE", (long) 30 * 24 * 60 * 60 * 365, keyPair); ks.setKeyEntry("newkey11", keyPair.getPrivate(), pin.toCharArray(), chain); out = new FileOutputStream(keystoreFile); ks.store(out, pin.toCharArray()); } finally { IOUtils.closeQuietly(out); } workerSession.setWorkerProperty(workerId, "DEFAULTKEY", "newkey11"); workerSession.reloadConfiguration(workerId); // Activate first so we can generate a key workerSession.activateSigner(workerId, pin); List<String> errors = workerSession.getStatus(workerId).getFatalErrors(); assertTrue("Fatal errors: " + errors, workerSession.getStatus(workerId).getFatalErrors().isEmpty()); } finally { FileUtils.deleteQuietly(keystoreFile); removeWorker(workerId); } } /** * Test importing a new certificate chain to an existing keystore. * @throws Exception */ public void testImportCertificateChain() throws Exception { LOG.info("testImportCertificateChain"); final boolean autoActivate = false; final int workerId = WORKER_CMS; try { setCMSSignerPropertiesCombined(workerId, autoActivate); // Generate key and issue certificate final KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA", "BC"); kpg.initialize(1024); final KeyPair keyPair = kpg.generateKeyPair(); // Create a key-pair and certificate in the keystore FileOutputStream out = null; try { KeyStore ks = KeyStore.getInstance("PKCS12", "BC"); ks.load(null, null); final X509Certificate[] chain = new X509Certificate[1]; chain[0] = getSelfCertificate("CN=Test", (long) 30 * 24 * 60 * 60 * 365, keyPair); ks.setKeyEntry("newkey11", keyPair.getPrivate(), pin.toCharArray(), chain); out = new FileOutputStream(keystoreFile); ks.store(out, pin.toCharArray()); } finally { IOUtils.closeQuietly(out); } workerSession.setWorkerProperty(workerId, "DEFAULTKEY", "newkey11"); workerSession.reloadConfiguration(workerId); // Activate first so we can generate a key workerSession.activateSigner(workerId, pin); List<String> errors = workerSession.getStatus(workerId).getFatalErrors(); assertTrue("Fatal errors: " + errors, workerSession.getStatus(workerId).getFatalErrors().isEmpty()); // generate a new certificate final X509Certificate newCert = getSelfCertificate("CN=TestNew", (long) 30 * 24 * 60 * 60 * 365, keyPair); workerSession.importCertificateChain(workerId, Arrays.asList(newCert.getEncoded()), "newkey11", null); final Certificate readCert = workerSession.getSignerCertificate(workerId); assertTrue("Matching certificates", Arrays.equals(newCert.getEncoded(), readCert.getEncoded())); } finally { FileUtils.deleteQuietly(keystoreFile); removeWorker(workerId); } } /** Creates a self signed certificate. */ private X509Certificate getSelfCertificate(String alias, long validity, KeyPair keyPair) throws Exception { final long currentTime = new Date().getTime(); final Date firstDate = new Date(currentTime - 24 * 60 * 60 * 1000); final Date lastDate = new Date(currentTime + validity * 1000); final X509v3CertificateBuilder cg = new JcaX509v3CertificateBuilder(new X500Principal(alias), BigInteger.valueOf(firstDate.getTime()), firstDate, lastDate, new X500Principal(alias), keyPair.getPublic()); final JcaContentSignerBuilder contentSignerBuilder = new JcaContentSignerBuilder("SHA1withRSA"); contentSignerBuilder.setProvider("BC"); final ContentSigner contentSigner = contentSignerBuilder.build(keyPair.getPrivate()); return new JcaX509CertificateConverter().getCertificate(cg.build(contentSigner)); } public void testGenerateKey_separateToken() throws Exception { LOG.info("testGenerateKey_separateToken"); final int workerId = WORKER_CMS; final int tokenId = CRYPTO_TOKEN; try { setCMSSignerPropertiesSeparateToken(workerId, tokenId, true); workerSession.reloadConfiguration(tokenId); workerSession.reloadConfiguration(workerId); // Add a reference key workerSession.generateSignerKey(tokenId, "RSA", "1024", "somekey123", pin.toCharArray()); // Check available aliases Set<String> aliases1 = getKeyAliases(tokenId); if (aliases1.isEmpty()) { throw new Exception("getKeyAliases is not working or the slot is empty"); } // If the key already exists, try to remove it first if (aliases1.contains(TEST_KEY_ALIAS)) { workerSession.removeKey(tokenId, TEST_KEY_ALIAS); aliases1 = getKeyAliases(tokenId); } if (aliases1.contains(TEST_KEY_ALIAS)) { throw new Exception("Pre-condition failed: Key with alias " + TEST_KEY_ALIAS + " already exists and removing it failed"); } // Generate a testkey workerSession.generateSignerKey(tokenId, "RSA", "1024", TEST_KEY_ALIAS, pin.toCharArray()); // Now expect the new TEST_KEY_ALIAS Set<String> expected = new HashSet<String>(aliases1); expected.add(TEST_KEY_ALIAS); Set<String> aliases2 = getKeyAliases(tokenId); assertEquals("new key added", expected, aliases2); } finally { FileUtils.deleteQuietly(keystoreFile); removeWorker(workerId); removeWorker(tokenId); } } public void testRemoveKey() throws Exception { LOG.info("testRemoveKey"); final int workerId = WORKER_CMS; try { setCMSSignerPropertiesCombined(workerId, true); workerSession.reloadConfiguration(workerId); // Add a reference key workerSession.generateSignerKey(workerId, "RSA", "1024", "somekey123", pin.toCharArray()); // Check available aliases Set<String> aliases1 = getKeyAliases(workerId); if (aliases1.isEmpty()) { throw new Exception("getKeyAliases is not working or the slot is empty"); } if (!aliases1.contains(TEST_KEY_ALIAS)) { // Generate a testkey workerSession.generateSignerKey(workerId, "RSA", "1024", TEST_KEY_ALIAS, pin.toCharArray()); aliases1 = getKeyAliases(workerId); } if (!aliases1.contains(TEST_KEY_ALIAS)) { throw new Exception("Pre-condition failed: Key with alias " + TEST_KEY_ALIAS + " did not exist and it could not be created"); } workerSession.reloadConfiguration(workerId); // Remove the key workerSession.removeKey(workerId, TEST_KEY_ALIAS); // Now expect the TEST_KEY_ALIAS to have been removed Set<String> aliases2 = getKeyAliases(workerId); Set<String> expected = new HashSet<String>(aliases1); expected.remove(TEST_KEY_ALIAS); assertEquals("new key removed", expected, aliases2); } finally { FileUtils.deleteQuietly(keystoreFile); removeWorker(workerId); } } public void testRemoveKey_separateToken() throws Exception { LOG.info("testRemoveKey_separateToken"); final int workerId = WORKER_CMS; final int tokenId = CRYPTO_TOKEN; try { setCMSSignerPropertiesSeparateToken(workerId, tokenId, true); workerSession.reloadConfiguration(tokenId); workerSession.reloadConfiguration(workerId); // Add a reference key workerSession.generateSignerKey(tokenId, "RSA", "1024", "somekey123", pin.toCharArray()); // Check available aliases Set<String> aliases1 = getKeyAliases(tokenId); if (aliases1.isEmpty()) { throw new Exception("getKeyAliases is not working or the slot is empty"); } if (!aliases1.contains(TEST_KEY_ALIAS)) { // Generate a testkey workerSession.generateSignerKey(tokenId, "RSA", "1024", TEST_KEY_ALIAS, pin.toCharArray()); aliases1 = getKeyAliases(tokenId); } if (!aliases1.contains(TEST_KEY_ALIAS)) { throw new Exception("Pre-condition failed: Key with alias " + TEST_KEY_ALIAS + " did not exist and it could not be created"); } workerSession.reloadConfiguration(tokenId); // Remove the key workerSession.removeKey(tokenId, TEST_KEY_ALIAS); // Now expect the TEST_KEY_ALIAS to have been removed Set<String> aliases2 = getKeyAliases(tokenId); Set<String> expected = new HashSet<String>(aliases1); expected.remove(TEST_KEY_ALIAS); assertEquals("new key removed", expected, aliases2); } finally { FileUtils.deleteQuietly(keystoreFile); removeWorker(workerId); removeWorker(tokenId); } } /** * Test that omitting KEYSTORETYPE gives a correct error message. * * @throws Exception */ public void testNoKeystoreType() throws Exception { LOG.info("testNoKeystoreType"); final int workerId = WORKER_CMS; try { setCMSSignerPropertiesCombined(workerId, true); workerSession.removeWorkerProperty(workerId, "KEYSTORETYPE"); workerSession.reloadConfiguration(workerId); final List<String> errors = workerSession.getStatus(workerId).getFatalErrors(); assertTrue("Should contain error", errors.contains("Failed to initialize crypto token: Missing KEYSTORETYPE property")); } finally { removeWorker(workerId); } } /** * Test that setting an unknown KEYSTORETYPE gives a correct error message. * * @throws Exception */ public void testUnknownKeystoreType() throws Exception { LOG.info("testNoKeystoreType"); final int workerId = WORKER_CMS; try { setCMSSignerPropertiesCombined(workerId, true); workerSession.setWorkerProperty(workerId, "KEYSTORETYPE", "FOOBAR"); workerSession.reloadConfiguration(workerId); final List<String> errors = workerSession.getStatus(workerId).getFatalErrors(); assertTrue("Should contain error", errors.contains( "Failed to initialize crypto token: KEYSTORETYPE should be either PKCS12, JKS, or INTERNAL")); } finally { removeWorker(workerId); } } /** * Test that omitting KEYSTOREPATH results in a config error. * * @throws Exception */ public void testMissingKeystorePath() throws Exception { LOG.info("testMissingKeystorePath"); final int workerId = WORKER_CMS; try { setCMSSignerPropertiesCombined(workerId, true); workerSession.removeWorkerProperty(workerId, "KEYSTOREPATH"); workerSession.reloadConfiguration(workerId); final List<String> errors = workerSession.getStatus(workerId).getFatalErrors(); assertTrue("Should contain error", errors.contains("Failed to initialize crypto token: Missing KEYSTOREPATH property")); } finally { removeWorker(workerId); } } /** * Test that setting KEYSTOREPATH not pointing an existing file results in a config error. * * @throws Exception */ public void testUnknownKeystorePath() throws Exception { LOG.info("testMissingKeystorePath"); final int workerId = WORKER_CMS; try { setCMSSignerPropertiesCombined(workerId, true); workerSession.setWorkerProperty(workerId, "KEYSTOREPATH", "non-existing.p12"); workerSession.reloadConfiguration(workerId); final List<String> errors = workerSession.getStatus(workerId).getFatalErrors(); assertTrue("Should contain error", errors.contains("Failed to initialize crypto token: File not found: non-existing.p12")); } finally { removeWorker(workerId); } } /** * Test that unsetting DEFAULTKEY results in a CryptoTokenOfflineException. * * @throws Exception */ public void testNoDefaultKey() throws Exception { LOG.info("testNoDefaultKey"); final int workerId = WORKER_CMS; try { setCMSSignerPropertiesCombined(workerId, true); // unset DEFAULTKEY workerSession.removeWorkerProperty(workerId, "DEFAULTKEY"); workerSession.reloadConfiguration(workerId); cmsSigner(workerId); } catch (CryptoTokenOfflineException e) { // expected } catch (Exception e) { fail("Unexpected exception: " + e.getClass().getName()); } finally { removeWorker(workerId); } } }