You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@taverna.apache.org by re...@apache.org on 2015/03/20 16:47:30 UTC

[5/8] incubator-taverna-engine git commit: package names changed to org.apache.taverna.*

http://git-wip-us.apache.org/repos/asf/incubator-taverna-engine/blob/55900be9/taverna-credential-manager-impl/src/main/java/org/apache/taverna/security/credentialmanager/impl/CredentialManagerImpl.java
----------------------------------------------------------------------
diff --git a/taverna-credential-manager-impl/src/main/java/org/apache/taverna/security/credentialmanager/impl/CredentialManagerImpl.java b/taverna-credential-manager-impl/src/main/java/org/apache/taverna/security/credentialmanager/impl/CredentialManagerImpl.java
new file mode 100644
index 0000000..b0ada7c
--- /dev/null
+++ b/taverna-credential-manager-impl/src/main/java/org/apache/taverna/security/credentialmanager/impl/CredentialManagerImpl.java
@@ -0,0 +1,2656 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*/
+
+package org.apache.taverna.security.credentialmanager.impl;
+
+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 static javax.security.auth.x500.X500Principal.RFC2253;
+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 org.apache.taverna.security.credentialmanager.CMException;
+import org.apache.taverna.security.credentialmanager.CredentialManager;
+import static org.apache.taverna.security.credentialmanager.CredentialManager.KeystoreType.KEYSTORE;
+import static org.apache.taverna.security.credentialmanager.CredentialManager.KeystoreType.TRUSTSTORE;
+import org.apache.taverna.security.credentialmanager.DistinguishedNameParser;
+import org.apache.taverna.security.credentialmanager.JavaTruststorePasswordProvider;
+import org.apache.taverna.security.credentialmanager.KeystoreChangedEvent;
+import org.apache.taverna.security.credentialmanager.MasterPasswordProvider;
+import org.apache.taverna.security.credentialmanager.ParsedDistinguishedName;
+import org.apache.taverna.security.credentialmanager.ServiceUsernameAndPasswordProvider;
+import org.apache.taverna.security.credentialmanager.TrustConfirmationProvider;
+import org.apache.taverna.security.credentialmanager.UsernamePassword;
+import org.apache.commons.io.FileUtils;
+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));
+	}
+
+	public void deleteRevokedCertificates(){
+		
+		if (truststore != null){
+			// Delete the old revoked or unnecessary BioCatalogue,
+			// BiodiversityCatalogue and heater's certificates, if present
+			
+			if (certificatesRevokedIndicatorFile == null){
+				certificatesRevokedIndicatorFile = new File(credentialManagerDirectory, CERTIFICATES_REVOKED_INDICATOR_FILE_NAME);
+			}
+			
+			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);
+						}
+					} catch (Exception ex) {
+						logger.info("Can't delete revoked certificate " + certURLToDelete, ex);
+					}
+				}
+				
+				// Touch the file
+				try {
+					FileUtils
+							.touch(certificatesRevokedIndicatorFile);
+				} catch (IOException ioex) {
+					// Hmmm, ignore this?
+					logger.error("Failed to touch " + certificatesRevokedIndicatorFile.getAbsolutePath(), ioex);
+				}
+			}
+			
+			//Save changes
+			try{
+				FileOutputStream fos = new FileOutputStream(truststoreFile);
+				truststore.store(fos, masterPassword.toCharArray());
+			}
+			catch(Exception ex){
+				String exMessage = "Failed to save Truststore after deleting revoked certificates.";
+				logger.error(exMessage, 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) {
+			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());
+					
+					// Delete the old revoked or unnecessary BioCatalogue,
+					// BiodiversityCatalogue and heater's certificates, if present
+					deleteRevokedCertificates();
+					
+				} 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);
+				}
+			} 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)
+					if (!(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 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 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 = null;
+					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 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 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 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 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 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 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 (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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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

<TRUNCATED>