org.elasticsearch.xpack.core.ssl.SSLService.java Source code

Java tutorial

Introduction

Here is the source code for org.elasticsearch.xpack.core.ssl.SSLService.java

Source

/*
 * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
 * or more contributor license agreements. Licensed under the Elastic License;
 * you may not use this file except in compliance with the Elastic License.
 */
package org.elasticsearch.xpack.core.ssl;

import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.nio.conn.ssl.SSLIOSessionStrategy;
import org.apache.lucene.util.SetOnce;
import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.common.CheckedSupplier;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.component.AbstractComponent;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.env.Environment;
import org.elasticsearch.xpack.core.XPackSettings;
import org.elasticsearch.xpack.core.common.socket.SocketAccess;
import org.elasticsearch.xpack.core.security.SecurityField;
import org.elasticsearch.xpack.core.ssl.cert.CertificateInfo;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLParameters;
import javax.net.ssl.SSLSessionContext;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509ExtendedKeyManager;
import javax.net.ssl.X509ExtendedTrustManager;

import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
import java.security.GeneralSecurityException;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.Set;

/**
 * Provides access to {@link SSLEngine} and {@link SSLSocketFactory} objects based on a provided configuration. All
 * configurations loaded by this service must be configured on construction.
 */
public class SSLService extends AbstractComponent {

    private final Map<SSLConfiguration, SSLContextHolder> sslContexts;
    private final SSLConfiguration globalSSLConfiguration;
    private final SetOnce<SSLConfiguration> transportSSLConfiguration = new SetOnce<>();
    private final Environment env;

    /**
     * Create a new SSLService that parses the settings for the ssl contexts that need to be created, creates them, and then caches them
     * for use later
     */
    public SSLService(Settings settings, Environment environment) {
        super(settings);
        this.env = environment;
        this.globalSSLConfiguration = new SSLConfiguration(settings.getByPrefix(XPackSettings.GLOBAL_SSL_PREFIX));
        this.sslContexts = loadSSLConfigurations();
    }

    private SSLService(Settings settings, Environment environment, SSLConfiguration globalSSLConfiguration,
            Map<SSLConfiguration, SSLContextHolder> sslContexts) {
        super(settings);
        this.env = environment;
        this.globalSSLConfiguration = globalSSLConfiguration;
        this.sslContexts = sslContexts;
    }

    /**
     * Creates a new SSLService that supports dynamic creation of SSLContext instances. Instances created by this service will not be
     * cached and will not be monitored for reloading. This dynamic server does have access to the cached and monitored instances that
     * have been created during initialization
     */
    public SSLService createDynamicSSLService() {
        return new SSLService(settings, env, globalSSLConfiguration, sslContexts) {

            @Override
            Map<SSLConfiguration, SSLContextHolder> loadSSLConfigurations() {
                // we don't need to load anything...
                return Collections.emptyMap();
            }

            /**
             * Returns the existing {@link SSLContextHolder} for the configuration
             * @throws IllegalArgumentException if not found
             */
            @Override
            SSLContextHolder sslContextHolder(SSLConfiguration sslConfiguration) {
                SSLContextHolder holder = sslContexts.get(sslConfiguration);
                if (holder == null) {
                    // normally we'd throw here but let's create a new one that is not cached and will not be monitored for changes!
                    holder = createSslContext(sslConfiguration);
                }
                return holder;
            }
        };
    }

    /**
     * Create a new {@link SSLIOSessionStrategy} based on the provided settings. The settings are used to identify the SSL configuration
     * that should be used to create the context.
     *
     * @param settings the settings used to identify the ssl configuration, typically under a *.ssl. prefix. An empty settings will return
     *                 a context created from the default configuration
     * @return Never {@code null}.
     */
    public SSLIOSessionStrategy sslIOSessionStrategy(Settings settings) {
        SSLConfiguration config = sslConfiguration(settings);
        SSLContext sslContext = sslContext(config);
        String[] ciphers = supportedCiphers(sslParameters(sslContext).getCipherSuites(), config.cipherSuites(),
                false);
        String[] supportedProtocols = config.supportedProtocols().toArray(Strings.EMPTY_ARRAY);
        HostnameVerifier verifier;

        if (config.verificationMode().isHostnameVerificationEnabled()) {
            verifier = SSLIOSessionStrategy.getDefaultHostnameVerifier();
        } else {
            verifier = NoopHostnameVerifier.INSTANCE;
        }

        return sslIOSessionStrategy(sslContext, supportedProtocols, ciphers, verifier);
    }

    /**
     * The {@link SSLParameters} that are associated with the {@code sslContext}.
     * <p>
     * This method exists to simplify testing since {@link SSLContext#getSupportedSSLParameters()} is {@code final}.
     *
     * @param sslContext The SSL context for the current SSL settings
     * @return Never {@code null}.
     */
    SSLParameters sslParameters(SSLContext sslContext) {
        return sslContext.getSupportedSSLParameters();
    }

    /**
     * This method only exists to simplify testing of {@link #sslIOSessionStrategy(Settings)} because {@link SSLIOSessionStrategy} does
     * not expose any of the parameters that you give it.
     *
     * @param sslContext SSL Context used to handle SSL / TCP requests
     * @param protocols Supported protocols
     * @param ciphers Supported ciphers
     * @param verifier Hostname verifier
     * @return Never {@code null}.
     */
    SSLIOSessionStrategy sslIOSessionStrategy(SSLContext sslContext, String[] protocols, String[] ciphers,
            HostnameVerifier verifier) {
        return new SSLIOSessionStrategy(sslContext, protocols, ciphers, verifier);
    }

    /**
     * Create a new {@link SSLSocketFactory} based on the provided settings. The settings are used to identify the ssl configuration that
     * should be used to create the socket factory. The socket factory will also properly configure the ciphers and protocols on each
     * socket that is created
     * @param settings the settings used to identify the ssl configuration, typically under a *.ssl. prefix. An empty settings will return
     *                 a factory created from the default configuration
     * @return Never {@code null}.
     */
    public SSLSocketFactory sslSocketFactory(Settings settings) {
        SSLConfiguration sslConfiguration = sslConfiguration(settings);
        SSLSocketFactory socketFactory = sslContext(sslConfiguration).getSocketFactory();
        return new SecuritySSLSocketFactory(socketFactory,
                sslConfiguration.supportedProtocols().toArray(Strings.EMPTY_ARRAY),
                supportedCiphers(socketFactory.getSupportedCipherSuites(), sslConfiguration.cipherSuites(), false));
    }

    /**
     * Creates an {@link SSLEngine} based on the provided settings. The settings are used to identify the ssl configuration that should be
     * used to create the engine. This SSLEngine cannot be used for hostname verification since the engine will not be created with the
     * host and port. This method is useful to obtain an SSLEngine that will be used for server connections or client connections that
     * will not use hostname verification.
     * @param settings the settings used to identify the ssl configuration, typically under a *.ssl. prefix. An empty settings will return
     *                 a SSLEngine created from the default configuration
     * @param fallbackSettings the settings that should be used for the fallback of the SSLConfiguration. Using {@link Settings#EMPTY}
     *                         results in a fallback to the global configuration
     * @return {@link SSLEngine}
     */
    public SSLEngine createSSLEngine(Settings settings, Settings fallbackSettings) {
        return createSSLEngine(settings, fallbackSettings, null, -1);
    }

    /**
     * Creates an {@link SSLEngine} based on the provided settings. The settings are used to identify the ssl configuration that should be
     * used to create the engine. This SSLEngine can be used for a connection that requires hostname verification assuming the provided
     * host and port are correct. The SSLEngine created by this method is most useful for clients with hostname verification enabled
     * @param settings the settings used to identify the ssl configuration, typically under a *.ssl. prefix. An empty settings will return
     *                 a SSLEngine created from the default configuration
     * @param fallbackSettings the settings that should be used for the fallback of the SSLConfiguration. Using {@link Settings#EMPTY}
     *                         results in a fallback to the global configuration
     * @param host the host of the remote endpoint. If using hostname verification, this should match what is in the remote endpoint's
     *             certificate
     * @param port the port of the remote endpoint
     * @return {@link SSLEngine}
     */
    public SSLEngine createSSLEngine(Settings settings, Settings fallbackSettings, String host, int port) {
        SSLConfiguration configuration = sslConfiguration(settings, fallbackSettings);
        return createSSLEngine(configuration, host, port);
    }

    /**
     * Creates an {@link SSLEngine} based on the provided configuration. This SSLEngine can be used for a connection that requires
     * hostname verification assuming the provided
     * host and port are correct. The SSLEngine created by this method is most useful for clients with hostname verification enabled
     * @param configuration the ssl configuration
     * @param host the host of the remote endpoint. If using hostname verification, this should match what is in the remote endpoint's
     *             certificate
     * @param port the port of the remote endpoint
     * @return {@link SSLEngine}
     * @see #sslConfiguration(Settings, Settings)
     */
    public SSLEngine createSSLEngine(SSLConfiguration configuration, String host, int port) {
        SSLContext sslContext = sslContext(configuration);
        SSLEngine sslEngine = sslContext.createSSLEngine(host, port);
        String[] ciphers = supportedCiphers(sslEngine.getSupportedCipherSuites(), configuration.cipherSuites(),
                false);
        String[] supportedProtocols = configuration.supportedProtocols().toArray(Strings.EMPTY_ARRAY);
        SSLParameters parameters = new SSLParameters(ciphers, supportedProtocols);
        if (configuration.verificationMode().isHostnameVerificationEnabled() && host != null) {
            // By default, a SSLEngine will not perform hostname verification. In order to perform hostname verification
            // we need to specify a EndpointIdentificationAlgorithm. We use the HTTPS algorithm to prevent against
            // man in the middle attacks for all of our connections.
            parameters.setEndpointIdentificationAlgorithm("HTTPS");
        }
        // we use the cipher suite order so that we can prefer the ciphers we set first in the list
        parameters.setUseCipherSuitesOrder(true);
        configuration.sslClientAuth().configure(parameters);

        // many SSLEngine options can be configured using either SSLParameters or direct methods on the engine itself, but there is one
        // tricky aspect; if you set a value directly on the engine and then later set the SSLParameters the value set directly on the
        // engine will be overwritten by the value in the SSLParameters
        sslEngine.setSSLParameters(parameters);
        return sslEngine;
    }

    /**
     * Returns whether the provided settings results in a valid configuration that can be used for server connections
     * @param sslConfiguration the configuration to check
     */
    public boolean isConfigurationValidForServerUsage(SSLConfiguration sslConfiguration) {
        return sslConfiguration.keyConfig() != KeyConfig.NONE;
    }

    /**
     * Indicates whether client authentication is enabled for a particular configuration
     * @param settings the settings used to identify the ssl configuration, typically under a *.ssl. prefix. The global configuration
     *                 will be used for fallback
     */
    public boolean isSSLClientAuthEnabled(Settings settings) {
        return isSSLClientAuthEnabled(settings, Settings.EMPTY);
    }

    /**
     * Indicates whether client authentication is enabled for a particular configuration
     * @param settings the settings used to identify the ssl configuration, typically under a *.ssl. prefix
     * @param fallback the settings that should be used for the fallback of the SSLConfiguration. Using {@link Settings#EMPTY}
     *                 results in a fallback to the global configuration
     */
    public boolean isSSLClientAuthEnabled(Settings settings, Settings fallback) {
        SSLConfiguration sslConfiguration = sslConfiguration(settings, fallback);
        return isSSLClientAuthEnabled(sslConfiguration);
    }

    /**
     * Indicates whether client authentication is enabled for a particular configuration
     */
    public boolean isSSLClientAuthEnabled(SSLConfiguration sslConfiguration) {
        return sslConfiguration.sslClientAuth().enabled();
    }

    /**
     * Returns the {@link VerificationMode} that is specified in the settings (or the default)
     * @param settings the settings used to identify the ssl configuration, typically under a *.ssl. prefix
     * @param fallback the settings that should be used for the fallback of the SSLConfiguration. Using {@link Settings#EMPTY}
     *                 results in a fallback to the global configuration
     */
    public VerificationMode getVerificationMode(Settings settings, Settings fallback) {
        SSLConfiguration sslConfiguration = sslConfiguration(settings, fallback);
        return sslConfiguration.verificationMode();
    }

    /**
     * Returns the {@link SSLContext} for the global configuration. Mainly used for testing
     */
    SSLContext sslContext() {
        return sslContextHolder(globalSSLConfiguration).sslContext();
    }

    /**
     * Returns the {@link SSLContext} for the configuration
     */
    SSLContext sslContext(SSLConfiguration configuration) {
        return sslContextHolder(configuration).sslContext();
    }

    /**
     * Returns the existing {@link SSLContextHolder} for the configuration
     * @throws IllegalArgumentException if not found
     */
    SSLContextHolder sslContextHolder(SSLConfiguration sslConfiguration) {
        SSLContextHolder holder = sslContexts.get(sslConfiguration);
        if (holder == null) {
            throw new IllegalArgumentException(
                    "did not find a SSLContext for [" + sslConfiguration.toString() + "]");
        }
        return holder;
    }

    /**
     * Returns the existing {@link SSLConfiguration} for the given settings
     * @param settings the settings for the ssl configuration
     * @return the ssl configuration for the provided settings. If the settings are empty, the global configuration is returned
     */
    SSLConfiguration sslConfiguration(Settings settings) {
        if (settings.isEmpty()) {
            return globalSSLConfiguration;
        }
        return new SSLConfiguration(settings, globalSSLConfiguration);
    }

    /**
     * Returns the existing {@link SSLConfiguration} for the given settings and applies the provided fallback settings instead of the global
     * configuration
     * @param settings the settings for the ssl configuration
     * @param fallbackSettings the settings that should be used for the fallback of the SSLConfiguration. Using {@link Settings#EMPTY}
     *                         results in a fallback to the global configuration
     * @return the ssl configuration for the provided settings. If the settings are empty, the global configuration is returned
     */
    public SSLConfiguration sslConfiguration(Settings settings, Settings fallbackSettings) {
        if (settings.isEmpty() && fallbackSettings.isEmpty()) {
            return globalSSLConfiguration;
        }
        SSLConfiguration fallback = sslConfiguration(fallbackSettings);
        return new SSLConfiguration(settings, fallback);
    }

    /**
     * Accessor to the loaded ssl configuration objects at the current point in time. This is useful for testing
     */
    Collection<SSLConfiguration> getLoadedSSLConfigurations() {
        return Collections.unmodifiableSet(new HashSet<>(sslContexts.keySet()));
    }

    /**
     * Returns the intersection of the supported ciphers with the requested ciphers. This method will also optionally log if unsupported
     * ciphers were requested.
     * @throws IllegalArgumentException if no supported ciphers are in the requested ciphers
     */
    String[] supportedCiphers(String[] supportedCiphers, List<String> requestedCiphers, boolean log) {
        List<String> supportedCiphersList = new ArrayList<>(requestedCiphers.size());
        List<String> unsupportedCiphers = new LinkedList<>();
        boolean found;
        for (String requestedCipher : requestedCiphers) {
            found = false;
            for (String supportedCipher : supportedCiphers) {
                if (supportedCipher.equals(requestedCipher)) {
                    found = true;
                    supportedCiphersList.add(requestedCipher);
                    break;
                }
            }

            if (!found) {
                unsupportedCiphers.add(requestedCipher);
            }
        }

        if (supportedCiphersList.isEmpty()) {
            throw new IllegalArgumentException("none of the ciphers " + Arrays.toString(requestedCiphers.toArray())
                    + " are supported by this JVM");
        }

        if (log && !unsupportedCiphers.isEmpty()) {
            logger.error(
                    "unsupported ciphers [{}] were requested but cannot be used in this JVM, however there are supported ciphers "
                            + "that will be used [{}]. If you are trying to use ciphers with a key length greater than 128 bits on an Oracle JVM, "
                            + "you will need to install the unlimited strength JCE policy files.",
                    unsupportedCiphers, supportedCiphersList);
        }

        return supportedCiphersList.toArray(new String[supportedCiphersList.size()]);
    }

    /**
     * Creates an {@link SSLContext} based on the provided configuration
     * @param sslConfiguration the configuration to use for context creation
     * @return the created SSLContext
     */
    private SSLContextHolder createSslContext(SSLConfiguration sslConfiguration) {
        if (logger.isDebugEnabled()) {
            logger.debug("using ssl settings [{}]", sslConfiguration);
        }
        X509ExtendedTrustManager trustManager = sslConfiguration.trustConfig().createTrustManager(env);
        X509ExtendedKeyManager keyManager = sslConfiguration.keyConfig().createKeyManager(env);
        return createSslContext(keyManager, trustManager, sslConfiguration);
    }

    /**
     * Creates an {@link SSLContext} based on the provided configuration and trust/key managers
     * @param sslConfiguration the configuration to use for context creation
     * @param keyManager the key manager to use
     * @param trustManager the trust manager to use
     * @return the created SSLContext
     */
    private SSLContextHolder createSslContext(X509ExtendedKeyManager keyManager,
            X509ExtendedTrustManager trustManager, SSLConfiguration sslConfiguration) {
        // Initialize sslContext
        try {
            SSLContext sslContext = SSLContext
                    .getInstance(sslContextAlgorithm(sslConfiguration.supportedProtocols()));
            sslContext.init(new X509ExtendedKeyManager[] { keyManager },
                    new X509ExtendedTrustManager[] { trustManager }, null);

            // check the supported ciphers and log them here to prevent spamming logs on every call
            supportedCiphers(sslContext.getSupportedSSLParameters().getCipherSuites(),
                    sslConfiguration.cipherSuites(), true);

            return new SSLContextHolder(sslContext, sslConfiguration);
        } catch (NoSuchAlgorithmException | KeyManagementException e) {
            throw new ElasticsearchException("failed to initialize the SSLContext", e);
        }
    }

    /**
     * Parses the settings to load all SSLConfiguration objects that will be used.
     */
    Map<SSLConfiguration, SSLContextHolder> loadSSLConfigurations() {
        Map<SSLConfiguration, SSLContextHolder> sslConfigurations = new HashMap<>();
        sslConfigurations.put(globalSSLConfiguration, createSslContext(globalSSLConfiguration));

        final Settings transportSSLSettings = settings.getByPrefix(XPackSettings.TRANSPORT_SSL_PREFIX);
        List<Settings> sslSettingsList = new ArrayList<>();
        sslSettingsList.add(getHttpTransportSSLSettings(settings));
        sslSettingsList.add(settings.getByPrefix("xpack.http.ssl."));
        sslSettingsList.addAll(getRealmsSSLSettings(settings));
        sslSettingsList.addAll(getMonitoringExporterSettings(settings));

        sslSettingsList.forEach((sslSettings) -> sslConfigurations.computeIfAbsent(
                new SSLConfiguration(sslSettings, globalSSLConfiguration), this::createSslContext));

        // transport is special because we want to use a auto-generated key when there isn't one
        final SSLConfiguration transportSSLConfiguration = new SSLConfiguration(transportSSLSettings,
                globalSSLConfiguration);
        this.transportSSLConfiguration.set(transportSSLConfiguration);
        List<Settings> profileSettings = getTransportProfileSSLSettings(settings);
        sslConfigurations.computeIfAbsent(transportSSLConfiguration, this::createSslContext);
        profileSettings.forEach((profileSetting) -> sslConfigurations.computeIfAbsent(
                new SSLConfiguration(profileSetting, transportSSLConfiguration), this::createSslContext));
        return Collections.unmodifiableMap(sslConfigurations);
    }

    /**
     * Returns information about each certificate that is referenced by any SSL configuration.
     * This includes certificates used for identity (with a private key) and those used for trust, but excludes
     * certificates that are provided by the JRE.
     * Due to the nature of KeyStores, this may include certificates that are available, but never used
     * such as a CA certificate that is no longer in use, or a server certificate for an unrelated host.
     * @see TrustConfig#certificates(Environment)
     */
    public Set<CertificateInfo> getLoadedCertificates() throws GeneralSecurityException, IOException {
        Set<CertificateInfo> certificates = new HashSet<>();
        for (SSLConfiguration config : this.getLoadedSSLConfigurations()) {
            certificates.addAll(config.getDefinedCertificates(env));
        }
        return certificates;
    }

    /**
     * This socket factory wraps an existing SSLSocketFactory and sets the protocols and ciphers on each SSLSocket after it is created. This
     * is needed even though the SSLContext is configured properly as the configuration does not flow down to the sockets created by the
     * SSLSocketFactory obtained from the SSLContext.
     */
    private static class SecuritySSLSocketFactory extends SSLSocketFactory {

        private final SSLSocketFactory delegate;
        private final String[] supportedProtocols;
        private final String[] ciphers;

        SecuritySSLSocketFactory(SSLSocketFactory delegate, String[] supportedProtocols, String[] ciphers) {
            this.delegate = delegate;
            this.supportedProtocols = supportedProtocols;
            this.ciphers = ciphers;
        }

        @Override
        public String[] getDefaultCipherSuites() {
            return ciphers;
        }

        @Override
        public String[] getSupportedCipherSuites() {
            return delegate.getSupportedCipherSuites();
        }

        @Override
        public Socket createSocket() throws IOException {
            SSLSocket sslSocket = createWithPermissions(delegate::createSocket);
            configureSSLSocket(sslSocket);
            return sslSocket;
        }

        @Override
        public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException {
            SSLSocket sslSocket = createWithPermissions(() -> delegate.createSocket(socket, host, port, autoClose));
            configureSSLSocket(sslSocket);
            return sslSocket;
        }

        @Override
        public Socket createSocket(String host, int port) throws IOException {
            SSLSocket sslSocket = createWithPermissions(() -> delegate.createSocket(host, port));
            configureSSLSocket(sslSocket);
            return sslSocket;
        }

        @Override
        public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException {
            SSLSocket sslSocket = createWithPermissions(
                    () -> delegate.createSocket(host, port, localHost, localPort));
            configureSSLSocket(sslSocket);
            return sslSocket;
        }

        @Override
        public Socket createSocket(InetAddress host, int port) throws IOException {
            SSLSocket sslSocket = createWithPermissions(() -> delegate.createSocket(host, port));
            configureSSLSocket(sslSocket);
            return sslSocket;
        }

        @Override
        public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort)
                throws IOException {
            SSLSocket sslSocket = createWithPermissions(
                    () -> delegate.createSocket(address, port, localAddress, localPort));
            configureSSLSocket(sslSocket);
            return sslSocket;
        }

        private void configureSSLSocket(SSLSocket socket) {
            SSLParameters parameters = new SSLParameters(ciphers, supportedProtocols);
            // we use the cipher suite order so that we can prefer the ciphers we set first in the list
            parameters.setUseCipherSuitesOrder(true);
            socket.setSSLParameters(parameters);
        }

        private static SSLSocket createWithPermissions(CheckedSupplier<Socket, IOException> supplier)
                throws IOException {
            return (SSLSocket) SocketAccess.doPrivileged(supplier);
        }
    }

    final class SSLContextHolder {
        private volatile SSLContext context;
        private final KeyConfig keyConfig;
        private final TrustConfig trustConfig;
        private final SSLConfiguration sslConfiguration;

        SSLContextHolder(SSLContext context, SSLConfiguration sslConfiguration) {
            this.context = context;
            this.sslConfiguration = sslConfiguration;
            this.keyConfig = sslConfiguration.keyConfig();
            this.trustConfig = sslConfiguration.trustConfig();
        }

        SSLContext sslContext() {
            return context;
        }

        /**
         * Invalidates the sessions in the provided {@link SSLSessionContext}
         */
        private void invalidateSessions(SSLSessionContext sslSessionContext) {
            Enumeration<byte[]> sessionIds = sslSessionContext.getIds();
            while (sessionIds.hasMoreElements()) {
                byte[] sessionId = sessionIds.nextElement();
                sslSessionContext.getSession(sessionId).invalidate();
            }
        }

        synchronized void reload() {
            invalidateSessions(context.getClientSessionContext());
            invalidateSessions(context.getServerSessionContext());
            reloadSslContext();
        }

        private void reloadSslContext() {
            try {
                X509ExtendedKeyManager loadedKeyManager = Optional.ofNullable(keyConfig.createKeyManager(env))
                        .orElse(getEmptyKeyManager());
                X509ExtendedTrustManager loadedTrustManager = Optional
                        .ofNullable(trustConfig.createTrustManager(env)).orElse(getEmptyTrustManager());
                SSLContext loadedSslContext = SSLContext
                        .getInstance(sslContextAlgorithm(sslConfiguration.supportedProtocols()));
                loadedSslContext.init(new X509ExtendedKeyManager[] { loadedKeyManager },
                        new X509ExtendedTrustManager[] { loadedTrustManager }, null);
                supportedCiphers(loadedSslContext.getSupportedSSLParameters().getCipherSuites(),
                        sslConfiguration.cipherSuites(), false);
                this.context = loadedSslContext;
            } catch (GeneralSecurityException | IOException e) {
                throw new ElasticsearchException("failed to initialize the SSLContext", e);
            }
        }

        X509ExtendedKeyManager getEmptyKeyManager() throws GeneralSecurityException, IOException {
            KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
            keyStore.load(null, null);
            KeyManagerFactory keyManagerFactory = KeyManagerFactory
                    .getInstance(KeyManagerFactory.getDefaultAlgorithm());
            keyManagerFactory.init(keyStore, null);
            return (X509ExtendedKeyManager) keyManagerFactory.getKeyManagers()[0];
        }

        X509ExtendedTrustManager getEmptyTrustManager() throws GeneralSecurityException, IOException {
            KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
            keyStore.load(null, null);
            TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance("X509");
            trustManagerFactory.init(keyStore);
            return (X509ExtendedTrustManager) trustManagerFactory.getTrustManagers()[0];
        }
    }

    private static List<Settings> getRealmsSSLSettings(Settings settings) {
        List<Settings> sslSettings = new ArrayList<>();
        Settings realmsSettings = settings.getByPrefix(SecurityField.setting("authc.realms."));
        for (String name : realmsSettings.names()) {
            Settings realmSSLSettings = realmsSettings.getAsSettings(name).getByPrefix("ssl.");
            if (realmSSLSettings.isEmpty() == false) {
                sslSettings.add(realmSSLSettings);
            }
        }
        return sslSettings;
    }

    private static List<Settings> getTransportProfileSSLSettings(Settings settings) {
        List<Settings> sslSettings = new ArrayList<>();
        Map<String, Settings> profiles = settings.getGroups("transport.profiles.", true);
        for (Entry<String, Settings> entry : profiles.entrySet()) {
            Settings profileSettings = entry.getValue().getByPrefix("xpack.security.ssl.");
            if (profileSettings.isEmpty() == false) {
                sslSettings.add(profileSettings);
            }
        }
        return sslSettings;
    }

    public static Settings getHttpTransportSSLSettings(Settings settings) {
        Settings httpSSLSettings = settings.getByPrefix(XPackSettings.HTTP_SSL_PREFIX);
        if (httpSSLSettings.isEmpty()) {
            return httpSSLSettings;
        }

        Settings.Builder builder = Settings.builder().put(httpSSLSettings);
        if (builder.get("client_authentication") == null) {
            builder.put("client_authentication", XPackSettings.HTTP_CLIENT_AUTH_DEFAULT);
        }
        return builder.build();
    }

    private static List<Settings> getMonitoringExporterSettings(Settings settings) {
        List<Settings> sslSettings = new ArrayList<>();
        Map<String, Settings> exportersSettings = settings.getGroups("xpack.monitoring.exporters.");
        for (Entry<String, Settings> entry : exportersSettings.entrySet()) {
            Settings exporterSSLSettings = entry.getValue().getByPrefix("ssl.");
            if (exporterSSLSettings.isEmpty() == false) {
                sslSettings.add(exporterSSLSettings);
            }
        }
        return sslSettings;
    }

    /**
     * Maps the supported protocols to an appropriate ssl context algorithm. We make an attempt to use the "best" algorithm when
     * possible. The names in this method are taken from the
     * <a href="http://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html">JCA Standard Algorithm Name
     * Documentation for Java 8</a>.
     */
    private static String sslContextAlgorithm(List<String> supportedProtocols) {
        if (supportedProtocols.isEmpty()) {
            return "TLSv1.2";
        }

        String algorithm = "SSL";
        for (String supportedProtocol : supportedProtocols) {
            switch (supportedProtocol) {
            case "TLSv1.2":
                return "TLSv1.2";
            case "TLSv1.1":
                if ("TLSv1.2".equals(algorithm) == false) {
                    algorithm = "TLSv1.1";
                }
                break;
            case "TLSv1":
                switch (algorithm) {
                case "TLSv1.2":
                case "TLSv1.1":
                    break;
                default:
                    algorithm = "TLSv1";
                }
                break;
            case "SSLv3":
                switch (algorithm) {
                case "SSLv2":
                case "SSL":
                    algorithm = "SSLv3";
                }
                break;
            case "SSLv2":
            case "SSLv2Hello":
                break;
            default:
                throw new IllegalArgumentException(
                        "found unexpected value in supported protocols: " + supportedProtocol);
            }
        }
        return algorithm;
    }
}