com.amazon.dlic.util.SettingsBasedSSLConfigurator Maven / Gradle / Ivy
/*
* Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* or in the "license" file accompanying this file. This file is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
package com.amazon.dlic.util;
import java.net.Socket;
import java.nio.file.Path;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.SecureRandom;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.KeyManager;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import org.apache.http.conn.ssl.DefaultHostnameVerifier;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.nio.conn.ssl.SSLIOSessionStrategy;
import org.apache.http.ssl.PrivateKeyDetails;
import org.apache.http.ssl.PrivateKeyStrategy;
import org.apache.http.ssl.SSLContextBuilder;
import org.apache.http.ssl.SSLContexts;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.elasticsearch.common.settings.Settings;
import com.amazon.opendistroforelasticsearch.security.ssl.util.SSLConfigConstants;
import com.amazon.opendistroforelasticsearch.security.support.PemKeyReader;
import com.google.common.collect.ImmutableList;
public class SettingsBasedSSLConfigurator {
private static final Logger log = LogManager.getLogger(SettingsBasedSSLConfigurator.class);
public static final String CERT_ALIAS = "cert_alias";
public static final String CA_ALIAS = "ca_alias";
public static final String ENABLE_SSL = "enable_ssl";
/**
* Shall STARTTLS shall be used?
*
* NOTE: The setting of this option is only reflected by the startTlsEnabled
* attribute of the returned SSLConfig object. Clients of this class need to
* take further measures to enable STARTTLS. It does not affect the
* SSLIOSessionStrategy and SSLConnectionSocketFactory objects returned from
* this class.
*/
public static final String ENABLE_START_TLS = "enable_start_tls";
public static final String ENABLE_SSL_CLIENT_AUTH = "enable_ssl_client_auth";
public static final String PEMKEY_FILEPATH = "pemkey_filepath";
public static final String PEMKEY_CONTENT = "pemkey_content";
public static final String PEMKEY_PASSWORD = "pemkey_password";
public static final String PEMCERT_FILEPATH = "pemcert_filepath";
public static final String PEMCERT_CONTENT = "pemcert_content";
public static final String PEMTRUSTEDCAS_CONTENT = "pemtrustedcas_content";
public static final String PEMTRUSTEDCAS_FILEPATH = "pemtrustedcas_filepath";
public static final String VERIFY_HOSTNAMES = "verify_hostnames";
public static final String TRUST_ALL = "trust_all";
private static final List DEFAULT_TLS_PROTOCOLS = ImmutableList.of("TLSv1.2", "TLSv1.1");
private SSLContextBuilder sslContextBuilder;
private final Settings settings;
private final String settingsKeyPrefix;
private final Path configPath;
private final String clientName;
private boolean enabled;
private boolean enableSslClientAuth;
private KeyStore effectiveTruststore;
private KeyStore effectiveKeystore;
private char[] effectiveKeyPassword;
private String effectiveKeyAlias;
private List effectiveTruststoreAliases;
public SettingsBasedSSLConfigurator(Settings settings, Path configPath, String settingsKeyPrefix,
String clientName) {
this.settings = settings;
this.configPath = configPath;
this.settingsKeyPrefix = normalizeSettingsKeyPrefix(settingsKeyPrefix);
this.clientName = clientName != null ? clientName : this.settingsKeyPrefix;
}
public SettingsBasedSSLConfigurator(Settings settings, Path configPath, String settingsKeyPrefix) {
this(settings, configPath, settingsKeyPrefix, null);
}
SSLContext buildSSLContext() throws SSLConfigException {
try {
if (isTrustAllEnabled()) {
sslContextBuilder = new OverlyTrustfulSSLContextBuilder();
} else {
sslContextBuilder = SSLContexts.custom();
}
configureWithSettings();
if (!this.enabled) {
return null;
}
return sslContextBuilder.build();
} catch (NoSuchAlgorithmException | KeyStoreException | KeyManagementException e) {
throw new SSLConfigException("Error while initializing SSL configuration for " + this.clientName, e);
}
}
public SSLConfig buildSSLConfig() throws SSLConfigException {
SSLContext sslContext = buildSSLContext();
if (sslContext == null) {
// disabled
return null;
}
return new SSLConfig(sslContext, getSupportedProtocols(), getSupportedCipherSuites(), getHostnameVerifier(),
isHostnameVerificationEnabled(), isTrustAllEnabled(), isStartTlsEnabled(), this.effectiveTruststore,
this.effectiveTruststoreAliases, this.effectiveKeystore, this.effectiveKeyPassword,
this.effectiveKeyAlias);
}
private boolean isHostnameVerificationEnabled() {
return getSettingAsBoolean(VERIFY_HOSTNAMES, true) && !isTrustAllEnabled();
}
private HostnameVerifier getHostnameVerifier() {
if (isHostnameVerificationEnabled()) {
return new DefaultHostnameVerifier();
} else {
return NoopHostnameVerifier.INSTANCE;
}
}
private String[] getSupportedProtocols() {
return getSettingAsArray("enabled_ssl_protocols", DEFAULT_TLS_PROTOCOLS);
}
private String[] getSupportedCipherSuites() {
return getSettingAsArray("enabled_ssl_ciphers", null);
}
private boolean isStartTlsEnabled() {
return getSettingAsBoolean(ENABLE_START_TLS, false);
}
private boolean isTrustAllEnabled() {
return getSettingAsBoolean(TRUST_ALL, false);
}
private void configureWithSettings() throws SSLConfigException, NoSuchAlgorithmException, KeyStoreException {
this.enabled = getSettingAsBoolean(ENABLE_SSL, false);
if (!this.enabled) {
return;
}
this.enableSslClientAuth = getSettingAsBoolean(ENABLE_SSL_CLIENT_AUTH, false);
if (settings.get(settingsKeyPrefix + PEMTRUSTEDCAS_FILEPATH, null) != null
|| settings.get(settingsKeyPrefix + PEMTRUSTEDCAS_CONTENT, null) != null) {
initFromPem();
} else {
initFromKeyStore();
}
if (effectiveTruststore != null) {
sslContextBuilder.loadTrustMaterial(effectiveTruststore, null);
}
if (enableSslClientAuth) {
if (effectiveKeystore != null) {
try {
sslContextBuilder.loadKeyMaterial(effectiveKeystore, effectiveKeyPassword,
new PrivateKeyStrategy() {
@Override
public String chooseAlias(Map aliases, Socket socket) {
if (aliases == null || aliases.isEmpty()) {
return effectiveKeyAlias;
}
if (effectiveKeyAlias == null || effectiveKeyAlias.isEmpty()) {
return aliases.keySet().iterator().next();
}
return effectiveKeyAlias;
}
});
} catch (UnrecoverableKeyException e) {
throw new RuntimeException(e);
}
}
}
}
private void initFromPem() throws SSLConfigException {
X509Certificate[] trustCertificates;
try {
trustCertificates = PemKeyReader.loadCertificatesFromStream(
PemKeyReader.resolveStream(settingsKeyPrefix + PEMTRUSTEDCAS_CONTENT, settings));
} catch (Exception e) {
throw new SSLConfigException(
"Error loading PEM from " + settingsKeyPrefix + PEMTRUSTEDCAS_CONTENT + " for " + this.clientName,
e);
}
if (trustCertificates == null) {
String path = PemKeyReader.resolve(settingsKeyPrefix + PEMTRUSTEDCAS_FILEPATH, settings, configPath,
!isTrustAllEnabled());
try {
trustCertificates = PemKeyReader.loadCertificatesFromFile(path);
} catch (Exception e) {
throw new SSLConfigException("Error loading PEM from " + path + " (" + settingsKeyPrefix
+ PEMTRUSTEDCAS_FILEPATH + ") for " + this.clientName, e);
}
}
// for client authentication
X509Certificate[] authenticationCertificate;
try {
authenticationCertificate = PemKeyReader.loadCertificatesFromStream(
PemKeyReader.resolveStream(settingsKeyPrefix + PEMCERT_CONTENT, settings));
} catch (Exception e) {
throw new SSLConfigException(
"Error loading PEM from " + settingsKeyPrefix + PEMCERT_CONTENT + " for " + this.clientName, e);
}
if (authenticationCertificate == null) {
String path = PemKeyReader.resolve(settingsKeyPrefix + PEMCERT_FILEPATH, settings, configPath,
enableSslClientAuth);
try {
authenticationCertificate = PemKeyReader.loadCertificatesFromFile(path);
} catch (Exception e) {
throw new SSLConfigException("Error loading PEM from " + path + " (" + settingsKeyPrefix
+ PEMCERT_FILEPATH + ") for " + this.clientName, e);
}
}
PrivateKey authenticationKey;
try {
authenticationKey = PemKeyReader.loadKeyFromStream(getSetting(PEMKEY_PASSWORD),
PemKeyReader.resolveStream(settingsKeyPrefix + PEMKEY_CONTENT, settings));
} catch (Exception e) {
throw new SSLConfigException(
"Error loading PEM from " + settingsKeyPrefix + PEMKEY_CONTENT + " for " + this.clientName, e);
}
if (authenticationKey == null) {
String path = PemKeyReader.resolve(settingsKeyPrefix + PEMKEY_FILEPATH, settings, configPath,
enableSslClientAuth);
try {
authenticationKey = PemKeyReader.loadKeyFromFile(getSetting(PEMKEY_PASSWORD), path);
} catch (Exception e) {
throw new SSLConfigException("Error loading PEM from " + path + " (" + settingsKeyPrefix
+ PEMKEY_FILEPATH + ") for " + this.clientName, e);
}
}
try {
effectiveKeyPassword = PemKeyReader.randomChars(12);
effectiveKeyAlias = "al";
effectiveTruststore = PemKeyReader.toTruststore(effectiveKeyAlias, trustCertificates);
effectiveKeystore = PemKeyReader.toKeystore(effectiveKeyAlias, effectiveKeyPassword,
authenticationCertificate, authenticationKey);
} catch (Exception e) {
throw new SSLConfigException("Error initializing SSLConfig for " + this.clientName, e);
}
}
private void initFromKeyStore() throws SSLConfigException {
KeyStore trustStore;
KeyStore keyStore;
try {
trustStore = PemKeyReader.loadKeyStore(
PemKeyReader.resolve(SSLConfigConstants.OPENDISTRO_SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, settings,
configPath, !isTrustAllEnabled()),
settings.get(SSLConfigConstants.OPENDISTRO_SECURITY_SSL_TRANSPORT_TRUSTSTORE_PASSWORD,
SSLConfigConstants.DEFAULT_STORE_PASSWORD),
settings.get(SSLConfigConstants.OPENDISTRO_SECURITY_SSL_TRANSPORT_TRUSTSTORE_TYPE));
} catch (Exception e) {
throw new SSLConfigException("Error loading trust store from "
+ settings.get(SSLConfigConstants.OPENDISTRO_SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH), e);
}
effectiveTruststoreAliases = getSettingAsList(CA_ALIAS, null);
// for client authentication
try {
keyStore = PemKeyReader.loadKeyStore(
PemKeyReader.resolve(SSLConfigConstants.OPENDISTRO_SECURITY_SSL_TRANSPORT_KEYSTORE_FILEPATH, settings,
configPath, enableSslClientAuth),
settings.get(SSLConfigConstants.OPENDISTRO_SECURITY_SSL_TRANSPORT_KEYSTORE_PASSWORD,
SSLConfigConstants.DEFAULT_STORE_PASSWORD),
settings.get(SSLConfigConstants.OPENDISTRO_SECURITY_SSL_TRANSPORT_KEYSTORE_TYPE));
} catch (Exception e) {
throw new SSLConfigException("Error loading key store from "
+ settings.get(SSLConfigConstants.OPENDISTRO_SECURITY_SSL_TRANSPORT_KEYSTORE_FILEPATH), e);
}
String keyStorePassword = settings.get(SSLConfigConstants.OPENDISTRO_SECURITY_SSL_TRANSPORT_KEYSTORE_PASSWORD,
SSLConfigConstants.DEFAULT_STORE_PASSWORD);
effectiveKeyPassword = keyStorePassword == null || keyStorePassword.isEmpty() ? null
: keyStorePassword.toCharArray();
effectiveKeyAlias = getSetting(CERT_ALIAS);
if (enableSslClientAuth && effectiveKeyAlias == null) {
throw new IllegalArgumentException(settingsKeyPrefix + CERT_ALIAS + " not given");
}
effectiveTruststore = trustStore;
effectiveKeystore = keyStore;
}
private String getSetting(String key) {
return settings.get(settingsKeyPrefix + key);
}
private Boolean getSettingAsBoolean(String key, Boolean defaultValue) {
return settings.getAsBoolean(settingsKeyPrefix + key, defaultValue);
}
private List getSettingAsList(String key, List defaultValue) {
return settings.getAsList(settingsKeyPrefix + key, defaultValue);
}
private String[] getSettingAsArray(String key, List defaultValue) {
List list = getSettingAsList(key, defaultValue);
if (list == null) {
return null;
}
return list.toArray(new String[list.size()]);
}
private static String normalizeSettingsKeyPrefix(String settingsKeyPrefix) {
if (settingsKeyPrefix == null || settingsKeyPrefix.length() == 0) {
return "";
} else if (!settingsKeyPrefix.endsWith(".")) {
return settingsKeyPrefix + ".";
} else {
return settingsKeyPrefix;
}
}
public static class SSLConfig {
private final SSLContext sslContext;
private final String[] supportedProtocols;
private final String[] supportedCipherSuites;
private final HostnameVerifier hostnameVerifier;
private final boolean startTlsEnabled;
private final boolean hostnameVerificationEnabled;
private final boolean trustAll;
private final KeyStore effectiveTruststore;
private final List effectiveTruststoreAliases;
private final KeyStore effectiveKeystore;
private final char[] effectiveKeyPassword;
private final String effectiveKeyAlias;
public SSLConfig(SSLContext sslContext, String[] supportedProtocols, String[] supportedCipherSuites,
HostnameVerifier hostnameVerifier, boolean hostnameVerificationEnabled, boolean trustAll,
boolean startTlsEnabled, KeyStore effectiveTruststore, List effectiveTruststoreAliases,
KeyStore effectiveKeystore, char[] effectiveKeyPassword, String effectiveKeyAlias) {
this.sslContext = sslContext;
this.supportedProtocols = supportedProtocols;
this.supportedCipherSuites = supportedCipherSuites;
this.hostnameVerifier = hostnameVerifier;
this.hostnameVerificationEnabled = hostnameVerificationEnabled;
this.trustAll = trustAll;
this.startTlsEnabled = startTlsEnabled;
this.effectiveTruststore = effectiveTruststore;
this.effectiveTruststoreAliases = effectiveTruststoreAliases;
this.effectiveKeystore = effectiveKeystore;
this.effectiveKeyPassword = effectiveKeyPassword;
this.effectiveKeyAlias = effectiveKeyAlias;
if (log.isDebugEnabled()) {
log.debug("Created SSLConfig: " + this);
}
}
public SSLContext getSslContext() {
return sslContext;
}
public String[] getSupportedProtocols() {
return supportedProtocols;
}
public String[] getSupportedCipherSuites() {
return supportedCipherSuites;
}
public HostnameVerifier getHostnameVerifier() {
return hostnameVerifier;
}
public SSLIOSessionStrategy toSSLIOSessionStrategy() {
return new SSLIOSessionStrategy(sslContext, supportedProtocols, supportedCipherSuites, hostnameVerifier);
}
public SSLConnectionSocketFactory toSSLConnectionSocketFactory() {
return new SSLConnectionSocketFactory(sslContext, supportedProtocols, supportedCipherSuites,
hostnameVerifier);
}
public boolean isStartTlsEnabled() {
return startTlsEnabled;
}
public boolean isHostnameVerificationEnabled() {
return hostnameVerificationEnabled;
}
public KeyStore getEffectiveTruststore() {
return effectiveTruststore;
}
public KeyStore getEffectiveKeystore() {
return effectiveKeystore;
}
public char[] getEffectiveKeyPassword() {
return effectiveKeyPassword;
}
public String getEffectiveKeyPasswordString() {
if (this.effectiveKeyPassword == null) {
return null;
} else {
return new String(this.effectiveKeyPassword);
}
}
public String getEffectiveKeyAlias() {
return effectiveKeyAlias;
}
public List getEffectiveTruststoreAliases() {
return effectiveTruststoreAliases;
}
public String[] getEffectiveTruststoreAliasesArray() {
if (this.effectiveTruststoreAliases == null) {
return null;
} else {
return this.effectiveTruststoreAliases.toArray(new String[this.effectiveTruststoreAliases.size()]);
}
}
public String[] getEffectiveKeyAliasesArray() {
if (this.effectiveKeyAlias == null) {
return null;
} else {
return new String[] { this.effectiveKeyAlias };
}
}
@Override
public String toString() {
return "SSLConfig [sslContext=" + sslContext + ", supportedProtocols=" + Arrays.toString(supportedProtocols)
+ ", supportedCipherSuites=" + Arrays.toString(supportedCipherSuites) + ", hostnameVerifier="
+ hostnameVerifier + ", startTlsEnabled=" + startTlsEnabled + ", hostnameVerificationEnabled="
+ hostnameVerificationEnabled + ", trustAll=" + trustAll + ", effectiveTruststore="
+ effectiveTruststore + ", effectiveTruststoreAliases=" + effectiveTruststoreAliases
+ ", effectiveKeystore=" + effectiveKeystore + ", effectiveKeyAlias=" + effectiveKeyAlias + "]";
}
public boolean isTrustAllEnabled() {
return trustAll;
}
}
public static class SSLConfigException extends Exception {
private static final long serialVersionUID = 5827273100470174111L;
public SSLConfigException() {
super();
}
public SSLConfigException(String message, Throwable cause, boolean enableSuppression,
boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
public SSLConfigException(String message, Throwable cause) {
super(message, cause);
}
public SSLConfigException(String message) {
super(message);
}
public SSLConfigException(Throwable cause) {
super(cause);
}
}
private static class OverlyTrustfulSSLContextBuilder extends SSLContextBuilder {
@Override
protected void initSSLContext(SSLContext sslContext, Collection keyManagers,
Collection trustManagers, SecureRandom secureRandom) throws KeyManagementException {
sslContext.init(!keyManagers.isEmpty() ? keyManagers.toArray(new KeyManager[keyManagers.size()]) : null,
new TrustManager[] { new OverlyTrustfulTrustManager() }, secureRandom);
}
}
private static class OverlyTrustfulTrustManager implements X509TrustManager {
@Override
public void checkClientTrusted(final X509Certificate[] chain, final String authType)
throws CertificateException {
}
@Override
public void checkServerTrusted(final X509Certificate[] chain, final String authType)
throws CertificateException {
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
}
}