blob: 26890335e1e9f5f0697c59563609ff9595eb31a8 [file] [log] [blame]
/*
* 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 static javax.security.auth.x500.X500Principal.RFC2253;
import static org.apache.taverna.security.credentialmanager.CredentialManager.KeystoreType.KEYSTORE;
import static org.apache.taverna.security.credentialmanager.CredentialManager.KeystoreType.TRUSTSTORE;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
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.nio.file.Files;
import java.nio.file.Path;
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 org.apache.commons.io.FileUtils;
import org.apache.log4j.Logger;
import org.apache.taverna.configuration.app.ApplicationConfiguration;
import org.apache.taverna.lang.observer.MultiCaster;
import org.apache.taverna.lang.observer.Observable;
import org.apache.taverna.lang.observer.Observer;
import org.apache.taverna.security.credentialmanager.CMException;
import org.apache.taverna.security.credentialmanager.CredentialManager;
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.bouncycastle.jce.provider.BouncyCastleProvider;
/**
* Provides an implementation of {@link #CredentialManagerService}.
*
*/
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, Path 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 (OutputStream fos = Files.newOutputStream(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(Path pkcs12File, String pkcs12Password)
throws CMException {
// Load the PKCS #12 keystore from the file
try (InputStream input = Files.newInputStream(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).toFile();
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(Path credentialManagerPath)
throws CMException {
File credentialManagerDirectory = credentialManagerPath.toFile();
if (credentialManagerDirectory == null)
throw new CMException(
"Credential Manager's configuration directory cannot be null.");
try {
if (!credentialManagerDirectory.exists())
credentialManagerDirectory.mkdir();
} catch (Exception e) {
throw new CMException(
"Failed to open Credential Manager's directory "
+ credentialManagerDirectory
+ " to load the security files: " + e.getMessage(),
e);
}
keystoreFile = new File(credentialManagerDirectory, KEYSTORE_FILE_NAME);
truststoreFile = new File(credentialManagerDirectory,
TRUSTSTORE_FILE_NAME);
// Are we resetting the directory? Has stuff been initialized yet?
// Then we need to reset everything else.
if (isInitialized) {
masterPassword = null;
keystore = null;
truststore = null;
isInitialized = false;
}
}
// private void loadSecurityFiles(String credentialManagerDirPath)
// throws CMException {
//
// // If credentialManagerDirPath is null (e.g. user did not specify -cmdir
// on the command line)
// // - try with Taverna's default one
// if (credentialManagerDirPath == null){
// credentialManagerDirectory =
// CMUtils.getCredentialManagerDefaultDirectory();
// }
//
// if (credentialManagerDirectory == null) {
// try {
// credentialManagerDirectory = new File(credentialManagerDirPath);
// } catch (Exception e) {
// throw new CMException(
// "Failed to open Credential Manager's directory to load the security files: "
// + e.getMessage(),
// e);
// }
// }
// if (keystoreFile == null){
// keystoreFile = new File(credentialManagerDirectory, KEYSTORE_FILE_NAME);
// }
// if (truststoreFile == null){
// truststoreFile = new File(credentialManagerDirectory,
// TRUSTSTORE_FILE_NAME);
// }
// }
/**
* Clear the cached service URIs that have username and password associated
* with them. Basically we keep the list of all service URIs (and a map of
* servce URIs to their URIs fragments to find the realm for HTTP AuthN)
* that have a password entry in the Keystore.
*/
public class ClearCachedServiceURIsObserver implements
Observer<KeystoreChangedEvent> {
@Override
public void notify(Observable<KeystoreChangedEvent> sender,
KeystoreChangedEvent message) throws Exception {
// Need to make sure we are initialized before we do anything else
// as Credential Manager can be created but not initialized
initialize();
/*
* If Keystore has changed - possibly some password entries have
* changed (could be key entries that have changed but we do not
* know) - so empty the service URI caches just in case.
*/
if (message.keystoreType.equals(KEYSTORE))
synchronized (keystore) {
cachedServiceURIsMap = null;
cachedServiceURIsList = null;
}
}
}
/**
* If any change to the Keystore or Truststore occurs - create the new
* SSLSocketFactory and set the new default SSLContext which is initialised
* with the updated Keystore and Truststore material
*/
public class KeystoreChangedObserver implements
Observer<KeystoreChangedEvent> {
@Override
public void notify(Observable<KeystoreChangedEvent> sender,
KeystoreChangedEvent message) throws Exception {
/*
* Create the new SSLSocketFactory and set the default SSLContext
* for HTTPS connetions in the JVM
*/
HttpsURLConnection
.setDefaultSSLSocketFactory(createSSLSocketFactory());
}
}
/**
* Set the master password providers for providing the master password to
* encrypt/decrypt the Credential Maager's Keystore and Truststore.
* <p>
* This is done through the Spring DM.
*/
public void setMasterPasswordProviders(
List<MasterPasswordProvider> masterPasswordProviders) {
this.masterPasswordProviders = masterPasswordProviders;
}
/**
* Get the master password providers for providing the master password to
* encrypt/decrypt the Credential Maager's Keystore and Truststore.
*
* @return the master password providers for providing the master password
* to encrypt/decrypt the Credential Maager's Keystore and
* Truststore.
*
*/
public List<MasterPasswordProvider> getMasterPasswordProviders() {
return masterPasswordProviders;
}
/**
* Set the Java truststore password providers for providing the password to
* encrypt/decrypt the Java's default truststore.
* <p>
* This is done through the Spring DM.
*/
public void setJavaTruststorePasswordProviders(
List<JavaTruststorePasswordProvider> javaTruststorePasswordProvider) {
this.javaTruststorePasswordProviders = javaTruststorePasswordProvider;
}
/**
* Get the Java truststore password providers for providing the password to
* encrypt/decrypt the Java's default truststore.
*
* @return Java truststore providers for providing the password to
* encrypt/decrypt the Java's default truststore
*/
public List<JavaTruststorePasswordProvider> getJavaTruststorePasswordProviders() {
return javaTruststorePasswordProviders;
}
/**
* Set the providers of username and passwords for services.
* <p>
* This is done through the Spring DM.
*/
public void setServiceUsernameAndPasswordProviders(
List<ServiceUsernameAndPasswordProvider> serviceUsernameAndPasswordProviders) {
this.serviceUsernameAndPasswordProviders = serviceUsernameAndPasswordProviders;
}
/**
* Get the providers of username and passwords for services.
*
* @return the providers of username and passwords for services.
*
*/
public List<ServiceUsernameAndPasswordProvider> getServiceUsernameAndPasswordProviders() {
return serviceUsernameAndPasswordProviders;
}
/**
* Set the providers of trust confirmation for HTTPS connections to external
* services/sites.
* <p>
* This is done through the Spring DM.
*/
public void setTrustConfirmationProviders(
List<TrustConfirmationProvider> trustConfirmationProviders) {
this.trustConfirmationProviders = trustConfirmationProviders;
}
/**
* Get the providers of trust confirmation for HTTPS connections to external
* services/sites
*
* @return the providers of trust confirmation for HTTPS connections to
* external services/sites
*
*/
public List<TrustConfirmationProvider> getTrustConfirmationProviders() {
return trustConfirmationProviders;
}
/**
* Sets the applicationConfiguration.
*
* @param applicationConfiguration
* the new value of applicationConfiguration
*/
public void setApplicationConfiguration(
ApplicationConfiguration applicationConfiguration) {
this.applicationConfiguration = applicationConfiguration;
}
}