/* * JBoss, Home of Professional Open Source. * Copyright 2008, Red Hat Middleware LLC, and individual contributors * as indicated by the @author tags. See the copyright.txt file in the * distribution for a full listing of individual contributors. * * This 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 (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.jboss.test.security.vault; import org.jboss.security.Util; import org.jboss.security.plugins.PBEUtils; import org.jboss.security.vault.SecurityVault; import org.jboss.security.vault.SecurityVaultException; import org.jboss.security.vault.SecurityVaultFactory; import org.jboss.security.vault.SecurityVaultUtil; import org.jboss.test.SecurityActions; import org.junit.Assume; import org.junit.Test; import org.picketbox.plugins.vault.PicketBoxSecurityVault; import javax.crypto.SecretKey; import javax.crypto.SecretKeyFactory; import javax.crypto.spec.PBEKeySpec; import javax.crypto.spec.PBEParameterSpec; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.lang.reflect.Field; import java.nio.channels.FileChannel; import java.security.AccessController; import java.security.PrivilegedAction; import java.util.HashMap; import java.util.Map; import static org.junit.Assert.*; /** * Unit Test the {@link SecurityVault} Implementation * * Note: replacement-vault.keystore has been created using: * keytool -genkey -alias mykey -keystore replacement-vault.keystore -keyalg RSA -keysize 1024 -storepass supersecret11 -keypass supersecret11 -dname "CN=Picketbox vault,OU=picketbox,O=JBoss" * * @author Anil.Saldhana@redhat.com * @since Aug 12, 2011 */ public class SecurityVaultUnitTestCase { //String dataDir = "${java.io.tmpdir}/enc/"; @Test public void testDefaultVault() throws Exception { SecurityVault vault = SecurityVaultFactory.get(); assertNotNull(vault); assertTrue(vault instanceof PicketBoxSecurityVault); assertFalse(vault.isInitialized()); } @Test public void testClassLoaderVault() throws Exception { //Back up the existing vault and reset it Field field = SecurityVaultFactory.class.getDeclaredField("vault"); field.setAccessible(true); Object existingVault = field.get(null); try { field.set(null, null); ClassLoader cl = SecurityVaultFactory.class.getClassLoader(); SecurityVault vault = SecurityVaultFactory.get(cl, TestVault.class.getName()); assertNotNull(vault); assertTrue(vault instanceof TestVault); assertFalse(vault.isInitialized()); } finally { if (existingVault != null) { field.set(null, existingVault); } } } @Test public void testHandshake() throws Exception { setInitialVaulConditions("src/test/resources/keystore/vault.jks", "target/vaults/vault1/vault.jks", "src/test/resources/keystore/vault_data", "target/vaults/vault1/vault_data"); SecurityVault vault = getNewSecurityVaultInstance(); Map<String,Object> options = getVaultOptionsMap( "target/vaults/vault1/vault.jks", "target/vaults/vault1/vault_data", "vault", "12438567", 50, "vault22"); vault.init(options); assertTrue(vault.isInitialized()); Map<String,Object> handshakeOptions = new HashMap<String,Object>(); handshakeOptions.put(PicketBoxSecurityVault.PUBLIC_CERT,"vault"); byte[] sharedKey = vault.handshake(handshakeOptions); assertNotNull(sharedKey); } @Test public void testHandshakeAnConversionForLongAlias() throws Exception { setInitialVaulConditions("src/test/resources/long_alias_keystore/vault.jks", "target/vaults/long_alias_keystore/vault.jks", "src/test/resources/long_alias_keystore/vault_data", "target/vaults/long_alias_keystore/vault_data"); SecurityVault vault = getNewSecurityVaultInstance(); Map<String,Object> options = getVaultOptionsMap( "target/vaults/long_alias_keystore/vault.jks", "target/vaults/long_alias_keystore/vault_data", "superverylongvaultname", "87654321", 23, "password1234", Boolean.TRUE); vault.init(options); assertTrue("Vault is supposed to be inicialized", vault.isInitialized()); Map<String,Object> handshakeOptions = new HashMap<String,Object>(); byte[] sharedKey = vault.handshake(handshakeOptions); assertNotNull(sharedKey); // not relevant anymore, but leaving it as is boolean containsLineBreaks = false; for (byte b: sharedKey) { if (b == '\n') { containsLineBreaks = true; break; } } assertFalse("Shared key returned from hadshake cannot contain line break character", containsLineBreaks); } @Test public void testMaskedPasswordFallback() throws Exception { setInitialVaulConditions( "src/test/resources/vault-fallback/vault-fallback.keystore", "target/vaults/vault-fallback/vault-fallback.keystore", "src/test/resources/vault-fallback/vault_data", "target/vaults/vault-fallback/vault_data"); // see src/test/resources/vault-fallback/readme.txt // this test uses wrongly generated maked password value and should fallback to correct one final Map<String, Object> options = getVaultOptionsMap( "target/vaults/vault-fallback/vault-fallback.keystore", "target/vaults/vault-fallback/vault_data", "valias", "bdfbdf12", 12, "MASK-UWB5tlhOmKYzJVl9KZaPN"); SecurityVault vault = getNewSecurityVaultInstance(); assertFalse(vault.isInitialized()); vault.init(options); assertTrue(vault.isInitialized()); vault.handshake(null); // let's try to check if proper values are stored in the vault assertSecretValue(vault, "vblock", "attr1", "secret1"); } @Test public void testStoreAndRetrieve() throws Exception { setInitialVaulConditions("src/test/resources/keystore/vault.jks", "target/vaults/vault2/vault.jks", "src/test/resources/keystore/vault_data", "target/vaults/vault2/vault_data"); Map<String,Object> options = getVaultOptionsMap( "target/vaults/vault2/vault.jks", "target/vaults/vault2/vault_data", "vault", "12438567", 50, "vault22"); String vaultBlock = "SecBean"; String attributeName = "theAttribute"; char[] attributeValue = "someValue".toCharArray(); SecurityVault vault = getNewSecurityVaultInstance(); vault.init(options); assertTrue(vault.isInitialized()); Map<String,Object> handshakeOptions = new HashMap<String,Object>(); byte[] sharedKey = vault.handshake(handshakeOptions); assertNotNull(sharedKey); vault.store(vaultBlock, attributeName, attributeValue , sharedKey); assertTrue(vault.exists(vaultBlock, attributeName)); //Now retrieve assertEquals(new String(attributeValue), new String(vault.retrieve(vaultBlock, attributeName, sharedKey))); vault.store(vaultBlock+"1", attributeName+"2", attributeValue , sharedKey); assertEquals(new String(attributeValue), new String(vault.retrieve(vaultBlock+"1", attributeName+"2", sharedKey))); System.out.println("Currently storing:" + vault.keyList()); assertTrue(vault.remove(vaultBlock+"1", attributeName+"2", sharedKey)); assertFalse(vault.exists(vaultBlock+"1", attributeName+"2")); } @Test public void testClassBasedKeystorePassword() throws Exception { setInitialVaulConditions("src/test/resources/keystore/vault.jks", "target/vaults/vault2/vault.jks", "src/test/resources/keystore/vault_data", "target/vaults/vault2/vault_data"); Map<String,Object> options = getVaultOptionsMap( "target/vaults/vault2/vault.jks", "target/vaults/vault2/vault_data", "vault", "12438567", 50, "{CLASS}org.jboss.test.security.vault.KeystorePasswordProvider"); String vaultBlock = "aBlock"; String attributeName = "anAttribute"; char[] attributeValue = "aValue".toCharArray(); SecurityVault vault = getNewSecurityVaultInstance(); vault.init(options); assertTrue(vault.isInitialized()); Map<String,Object> handshakeOptions = new HashMap<String,Object>(); byte[] sharedKey = vault.handshake(handshakeOptions); assertNotNull(sharedKey); vault.store(vaultBlock, attributeName, attributeValue , sharedKey); assertTrue(vault.exists(vaultBlock, attributeName)); //Now retrieve assertEquals(new String(attributeValue), new String(vault.retrieve(vaultBlock, attributeName, sharedKey))); vault.store(vaultBlock+"1", attributeName+"2", attributeValue, sharedKey); assertEquals(new String(attributeValue), new String(vault.retrieve(vaultBlock+"1", attributeName+"2", sharedKey))); System.out.println("Currently storing:" + vault.keyList()); assertTrue(vault.remove(vaultBlock+"1", attributeName+"2", sharedKey)); assertFalse(vault.exists(vaultBlock+"1", attributeName+"2")); } @Test public void testExtCmdBasedKeystorePassword() throws Exception { // since this test uses an external BASH script it is valid for Linux systems only String OS_NAME = SecurityActions.getProperty("os.name", null); Assume.assumeTrue(OS_NAME.startsWith("Linux") || OS_NAME.startsWith("LINUX")); setInitialVaulConditions("src/test/resources/keystore/vault.jks", "target/vaults/vault2/vault.jks", "src/test/resources/keystore/vault_data", "target/vaults/vault2/vault_data"); String absolutePathToAskPass = SecurityVaultUnitTestCase.class.getResource("/bin/askpass.sh").getFile(); System.out.println("absolutePathToAskPass: " + absolutePathToAskPass); // 'Enter passphrase for *' is hard-coded into kwalletaskpass for example Map<String,Object> options = getVaultOptionsMap( "target/vaults/vault2/vault.jks", "target/vaults/vault2/vault_data", "vault", "12438567", 50, "{CMD}/bin/sh," + absolutePathToAskPass + ",Enter passphrase for askpass test"); String vaultBlock = "aBlock"; String attributeName = "anAttribute"; char[] attributeValue = "aValue".toCharArray(); SecurityVault vault = getNewSecurityVaultInstance(); vault.init(options); assertTrue(vault.isInitialized()); Map<String,Object> handshakeOptions = new HashMap<String,Object>(); byte[] sharedKey = vault.handshake(handshakeOptions); assertNotNull(sharedKey); vault.store(vaultBlock, attributeName, attributeValue , sharedKey); assertTrue(vault.exists(vaultBlock, attributeName)); //Now retrieve assertEquals(new String(attributeValue), new String(vault.retrieve(vaultBlock, attributeName, sharedKey))); vault.store(vaultBlock+"1", attributeName+"2", attributeValue, sharedKey); assertEquals(new String(attributeValue), new String(vault.retrieve(vaultBlock+"1", attributeName+"2", sharedKey))); System.out.println("Currently storing:" + vault.keyList()); assertTrue(vault.remove(vaultBlock+"1", attributeName+"2", sharedKey)); assertFalse(vault.exists(vaultBlock+"1", attributeName+"2")); } /** * See src/test/resources/vault-v0/readme.txt for initial vault setup (including secured attributes). * @throws Exception */ @Test public void testConversion() throws Exception { setInitialVaulConditions("src/test/resources/vault-v0/vault-jks.keystore", "target/vaults/vault-v0/vault-jks.keystore", "src/test/resources/vault-v0/vault_data", "target/vaults/vault-v0/vault_data"); final Map<String, Object> options = getVaultOptionsMap( "target/vaults/vault-v0/vault-jks.keystore", "target/vaults/vault-v0/vault_data", "thealias", "24681359", 88, "secretsecret"); SecurityVault vault = getNewSecurityVaultInstance(); // init should do the automatic conversion vault.init(options); assertTrue(vault.isInitialized()); byte[] sharedKey = vault.handshake(null); assertNotNull(sharedKey); // let's try to check if the converted vault contains all secret attributes from initial vault assertSecretValue(vault, "vb", "attr1", "pwd1"); assertSecretValue(vault, "vb", "attr2", "pwd2"); assertSecretValue(vault, "vb1", "attr1", "pwd3"); assertSecretValue(vault, "vb2", "attr2", "pwd4"); assertSecretValue(vault, "vb2", "attr3", "pwd5"); assertSecretValue(vault, "vb", "attr3", "pwd6"); // get new instance of vault to simulate restart of application server SecurityVault convertedVault = getNewSecurityVaultInstance(); assertFalse(convertedVault.isInitialized()); convertedVault.init(options); assertTrue(convertedVault.isInitialized()); convertedVault.handshake(null); // now try the same attributes on converted vault after restart assertSecretValue(convertedVault, "vb", "attr1", "pwd1"); assertSecretValue(convertedVault, "vb", "attr2", "pwd2"); assertSecretValue(convertedVault, "vb1", "attr1", "pwd3"); assertSecretValue(convertedVault, "vb2", "attr2", "pwd4"); assertSecretValue(convertedVault, "vb2", "attr3", "pwd5"); assertSecretValue(convertedVault, "vb", "attr3", "pwd6"); } @Test public void testVault_V1_open_retrieve() throws Exception { setInitialVaulConditions("src/test/resources/vault-v1/vault-jceks.keystore", "target/vaults/vault-v1/vault-jceks.keystore", "src/test/resources/vault-v1/vault_data", "target/vaults/vault-v1/vault_data"); final Map<String, Object> options = getVaultOptionsMap( "target/vaults/vault-v1/vault-jceks.keystore", "target/vaults/vault-v1/vault_data", "test", "12345678", 34, "secretsecret"); SecurityVault vault = getNewSecurityVaultInstance(); assertFalse(vault.isInitialized()); vault.init(options); assertTrue(vault.isInitialized()); vault.handshake(null); // let's try to check if proper values are stored in the vault assertSecretValue(vault, "vb1", "attr11", "secret11"); assertSecretValue(vault, "vb1", "attr12", "secret12"); } @Test(expected = SecurityVaultException.class) public void testVault_V1_open_wrong_alias() throws Exception { setInitialVaulConditions("src/test/resources/vault-v1/vault-jceks.keystore", "target/vaults/vault-v1-wrong/vault-jceks.keystore", "src/test/resources/vault-v1/vault_data", "target/vaults/vault-v1-wrong/vault_data"); final Map<String, Object> options = getVaultOptionsMap( "target/vaults/vault-v1-wrong/vault-jceks.keystore", "target/vaults/vault-v1-wrong/vault_data", "thewrongalias", "12345678", 34, "secretsecret"); SecurityVault vault = getNewSecurityVaultInstance(); assertFalse(vault.isInitialized()); vault.init(options); } @Test(expected = SecurityVaultException.class) public void testVaultWithReplacedKeystore() throws Exception { setInitialVaulConditions("src/test/resources/vault-v1/vault-replacement-jceks.keystore", "target/vaults/vault-v1/vault-jceks.keystore", "src/test/resources/vault-v1/vault_data", "target/vaults/vault-v1/vault_data"); final Map<String, Object> options = getVaultOptionsMap( "target/vaults/vault-v1/vault-jceks.keystore", "target/vaults/vault-v1/vault_data", "test", "12345678", 34, "secretsecret"); SecurityVault vault = getNewSecurityVaultInstance(); assertFalse(vault.isInitialized()); vault.init(options); assertTrue(vault.isInitialized()); vault.handshake(null); // let's try to check if the converted vault contains all secret attributes from initial vault assertSecretValue(vault, "vb1", "attr11", "secret11"); assertSecretValue(vault, "vb1", "attr12", "secret12"); } @Test public void testMoreSecretKeys() throws Exception { setInitialVaulConditions("src/test/resources/vault-v1-more/vault-jceks.keystore", "target/vaults/vault-v1-more/vault-jceks.keystore", "src/test/resources/vault-v1-more/vault_data", "target/vaults/vault-v1-more/vault_data"); final Map<String, Object> options = getVaultOptionsMap( "target/vaults/vault-v1-more/vault-jceks.keystore", "target/vaults/vault-v1-more/vault_data", "test", "12345678", 34, "secretsecret"); SecurityVault vault = getNewSecurityVaultInstance(); assertFalse(vault.isInitialized()); vault.init(options); assertTrue(vault.isInitialized()); vault.handshake(null); // let's try to check if proper values are stored in the vault assertSecretValue(vault, "vb1", "attr11", "secret11"); assertSecretValue(vault, "vb1", "attr12", "secret12"); final Map<String, Object> options2 = getVaultOptionsMap( "target/vaults/vault-v1-more/vault-jceks.keystore", "target/vaults/vault-v1-more/vault_data", "test2", "12345678", 34, "secretsecret"); SecurityVault vault2 = getNewSecurityVaultInstance(); assertFalse(vault2.isInitialized()); vault2.init(options2); assertTrue(vault2.isInitialized()); vault2.handshake(null); // let's try to check different alias can retrieve proper attribute assertSecretValue(vault2, "vb1", "attr13", "secret13"); try { assertSecretValue(vault2, "vb1", "attr11", "secret11"); fail("retrieving security attribute with different secret key alias has to fail."); } catch (SecurityVaultException e) { // deliberately empty } catch (Throwable e) { fail("unexpected exception " + e.getStackTrace().toString()); } } @Test public void testUtil() throws Exception { assertFalse(SecurityVaultUtil.isVaultFormat((String)null)); assertFalse(SecurityVaultUtil.isVaultFormat((char[])null)); } private String getMaskedPassword(String pwd, String salt, int iterationCount) throws Exception { if (Util.isPasswordCommand(pwd)) return pwd; if (pwd.startsWith(PicketBoxSecurityVault.PASS_MASK_PREFIX)) return pwd; String algo = "PBEwithMD5andDES"; // Create the PBE secret key SecretKeyFactory factory = SecretKeyFactory.getInstance("PBEwithMD5andDES"); char[] password = "somearbitrarycrazystringthatdoesnotmatter".toCharArray(); PBEParameterSpec cipherSpec = new PBEParameterSpec(salt.getBytes(), iterationCount); PBEKeySpec keySpec = new PBEKeySpec(password); SecretKey cipherKey = factory.generateSecret(keySpec); String maskedPass = PBEUtils.encode64(pwd.getBytes(), algo, cipherKey, cipherSpec); return PicketBoxSecurityVault.PASS_MASK_PREFIX + maskedPass; } private Map<String, Object> getVaultOptionsMap(String keystore, String encDataDir, String alias, String salz, int iter, String password, Boolean createKeystore) throws Exception { Map<String, Object> options = new HashMap<String, Object>(); options.put(PicketBoxSecurityVault.KEYSTORE_URL, keystore); options.put(PicketBoxSecurityVault.KEYSTORE_PASSWORD, getMaskedPassword(password, salz, iter)); options.put(PicketBoxSecurityVault.KEYSTORE_ALIAS, alias); options.put(PicketBoxSecurityVault.SALT, salz); options.put(PicketBoxSecurityVault.ITERATION_COUNT, String.valueOf(iter)); options.put(PicketBoxSecurityVault.ENC_FILE_DIR, encDataDir); if (createKeystore != null) { options.put(PicketBoxSecurityVault.CREATE_KEYSTORE, createKeystore.toString()); } return options; } private Map<String, Object> getVaultOptionsMap(String keystore, String encDataDir, String alias, String salz, int iter, String password) throws Exception { return getVaultOptionsMap(keystore, encDataDir, alias, salz, iter, password, null); } public static void setInitialVaulConditions(String originalKeyStoreFile, String targetKeyStoreFile, String originalVaultContentDir, String targetVaultContentDir) throws Exception { File tKS = new File(targetKeyStoreFile); File parent = tKS.getParentFile(); if (!parent.exists()) { parent.mkdirs(); } SecurityVaultUnitTestCase.copyFile(new File(originalKeyStoreFile), tKS); File targetVaultContent = new File(targetVaultContentDir); cleanDirectory(targetVaultContent); File originVault = new File(originalVaultContentDir); for (File f : originVault.listFiles()) { if (f.isFile()) // some version control systems add a hidden directory, we must make sure we won't copy those. SecurityVaultUnitTestCase.copyFile(f, new File(targetVaultContent.getAbsolutePath() + File.separator + f.getName())); } } /** * Make clean new directory. * * @param directory */ public static void cleanDirectory(File directory) { if (directory.exists()) { for (File f: directory.listFiles()) { f.delete(); } directory.delete(); } directory.mkdirs(); } /** * Copy file method. * * @param sourceFile * @param destFile * @throws IOException */ public static void copyFile(File sourceFile, File destFile) throws IOException { if (!destFile.exists()) { destFile.createNewFile(); } FileInputStream fIn = null; FileOutputStream fOut = null; FileChannel source = null; FileChannel destination = null; try { fIn = new FileInputStream(sourceFile); source = fIn.getChannel(); fOut = new FileOutputStream(destFile); destination = fOut.getChannel(); long transfered = 0; long bytes = source.size(); while (transfered < bytes) { transfered += destination.transferFrom(source, 0, source.size()); destination.position(transfered); } } finally { if (source != null) { source.close(); } else if (fIn != null) { fIn.close(); } if (destination != null) { destination.close(); } else if (fOut != null) { fOut.close(); } } } static Class<?> loadClass(final Class<?> clazz, final String fqn) { return AccessController.doPrivileged(new PrivilegedAction<Class<?>>() { public Class<?> run() { ClassLoader cl = clazz.getClassLoader(); Class<?> loadedClass = null; try { loadedClass = cl.loadClass(fqn); } catch (ClassNotFoundException e) { } return loadedClass; } }); } private void assertSecretValue(SecurityVault vault, String vaultBlock, String attributeName, String expectedSecuredAttributeValue) throws SecurityVaultException { assertEquals("Expected value has to match the one in vault. " + vaultBlock + ":" + attributeName + "=" + expectedSecuredAttributeValue, new String(expectedSecuredAttributeValue), new String(vault.retrieve(vaultBlock, attributeName, null))); } /** * get new instance of vault to simulate restart of application server * @return * @throws Exception */ private SecurityVault getNewSecurityVaultInstance() throws Exception { Class<?> vaultClass = loadClass(SecurityVaultFactory.class, "org.picketbox.plugins.vault.PicketBoxSecurityVault"); return (SecurityVault)vaultClass.newInstance(); } }