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.IOException; import java.math.BigInteger; import java.security.InvalidAlgorithmParameterException; import java.security.KeyPair; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; import java.security.cert.Certificate; import java.security.cert.CertificateEncodingException; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.util.Arrays; import java.util.Date; import java.util.LinkedList; import java.util.List; import java.util.concurrent.TimeUnit; import org.apache.log4j.Logger; import org.bouncycastle.asn1.x500.X500Name; import org.bouncycastle.cert.X509CertificateHolder; import org.bouncycastle.cert.X509v3CertificateBuilder; import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder; import org.bouncycastle.operator.OperatorCreationException; import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; import org.bouncycastle.pkcs.PKCS10CertificationRequest; import org.bouncycastle.util.encoders.Base64; import org.cesecore.authorization.AuthorizationDeniedException; import org.cesecore.util.CertTools; import org.cesecore.util.query.Criteria; import org.cesecore.util.query.QueryCriteria; import org.cesecore.util.query.elems.RelationalOperator; import org.cesecore.util.query.elems.Term; import static org.junit.Assert.*; import org.signserver.common.Base64SignerCertReqData; import org.signserver.common.CryptoTokenOfflineException; import org.signserver.common.ICertReqData; import org.signserver.common.ISignerCertReqInfo; import org.signserver.common.InvalidWorkerIdException; import org.signserver.common.OperationUnsupportedException; import org.signserver.common.PKCS10CertReqInfo; import org.signserver.common.QueryException; import org.signserver.common.SignServerException; import org.signserver.common.UnsupportedCryptoTokenParameter; import org.signserver.test.utils.builders.CryptoUtils; import org.signserver.testutils.ModulesTestCase; /** * Generic CryptoToken tests. This class can be extended and the abstract * methods implemented to test a specific CryptoToken implementation. * * @author Markus Kils * @version $Id: CryptoTokenTestBase.java 6148 2015-07-17 11:27:23Z netmackan $ */ public abstract class CryptoTokenTestBase extends ModulesTestCase { /** Logger for this class. */ private static final Logger LOG = Logger.getLogger(CryptoTokenTestBase.class); protected abstract TokenSearchResults searchTokenEntries(final int startIndex, final int max, final QueryCriteria qc, final boolean includeData) throws InvalidWorkerIdException, AuthorizationDeniedException, SignServerException, OperationUnsupportedException, CryptoTokenOfflineException, QueryException, InvalidAlgorithmParameterException, UnsupportedCryptoTokenParameter; protected abstract void generateKey(String keyType, String keySpec, String alias) throws CryptoTokenOfflineException, InvalidWorkerIdException, SignServerException; protected abstract boolean destroyKey(String alias) throws CryptoTokenOfflineException, InvalidWorkerIdException, SignServerException, KeyStoreException; protected abstract void importCertificateChain(List<Certificate> chain, String alias) throws CryptoTokenOfflineException, IllegalArgumentException, CertificateException, CertificateEncodingException, OperationUnsupportedException; protected abstract ICertReqData genCertificateRequest(ISignerCertReqInfo req, boolean explicitEccParameters, String alias) throws CryptoTokenOfflineException, InvalidWorkerIdException; protected abstract List<Certificate> getCertificateChain(String alias) throws CryptoTokenOfflineException, InvalidWorkerIdException; // private static final Set<String> longFields; // private static final Set<String> dateFields; // private static final Set<RelationalOperator> noArgOps; // private static final Set<String> allowedFields; // TODO: This could be useful in some accessible helper class // static { // longFields = new HashSet<String>(); // longFields.add(AuditRecordData.FIELD_SEQUENCENUMBER); // // dateFields = new HashSet<String>(); // dateFields.add(AuditRecordData.FIELD_TIMESTAMP); // // noArgOps = new HashSet<RelationalOperator>(); // noArgOps.add(RelationalOperator.NULL); // noArgOps.add(RelationalOperator.NOTNULL); // // // allowed fields from CESeCore // // TODO: should maybe define this in CESeCore? // allowedFields = new HashSet<String>(); // allowedFields.add(AuditRecordData.FIELD_ADDITIONAL_DETAILS); // allowedFields.add(AuditRecordData.FIELD_AUTHENTICATION_TOKEN); // allowedFields.add(AuditRecordData.FIELD_CUSTOM_ID); // allowedFields.add(AuditRecordData.FIELD_EVENTSTATUS); // allowedFields.add(AuditRecordData.FIELD_EVENTTYPE); // allowedFields.add(AuditRecordData.FIELD_MODULE); // allowedFields.add(AuditRecordData.FIELD_NODEID); // allowedFields.add(AuditRecordData.FIELD_SEARCHABLE_DETAIL1); // allowedFields.add(AuditRecordData.FIELD_SEARCHABLE_DETAIL2); // allowedFields.add(AuditRecordData.FIELD_SERVICE); // allowedFields.add(AuditRecordData.FIELD_SEQUENCENUMBER); // allowedFields.add(AuditRecordData.FIELD_TIMESTAMP); // } public static final String FIELD_ALIAS = "alias"; /** * TODO tests... * * Checks that the entries are returned in the same order for each call (given no entries added or removed). * @param existingKey * @throws Exception */ protected void searchTokenEntriesHelper(final String existingKey) throws Exception { final String[] testAliases = new String[] { "alias-14", "alias-13", "alias-5", "alias-10", "alias-2", "alias-1" }; try { // First it is empty TokenSearchResults searchResults = searchTokenEntries(0, Integer.MAX_VALUE, QueryCriteria.create(), true); LinkedList<String> aliases = new LinkedList<String>(); for (TokenEntry entry : searchResults.getEntries()) { aliases.add(entry.getAlias()); } LOG.info("Existing aliases: " + aliases); assertEquals("no entries except the test key yet", 1, searchResults.getEntries().size()); assertFalse("no more entries", searchResults.isMoreEntriesAvailable()); // Now create some entries for (String alias : testAliases) { generateKey("RSA", "1024", alias); } searchResults = searchTokenEntries(0, Integer.MAX_VALUE, QueryCriteria.create(), true); aliases = new LinkedList<String>(); for (TokenEntry entry : searchResults.getEntries()) { aliases.add(entry.getAlias()); } // Check that all aliases are there for (String alias : testAliases) { assertTrue("should contain " + alias + " but only had " + aliases, aliases.contains(alias)); } assertTrue("should contain " + existingKey + " but only had " + aliases, aliases.contains(existingKey)); assertEquals("no more aliases than the expected in " + aliases, testAliases.length + 1, aliases.size()); final String[] allAliases = aliases.toArray(new String[0]); LOG.info("allAliases: " + Arrays.toString(allAliases)); // Search 1 at the time searchResults = searchTokenEntries(0, 1, QueryCriteria.create(), true); aliases = new LinkedList<String>(); for (TokenEntry entry : searchResults.getEntries()) { aliases.add(entry.getAlias()); } assertArrayEquals(new String[] { allAliases[0] }, aliases.toArray()); assertTrue("more entries available", searchResults.isMoreEntriesAvailable()); // Search 1 at the time searchResults = searchTokenEntries(1, 1, QueryCriteria.create(), true); aliases = new LinkedList<String>(); for (TokenEntry entry : searchResults.getEntries()) { aliases.add(entry.getAlias()); } assertArrayEquals(new String[] { allAliases[1] }, aliases.toArray()); assertTrue("more entries available", searchResults.isMoreEntriesAvailable()); // Search 4 at the time, and then there are no more searchResults = searchTokenEntries(2, 5, QueryCriteria.create(), true); aliases = new LinkedList<String>(); for (TokenEntry entry : searchResults.getEntries()) { aliases.add(entry.getAlias()); } assertArrayEquals( new String[] { allAliases[2], allAliases[3], allAliases[4], allAliases[5], allAliases[6] }, aliases.toArray()); assertFalse("no more entries available", searchResults.isMoreEntriesAvailable()); // Querying out of index returns empty results searchResults = searchTokenEntries(7, 1, QueryCriteria.create(), true); aliases = new LinkedList<String>(); for (TokenEntry entry : searchResults.getEntries()) { aliases.add(entry.getAlias()); } assertArrayEquals(new String[] {}, aliases.toArray()); assertFalse("no more entries available", searchResults.isMoreEntriesAvailable()); // Query one specific entry searchResults = searchTokenEntries(0, Integer.MAX_VALUE, QueryCriteria.create().add(new Term(RelationalOperator.EQ, CryptoTokenHelper.TokenEntryFields.alias.name(), allAliases[3])), true); aliases = new LinkedList<String>(); for (TokenEntry entry : searchResults.getEntries()) { aliases.add(entry.getAlias()); } assertArrayEquals(new String[] { allAliases[3] }, aliases.toArray()); assertFalse("no more entries available", searchResults.isMoreEntriesAvailable()); // Query two specific entries searchResults = searchTokenEntries(0, Integer.MAX_VALUE, QueryCriteria.create() .add(Criteria.or( new Term(RelationalOperator.EQ, CryptoTokenHelper.TokenEntryFields.alias.name(), allAliases[3]), new Term(RelationalOperator.EQ, CryptoTokenHelper.TokenEntryFields.alias.name(), allAliases[1]))), true); aliases = new LinkedList<String>(); for (TokenEntry entry : searchResults.getEntries()) { aliases.add(entry.getAlias()); } assertArrayEquals(new String[] { allAliases[1], allAliases[3] }, aliases.toArray()); assertFalse("no more entries available", searchResults.isMoreEntriesAvailable()); // Query all except 3 and 1 searchResults = searchTokenEntries(0, Integer.MAX_VALUE, QueryCriteria.create() .add(Criteria.and( new Term(RelationalOperator.NEQ, CryptoTokenHelper.TokenEntryFields.alias.name(), allAliases[3]), new Term(RelationalOperator.NEQ, CryptoTokenHelper.TokenEntryFields.alias.name(), allAliases[1]))), true); aliases = new LinkedList<String>(); for (TokenEntry entry : searchResults.getEntries()) { aliases.add(entry.getAlias()); } assertArrayEquals( new String[] { allAliases[0], allAliases[2], allAliases[4], allAliases[5], allAliases[6] }, aliases.toArray()); assertFalse("no more entries available", searchResults.isMoreEntriesAvailable()); // Query all except 3 and 1, only get the 4 first entries searchResults = searchTokenEntries(0, 4, QueryCriteria.create() .add(Criteria.and( new Term(RelationalOperator.NEQ, CryptoTokenHelper.TokenEntryFields.alias.name(), allAliases[3]), new Term(RelationalOperator.NEQ, CryptoTokenHelper.TokenEntryFields.alias.name(), allAliases[1]))), true); aliases = new LinkedList<String>(); for (TokenEntry entry : searchResults.getEntries()) { aliases.add(entry.getAlias()); } assertArrayEquals(new String[] { allAliases[0], allAliases[2], allAliases[4], allAliases[5] }, aliases.toArray()); assertTrue("more entries available", searchResults.isMoreEntriesAvailable()); // Query all except 3 and 1 (same as last), but get the next one searchResults = searchTokenEntries(4, Integer.MAX_VALUE, QueryCriteria.create() .add(Criteria.and( new Term(RelationalOperator.NEQ, CryptoTokenHelper.TokenEntryFields.alias.name(), allAliases[3]), new Term(RelationalOperator.NEQ, CryptoTokenHelper.TokenEntryFields.alias.name(), allAliases[1]))), true); aliases = new LinkedList<String>(); for (TokenEntry entry : searchResults.getEntries()) { aliases.add(entry.getAlias()); } assertArrayEquals(new String[] { allAliases[6] }, aliases.toArray()); assertFalse("no more entries available", searchResults.isMoreEntriesAvailable()); // Query with both AND and OR // "alias-14", "alias-13", "alias-5", "alias-10", "alias-2", "alias-1" // (alias EQ "alias-2") OR (alias LIKE alias-1% AND alias NEQ alias-13) => alias-14, alias-10, alias-2, alias-1 searchResults = searchTokenEntries(0, Integer.MAX_VALUE, QueryCriteria.create().add(Criteria.or( new Term(RelationalOperator.EQ, CryptoTokenHelper.TokenEntryFields.alias.name(), "alias-2"), Criteria.and( new Term(RelationalOperator.LIKE, CryptoTokenHelper.TokenEntryFields.alias.name(), "alias-1%"), new Term(RelationalOperator.NEQ, CryptoTokenHelper.TokenEntryFields.alias.name(), "alias-13")))), true); aliases = new LinkedList<String>(); for (TokenEntry entry : searchResults.getEntries()) { aliases.add(entry.getAlias()); } String[] expected = new String[] { "alias-14", "alias-10", "alias-2", "alias-1" }; Arrays.sort(expected); String[] actual = aliases.toArray(new String[0]); Arrays.sort(actual); assertArrayEquals(expected, actual); assertFalse("no more entries available", searchResults.isMoreEntriesAvailable()); // Check that data is not included boolean includeData = false; searchResults = searchTokenEntries(0, Integer.MAX_VALUE, QueryCriteria.create().add(new Term(RelationalOperator.EQ, CryptoTokenHelper.TokenEntryFields.alias.name(), allAliases[3])), includeData); TokenEntry entry = searchResults.getEntries().iterator().next(); assertNull("chain", entry.getChain()); assertNull("parsedChain", entry.getParsedChain()); assertNull("date", entry.getCreationDate()); assertNull("trustedCert", entry.getTrustedCertificate()); assertNull("parsedTrustedCert", entry.getParsedTrustedCertificate()); assertTrue("info", entry.getInfo() == null || entry.getInfo().isEmpty()); } finally { for (String alias : testAliases) { try { destroyKey(alias); } catch (Exception ex) { LOG.error("Failed to remove alias: " + alias + ": " + ex.getLocalizedMessage()); } } } } protected void importCertificateChainHelper(final String existingKey) throws NoSuchAlgorithmException, NoSuchProviderException, OperatorCreationException, IOException, CertificateException, CryptoTokenOfflineException, IllegalArgumentException, CertificateEncodingException, OperationUnsupportedException, InvalidWorkerIdException, SignServerException { final String additionalAlias = "additionalKey"; try { final ISignerCertReqInfo req = new PKCS10CertReqInfo("SHA1WithRSA", "CN=imported", null); Base64SignerCertReqData reqData = (Base64SignerCertReqData) genCertificateRequest(req, false, existingKey); // Issue certificate PKCS10CertificationRequest csr = new PKCS10CertificationRequest( Base64.decode(reqData.getBase64CertReq())); KeyPair issuerKeyPair = CryptoUtils.generateRSA(512); final X509CertificateHolder cert = new X509v3CertificateBuilder(new X500Name("CN=Test Issuer"), BigInteger.ONE, new Date(), new Date(System.currentTimeMillis() + TimeUnit.DAYS.toMillis(365)), csr.getSubject(), csr.getSubjectPublicKeyInfo()) .build(new JcaContentSignerBuilder("SHA256WithRSA").setProvider("BC") .build(issuerKeyPair.getPrivate())); // import certficate chain importCertificateChain(Arrays.asList(CertTools.getCertfromByteArray(cert.getEncoded())), existingKey); List<Certificate> chain = getCertificateChain(existingKey); assertEquals("Number of certs", 1, chain.size()); Certificate foundCert = chain.get(0); assertTrue("Imported cert", Arrays.equals(foundCert.getEncoded(), cert.getEncoded())); generateKey("RSA", "1024", additionalAlias); // Isse additional certificate reqData = (Base64SignerCertReqData) genCertificateRequest(req, false, additionalAlias); csr = new PKCS10CertificationRequest(Base64.decode(reqData.getBase64CertReq())); issuerKeyPair = CryptoUtils.generateRSA(512); final X509CertificateHolder newCert = new X509v3CertificateBuilder(new X500Name("CN=Test Issuer2"), BigInteger.ONE, new Date(), new Date(System.currentTimeMillis() + TimeUnit.DAYS.toMillis(365)), csr.getSubject(), csr.getSubjectPublicKeyInfo()) .build(new JcaContentSignerBuilder("SHA256WithRSA").setProvider("BC") .build(issuerKeyPair.getPrivate())); // import certficate chain importCertificateChain(Arrays.asList(CertTools.getCertfromByteArray(newCert.getEncoded())), additionalAlias); // check that previously imported cert chain is un-affected chain = getCertificateChain(existingKey); assertEquals("Number of certs", 1, chain.size()); foundCert = chain.get(0); assertTrue("Imported cert", Arrays.equals(foundCert.getEncoded(), cert.getEncoded())); // Test that it is not allowed to import a certificate for // an other key try { final List<Certificate> chainForExistingKey = chain; final String aliasForAnOtherKey = additionalAlias; importCertificateChain(chainForExistingKey, aliasForAnOtherKey); fail("Should have thrown exception about the key not matching"); } catch (CryptoTokenOfflineException expected) { assertTrue("ex: " + expected.getMessage(), expected.getMessage().contains("does not match")); } } finally { try { destroyKey(additionalAlias); } catch (KeyStoreException ex) { LOG.error("Failed to remove additional key"); } } } /** * Tests export of certificate chain. First imports a generate certificate * chain and then checks that it can be read back. Then imports an other * chain and checks again. * @param existingKey entry to use */ protected void exportCertificatesHelper(final String existingKey) throws CryptoTokenOfflineException, KeyStoreException, InvalidWorkerIdException, SignServerException, IllegalArgumentException, CertificateException, CertificateEncodingException, OperationUnsupportedException, NoSuchAlgorithmException, NoSuchProviderException, OperatorCreationException, IOException, QueryException, OperationUnsupportedException, AuthorizationDeniedException, InvalidAlgorithmParameterException, UnsupportedCryptoTokenParameter { final ISignerCertReqInfo req = new PKCS10CertReqInfo("SHA1WithRSA", "CN=imported", null); final Base64SignerCertReqData reqData = (Base64SignerCertReqData) genCertificateRequest(req, false, existingKey); // Generate a certificate chain that we will try to import and later export KeyPair issuerKeyPair = CryptoUtils.generateRSA(512); final X509CertificateHolder issuerCert = new JcaX509v3CertificateBuilder( new X500Name("CN=Test Import/Export CA"), BigInteger.ONE, new Date(), new Date(System.currentTimeMillis() + TimeUnit.DAYS.toMillis(3650)), new X500Name("CN=Test Import/Export CA"), issuerKeyPair.getPublic()) .build(new JcaContentSignerBuilder("SHA256WithRSA").setProvider("BC") .build(issuerKeyPair.getPrivate())); PKCS10CertificationRequest csr = new PKCS10CertificationRequest(Base64.decode(reqData.getBase64CertReq())); final X509CertificateHolder subjectCert1 = new X509v3CertificateBuilder( new X500Name("CN=Test Import/Export CA"), BigInteger.ONE, new Date(), new Date(System.currentTimeMillis() + TimeUnit.DAYS.toMillis(365)), new X500Name("CN=Test Import/Export 1"), csr.getSubjectPublicKeyInfo()) .build(new JcaContentSignerBuilder("SHA256WithRSA").setProvider("BC") .build(issuerKeyPair.getPrivate())); final X509CertificateHolder subjectCert2 = new X509v3CertificateBuilder( new X500Name("CN=Test Import/Export CA"), BigInteger.ONE, new Date(), new Date(System.currentTimeMillis() + TimeUnit.DAYS.toMillis(365)), new X500Name("CN=Test Import/Export 2"), csr.getSubjectPublicKeyInfo()) .build(new JcaContentSignerBuilder("SHA256WithRSA").setProvider("BC") .build(issuerKeyPair.getPrivate())); // Import certficate chain 1 importCertificateChain(Arrays.asList(CertTools.getCertfromByteArray(subjectCert1.getEncoded()), CertTools.getCertfromByteArray(issuerCert.getEncoded())), existingKey); // Find the entry TokenSearchResults searchResults = searchTokenEntries(0, Integer.MAX_VALUE, QueryCriteria.create() .add(new Term(RelationalOperator.EQ, CryptoTokenHelper.TokenEntryFields.alias.name(), existingKey)), true); LinkedList<String> aliases = new LinkedList<String>(); for (TokenEntry entry : searchResults.getEntries()) { aliases.add(entry.getAlias()); } assertArrayEquals(new String[] { existingKey }, aliases.toArray()); TokenEntry entry = searchResults.getEntries().iterator().next(); Certificate[] parsedChain = entry.getParsedChain(); assertEquals("right subject", new JcaX509CertificateConverter().getCertificate(subjectCert1).getSubjectX500Principal().getName(), ((X509Certificate) parsedChain[0]).getSubjectX500Principal().getName()); assertEquals("right issuer", new JcaX509CertificateConverter().getCertificate(issuerCert).getSubjectX500Principal().getName(), ((X509Certificate) parsedChain[1]).getSubjectX500Principal().getName()); // Import certificate chain 2 importCertificateChain(Arrays.asList(CertTools.getCertfromByteArray(subjectCert2.getEncoded()), CertTools.getCertfromByteArray(issuerCert.getEncoded())), existingKey); // Find the entry searchResults = searchTokenEntries(0, Integer.MAX_VALUE, QueryCriteria.create() .add(new Term(RelationalOperator.EQ, CryptoTokenHelper.TokenEntryFields.alias.name(), existingKey)), true); entry = searchResults.getEntries().iterator().next(); parsedChain = entry.getParsedChain(); assertEquals("right subject", new JcaX509CertificateConverter().getCertificate(subjectCert2).getSubjectX500Principal().getName(), ((X509Certificate) parsedChain[0]).getSubjectX500Principal().getName()); assertEquals("right issuer", new JcaX509CertificateConverter().getCertificate(issuerCert).getSubjectX500Principal().getName(), ((X509Certificate) parsedChain[1]).getSubjectX500Principal().getName()); } }