net.sf.taverna.t2.security.credentialmanager.impl.CredentialManagerImpl.java Source code

Java tutorial

Introduction

Here is the source code for net.sf.taverna.t2.security.credentialmanager.impl.CredentialManagerImpl.java

Source

/*******************************************************************************
 * Copyright (C) 2008-2014 The University of Manchester
 *
 *  Modifications to the initial code base are copyright of their
 *  respective authors, or their employers as appropriate.
 *
 *  This program 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 program 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 program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
 ******************************************************************************/
package net.sf.taverna.t2.security.credentialmanager.impl;

import static javax.security.auth.x500.X500Principal.RFC2253;
import static net.sf.taverna.t2.security.credentialmanager.CredentialManager.KeystoreType.KEYSTORE;
import static net.sf.taverna.t2.security.credentialmanager.CredentialManager.KeystoreType.TRUSTSTORE;
import static org.apache.commons.io.FileUtils.touch;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Method;
import java.math.BigInteger;
import java.net.Authenticator;
import java.net.Socket;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.security.Key;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.Principal;
import java.security.PrivateKey;
import java.security.SecureRandom;
import java.security.Security;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;

import javax.crypto.spec.SecretKeySpec;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509ExtendedKeyManager;
import javax.net.ssl.X509KeyManager;
import javax.net.ssl.X509TrustManager;

import net.sf.taverna.t2.lang.observer.MultiCaster;
import net.sf.taverna.t2.lang.observer.Observable;
import net.sf.taverna.t2.lang.observer.Observer;
import net.sf.taverna.t2.security.credentialmanager.CMException;
import net.sf.taverna.t2.security.credentialmanager.CredentialManager;
import net.sf.taverna.t2.security.credentialmanager.DistinguishedNameParser;
import net.sf.taverna.t2.security.credentialmanager.JavaTruststorePasswordProvider;
import net.sf.taverna.t2.security.credentialmanager.KeystoreChangedEvent;
import net.sf.taverna.t2.security.credentialmanager.MasterPasswordProvider;
import net.sf.taverna.t2.security.credentialmanager.ParsedDistinguishedName;
import net.sf.taverna.t2.security.credentialmanager.ServiceUsernameAndPasswordProvider;
import net.sf.taverna.t2.security.credentialmanager.TrustConfirmationProvider;
import net.sf.taverna.t2.security.credentialmanager.UsernamePassword;

import org.apache.log4j.Logger;
import org.bouncycastle.jce.provider.BouncyCastleProvider;

import uk.org.taverna.configuration.app.ApplicationConfiguration;

/**
 * Provides an implementation of {@link #CredentialManagerService}.
 * 
 * @author Alex Nenadic
 * @author Stian Soiland-Reyes
 */
public class CredentialManagerImpl implements CredentialManager, Observable<KeystoreChangedEvent> {
    /** Various passwords to try for the Java's default truststore. */
    public static List<String> defaultTrustStorePasswords = Arrays
            .asList(System.getProperty(PROPERTY_TRUSTSTORE_PASSWORD, ""), "changeit", "changeme", "");

    /*
     * For Taverna 2.2 and older - Keystore was BC-type with user-set password
     * and Truststore was JKS-type with the default password
     */
    public static final String OLD_TRUSTSTORE_PASSWORD = "Tu/Ap%2_$dJt6*+Rca9v";
    public static final String OLD_T2TRUSTSTORE_FILE = "t2truststore.jks";

    private static Logger logger = Logger.getLogger(CredentialManagerImpl.class);

    // Multicaster of KeystoreChangedEventS
    private MultiCaster<KeystoreChangedEvent> multiCaster = new MultiCaster<>(this);

    /**
     * A directory containing Credential Manager's Keystore/Truststore/etc.
     * files.
     */
    private File credentialManagerDirectory = null;

    /**
     * Master password for Credential Manager - used to create/access the
     * Keystore and Truststore.
     */
    private String masterPassword;

    // Keystore file
    private File keystoreFile = null;

    // Truststore file
    private File truststoreFile = null;

    /**
     * Keystore containing user's passwords and private keys with corresponding
     * public key certificate chains.
     */
    private KeyStore keystore;

    /**
     * Truststore containing trusted certificates of CA authorities and services
     * (servers).
     */
    private KeyStore truststore;

    /**
     * Has the Credential Manager been initialized (i.e. the Keystore/Truststore
     * loaded, etc.)
     */
    private boolean isInitialized = false;

    /*
     * Whether SSLSocketFactory has been initialised with Taverna's
     * Keystore/Truststore. Actually tavernaSSLSocketFactory==null? check tells
     * us if Taverna's SSLSocketFactory has been initialised
     */
    // private static boolean sslInitialized = false;

    private static SSLSocketFactory tavernaSSLSocketFactory;

    /**
     * Observer of changes to the Keystore and Truststore that updates the
     * default SSLContext and SSLSocketFactory at the single location rather
     * than all over the code when changes to the keystores occur.
     */
    private KeystoreChangedObserver keystoresChangedObserver = new KeystoreChangedObserver();

    /**
     * Cached list of all services that have a username/password entry in the
     * Keystore
     */
    private List<URI> cachedServiceURIsList = null;

    /**
     * Cached map of all URI fragments to their original URIs for services that
     * have a username/password entry in the Keystore. This is normally used to
     * recursively discover the realm of the service for HTTP authentication so
     * we do not have to ask user for their username and password for every
     * service in the same realm.
     */
    private HashMap<URI, URI> cachedServiceURIsMap = null;

    // Observer that clears the above list and map on any change to the Keystore
    private ClearCachedServiceURIsObserver clearCachedServiceURIsObserver = new ClearCachedServiceURIsObserver();

    /** A list of master password providers */
    private List<MasterPasswordProvider> masterPasswordProviders;

    /**
     * A list of Java truststore password (used to encrypt/decrypt the Java's
     * default truststore) providers
     */
    private List<JavaTruststorePasswordProvider> javaTruststorePasswordProviders;

    /** A list of providers of usernames and passwords for services */
    private List<ServiceUsernameAndPasswordProvider> serviceUsernameAndPasswordProviders;

    /** A list of providers of trust confirmation for services */
    private List<TrustConfirmationProvider> trustConfirmationProviders;

    private ApplicationConfiguration applicationConfiguration;

    private File certificatesRevokedIndicatorFile;

    private DistinguishedNameParser dnParser = new DistinguishedNameParserImpl();

    /**
     * Return an array of URLs for 'special' trusted CAs' certificates contained in
     * the resources folder that need to be loaded into Truststore, so that we can establish trust 
     * into services such as BioCatalogue, BiodiversityCatalogue, heater, etc. by default.
     * */
    private static List<URL> getSpecialTrustedCertificates() {
        List<URL> urls = new ArrayList<>();
        Class<?> c = CredentialManager.class;
        urls.add(c.getResource("/trusted-certificates/TERENASSLCA.crt"));
        urls.add(c.getResource("/trusted-certificates/UTNAddTrustServer_CA.crt"));
        urls.add(c.getResource("/trusted-certificates/AddTrustExternalCARoot.crt"));
        return urls;
    }

    /**
     * Connects this credential manager to the Java {@linkplain Authenticator HTTP authenticator mechanism}.
     */
    public void installAuthenticator() {
        Authenticator.setDefault(new CredentialManagerAuthenticator(this));
    }

    /**
     * Delete the old revoked or unnecessary BioCatalogue, BiodiversityCatalogue
     * and heater's certificates, if present
     */
    public void deleteRevokedCertificates() {
        if (truststore == null)
            return;
        if (certificatesRevokedIndicatorFile == null)
            certificatesRevokedIndicatorFile = new File(credentialManagerDirectory,
                    CERTIFICATES_REVOKED_INDICATOR_FILE_NAME);

        boolean saveFile = false;
        if (!certificatesRevokedIndicatorFile.exists()) {
            List<URL> certURLsToDelete = new ArrayList<>();
            Class<?> c = CredentialManager.class;
            certURLsToDelete.add(c.getResource("/trusted-certificates/www.biocatalogue.org-revoked.pem"));
            certURLsToDelete.add(c.getResource("/trusted-certificates/www.biodiversitycatalogue.org-revoked.pem"));
            certURLsToDelete.add(c.getResource("/trusted-certificates/heater.cs.man.ac.uk-not-needed.pem"));

            for (URL certURLToDelete : certURLsToDelete) {
                try (InputStream certStreamToDelete = certURLToDelete.openStream()) {
                    // We know there will be only one cert in the chain
                    CertificateFactory cf = CertificateFactory.getInstance("X.509");
                    Certificate certToDelete = cf.generateCertificates(certStreamToDelete)
                            .toArray(new Certificate[0])[0];
                    String aliasToDelete = truststore.getCertificateAlias(certToDelete);
                    if (aliasToDelete != null) {
                        truststore.deleteEntry(aliasToDelete);
                        logger.warn("Deleting revoked/unnecessary certificate " + aliasToDelete);
                        saveFile = true;
                    }
                } catch (Exception ex) {
                    logger.info("Can't delete revoked certificate " + certURLToDelete, ex);
                }
            }

            // Touch the file
            try {
                touch(certificatesRevokedIndicatorFile);
            } catch (IOException ioex) {
                // Hmmm, ignore this?
                logger.error("Failed to touch " + certificatesRevokedIndicatorFile.getAbsolutePath(), ioex);
            }
        }

        if (saveFile) {
            // Save changes
            try (FileOutputStream fos = new FileOutputStream(truststoreFile)) {
                truststore.store(fos, masterPassword.toCharArray());
            } catch (Exception ex) {
                logger.error("Failed to save Truststore after deleting revoked certificates.", ex);
            }
        }
    }

    public CredentialManagerImpl() throws CMException {
        /*
         * Make sure we have BouncyCastle provider installed, just in case
         * (needed for some tests and reading PKCS#12 keystores)
         */
        Security.addProvider(new BouncyCastleProvider());

        /*
         * Open the files stored in the (DEFAULT!!!) Credential Manager's
         * directory
         */
        // loadDefaultConfigurationFiles();
        // FIXME
        /*
         * Get the location of the directory containing the Keystore and
         * Truststore somehow - from OSGi's Configuration Service
         */

        // initialize();
    }

    /**
     * Initialize Credential Manager - load the Keystore and Truststore.
     */
    private void initialize() throws CMException {
        /*
         * Only do this if the Credential Manager has not been initialized so
         * far
         */
        if (!isInitialized) {
            masterPassword = getMasterPassword();

            this.addObserver(clearCachedServiceURIsObserver);
            this.addObserver(keystoresChangedObserver);

            // Load the Keystore
            try {
                loadKeystore();
                logger.info("loaded the Keystore");
            } catch (CMException cme) {
                isInitialized = false;
                masterPassword = null; // just in case we need to try again
                // logger.error(cme.getMessage(), cme);
                throw cme;
            }

            // Load the Truststore
            try {
                loadTruststore();
                logger.info("loaded the Truststore");
            } catch (CMException cme) {
                isInitialized = false;
                masterPassword = null; // just in case we need to try again
                // logger.error(cme.getMessage(), cme);
                throw cme;
            }

            isInitialized = true;
        }
    }

    /**
     * Get the master password from the available providers.
     * 
     * @return master password
     * @throws CMException
     *             if none of the providers can provide a non-null master
     *             password
     */
    private String getMasterPassword() throws CMException {
        if (masterPassword != null)
            return masterPassword;

        if (keystoreFile == null)
            loadDefaultSecurityFiles();

        boolean firstTime = !keystoreFile.exists();

        /**
         * Master password providers are already sorted by their priority by the
         * OSGi framework
         */
        for (MasterPasswordProvider masterPasswordProvider : masterPasswordProviders) {
            // FIXME how to handle default password providers!?
            String password = masterPasswordProvider.getMasterPassword(firstTime);
            if (password != null)
                return password;
        }

        /*
         * We are in big trouble - we do not have a single master password
         * provider.
         */
        String exMessage = "Failed to obtain master password from providers: " + masterPasswordProviders;
        logger.error(exMessage);
        throw new CMException(exMessage);
    }

    /**
     * Load Taverna's Keystore from a file on the disk.
     */
    private void loadKeystore() throws CMException {
        if (keystore == null) {
            try {
                // Try to create Taverna's Keystore as Bouncy Castle UBER-type
                // keystore.
                keystore = KeyStore.getInstance("UBER", "BC");
            } catch (Exception ex) {
                // The requested keystore type is not available from security
                // providers.
                throw new CMException("Failed to instantiate Taverna's Keystore.", ex);
            }

            if (keystoreFile.exists()) { // If the file exists, open it
                // Try to load the Keystore
                try (FileInputStream fis = new FileInputStream(keystoreFile)) {
                    // Load the Keystore from the file
                    keystore.load(fis, masterPassword.toCharArray());
                } catch (Exception ex) {
                    keystore = null;
                    /*
                     * make it null as it was just created but failed to load so
                     * it is not null
                     */
                    masterPassword = null;
                    /*
                     * it is probably the wrong password so do not remember it
                     * just in case
                     */
                    String exMessage = "Failed to load Taverna's Keystore from " + keystoreFile.getAbsolutePath()
                            + ". Possible reason: incorrect password or corrupted file.";
                    logger.error(exMessage, ex);
                    throw new CMException(exMessage, ex);
                }
            } else {
                // Otherwise create a new empty Keystore
                try (FileOutputStream fos = new FileOutputStream(keystoreFile)) {
                    keystore.load(null, null);
                    // Immediately save the new (empty) Keystore to the file
                    keystore.store(fos, masterPassword.toCharArray());
                } catch (Exception ex) {
                    String exMessage = "Failed to generate a new empty Keystore.";
                    // logger.error(exMessage, ex);
                    throw new CMException(exMessage, ex);
                }
            }

            /*
             * Taverna distro for MAC contains info.plist file with some Java
             * system properties set to use the Keychain which clashes with what
             * we are setting here so we need to clear them
             */
            System.clearProperty(PROPERTY_KEYSTORE_TYPE);
            System.clearProperty(PROPERTY_KEYSTORE_PROVIDER);

            /*
             * Not quite sure why we still need to set these two properties
             * since we are creating our own SSLSocketFactory with our own
             * KeyManager that uses Taverna's Keystore, but seem like after
             * Taverna starts up and the first time it needs SSLSocketFactory
             * for HTTPS connection it is still using the default Java's
             * keystore unless these properties are set. Set the system property
             * "javax.net.ssl.keystore" to use Taverna's keystore.
             *
             * Axis 1 likes reading from these properties but seems to work as
             * well with Taverna's SSLSocetFactory as well. We do not want to
             * expose these as they can be read from Beanshells.
             */
            // System.setProperty(PROPERTY_KEYSTORE, keystoreFile.getAbsolutePath());
            // System.setProperty(PROPERTY_KEYSTORE_PASSWORD, masterPassword);
            System.clearProperty(PROPERTY_KEYSTORE);
            System.clearProperty(PROPERTY_KEYSTORE_PASSWORD);
        }
    }

    /**
     * Load Taverna's Truststore from a file on a disk. If the Truststore does
     * not already exist, a new empty one will be created and contents of Java's
     * truststore located in <JAVA_HOME>/lib/security/cacerts will be copied
     * over to the Truststore.
     */
    private void loadTruststore() throws CMException {
        if (truststore != null)
            return;
        try {
            /*
             * Try to create Taverna's Truststore as Bouncy Castle UBER-type
             * keystore.
             */
            truststore = KeyStore.getInstance("UBER", "BC");
        } catch (Exception ex) {
            /*
             * The requested keystore type is not available from security
             * providers.
             */
            throw new CMException("Failed to instantiate Taverna's Truststore", ex);
        }

        if (truststoreFile.exists()) {
            /*
             * If the Truststore file already exists, open it and load the
             * Truststore
             */
            try (FileInputStream fis = new FileInputStream(truststoreFile)) {
                // Load the Truststore from the file
                truststore.load(fis, masterPassword.toCharArray());
            } catch (Exception ex) {
                /* Clear out things that are useless/hindering now */
                truststore = null;
                masterPassword = null;
                String exMessage = "Failed to load Taverna's Truststore from " + truststoreFile.getAbsolutePath()
                        + ". Possible reason: incorrect password or corrupted file.";
                logger.error(exMessage, ex);
                throw new CMException(exMessage, ex);
            }
            deleteRevokedCertificates();
        } else {
            /*
             * Otherwise create a new empty Truststore and load it with certs
             * from Java's truststore.
             */
            File javaTruststoreFile = new File(System.getProperty("java.home"), "lib/security/cacerts");
            KeyStore javaTruststore = null;

            // Java's truststore is of type "JKS" - try to load it
            try {
                javaTruststore = KeyStore.getInstance("JKS");
            } catch (Exception ex) {
                // The requested keystore type is not available from the
                // provider
                throw new CMException(
                        "Failed to instantiate a 'JKS'-type keystore " + "for reading Java's truststore.", ex);
            }

            boolean loadedJavaTruststore = false;
            /*
             * Load Java's truststore from the file - try with the default Java
             * truststore passwords.
             */
            for (String password : defaultTrustStorePasswords) {
                logger.info("Trying to load Java truststore using password: " + password);
                try (FileInputStream fis = new FileInputStream(javaTruststoreFile)) {
                    javaTruststore.load(fis, password.toCharArray());
                    loadedJavaTruststore = true;
                    break;
                } catch (IOException ioex) {
                    /*
                     * If there is an I/O or format problem with the keystore
                     * data, or if the given password was incorrect. (Thank you
                     * Sun, now I can't know if it is the file or the
                     * password..)
                     */
                    logger.info(String.format(
                            "Failed to load the Java truststore to copy "
                                    + "over certificates using default password: " + "%s from %s",
                            password, javaTruststoreFile));
                } catch (NoSuchAlgorithmException e) {
                    logger.error("Unknown encryption algorithm " + "while loading Java truststore from "
                            + javaTruststoreFile, e);
                    break;
                } catch (CertificateException e) {
                    logger.error("Certificate error while " + "loading Java truststore from " + javaTruststoreFile,
                            e);
                    break;
                }
            }

            /*
             * Default Java truststore passwords failed - possibly the user has
             * changed it. Ask the Java truststore password providers if they
             * can help - this will typically pop up a dialog to ask the user if
             * we are in a graphical environment. If not, we will simply not
             * copy the default truststore certificates into Credential
             * Manager's Truststore.
             */
            if (!loadedJavaTruststore
                    && !loadJavaTruststoreUsingPasswordProviders(javaTruststore, javaTruststoreFile)) {
                String error = "Credential manager failed to load" + " certificates from Java's truststore.";
                String help = "Try using the system property -D" + PROPERTY_TRUSTSTORE_PASSWORD
                        + "=TheTrustStorePassword";
                logger.error(error + " " + help);
                // FIXME Writes to standard error!
                System.err.println(error);
                System.err.println(help);
            }

            // Create a new empty Truststore for Taverna
            try (FileOutputStream fos = new FileOutputStream(truststoreFile)) {
                truststore.load(null, null);
                if (loadedJavaTruststore) {
                    // Copy certificates into Taverna's Truststore from
                    // Java's truststore.
                    Enumeration<String> aliases = javaTruststore.aliases();
                    while (aliases.hasMoreElements()) {
                        Certificate certificate = javaTruststore.getCertificate(aliases.nextElement());
                        if (certificate instanceof X509Certificate)
                            truststore.setCertificateEntry(
                                    createTrustedCertificateAlias((X509Certificate) certificate), certificate);
                    }
                }

                // Insert special trusted CA certificates
                logger.info(
                        "Loading certificates of trusted CAs so as to establish trust into our services such as BioCatalogue, BiodiversityCatalogue, heater, etc.");
                CertificateFactory cf = CertificateFactory.getInstance("X.509");
                for (URL trustedCertURL : getSpecialTrustedCertificates())
                    // Load the certificate (possibly a chain) from the
                    // stream
                    try (InputStream stream = trustedCertURL.openStream()) {
                        for (Certificate c : cf.generateCertificates(stream))
                            truststore.setCertificateEntry(createTrustedCertificateAlias((X509Certificate) c), c);
                    } catch (Exception cex) {
                        logger.error("Failed to insert trusted certificate entry in the Truststore", cex);
                    }

                // Immediately save the new Truststore to the file
                truststore.store(fos, masterPassword.toCharArray());
            } catch (Exception ex) {
                /*
                 * make truststore null as it was just created but failed to
                 * save so we should retry next time
                 */
                truststore = null;
                throw new CMException("Failed to generate new empty Taverna's Truststore", ex);
            }
        }

        /*
         * Taverna distro for MAC contains info.plist file with some Java system
         * properties set to use the Keychain which clashes with what we are
         * setting here so we need to clear them.
         */
        System.clearProperty(PROPERTY_TRUSTSTORE_TYPE);
        System.clearProperty(PROPERTY_TRUSTSTORE_PROVIDER);

        /*
         * Not quite sure why we still need to set these two properties since we
         * are creating our own SSLSocketFactory with our own TrustManager that
         * uses Taverna's Truststore, but seem like after Taverna starts up and
         * the first time it needs SSLSocketFactory for HTTPS connection it is
         * still using the default Java's truststore unless these properties are
         * set. Set the system property "javax.net.ssl.Truststore" to use
         * Taverna's truststore.
         */

        /*
         * Axis 1 likes reading from these properties but seems to work as well
         * with Taverna's SSLSocetFactory as well. We do not want to expose
         * these as they can be read from Beanshells.
         */
        // System.setProperty(PROPERTY_TRUSTSTORE, truststoreFile.getAbsolutePath());
        // System.setProperty(PROPERTY_TRUSTSTORE_PASSWORD, masterPassword);
        System.clearProperty(PROPERTY_TRUSTSTORE);
        System.clearProperty(PROPERTY_TRUSTSTORE_PASSWORD);
    }

    /**
     * Load the given keystore (which is Java's default truststore) from the
     * given file (pointing to the Java's default truststore) using the
     * {@link JavaTruststorePasswordProvider}s lookup to obtain the password for
     * the keytore.
     * 
     * @param javaTruststore
     *            Java's default truststore
     * @param javaTruststoreFile
     *            Java's default truststore file
     * @return true if managed to load the keystore using the provided
     *         passwords; false otherwise
     */
    private boolean loadJavaTruststoreUsingPasswordProviders(KeyStore javaTruststore, File javaTruststoreFile) {
        String javaTruststorePassword = null;
        for (JavaTruststorePasswordProvider provider : javaTruststorePasswordProviders) {
            javaTruststorePassword = provider.getJavaTruststorePassword();
            if (javaTruststorePassword == null)
                continue;
            try (FileInputStream fis = new FileInputStream(javaTruststoreFile)) {
                javaTruststore.load(fis, javaTruststorePassword.toCharArray());
                return true;
            } catch (Exception ex) {
                String exMessage = "Failed to load the Java truststore to copy over certificates"
                        + " using user-provided password from password provider " + provider;
                logger.warn(exMessage, ex);
                return false;
            }
        }
        String exMessage = "No Java truststore password provider could unlock "
                + "Java's truststore. Creating a new empty " + "Truststore for Taverna.";
        logger.error(exMessage);
        return false;
    }

    /**
     * Get a username and password pair for the given service, or null if it
     * does not exit. The returned array contains username as the first element
     * and password as the second.
     * 
     * @deprecated Use
     *             {@link #getUsernameAndPasswordForService(URI, boolean, String)}
     *             instead
     */
    @Deprecated
    public String[] getUsernameAndPasswordForService(String serviceURL) throws CMException {
        /*
         * Need to make sure we are initialized before we do anything else, as
         * the Credential Manager can be created but not initialized.
         */
        initialize();

        UsernamePassword usernamePassword = getUsernameAndPasswordForService(URI.create(serviceURL), false, null);
        if (usernamePassword == null)
            return null;

        String[] pair = new String[2];
        pair[0] = usernamePassword.getUsername();
        pair[1] = String.valueOf(usernamePassword.getPassword());
        usernamePassword.resetPassword();
        return pair;
    }

    /**
     * Get a username and password pair for the given service's URI, or null if
     * it does not exit.
     * <p>
     * If the username and password are not available in the Keystore, it will
     * invoke implementations of the {@link ServiceUsernameAndPasswordProvider}
     * interface asking the user (typically through the UI) or resolving
     * hard-coded credentials.
     * <p>
     * If the parameter <code>useURIPathRecursion</code> is true, then the
     * Credential Manager will also attempt to look for stored credentials for
     * each of the parent fragments of the URI.
     * 
     * @param serviceURI
     *            The URI of the service for which we are providing the username
     *            and password
     * @param useURIPathRecursion
     *            Whether to look for any username and passwords stored in the
     *            Keystore for the parent fragments of the service URI (for
     *            example, we are looking for the credentials for service
     *            http://somehost/some-fragment but we already have credentials
     *            stored for http://somehost which can be reused)
     * @param requestingMessage
     *            The message to be presented to the user when asking for the
     *            username and password, normally useful for UI providers that
     *            pop up dialogs, can be ignored otherwise
     * @return username and password pair for the given service
     * @throws CMException
     *             if anything goes wrong during Keystore lookup, etc.
     */
    @Override
    public UsernamePassword getUsernameAndPasswordForService(URI serviceURI, boolean usePathRecursion,
            String requestingMessage) throws CMException {
        /*
         * Need to make sure we are initialized before we do anything else, as
         * the Credential Manager can be created but not initialized.
         */
        initialize();

        synchronized (keystore) {
            SecretKeySpec passwordKey = null;
            LinkedHashSet<URI> possibleServiceURIsToLookup = getPossibleServiceURIsToLookup(serviceURI,
                    usePathRecursion);
            Map<URI, URI> allServiceURIs = getFragmentMappedURIsForAllUsernameAndPasswordPairs();

            try {
                for (URI lookupURI : possibleServiceURIsToLookup) {
                    URI mappedURI = allServiceURIs.get(lookupURI);
                    if (mappedURI == null)
                        continue;

                    /*
                     * We found it - get the username and password in the
                     * Keystore associated with this service URI
                     */
                    String alias = "password#" + mappedURI.toASCIIString();
                    passwordKey = (SecretKeySpec) keystore.getKey(alias, masterPassword.toCharArray());
                    if (passwordKey == null) {
                        // Unexpected, it was just there in the map!
                        logger.warn("Could not find alias " + alias + " for known uri " + lookupURI
                                + ", just deleted?");
                        /*
                         * Remember we went outside synchronized(keystore) while
                         * looping
                         */
                        continue;
                    }
                    String unpasspair = new String(passwordKey.getEncoded(), UTF_8);
                    /*
                     * decoded key contains string
                     * <USERNAME><SEPARATOR_CHARACTER><PASSWORD>
                     */

                    int separatorAt = unpasspair.indexOf(USERNAME_AND_PASSWORD_SEPARATOR_CHARACTER);
                    if (separatorAt < 0)
                        throw new CMException("Invalid credentials stored for " + lookupURI);

                    String username = unpasspair.substring(0, separatorAt);
                    String password = unpasspair.substring(separatorAt + 1);

                    UsernamePassword usernamePassword = new UsernamePassword();
                    usernamePassword.setUsername(username);
                    usernamePassword.setPassword(password.toCharArray());
                    return usernamePassword;
                }

                // Nothing found in the Keystore, let's lookup using the service
                // username and password providers
                for (ServiceUsernameAndPasswordProvider provider : serviceUsernameAndPasswordProviders) {
                    UsernamePassword usernamePassword = provider.getServiceUsernameAndPassword(serviceURI,
                            requestingMessage);
                    if (usernamePassword == null)
                        continue;
                    if (usernamePassword.isShouldSave()) {
                        URI uri = serviceURI;
                        if (usePathRecursion)
                            uri = normalizeServiceURI(serviceURI);
                        addUsernameAndPasswordForService(usernamePassword, uri);
                    }
                    return usernamePassword;
                }
                // Giving up
                return null;
            } catch (Exception ex) {
                String exMessage = "Failed to get the username and password pair for service " + serviceURI
                        + " from the Keystore";
                logger.error(exMessage, ex);
                throw new CMException(exMessage, ex);
            }
        }
    }

    protected Map<URI, URI> getFragmentMappedURIsForAllUsernameAndPasswordPairs() throws CMException {
        synchronized (Security.class) {// FIXME synchonization on strange thing!
            if (cachedServiceURIsMap == null) {
                HashMap<URI, URI> map = new HashMap<>();
                // Get all service URIs that have username and password in the
                // Keystore
                for (URI serviceURI : getServiceURIsForAllUsernameAndPasswordPairs()) {
                    // Always store 1-1, with or without fragment
                    map.put(serviceURI, serviceURI);
                    if (serviceURI.getFragment() == null)
                        continue;

                    // Look up the no-fragment uri as an additional mapping
                    URI noFragment;
                    try {
                        noFragment = dnParser.setFragmentForURI(serviceURI, null);
                    } catch (URISyntaxException e) {
                        logger.warn("Could not reset fragment for service URI " + serviceURI);
                        continue;
                    }
                    if (map.containsKey(noFragment)) {
                        if (map.get(noFragment).getFragment() != null) {
                            // No mapping for duplicates
                            map.remove(noFragment);
                            continue;
                        } // else it's noFragment -> noFragment, which is OK
                    } else {
                        // Brand new, put it in
                        map.put(noFragment, serviceURI);
                    }
                }
                cachedServiceURIsMap = map;
            }
            return cachedServiceURIsMap;
        }
    }

    /*
     * Creates a list of possible URIs to look up when searching for username
     * and password for a service with a given URI. This is mainly useful for
     * HTTP AuthN when we save the realm URI rather than the exact service URI
     * as we want that username and password pair to be used for the whole realm
     * and not bother user for credentials every time them access a URL from
     * that realm.
     */
    protected LinkedHashSet<URI> getPossibleServiceURIsToLookup(URI serviceURI, boolean usePathRecursion) {
        try {
            serviceURI = serviceURI.normalize();
            serviceURI = dnParser.setUserInfoForURI(serviceURI, null);
        } catch (URISyntaxException ex) {
            logger.warn("Could not strip userinfo from " + serviceURI, ex);
        }

        /*
         * We'll use a LinkedHashSet to avoid checking for duplicates, like if
         * serviceURI.equals(withoutQuery) Only the first hit should be added to
         * the set.
         */
        LinkedHashSet<URI> possibles = new LinkedHashSet<URI>();

        possibles.add(serviceURI);
        if (!usePathRecursion || !serviceURI.isAbsolute())
            return possibles;

        /*
         * We'll preserve the fragment, as it is used to indicate the realm
         */
        String rawFragment = serviceURI.getRawFragment();
        if (rawFragment == null)
            rawFragment = "";

        URI withoutQuery = serviceURI.resolve(serviceURI.getRawPath());
        addFragmentedURI(possibles, withoutQuery, rawFragment);

        // Immediate parent
        URI parent = withoutQuery.resolve(".");
        addFragmentedURI(possibles, parent, rawFragment);
        URI oldParent = null;
        // Top parent (to be added later)
        URI root = parent.resolve("/");
        while (!parent.equals(oldParent) && !parent.equals(root) && parent.getPath().length() > 0) {
            // Intermediate parents, but not for "http://bla.org" as we would
            // find "http://bla.org.."
            oldParent = parent;
            parent = parent.resolve("..");
            addFragmentedURI(possibles, parent, rawFragment);
        }
        // In case while-loop did not do so, also include root
        addFragmentedURI(possibles, root, rawFragment);
        if (rawFragment.length() > 0)
            // Add the non-fragment versions in the bottom of the list
            for (URI withFragment : new ArrayList<>(possibles))
                try {
                    possibles.add(dnParser.setFragmentForURI(withFragment, null));
                } catch (URISyntaxException e) {
                    logger.warn("Could not non-fragment URI " + withFragment);
                }
        return possibles;
    }

    public void addFragmentedURI(LinkedHashSet<URI> possibles, URI uri, String rawFragment) {
        if (rawFragment != null && rawFragment.length() > 0)
            uri = uri.resolve("#" + rawFragment);
        possibles.add(uri);
    }

    /**
     * Get service URLs associated with all username/password pairs currently in
     * the Keystore.
     * 
     * @deprecated
     * @see #getServiceURIsForAllUsernameAndPasswordPairs()
     */
    @Deprecated
    public ArrayList<String> getServiceURLsforAllUsernameAndPasswordPairs() throws CMException {
        /*
         * Need to make sure we are initialized before we do anything else, as
         * the Credential Manager can be created but not initialized.
         */
        initialize();

        List<URI> uris = getServiceURIsForAllUsernameAndPasswordPairs();
        ArrayList<String> serviceURLs = new ArrayList<>();
        for (URI uri : uris)
            serviceURLs.add(uri.toASCIIString());
        return serviceURLs;
    }

    /**
     * Insert a username and password pair for the given service URI in the
     * Keystore.
     * <p>
     * Effectively, this method inserts a new secret key entry in the Keystore,
     * where key contains <USERNAME>"\000"<PASSWORD> string, i.e. password is
     * prepended with the username and separated by a \000 character (which
     * hopefully will not appear in the username).
     * <p>
     * Username and password string is saved in the Keystore as byte array using
     * SecretKeySpec (which constructs a secret key from the given byte array
     * but does not check if the given bytes indeed specify a secret key of the
     * specified algorithm).
     * <p>
     * An alias used to identify the username and password entry is constructed
     * as "password#"<SERVICE_URL> using the service URL this username/password
     * pair is to be used for.
     * 
     * @param usernamePassword
     *            The {@link UsernamePassword} to store
     * @param serviceURI
     *            The (possibly normalized) URI to store the credentials under
     * @throws CMException
     *             If the credentials could not be stored
     * @return the alias under which this username and password entry was saved
     *         in the Keystore
     */
    @Override
    public String addUsernameAndPasswordForService(UsernamePassword usernamePassword, URI serviceURI)
            throws CMException {
        /*
         * Need to make sure we are initialized before we do anything else, as
         * the Credential Manager can be created but not initialized.
         */
        initialize();

        String uriString = serviceURI.toASCIIString();
        String alias = saveUsernameAndPasswordForService(usernamePassword.getUsername(),
                String.valueOf(usernamePassword.getPassword()), uriString);
        return alias;
    }

    /**
     * Insert a new username and password pair in the Keystore for the given
     * service URL string.
     * <p>
     * Effectively, this method inserts a new secret key entry in the Keystore,
     * where key contains <USERNAME>"\000"<PASSWORD> string, i.e. password is
     * prepended with the username and separated by a \000 character.
     * <p>
     * Username and password string is saved in the Keystore as byte array using
     * SecretKeySpec (which constructs a secret key from the given byte array
     * but does not check if the given bytes indeed specify a secret key of the
     * specified algorithm).
     * <p>
     * An alias used to identify the username and password entry is constructed
     * as "password#"<SERVICE_URL> using the service URL this username/password
     * pair is to be used for.
     * <p>
     * 
     * @return the alias under which this username and password entry was saved
     *         in the Keystore
     * @deprecated Use
     *             {@link #addUsernameAndPasswordForService(UsernamePassword, URI)}
     *             instead
     */
    @Deprecated
    public String saveUsernameAndPasswordForService(String username, String password, String serviceURL)
            throws CMException {
        /*
         * Need to make sure we are initialized before we do anything else, as
         * the Credential Manager can be created but not initialized.
         */
        initialize();

        String alias = null;

        // Alias for the username and password entry
        synchronized (keystore) {
            alias = "password#" + serviceURL;
            /*
             * Password (together with its related username) is wrapped as a
             * SecretKeySpec that implements SecretKey and constructs a secret
             * key from the given password as a byte array. The reason for this
             * is that we can only save instances of Key objects in the
             * Keystore, and SecretKeySpec class is useful for raw secret keys
             * (i.e. username and passwords concats) that can be represented as
             * a byte array and have no key or algorithm parameters associated
             * with them, e.g., DES or Triple DES. That is why we create it with
             * the name "DUMMY" for algorithm name, as this is not checked for
             * anyway.
             * 
             * Use a separator character that will not appear in the username or
             * password.
             */
            String keyToSave = username + USERNAME_AND_PASSWORD_SEPARATOR_CHARACTER + password;

            SecretKeySpec passwordKey;
            try {
                passwordKey = new SecretKeySpec(keyToSave.getBytes(UTF_8), "DUMMY");
            } catch (UnsupportedEncodingException e) {
                throw new RuntimeException("Could not find encoding " + UTF_8);
            }
            try {
                keystore.setKeyEntry(alias, passwordKey, masterPassword.toCharArray(), null);
                saveKeystore(KEYSTORE);
                multiCaster.notify(new KeystoreChangedEvent(KEYSTORE));
            } catch (Exception ex) {
                String exMessage = "Failed to insert username and password pair for service " + serviceURL
                        + " in the Keystore";
                logger.error(exMessage, ex);
                throw new CMException(exMessage, ex);
            }
        }

        return alias;
    }

    /**
     * Delete a username and password pair for the given service URI from the
     * Keystore.
     */
    @Override
    public void deleteUsernameAndPasswordForService(URI serviceURI) throws CMException {
        /*
         * Need to make sure we are initialized before we do anything else, as
         * the Credential Manager can be created but not initialized.
         */
        initialize();

        String uriString = serviceURI.toASCIIString();
        deleteUsernameAndPasswordForService(uriString);
    }

    /**
     * Delete a username and password pair for the given service URL string from
     * the Keystore.
     * 
     * @deprecated Use
     *             {@link #deleteUsernameAndPasswordForService(URI serviceURI)}
     *             instead.
     */
    @Deprecated
    public void deleteUsernameAndPasswordForService(String serviceURL) throws CMException {
        /*
         * Need to make sure we are initialized before we do anything else, as
         * the Credential Manager can be created but not initialized.
         */
        initialize();

        synchronized (keystore) {
            deleteEntry(KEYSTORE, "password#" + serviceURL);
            saveKeystore(KEYSTORE);
            multiCaster.notify(new KeystoreChangedEvent(KEYSTORE));
        }
    }

    /**
     * Insert a new key entry containing private key and the corresponding
     * public key certificate chain in the Keystore.
     * 
     * An alias used to identify the keypair entry is constructed as:
     * "keypair#"<CERT_SUBJECT_COMMON_NAME>"#"<CERT_ISSUER_COMMON_NAME>"#"<
     * CERT_SERIAL_NUMBER>
     * 
     * @return the alias under which this key entry was saved in the Keystore
     */
    @Override
    public String addKeyPair(Key privateKey, Certificate[] certs) throws CMException {
        /*
         * Need to make sure we are initialized before we do anything else, as
         * the Credential Manager can be created but not initialized.
         */
        initialize();

        String alias = null;

        synchronized (keystore) {
            // Create an alias for the new key pair entry in the Keystore as
            // "keypair#"<CERT_SUBJECT_COMMON_NAME>"#"<CERT_ISSUER_COMMON_NAME>"#"<CERT_SERIAL_NUMBER>
            String ownerDN = ((X509Certificate) certs[0]).getSubjectX500Principal().getName(RFC2253);
            ParsedDistinguishedName parsedDN = dnParser.parseDN(ownerDN);
            String ownerCN = parsedDN.getCN(); // owner's common name

            // Get the hexadecimal representation of the certificate's serial
            // number
            String serialNumber = new BigInteger(1, ((X509Certificate) certs[0]).getSerialNumber().toByteArray())
                    .toString(16).toUpperCase();

            String issuerDN = ((X509Certificate) certs[0]).getIssuerX500Principal().getName(RFC2253);
            parsedDN = dnParser.parseDN(issuerDN);
            String issuerCN = parsedDN.getCN(); // issuer's common name

            alias = "keypair#" + ownerCN + "#" + issuerCN + "#" + serialNumber;

            try {
                keystore.setKeyEntry(alias, privateKey, masterPassword.toCharArray(), certs);
                saveKeystore(KEYSTORE);
                multiCaster.notify(new KeystoreChangedEvent(KEYSTORE));

                /*
                 * This is now done from the KeystoresChangedObserver's notify
                 * method. Update the default SSLSocketFactory used by the
                 * HttpsURLConnectionS
                 */
                // HttpsURLConnection.setDefaultSSLSocketFactory(createTavernaSSLSocketFactory());
                logger.debug("updating SSLSocketFactory after inserting a key pair");
            } catch (CMException ex) {
                throw ex;
            } catch (Exception ex) {
                throw new CMException("failed to insert " + "the key pair entry in the Keystore", ex);
            }
        }
        return alias;
    }

    /**
     * Checks if the Keystore contains the given key pair entry (private key and
     * its corresponding public key certificate chain).
     */
    @Override
    public boolean hasKeyPair(Key privateKey, Certificate[] certs) throws CMException {
        // Create an alias for the new key pair entry in the Keystore as
        // "keypair#"<CERT_SUBJECT_COMMON_NAME>"#"<CERT_ISSUER_COMMON_NAME>"#"<CERT_SERIAL_NUMBER>

        String alias = createKeyPairAlias(privateKey, certs);
        return hasEntryWithAlias(KEYSTORE, alias);
    }

    /**
     * Delete a key pair entry from the Keystore given its alias.
     */
    @Override
    public void deleteKeyPair(String alias) throws CMException {
        /*
         * Need to make sure we are initialized before we do anything else, as
         * the Credential Manager can be created but not initialized.
         */
        initialize();

        synchronized (keystore) {
            deleteEntry(KEYSTORE, alias);
            saveKeystore(KEYSTORE);
            multiCaster.notify(new KeystoreChangedEvent(KEYSTORE));

            /*
             * This is now done from the KeyManager's nad TrustManager's notify
             * methods. Update the default SSLSocketFactory used by the
             * HttpsURLConnectionS
             */
            // HttpsURLConnection.setDefaultSSLSocketFactory(createTavernaSSLSocketFactory());

            logger.info("updating SSLSocketFactory after deleting a keypair");
        }
    }

    /**
     * Delete a key pair entry from the Keystore given its private and public
     * key parts.
     */
    @Override
    public void deleteKeyPair(Key privateKey, Certificate[] certs) throws CMException {
        deleteKeyPair(createKeyPairAlias(privateKey, certs));
    }

    /**
     * Export a key entry containing private key and public key certificate
     * chain from the Keystore to a PKCS #12 file.
     */
    @Override
    public void exportKeyPair(String alias, File exportFile, String pkcs12Password) throws CMException {
        /*
         * Need to make sure we are initialized before we do anything else, as
         * the Credential Manager can be created but not initialized.
         */
        initialize();

        synchronized (keystore) {
            // Export the key pair
            try {
                // Get the private key for the alias
                PrivateKey privateKey = (PrivateKey) keystore.getKey(alias, masterPassword.toCharArray());

                // Get the related public key's certificate chain
                Certificate[] certChain = getKeyPairsCertificateChain(alias);

                // Create a new PKCS #12 keystore
                KeyStore newPkcs12 = KeyStore.getInstance("PKCS12", "BC");
                newPkcs12.load(null, null);

                // Place the private key and certificate chain into the PKCS #12
                // keystore. Construct a new alias as
                // "<SUBJECT_COMMON_NAME>'s <ISSUER_ORGANISATION> ID"

                String sDN = ((X509Certificate) certChain[0]).getSubjectX500Principal().getName(RFC2253);

                ParsedDistinguishedName parsedDN = dnParser.parseDN(sDN);
                String sCN = parsedDN.getCN();

                String iDN = ((X509Certificate) certChain[0]).getIssuerX500Principal().getName(RFC2253);
                parsedDN = dnParser.parseDN(iDN);
                String iCN = parsedDN.getCN();

                String pkcs12Alias = sCN + "'s " + iCN + " ID";
                newPkcs12.setKeyEntry(pkcs12Alias, privateKey, new char[0], certChain);

                // Store the new PKCS #12 keystore on the disk
                try (FileOutputStream fos = new FileOutputStream(exportFile)) {
                    newPkcs12.store(fos, pkcs12Password.toCharArray());
                }
            } catch (Exception ex) {
                String exMessage = "Failed to export the key pair from the Keystore";
                logger.error(exMessage, ex);
                throw new CMException(exMessage, ex);
            }
        }
    }

    /**
     * Get certificate entry from the Keystore or Truststore. If the given alias
     * name identifies a trusted certificate entry, the certificate associated
     * with that entry is returned from the Truststore. If the given alias name
     * identifies a key pair entry, the first element of the certificate chain
     * of that entry is returned from the Keystore.
     */
    @Override
    public Certificate getCertificate(KeystoreType ksType, String alias) throws CMException {
        /*
         * Need to make sure we are initialized before we do anything else, as
         * the Credential Manager can be created but not initialized.
         */
        initialize();

        try {
            switch (ksType) {
            case KEYSTORE:
                synchronized (keystore) {
                    return keystore.getCertificate(alias);
                }
            case TRUSTSTORE:
                synchronized (truststore) {
                    return truststore.getCertificate(alias);
                }
            default:
                return null;
            }
        } catch (Exception ex) {
            String exMessage = "Failed to fetch certificate from the " + ksType;
            logger.error(exMessage, ex);
            throw new CMException(exMessage, ex);
        }
    }

    /**
     * Get certificate chain for the key pair entry from the Keystore. This
     * method works for the Keystore only as the Truststore does not contain key
     * pair entries, but trusted certificate entries only.
     */
    @Override
    public Certificate[] getKeyPairsCertificateChain(String alias) throws CMException {
        /*
         * Need to make sure we are initialized before we do anything else, as
         * the Credential Manager can be created but not initialized.
         */
        initialize();

        try {
            synchronized (keystore) {
                return keystore.getCertificateChain(alias);
            }
        } catch (Exception ex) {
            String exMessage = "Failed to fetch certificate chain for the keypair from the Keystore";
            logger.error(exMessage, ex);
            throw new CMException(exMessage, ex);
        }
    }

    /**
     * Get the private key part of a key pair entry from the Keystore given its
     * alias.
     * <p>
     * This method works for the Keystore only as the Truststore does not
     * contain key pair entries, but trusted certificate entries only.
     * 
     * @throws CMException
     */
    @Override
    public Key getKeyPairsPrivateKey(String alias) throws CMException {
        /*
         * Need to make sure we are initialized before we do anything else, as
         * the Credential Manager can be created but not initialized.
         */
        initialize();

        try {
            synchronized (keystore) {
                return keystore.getKey(alias, masterPassword.toCharArray());
            }
        } catch (Exception ex) {
            String exMessage = "Failed to fetch private key for the keypair from the Keystore";
            logger.error(exMessage, ex);
            throw new CMException(exMessage, ex);
        }
    }

    /**
     * Insert a trusted certificate entry in the Truststore with an alias
     * constructed as:
     * 
     * "trustedcert#<CERT_SUBJECT_COMMON_NAME>"#"<CERT_ISSUER_COMMON_NAME>"#
     * "<CERT_SERIAL_NUMBER>
     * 
     * @return the alias under which this trusted certificate entry was saved in
     *         the Keystore
     */
    @Override
    public String addTrustedCertificate(X509Certificate cert) throws CMException {
        /*
         * Need to make sure we are initialized before we do anything else, as
         * the Credential Manager can be created but not initialized.
         */
        initialize();

        String alias = null;

        synchronized (truststore) {
            // Create an alias for the new trusted certificate entry in the
            // Truststore as
            // "trustedcert#"<CERT_SUBJECT_COMMON_NAME>"#"<CERT_ISSUER_COMMON_NAME>"#"<CERT_SERIAL_NUMBER>
            alias = createTrustedCertificateAlias(cert);
            try {
                truststore.setCertificateEntry(alias, cert);
                saveKeystore(TRUSTSTORE);
                multiCaster.notify(new KeystoreChangedEvent(TRUSTSTORE));

                /*
                 * This is now done from the KeystoresChangedObserver's notify
                 * method. Update the default SSLSocketFactory used by the
                 * HttpsURLConnectionS
                 */
                // HttpsURLConnection.setDefaultSSLSocketFactory(createTavernaSSLSocketFactory());

                logger.debug("Updating SSLSocketFactory after inserting a trusted certificate");
            } catch (Exception ex) {
                throw new CMException("failed to insert trusted certificate entry in the Truststore", ex);
            }
        }

        return alias;
    }

    /**
     * Create a Keystore alias that would be used for adding the given key pair
     * (private and public key) entry to the Keystore. The alias is cretaed as
     * "keypair#"
     * &lt;CERT_SUBJECT_COMMON_NAME&gt;"#"&lt;CERT_ISSUER_COMMON_NAME&gt
     * ;"#"&lt;CERT_SERIAL_NUMBER&gt;
     * 
     * @param privateKey
     *            private key
     * @param certs
     *            public key's certificate chain
     * @return
     */
    @Override
    public String createKeyPairAlias(Key privateKey, Certificate certs[]) {
        String ownerDN = ((X509Certificate) certs[0]).getSubjectX500Principal().getName(RFC2253);
        ParsedDistinguishedName parsedDN = dnParser.parseDN(ownerDN);
        String ownerCN = parsedDN.getCN(); // owner's common name

        /*
         * Get the hexadecimal representation of the certificate's serial number
         */
        String serialNumber = new BigInteger(1, ((X509Certificate) certs[0]).getSerialNumber().toByteArray())
                .toString(16).toUpperCase();

        String issuerDN = ((X509Certificate) certs[0]).getIssuerX500Principal().getName(RFC2253);
        parsedDN = dnParser.parseDN(issuerDN);
        String issuerCN = parsedDN.getCN(); // issuer's common name

        String alias = "keypair#" + ownerCN + "#" + issuerCN + "#" + serialNumber;
        return alias;
    }

    /**
     * Create a Truststore alias that would be used for adding the given trusted
     * X509 certificate to the Truststore. The alias is cretaed as
     * "trustedcert#"<CERT_SUBJECT_COMMON_NAME>"#"<CERT_ISSUER_COMMON_NAME>"#"<
     * CERT_SERIAL_NUMBER>
     * 
     * @param cert
     *            certificate to generate the alias for
     * @return the alias for the given certificate
     */
    @Override
    public String createTrustedCertificateAlias(X509Certificate cert) {
        String ownerDN = cert.getSubjectX500Principal().getName(RFC2253);
        ParsedDistinguishedName parsedDN = dnParser.parseDN(ownerDN);
        String owner;
        String ownerCN = parsedDN.getCN(); // owner's common name
        String ownerOU = parsedDN.getOU();
        String ownerO = parsedDN.getO();
        if (!ownerCN.equals("none")) { // try owner's CN first
            owner = ownerCN;
        } // try owner's OU
        else if (!ownerOU.equals("none")) {
            owner = ownerOU;
        } else if (!ownerO.equals("none")) { // finally use owner's Organisation
            owner = ownerO;
        } else {
            owner = "<Not Part of Certificate>";
        }

        /* Get the hexadecimal representation of the certificate's serial number */
        String serialNumber = new BigInteger(1, cert.getSerialNumber().toByteArray()).toString(16).toUpperCase();

        String issuerDN = cert.getIssuerX500Principal().getName(RFC2253);
        parsedDN = dnParser.parseDN(issuerDN);
        String issuer;
        String issuerCN = parsedDN.getCN(); // issuer's common name
        String issuerOU = parsedDN.getOU();
        String issuerO = parsedDN.getO();
        if (!issuerCN.equals("none")) { // try issuer's CN first
            issuer = issuerCN;
        } // try issuer's OU
        else if (!issuerOU.equals("none")) {
            issuer = issuerOU;
        } else if (!issuerO.equals("none")) { // finally use issuer's
            // Organisation
            issuer = issuerO;
        } else {
            issuer = "<Not Part of Certificate>";
        }

        String alias = "trustedcert#" + owner + "#" + issuer + "#" + serialNumber;
        return alias;
    }

    /**
     * Checks if the Truststore contains the given public key certificate.
     */
    @Override
    public boolean hasTrustedCertificate(Certificate cert) throws CMException {
        // Create an alias for the new trusted certificate entry in the
        // Truststore as
        // "trustedcert#"<CERT_SUBJECT_COMMON_NAME>"#"<CERT_ISSUER_COMMON_NAME>"#"<CERT_SERIAL_NUMBER>
        String alias = createTrustedCertificateAlias((X509Certificate) cert);
        return hasEntryWithAlias(TRUSTSTORE, alias);
    }

    /**
     * Delete a trusted certificate entry from the Truststore given its alias.
     */
    @Override
    public void deleteTrustedCertificate(String alias) throws CMException {
        /*
         * Need to make sure we are initialized before we do anything else, as
         * the Credential Manager can be created but not initialized.
         */
        initialize();

        synchronized (truststore) {
            deleteEntry(TRUSTSTORE, alias);
            saveKeystore(TRUSTSTORE);
            multiCaster.notify(new KeystoreChangedEvent(TRUSTSTORE));

            /*
             * This is now done from the KeyManager's nad TrustManager's notify
             * methods Update the default SSLSocketFactory used by the
             * HttpsURLConnectionS
             */
            // HttpsURLConnection.setDefaultSSLSocketFactory(createTavernaSSLSocketFactory());

            logger.info("Updating SSLSocketFactory " + "after deleting a trusted certificate");
        }
    }

    /**
     * Delete a trusted certificate entry from the Truststore given the
     * certificate.
     */
    @Override
    public void deleteTrustedCertificate(X509Certificate cert) throws CMException {
        String alias = createTrustedCertificateAlias(cert);
        deleteTrustedCertificate(alias);
    }

    /**
     * Check if the given alias identifies is a key entry in the Keystore.
     */
    @Override
    public boolean isKeyEntry(String alias) throws CMException {
        /*
         * Need to make sure we are initialized before we do anything else, as
         * the Credential Manager can be created but not initialized.
         */
        initialize();

        try {
            synchronized (keystore) {
                return keystore.isKeyEntry(alias);
            }
        } catch (Exception ex) {
            String exMessage = "failed to access the key entry in the Keystore";
            logger.error(exMessage, ex);
            throw new CMException(exMessage, ex);
        }
    }

    /**
     * Delete an entry from the Keystore or the Truststore.
     */
    private void deleteEntry(KeystoreType ksType, String alias) throws CMException {
        /*
         * Need to make sure we are initialized before we do anything else, as
         * the Credential Manager can be created but not initialized.
         */
        initialize();

        try {
            switch (ksType) {
            case KEYSTORE:
                synchronized (keystore) {
                    if (keystore.containsAlias(alias))
                        keystore.deleteEntry(alias);
                    return;
                }
            case TRUSTSTORE:
                synchronized (truststore) {
                    if (truststore.containsAlias(alias))
                        truststore.deleteEntry(alias);
                    return;
                }
            }
        } catch (Exception ex) {
            String exMessage = "failed to delete the entry with alias " + alias + " from the " + ksType;
            logger.error(exMessage, ex);
            throw new CMException(exMessage, ex);
        }
    }

    /**
     * Check if the Keystore/Truststore contains an entry with the given alias.
     */
    @Override
    public boolean hasEntryWithAlias(KeystoreType ksType, String alias) throws CMException {
        /*
         * Need to make sure we are initialized before we do anything else, as
         * the Credential Manager can be created but not initialized.
         */
        initialize();

        try {
            switch (ksType) {
            case KEYSTORE:
                synchronized (keystore) {
                    return keystore.containsAlias(alias);
                }
            case TRUSTSTORE:
                synchronized (truststore) {
                    return truststore.containsAlias(alias);
                }
            default:
                return false;
            }
        } catch (Exception ex) {
            String exMessage = "failed to access the " + ksType + " to check if an alias exists";
            logger.error(exMessage, ex);
            throw new CMException(exMessage, ex);
        }
    }

    /**
     * Get all the aliases from the Keystore/Truststore or null if there was
     * some error while accessing it.
     */
    @Override
    public ArrayList<String> getAliases(KeystoreType ksType) throws CMException {
        /*
         * Need to make sure we are initialized before we do anything else, as
         * the Credential Manager can be created but not initialized.
         */
        initialize();

        try {
            switch (ksType) {
            case KEYSTORE:
                synchronized (keystore) {
                    return Collections.list(keystore.aliases());
                }
            case TRUSTSTORE:
                synchronized (truststore) {
                    return Collections.list(truststore.aliases());
                }
            default:
                return null;
            }
        } catch (Exception ex) {
            String exMessage = "failed to access the " + ksType + " to get the aliases";
            logger.error(exMessage, ex);
            throw new CMException(exMessage, ex);
        }
    }

    /**
     * Get service URIs associated with all username/password pairs currently in
     * the Keystore.
     * 
     * @see #hasUsernamePasswordForService(URI)
     */
    @Override
    public List<URI> getServiceURIsForAllUsernameAndPasswordPairs() throws CMException {
        /*
         * Need to make sure we are initialized before we do anything else, as
         * the Credential Manager can be created but not initialized.
         */
        initialize();

        synchronized (keystore) {
            if (cachedServiceURIsList == null) {
                List<URI> serviceURIs = new ArrayList<>();
                for (String alias : getAliases(KEYSTORE)) {
                    /*
                     * We are only interested in username/password entries here.
                     * Alias for such entries is constructed as
                     * "password#"<SERVICE_URI> where SERVICE_URI is the service
                     * this username/password pair is to be used for.
                     */
                    if (!alias.startsWith("password#"))
                        continue;
                    String[] split = alias.split("#", 2);
                    if (split.length != 2) {
                        logger.warn("Invalid alias " + alias);
                        continue;
                    }
                    String uriStr = split[1];
                    URI uri = URI.create(uriStr);
                    serviceURIs.add(uri);
                }
                cachedServiceURIsList = serviceURIs;
            }
            return cachedServiceURIsList;
        }
    }

    /**
     * Load a PKCS12-type keystore from a file using the supplied password.
     */
    @Override
    public KeyStore loadPKCS12Keystore(File pkcs12File, String pkcs12Password) throws CMException {
        // Load the PKCS #12 keystore from the file
        try (InputStream input = new FileInputStream(pkcs12File)) {
            KeyStore pkcs12 = KeyStore.getInstance("PKCS12", "BC");
            pkcs12.load(input, pkcs12Password.toCharArray());
            return pkcs12;
        } catch (Exception ex) {
            String exMessage = "failed to open a PKCS12-type keystore";
            logger.error(exMessage, ex);
            throw new CMException(exMessage, ex);
        }
    }

    /**
     * Add an observer of the changes to the Keystore or Truststore.
     */
    @Override
    public void addObserver(Observer<KeystoreChangedEvent> observer) {
        multiCaster.addObserver(observer);
    }

    /**
     * Get all current observers of changes to the Keystore or Truststore.
     */
    @Override
    public List<Observer<KeystoreChangedEvent>> getObservers() {
        return multiCaster.getObservers();
    }

    /**
     * Remove an observer of the changes to the Keystore or Truststore.
     */
    @Override
    public void removeObserver(Observer<KeystoreChangedEvent> observer) {
        multiCaster.removeObserver(observer);
    }

    // /**
    // * Checks if Credential Manager has been initialised.
    // */
    // public boolean isInitialized() {
    // return isInitialized;
    // }

    // /**
    // * Check if Keystore/Truststore file already exists on disk.
    // */
    // private boolean exists(KeystoreType ksType) {
    //
    // if (ksType.equals(KEYSTORE))
    // return keystoreFile.exists();
    // else if (ksType.equals(TRUSTSTORE)) {
    // return truststoreFile.exists();
    // } else
    // return false;
    // }

    /**
     * Save the Keystore back to the file it was originally loaded from.
     */
    private void saveKeystore(KeystoreType ksType) throws CMException {
        /*
         * Need to make sure we are initialized before we do anything else, as
         * the Credential Manager can be created but not initialized.
         */
        initialize();

        try {
            switch (ksType) {
            case KEYSTORE:
                synchronized (keystore) {
                    try (FileOutputStream fos = new FileOutputStream(keystoreFile)) {
                        keystore.store(fos, masterPassword.toCharArray());
                        return;
                    }
                }
            case TRUSTSTORE:
                synchronized (truststore) {
                    try (FileOutputStream fos = new FileOutputStream(truststoreFile)) {
                        // Hard-coded trust store password
                        truststore.store(fos, masterPassword.toCharArray());
                        return;
                    }
                }
            }
        } catch (Exception ex) {
            String exMessage = "failed to save the " + ksType;
            logger.error(exMessage, ex);
            throw new CMException(exMessage, ex);
        }
    }

    /**
     * Checks if Keystore's master password is the same as the one provided.
     */
    @Override
    public boolean confirmMasterPassword(String password) throws CMException {
        /*
         * Need to make sure we are initialized before we do anything else, as
         * the Credential Manager can be created but not initialized.
         */
        initialize();

        return (masterPassword != null) && masterPassword.equals(password);
    }

    private static final boolean REALLY_DISABLED = false;

    /**
     * Change the Keystore and the Truststore's master password to the one
     * provided. The Keystore and Truststore both use the same password.
     */
    @Override
    public void changeMasterPassword(String newMasterPassword) throws CMException {
        /*
         * Need to make sure we are initialized before we do anything else, as
         * the Credential Manager can be created but not initialized.
         */
        initialize();

        String oldMasterPassword = masterPassword;
        KeyStore oldKeystore = keystore;
        KeyStore oldTruststore = truststore;

        try {
            synchronized (keystore) {
                // Create a new keystore and copy all items from the current
                // one, encrypting them with the new password
                KeyStore newKeystore = null;
                try {
                    // Try to create Taverna's Keystore as Bouncy Castle
                    // UBER-type keystore.
                    newKeystore = KeyStore.getInstance("UBER", "BC");
                } catch (Exception ex) {
                    // The requested keystore type is not available from
                    // security providers.
                    String exMessage = "Failed to instantiate a new Bouncy Castle Keystore when changing master password.";
                    throw new CMException(exMessage, ex);
                }
                try {
                    // Initialize a new empty keystore
                    newKeystore.load(null, null);
                } catch (Exception ex) {
                    String exMessage = "Failed to create a new empty Keystore to copy over the entries from the current one.";
                    throw new CMException(exMessage, ex);
                }

                Enumeration<String> aliases = keystore.aliases();
                while (aliases.hasMoreElements()) {
                    String alias = aliases.nextElement();
                    if (REALLY_DISABLED) {
                        if (alias.startsWith("password#")) { // a password entry
                            SecretKeySpec passwordKey = (((SecretKeySpec) keystore.getKey(alias,
                                    masterPassword.toCharArray())));
                            newKeystore.setKeyEntry(alias, passwordKey, newMasterPassword.toCharArray(), null);
                        } else if (alias.startsWith("keypair#")) { // a private key entry
                            // Get the private key for the alias
                            PrivateKey privateKey = (PrivateKey) keystore.getKey(alias,
                                    masterPassword.toCharArray());
                            // Get the related public key's certificate chain
                            Certificate[] certChain = keystore.getCertificateChain(alias);
                            newKeystore.setKeyEntry(alias, privateKey, newMasterPassword.toCharArray(), certChain);
                        }
                    }
                    /*
                     * Do all entries at once, not reason to separate password &
                     * key pair entries
                     */
                    newKeystore.setEntry(alias,
                            keystore.getEntry(alias, new KeyStore.PasswordProtection(masterPassword.toCharArray())),
                            new KeyStore.PasswordProtection(newMasterPassword.toCharArray()));
                }
                try (FileOutputStream fos = new FileOutputStream(keystoreFile)) {
                    newKeystore.store(fos, newMasterPassword.toCharArray());
                }
                keystore = newKeystore;
            }

            /*
             * Truststore does not need to be re-encrypeted item by item as
             * entries there are not encrypted, just the whole truststore
             */
            synchronized (truststore) {
                try (FileOutputStream fos = new FileOutputStream(truststoreFile)) {
                    truststore.store(fos, newMasterPassword.toCharArray());
                }
            }

            // Set the new master password as well
            masterPassword = newMasterPassword;
        } catch (Exception ex) {
            // rollback
            keystore = oldKeystore;
            truststore = oldTruststore;
            masterPassword = oldMasterPassword;
            saveKeystore(KEYSTORE);
            saveKeystore(TRUSTSTORE);

            String exMessage = "Failed to change maaster password - reverting to the old one";
            logger.error(exMessage, ex);
            throw new CMException(exMessage);
        }
    }

    @Override
    public void initializeSSL() throws CMException {
        /*
         * We use the lazy initialization of Credential Manager from inside the
         * Taverna's SSLSocketFactory (i.e. KeyManager's and TrustManager's
         * init() methods) when it is actually needed so do not initialize it
         * here. These init() methods will not be called unledd a SSL connection
         * is attempted somewhere from Taverna and it is inside them that we
         * actually call the initialize() method on Credential Manager (and not
         * from the Credential Manager's constructor - hence lazy).
         * 
         * Create Taverna's SSLSocketFactory and set the SSL socket factory from
         * HttpsURLConnectionS to use it
         */
        if (tavernaSSLSocketFactory == null)
            HttpsURLConnection.setDefaultSSLSocketFactory(createSSLSocketFactory());
    }

    /**
     * Creates SSLSocketFactory based on Credential Manager's Keystore and
     * Truststore but only initalizes Credential Manager when one of the methods
     * needed for creating an HTTPS connection is invoked.
     */
    private SSLSocketFactory createSSLSocketFactory() throws CMException {
        SSLContext sc = null;
        try {
            sc = SSLContext.getInstance("SSLv3");
        } catch (NoSuchAlgorithmException e1) {
            throw new CMException("Failed to create SSL socket factory: "
                    + "the SSL algorithm was not available from any crypto provider", e1);
        }

        KeyManager[] keyManagers = null;
        try {
            // Create our own KeyManager with (possibly not yet initialised)
            // Taverna's Keystore
            keyManagers = new KeyManager[] { new TavernaKeyManager() };
        } catch (Exception e) {
            throw new CMException("Failed to create SSL socket factory: " + "could not initiate SSL Key Manager",
                    e);
        }

        TrustManager[] trustManagers = null;
        try {
            // Create our own TrustManager with (possibly not yet initialised)
            // Taverna's Truststore
            trustManagers = new TrustManager[] { new TavernaTrustManager() };
        } catch (Exception e) {
            throw new CMException("Failed to create SSL socket factory: " + "could not initiate SSL Trust Manager",
                    e);
        }

        try {
            sc.init(keyManagers, trustManagers, new SecureRandom());
        } catch (KeyManagementException kmex) {
            throw new CMException("Failed to initiate the SSL socet factory", kmex);
        }

        /*
         * Set the default SSLContext to be used for subsequent SSL sockets from
         * Java
         */
        SSLContext.setDefault(sc);

        /*
         * Create SSL socket to be used for HTTPS connections from the JVM e.g.
         * REST activity that uses Apache HTTP client library
         */
        tavernaSSLSocketFactory = sc.getSocketFactory();

        return tavernaSSLSocketFactory;
    }

    @Override
    public SSLSocketFactory getTavernaSSLSocketFactory() throws CMException {
        if (tavernaSSLSocketFactory == null)
            return createSSLSocketFactory();
        return tavernaSSLSocketFactory;
    }

    @Override
    public Authenticator getAuthenticator() {
        return new CredentialManagerAuthenticator(this);
    }

    /**
     * Taverna's Key Manager is a customised X509KeyManager that initilises
     * Credential Manager only if certain methods on it are invoked, i.e. if
     * acces to Keystore is actually needed to authenticate the user.
     */
    private class TavernaKeyManager extends X509ExtendedKeyManager {
        /**
         * The X509KeyManager as returned by the SunX509 provider, initialised
         * with the Keystore.
         */
        private X509KeyManager sunJSSEX509KeyManager = null;

        /**
         * Lazy initialisation - unless we are actually asked to do some SSL
         * stuff - do not initialise Credential Manager as it will most probably
         * result in popping the master password window, which we want to avoid
         * early on while Taverna is starting, unless we need to contact a
         * secure service early, e.g. to populate the Service Panel.
         */
        private void init() throws Exception {
            logger.debug("inside TavernaKeyManager.init()");

            // Create a "default" JSSE X509KeyManager.
            KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509", "SunJSSE");

            if (!isInitialized)
                // If we have not initialised the Credential Manager so far -
                // now is the time to do it
                try {
                    logger.debug("Credential Manager has not been instantiated yet");
                    initialize();
                    logger.debug("Credential Manager instantiated");
                } catch (CMException cme) {
                    throw new Exception("Could not initialize Taverna's KeyManager for SSLSocketFactory:"
                            + " failed to initialise Credential Manager.");
                }

            // Keystore and master password should not be null as we have just
            // initalised Credential Manager
            synchronized (keystore) {
                logger.debug("Reinitialising the KeyManager.");

                kmf.init(keystore, masterPassword.toCharArray());

                /*
                 * Iterate over the KeyManagers, look for an instance of
                 * X509KeyManager. If found, use that as our "default" key
                 * manager.
                 */
                for (KeyManager km : kmf.getKeyManagers())
                    if (km instanceof X509KeyManager) {
                        sunJSSEX509KeyManager = (X509KeyManager) km;
                        return;
                    }

                // X509KeyManager not found - we have to fail the constructor.
                throw new Exception("Could not initialize Taverna's KeyManager for SSLSocketFactory:"
                        + " could not find a SunJSSE X509 KeyManager.");
            }
        }

        @Override
        public String chooseClientAlias(String[] keyType, Principal[] issuers, Socket socket) {
            logger.info("inside chooseClientAlias()");

            // We have postponed initialization until we are actually asked to
            // do something
            try {
                if (sunJSSEX509KeyManager == null)
                    init();
            } catch (Exception e) {
                logger.error(e);
                return null;
            }
            // Delegate the decision to the default key manager
            return sunJSSEX509KeyManager.chooseClientAlias(keyType, issuers, socket);
        }

        @Override
        public String chooseServerAlias(String keyType, Principal[] issuers, Socket socket) {
            // TODO Auto-generated method stub
            return null;
        }

        @Override
        public X509Certificate[] getCertificateChain(String alias) {
            logger.debug("inside getCertificateChain()");
            // We have postponed initialisation until we are actually asked to
            // do something
            try {
                if (sunJSSEX509KeyManager == null)
                    init();
            } catch (Exception e) {
                logger.error(e);
                return null;
            }
            // Delegate the decision to the default key manager
            return sunJSSEX509KeyManager.getCertificateChain(alias);
        }

        @Override
        public String[] getClientAliases(String keyType, Principal[] issuers) {
            logger.debug("inside getClientAliases()");
            // We have postponed initialisation until we are actually asked to
            // do something
            try {
                if (sunJSSEX509KeyManager == null)
                    init();
            } catch (Exception e) {
                logger.error(e);
                return null;
            }
            // Delegate the decision to the default key manager
            return sunJSSEX509KeyManager.getClientAliases(keyType, issuers);
        }

        @Override
        public PrivateKey getPrivateKey(String alias) {
            logger.debug("inside getPrivateKey()");
            // We have postponed initialisation until we are actually asked to
            // do something
            try {
                if (sunJSSEX509KeyManager == null)
                    init();
            } catch (Exception e) {
                logger.error(e);
                return null;
            }
            // Delegate the decision to the default key manager
            return sunJSSEX509KeyManager.getPrivateKey(alias);
        }

        @Override
        public String[] getServerAliases(String keyType, Principal[] issuers) {
            // TODO Auto-generated method stub
            return null;
        }
    }

    /**
     * Taverna's Trust Manager is a customised X509TrustManager that initilizes
     * Credential Manager only if certain methods on it are invoked, i.e. if
     * acces to Truststore is actually needed to authenticate the remote
     * service.
     */
    private class TavernaTrustManager implements X509TrustManager {
        /**
         * The default X509TrustManager as returned by SunX509 provider,
         * initialised with the Truststore. We delegate decisions to it, and
         * fall back to ask the user if the default X509TrustManager does not
         * trust the server's certificate.
         */
        private X509TrustManager sunJSSEX509TrustManager = null;

        /**
         * Lazy initialization - unless we are actually asked to do some SSL
         * stuff - do not initialise Credential Manager as it will most probably
         * result in popping the master password window, which we want to avoid
         * early on while Taverna is starting, unless we need to contact a
         * secure service early, e.g. to populate Service Panel.
         */
        private void init() throws Exception {
            logger.debug("inside TavernaTrustManager.init()");

            // Create a "default" JSSE X509TrustManager.
            TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509", "SunJSSE");

            if (!isInitialized) {
                logger.debug("inside TavernaTrustManager.init() - "
                        + "Credential Manager has not been instantiated yet.");
                // If we have not initialised the Credential Manager so far -
                // now is the time to do it
                try {
                    initialize();
                    logger.debug("inside Taverna TrustManager.init() - " + "Credential Manager instantiated.");
                } catch (CMException cme) {
                    throw new Exception("Could not initialize Taverna's TrustManager for SSLSocketFactory: "
                            + "failed to initialise Credential Manager.");
                }
            }

            // Truststore should not be null as we have just initalised
            // Credential Manager above
            synchronized (truststore) {
                logger.debug("inside TavernaTrustManager.init() - " + "Reinitialising the TrustManager.");
                SSLSocketFactory.getDefault();
                tmf.init(truststore);

                /*
                 * Iterate over the TrustManagers, look for an instance of
                 * X509TrustManager. If found, use that as our "default" trust
                 * manager.
                 */
                for (TrustManager tm : tmf.getTrustManagers())
                    if (tm instanceof X509TrustManager) {
                        sunJSSEX509TrustManager = (X509TrustManager) tm;
                        return;
                    }

                // X509TrustManager not found - we have to fail the constructor.
                throw new Exception("Could not initialize Taverna's TrustManager for SSLSocketFactory.");
            }
        }

        /*
         * This method is called on the server-side for establishing trust with
         * a client.
         */
        @Override
        public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
        }

        /*
         * This method is called on the client-side for establishing trust with
         * a server. We first try to delegate to the default trust manager that
         * uses Taverna's Truststore. If that falls through we ask the user if
         * they want to trust the certificate.
         */
        @Override
        public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
            // We have postponed initialisation until we are actually asked to
            // do something
            try {
                if (sunJSSEX509TrustManager == null)
                    init();
            } catch (Exception e) {
                logger.error(e);
                throw new CertificateException(e);
            }
            // Delegate the decision to the default trust manager
            try {
                sunJSSEX509TrustManager.checkServerTrusted(chain, authType);
            } catch (CertificateException excep) {
                // Pop up a dialog box asking whether to trust the server's
                // certificate chain.
                if (!shouldTrust(chain))
                    throw excep;
            }
        }

        @Override
        public X509Certificate[] getAcceptedIssuers() {
            // We have postponed initialisation until we are actually asked to
            // do something
            try {
                if (sunJSSEX509TrustManager == null)
                    init();
            } catch (Exception e) {
                logger.error(e);
                return null;
            }
            return sunJSSEX509TrustManager.getAcceptedIssuers();
        }
    }

    /**
     * Checks if a service is trusted and if not - asks user if they want to
     * trust it.
     */
    private boolean shouldTrust(final X509Certificate[] chain) throws IllegalArgumentException {
        if (chain == null || chain.length == 0)
            throw new IllegalArgumentException("At least one certificate needed in chain");

        /*
         * If the certificate already exists in the truststore, it is implicitly
         * trusted. This will try to avoid prompting user twice as
         * checkServerTrusted() method gets called twice.
         * 
         * Well, this is not working - checkServerTrusted() is still called
         * twice.
         */
        String alias = createTrustedCertificateAlias(chain[0]);
        try {
            if (truststore.containsAlias(alias))
                return true;
        } catch (KeyStoreException e) {
            // Ignore
        }

        String name = chain[0].getSubjectX500Principal().getName();
        for (TrustConfirmationProvider trustConfirmationProvider : trustConfirmationProviders) {
            Boolean trustConfirmation = trustConfirmationProvider.shouldTrustCertificate(chain);
            if (trustConfirmation == null)
                // SPI can't say yes or no, try next one
                continue;

            try {
                if (trustConfirmation) {
                    // initialize(); // init the Credential Manager if needed
                    addTrustedCertificate((X509Certificate) chain[0]);
                    // this will initialize Cred. Manager
                    logger.info("Stored trusted certificate " + name);
                }
            } catch (CMException ex) {
                logger.error("Credential Manager failed to " + "save trusted certificate " + name, ex);
            }
            if (logger.isDebugEnabled()) {
                if (trustConfirmation) {
                    logger.debug("Trusting " + name + " according to " + trustConfirmationProvider);
                } else {
                    logger.debug("Not trusting " + name + " according to " + trustConfirmationProvider);
                }
            }
            return trustConfirmation.booleanValue();
        }
        logger.warn("No TrustConfirmationProvider instances could confirm or deny the trust in " + name);
        // None of the trust confirmation providers (if there were any at all)
        // could confirm
        return false;
    }

    /**
     * Normalize an URI for insertion as the basis for path-recursive lookups,
     * ie. strip query and filename. For example:
     * 
     * <pre>
     * URI uri = URI.create("http://foo.org/dir1/dirX/../dir2/filename.html?q=x")
     * System.out.println(CredentialManager.normalizeServiceURI(uri));
     * >>> http://foo.org/dir1/dir2/
     * uri = URI.create("http://foo.org/dir1/dir2/");
     * System.out.println(CredentialManager.normalizeServiceURI(uri));
     * >>> http://foo.org/dir1/dir2/
     * </pre>
     * <p>
     * Note that #fragments are preserved, as these are used to indicate HTTP
     * Basic Auth realms
     * 
     * @param serviceURI
     *            URI for a service that is to be normalized
     * @return A normalized URI without query, userinfo or filename, ie. where
     *         uri.resolve(".").equals(uri).
     */
    public URI normalizeServiceURI(URI serviceURI) {
        try {
            // Strip userinfo, keep fragment
            URI normalized = dnParser.setUserInfoForURI(serviceURI, null).normalize();
            return dnParser.setFragmentForURI(normalized.resolve("."), serviceURI.getFragment());
        } catch (URISyntaxException ex) {
            return serviceURI;
        }
    }

    /**
     * Reset the JVMs cache for authentication like HTTP Basic Auth.
     * <p>
     * Note that this method uses undocumented calls to
     * <code>sun.net.www.protocol.http.AuthCacheValue</code> which might not be
     * valid in virtual machines other than Sun Java 6. If these calls fail,
     * this method will log the error and return <code>false</code>.
     * 
     * @return <code>true</code> if the JVMs cache could be reset, or
     *         <code>false</code> otherwise.
     */
    @Override
    public boolean resetAuthCache() {
        // Sun should expose an official API to do this
        try {
            Class<?> AuthCacheValue = Class.forName("sun.net.www.protocol.http.AuthCacheValue");
            Class<?> AuthCacheImpl = Class.forName("sun.net.www.protocol.http.AuthCacheImpl");
            Class<?> AuthCache = Class.forName("sun.net.www.protocol.http.AuthCache");
            Method setAuthCache = AuthCacheValue.getMethod("setAuthCache", AuthCache);
            setAuthCache.invoke(null, AuthCacheImpl.newInstance());
            return true;
        } catch (Exception ex) {
            logger.warn("Could not reset authcache, non-Sun JVM or internal Sun classes changed", ex);
            return false;
        }
    }

    /**
     * Checks if the Keystore contains a username and password for the given
     * service URI.
     */
    @Override
    public boolean hasUsernamePasswordForService(URI serviceURI) throws CMException {
        Map<URI, URI> mappedServiceURIs = getFragmentMappedURIsForAllUsernameAndPasswordPairs();
        for (URI possible : getPossibleServiceURIsToLookup(serviceURI, true))
            if (mappedServiceURIs.containsKey(possible))
                return true;
        return false;
    }

    private void loadDefaultSecurityFiles() {
        if (credentialManagerDirectory == null)
            credentialManagerDirectory = dnParser.getCredentialManagerDefaultDirectory(applicationConfiguration);
        if (keystoreFile == null)
            keystoreFile = new File(credentialManagerDirectory, KEYSTORE_FILE_NAME);
        if (truststoreFile == null)
            truststoreFile = new File(credentialManagerDirectory, TRUSTSTORE_FILE_NAME);
    }

    /**
     * Set the directory where Credential Manager's Keystore and Truststore
     * files will be read from. If this method is not used, the directory will
     * default to <TAVERNA_HOME>/security somewhere in user's home directory.
     * 
     * If you want to use this method to change the location of Credential
     * Manager's configuration directory then make sure you call it before any
     * other method on Credential Manager.
     * 
     * This was supposed to be done through OSGi services.
     * 
     * @param credentialManagerDirectory
     * @throws CMException
     */
    @Override
    public void setConfigurationDirectoryPath(File credentialManagerDirectory) throws CMException {
        if (credentialManagerDirectory == null)
            throw new CMException("Credential Manager's configuration directory cannot be null.");

        try {
            if (!credentialManagerDirectory.exists())
                credentialManagerDirectory.mkdir();
        } catch (Exception e) {
            throw new CMException("Failed to open Credential Manager's directory " + credentialManagerDirectory
                    + " to load the security files: " + e.getMessage(), e);
        }

        keystoreFile = new File(credentialManagerDirectory, KEYSTORE_FILE_NAME);
        truststoreFile = new File(credentialManagerDirectory, TRUSTSTORE_FILE_NAME);

        // Are we resetting the directory? Has stuff been initialized yet?
        // Then we need to reset everything else.
        if (isInitialized) {
            masterPassword = null;
            keystore = null;
            truststore = null;
            isInitialized = false;
        }
    }

    // private void loadSecurityFiles(String credentialManagerDirPath)
    // throws CMException {
    //
    // // If credentialManagerDirPath is null (e.g. user did not specify -cmdir
    // on the command line)
    // // - try with Taverna's default one
    // if (credentialManagerDirPath == null){
    // credentialManagerDirectory =
    // CMUtils.getCredentialManagerDefaultDirectory();
    // }
    //
    // if (credentialManagerDirectory == null) {
    // try {
    // credentialManagerDirectory = new File(credentialManagerDirPath);
    // } catch (Exception e) {
    // throw new CMException(
    // "Failed to open Credential Manager's directory to load the security files: "
    // + e.getMessage(),
    // e);
    // }
    // }
    // if (keystoreFile == null){
    // keystoreFile = new File(credentialManagerDirectory, KEYSTORE_FILE_NAME);
    // }
    // if (truststoreFile == null){
    // truststoreFile = new File(credentialManagerDirectory,
    // TRUSTSTORE_FILE_NAME);
    // }
    // }

    /**
     * Clear the cached service URIs that have username and password associated
     * with them. Basically we keep the list of all service URIs (and a map of
     * servce URIs to their URIs fragments to find the realm for HTTP AuthN)
     * that have a password entry in the Keystore.
     */
    public class ClearCachedServiceURIsObserver implements Observer<KeystoreChangedEvent> {
        @Override
        public void notify(Observable<KeystoreChangedEvent> sender, KeystoreChangedEvent message) throws Exception {
            /*
             * Need to make sure we are initialized before we do anything else,
             * as the Credential Manager can be created but not initialized.
             */
            initialize();

            /*
             * If Keystore has changed - possibly some password entries have
             * changed (could be key entries that have changed but we do not
             * know) - so empty the service URI caches just in case.
             */
            if (message.keystoreType.equals(KEYSTORE))
                synchronized (keystore) {
                    cachedServiceURIsMap = null;
                    cachedServiceURIsList = null;
                }
        }
    }

    /**
     * If any change to the Keystore or Truststore occurs - create the new
     * SSLSocketFactory and set the new default SSLContext which is initialised
     * with the updated Keystore and Truststore material
     */
    public class KeystoreChangedObserver implements Observer<KeystoreChangedEvent> {
        @Override
        public void notify(Observable<KeystoreChangedEvent> sender, KeystoreChangedEvent message) throws Exception {
            /*
             * Create the new SSLSocketFactory and set the default SSLContext
             * for HTTPS connetions in the JVM
             */
            HttpsURLConnection.setDefaultSSLSocketFactory(createSSLSocketFactory());
        }
    }

    /**
     * Set the master password providers for providing the master password to
     * encrypt/decrypt the Credential Maager's Keystore and Truststore.
     * <p>
     * This is done through the Spring DM.
     */
    public void setMasterPasswordProviders(List<MasterPasswordProvider> masterPasswordProviders) {
        this.masterPasswordProviders = masterPasswordProviders;
    }

    /**
     * Get the master password providers for providing the master password to
     * encrypt/decrypt the Credential Maager's Keystore and Truststore.
     * 
     * @return the master password providers for providing the master password
     *         to encrypt/decrypt the Credential Maager's Keystore and
     *         Truststore.
     * 
     */
    public List<MasterPasswordProvider> getMasterPasswordProviders() {
        return masterPasswordProviders;
    }

    /**
     * Set the Java truststore password providers for providing the password to
     * encrypt/decrypt the Java's default truststore.
     * <p>
     * This is done through the Spring DM.
     */
    public void setJavaTruststorePasswordProviders(
            List<JavaTruststorePasswordProvider> javaTruststorePasswordProvider) {
        this.javaTruststorePasswordProviders = javaTruststorePasswordProvider;
    }

    /**
     * Get the Java truststore password providers for providing the password to
     * encrypt/decrypt the Java's default truststore.
     * 
     * @return Java truststore providers for providing the password to
     *         encrypt/decrypt the Java's default truststore
     */
    public List<JavaTruststorePasswordProvider> getJavaTruststorePasswordProviders() {
        return javaTruststorePasswordProviders;
    }

    /**
     * Set the providers of username and passwords for services.
     * <p>
     * This is done through the Spring DM.
     */
    public void setServiceUsernameAndPasswordProviders(
            List<ServiceUsernameAndPasswordProvider> serviceUsernameAndPasswordProviders) {
        this.serviceUsernameAndPasswordProviders = serviceUsernameAndPasswordProviders;
    }

    /**
     * Get the providers of username and passwords for services.
     * 
     * @return the providers of username and passwords for services.
     * 
     */
    public List<ServiceUsernameAndPasswordProvider> getServiceUsernameAndPasswordProviders() {
        return serviceUsernameAndPasswordProviders;
    }

    /**
     * Set the providers of trust confirmation for HTTPS connections to external
     * services/sites.
     * <p>
     * This is done through the Spring DM.
     */
    public void setTrustConfirmationProviders(List<TrustConfirmationProvider> trustConfirmationProviders) {
        this.trustConfirmationProviders = trustConfirmationProviders;
    }

    /**
     * Get the providers of trust confirmation for HTTPS connections to external
     * services/sites
     * 
     * @return the providers of trust confirmation for HTTPS connections to
     *         external services/sites
     * 
     */
    public List<TrustConfirmationProvider> getTrustConfirmationProviders() {
        return trustConfirmationProviders;
    }

    /**
     * Sets the applicationConfiguration.
     * 
     * @param applicationConfiguration
     *            the new value of applicationConfiguration
     */
    public void setApplicationConfiguration(ApplicationConfiguration applicationConfiguration) {
        this.applicationConfiguration = applicationConfiguration;
    }
}