/*
 * 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.apache.tomcat.vault.security.vault;

import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
import org.apache.tomcat.util.res.StringManager;
import org.apache.tomcat.vault.security.Util;
import org.apache.tomcat.vault.security.plugins.PBEUtils;
import org.apache.tomcat.vault.util.EncryptionUtil;
import org.apache.tomcat.vault.util.KeyStoreUtil;
import org.apache.tomcat.vault.util.StringUtil;

import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.PBEParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.*;
import java.nio.channels.FileChannel;
import java.security.GeneralSecurityException;
import java.security.KeyStore;
import java.security.KeyStore.Entry;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.util.Enumeration;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;

/**
 * An instance of {@link SecurityVault} that uses
 * a {@link KeyStore}
 * The shared key just uses a concatenation of a {@link java.util.UUID}
 * and a keystore alias.
 * <p>
 * The following options are expected in the {@link SecurityVault#init(Map)} call:
 * ENC_FILE_DIR: the location where the encoded files will be kept. End with "/" or "\" based on your platform
 * KEYSTORE_URL: location where your keystore is located
 * KEYSTORE_PASSWORD: keystore password.
 * 'plain text' masked password (has to be prepended with MASK-)
 * '{EXT}...' where the '...' is the exact command
 * '{EXTC[:expiration_in_millis]}...' where the '...' is the exact command
 * line that will be passed to the Runtime.exec(String) method to execute a
 * platform command. The first line of the command output is used as the
 * password.
 * EXTC variant will cache the passwords for expiration_in_millis milliseconds.
 * Default cache expiration is 0 = infinity.
 * '{CMD}...' or '{CMDC}...' for a general command to execute. The general
 * command is a string delimited by ',' where the first part is the actual
 * command and further parts represents its parameters. The comma can be
 * backslashed in order to keep it as the part of a parameter.
 * KEYSTORE_ALIAS: Alias where the keypair is located
 * SALT: salt of the masked password. Ensured it is 8 characters in length
 * ITERATION_COUNT: Iteration Count of the masked password.
 * KEY_SIZE: Key size of encryption. Default is 128 bytes.
 * CREATE_KEYSTORE: Whether PicketBox Security Vault has to create missing key store in time of initialization. Default is "FALSE". Implies KEYSTORE_TYPE "JCEKS".
 * KEYSTORE_TYPE: Key store type. Default is JCEKS.
 *
 * @author Anil.Saldhana@redhat.com
 * @author Peter Skopek (pskopek_at_redhat_dot_com)
 * @since Aug 12, 2011
 */
public class PicketBoxSecurityVault implements SecurityVault {
    private static final StringManager sm = StringManager.getManager(PicketBoxSecurityVault.class.getPackage().getName());
    private static final Log log = LogFactory.getLog(PicketBoxSecurityVault.class);
    private static final StringManager msm = StringManager.getManager("org.apache.tomcat.vault.security.resources");

    protected boolean finishedInit = false;

    protected KeyStore keystore = null;

    protected String encryptionAlgorithm = "AES";

    protected int keySize = 128;

    private char[] keyStorePWD = null;

    private String alias = null;

    private SecurityVaultData vaultContent = null;

    private SecretKey adminKey;

    private String decodedEncFileDir;

    private boolean createKeyStore = false;

    private String keyStoreType = defaultKeyStoreType;

    // options
    public static final String ENC_FILE_DIR = "ENC_FILE_DIR";

    public static final String KEYSTORE_URL = "KEYSTORE_URL";

    public static final String KEYSTORE_PASSWORD = "KEYSTORE_PASSWORD";

    public static final String KEYSTORE_ALIAS = "KEYSTORE_ALIAS";

    public static final String SALT = "SALT";

    public static final String ITERATION_COUNT = "ITERATION_COUNT";

    public static final String PASS_MASK_PREFIX = "MASK-";

    public static final String PUBLIC_CERT = "PUBLIC_CERT";

    public static final String KEY_SIZE = "KEY_SIZE";

    public static final String CREATE_KEYSTORE = "CREATE_KEYSTORE";

    public static final String KEYSTORE_TYPE = "KEYSTORE_TYPE";

    // backward compatibility constants
    private static final String ENCODED_FILE = "ENC.dat";
    private static final String SHARED_KEY_FILE = "Shared.dat";
    private static final String ADMIN_KEY = "ADMIN_KEY";

    protected static final String VAULT_CONTENT_FILE = "VAULT.dat"; // versioned vault data file
    protected static final String defaultKeyStoreType = "JCEKS";


    /* (non-Javadoc)
     * @see org.jboss.security.vault.SecurityVault#init(java.util.Map)
     */
    public void init(Map<String, Object> options) throws SecurityVaultException {

        if (Util.isFIPS()) {
            throw new SecurityVaultException(msm.getString("cantdoFIPS"));
        }
        if (options == null || options.isEmpty())
            throw new IllegalArgumentException(msm.getString("invalidNullOrEmptyOptionMap", "options"));

        String keystoreURL = (String) options.get(KEYSTORE_URL);
        if (keystoreURL == null)
            throw new SecurityVaultException(msm.getString("invalidNullOrEmptyOptionMessage", KEYSTORE_URL));

        if (keystoreURL.contains("${")) {
            keystoreURL = keystoreURL.replaceAll(":", StringUtil.PROPERTY_DEFAULT_SEPARATOR);  // replace single ":" with PL default
        }
        keystoreURL = StringUtil.getSystemPropertyAsString(keystoreURL);

        String password = (String) options.get(KEYSTORE_PASSWORD);
        if (password == null)
            throw new SecurityVaultException(msm.getString("invalidNullOrEmptyOptionMessage", KEYSTORE_PASSWORD));
        if (password.startsWith(PASS_MASK_PREFIX) == false
                && Util.isPasswordCommand(password) == false)
            throw new SecurityVaultException(msm.getString("invalidKeystorePasswordFormatMessage"));

        String salt = (String) options.get(SALT);
        if (salt == null)
            throw new SecurityVaultException(msm.getString("invalidNullOrEmptyOptionMessage", SALT));

        String iterationCountStr = (String) options.get(ITERATION_COUNT);
        if (iterationCountStr == null)
            throw new SecurityVaultException(msm.getString("invalidNullOrEmptyOptionMessage", ITERATION_COUNT));
        int iterationCount = Integer.parseInt(iterationCountStr);

        this.alias = (String) options.get(KEYSTORE_ALIAS);
        if (alias == null)
            throw new SecurityVaultException(msm.getString("invalidNullOrEmptyOptionMessage", KEYSTORE_ALIAS));

        String keySizeStr = (String) options.get(KEY_SIZE);
        if (keySizeStr != null) {
            keySize = Integer.parseInt(keySizeStr);
        }

        String encFileDir = (String) options.get(ENC_FILE_DIR);
        if (encFileDir == null)
            throw new SecurityVaultException(msm.getString("invalidNullOrEmptyOptionMessage", ENC_FILE_DIR));

        createKeyStore = (options.get(CREATE_KEYSTORE) != null ? Boolean.parseBoolean((String) options.get(CREATE_KEYSTORE))
                : false);
        keyStoreType = (options.get(KEYSTORE_TYPE) != null ? (String) options.get(KEYSTORE_TYPE) : defaultKeyStoreType);

        try {
            keyStorePWD = loadKeystorePassword(password, salt, iterationCount);
            keystore = getKeyStore(keystoreURL);

            checkAndConvertKeyStoreToJCEKS(keystoreURL);

        } catch (Exception e) {
            throw new SecurityVaultException(e);
        }

        // read and possibly convert vault content
        readVaultContent(keystoreURL, encFileDir);

        log.info(sm.getString("picketBoxSecurityVault.vaultInitialized"));
        finishedInit = true;


    }

    /* (non-Javadoc)
     * @see org.jboss.security.vault.SecurityVault#isInitialized()
     */
    public boolean isInitialized() {
        return finishedInit;
    }

    /* (non-Javadoc)
     * @see org.jboss.security.vault.SecurityVault#handshake(java.util.Map)
     */
    public byte[] handshake(Map<String, Object> handshakeOptions) throws SecurityVaultException {
        return new byte[keySize];
    }

    /* (non-Javadoc)
     * @see org.jboss.security.vault.SecurityVault#keyList()
     */
    public Set<String> keyList() throws SecurityVaultException {
        return vaultContent.getVaultDataKeys();
    }

    /* (non-Javadoc)
     * @see org.jboss.security.vault.SecurityVault#store(java.lang.String, java.lang.String, char[], byte[])
     */
    public void store(String vaultBlock, String attributeName, char[] attributeValue, byte[] sharedKey)
            throws SecurityVaultException {
        if (StringUtil.isNullOrEmpty(vaultBlock))
            throw new IllegalArgumentException(msm.getString("invalidNullArgument", "vaultBlock"));
        if (StringUtil.isNullOrEmpty(attributeName))
            throw new IllegalArgumentException(msm.getString("invalidNullArgument", "attributeName"));

        String av = new String(attributeValue);

        EncryptionUtil util = new EncryptionUtil(encryptionAlgorithm, keySize);
        try {
            SecretKeySpec sKeySpec = new SecretKeySpec(adminKey.getEncoded(), encryptionAlgorithm);
            byte[] encryptedData = util.encrypt(av.getBytes(), sKeySpec);
            vaultContent.addVaultData(alias, vaultBlock, attributeName, encryptedData);
        } catch (Exception e1) {
            throw new SecurityVaultException(msm.getString("unableToEncryptDataMessage"), e1);
        }

        try {
            writeVaultData();
        } catch (IOException e) {
            throw new SecurityVaultException(msm.getString("unableToWriteVaultDataFileMessage", VAULT_CONTENT_FILE), e);
        }
    }

    /* (non-Javadoc)
     * @see org.jboss.security.vault.SecurityVault#retrieve(java.lang.String, java.lang.String, byte[])
     */
    public char[] retrieve(String vaultBlock, String attributeName, byte[] sharedKey) throws SecurityVaultException {
        if (StringUtil.isNullOrEmpty(vaultBlock))
            throw new IllegalArgumentException(msm.getString("invalidNullArgument", "vaultBlock"));
        if (StringUtil.isNullOrEmpty(attributeName))
            throw new IllegalArgumentException(msm.getString("invalidNullArgument", "attributeName"));

        byte[] encryptedValue = vaultContent.getVaultData(alias, vaultBlock, attributeName);

        if (encryptedValue == null) {
            throw new SecurityVaultException(sm.getString("picketBoxSecurityVault.attributeNotInVault", alias,
                vaultBlock, attributeName));
        }

        SecretKeySpec secretKeySpec = new SecretKeySpec(adminKey.getEncoded(), encryptionAlgorithm);
        EncryptionUtil encUtil = new EncryptionUtil(encryptionAlgorithm, keySize);
        try {
            return (new String(encUtil.decrypt(encryptedValue, secretKeySpec))).toCharArray();
        } catch (Exception e) {
            throw new SecurityVaultException(e);
        }
    }

    /* (non-Javadoc)
     * @see org.jboss.security.vault.SecurityVault#exists(java.lang.String, java.lang.String)
     */
    public boolean exists(String vaultBlock, String attributeName) throws SecurityVaultException {
        return vaultContent.getVaultData(alias, vaultBlock, attributeName) != null;
    }

    /* (non-Javadoc)
     * @see org.jboss.security.vault.SecurityVault#remove(java.lang.String, java.lang.String, byte[])
     */
    public boolean remove(String vaultBlock, String attributeName, byte[] sharedKey)
            throws SecurityVaultException {
        if (StringUtil.isNullOrEmpty(vaultBlock))
            throw new IllegalArgumentException(msm.getString("invalidNullArgument", "vaultBlock"));
        if (StringUtil.isNullOrEmpty(attributeName))
            throw new IllegalArgumentException(msm.getString("invalidNullArgument", "attributeName"));

        try {
            if (vaultContent.deleteVaultData(alias, vaultBlock, attributeName)) {
                writeVaultData();
                return true;
            }
            return false;
        } catch (IOException e) {
            throw new SecurityVaultException(msm.getString("unableToWriteVaultDataFileMessage", VAULT_CONTENT_FILE), e);
        } catch (Exception e) {
            throw new SecurityVaultException(e);
        }
    }

    private char[] loadKeystorePassword(String passwordDef, String salt, int iterationCount) throws Exception {
        final char[] password;

        if (passwordDef.startsWith(PASS_MASK_PREFIX)) {
            String keystorePass = decode(passwordDef, salt, iterationCount);
            password = keystorePass.toCharArray();
        } else
            password = Util.loadPassword(passwordDef);

        return password;
    }

    private String decode(String maskedString, String salt, int iterationCount) throws Exception {
        String pbeAlgo = "PBEwithMD5andDES";
        if (maskedString.startsWith(PASS_MASK_PREFIX)) {
            // Create the PBE secret key
            SecretKeyFactory factory = SecretKeyFactory.getInstance(pbeAlgo);

            char[] password = "somearbitrarycrazystringthatdoesnotmatter".toCharArray();
            PBEParameterSpec cipherSpec = new PBEParameterSpec(salt.getBytes(), iterationCount);
            PBEKeySpec keySpec = new PBEKeySpec(password);
            SecretKey cipherKey = factory.generateSecret(keySpec);

            maskedString = maskedString.substring(PASS_MASK_PREFIX.length());
            String decodedValue = PBEUtils.decode64(maskedString, pbeAlgo, cipherKey, cipherSpec);
            maskedString = decodedValue;
        }
        return maskedString;
    }

    private void setUpVault(String keystoreURL, String decodedEncFileDir) throws NoSuchAlgorithmException, IOException {
        vaultContent = new SecurityVaultData();
        writeVaultData();

        SecretKey sk = getAdminKey();
        if (sk != null) {
            adminKey = sk;
        } else {
            if (!createKeyStore) {
                throw new RuntimeException(msm.getString("vaultDoesnotContainSecretKey", alias));
            }
            // try to generate new admin key and store it under specified alias
            EncryptionUtil util = new EncryptionUtil(encryptionAlgorithm, keySize);
            sk = util.generateKey();
            KeyStore.SecretKeyEntry skEntry = new KeyStore.SecretKeyEntry(sk);
            try {
                keystore.setEntry(alias, skEntry, new KeyStore.PasswordProtection(keyStorePWD));
                adminKey = sk;
                saveKeyStoreToFile(keystoreURL);
            } catch (KeyStoreException e) {
                throw new RuntimeException(msm.getString("noSecretKeyandAliasAlreadyUsed", alias));
            } catch (Exception e) {
                throw new RuntimeException(msm.getString("unableToStoreKeyStoreToFile", keystoreURL), e);
            }
        }
    }

    private void writeVaultData() throws IOException {
        FileOutputStream fos = null;
        ObjectOutputStream oos = null;
        try {
            fos = new FileOutputStream(decodedEncFileDir + VAULT_CONTENT_FILE);
            oos = new ObjectOutputStream(fos);
            oos.writeObject(vaultContent);
        } finally {
            safeClose(oos);
            safeClose(fos);
        }
    }

    private boolean vaultFileExists(String fileName) {
        File file = new File(this.decodedEncFileDir + fileName);
        return file != null && file.exists();
    }

    private boolean directoryExists(String dir) {
        File file = new File(dir);
        return file != null && file.exists();
    }

    private void safeClose(InputStream fis) {
        try {
            if (fis != null) {
                fis.close();
            }
        } catch (Exception e) {
        }
    }

    private void safeClose(OutputStream os) {
        try {
            if (os != null) {
                os.close();
            }
        } catch (Exception e) {
        }
    }

    private void readVaultContent(String keystoreURL, String encFileDir) throws SecurityVaultException {

        try {
            if (encFileDir.contains("${)")) {
                encFileDir = encFileDir.replaceAll(":", StringUtil.PROPERTY_DEFAULT_SEPARATOR);
            }
            decodedEncFileDir = StringUtil.getSystemPropertyAsString(encFileDir); // replace single ":" with PL default

            if (directoryExists(decodedEncFileDir) == false)
                throw new SecurityVaultException(msm.getString("fileOrDirectoryDoesNotExistMessage", decodedEncFileDir));

            if (!(decodedEncFileDir.endsWith("/") || decodedEncFileDir.endsWith("\\"))) {
                decodedEncFileDir = decodedEncFileDir + File.separator;
            }

            if (vaultFileExists(ENCODED_FILE)) {
                if (vaultFileExists(VAULT_CONTENT_FILE)) {
                    log.error(sm.getString("picketBoxSecurityVault.mixedVaultDataFound",
                            VAULT_CONTENT_FILE, ENCODED_FILE, decodedEncFileDir + ENCODED_FILE));
                    throw new RuntimeException(msm.getString("mixedVaultDataFound", VAULT_CONTENT_FILE, ENCODED_FILE));
                } else {
                    convertVaultContent(keystoreURL, alias);
                }
            } else {
                if (vaultFileExists(VAULT_CONTENT_FILE)) {
                    readVersionedVaultContent();
                } else {
                    setUpVault(keystoreURL, decodedEncFileDir);
                }
            }

        } catch (Exception e) {
            throw new SecurityVaultException(e);
        }

    }

    @SuppressWarnings("unchecked")
    private void convertVaultContent(String keystoreURL, String alias) throws Exception {
        FileInputStream fis = null;
        ObjectInputStream ois = null;
        Map<String, byte[]> theContent;

        try {
            fis = new FileInputStream(decodedEncFileDir + ENCODED_FILE);
            ois = new ObjectInputStream(fis);
            theContent = (Map<String, byte[]>) ois.readObject();
        } finally {
            safeClose(fis);
            safeClose(ois);
        }

        // create new SecurityVaultData object for transformed vault data
        vaultContent = new SecurityVaultData();

        adminKey = null;
        for (String key : theContent.keySet()) {
            if (key.equals(ADMIN_KEY)) {
                byte[] admin_key = theContent.get(key);
                adminKey = new SecretKeySpec(admin_key, encryptionAlgorithm);
            } else {
                if (key.contains("_")) {
                    StringTokenizer tokenizer = new StringTokenizer(key, "_");
                    String vaultBlock = tokenizer.nextToken();
                    String attributeName = tokenizer.nextToken();
                    if (tokenizer.hasMoreTokens()) {
                        attributeName = key.substring(vaultBlock.length() + 1);
                        log.info(sm.getString("picketBoxSecurityVault.ambiguosKeyForSecurityVaultTransformation", "_", vaultBlock, attributeName));
                    }
                    byte[] encodedAttributeValue = theContent.get(key);
                    vaultContent.addVaultData(alias, vaultBlock, attributeName, encodedAttributeValue);
                }
            }
        }
        if (adminKey == null) {
            throw new RuntimeException(msm.getString("missingAdminKeyInOriginalVaultData"));
        }

        // add secret key (admin_key) to keystore
        KeyStore.SecretKeyEntry skEntry = new KeyStore.SecretKeyEntry(adminKey);
        KeyStore.PasswordProtection p = new KeyStore.PasswordProtection(keyStorePWD);
        Entry e = keystore.getEntry(alias, p);
        if (e != null) {
            // rename the old entry
            String originalAlias = alias + "-original";
            keystore.setEntry(originalAlias, e, p);
            keystore.deleteEntry(alias);
        }
        keystore.setEntry(alias, skEntry, new KeyStore.PasswordProtection(keyStorePWD));

        // save the current keystore
        saveKeyStoreToFile(keystoreURL);

        // backup original vault file (shared key file cannot be saved for obvious reasons
        copyFile(new File(decodedEncFileDir + ENCODED_FILE), new File(decodedEncFileDir + ENCODED_FILE + ".original"));

        // save vault data file
        writeVaultData();

        // delete original vault files
        File f = new File(decodedEncFileDir + ENCODED_FILE);
        if (!f.delete()) {
            log.warn(sm.getString("picketBoxSecurityVault.cannotDeleteOriginalVaultFile", f.getCanonicalPath()));
        }
        f = new File(decodedEncFileDir + SHARED_KEY_FILE);
        if (!f.delete()) {
            log.warn(sm.getString("picketBoxSecurityVault.cannotDeleteOriginalVaultFile", f.getCanonicalPath()));
        }

    }

    private void saveKeyStoreToFile(String keystoreURL) throws Exception {
        keystore.store(new FileOutputStream(new File(keystoreURL)), keyStorePWD);
    }

    private void checkAndConvertKeyStoreToJCEKS(String keystoreURL) throws Exception {
        if (keystore.getType().equalsIgnoreCase("JKS")) {

            // backup original keystore file
            copyFile(new File(keystoreURL), new File(keystoreURL + ".original"));

            KeyStore jceks = KeyStoreUtil.createKeyStore("JCEKS", keyStorePWD);

            Enumeration<String> aliases = keystore.aliases();
            while (aliases.hasMoreElements()) {
                String entryAlias = aliases.nextElement();
                KeyStore.PasswordProtection p = new KeyStore.PasswordProtection(keyStorePWD);
                KeyStore.Entry e = keystore.getEntry(entryAlias, p);
                jceks.setEntry(entryAlias, e, p);
            }
            keystore = jceks;
            keyStoreType = "JCEKS"; // after conversion we have to change keyStoreType to the one we really have
            saveKeyStoreToFile(keystoreURL);
            log.info(sm.getString("picketBoxSecurityVault.keyStoreConvertedToJCEKS", KEYSTORE_URL));
        }
    }


    private void readVersionedVaultContent() throws Exception {
        FileInputStream fis = null;
        ObjectInputStream ois = null;
        try {
            fis = new FileInputStream(decodedEncFileDir + VAULT_CONTENT_FILE);
            ois = new ObjectInputStream(fis);
            vaultContent = (SecurityVaultData) ois.readObject();
        } finally {
            safeClose(fis);
            safeClose(ois);
        }

        adminKey = getAdminKey();
        if (adminKey == null) {
            throw new RuntimeException(msm.getString("vaultDoesnotContainSecretKey", alias));
        }
    }

    /**
     * Returns SecretKey stored in defined keystore under defined alias.
     * If no such SecretKey exists returns null.
     *
     * @return
     */
    private SecretKey getAdminKey() {
        try {
            Entry e = keystore.getEntry(alias, new KeyStore.PasswordProtection(keyStorePWD));
            if (e instanceof KeyStore.SecretKeyEntry) {
                return ((KeyStore.SecretKeyEntry) e).getSecretKey();
            }
        } catch (Exception e) {
            log.info(sm.getString("picketBoxSecurityVault.vaultDoesNotContainSecretKey", alias));
            return null;
        }
        return null;
    }

    /**
     * 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();
            }
        }
    }

    /**
     * Get key store based on options passed to PicketBoxSecurityVault.
     *
     * @return
     */
    private KeyStore getKeyStore(String keystoreURL) {

        try {
            if (createKeyStore) {
                return KeyStoreUtil.createKeyStore(keyStoreType, keyStorePWD);
            }
        } catch (Throwable e) {
            throw new RuntimeException(msm.getString("unableToGetKeyStore", keystoreURL), e);
        }

        try {
            return KeyStoreUtil.getKeyStore(keyStoreType, keystoreURL, keyStorePWD);
        } catch (IOException e) {
            throw new RuntimeException(msm.getString("unableToGetKeyStore", keystoreURL), e);
        } catch (GeneralSecurityException e) {
            throw new RuntimeException(msm.getString("unableToGetKeyStore", keystoreURL), e);
        }
    }

}
