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#"
+ * <CERT_SUBJECT_COMMON_NAME>"#"<CERT_ISSUER_COMMON_NAME>
+ * ;"#"<CERT_SERIAL_NUMBER>
+ *
+ * @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>