/*
* 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.runtime.core.api.security.tls;
import static org.mule.runtime.core.api.config.MuleProperties.SYSTEM_PROPERTY_PREFIX;
import org.mule.runtime.api.lifecycle.CreateException;
import org.mule.runtime.core.api.security.TlsDirectKeyStore;
import org.mule.runtime.core.api.security.TlsDirectTrustStore;
import org.mule.runtime.core.api.security.TlsIndirectKeyStore;
import org.mule.runtime.core.config.i18n.CoreMessages;
import org.mule.runtime.core.util.ArrayUtils;
import org.mule.runtime.core.util.FileUtils;
import org.mule.runtime.core.util.IOUtils;
import org.mule.runtime.core.util.SecurityUtils;
import org.mule.runtime.core.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.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.lang.BooleanUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* 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.runtime.core.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.runtime.core.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.runtime.core.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 {
public static final String DEFAULT_KEYSTORE = ".keystore";
public static final String DEFAULT_KEYSTORE_TYPE = KeyStore.getDefaultType();
public static final String DEFAULT_KEYMANAGER_ALGORITHM = KeyManagerFactory.getDefaultAlgorithm();
public static final String DEFAULT_SSL_TYPE = "TLSv1";
public static final String JSSE_NAMESPACE = "javax.net";
public static final String PROPERTIES_FILE_PATTERN = "tls-%s.conf";
public static final String DEFAULT_SECURITY_MODEL = "default";
public static final String FIPS_SECURITY_MODEL = "fips140-2";
public static final String DISABLE_SYSTEM_PROPERTIES_MAPPING_PROPERTY =
SYSTEM_PROPERTY_PREFIX + "tls.disableSystemPropertiesMapping";
private Logger logger = LoggerFactory.getLogger(getClass());
private String sslType = DEFAULT_SSL_TYPE;
// 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 = DEFAULT_KEYMANAGER_ALGORITHM;
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 = DEFAULT_KEYMANAGER_ALGORITHM;
private TrustManagerFactory trustManagerFactory = null;
private boolean explicitTrustStoreOnly = false;
private boolean requireClientAuthentication = false;
private TlsProperties tlsProperties = new TlsProperties();
private boolean disableSystemPropertiesMapping = true;
/**
* 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;
String disableSystemPropertiesMappingValue = System.getProperty(DISABLE_SYSTEM_PROPERTIES_MAPPING_PROPERTY);
if (disableSystemPropertiesMappingValue != null) {
disableSystemPropertiesMapping = BooleanUtils.toBoolean(disableSystemPropertiesMappingValue);
}
}
// 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);
if (!anon) {
initKeyManagerFactory();
}
initTrustManagerFactory();
if (logger.isDebugEnabled()) {
logger.debug("TLS system properties mapping is " + (disableSystemPropertiesMapping ? "disabled" : "enabled"));
}
if (null != namespace && !disableSystemPropertiesMapping) {
new TlsPropertiesMapper(namespace).writeToProperties(System.getProperties(), this);
}
tlsProperties.load(String.format(PROPERTIES_FILE_PATTERN, SecurityUtils.getSecurityModel()));
}
private void validate(boolean anon) throws CreateException {
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 new RestrictedSSLSocketFactory(getSslContext(), getEnabledCipherSuites(), getEnabledProtocols());
}
public SSLServerSocketFactory getServerSocketFactory() throws NoSuchAlgorithmException, KeyManagementException {
return new RestrictedSSLServerSocketFactory(getSslContext(), getEnabledCipherSuites(), getEnabledProtocols());
}
public String[] getEnabledCipherSuites() {
return tlsProperties.getEnabledCipherSuites();
}
public String[] getEnabledProtocols() {
return tlsProperties.getEnabledProtocols();
}
public SSLContext getSslContext() throws NoSuchAlgorithmException, KeyManagementException {
TrustManager[] trustManagers = null == getTrustManagerFactory() ? null : getTrustManagerFactory().getTrustManagers();
return getSslContext(trustManagers);
}
public SSLContext getSslContext(TrustManager[] trustManagers) throws NoSuchAlgorithmException, KeyManagementException {
KeyManager[] keyManagers = null == getKeyManagerFactory() ? null : getKeyManagerFactory().getKeyManagers();
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) {
String[] enabledProtocols = tlsProperties.getEnabledProtocols();
if (enabledProtocols != null && !ArrayUtils.contains(enabledProtocols, sslType)) {
throw new IllegalArgumentException(String.format("Protocol %s is not allowed in current configuration", sslType));
}
this.sslType = sslType;
}
// 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, DEFAULT_KEYMANAGER_ALGORITHM);
}
@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;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof TlsConfiguration)) {
return false;
}
TlsConfiguration that = (TlsConfiguration) o;
if (explicitTrustStoreOnly != that.explicitTrustStoreOnly) {
return false;
}
if (requireClientAuthentication != that.requireClientAuthentication) {
return false;
}
if (clientKeyStoreName != null ? !clientKeyStoreName.equals(that.clientKeyStoreName) : that.clientKeyStoreName != null) {
return false;
}
if (clientKeyStorePassword != null ? !clientKeyStorePassword.equals(that.clientKeyStorePassword)
: that.clientKeyStorePassword != null) {
return false;
}
if (clientKeyStoreType != null ? !clientKeyStoreType.equals(that.clientKeyStoreType) : that.clientKeyStoreType != null) {
return false;
}
if (keyAlias != null ? !keyAlias.equals(that.keyAlias) : that.keyAlias != null) {
return false;
}
if (keyManagerAlgorithm != null ? !keyManagerAlgorithm.equals(that.keyManagerAlgorithm) : that.keyManagerAlgorithm != null) {
return false;
}
if (keyManagerFactory != null ? !keyManagerFactory.equals(that.keyManagerFactory) : that.keyManagerFactory != null) {
return false;
}
if (keyPassword != null ? !keyPassword.equals(that.keyPassword) : that.keyPassword != null) {
return false;
}
if (keyStoreName != null ? !keyStoreName.equals(that.keyStoreName) : that.keyStoreName != null) {
return false;
}
if (keyStorePassword != null ? !keyStorePassword.equals(that.keyStorePassword) : that.keyStorePassword != null) {
return false;
}
if (keystoreType != null ? !keystoreType.equals(that.keystoreType) : that.keystoreType != null) {
return false;
}
if (sslType != null ? !sslType.equals(that.sslType) : that.sslType != null) {
return false;
}
if (tlsProperties != null ? !tlsProperties.equals(that.tlsProperties) : that.tlsProperties != null) {
return false;
}
if (trustManagerAlgorithm != null ? !trustManagerAlgorithm.equals(that.trustManagerAlgorithm)
: that.trustManagerAlgorithm != null) {
return false;
}
if (trustManagerFactory != null ? !trustManagerFactory.equals(that.trustManagerFactory) : that.trustManagerFactory != null) {
return false;
}
if (trustStoreName != null ? !trustStoreName.equals(that.trustStoreName) : that.trustStoreName != null) {
return false;
}
if (trustStorePassword != null ? !trustStorePassword.equals(that.trustStorePassword) : that.trustStorePassword != null) {
return false;
}
if (trustStoreType != null ? !trustStoreType.equals(that.trustStoreType) : that.trustStoreType != null) {
return false;
}
return true;
}
@Override
public int hashCode() {
int result = sslType != null ? sslType.hashCode() : 0;
int hashcodePrimeNumber = 31;
result = hashcodePrimeNumber * result + (keyStoreName != null ? keyStoreName.hashCode() : 0);
result = hashcodePrimeNumber * result + (keyAlias != null ? keyAlias.hashCode() : 0);
result = hashcodePrimeNumber * result + (keyPassword != null ? keyPassword.hashCode() : 0);
result = hashcodePrimeNumber * result + (keyStorePassword != null ? keyStorePassword.hashCode() : 0);
result = hashcodePrimeNumber * result + (keystoreType != null ? keystoreType.hashCode() : 0);
result = hashcodePrimeNumber * result + (keyManagerAlgorithm != null ? keyManagerAlgorithm.hashCode() : 0);
result = hashcodePrimeNumber * result + (keyManagerFactory != null ? keyManagerFactory.hashCode() : 0);
result = hashcodePrimeNumber * result + (clientKeyStoreName != null ? clientKeyStoreName.hashCode() : 0);
result = hashcodePrimeNumber * result + (clientKeyStorePassword != null ? clientKeyStorePassword.hashCode() : 0);
result = hashcodePrimeNumber * result + (clientKeyStoreType != null ? clientKeyStoreType.hashCode() : 0);
result = hashcodePrimeNumber * result + (trustStoreName != null ? trustStoreName.hashCode() : 0);
result = hashcodePrimeNumber * result + (trustStorePassword != null ? trustStorePassword.hashCode() : 0);
result = hashcodePrimeNumber * result + (trustStoreType != null ? trustStoreType.hashCode() : 0);
result = hashcodePrimeNumber * result + (trustManagerAlgorithm != null ? trustManagerAlgorithm.hashCode() : 0);
result = hashcodePrimeNumber * result + (trustManagerFactory != null ? trustManagerFactory.hashCode() : 0);
result = hashcodePrimeNumber * result + (explicitTrustStoreOnly ? 1 : 0);
result = hashcodePrimeNumber * result + (requireClientAuthentication ? 1 : 0);
result = hashcodePrimeNumber * result + (tlsProperties != null ? tlsProperties.hashCode() : 0);
return result;
}
}