/*
*
* JBoss, Home of Professional Open Source.
* Copyright 2013, Red Hat, Inc., 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.as.security;
import static java.util.Collections.addAll;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.PrintStream;
import java.net.URLDecoder;
import java.security.KeyStore;
import java.security.Permission;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.jboss.as.security.vault.MockRuntimeVaultReader;
import org.jboss.as.security.vault.VaultTool;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
public class VaultToolTestCase {
private static final String KEYSTORE_PASSWORD = "mypassword";
private static final String BLOCK_NAME = "myblock";
private static final String ATTRIBUTE_NAME = "the_attribute_I_want_to_store";
private static final String VALUE_TO_STORE = "the_value";
private static final String KEYSTORE_ALIAS_VALUE = "vault";
private static final String ITERATION_COUNT_VALUE = "12";
private static final String CODE_LOCATION = VaultToolTestCase.class.getProtectionDomain().getCodeSource().getLocation().getFile();
private static final String KEYSTORE_URL_VALUE = getKeystorePath();
private static final String ENC_FILE_DIR_VALUE = CODE_LOCATION + "test_vault_dir";
private static final String MASKED_MYPASSWORD_VALUE = "MASK-0UWB5tlhOmKYzJVl9KZaPN";
private static final String MASKED_MYPASSWORD_VALUE_INCORRECT = "MASK-UWB5tlhOmKYzJVl9KZaPN";
private static final String SALT_VALUE = "bdfbdf12";
private static final ByteArrayOutputStream SYSTEM_OUT = new ByteArrayOutputStream();
private static String getKeystorePath() {
try {
return new String(URLDecoder.decode(CODE_LOCATION, "UTF-8" ) + "org/jboss/as/security/vault.keystore");
} catch (Exception e) {
throw new Error("Unable to decode url", e);
}
}
private static void createAndFillKeystore(String fileName) throws Exception {
KeyStore ks = KeyStore.getInstance("JCEKS");
ks.load(null, KEYSTORE_PASSWORD.toCharArray());
KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
keyGenerator.init(128);
SecretKey secretKey = keyGenerator.generateKey();
KeyStore.SecretKeyEntry skEntry = new KeyStore.SecretKeyEntry(secretKey);
KeyStore.PasswordProtection p = new KeyStore.PasswordProtection(KEYSTORE_PASSWORD.toCharArray());
ks.setEntry(KEYSTORE_ALIAS_VALUE, skEntry, p);
ks.store(new FileOutputStream(fileName), KEYSTORE_PASSWORD.toCharArray());
}
@Test
public void testVaultTool() throws Exception {
doTestVaultTool(true, false);
}
@Test
@Ignore
public void testVaultFallback() throws Exception {
doTestVaultTool(true, true);
}
@Test
public void testNoKeyStoreFile() throws Exception {
doTestVaultTool(false, false);
}
private void doTestVaultTool(boolean prepareKeystore, boolean testFallbackFromIncorrectPasswordValue) throws Exception {
if (prepareKeystore) {
createAndFillKeystore(KEYSTORE_URL_VALUE);
}
// Replaces standard output with a ByteArrayOutputStream to parse the output later.
System.setOut(new PrintStream(SYSTEM_OUT));
try {
String[] args = generateArgs(); // Generate the required arguments
VaultTool.main(args);
} catch (ExitException e) {
Assert.assertEquals("Exit status is equal to 0", 0, e.status);
}
SYSTEM_OUT.flush();
String ouput = new String(SYSTEM_OUT.toByteArray());
String[] outputLines = ouput.split("\n");
String vaultSharedKey = getStoredAttributeSharedKey(outputLines);
Assert.assertNotNull("VaultTool did not return a line starting with VAULT::", vaultSharedKey);
MockRuntimeVaultReader rvr = new MockRuntimeVaultReader();
Map<String, Object> options = generateVaultOptionsMap(testFallbackFromIncorrectPasswordValue);
rvr.createVault("", options);
String retrievedValueFromVault = rvr.retrieveFromVault(vaultSharedKey);
Assert.assertEquals("The value retrieved from vault is not the same as the one initially stored", VALUE_TO_STORE,
retrievedValueFromVault);
}
private Map<String, Object> generateVaultOptionsMap(boolean testFallbackFromIncorrectPasswordValue) {
Map<String, Object> options = new HashMap<String, Object>();
options.put("KEYSTORE_URL", KEYSTORE_URL_VALUE);
if (testFallbackFromIncorrectPasswordValue) {
options.put("KEYSTORE_PASSWORD", MASKED_MYPASSWORD_VALUE_INCORRECT);
} else {
options.put("KEYSTORE_PASSWORD", MASKED_MYPASSWORD_VALUE);
}
options.put("SALT", SALT_VALUE);
options.put("ITERATION_COUNT", ITERATION_COUNT_VALUE);
options.put("KEYSTORE_ALIAS", KEYSTORE_ALIAS_VALUE);
options.put("ENC_FILE_DIR", ENC_FILE_DIR_VALUE);
return options;
}
private String getStoredAttributeSharedKey(String[] linesOfOutput) {
String vaultSharedKey = null;
for (String line : linesOfOutput) {
if (line.startsWith("VAULT::")) {
vaultSharedKey = line;
break;
}
}
return vaultSharedKey;
}
/**
* Generates arguments to create security vault and store a password inside.
* @return
*/
private String[] generateArgs() {
List<String> args = new ArrayList<String>();
addAll(args, "-k", KEYSTORE_URL_VALUE);
addAll(args, "-p", KEYSTORE_PASSWORD);
addAll(args, "-e", ENC_FILE_DIR_VALUE);
addAll(args, "-s", SALT_VALUE);
addAll(args, "-i", ITERATION_COUNT_VALUE);
addAll(args, "-b", BLOCK_NAME);
addAll(args, "-a", ATTRIBUTE_NAME);
addAll(args, "-x", VALUE_TO_STORE);
addAll(args, "-t");
return args.toArray(new String[0]);
}
protected static class ExitException extends SecurityException {
public final int status;
public ExitException(int status) {
super("There is no escape!");
this.status = status;
}
}
private static class NoExitSecurityManager extends SecurityManager {
@Override
public void checkPermission(Permission perm) {
// allow anything.
}
@Override
public void checkPermission(Permission perm, Object context) {
// allow anything.
}
@Override
public void checkExit(int status) {
super.checkExit(status);
throw new ExitException(status);
}
}
@Before
public void setUp() throws Exception {
cleanDirectory(ENC_FILE_DIR_VALUE);
System.setSecurityManager(new NoExitSecurityManager());
}
@After
public void tearDown() throws Exception {
System.setSecurityManager(null); // or save and restore original
cleanDirectory(ENC_FILE_DIR_VALUE);
File keyStoreFile = new File(KEYSTORE_URL_VALUE);
if (keyStoreFile.exists()) {
keyStoreFile.delete();
}
}
/**
* Clean given directory.
*
* @param directory
*/
private static void cleanDirectory(String dir) {
File directory = new File(dir);
if (directory.exists()) {
for (File f : directory.listFiles()) {
f.delete();
}
}
}
}