org.mule.api.security.tls.TlsConfiguration.java Source code

Java tutorial

Introduction

Here is the source code for org.mule.api.security.tls.TlsConfiguration.java

Source

/*
 * $Id$
 * --------------------------------------------------------------------------------------
 * Copyright (c) MuleSoft, Inc.  All rights reserved.  http://www.mulesoft.com
 *
 * The software in this package is published under the terms of the CPAL v1.0
 * license, a copy of which has been included with this distribution in the
 * LICENSE.txt file.
 */

package org.mule.api.security.tls;

import org.mule.api.lifecycle.CreateException;
import org.mule.api.security.TlsDirectKeyStore;
import org.mule.api.security.TlsDirectTrustStore;
import org.mule.api.security.TlsIndirectKeyStore;
import org.mule.api.security.TlsProtocolHandler;
import org.mule.api.security.provider.AutoDiscoverySecurityProviderFactory;
import org.mule.api.security.provider.SecurityProviderFactory;
import org.mule.api.security.provider.SecurityProviderInfo;
import org.mule.config.i18n.CoreMessages;
import org.mule.util.FileUtils;
import org.mule.util.IOUtils;
import org.mule.util.StringUtils;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.security.GeneralSecurityException;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.Provider;
import java.security.Security;
import java.util.Enumeration;

import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLServerSocketFactory;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * Support for configuring TLS/SSL connections.
 * <p/>
 * <h2>Introduction</h2>
 * <p/>
 * This class was introduced to centralise the work of TLS/SSL configuration.  It is intended
 * to be backwards compatible with earlier code (as much as possible) and so is perhaps more
 * complex than would be necessary if starting from zero - the main source of confusion is the
 * distinction between direct and indirect creation of sockets and stores.
 * <p/>
 * <h2>Configuration</h2>
 * <p/>
 * The documentation in this class is intended more for programmers than end uses.  If you are
 * configuring a connector the interfaces {@link org.mule.api.security.TlsIndirectTrustStore},
 * {@link TlsDirectTrustStore},
 * {@link TlsDirectKeyStore} and {@link TlsIndirectKeyStore} should provide guidance to individual
 * properties.  In addition you should check the documentation for the specific protocol / connector
 * used and may also need to read the discussion on direct and indirect socket and store creation
 * below (or, more simply, just use whichever key store interface your connector implements!).
 * <p/>
 * <h2>Programming</h2>
 * <p/>
 * This class is intended to be used as a delegate as we typically want to add security to an
 * already existing connector (so we inherit from that connector, implement the appropriate
 * interfaces from {@link org.mule.api.security.TlsIndirectTrustStore}, {@link TlsDirectTrustStore},
 * {@link TlsDirectKeyStore} and {@link TlsIndirectKeyStore}, and then forward calls to the
 * interfaces to an instance of this class).
 * <p/>
 * <p>For setting System properties (and reading them) use {@link TlsPropertiesMapper}.  This
 * can take a "namespace" which can then be used by {@link TlsPropertiesSocketFactory} to
 * construct an appropriate socket factory.  This approach (storing to properties and then
 * retrieving that information later in a socket factory) lets us pass TLS/SSL configuration
 * into libraries that are configured by specifying on the socket factory class.</p>
 * <p/>
 * <h2>Direct and Indirect Socket and Store Creation</h2>
 * <p/>
 * For the SSL transport, which historically defined parameters for many different secure
 * transports, the configuration interfaces worked as follows:
 * <p/>
 * <dl>
 * <dt>{@link TlsDirectTrustStore}</dt><dd>Used to generate trust store directly and indirectly
 * for all TLS/SSL conections via System properties</dd>
 * <dt>{@link TlsDirectKeyStore}</dt><dd>Used to generate key store directly</dd>
 * <dt>{@link TlsIndirectKeyStore}</dt><dd>Used to generate key store indirectly for all
 * TLS/SSL conections via System properties</dd>
 * </dl>
 * <p/>
 * Historically, many other transports relied on the indirect configurations defined above.
 * So they implemented {@link org.mule.api.security.TlsIndirectTrustStore}
 * (a superclass of {@link TlsDirectTrustStore})
 * and relied on {@link TlsIndirectKeyStore} from the SSL configuration.  For continuity these
 * interfaces continue to be used, even though
 * the configurations are now typically (see individual connector/protocol documentation) specific
 * to a protocol or connector.  <em>Note - these interfaces are new, but the original code had
 * those methods, used as described.  The new interfaces only make things explicit.</em>
 * <p/>
 * <p><em>Note for programmers</em> One way to understand the above is to see that many
 * protocols are handled by libraries that are configured by providing either properties or
 * a socket factory.  In both cases (the latter via {@link TlsPropertiesSocketFactory}) we
 * continue to use properties and the "indirect" interface.  Note also that the mapping
 * in {@link TlsPropertiesMapper} correctly handles the asymmetry, so an initial call to
 * {@link TlsConfiguration} uses the keystore defined via {@link TlsDirectKeyStore}, but
 * when a {@link TlsConfiguration} is retrieved from System proerties using
 * {@link TlsPropertiesMapper#readFromProperties(TlsConfiguration,java.util.Properties)}
 * the "indirect" properties are supplied as "direct" values, meaning that the "indirect"
 * socket factory can be retrieved from {@link #getKeyManagerFactory()}.  It just works.</p>
 */
public final class TlsConfiguration
        implements TlsDirectTrustStore, TlsDirectKeyStore, TlsIndirectKeyStore, TlsProtocolHandler {
    public static final String DEFAULT_KEYSTORE = ".keystore";
    public static final String DEFAULT_KEYSTORE_TYPE = KeyStore.getDefaultType();
    public static final String JSSE_NAMESPACE = "javax.net";

    private Log logger = LogFactory.getLog(getClass());

    private SecurityProviderFactory spFactory = new AutoDiscoverySecurityProviderFactory();
    private SecurityProviderInfo spInfo = spFactory.getSecurityProviderInfo();
    private Provider provider = spFactory.getProvider();
    private String sslType = spInfo.getDefaultSslType();

    // global
    private String protocolHandler = spInfo.getProtocolHandler();

    // this is the key store that is generated in-memory and available to connectors explicitly.
    // it is local to the socket.
    private String keyStoreName = DEFAULT_KEYSTORE; // was default in https but not ssl
    private String keyAlias = null;
    private String keyPassword = null;
    private String keyStorePassword = null;
    private String keystoreType = DEFAULT_KEYSTORE_TYPE;
    private String keyManagerAlgorithm = spInfo.getKeyManagerAlgorithm();
    private KeyManagerFactory keyManagerFactory = null;

    // this is the key store defined in system properties that is used implicitly.
    // note that some transports use different namespaces within system properties,
    // so this is typically global across a particular transport.
    // it is also used as the trust store defined in system properties if no other trust
    // store is given and explicitTrustStoreOnly is false
    private String clientKeyStoreName = null;
    private String clientKeyStorePassword = null;
    private String clientKeyStoreType = DEFAULT_KEYSTORE_TYPE;

    // this is the trust store used to construct sockets both explicitly
    // and globally (if not set, see client key above) via the jvm defaults.
    private String trustStoreName = null;
    private String trustStorePassword = null;
    private String trustStoreType = DEFAULT_KEYSTORE_TYPE;
    private String trustManagerAlgorithm = spInfo.getKeyManagerAlgorithm();
    private TrustManagerFactory trustManagerFactory = null;
    private boolean explicitTrustStoreOnly = false;
    private boolean requireClientAuthentication = false;

    /**
     * Support for TLS connections with a given initial value for the key store
     *
     * @param keyStore initial value for the key store
     */
    public TlsConfiguration(String keyStore) {
        this.keyStoreName = keyStore;
    }

    // note - in what follows i'm using "raw" variables rather than accessors because
    // i think the names are clearer.  the API names for the accessors are historical
    // and not a close fit to actual use (imho).

    /**
     * @param anon      If the connection is anonymous then we don't care about client keys
     * @param namespace Namespace to use for global properties (for JSSE use JSSE_NAMESPACE)
     * @throws CreateException ON initialisation problems
     */
    public void initialise(boolean anon, String namespace) throws CreateException {
        if (logger.isDebugEnabled()) {
            logger.debug("initialising: anon " + anon);
        }
        validate(anon);

        Security.addProvider(provider);
        System.setProperty("java.protocol.handler.pkgs", protocolHandler);

        if (!anon) {
            initKeyManagerFactory();
        }
        initTrustManagerFactory();

        if (null != namespace) {
            new TlsPropertiesMapper(namespace).writeToProperties(System.getProperties(), this);
        }
    }

    private void validate(boolean anon) throws CreateException {
        assertNotNull(getProvider(), "The security provider cannot be null");
        if (!anon) {
            assertNotNull(getKeyStore(), "The KeyStore location cannot be null");
            assertNotNull(getKeyPassword(), "The Key password cannot be null");
            assertNotNull(getKeyStorePassword(), "The KeyStore password cannot be null");
            assertNotNull(getKeyManagerAlgorithm(), "The Key Manager Algorithm cannot be null");
        }
    }

    private void initKeyManagerFactory() throws CreateException {
        if (logger.isDebugEnabled()) {
            logger.debug("initialising key manager factory from keystore data");
        }

        KeyStore tempKeyStore;
        try {
            tempKeyStore = loadKeyStore();
            checkKeyStoreContainsAlias(tempKeyStore);
        } catch (Exception e) {
            throw new CreateException(CoreMessages.failedToLoad("KeyStore: " + keyStoreName), e, this);
        }

        try {
            keyManagerFactory = KeyManagerFactory.getInstance(getKeyManagerAlgorithm());
            keyManagerFactory.init(tempKeyStore, keyPassword.toCharArray());
        } catch (Exception e) {
            throw new CreateException(CoreMessages.failedToLoad("Key Manager"), e, this);
        }
    }

    protected KeyStore loadKeyStore() throws GeneralSecurityException, IOException {
        KeyStore tempKeyStore = KeyStore.getInstance(keystoreType);

        InputStream is = IOUtils.getResourceAsStream(keyStoreName, getClass());
        if (null == is) {
            throw new FileNotFoundException(
                    CoreMessages.cannotLoadFromClasspath("Keystore: " + keyStoreName).getMessage());
        }

        tempKeyStore.load(is, keyStorePassword.toCharArray());
        return tempKeyStore;
    }

    protected void checkKeyStoreContainsAlias(KeyStore keyStore) throws KeyStoreException {
        if (StringUtils.isNotBlank(keyAlias)) {
            boolean keyAliasFound = false;

            Enumeration<String> aliases = keyStore.aliases();
            while (aliases.hasMoreElements()) {
                String alias = aliases.nextElement();

                if (alias.equals(keyAlias)) {
                    // if alias is found all is valid but continue processing to strip out all
                    // other (unwanted) keys
                    keyAliasFound = true;
                } else {
                    // if the current alias is not the one we are looking for, remove
                    // it from the keystore
                    keyStore.deleteEntry(alias);
                }
            }

            // if the alias was not found, throw an exception
            if (!keyAliasFound) {
                throw new IllegalStateException("Key with alias \"" + keyAlias + "\" was not found");
            }
        }
    }

    private void initTrustManagerFactory() throws CreateException {
        if (null != trustStoreName) {
            trustStorePassword = null == trustStorePassword ? "" : trustStorePassword;

            KeyStore trustStore;
            try {
                trustStore = KeyStore.getInstance(trustStoreType);
                InputStream is = IOUtils.getResourceAsStream(trustStoreName, getClass());
                if (null == is) {
                    throw new FileNotFoundException(
                            "Failed to load truststore from classpath or local file: " + trustStoreName);
                }
                trustStore.load(is, trustStorePassword.toCharArray());
            } catch (Exception e) {
                throw new CreateException(CoreMessages.failedToLoad("TrustStore: " + trustStoreName), e, this);
            }

            try {
                trustManagerFactory = TrustManagerFactory.getInstance(trustManagerAlgorithm);
                trustManagerFactory.init(trustStore);
            } catch (Exception e) {
                throw new CreateException(
                        CoreMessages.failedToLoad("Trust Manager (" + trustManagerAlgorithm + ")"), e, this);
            }
        }
    }

    private static void assertNotNull(Object value, String message) {
        if (null == value) {
            throw new IllegalArgumentException(message);
        }
    }

    private static String defaultForNull(String value, String deflt) {
        if (null == value) {
            return deflt;
        } else {
            return value;
        }
    }

    public SSLSocketFactory getSocketFactory() throws NoSuchAlgorithmException, KeyManagementException {
        return getSslContext().getSocketFactory();
    }

    public SSLServerSocketFactory getServerSocketFactory() throws NoSuchAlgorithmException, KeyManagementException {
        return getSslContext().getServerSocketFactory();
    }

    public SSLContext getSslContext() throws NoSuchAlgorithmException, KeyManagementException {
        KeyManager[] keyManagers = null == getKeyManagerFactory() ? null : getKeyManagerFactory().getKeyManagers();
        TrustManager[] trustManagers = null == getTrustManagerFactory() ? null
                : getTrustManagerFactory().getTrustManagers();

        SSLContext context = SSLContext.getInstance(getSslType());
        // TODO - nice to have a configurable random number source set here
        context.init(keyManagers, trustManagers, null);
        return context;
    }

    public String getSslType() {
        return sslType;
    }

    public void setSslType(String sslType) {
        this.sslType = sslType;
    }

    public Provider getProvider() {
        return provider;
    }

    public void setProvider(Provider provider) {
        this.provider = provider;
    }

    @Override
    public String getProtocolHandler() {
        return protocolHandler;
    }

    @Override
    public void setProtocolHandler(String protocolHandler) {
        this.protocolHandler = protocolHandler;
    }

    public SecurityProviderFactory getSecurityProviderFactory() {
        return spFactory;
    }

    public void setSecurityProviderFactory(SecurityProviderFactory spFactory) {
        this.spFactory = spFactory;
    }

    // access to the explicit key store variables

    @Override
    public String getKeyStore() {
        return keyStoreName;
    }

    @Override
    public void setKeyStore(String name) throws IOException {
        keyStoreName = name;
        if (null != keyStoreName) {
            keyStoreName = FileUtils.getResourcePath(keyStoreName, getClass());
            if (logger.isDebugEnabled()) {
                logger.debug("Normalised keyStore path to: " + keyStoreName);
            }
        }
    }

    @Override
    public String getKeyPassword() {
        return keyPassword;
    }

    @Override
    public void setKeyPassword(String keyPassword) {
        this.keyPassword = keyPassword;
    }

    @Override
    public String getKeyStorePassword() {
        return keyStorePassword;
    }

    @Override
    public void setKeyStorePassword(String storePassword) {
        this.keyStorePassword = storePassword;
    }

    @Override
    public String getKeyStoreType() {
        return keystoreType;
    }

    @Override
    public void setKeyStoreType(String keystoreType) {
        this.keystoreType = keystoreType;
    }

    @Override
    public String getKeyManagerAlgorithm() {
        return keyManagerAlgorithm;
    }

    @Override
    public void setKeyManagerAlgorithm(String keyManagerAlgorithm) {
        this.keyManagerAlgorithm = keyManagerAlgorithm;
    }

    @Override
    public KeyManagerFactory getKeyManagerFactory() {
        return keyManagerFactory;
    }

    // access to the implicit key store variables

    @Override
    public String getClientKeyStore() {
        return clientKeyStoreName;
    }

    @Override
    public void setClientKeyStore(String name) throws IOException {
        clientKeyStoreName = name;
        if (null != clientKeyStoreName) {
            clientKeyStoreName = FileUtils.getResourcePath(clientKeyStoreName, getClass());
            if (logger.isDebugEnabled()) {
                logger.debug("Normalised clientKeyStore path to: " + clientKeyStoreName);
            }
        }
    }

    @Override
    public String getClientKeyStorePassword() {
        return clientKeyStorePassword;
    }

    @Override
    public void setClientKeyStorePassword(String clientKeyStorePassword) {
        this.clientKeyStorePassword = clientKeyStorePassword;
    }

    @Override
    public void setClientKeyStoreType(String clientKeyStoreType) {
        this.clientKeyStoreType = clientKeyStoreType;
    }

    @Override
    public String getClientKeyStoreType() {
        return clientKeyStoreType;
    }

    // access to trust store variables

    @Override
    public String getTrustStore() {
        return trustStoreName;
    }

    @Override
    public void setTrustStore(String name) throws IOException {
        trustStoreName = name;
        if (null != trustStoreName) {
            trustStoreName = FileUtils.getResourcePath(trustStoreName, getClass());
            if (logger.isDebugEnabled()) {
                logger.debug("Normalised trustStore path to: " + trustStoreName);
            }
        }
    }

    @Override
    public String getTrustStorePassword() {
        return trustStorePassword;
    }

    @Override
    public void setTrustStorePassword(String trustStorePassword) {
        this.trustStorePassword = trustStorePassword;
    }

    @Override
    public String getTrustStoreType() {
        return trustStoreType;
    }

    @Override
    public void setTrustStoreType(String trustStoreType) {
        this.trustStoreType = trustStoreType;
    }

    @Override
    public String getTrustManagerAlgorithm() {
        return trustManagerAlgorithm;
    }

    @Override
    public void setTrustManagerAlgorithm(String trustManagerAlgorithm) {
        this.trustManagerAlgorithm = defaultForNull(trustManagerAlgorithm, spInfo.getKeyManagerAlgorithm());
    }

    @Override
    public TrustManagerFactory getTrustManagerFactory() {
        return trustManagerFactory;
    }

    @Override
    public void setTrustManagerFactory(TrustManagerFactory trustManagerFactory) {
        this.trustManagerFactory = trustManagerFactory;
    }

    @Override
    public boolean isExplicitTrustStoreOnly() {
        return explicitTrustStoreOnly;
    }

    @Override
    public void setExplicitTrustStoreOnly(boolean explicitTrustStoreOnly) {
        this.explicitTrustStoreOnly = explicitTrustStoreOnly;
    }

    @Override
    public boolean isRequireClientAuthentication() {
        return requireClientAuthentication;
    }

    @Override
    public void setRequireClientAuthentication(boolean requireClientAuthentication) {
        this.requireClientAuthentication = requireClientAuthentication;
    }

    @Override
    public String getKeyAlias() {
        return keyAlias;
    }

    @Override
    public void setKeyAlias(String keyAlias) {
        this.keyAlias = keyAlias;
    }
}