/* * Copyright (c) 2013-2014 EMC Corporation * All Rights Reserved */ package com.emc.storageos.security.keystore; import java.io.FileInputStream; import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; import java.security.KeyStore; import java.security.KeyStore.LoadStoreParameter; import java.security.KeyStore.PrivateKeyEntry; import java.security.KeyStore.ProtectionParameter; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.UnrecoverableEntryException; import java.security.UnrecoverableKeyException; import java.security.cert.Certificate; import java.security.cert.CertificateException; import java.util.ArrayList; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Properties; import com.emc.storageos.security.ApplicationContextUtil; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.springframework.util.CollectionUtils; import com.emc.storageos.coordinator.client.service.impl.CoordinatorClientImpl; import com.emc.storageos.coordinator.client.service.impl.CoordinatorClientInetAddressMap; import com.emc.storageos.coordinator.client.service.impl.DualInetAddress; import com.emc.storageos.coordinator.common.impl.ZkConnection; import com.emc.storageos.security.exceptions.SecurityException; import com.emc.storageos.security.keystore.impl.DistributedKeyStoreImpl; import com.emc.storageos.security.keystore.impl.DistributedLoadKeyStoreParam; import com.emc.storageos.security.keystore.impl.KeyCertificateAlgorithmValuesHolder; import com.emc.storageos.security.keystore.impl.KeyCertificateEntry; import com.emc.storageos.security.keystore.impl.KeyCertificatePairGenerator; import com.emc.storageos.security.keystore.impl.KeystoreEngine; import com.emc.storageos.security.keystore.impl.SecurityProvider; import com.emc.storageos.security.keystore.impl.TrustedCertificateEntry; import com.emc.storageos.svcs.errorhandling.resources.ServiceCode; /** * */ public class KeystoreTest { private final String coordinatorServer = "coordinator://localhost:2181"; private final String defaultOvfPropsLocation = "/etc/config.defaults"; private final String ovfPropsLocation = "/etc/ovfenv.properties"; private DistributedLoadKeyStoreParam loadStoreParam; private LoadStoreParameter invalidLoadStoreParam; private KeyCertificatePairGenerator gen; private final CoordinatorClientImpl coordinatorClient = new CoordinatorClientImpl(); @Before public void setup() throws URISyntaxException, IOException { ApplicationContextUtil.initContext(System.getProperty("buildType"), ApplicationContextUtil.SECURITY_CONTEXTS); List<URI> uri = new ArrayList<URI>(); uri.add(URI.create(coordinatorServer)); ZkConnection connection = new ZkConnection(); connection.setServer(uri); connection.build(); coordinatorClient.setZkConnection(connection); CoordinatorClientInetAddressMap map = new CoordinatorClientInetAddressMap(); map.setNodeId("standalone"); DualInetAddress localAddress = DualInetAddress.fromAddresses("127.0.0.1", "::1"); map.setDualInetAddress(localAddress); Map<String, DualInetAddress> controllerNodeIPLookupMap = new HashMap<String, DualInetAddress>(); controllerNodeIPLookupMap.put("localhost", localAddress); map.setControllerNodeIPLookupMap(controllerNodeIPLookupMap); coordinatorClient.setInetAddessLookupMap(map); coordinatorClient.start(); FileInputStream is = new FileInputStream(defaultOvfPropsLocation); Properties defaultProp = new Properties(); defaultProp.load(is); is.close(); is = new FileInputStream(ovfPropsLocation); Properties ovfProps = new Properties(); ovfProps.load(is); is.close(); CoordinatorClientImpl.setDefaultProperties(defaultProp); CoordinatorClientImpl.setOvfProperties(ovfProps); loadStoreParam = new DistributedLoadKeyStoreParam(); loadStoreParam.setCoordinator(coordinatorClient); invalidLoadStoreParam = new LoadStoreParameter() { @Override public ProtectionParameter getProtectionParameter() { return null; } }; gen = new KeyCertificatePairGenerator(); KeyCertificateAlgorithmValuesHolder values = new KeyCertificateAlgorithmValuesHolder(coordinatorClient); gen.setKeyCertificateAlgorithmValuesHolder(values); } @Test public void testZookeeperKeystore() throws IOException { DistributedKeyStore zookeeperKeystore = new DistributedKeyStoreImpl(); boolean exceptionThrown = false; try { zookeeperKeystore.init(invalidLoadStoreParam); } catch (SecurityException e) { exceptionThrown = true; } Assert.assertTrue(exceptionThrown); zookeeperKeystore.init(loadStoreParam); // this is in case this test was run previously zookeeperKeystore.setTrustedCertificates(null); KeyCertificateEntry origEntry = gen.generateKeyCertificatePair(); origEntry.setCreationDate(new Date()); zookeeperKeystore.setKeyCertificatePair(origEntry); KeyCertificateEntry storedEntry = zookeeperKeystore.getKeyCertificatePair(); assertKeyCertificateEntriesEquals(origEntry, storedEntry); origEntry = gen.generateKeyCertificatePair(); TrustedCertificateEntry origCertEntry = new TrustedCertificateEntry(origEntry.getCertificateChain()[0], new Date()); Map<String, TrustedCertificateEntry> origCertEntries = new HashMap<String, TrustedCertificateEntry>(); origCertEntries.put("trustedCert1", origCertEntry); zookeeperKeystore.addTrustedCertificate("trustedCert1", origCertEntry); origEntry = gen.generateKeyCertificatePair(); origCertEntry = new TrustedCertificateEntry(origEntry.getCertificateChain()[0], new Date()); origCertEntries.put("trustedCert2", origCertEntry); zookeeperKeystore.addTrustedCertificate("trustedCert2", origCertEntry); assertTrustedCertsEquals(origCertEntries, zookeeperKeystore.getTrustedCertificates()); origEntry = gen.generateKeyCertificatePair(); origCertEntry = new TrustedCertificateEntry(origEntry.getCertificateChain()[0], new Date()); origCertEntries.put("trustedCert3", origCertEntry); zookeeperKeystore.setTrustedCertificates(origCertEntries); assertTrustedCertsEquals(origCertEntries, zookeeperKeystore.getTrustedCertificates()); origCertEntries.remove("trustedCert3"); zookeeperKeystore.setTrustedCertificates(origCertEntries); assertTrustedCertsEquals(origCertEntries, zookeeperKeystore.getTrustedCertificates()); origCertEntries.remove("trustedCert2"); zookeeperKeystore.removeTrustedCertificate("trustedCert2"); assertTrustedCertsEquals(origCertEntries, zookeeperKeystore.getTrustedCertificates()); zookeeperKeystore.removeTrustedCertificate("trustedCert10"); } /** * @param expected * @param actual */ private void assertTrustedCertsEquals( Map<String, TrustedCertificateEntry> expected, Map<String, TrustedCertificateEntry> actual) { if (CollectionUtils.isEmpty(expected)) { Assert.assertTrue(CollectionUtils.isEmpty(actual)); } else { Assert.assertFalse(CollectionUtils.isEmpty(actual)); Assert.assertEquals(expected.size(), actual.size()); for (Entry<String, TrustedCertificateEntry> entry : expected.entrySet()) { Assert.assertTrue(actual.containsKey(entry.getKey())); assertEqualCertificateEntry(entry.getValue(), actual.get(entry.getKey())); } } } /** * @param value * @param trustedCertificateEntry */ private void assertEqualCertificateEntry(TrustedCertificateEntry expected, TrustedCertificateEntry actual) { Assert.assertEquals(expected.getCreationDate(), actual.getCreationDate()); Assert.assertEquals(expected.getCertificate(), actual.getCertificate()); } /** * @param expected * @param actual */ private void assertKeyCertificateEntriesEquals(KeyCertificateEntry expected, KeyCertificateEntry actual) { org.junit.Assert.assertArrayEquals(expected.getKey(), actual.getKey()); Assert.assertEquals(expected.getCertificateChain()[0], actual.getCertificateChain()[0]); Assert.assertEquals(expected.getCreationDate(), actual.getCreationDate()); } @Test public void testKeystoreEngine() throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException, InterruptedException, UnrecoverableEntryException { DistributedKeyStore zookeeperKeystore = new DistributedKeyStoreImpl(); zookeeperKeystore.init(loadStoreParam); // this is in case this test was run previously zookeeperKeystore.setTrustedCertificates(null); zookeeperKeystore.setKeyCertificatePair(null); // test keystore loading KeyStore ks = KeyStore.getInstance(SecurityProvider.KEYSTORE_TYPE, new SecurityProvider()); boolean exceptionThrown = false; try { ks.load(null, null); } catch (SecurityException e) { Assert.assertEquals(ServiceCode.SECURITY_ERROR, e.getServiceCode()); Assert.assertEquals( "Could not initialize the keystore. The ViPR keystore can only be initialized with a LoadKeyStoreParam.", e.getMessage()); exceptionThrown = true; } Assert.assertTrue(exceptionThrown); exceptionThrown = false; try { ks.load(invalidLoadStoreParam); } catch (SecurityException e) { Assert.assertEquals(ServiceCode.SECURITY_ERROR, e.getServiceCode()); Assert.assertEquals( "Could not initialize the keystore. The ViPR keystore can only be initialized with a LoadKeyStoreParam.", e.getMessage()); exceptionThrown = true; } Assert.assertTrue(exceptionThrown); // now it shouldn't throw ks.load(loadStoreParam); // //////////////////////////////////////////////////////////////////////// // / // / key tests // / // //////////////////////////////////////////////////////////////////////// // should have by default the ViPR key List<String> expectedAliases = new ArrayList<String>(); expectedAliases.add(KeystoreEngine.ViPR_KEY_AND_CERTIFICATE_ALIAS); assertAliasesIn(ks, expectedAliases); // update the vipr key using ks.setEntry Date beforeDate = new Date(); KeyCertificateEntry entry = gen.generateKeyCertificatePair(); KeyStore.PrivateKeyEntry privateKeyEntry = new PrivateKeyEntry( KeyCertificatePairGenerator.loadPrivateKeyFromBytes(entry .getKey()), entry.getCertificateChain()); KeyStore.PasswordProtection empryProtectionParam = new KeyStore.PasswordProtection("".toCharArray()); ks.setEntry(KeystoreEngine.ViPR_KEY_AND_CERTIFICATE_ALIAS, privateKeyEntry, new KeyStore.PasswordProtection("123".toCharArray())); Date afterDate = new Date(); assertKeyCertificateEntryEquals(ks, entry); assertCreationDateInTImeRange(ks, KeystoreEngine.ViPR_KEY_AND_CERTIFICATE_ALIAS, beforeDate, afterDate); // set the key entry using setKeyEntry (there are 2 versions, one with the Key // object, and another with byte[] ) beforeDate = new Date(); entry = gen.generateKeyCertificatePair(); ks.setKeyEntry(KeystoreEngine.ViPR_KEY_AND_CERTIFICATE_ALIAS, entry.getKey(), entry.getCertificateChain()); afterDate = new Date(); assertKeyCertificateEntryEquals(ks, entry); assertCreationDateInTImeRange(ks, KeystoreEngine.ViPR_KEY_AND_CERTIFICATE_ALIAS, beforeDate, afterDate); beforeDate = new Date(); entry = gen.generateKeyCertificatePair(); ks.setKeyEntry( KeystoreEngine.ViPR_KEY_AND_CERTIFICATE_ALIAS, KeyCertificatePairGenerator.loadPrivateKeyFromBytes(entry.getKey()), "".toCharArray(), entry.getCertificateChain()); afterDate = new Date(); assertKeyCertificateEntryEquals(ks, entry); assertCreationDateInTImeRange(ks, KeystoreEngine.ViPR_KEY_AND_CERTIFICATE_ALIAS, beforeDate, afterDate); // //////////////////////////////////////////////////////////////////////// // / // / certificates tests // / // //////////////////////////////////////////////////////////////////////// String certAlias = "someCert"; // add a new trusted certificate using ks.setEntry beforeDate = new Date(); entry = gen.generateKeyCertificatePair(); KeyStore.TrustedCertificateEntry trustedCertEntry = new KeyStore.TrustedCertificateEntry(entry.getCertificateChain()[0]); ks.setEntry(certAlias, trustedCertEntry, null); afterDate = new Date(); expectedAliases.add(certAlias); assertAliasesIn(ks, expectedAliases); assertTrustedCertEquals(ks, entry, certAlias); assertCreationDateInTImeRange(ks, certAlias, beforeDate, afterDate); // add a new trusted certificate using ks.setCertificateEntry beforeDate = new Date(); entry = gen.generateKeyCertificatePair(); certAlias = "someCert1"; ks.setCertificateEntry(certAlias, entry.getCertificateChain()[0]); afterDate = new Date(); expectedAliases.add(certAlias); assertAliasesIn(ks, expectedAliases); assertTrustedCertEquals(ks, entry, certAlias); assertCreationDateInTImeRange(ks, certAlias, beforeDate, afterDate); // remove the trusted certificate entry ks.deleteEntry(certAlias); expectedAliases.remove(certAlias); assertAliasesIn(ks, expectedAliases); // //////////////////////////////////////////////////////////////////////// // / // / Negative testing // / // //////////////////////////////////////////////////////////////////////// String invalidEntryName = "invalidEntry"; // cannot delete the ViPR key exceptionThrown = false; try { ks.deleteEntry(KeystoreEngine.ViPR_KEY_AND_CERTIFICATE_ALIAS); } catch (SecurityException e) { Assert.assertEquals(ServiceCode.SECURITY_ERROR, e.getServiceCode()); Assert.assertEquals( "The ViPR key and certificate cannot be deleted, it can only be updated.", e.getMessage()); exceptionThrown = true; } catch (KeyStoreException e) { Assert.fail(); } Assert.assertTrue(exceptionThrown); assertAliasesIn(ks, expectedAliases); entry = gen.generateKeyCertificatePair(); // try to set a key that is not the vipr key // using ks.setEntry privateKeyEntry = new PrivateKeyEntry( KeyCertificatePairGenerator.loadPrivateKeyFromBytes(entry .getKey()), entry.getCertificateChain()); exceptionThrown = false; try { ks.setEntry(invalidEntryName, privateKeyEntry, empryProtectionParam); } catch (SecurityException e) { Assert.assertEquals(ServiceCode.SECURITY_ERROR, e.getServiceCode()); Assert.assertEquals( "Cannot update any key and certificate entry except for the ViPR key and certificate.", e.getMessage()); exceptionThrown = true; } Assert.assertTrue(exceptionThrown); assertAliasesIn(ks, expectedAliases); // using ks.setKey which accepts byte[] try { ks.setKeyEntry(invalidEntryName, entry.getKey(), entry.getCertificateChain()); } catch (SecurityException e) { Assert.assertEquals(ServiceCode.SECURITY_ERROR, e.getServiceCode()); Assert.assertEquals( "Cannot update any key and certificate entry except for the ViPR key and certificate.", e.getMessage()); exceptionThrown = true; } Assert.assertTrue(exceptionThrown); assertAliasesIn(ks, expectedAliases); // using ks.setKey which accepts Key object try { ks.setKeyEntry(invalidEntryName, KeyCertificatePairGenerator.loadPrivateKeyFromBytes(entry.getKey()), "".toCharArray(), entry.getCertificateChain()); } catch (SecurityException e) { Assert.assertEquals(ServiceCode.SECURITY_ERROR, e.getServiceCode()); Assert.assertEquals( "Cannot update any key and certificate entry except for the ViPR key and certificate.", e.getMessage()); exceptionThrown = true; } Assert.assertTrue(exceptionThrown); assertAliasesIn(ks, expectedAliases); // try getting an invalid entry Assert.assertFalse(ks.containsAlias(invalidEntryName)); Assert.assertFalse(ks.entryInstanceOf(invalidEntryName, KeyStore.TrustedCertificateEntry.class)); Assert.assertFalse(ks.entryInstanceOf(invalidEntryName, KeyStore.PrivateKeyEntry.class)); Assert.assertFalse(ks.entryInstanceOf(invalidEntryName, KeyStore.SecretKeyEntry.class)); Assert.assertFalse(ks.isCertificateEntry(invalidEntryName)); Assert.assertFalse(ks.isKeyEntry(invalidEntryName)); Assert.assertNull(ks.getCertificate(invalidEntryName)); Assert.assertNull(ks.getCertificateAlias(entry.getCertificateChain()[0])); Assert.assertNull(ks.getCertificateChain(invalidEntryName)); Assert.assertNull(ks.getCreationDate(invalidEntryName)); Assert.assertNull(ks.getEntry(invalidEntryName, empryProtectionParam)); Assert.assertNull(ks.getKey(invalidEntryName, "".toCharArray())); // try to delete an entry that does not exist exceptionThrown = false; try { ks.deleteEntry(invalidEntryName); } catch (SecurityException e) { Assert.fail(); } catch (KeyStoreException e) { exceptionThrown = true; Assert.assertEquals("The specified alias " + invalidEntryName + " does not exist", e.getMessage()); } Assert.assertTrue(exceptionThrown); } /** * @param ks * @param entry * @param beforeDate * @param afterDate * @param certAlias * @throws KeyStoreException * @throws NoSuchAlgorithmException * @throws UnrecoverableEntryException * @throws UnrecoverableKeyException */ private void assertTrustedCertEquals(KeyStore ks, KeyCertificateEntry entry, String certAlias) throws KeyStoreException, NoSuchAlgorithmException, UnrecoverableEntryException, UnrecoverableKeyException { Assert.assertTrue(ks.isCertificateEntry(certAlias)); Assert.assertFalse(ks.isKeyEntry(certAlias)); Assert.assertTrue(ks.entryInstanceOf(certAlias, KeyStore.TrustedCertificateEntry.class)); Assert.assertFalse(ks.entryInstanceOf(certAlias, KeyStore.PrivateKeyEntry.class)); Assert.assertFalse(ks.entryInstanceOf(certAlias, KeyStore.SecretKeyEntry.class)); Certificate cert = ks.getCertificate(certAlias); Assert.assertNotNull(cert); Assert.assertEquals(entry.getCertificateChain()[0], cert); Assert.assertEquals(certAlias, ks.getCertificateAlias(cert)); Assert.assertNull(ks.getCertificateChain(certAlias)); KeyStore.Entry ksEntry = ks.getEntry(certAlias, null); Assert.assertEquals(KeyStore.TrustedCertificateEntry.class, ksEntry.getClass()); KeyStore.TrustedCertificateEntry trustedCertEntry = (KeyStore.TrustedCertificateEntry) ksEntry; Assert.assertEquals(entry.getCertificateChain()[0], trustedCertEntry.getTrustedCertificate()); Assert.assertNull(ks.getKey(certAlias, null)); } /** * @param ks * @param entry * @param beforeDate * @param afterDate * @throws KeyStoreException * @throws NoSuchAlgorithmException * @throws UnrecoverableEntryException */ private void assertKeyCertificateEntryEquals(KeyStore ks, KeyCertificateEntry entry) throws KeyStoreException, NoSuchAlgorithmException, UnrecoverableEntryException { KeyStore.PasswordProtection empryProtectionParam = new KeyStore.PasswordProtection("123".toCharArray()); KeyStore.Entry ksEntry = ks.getEntry(KeystoreEngine.ViPR_KEY_AND_CERTIFICATE_ALIAS, empryProtectionParam); Assert.assertTrue(ksEntry instanceof KeyStore.PrivateKeyEntry); Assert.assertTrue(ksEntry instanceof KeyStore.PrivateKeyEntry); KeyStore.PrivateKeyEntry ksKey = (PrivateKeyEntry) ksEntry; Assert.assertEquals( KeyCertificatePairGenerator.loadPrivateKeyFromBytes(entry.getKey()), ksKey.getPrivateKey()); Assert.assertArrayEquals(entry.getCertificateChain(), ksKey.getCertificateChain()); Assert.assertArrayEquals(entry.getCertificateChain(), ks.getCertificateChain(KeystoreEngine.ViPR_KEY_AND_CERTIFICATE_ALIAS)); Assert.assertArrayEquals(entry.getKey(), ks.getKey(KeystoreEngine.ViPR_KEY_AND_CERTIFICATE_ALIAS, null) .getEncoded()); Assert.assertTrue(ks.entryInstanceOf( KeystoreEngine.ViPR_KEY_AND_CERTIFICATE_ALIAS, KeyStore.PrivateKeyEntry.class)); Assert.assertFalse(ks.entryInstanceOf( KeystoreEngine.ViPR_KEY_AND_CERTIFICATE_ALIAS, KeyStore.SecretKeyEntry.class)); Assert.assertFalse(ks.entryInstanceOf( KeystoreEngine.ViPR_KEY_AND_CERTIFICATE_ALIAS, KeyStore.TrustedCertificateEntry.class)); Assert.assertEquals(entry.getCertificateChain()[0], ks.getCertificate(KeystoreEngine.ViPR_KEY_AND_CERTIFICATE_ALIAS)); Assert.assertEquals(KeystoreEngine.ViPR_KEY_AND_CERTIFICATE_ALIAS, ks.getCertificateAlias(entry.getCertificateChain()[0])); Assert.assertFalse(ks .isCertificateEntry(KeystoreEngine.ViPR_KEY_AND_CERTIFICATE_ALIAS)); Assert.assertTrue(ks.isKeyEntry(KeystoreEngine.ViPR_KEY_AND_CERTIFICATE_ALIAS)); } private void assertCreationDateInTImeRange(KeyStore ks, String alias, Date beforeDate, Date afterDate) throws KeyStoreException { Date creationDate = ks.getCreationDate(alias); Assert.assertTrue(creationDate.after(beforeDate)); Assert.assertTrue(creationDate.before(afterDate)); } private void assertAliasesEquals(KeyStore ks, List<String> expectedAliases) throws KeyStoreException { // the sizeof the keystore should be 1 on startup Assert.assertEquals(expectedAliases.size(), ks.size()); List<String> aliases = Collections.list(ks.aliases()); Assert.assertEquals(expectedAliases.size(), aliases.size()); for (String expectedAlias : expectedAliases) { Assert.assertTrue(aliases.contains(expectedAlias)); Assert.assertTrue(ks.containsAlias(expectedAlias)); } } private void assertAliasesIn(KeyStore ks, List<String> expectedAliases) throws KeyStoreException { for (String expectedAlias : expectedAliases) { Assert.assertTrue(ks.containsAlias(expectedAlias)); } } }