/*
* JBoss, Home of Professional Open Source
* Copyright 2014, JBoss Inc., and individual contributors as indicated
* by the @authors tag.
*
* Licensed under the Apache License, Version 2.0 (the "License")
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jboss.as.test.manualmode.vault;
import java.io.File;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.PBEParameterSpec;
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.picketbox.plugins.vault.PicketBoxSecurityVault;
final class TestVaultSession {
static final String VAULT_ENC_ALGORITHM = "PBEwithMD5andDES";
private String keystoreURL;
private String keystorePassword;
private String keystoreMaskedPassword;
private String encryptionDirectory;
private String salt;
private int iterationCount;
private SecurityVault vault;
private String vaultAlias;
/**
* Constructor to create VaultSession.
*
* @param keystoreURL
* @param keystorePassword
* @param encryptionDirectory
* @param salt
* @param iterationCount
* @throws Exception
*/
TestVaultSession(String keystoreURL, String keystorePassword, String encryptionDirectory, String salt, int iterationCount)
throws Exception {
this.keystoreURL = keystoreURL;
this.keystorePassword = keystorePassword;
this.encryptionDirectory = encryptionDirectory;
this.salt = salt;
this.iterationCount = iterationCount;
validate();
}
/**
* Validate fields sent to this class's constructor.
*/
private void validate() throws Exception {
validateKeystoreURL();
validateEncryptionDirectory();
validateSalt();
validateIterationCount();
validateKeystorePassword();
}
protected void validateKeystoreURL() throws Exception {
File f = new File(keystoreURL);
if (!f.exists()) {
throw new Exception(String.format("Keystore '%s' doesn't exist.", keystoreURL));
} else if (!f.canWrite() || !f.isFile()) {
throw new Exception(String.format("Keystore [%s] is not writable or not a file.", keystoreURL));
}
}
protected void validateKeystorePassword() throws Exception {
if (keystorePassword == null) {
throw new Exception("Keystore password has to be specified");
}
}
protected void validateEncryptionDirectory() throws Exception {
if (encryptionDirectory == null) {
throw new Exception("Encryption directory has to be specified.");
}
if (!encryptionDirectory.endsWith("/") || encryptionDirectory.endsWith("\\")) {
encryptionDirectory = encryptionDirectory + (System.getProperty("file.separator", "/"));
}
File d = new File(encryptionDirectory);
if (!d.exists()) {
if (!d.mkdirs()) {
throw new Exception(String.format("Cannot create encryption directory %s", d.getAbsolutePath()));
}
}
if (!d.isDirectory()) {
throw new Exception(String.format("Encryption directory is not a directory or doesn't exist. (%s)", encryptionDirectory));
}
}
protected void validateIterationCount() throws Exception {
if (iterationCount < 1 && iterationCount > Integer.MAX_VALUE) {
throw new Exception("Iteration count has to be within 1 - \" + Integer.MAX_VALUE + \", but it is %s.");
}
}
protected void validateSalt() throws Exception {
if (salt == null || salt.length() != 8) {
throw new Exception("Salt has to be exactly 8 characters long.");
}
}
/**
* Method to compute masked password based on class attributes.
*
* @return masked password prefixed with {link @PicketBoxSecurityVault.PASS_MASK_PREFIX}.
* @throws Exception
*/
private String computeMaskedPassword() throws Exception {
// Create the PBE secret key
SecretKeyFactory factory = SecretKeyFactory.getInstance(VAULT_ENC_ALGORITHM);
char[] password = "somearbitrarycrazystringthatdoesnotmatter".toCharArray();
PBEParameterSpec cipherSpec = new PBEParameterSpec(salt.getBytes(StandardCharsets.UTF_8), iterationCount);
PBEKeySpec keySpec = new PBEKeySpec(password);
SecretKey cipherKey = factory.generateSecret(keySpec);
String maskedPass = PBEUtils.encode64(keystorePassword.getBytes(StandardCharsets.UTF_8), VAULT_ENC_ALGORITHM, cipherKey, cipherSpec);
return PicketBoxSecurityVault.PASS_MASK_PREFIX + maskedPass;
}
/**
* Initialize the underlying vault.
*
* @throws Exception
*/
private void initSecurityVault() throws Exception {
try {
this.vault = SecurityVaultFactory.get();
this.vault.init(getVaultOptionsMap());
handshake();
} catch (SecurityVaultException e) {
throw new Exception(e);
}
}
/**
* Start the vault with given alias.
*
* @param vaultAlias
* @throws Exception
*/
void startVaultSession(String vaultAlias) throws Exception {
if (vaultAlias == null) {
throw new Exception("Vault alias has to be specified.");
}
this.keystoreMaskedPassword = (org.jboss.security.Util.isPasswordCommand(keystorePassword))
? keystorePassword
: computeMaskedPassword();
this.vaultAlias = vaultAlias;
initSecurityVault();
}
private Map<String, Object> getVaultOptionsMap() {
Map<String, Object> options = new HashMap<String, Object>();
options.put(PicketBoxSecurityVault.KEYSTORE_URL, keystoreURL);
options.put(PicketBoxSecurityVault.KEYSTORE_PASSWORD, keystoreMaskedPassword);
options.put(PicketBoxSecurityVault.KEYSTORE_ALIAS, vaultAlias);
options.put(PicketBoxSecurityVault.SALT, salt);
options.put(PicketBoxSecurityVault.ITERATION_COUNT, Integer.toString(iterationCount));
options.put(PicketBoxSecurityVault.ENC_FILE_DIR, encryptionDirectory);
options.put(PicketBoxSecurityVault.CREATE_KEYSTORE, Boolean.TRUE.toString());
return options;
}
private void handshake() throws SecurityVaultException {
Map<String, Object> handshakeOptions = new HashMap<String, Object>();
handshakeOptions.put(PicketBoxSecurityVault.PUBLIC_CERT, vaultAlias);
vault.handshake(handshakeOptions);
}
/**
* Add secured attribute to specified vault block. This method can be called only after successful
* startVaultSession() call.
*
* @param vaultBlock
* @param attributeName
* @param attributeValue
* @return secured attribute configuration
*/
String addSecuredAttribute(String vaultBlock, String attributeName, char[] attributeValue) throws Exception {
vault.store(vaultBlock, attributeName, attributeValue, null);
return securedAttributeConfigurationString(vaultBlock, attributeName);
}
/**
* Returns configuration string for secured attribute.
*
* @param vaultBlock
* @param attributeName
* @return
*/
private String securedAttributeConfigurationString(String vaultBlock, String attributeName) {
return "VAULT::" + vaultBlock + "::" + attributeName + "::1";
}
/**
* Returns vault configuration string in user readable form.
*
* @return
*/
String vaultConfiguration() {
StringBuilder sb = new StringBuilder();
sb.append("<vault>").append("\n")
.append(" <vault-option name=\"KEYSTORE_URL\" value=\"" + keystoreURL + "\"/>").append("\n")
.append(" <vault-option name=\"KEYSTORE_PASSWORD\" value=\"" + keystoreMaskedPassword + "\"/>").append("\n")
.append(" <vault-option name=\"KEYSTORE_ALIAS\" value=\"" + vaultAlias + "\"/>").append("\n")
.append(" <vault-option name=\"SALT\" value=\"" + salt + "\"/>").append("\n")
.append(" <vault-option name=\"ITERATION_COUNT\" value=\"" + iterationCount + "\"/>").append("\n")
.append(" <vault-option name=\"ENC_FILE_DIR\" value=\"" + encryptionDirectory + "\"/>").append("\n")
.append("</vault>");
return sb.toString();
}
}