/*
* 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.wildfly.test.security;
import java.io.File;
import java.io.FileOutputStream;
import java.security.KeyStore;
import java.util.Random;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import org.jboss.logging.Logger;
import org.picketbox.plugins.vault.PicketBoxSecurityVault;
import org.picketbox.util.KeyStoreUtil;
/**
* VaultHandler is a handler for PicketBox Security Vault associated files. It can be used one-to-one with vault. It can create
* required keystore and after action delete all vault data files. It should be used for testing purpose only.
*
* @author Peter Skopek (pskopek at redhat dot com)
*
*/
public class VaultHandler {
private static Logger LOGGER = Logger.getLogger(VaultHandler.class);
public static final String ENC_DAT_FILE = "ENC.dat";
public static final String SHARED_DAT_FILE = "Shared.dat";
public static final String VAULT_DAT_FILE = "VAULT.dat";
public static final String DEFAULT_KEYSTORE_FILE = "vault.keystore";
private String encodedVaultFileDirectory;
private String keyStoreType;
private String keyStore;
private String keyStorePassword;
private int keySize = 128;
private String alias = "defaultalias";
private String salt;
private int iterationCount;
private VaultSession vaultSession;
private static String FILE_SEPARATOR = System.getProperty("file.separator");
private static String TMP_DIR = System.getProperty("java.io.tmpdir");
private static String DEFAULT_PASSWORD = "super_secret";
/**
* Create vault with all required files. It is the most complete constructor.
* If keyStore doesn't exist it will be created with specified keyStoreType and
* encryption directory will be created if not existent.
*
* @param keyStore
* @param keyStorePassword
* @param keyStoreType - JCEKS, JKS or null
* @param encodedVaultFileDirectory
* @param keySize
* @param alias
* @param salt
* @param iterationCount
*/
public VaultHandler(String keyStore, String keyStorePassword, String keyStoreType, String encodedVaultFileDirectory,
int keySize, String alias, String salt, int iterationCount) {
if (alias != null) {
this.alias = alias;
}
if (keySize != 0) {
this.keySize = keySize;
}
if (keyStoreType == null) {
this.keyStoreType = "JCEKS";
} else {
if (!keyStoreType.equals("JCEKS") && !keyStoreType.equals("JKS")) {
throw new IllegalArgumentException("Wrong keyStoreType. Supported are only (JCEKS or JKS). Preferred is JCEKS.");
}
this.keyStoreType = keyStoreType;
}
if (keyStorePassword == null) {
this.keyStorePassword = DEFAULT_PASSWORD;
} else if (keyStorePassword.startsWith(PicketBoxSecurityVault.PASS_MASK_PREFIX)) {
throw new IllegalArgumentException("keyStorePassword cannot be a masked password, use plain text password, please");
} else {
this.keyStorePassword = keyStorePassword;
}
try {
File keyStoreFile = new File(keyStore);
if (!keyStoreFile.exists()) {
if (!this.keyStoreType.equals("JCEKS")) {
throw new RuntimeException("keyStoreType has to be JCEKS when creating new key store");
}
File keyStoreParent = keyStoreFile.getAbsoluteFile().getParentFile();
if (keyStoreParent != null) {
if (!keyStoreParent.exists()) {
assert keyStoreParent.mkdirs();
} else {
assert keyStoreParent.isDirectory();
}
}
KeyStore ks = KeyStoreUtil.createKeyStore(this.keyStoreType, this.keyStorePassword.toCharArray());
KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
keyGenerator.init(this.keySize);
SecretKey secretKey = keyGenerator.generateKey();
KeyStore.SecretKeyEntry skEntry = new KeyStore.SecretKeyEntry(secretKey);
KeyStore.PasswordProtection p = new KeyStore.PasswordProtection(this.keyStorePassword.toCharArray());
ks.setEntry(this.alias, skEntry, p);
ks.store(new FileOutputStream(keyStoreFile), this.keyStorePassword.toCharArray());
}
this.keyStore = keyStoreFile.getAbsolutePath();
} catch (Exception e) {
throw new RuntimeException("Problem creating keyStore: ", e);
}
File vaultDirectory = new File(encodedVaultFileDirectory).getAbsoluteFile();
if (!vaultDirectory.exists()) {
assert vaultDirectory.mkdirs();
} else if (!vaultDirectory.isDirectory()) {
throw new RuntimeException("Vault encryption directory has to be directory, but "
+ vaultDirectory.getAbsolutePath() + " is not.");
}
this.encodedVaultFileDirectory = vaultDirectory.getAbsolutePath();
if (salt == null) {
String tmp = Long.toHexString(System.currentTimeMillis())
+ Long.toHexString(System.currentTimeMillis())
+ Long.toHexString(System.currentTimeMillis())
+ Long.toHexString(System.currentTimeMillis());
this.salt = tmp.substring(0, 8);
} else {
this.salt = salt;
}
if (iterationCount <= 0) {
this.iterationCount = new Random().nextInt(90) + 1;
} else {
this.iterationCount = iterationCount;
}
if (LOGGER.isDebugEnabled()) {
logCreatedVault();
}
try {
this.vaultSession = new VaultSession(this.keyStore, this.keyStorePassword, this.encodedVaultFileDirectory,
this.salt, this.iterationCount);
this.vaultSession.startVaultSession(this.alias);
} catch (Exception e) {
throw new RuntimeException("Problem creating VaultSession: ", e);
}
LOGGER.debug("VaultSession started");
}
/**
* Constructor with all default values, but keyStore and encodedVaultFileDirectory.
*
* @param keyStore
* @param encodedVaultFileDirectory
*/
public VaultHandler(String keyStore, String encodedVaultFileDirectory) {
this(keyStore, null, null, encodedVaultFileDirectory, 0, null, null, 0);
}
/**
* Constructor with all default values, but encodedVaultFileDirectory.
* @param encodedVaultFileDirectory
*/
public VaultHandler(String encodedVaultFileDirectory) {
this(getDefaultKeystoreFile(encodedVaultFileDirectory), encodedVaultFileDirectory);
}
/**
* Constructor with all default values.
*/
public VaultHandler() {
this(TMP_DIR);
}
public String getMaskedKeyStorePassword() {
if (vaultSession != null) {
return vaultSession.getKeystoreMaskedPassword();
} else {
throw new RuntimeException("getMaskedKeyStorePassword: Vault inside this handler is not initialized or created");
}
}
public String addSecuredAttribute(String vaultBlock, String attributeName, char[] attributeValue) {
if (vaultSession != null) {
try {
return vaultSession.addSecuredAttribute(vaultBlock, attributeName, attributeValue);
} catch (Exception e) {
throw new RuntimeException(e);
}
} else {
throw new RuntimeException("addSecuredAttribute: Vault inside this handler is not initialized or created");
}
}
public boolean exists(String vaultBlock, String attributeName) {
if (vaultSession != null) {
try {
return vaultSession.checkSecuredAttribute(vaultBlock, attributeName);
} catch (Exception e) {
throw new RuntimeException(e);
}
} else {
throw new RuntimeException("exists: Vault inside this handler is not initialized or created");
}
}
/**
* Return VaultSession for further vault manipulation when needed.
* @return
*/
public VaultSession getVaultSession() {
return vaultSession;
}
/**
* Delete all associated vault files and keystore. After this action VaultHandler is not usable anymore.
*/
public void cleanUp() {
cleanFilesystem(encodedVaultFileDirectory, false, keyStore);
vaultSession = null;
}
public String getEncodedVaultFileDirectory() {
return encodedVaultFileDirectory;
}
public String getKeyStoreType() {
return keyStoreType;
}
public String getKeyStore() {
return keyStore;
}
public int getKeySize() {
return keySize;
}
public String getAlias() {
return alias;
}
public String getSalt() {
return salt;
}
public String getKeyStorePassword() {
return keyStorePassword;
}
public int getIterationCount() {
return iterationCount;
}
public String getIterationCountAsString() {
return Integer.toString(iterationCount);
}
private void logCreatedVault() {
LOGGER.debug("keystoreURL="+keyStore);
LOGGER.debug("KEYSTORE_PASSWORD="+keyStorePassword);
LOGGER.debug("ENC_FILE_DIR="+encodedVaultFileDirectory);
LOGGER.debug("KEYSTORE_ALIAS="+alias);
LOGGER.debug("SALT="+salt);
LOGGER.debug("ITERATION_COUNT="+iterationCount);
}
private static String getDefaultKeystoreFile(String encodedVaultFileDirectory) {
return encodedVaultFileDirectory + FILE_SEPARATOR + DEFAULT_KEYSTORE_FILE;
}
/**
* Removes vault files from the given directory, with the assumption that the key store is located inside
* the directory in a file named {@link #DEFAULT_KEYSTORE_FILE}.
*
* @param encodedVaultFileDirectory the path of the directory
* @param removeEncodedVaultFileDirectory {@code true} if the directory itself should be removed
*/
public static void cleanFilesystem(String encodedVaultFileDirectory, boolean removeEncodedVaultFileDirectory) {
cleanFilesystem(encodedVaultFileDirectory, removeEncodedVaultFileDirectory, getDefaultKeystoreFile(encodedVaultFileDirectory));
}
/**
* Removes vault files from the given directory, and also removes the given key store file.
*
* @param encodedVaultFileDirectory the path of the directory
* @param removeEncodedVaultFileDirectory {@code true} if the directory itself should be removed
* @param keyStore the path of the key store
*/
public static void cleanFilesystem(String encodedVaultFileDirectory, boolean removeEncodedVaultFileDirectory, String keyStore) {
cleanFilesystem(new File(encodedVaultFileDirectory), removeEncodedVaultFileDirectory, new File(keyStore));
}
private static void cleanFilesystem(File encodedVaultFileDirectory, boolean removeEncodedVaultFileDirectory,
File keyStore) {
deleteIfExists(keyStore);
File f = new File(keyStore.getParent(), keyStore.getName() + ".original");
deleteIfExists(f);
if (removeEncodedVaultFileDirectory) {
deleteDirectory(encodedVaultFileDirectory);
} else {
cleanEncodedVaultFileDirectory(encodedVaultFileDirectory);
}
}
private static void deleteDirectory(File f) {
File[] children = f.isDirectory() ? f.listFiles() : null;
if (children != null) {
for (File child : children) {
deleteDirectory(child);
}
}
deleteIfExists(f);
}
private static void cleanEncodedVaultFileDirectory(File encodedVaultFileDirectory) {
File f = new File(encodedVaultFileDirectory, VAULT_DAT_FILE);
deleteIfExists(f);
f = new File(encodedVaultFileDirectory, ENC_DAT_FILE);
deleteIfExists(f);
f = new File(encodedVaultFileDirectory, ENC_DAT_FILE + ".original");
deleteIfExists(f);
f = new File(encodedVaultFileDirectory, SHARED_DAT_FILE);
deleteIfExists(f);
// there might be a KEYSTORE_README file in the directory as a placeholder
f = new File(encodedVaultFileDirectory, "KEYSTORE_README");
deleteIfExists(f);
}
private static void deleteIfExists(File f) {
assert !f.exists() || f.delete();
}
}