| /******************************************************************************* |
| * Copyright (C) 2008-2010 The University of Manchester |
| * |
| * Modifications to the initial code base are copyright of their |
| * respective authors, or their employers as appropriate. |
| * |
| * This program is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Lesser General Public License |
| * as published by the Free Software Foundation; either version 2.1 of |
| * the License, or (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, but |
| * WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public |
| * License along with this program; if not, write to the Free Software |
| * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 |
| ******************************************************************************/ |
| package net.sf.taverna.t2.security.credentialmanager; |
| |
| 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.Socket; |
| import java.net.URI; |
| import java.net.URISyntaxException; |
| 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.KeyStore.Entry; |
| 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.Collection; |
| import java.util.Collections; |
| import java.util.Comparator; |
| import java.util.Enumeration; |
| import java.util.HashMap; |
| import java.util.Iterator; |
| 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 javax.security.auth.x500.X500Principal; |
| |
| import net.sf.taverna.t2.lang.observer.MultiCaster; |
| import net.sf.taverna.t2.lang.observer.Observable; |
| import net.sf.taverna.t2.lang.observer.Observer; |
| import net.sf.taverna.t2.spi.SPIRegistry; |
| |
| import org.apache.commons.io.FileUtils; |
| import org.apache.log4j.Logger; |
| import org.bouncycastle.jce.provider.BouncyCastleProvider; |
| |
| /** |
| * Provides a wrapper for Taverna's Keystore and Truststore and implements |
| * methods for managing user's credentials (passwords, private/proxy key pairs) |
| * and trusted services and CAs' public key certificates. |
| * |
| * Keystore and Truststore are Bouncy Castle UBER-type keystores. |
| * |
| * @author Alex Nenadic |
| * @author Stian Soiland-Reyes |
| */ |
| |
| public class CredentialManager implements Observable<KeystoreChangedEvent> { |
| |
| private static final String UTF_8 = "UTF-8"; |
| |
| private static final String PROPERTY_TRUSTSTORE = "javax.net.ssl.trustStore"; |
| private static final String PROPERTY_TRUSTSTORE_PASSWORD = "javax.net.ssl.trustStorePassword"; |
| private static final String PROPERTY_KEYSTORE = "javax.net.ssl.keyStore"; |
| private static final String PROPERTY_KEYSTORE_PASSWORD = "javax.net.ssl.keyStorePassword"; |
| private static final String PROPERTY_KEYSTORE_TYPE = "javax.net.ssl.keyStoreType"; |
| private static final String PROPERTY_KEYSTORE_PROVIDER = "javax.net.ssl.keyStoreProvider"; |
| private static final String PROPERTY_TRUSTSTORE_TYPE = "javax.net.ssl.trustStoreType"; |
| private static final String PROPERTY_TRUSTSTORE_PROVIDER = "javax.net.ssl.trustStoreProvider"; |
| |
| // Various passwords to try for the Java's default truststore. |
| public static List<String> defaultTrustStorePasswords = Arrays.asList( |
| System.getProperty(PROPERTY_TRUSTSTORE_PASSWORD, ""), "changeit", |
| "changeme", ""); |
| |
| public static final String T2TRUSTSTORE_FILE = "t2truststore.ubr"; |
| public static final String T2KEYSTORE_FILE = "t2keystore.ubr"; |
| |
| // 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"; |
| |
| // ASCII NUL character - for separating the username from the rest of the string |
| // when saving it in the Keystore. Seems like a good separator as it will highly |
| // unlikely feature in a username. |
| public static final char USERNAME_AND_PASSWORD_SEPARATOR_CHARACTER = '\u0000'; |
| |
| private static Logger logger = Logger.getLogger(CredentialManager.class); |
| |
| // Multicaster of KeystoreChangedEventS |
| private MultiCaster<KeystoreChangedEvent> multiCaster = new MultiCaster<KeystoreChangedEvent>( |
| this); |
| |
| // A directory containing Credential Manager's Keystore/Truststore/etc. files. |
| private static File credentialManagerDirectory = null; |
| |
| // Master password for Credential Manager - used to create/access the Keystore and Truststore. |
| private static String masterPassword; |
| |
| // Keystore file |
| private static File keystoreFile = null; |
| |
| // Truststore file |
| private static File truststoreFile = null; |
| |
| // Keystore containing user's passwords and private keys with corresponding public key certificate chains. |
| private static KeyStore keystore; |
| |
| // Truststore containing trusted certificates of CA authorities and services (servers). |
| private static KeyStore truststore; |
| |
| // Constants denoting which of the two keystores (Keystore or Truststore) we are currently performing |
| // an operation on. |
| public static final String KEYSTORE = "Keystore"; |
| |
| public static final String TRUSTSTORE = "Truststore"; |
| |
| // Existence of this file in the Credential Manager folder |
| // indicates the user has set the master password so do not use the default password |
| public static final String USER_SET_MASTER_PASSWORD_INDICATOR_FILE_NAME = "user_set_master_password"; |
| public static File USER_SET_MASTER_PASSWORD_INDICATOR_FILE; |
| |
| // Default password for Truststore - needed as the Truststore needs to be |
| // populated before the Workbench starts up to initiate the SSLSocketFactory |
| // and to avoid popping up a dialog to ask the user for it. |
| //private static final String TRUSTSTORE_PASSWORD = "Tu/Ap%2_$dJt6*+Rca9v"; |
| |
| // Whether SSLSocketFactory has been initialised with Taverna's Keystore/Truststore. |
| private static boolean sslInitialised = false; |
| // 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(); |
| |
| // Credential Manager singleton |
| private static CredentialManager INSTANCE; |
| |
| static SPIRegistry<CredentialProviderSPI> masterPasswordProviderSPI = new SPIRegistry<CredentialProviderSPI>( |
| CredentialProviderSPI.class); |
| |
| // Existence of this file in the Credential Manager folder |
| // indicates the we have deleted the revoked certificates from some of our services - |
| // BioCatalogue, BiodiversityCatalogue, heater. |
| public static File CERTIFICATES_REVOKED_INDICATOR_FILE; |
| public static final String CERTIFICATES_REVOKED_INDICATOR_FILE_NAME = "certificates_revoked"; |
| |
| /** |
| * Return a CredentialManager singleton. |
| */ |
| public static synchronized CredentialManager getInstance() |
| throws CMException { |
| if (INSTANCE == null) { |
| INSTANCE = new CredentialManager(); |
| } |
| return INSTANCE; |
| } |
| |
| /** |
| * Return a Credential Manager singleton for a given master password. |
| * This should really only be used from CredentialManagerUI where we want to |
| * allow user to cancel entering the password (which only results in the |
| * CredentialManagerUI dialog not being shown), so we have to manage |
| * obtaining the password ourselves. Otherwise, Credential Manager itself |
| * takes care of getting the password from the user using password |
| * providers. If a user cancels inside a password provider that should cause |
| * an error. |
| */ |
| public static synchronized CredentialManager getInstance( |
| String masterPassword) throws CMException { |
| if (INSTANCE == null) { |
| INSTANCE = new CredentialManager(masterPassword); |
| } else { |
| if (masterPassword == null){ |
| // Then we'd better be using the default master password |
| if (!CredentialManager.getUseDefaultMasterPassword()){ |
| String exMessage = "Incorrect master password for Credential Manager."; |
| throw new CMException(exMessage); |
| } |
| } |
| else if (!confirmMasterPassword(masterPassword)) { |
| String exMessage = "Incorrect master password for Credential Manager."; |
| throw new CMException(exMessage); |
| } |
| } |
| return INSTANCE; |
| } |
| |
| /** |
| * Return a Credential Manager singleton for a given master password and a |
| * path to a directory where to find the relevant Keystore/Trustore/etc. files. |
| * This constructor is used if you want Credential Manager to read the files |
| * from a location different from the default one (which for the Workbench is |
| * in <TAVERNA_HOME>/security). |
| */ |
| public static synchronized CredentialManager getInstance(File credentialManagerDir, |
| String masterPassword) throws CMException { |
| if (INSTANCE == null) { |
| INSTANCE = new CredentialManager(credentialManagerDir, masterPassword); |
| } |
| return INSTANCE; |
| } |
| |
| /** |
| * Return a Credential Manager singleton for a given master password and a |
| * path to a directory where to find the relevant Keystore/Trustore/etc. files. |
| * This constructor is used if you want Credential Manager to read the files |
| * from a location different from the default one (which for the Workbench is |
| * in <TAVERNA_HOME>/security). |
| * |
| * NOTE: This method does not create empty keystores if it does not find the files! |
| */ |
| public static synchronized CredentialManager getInstanceNoCreation(File credentialManagerDir, |
| String masterPassword) throws CMException { |
| if (INSTANCE == null) { |
| INSTANCE = new CredentialManager(credentialManagerDir, masterPassword, false); |
| } |
| return INSTANCE; |
| } |
| |
| public static synchronized CredentialManager getInstanceIfInitialized() { |
| return INSTANCE; |
| } |
| |
| /** |
| * Override the Object's clone method to prevent the singleton object being cloned. |
| */ |
| @Override |
| public Object clone() throws CloneNotSupportedException { |
| throw new CloneNotSupportedException(); |
| } |
| |
| /** |
| * Credential Manager's constructor. |
| * |
| * @see #getInstance() |
| * @see #getInstance(String) |
| * @see #getInstanceIfInitialized() |
| */ |
| private CredentialManager() throws CMException { |
| |
| // Open the files stored in the (DEFAULT!!!) Credential Manager's directory |
| loadDefaultSecurityFiles(); |
| masterPassword = getMasterPassword(); |
| init(true); |
| } |
| |
| |
| /** |
| * Credential Manager's constructor for a given master password. |
| */ |
| private CredentialManager(String password) throws CMException { |
| |
| // Open the files stored in the (DEFAULT!!!) Credential Manager's directory |
| loadDefaultSecurityFiles(); |
| if (password != null){ |
| masterPassword = password; |
| } |
| else{ |
| masterPassword = getMasterPassword(); |
| } |
| init(true); |
| } |
| |
| /** |
| * Credential Manager's constructor for a given path to the security folder to load the keystores. |
| * This constructor should try to figure out the master password itself using the master password SPIs. |
| */ |
| private CredentialManager(File credentialManagerDir) throws CMException { |
| |
| // Open the files stored in credentialManagerDir |
| loadSecurityFiles(credentialManagerDir); |
| masterPassword = getMasterPassword(); |
| init(true); |
| } |
| |
| /** |
| * Credential Manager's constructor for a given master password and a directory where |
| * Credential Manager's Keystore/Truststore/etc. files are located. |
| */ |
| private CredentialManager(File credentialManagerDir, String password) throws CMException { |
| |
| // Open the files stored in the Credential Manager's directory passed in |
| loadSecurityFiles(credentialManagerDir); |
| masterPassword = password; |
| init(true); |
| } |
| |
| /** |
| * Credential Manager's constructor for a given master password and a directory where |
| * Credential Manager's Keystore/Truststore/etc. files are located. |
| * |
| * It also has a flag to tell it whether or not to create empty keystores |
| * if they do not exist on disk already. This is needed by the |
| * Command Line Tool, which should not create empty keystores - just load |
| * existing ones. |
| */ |
| private CredentialManager(File credentialManagerDir, String password, boolean createEmptyKeystores) throws CMException { |
| |
| if (credentialManagerDir == null){ |
| loadDefaultSecurityFiles(); |
| } |
| else |
| { |
| // Open the files stored in the Credential Manager's directory passed in |
| loadSecurityFiles(credentialManagerDir); |
| } |
| |
| if (password == null){ |
| masterPassword = getMasterPassword(); |
| } |
| else{ |
| masterPassword = password; |
| } |
| if (masterPassword == null){ |
| throw new CMException("Master password for Credential Manager cannot be null."); |
| } |
| |
| init(createEmptyKeystores); |
| |
| } |
| |
| private String getMasterPassword() throws CMException { |
| |
| if (keystoreFile == null){ |
| loadDefaultSecurityFiles(); |
| } |
| |
| boolean firstTime = !keystoreFile.exists(); |
| |
| List<CredentialProviderSPI> masterPasswordProviders = findMasterPasswordProviders(); |
| for (CredentialProviderSPI provider : masterPasswordProviders) { |
| if (!provider.canProvideMasterPassword()) { |
| continue; |
| } |
| String password = provider.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 for Credential Manager from providers: " |
| + masterPasswordProviders; |
| String exMessage2 = "Failed to obtain master password for Credential Manager"; |
| logger.error(exMessage); |
| throw new CMException(exMessage2); |
| } |
| |
| |
| |
| private static List<CredentialProviderSPI> findMasterPasswordProviders() { |
| List<CredentialProviderSPI> masterPasswordProviders = masterPasswordProviderSPI |
| .getInstances(); |
| Collections.sort(masterPasswordProviders, |
| new Comparator<CredentialProviderSPI>() { |
| public int compare(CredentialProviderSPI o1, |
| CredentialProviderSPI o2) { |
| // Reverse sort - highest provider first |
| return o2.getProviderPriority() |
| - o1.getProviderPriority(); |
| } |
| }); |
| return masterPasswordProviders; |
| } |
| |
| /** |
| * Initialise Credential Manager - load the Keystore and Truststore. |
| */ |
| private void init(boolean createEmptyKeystores) throws CMException { |
| |
| this.addObserver(clearCachedServiceURIsObserver); |
| this.addObserver(keystoresChangedObserver); |
| |
| // Make sure we have BouncyCastle provider installed, just in case (needed for some tests and reading PKCS#12 keystores) |
| Security.addProvider(new BouncyCastleProvider()); |
| |
| // Load the Keystore |
| try { |
| loadKeystore(createEmptyKeystores); |
| logger.info("Credential Manager: Loaded the Keystore."); |
| } catch (CMException cme) { |
| //logger.error(cme.getMessage(), cme); |
| throw cme; |
| } |
| |
| // Load the Truststore |
| try { |
| loadTruststore(createEmptyKeystores); |
| logger.info("Credential Manager: Loaded the Truststore."); |
| } catch (CMException cme) { |
| //logger.error(cme.getMessage(), cme); |
| throw cme; |
| } |
| } |
| |
| /** |
| * Load Taverna's Keystore from a file on the disk. |
| */ |
| protected static void loadKeystore(boolean createEmptyKeystores) |
| 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. |
| String exMessage = "Failed to instantiate Taverna's Keystore."; |
| throw new CMException(exMessage, ex); |
| } |
| |
| if (keystoreFile.exists()) { // If the file exists, open it |
| |
| // Try to load the Keystore |
| FileInputStream fis = null; |
| try { |
| // Get the file |
| 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. Possible reason: incorrect password or corrupted file."; |
| logger.error(exMessage, ex); |
| throw new CMException(exMessage, ex); |
| } finally { |
| if (fis != null) { |
| try { |
| fis.close(); |
| } catch (IOException e) { |
| // ignore |
| } |
| } |
| } |
| } else { |
| if (createEmptyKeystores){ |
| // Otherwise create a new empty Keystore if createEmptyKeystores flag is set |
| FileOutputStream fos = null; |
| try { |
| keystore.load(null, null); |
| // Immediately save the new (empty) Keystore to the file |
| fos = new FileOutputStream(keystoreFile); |
| 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); |
| } finally { |
| if (fos != null) { |
| try { |
| fos.close(); |
| } catch (IOException e) { |
| // ignore |
| } |
| } |
| } |
| } |
| } |
| |
| /* |
| * 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); // "javax.net.ssl.keyStoreType" |
| System.clearProperty(PROPERTY_KEYSTORE_PROVIDER); // "javax.net.ssl.keyStoreProvider" |
| |
| /* |
| * 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); // "javax.net.ssl.keyStore" |
| System.clearProperty(PROPERTY_KEYSTORE_PASSWORD); // "javax.net.ssl.keyStorePassword" |
| } |
| } |
| |
| |
| |
| /** |
| * 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 static void loadTruststore(boolean createEmptyKeystores) |
| 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. |
| String exMessage = "Failed to instantiate Taverna's Truststore."; |
| throw new CMException(exMessage, ex); |
| } |
| |
| if (truststoreFile.exists()) { |
| // If the Truststore file already exists, open it and load the |
| // Truststore |
| FileInputStream fis = null; |
| try { |
| // Get the file |
| 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) { |
| truststore = 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 Truststore."; |
| logger.error(exMessage, ex); |
| throw new CMException(exMessage, ex); |
| } finally { |
| if (fis != null) { |
| try { |
| fis.close(); |
| fis = null; |
| } catch (IOException e) { |
| // ignore |
| } |
| } |
| } |
| } else { |
| if (createEmptyKeystores){ |
| /* |
| * Create a new empty Truststore and load it with |
| * certs from Java's truststore, if createEmptyKeystores flag is set. |
| */ |
| 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 |
| String exMessage = "Failed to instantiate a 'JKS'-type keystore for reading Java's truststore."; |
| //logger.error(exMessage, ex); |
| throw new CMException(exMessage, ex); |
| } |
| |
| FileInputStream fis = null; |
| 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 { |
| // Get the file |
| 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..) |
| */ |
| String message = "Failed to load the Java " |
| + "truststore to copy " |
| + "over certificates using default password: " |
| + password + " from " + javaTruststoreFile; |
| logger.info(message); |
| } 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; |
| } finally { |
| if (fis != null) { |
| try { |
| fis.close(); |
| } catch (IOException e) { |
| logger.warn("Could not close input stream to " |
| + javaTruststoreFile, e); |
| } |
| } |
| } |
| } |
| |
| if (!loadedJavaTruststore) { |
| // Try using SPIs (typically pop up GUI) |
| if (!(copyPasswordFromGUI(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); |
| System.err.println(error); |
| System.err.println(help); |
| } |
| } |
| |
| FileOutputStream fos = null; |
| // Create a new empty Truststore for Taverna |
| try { |
| truststore.load(null, null); |
| if (loadedJavaTruststore) { |
| // Copy certificates into Taverna's Truststore from |
| // Java's truststore. |
| Enumeration<String> aliases = javaTruststore.aliases(); |
| while (aliases.hasMoreElements()) { |
| String alias = aliases.nextElement(); |
| Certificate certificate = javaTruststore |
| .getCertificate(alias); |
| if (certificate instanceof X509Certificate) { |
| String trustedCertAlias = createX509CertificateAlias((X509Certificate) certificate); |
| truststore.setCertificateEntry( |
| trustedCertAlias, certificate); |
| } |
| } |
| } |
| |
| // Insert special trusted CA certificates |
| InputStream[] trustedCertStreams = getSpecialTrustedCertificateStreams(); |
| logger.info("Loading trusted certificates for our services such as BioCatalogue, BiodiversityCatalogue, heater, etc."); |
| for (InputStream trustedCertStream : trustedCertStreams) { |
| // Load the certificate (possibly a chain) from the stream |
| List<X509Certificate> trustedCertChain = new ArrayList<X509Certificate>(); |
| try { |
| CertificateFactory cf = CertificateFactory.getInstance("X.509"); |
| Collection<? extends Certificate> c = cf.generateCertificates(trustedCertStream); |
| Iterator<? extends Certificate> i = c.iterator(); |
| while (i.hasNext()) { |
| trustedCertChain.add((X509Certificate) i.next()); |
| } |
| } catch (Exception cex) { |
| logger.error(cex); |
| } finally { |
| try { |
| trustedCertStream.close(); |
| } catch (Exception ex) { |
| // ignore |
| } |
| } |
| |
| if (trustedCertChain.size() > 0) { // Managed to load certificate (chain) |
| for (X509Certificate trustedCert : trustedCertChain){ |
| String alias = createX509CertificateAlias(trustedCert); |
| try { |
| truststore.setCertificateEntry(alias, trustedCert); |
| } |
| catch (Exception ex) { |
| String exMessage = "Credential Manager: Failed to insert trusted certificate entry in the Truststore."; |
| logger.error(exMessage, ex); |
| } |
| } |
| } |
| } |
| |
| // Immediately save the new Truststore to the file |
| fos = new FileOutputStream(truststoreFile); |
| truststore.store(fos, masterPassword.toCharArray()); |
| } catch (Exception ex) { |
| truststore = null;// make it null as it was just created but failed to save so we should retry next time |
| String exMessage = "Failed to generate new empty Taverna's Truststore."; |
| logger.error(exMessage, ex); |
| throw new CMException(exMessage, ex); |
| } finally { |
| if (fos != null) { |
| try { |
| fos.close(); |
| } catch (IOException e) { |
| // ignore |
| } |
| } |
| } |
| } |
| } |
| |
| /* |
| * 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); // "javax.net.ssl.trustStoreType" |
| System.clearProperty(PROPERTY_TRUSTSTORE_PROVIDER);// "javax.net.ssl.trustStoreProvider" |
| |
| /* |
| * 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); // "javax.net.ssl.trustStore" |
| System.clearProperty(PROPERTY_TRUSTSTORE_PASSWORD); // "javax.net.ssl.trustStorePassword" |
| |
| } |
| } |
| |
| |
| // Return an array of input streams for 'special' trusted CA certificates contained in the |
| // resources folder that need to be loaded into Truststore, so that we can trust services like |
| // BioCatalogue, BiodiversityCatalogue, heater, etc. by default. |
| public static InputStream[] getSpecialTrustedCertificateStreams(){ |
| |
| InputStream cert1 = CredentialManager.class.getResourceAsStream( |
| "/trusted-certificates/TERENASSLCA.crt"); |
| InputStream cert2 = CredentialManager.class.getResourceAsStream( |
| "/trusted-certificates/UTNAddTrustServer_CA.crt"); |
| InputStream cert3 = CredentialManager.class.getResourceAsStream( |
| "/trusted-certificates/AddTrustExternalCARoot.crt"); |
| |
| InputStream[] trustedCertStreams = new InputStream[] {cert1, cert2, cert3}; |
| |
| return trustedCertStreams; |
| } |
| |
| public static void deleteRevokedCertificates(){ |
| |
| if (truststore != null){ |
| // Delete the old revoked or unnecessary BioCatalogue, |
| // BiodiversityCatalogue and heater's certificates, if present |
| |
| if (CERTIFICATES_REVOKED_INDICATOR_FILE == null){ |
| CERTIFICATES_REVOKED_INDICATOR_FILE = new File(credentialManagerDirectory, CERTIFICATES_REVOKED_INDICATOR_FILE_NAME); |
| } |
| |
| if (!CERTIFICATES_REVOKED_INDICATOR_FILE.exists()) { |
| |
| InputStream biocat = CredentialManager.class |
| .getResourceAsStream("/trusted-certificates/www.biocatalogue.org-revoked.pem"); |
| InputStream biodivcat = CredentialManager.class |
| .getResourceAsStream("/trusted-certificates/www.biodiversitycatalogue.org-revoked.pem"); |
| InputStream heater = CredentialManager.class |
| .getResourceAsStream("/trusted-certificates/heater.cs.man.ac.uk-not-needed.pem"); |
| InputStream[] certStreamsToDelete = new InputStream[] { |
| biocat, biodivcat, heater }; |
| |
| for (InputStream certStreamToDelete : certStreamsToDelete) { |
| // Load the certificate (possibly a chain) from the |
| // stream |
| List<X509Certificate> certChainToDelete = new ArrayList<X509Certificate>(); |
| try { |
| CertificateFactory cf = CertificateFactory |
| .getInstance("X.509"); |
| Collection<? extends Certificate> c = cf |
| .generateCertificates(certStreamToDelete); |
| Iterator<? extends Certificate> i = c |
| .iterator(); |
| while (i.hasNext()) { |
| certChainToDelete.add((X509Certificate) i |
| .next()); |
| } |
| String aliasToDelete = truststore |
| .getCertificateAlias(certChainToDelete |
| .get(0)); // We know there will |
| // be only one cert |
| // in the chain |
| |
| if (aliasToDelete != null) { |
| truststore.deleteEntry(aliasToDelete); |
| logger.warn("Deleting revoked certificate " |
| + aliasToDelete); |
| } |
| } catch (Exception ex) { |
| logger.error(ex.getMessage(), ex); |
| } finally { |
| try { |
| certStreamToDelete.close(); |
| } catch (Exception ex) { |
| // ignore and carry on |
| logger.error(ex); |
| } |
| } |
| } |
| // Touch the file |
| try { |
| FileUtils |
| .touch(CERTIFICATES_REVOKED_INDICATOR_FILE); |
| } catch (IOException ioex) { |
| // Hmmm, ignore this? |
| logger.error("Failed to touch " + CERTIFICATES_REVOKED_INDICATOR_FILE.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 certificate"; |
| logger.error(exMessage, ex); |
| } |
| } |
| } |
| |
| private static boolean copyPasswordFromGUI(KeyStore javaTruststore, |
| File javaTruststoreFile) { |
| List<CredentialProviderSPI> masterPasswordProviders = findMasterPasswordProviders(); |
| String javaTruststorePassword = null; |
| for (CredentialProviderSPI provider : masterPasswordProviders) { |
| if (!provider.canProvideJavaTruststorePassword()) { |
| continue; |
| } |
| javaTruststorePassword = provider.getJavaTruststorePassword(); |
| if (javaTruststorePassword == null) { |
| continue; |
| } |
| FileInputStream fis = null; |
| try { |
| 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 spi " + provider; |
| logger.warn(exMessage, ex); |
| return false; |
| } finally { |
| if (fis != null) { |
| try { |
| fis.close(); |
| } catch (IOException e) { |
| // ignore |
| } |
| } |
| } |
| } |
| String exMessage = "None (if any) MasterPasswordProviderSPI 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 { |
| 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, or null if it |
| * does not exit. |
| * <p> |
| * If the credentials are not available in the Keystore, it will |
| * invoke SPI implementations of the {@link UsernamePasswordProviderSPI} |
| * interface for asking the user (typically through the UI) or resolving |
| * hard-coded credentials. |
| * <p> |
| * If the parameter <code>usePathRecursion</code> is true, then the |
| * credential manager will also attempt to look for stored credentials for |
| * each of the parents in the URI. |
| */ |
| public UsernamePassword getUsernameAndPasswordForService(URI serviceURI, |
| boolean usePathRecursion, String requestingPrompt) |
| throws CMException { |
| |
| 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 |
| // Keystrote 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(CredentialManager.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 SPIs |
| for (CredentialProviderSPI credProvider : findMasterPasswordProviders()) { |
| if (credProvider.canProvideUsernamePassword(serviceURI)) { |
| UsernamePassword usernamePassword = credProvider |
| .getUsernamePassword(serviceURI, |
| requestingPrompt); |
| if (usernamePassword == null) { |
| continue; |
| } |
| if (usernamePassword.isShouldSave()) { |
| URI uri = serviceURI; |
| if (usePathRecursion) { |
| uri = normalizeServiceURI(serviceURI); |
| } |
| saveUsernameAndPasswordForService(usernamePassword, |
| uri); |
| } |
| return usernamePassword; |
| } |
| } |
| // Giving up |
| return null; |
| } catch (Exception ex) { |
| String exMessage = "Credential Manager: Failed to get the username and password pair for service " |
| + serviceURI + " from the Keystore."; |
| logger.error(exMessage, ex); |
| throw (new CMException(exMessage)); |
| } |
| } |
| } |
| |
| protected Map<URI, URI> getFragmentMappedURIsForAllUsernameAndPasswordPairs() |
| throws CMException { |
| synchronized (Security.class) { |
| if (cachedServiceURIsMap == null) { |
| HashMap<URI, URI> map = new HashMap<URI, URI>(); |
| // Get all service URIs that have username and password in the Keystore |
| List<URI> serviceURIs = getServiceURIsForAllUsernameAndPasswordPairs(); |
| for (URI serviceURI : serviceURIs) { |
| // 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 = 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 static LinkedHashSet<URI> getPossibleServiceURIsToLookup(URI serviceURI, |
| boolean usePathRecursion) { |
| |
| try { |
| serviceURI = serviceURI.normalize(); |
| serviceURI = 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<URI>(possibles)) { |
| try { |
| possibles.add(setFragmentForURI(withFragment, null)); |
| } catch (URISyntaxException e) { |
| logger.warn("Could not non-fragment URI " + withFragment); |
| } |
| } |
| } |
| return possibles; |
| } |
| |
| private static 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 { |
| List<URI> uris = getServiceURIsForAllUsernameAndPasswordPairs(); |
| ArrayList<String> serviceURLs = new ArrayList<String>(); |
| for (URI uri : uris) { |
| serviceURLs.add(uri.toASCIIString()); |
| } |
| return serviceURLs; |
| } |
| |
| /** |
| * Insert a new username and password pair in the keystore for the given |
| * service URL. |
| * <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. |
| * <p> |
| * |
| * @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 |
| */ |
| public void saveUsernameAndPasswordForService( |
| UsernamePassword usernamePassword, URI serviceURI) |
| throws CMException { |
| String uri = serviceURI.toASCIIString(); |
| saveUsernameAndPasswordForService(usernamePassword.getUsername(), |
| String.valueOf(usernamePassword.getPassword()), uri); |
| } |
| |
| /** |
| * Insert a new username and password pair in the Keystore for the given |
| * service URL. |
| * <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> |
| * |
| * @deprecated Use |
| * {@link #saveUsernameAndPasswordForService(UsernamePassword, URI)} |
| * instead |
| */ |
| @Deprecated |
| public void saveUsernameAndPasswordForService(String username, |
| String password, String serviceURL) throws CMException { |
| |
| synchronized (keystore) { |
| |
| // Alias for the username and password entry |
| String 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( |
| CredentialManager.KEYSTORE)); |
| } catch (Exception ex) { |
| String exMessage = "Credential Manager: Failed to insert username and password pair for service " |
| + serviceURL + " in the Keystore."; |
| logger.error(exMessage, ex); |
| throw (new CMException(exMessage)); |
| } |
| } |
| } |
| |
| /** |
| * Delete a username and password pair for the given service URL from the |
| * Keystore. |
| */ |
| public void deleteUsernameAndPasswordForService(String serviceURL) |
| throws CMException { |
| synchronized (keystore) { |
| deleteEntry(KEYSTORE, "password#" + serviceURL); |
| saveKeystore(KEYSTORE); |
| multiCaster.notify(new KeystoreChangedEvent(CredentialManager.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> |
| */ |
| public void saveKeyPair(Key privateKey, Certificate[] certs) throws CMException { |
| |
| 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(X500Principal.RFC2253); |
| CMUtils util = new CMUtils(); |
| util.parseDN(ownerDN); |
| String ownerCN = util.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(X500Principal.RFC2253); |
| util.parseDN(issuerDN); |
| String issuerCN = util.getCN(); // issuer's common name |
| |
| String alias = "keypair#" + ownerCN + "#" + issuerCN + "#" |
| + serialNumber; |
| |
| try { |
| keystore.setKeyEntry(alias, privateKey, masterPassword.toCharArray(), certs); |
| saveKeystore(KEYSTORE); |
| multiCaster.notify(new KeystoreChangedEvent( |
| CredentialManager.KEYSTORE)); |
| |
| // This is now done from the KeystoresChangedObserver's notify method. |
| // Update the default SSLSocketFactory used by the HttpsURLConnectionS |
| //HttpsURLConnection.setDefaultSSLSocketFactory(createTavernaSSLSocketFactory()); |
| logger.info("Credential Manager: Updating SSLSocketFactory after inserting a key pair."); |
| } catch (Exception ex) { |
| String exMessage = "Credential Manager: Failed to insert the key pair entry in the Keystore."; |
| //logger.error(exMessage, ex); |
| throw (new CMException(exMessage, ex)); |
| } |
| } |
| } |
| |
| /** |
| * Checks if the Keystore contains the key pair entry. |
| */ |
| public boolean containsKeyPair(Key privateKey, Certificate[] certs) |
| throws CMException { |
| 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(X500Principal.RFC2253); |
| CMUtils util = new CMUtils(); |
| util.parseDN(ownerDN); |
| String ownerCN = util.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(X500Principal.RFC2253); |
| util.parseDN(issuerDN); |
| String issuerCN = util.getCN(); // issuer's common name |
| |
| String alias = "keypair#" + ownerCN + "#" + issuerCN + "#" |
| + serialNumber; |
| |
| try { |
| return keystore.containsAlias(alias); |
| } catch (KeyStoreException ex) { |
| String exMessage = "Credential Manager: Failed to get aliases from the Keystore to check if it contains the given key pair."; |
| logger.error(exMessage, ex); |
| throw (new CMException(exMessage)); |
| } |
| } |
| } |
| |
| /** |
| * Deletes a key pair entry from the Keystore. |
| */ |
| public void deleteKeyPair(String alias) throws CMException { |
| |
| // TODO: We are passing alias for now but we want to be passing |
| // the private key and its public key certificate. |
| |
| // // 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(X500Principal.RFC2253); |
| // CMX509Util util = new CMX509Util(); |
| // util.parseDN(ownerDN); |
| // String ownerCN = util.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(X500Principal.RFC2253); |
| // util.parseDN(issuerDN); |
| // String issuerCN = util.getCN(); // issuer's common name |
| // |
| // String alias = "keypair#" + ownerCN + "#" + issuerCN + "#" + |
| // serialNumber; |
| synchronized (keystore) { |
| deleteEntry(KEYSTORE, alias); |
| saveKeystore(KEYSTORE); |
| multiCaster.notify(new KeystoreChangedEvent(CredentialManager.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("Credential Manager: Updating SSLSocketFactory " |
| + "after deleting a keypair."); |
| } |
| } |
| |
| /** |
| * Exports a key entry containing private key and public key certificate |
| * chain from the Keystore to a PKCS #12 file. |
| */ |
| public void exportKeyPair(String alias, File exportFile, |
| String pkcs12Password) throws CMException { |
| |
| FileOutputStream fos = null; |
| |
| 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 = getCertificateChain(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( |
| X500Principal.RFC2253); |
| CMUtils util = new CMUtils(); |
| util.parseDN(sDN); |
| String sCN = util.getCN(); |
| |
| String iDN = ((X509Certificate) certChain[0]) |
| .getIssuerX500Principal() |
| .getName(X500Principal.RFC2253); |
| util.parseDN(iDN); |
| String iCN = util.getCN(); |
| |
| String pkcs12Alias = sCN + "'s " + iCN + " ID"; |
| newPkcs12.setKeyEntry(pkcs12Alias, privateKey, new char[0], |
| certChain); |
| |
| // Store the new PKCS #12 keystore on the disk |
| fos = new FileOutputStream(exportFile); |
| newPkcs12.store(fos, pkcs12Password.toCharArray()); |
| fos.close(); |
| } catch (Exception ex) { |
| String exMessage = "Credential Manager: Failed to export the key pair from the Keystore."; |
| logger.error(exMessage, ex); |
| throw (new CMException(exMessage)); |
| } finally { |
| if (fos != null) { |
| try { |
| fos.close(); |
| } catch (IOException e) { |
| // ignore |
| } |
| } |
| } |
| } |
| } |
| |
| /** |
| * 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. |
| */ |
| public Certificate getCertificate(String ksType, String alias) |
| throws CMException { |
| try { |
| if (ksType.equals(KEYSTORE)) { |
| synchronized (keystore) { |
| return keystore.getCertificate(alias); |
| } |
| } else if (ksType.equals(TRUSTSTORE)) { |
| synchronized (truststore) { |
| return truststore.getCertificate(alias); |
| } |
| } else { |
| return null; |
| } |
| } catch (Exception ex) { |
| String exMessage = "Credential Manager: Failed to fetch certificate from the " |
| + ksType + "."; |
| logger.error(exMessage, ex); |
| throw (new CMException(exMessage)); |
| } |
| } |
| |
| /** |
| * Gets certificate chain for the key pair entry from the Keystore. This |
| * method works for Keystore only as Truststore does not contain key pair |
| * entries, but trusted certificate entries only. |
| */ |
| public Certificate[] getCertificateChain(String alias) throws CMException { |
| synchronized (keystore) { |
| try { |
| return keystore.getCertificateChain(alias); |
| } catch (Exception ex) { |
| String exMessage = "Credential Manager: Failed to fetch certificate chain for the keypair from the Keystore"; |
| logger.error(exMessage, ex); |
| throw (new CMException(exMessage)); |
| } |
| } |
| } |
| |
| /** |
| * Inserts a trusted certificate entry in the Truststore with an alias |
| * constructed as: |
| * |
| * "trustedcert#<CERT_SUBJECT_COMMON_NAME>"#"<CERT_ISSUER_COMMON_NAME>"# |
| * "<CERT_SERIAL_NUMBER> |
| */ |
| public void saveTrustedCertificate(X509Certificate cert) throws CMException { |
| |
| 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> |
| String alias = createX509CertificateAlias(cert); |
| try { |
| truststore.setCertificateEntry(alias, cert); |
| saveKeystore(TRUSTSTORE); |
| multiCaster.notify(new KeystoreChangedEvent( |
| CredentialManager.TRUSTSTORE)); |
| |
| // This is now done from the KeystoresChangedObserver's notify method. |
| // Update the default SSLSocketFactory used by the HttpsURLConnectionS |
| //HttpsURLConnection.setDefaultSSLSocketFactory(createTavernaSSLSocketFactory()); |
| |
| logger.info("Credential Manager: Updating SSLSocketFactory after inserting a trusted certificate."); |
| } |
| catch (Exception ex) { |
| String exMessage = "Credential Manager: Failed to insert trusted certificate entry in the Truststore."; |
| //logger.error(exMessage, ex); |
| throw (new CMException(exMessage, ex)); |
| } |
| } |
| } |
| |
| /** |
| * Create a Truststore alias for the trusted certificate as |
| * "trustedcert#"<CERT_SUBJECT_COMMON_NAME>"#"<CERT_ISSUER_COMMON_NAME>"#"< |
| * CERT_SERIAL_NUMBER> |
| */ |
| private static String createX509CertificateAlias(X509Certificate cert) { |
| String ownerDN = cert.getSubjectX500Principal().getName( |
| X500Principal.RFC2253); |
| CMUtils util = new CMUtils(); |
| util.parseDN(ownerDN); |
| String owner; |
| String ownerCN = util.getCN(); // owner's common name |
| String ownerOU = util.getOU(); |
| String ownerO = util.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( |
| X500Principal.RFC2253); |
| util.parseDN(issuerDN); |
| String issuer; |
| String issuerCN = util.getCN(); // issuer's common name |
| String issuerOU = util.getOU(); |
| String issuerO = util.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; |
| } |
| |
| /** |
| * Deletes a trusted certificate entry from the Truststore. |
| */ |
| public void deleteTrustedCertificate(String alias) throws CMException { |
| |
| synchronized (truststore) { |
| deleteEntry(TRUSTSTORE, alias); |
| saveKeystore(TRUSTSTORE); |
| multiCaster.notify(new KeystoreChangedEvent( |
| CredentialManager.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("Credential Manager: Updating SSLSocketFactory " |
| + "after deleting a trusted certificate."); |
| } |
| } |
| |
| /** |
| * Checks if the given entry is a key entry in the Keystore. |
| */ |
| public boolean isKeyEntry(String alias) throws CMException { |
| |
| try { |
| synchronized (keystore) { |
| return keystore.isKeyEntry(alias); |
| } |
| } catch (Exception ex) { |
| String exMessage = "Credential Manager: Failed to access the key entry in the Keystore."; |
| logger.error(exMessage, ex); |
| throw (new CMException(exMessage)); |
| } |
| } |
| |
| /** |
| * Deletes an entry from the Keystore or the Truststore. |
| */ |
| private void deleteEntry(String ksType, String alias) throws CMException { |
| try { |
| if (ksType.equals(KEYSTORE)) { |
| synchronized (keystore) { |
| keystore.deleteEntry(alias); |
| } |
| } |
| else if (ksType.equals(TRUSTSTORE)) { |
| synchronized (truststore) { |
| truststore.deleteEntry(alias); |
| } |
| } |
| } catch (Exception ex) { |
| String exMessage = "Credential Manager: Failed to delete the entry with alias " |
| + alias + "from the " + ksType + "."; |
| logger.error(exMessage, ex); |
| throw (new CMException(exMessage)); |
| } |
| } |
| |
| /** |
| * Check if a keystore contains an entry with the given alias. |
| */ |
| public boolean containsAlias(String ksType, String alias) |
| throws CMException { |
| try { |
| if (ksType.equals(KEYSTORE)) |
| synchronized (keystore) { |
| return keystore.containsAlias(alias); |
| } |
| else if (ksType.equals(TRUSTSTORE)) |
| synchronized (truststore) { |
| return truststore.containsAlias(alias); |
| } |
| else { |
| return false; |
| } |
| } catch (Exception ex) { |
| String exMessage = "Credential Manager: Failed to access the " |
| + ksType + " to check if an alias exists."; |
| logger.error(exMessage, ex); |
| throw (new CMException(exMessage)); |
| } |
| } |
| |
| /** |
| * Gets all the aliases from the Keystore/Truststore or null if there was some error |
| * while accessing it. |
| */ |
| public ArrayList<String> getAliases(String ksType) throws CMException { |
| |
| try { |
| if (ksType.equals(KEYSTORE)) { |
| synchronized (keystore) { |
| return Collections.list(keystore.aliases()); |
| } |
| } else if (ksType.equals(TRUSTSTORE)) { |
| synchronized (truststore) { |
| return Collections.list(truststore.aliases()); |
| } |
| } else { |
| return null; |
| } |
| } catch (Exception ex) { |
| String exMessage = "Credential Manager: Failed to access the " |
| + ksType + " to get the aliases."; |
| logger.error(exMessage, ex); |
| throw new CMException(exMessage); |
| } |
| } |
| |
| /** |
| * Get service URIs associated with all username/password pairs currently in |
| * the Keystore. |
| * |
| * @see #hasUsernamePasswordForService(URI) |
| */ |
| public List<URI> getServiceURIsForAllUsernameAndPasswordPairs() throws CMException { |
| synchronized (keystore) { |
| if (cachedServiceURIsList == null) { |
| List<URI> serviceURIs = new ArrayList<URI>(); |
| for (String alias : getAliases(CredentialManager.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; |
| } |
| } |
| |
| /** |
| * Check if Keystore/Truststore file already exists on disk. |
| */ |
| public boolean exists(String 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. |
| */ |
| public void saveKeystore(String ksType) throws CMException { |
| |
| FileOutputStream fos = null; |
| try { |
| if (ksType.equals(KEYSTORE)) { |
| synchronized (keystore) { |
| fos = new FileOutputStream(keystoreFile); |
| keystore.store(fos, masterPassword.toCharArray()); |
| } |
| |
| } else if (ksType.equals(TRUSTSTORE)) { |
| synchronized (truststore) { |
| fos = new FileOutputStream(truststoreFile); |
| truststore.store(fos, masterPassword.toCharArray()); |
| } |
| } |
| } catch (Exception ex) { |
| String exMessage = "Credential Manager: Failed to save the " |
| + ksType + "."; |
| logger.error(exMessage, ex); |
| throw (new CMException(exMessage)); |
| } finally { |
| if (fos != null) { |
| try { |
| fos.close(); |
| } catch (IOException e) { |
| // ignore |
| } |
| } |
| } |
| } |
| |
| /** |
| * Loads a PKCS12-type keystore from a file using the supplied password. |
| */ |
| public KeyStore loadPKCS12Keystore(File pkcs12File, String pkcs12Password) |
| throws CMException { |
| |
| // Load the PKCS #12 keystore from the file |
| KeyStore pkcs12; |
| try { |
| pkcs12 = KeyStore.getInstance("PKCS12", "BC"); |
| pkcs12.load(new FileInputStream(pkcs12File), pkcs12Password |
| .toCharArray()); |
| return pkcs12; |
| } catch (Exception ex) { |
| String exMessage = "Credential Manager: Failed to open a PKCS12-type keystore."; |
| logger.error(exMessage, ex); |
| throw (new CMException(exMessage)); |
| } |
| } |
| |
| public void addObserver(Observer<KeystoreChangedEvent> observer) { |
| multiCaster.addObserver(observer); |
| } |
| |
| public List<Observer<KeystoreChangedEvent>> getObservers() { |
| return multiCaster.getObservers(); |
| } |
| |
| public void removeObserver(Observer<KeystoreChangedEvent> observer) { |
| multiCaster.removeObserver(observer); |
| } |
| |
| /** |
| * Checks if Credential Manager has been initialised. |
| */ |
| public static synchronized boolean isInitialised() { |
| return (INSTANCE != null); |
| } |
| |
| /** |
| * Checks if Keystore's master password is the same as the one provided. |
| */ |
| public static boolean confirmMasterPassword(String password) { |
| return((masterPassword != null) && masterPassword.equals(password)) ; |
| } |
| |
| /** |
| * Changes the Keystore master password. Truststore is using a different |
| * pre-set password. |
| */ |
| public void changeMasterPassword(String newMasterPassword) throws CMException { |
| FileOutputStream fos = null; |
| |
| 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 (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 |
| Entry entry = keystore.getEntry(alias, |
| new KeyStore.PasswordProtection(masterPassword.toCharArray())); |
| newKeystore.setEntry(alias, entry, |
| new KeyStore.PasswordProtection( |
| newMasterPassword.toCharArray())); |
| } |
| 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) { |
| 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 = "Credential Manager: Failed to change maaster password - reverting to the old."; |
| logger.error(exMessage, ex); |
| throw (new CMException(exMessage)); |
| } finally { |
| if (fos != null) { |
| try { |
| fos.close(); |
| } catch (IOException e) { |
| // ignore |
| } |
| } |
| } |
| } |
| |
| public static void initialiseSSL() throws CMException { |
| if (!sslInitialised) { |
| // We use lazy initialisation of Credential Manager from inside |
| // the Taverna's SSLSocketFactory when it is actually needed so do |
| // not instantiate it here |
| //getInstance(); |
| |
| // Create Taverna's SSLSocketFactory and set the SSL socket factory |
| // from HttpsURLConnectionS to use it |
| HttpsURLConnection.setDefaultSSLSocketFactory(createTavernaSSLSocketFactory()); |
| sslInitialised = true; |
| } |
| } |
| |
| public static void initialiseSSL(String password) throws CMException { |
| if (!sslInitialised) { |
| // We use lazy initialisation of Credential Manager from inside |
| // the Taverna's SSLSocketFactory when it is actually needed so do |
| // not instantiate it here |
| //getInstance(password); |
| |
| // Create Taverna's SSLSocketFactory and set the SSL socket factory |
| // from HttpsURLConnectionS to use it |
| HttpsURLConnection.setDefaultSSLSocketFactory(createTavernaSSLSocketFactory()); |
| sslInitialised = true; |
| } |
| } |
| |
| /** |
| * Configures SSLContext (keystore and trustore) and a special SSLSocketFactory |
| * to be used for HTTPS connections from Taverna. |
| * It has to initialize the Credential Manager (Keystore and Truststore) |
| * in the process (from the files in the given directory) and using the given master password. |
| * |
| * This method is to be used from the Command Line Tool when you need to have the |
| * master password in advance and use a special location for Credential Manager's files. |
| * @throws CMException |
| */ |
| public static void load_and_inialiseSSL(File credentialManagerDir, String masterPassword) throws CMException { |
| if (!sslInitialised) { |
| // We have to do this now - it will init the Keystore/Truststore and |
| // get them ready for SSLSocketFactory (do not use lazy initialisation |
| // as usual as this is typically used from the Command Line Tool - everything needs |
| // to be ready and loaded) |
| getInstanceNoCreation(credentialManagerDir, masterPassword); |
| |
| // Create Taverna's SSLSocketFactory and set the SSL socket factory |
| // from HttpsURLConnectionS to use it |
| HttpsURLConnection.setDefaultSSLSocketFactory(createTavernaSSLSocketFactory()); |
| sslInitialised = true; |
| } |
| } |
| |
| // /** |
| // * SSL Socket factory used by Taverna that uses special |
| // * {@link TavernaTrustManager} that gets initialised every time Credential |
| // * Manager's Keystore or Truststore is updated. |
| // * |
| // * Inspired by Tom Oinn's ThreadLocalSSLSoketFactory. |
| // */ |
| // public static SSLSocketFactory createTavernaSSLSocketFactoryOld() 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; |
| // synchronized (keystore) { // Keystore must not be null at this point!!! |
| // try { |
| // // Create KeyManager factory and load Taverna's Keystore object |
| // // in it |
| // KeyManagerFactory keyManagerFactory = KeyManagerFactory |
| // .getInstance("SunX509", "SunJSSE"); |
| // keyManagerFactory.init(keystore, masterPassword.toCharArray()); |
| // keyManagers = keyManagerFactory.getKeyManagers(); |
| // } catch (Exception e) { |
| // throw new CMException( |
| // "Failed to create SSL socket factory: could not initiate SSL key manager", |
| // e); |
| // } |
| // } |
| // |
| // TrustManager[] trustManagers = null; |
| // synchronized (truststore) { // Truststore must not be null at this point!!! |
| // try { |
| // // Create our own TrustManager with 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 |
| // // e.g. REST activity that uses Apache HTTP client library |
| // return sc.getSocketFactory(); |
| // } |
| |
| /** |
| * Creates SSLSocketFactory based on Credential MAnager's Keystore and Truststore |
| * but only initalises Credential Manager when one of the methods needed for creating an |
| * HTTPS connection is invoked. |
| */ |
| public static SSLSocketFactory createTavernaSSLSocketFactory() 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 |
| // e.g. REST activity that uses Apache HTTP client library |
| return sc.getSocketFactory(); |
| } |
| |
| /** |
| * 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 static class TavernaKeyManager extends X509ExtendedKeyManager { |
| // The X509KeyManager as returned by the SunX509 provider, initialised with the Keystore. |
| 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 Service Panel. |
| private void init() throws Exception { |
| |
| logger.info("Credential Manager: inside TavernaKeyManager.init()"); |
| |
| // Create a "default" JSSE X509KeyManager. |
| KeyManagerFactory kmf = KeyManagerFactory.getInstance( |
| "SunX509", "SunJSSE"); |
| |
| if (INSTANCE == null){ |
| logger.info("Credential Manager: inside TavernaKeyManager.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{ |
| getInstance(); |
| logger.info("Credential Manager: inside TavernaKeyManager.init() - 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.info("Credential Manager: inside TavernaKeyManager.init() - Reinitialising the KeyManager."); |
| |
| kmf.init(keystore, masterPassword.toCharArray()); |
| |
| KeyManager kms[] = kmf.getKeyManagers(); |
| /* |
| * Iterate over the returned KeyManagers, look for an instance |
| * of X509KeyManager. If found, use that as our "default" key |
| * manager. |
| */ |
| for (int i = 0; i < kms.length; i++) { |
| if (kms[i] instanceof X509KeyManager) { |
| sunJSSEX509KeyManager = (X509KeyManager) kms[i]; |
| 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("Credential Manager: inside chooseClientAlias()"); |
| |
| // We have postponed initialisation until we are actually asked to do something |
| if (sunJSSEX509KeyManager == null){ |
| try { |
| init(); |
| } catch (Exception e) { |
| e.printStackTrace(); |
| 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.info("Credential Manager: inside getCertificateChain()"); |
| // We have postponed initialisation until we are actually asked to do something |
| if (sunJSSEX509KeyManager == null){ |
| try { |
| init(); |
| } catch (Exception e) { |
| e.printStackTrace(); |
| 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.info("Credential Manager: inside getClientAliases()"); |
| // We have postponed initialisation until we are actually asked to do something |
| if (sunJSSEX509KeyManager == null){ |
| try { |
| init(); |
| } catch (Exception e) { |
| e.printStackTrace(); |
| 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.info("Credential Manager: inside getPrivateKey()"); |
| // We have postponed initialisation until we are actually asked to do something |
| if (sunJSSEX509KeyManager == null){ |
| try { |
| init(); |
| } catch (Exception e) { |
| e.printStackTrace(); |
| 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 initilises 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 static 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. |
| */ |
| X509TrustManager sunJSSEX509TrustManager = 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 Service Panel. |
| private void init() throws Exception { |
| |
| logger.info("Credential Manager: inside TavernaTrustManager.init()"); |
| |
| // Create a "default" JSSE X509TrustManager. |
| TrustManagerFactory tmf = TrustManagerFactory.getInstance( |
| "SunX509", "SunJSSE"); |
| |
| if (INSTANCE == null){ |
| logger.info("Credential Manager: 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{ |
| getInstance(); |
| logger.info("Credential Manager: 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.info("Credential Manager: inside TavernaTrustManager.init() - Reinitialising the TrustManager."); |
| SSLSocketFactory.getDefault(); |
| tmf.init(truststore); |
| |
| TrustManager tms[] = tmf.getTrustManagers(); |
| /* |
| * Iterate over the returned TrustManagers, look for an instance |
| * of X509TrustManager. If found, use that as our "default" |
| * trust manager. |
| */ |
| for (int i = 0; i < tms.length; i++) { |
| if (tms[i] instanceof X509TrustManager) { |
| sunJSSEX509TrustManager = (X509TrustManager) tms[i]; |
| 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. |
| */ |
| 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. |
| */ |
| public void checkServerTrusted(X509Certificate[] chain, String authType) |
| throws CertificateException { |
| |
| // We have postponed initialisation until we are actually asked to do something |
| if (sunJSSEX509TrustManager == null){ |
| try { |
| init(); |
| } catch (Exception e) { |
| e.printStackTrace(); |
| logger.error(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; |
| } |
| } |
| } |
| |
| public X509Certificate[] getAcceptedIssuers() { |
| // We have postponed initialisation until we are actually asked to do something |
| if (sunJSSEX509TrustManager == null){ |
| try { |
| init(); |
| } catch (Exception e) { |
| e.printStackTrace(); |
| 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. |
| */ |
| public static 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 = createX509CertificateAlias(chain[0]); |
| try { |
| if (truststore.containsAlias(alias)) { |
| return true; |
| } |
| } catch (KeyStoreException e) { |
| // Ignore |
| } |
| |
| String name = chain[0].getSubjectX500Principal().getName(); |
| for (CredentialProviderSPI confirm : findMasterPasswordProviders()) { |
| if (!confirm.canHandleTrustConfirmation(chain)) { |
| continue; |
| } |
| TrustConfirmation confirmation = confirm.shouldTrust(chain); |
| if (confirmation == null) { |
| // SPI can't say yes or no, try next one |
| continue; |
| } |
| if (confirmation.isShouldTrust() && confirmation.isShouldSave()) { |
| try { |
| CredentialManager credManager = CredentialManager |
| .getInstance(); |
| credManager |
| .saveTrustedCertificate((X509Certificate) chain[0]); |
| logger.info("Stored trusted certificate " + name); |
| } catch (CMException ex) { |
| logger.error("Credential Manager failed to " |
| + "save trusted certificate " + name, ex); |
| } |
| } |
| if (logger.isDebugEnabled()) { |
| if (confirmation.isShouldTrust()) { |
| logger.debug("Trusting " + name + " according to " |
| + confirm); |
| } else { |
| logger.debug("Not trusting " + name + " according to " |
| + confirm); |
| } |
| } |
| return confirmation.isShouldTrust(); |
| } |
| logger |
| .warn("No ConfirmTrustedCertificateSPI instances could could confirm or deny trusting of " |
| + name); |
| // None of the SPIs (if any) could confirm |
| return false; |
| } |
| |
| /** |
| * Normalize an URI for insertion as the basis for path-recursive lookups, |
| * ie. strip query and filename. For example: <code> |
| * 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/ |
| * </code> |
| * <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 static URI normalizeServiceURI(URI serviceURI) { |
| try { |
| URI noUserInfo = setUserInfoForURI(serviceURI, null); |
| URI normalized = noUserInfo.normalize(); |
| URI parent = normalized.resolve("."); |
| // Strip userinfo, keep fragment |
| URI withFragment = setFragmentForURI(parent, serviceURI |
| .getFragment()); |
| return withFragment; |
| } catch (URISyntaxException ex) { |
| return serviceURI; |
| } |
| } |
| |
| public static URI setFragmentForURI(URI uri, String fragment) |
| throws URISyntaxException { |
| return new URI(uri.getScheme(), uri.getUserInfo(), uri.getHost(), uri |
| .getPort(), uri.getPath(), uri.getQuery(), fragment); |
| } |
| |
| public static URI setUserInfoForURI(URI uri, String userinfo) |
| throws URISyntaxException { |
| return new URI(uri.getScheme(), userinfo, uri.getHost(), uri.getPort(), |
| uri.getPath(), uri.getQuery(), uri.getFragment()); |
| } |
| |
| /** |
| * Reset the VMs 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 VMs cache could be reset, or |
| * <code>false</code> otherwise. |
| */ |
| 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 VM or internal Sun classes changed", |
| ex); |
| return false; |
| } |
| } |
| |
| public boolean hasUsernamePasswordForService(URI uri) throws CMException { |
| Map<URI, URI> mappedServiceURIs = getFragmentMappedURIsForAllUsernameAndPasswordPairs(); |
| for (URI possible : getPossibleServiceURIsToLookup(uri, true)) { |
| if (mappedServiceURIs.containsKey(possible)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| private static void loadDefaultSecurityFiles() { |
| if (credentialManagerDirectory == null){ |
| credentialManagerDirectory = CMUtils.getCredentialManagerDefaultDirectory(); |
| } |
| if (keystoreFile == null){ |
| keystoreFile = new File(credentialManagerDirectory, T2KEYSTORE_FILE); |
| } |
| if (truststoreFile == null){ |
| truststoreFile = new File(credentialManagerDirectory,T2TRUSTSTORE_FILE); |
| } |
| } |
| |
| private static void loadSecurityFiles(File credentialManagerDir) |
| throws CMException { |
| |
| if (credentialManagerDirectory == null) { |
| if (credentialManagerDir.exists()) { |
| credentialManagerDirectory = credentialManagerDir; |
| } |
| else{ |
| throw new CMException("Failed to open Credential Manager's directory to load the security files: Directory does not exist."); |
| } |
| } |
| if (keystoreFile == null){ |
| keystoreFile = new File(credentialManagerDirectory, T2KEYSTORE_FILE); |
| } |
| if (truststoreFile == null){ |
| truststoreFile = new File(credentialManagerDirectory,T2TRUSTSTORE_FILE); |
| } |
| } |
| |
| // 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> { |
| public void notify(Observable<KeystoreChangedEvent> sender, |
| KeystoreChangedEvent message) throws Exception { |
| // If Keystore has changed - possibly some password entries have changed |
| // (could be key entries that have chabged 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> { |
| public void notify(Observable<KeystoreChangedEvent> sender, |
| KeystoreChangedEvent message) throws Exception { |
| // Create the new SSLSocketFactory and set the default SSLContext |
| HttpsURLConnection.setDefaultSSLSocketFactory(createTavernaSSLSocketFactory()); |
| } |
| } |
| |
| public static boolean getUseDefaultMasterPassword(){ |
| if (credentialManagerDirectory == null){ |
| credentialManagerDirectory = CMUtils.getCredentialManagerDefaultDirectory(); |
| } |
| if (USER_SET_MASTER_PASSWORD_INDICATOR_FILE == null){ |
| USER_SET_MASTER_PASSWORD_INDICATOR_FILE = new File(credentialManagerDirectory, USER_SET_MASTER_PASSWORD_INDICATOR_FILE_NAME); |
| } |
| return !USER_SET_MASTER_PASSWORD_INDICATOR_FILE.exists(); |
| } |
| |
| public static void setUseDefaultMasterPassword(boolean useDefaultMasterPassword){ |
| if (credentialManagerDirectory == null){ |
| credentialManagerDirectory = CMUtils.getCredentialManagerDefaultDirectory(); |
| } |
| if (USER_SET_MASTER_PASSWORD_INDICATOR_FILE == null){ |
| USER_SET_MASTER_PASSWORD_INDICATOR_FILE = new File(credentialManagerDirectory, USER_SET_MASTER_PASSWORD_INDICATOR_FILE_NAME); |
| } |
| if (useDefaultMasterPassword){ |
| if (USER_SET_MASTER_PASSWORD_INDICATOR_FILE.exists()) { |
| // Delete the file |
| USER_SET_MASTER_PASSWORD_INDICATOR_FILE.delete(); |
| } |
| } else { |
| if (!USER_SET_MASTER_PASSWORD_INDICATOR_FILE.exists()) { |
| // Touch the file |
| try { |
| FileUtils.touch(USER_SET_MASTER_PASSWORD_INDICATOR_FILE); |
| } catch (IOException ioex) { |
| // Hmmm, ignore this? |
| logger.error(ioex); |
| } |
| } |
| } |
| } |
| } |