/*
 * Copyright (c) 2007, 2024 Oracle and/or its affiliates. All rights reserved.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v. 2.0, which is available at
 * http://www.eclipse.org/legal/epl-2.0.
 *
 * This Source Code may also be made available under the following Secondary
 * Licenses when the conditions for such availability set forth in the
 * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
 * version 2 with the GNU Classpath Exception, which is available at
 * https://www.gnu.org/software/classpath/license.html.
 *
 * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
 */

package org.glassfish.jersey;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.nio.file.Files;
import java.security.AccessController;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import java.util.Arrays;
import java.util.Objects;
import java.util.Properties;
import java.util.logging.Logger;

import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManagerFactory;

import org.glassfish.jersey.internal.LocalizationMessages;
import org.glassfish.jersey.internal.util.PropertiesHelper;

/**
 * Utility class, which helps to configure {@link SSLContext} instances.
 *
 * For example:
 * <pre>
 * SslConfigurator sslConfig = SslConfigurator.newInstance()
 *    .trustStoreFile("truststore.jks")
 *    .trustStorePassword("asdfgh")
 *    .trustStoreType("JKS")
 *    .trustManagerFactoryAlgorithm("PKIX")
 *
 *    .keyStoreFile("keystore.jks")
 *    .keyPassword("asdfgh")
 *    .keyStoreType("JKS")
 *    .keyManagerFactoryAlgorithm("SunX509")
 *    .keyStoreProvider("SunJSSE")
 *
 *    .securityProtocol("SSL");
 *
 * SSLContext sslContext = sslConfig.createSSLContext();
 * </pre>
 *
 * @author Alexey Stashok
 * @author Hubert Iwaniuk
 * @author Bruno Harbulot
 * @author Marek Potociar
 */
@SuppressWarnings("UnusedDeclaration")
public final class SslConfigurator {

    /**
     * <em>Trust</em> store provider name.
     *
     * The value MUST be a {@code String} representing the name of a <em>trust</em> store provider.
     * <p>
     * No default value is set.
     * </p>
     * <p>
     * The name of the configuration property is <tt>{@value}</tt>.
     * </p>
     */
    public static final String TRUST_STORE_PROVIDER = "javax.net.ssl.trustStoreProvider";
    /**
     * <em>Key</em> store provider name.
     *
     * The value MUST be a {@code String} representing the name of a <em>trust</em> store provider.
     * <p>
     * No default value is set.
     * </p>
     * <p>
     * The name of the configuration property is <tt>{@value}</tt>.
     * </p>
     */
    public static final String KEY_STORE_PROVIDER = "javax.net.ssl.keyStoreProvider";
    /**
     * <em>Trust</em> store file name.
     *
     * The value MUST be a {@code String} representing the name of a <em>trust</em> store file.
     * <p>
     * No default value is set.
     * </p>
     * <p>
     * The name of the configuration property is <tt>{@value}</tt>.
     * </p>
     */
    public static final String TRUST_STORE_FILE = "javax.net.ssl.trustStore";
    /**
     * <em>Key</em> store file name.
     *
     * The value MUST be a {@code String} representing the name of a <em>key</em> store file.
     * <p>
     * No default value is set.
     * </p>
     * <p>
     * The name of the configuration property is <tt>{@value}</tt>.
     * </p>
     */
    public static final String KEY_STORE_FILE = "javax.net.ssl.keyStore";
    /**
     * <em>Trust</em> store file password - the password used to unlock the <em>trust</em> store file.
     *
     * The value MUST be a {@code String} representing the <em>trust</em> store file password.
     * <p>
     * No default value is set.
     * </p>
     * <p>
     * The name of the configuration property is <tt>{@value}</tt>.
     * </p>
     */
    public static final String TRUST_STORE_PASSWORD = "javax.net.ssl.trustStorePassword";
    /**
     * <em>Key</em> store file password - the password used to unlock the <em>trust</em> store file.
     *
     * The value MUST be a {@code String} representing the <em>key</em> store file password.
     * <p>
     * No default value is set.
     * </p>
     * <p>
     * The name of the configuration property is <tt>{@value}</tt>.
     * </p>
     */
    public static final String KEY_STORE_PASSWORD = "javax.net.ssl.keyStorePassword";
    /**
     * <em>Trust</em> store type (see {@link java.security.KeyStore#getType()} for more info).
     *
     * The value MUST be a {@code String} representing the <em>trust</em> store type name.
     * <p>
     * No default value is set.
     * </p>
     * <p>
     * The name of the configuration property is <tt>{@value}</tt>.
     * </p>
     */
    public static final String TRUST_STORE_TYPE = "javax.net.ssl.trustStoreType";
    /**
     * <em>Key</em> store type (see {@link java.security.KeyStore#getType()} for more info).
     *
     * The value MUST be a {@code String} representing the <em>key</em> store type name.
     * <p>
     * No default value is set.
     * </p>
     * <p>
     * The name of the configuration property is <tt>{@value}</tt>.
     * </p>
     */
    public static final String KEY_STORE_TYPE = "javax.net.ssl.keyStoreType";
    /**
     * <em>Key</em> manager factory algorithm name.
     *
     * The value MUST be a {@code String} representing the <em>key</em> manager factory algorithm name.
     * <p>
     * No default value is set.
     * </p>
     * <p>
     * The name of the configuration property is <tt>{@value}</tt>.
     * </p>
     */
    public static final String KEY_MANAGER_FACTORY_ALGORITHM = "ssl.keyManagerFactory.algorithm";
    /**
     * <em>Key</em> manager factory provider name.
     *
     * The value MUST be a {@code String} representing the <em>key</em> manager factory provider name.
     * <p>
     * No default value is set.
     * </p>
     * <p>
     * The name of the configuration property is <tt>{@value}</tt>.
     * </p>
     */
    public static final String KEY_MANAGER_FACTORY_PROVIDER = "ssl.keyManagerFactory.provider";
    /**
     * <em>Trust</em> manager factory algorithm name.
     *
     * The value MUST be a {@code String} representing the <em>trust</em> manager factory algorithm name.
     * <p>
     * No default value is set.
     * </p>
     * <p>
     * The name of the configuration property is <tt>{@value}</tt>.
     * </p>
     */
    public static final String TRUST_MANAGER_FACTORY_ALGORITHM = "ssl.trustManagerFactory.algorithm";
    /**
     * <em>Trust</em> manager factory provider name.
     *
     * The value MUST be a {@code String} representing the <em>trust</em> manager factory provider name.
     * <p>
     * No default value is set.
     * </p>
     * <p>
     * The name of the configuration property is <tt>{@value}</tt>.
     * </p>
     */
    public static final String TRUST_MANAGER_FACTORY_PROVIDER = "ssl.trustManagerFactory.provider";
    /**
     * Default SSL configuration that is used to create default SSL context instances that do not take into
     * account system properties.
     */
    private static final SslConfigurator DEFAULT_CONFIG_NO_PROPS = new SslConfigurator(false);
    /**
     * Logger.
     */
    private static final Logger LOGGER = Logger.getLogger(SslConfigurator.class.getName());

    private KeyStore keyStore;
    private KeyStore trustStore;

    private String trustStoreProvider;
    private String keyStoreProvider;

    private String trustStoreType;
    private String keyStoreType;

    private char[] trustStorePass;
    private char[] keyStorePass;
    private char[] keyPass;

    private String trustStoreFile;
    private String keyStoreFile;

    private URL trustStoreUrl;
    private URL keyStoreUrl;

    private byte[] trustStoreBytes;
    private byte[] keyStoreBytes;

    private String trustManagerFactoryAlgorithm;
    private String keyManagerFactoryAlgorithm;

    private String trustManagerFactoryProvider;
    private String keyManagerFactoryProvider;

    private String securityProtocol = "TLS";

    /**
     * Get a new instance of a {@link SSLContext} configured using default configuration settings.
     *
     * The default SSL configuration is initialized from system properties. This method is a shortcut
     * for {@link #getDefaultContext(boolean) getDefaultContext(true)}.
     *
     * @return new instance of a default SSL context initialized from system properties.
     */
    public static SSLContext getDefaultContext() {
        return getDefaultContext(true);
    }

    /**
     * Get a new instance of a {@link SSLContext} configured using default configuration settings.
     *
     * If {@code readSystemProperties} parameter is set to {@code true}, the default SSL configuration
     * is initialized from system properties.
     *
     * @param readSystemProperties if {@code true}, the default SSL context will be initialized using
     *                             system properties.
     * @return new instance of a default SSL context initialized from system properties.
     */
    public static SSLContext getDefaultContext(boolean readSystemProperties) {
        if (readSystemProperties) {
            return new SslConfigurator(true).createSSLContext();
        } else {
            return DEFAULT_CONFIG_NO_PROPS.createSSLContext();
        }
    }

    /**
     * Get a new & initialized SSL configurator instance. The the instantiated configurator will be empty.
     *
     * @return new & initialized SSL configurator instance.
     */
    public static SslConfigurator newInstance() {
        return new SslConfigurator(false);
    }

    /**
     * Get a new SSL configurator instance.
     *
     * @param readSystemProperties if {@code true}, {@link #retrieve() Retrieves} the initial configuration from
     *                             {@link System#getProperty(String)}}, otherwise the instantiated configurator will
     *                             be empty.
     * @return new SSL configurator instance.
     */
    public static SslConfigurator newInstance(boolean readSystemProperties) {
        return new SslConfigurator(readSystemProperties);
    }

    private SslConfigurator(boolean readSystemProperties) {
        if (readSystemProperties) {
            retrieve();
        }
    }

    private SslConfigurator(SslConfigurator that) {
        this.keyStore = that.keyStore;
        this.trustStore = that.trustStore;
        this.trustStoreProvider = that.trustStoreProvider;
        this.keyStoreProvider = that.keyStoreProvider;
        this.trustStoreType = that.trustStoreType;
        this.keyStoreType = that.keyStoreType;
        this.trustStorePass = that.trustStorePass;
        this.keyStorePass = that.keyStorePass;
        this.keyPass = that.keyPass;
        this.trustStoreFile = that.trustStoreFile;
        this.keyStoreFile = that.keyStoreFile;
        this.keyStoreUrl = that.keyStoreUrl;
        this.trustStoreUrl = that.trustStoreUrl;
        this.trustStoreBytes = that.trustStoreBytes;
        this.keyStoreBytes = that.keyStoreBytes;
        this.trustManagerFactoryAlgorithm = that.trustManagerFactoryAlgorithm;
        this.keyManagerFactoryAlgorithm = that.keyManagerFactoryAlgorithm;
        this.trustManagerFactoryProvider = that.trustManagerFactoryProvider;
        this.keyManagerFactoryProvider = that.keyManagerFactoryProvider;
        this.securityProtocol = that.securityProtocol;
    }

    /**
     * Create a copy of the current SSL configurator instance.
     *
     * @return copy of the current SSL configurator instance
     */
    public SslConfigurator copy() {
        return new SslConfigurator(this);
    }

    /**
     * Set the <em>trust</em> store provider name.
     *
     * @param trustStoreProvider <em>trust</em> store provider to set.
     * @return updated SSL configurator instance.
     */
    public SslConfigurator trustStoreProvider(String trustStoreProvider) {
        this.trustStoreProvider = trustStoreProvider;
        return this;
    }

    /**
     * Set the <em>key</em> store provider name.
     *
     * @param keyStoreProvider <em>key</em> store provider to set.
     * @return updated SSL configurator instance.
     */
    public SslConfigurator keyStoreProvider(String keyStoreProvider) {
        this.keyStoreProvider = keyStoreProvider;
        return this;
    }

    /**
     * Set the type of <em>trust</em> store.
     *
     * @param trustStoreType type of <em>trust</em> store to set.
     * @return updated SSL configurator instance.
     */
    public SslConfigurator trustStoreType(String trustStoreType) {
        this.trustStoreType = trustStoreType;
        return this;
    }

    /**
     * Set the type of <em>key</em> store.
     *
     * @param keyStoreType type of <em>key</em> store to set.
     * @return updated SSL configurator instance.
     */
    public SslConfigurator keyStoreType(String keyStoreType) {
        this.keyStoreType = keyStoreType;
        return this;
    }

    /**
     * Set the password of <em>trust</em> store.
     *
     * @param password password of <em>trust</em> store to set.
     * @return updated SSL configurator instance.
     */
    public SslConfigurator trustStorePassword(String password) {
        this.trustStorePass = password.toCharArray();
        return this;
    }

    /**
     * Set the password of <em>key</em> store.
     *
     * @param password password of <em>key</em> store to set.
     * @return updated SSL configurator instance.
     */
    public SslConfigurator keyStorePassword(String password) {
        this.keyStorePass = password.toCharArray();
        return this;
    }

    /**
     * Set the password of <em>key</em> store.
     *
     * @param password password of <em>key</em> store to set.
     * @return updated SSL configurator instance.
     */
    public SslConfigurator keyStorePassword(char[] password) {
        this.keyStorePass = password.clone();
        return this;
    }

    /**
     * Set the password of the key in the <em>key</em> store.
     *
     * @param password password of <em>key</em> to set.
     * @return updated SSL configurator instance.
     */
    public SslConfigurator keyPassword(String password) {
        this.keyPass = password.toCharArray();
        return this;
    }

    /**
     * Set the password of the key in the <em>key</em> store.
     *
     * @param password password of <em>key</em> to set.
     * @return updated SSL configurator instance.
     */
    public SslConfigurator keyPassword(char[] password) {
        this.keyPass = password.clone();
        return this;
    }

    /**
     * Set the <em>trust</em> store file name.
     * <p>
     * Setting a trust store instance resets any {@link #trustStore(java.security.KeyStore) trust store instance},
     * {@link #trustStoreBytes(byte[]) trust store payload} or {@link #trustStoreUrl(URL) trust store url}
     * value previously set.
     * </p>
     *
     * @param fileName {@link java.io.File file} name of the <em>trust</em> store.
     * @return updated SSL configurator instance.
     */
    public SslConfigurator trustStoreFile(String fileName) {
        this.trustStoreFile = fileName;
        this.trustStoreBytes = null;
        this.trustStoreUrl = null;
        this.trustStore = null;
        return this;
    }

    /**
     * Set the <em>trust</em> store file url.
     * <p>
     * Setting a trust store instance resets any {@link #trustStore(java.security.KeyStore) trust store instance},
     * {@link #trustStoreBytes(byte[]) trust store payload} or {@link #trustStoreUrl(URL) trust store url}
     * value previously set.
     * </p>
     *
     * @param url {@link java.net.URL url} link of the <em>trust</em> store.
     * @return updated SSL configurator instance.
     */
    public SslConfigurator trustStoreUrl(URL url) {
        this.trustStoreFile = null;
        this.trustStoreBytes = null;
        this.trustStoreUrl = url;
        this.trustStore = null;
        return this;
    }

    /**
     * Set the <em>trust</em> store payload as byte array.
     * <p>
     * Setting a trust store instance resets any {@link #trustStoreFile(String) trust store file},
     * {@link #trustStore(java.security.KeyStore) trust store instance} or {@link #trustStoreUrl(URL) trust store url}
     * value previously set.
     * </p>
     *
     * @param payload <em>trust</em> store payload.
     * @return updated SSL configurator instance.
     */
    public SslConfigurator trustStoreBytes(byte[] payload) {
        this.trustStoreBytes = payload.clone();
        this.trustStoreFile = null;
        this.trustStoreUrl = null;
        this.trustStore = null;
        return this;
    }

    /**
     * Set the <em>key</em> store file name.
     * <p>
     * Setting a key store instance resets any {@link #keyStore(java.security.KeyStore) key store instance},
     * {@link #keyStoreBytes(byte[]) key store payload} or {@link #keyStoreUrl(URL) key store url} value previously set.
     * </p>
     *
     * @param fileName {@link java.io.File file} name of the <em>key</em> store.
     * @return updated SSL configurator instance.
     */
    public SslConfigurator keyStoreFile(String fileName) {
        this.keyStoreFile = fileName;
        this.keyStoreUrl = null;
        this.keyStoreBytes = null;
        this.keyStore = null;
        return this;
    }

    /**
     * Set the <em>key</em> store url.
     * <p>
     * Setting a key store instance resets any {@link #keyStore(java.security.KeyStore) key store instance},
     * {@link #keyStoreBytes(byte[]) key store payload} or {@link #keyStoreFile(String) key store file}
     * value previously set.
     * </p>
     *
     * @param url {@link java.net.URL url} of the <em>key</em> store.
     * @return updated SSL configurator instance.
     */
    public SslConfigurator keyStoreUrl(URL url) {
        this.keyStoreFile = null;
        this.keyStoreUrl = url;
        this.keyStoreBytes = null;
        this.keyStore = null;
        return this;
    }

    /**
     * Set the <em>key</em> store payload as byte array.
     * <p>
     * Setting a key store instance resets any {@link #keyStoreFile(String) key store file},
     * {@link #keyStore(java.security.KeyStore) key store instance} or {@link #keyStoreUrl(URL) key store url}
     * value previously set.
     * </p>
     *
     * @param payload <em>key</em> store payload.
     * @return updated SSL configurator instance.
     */
    public SslConfigurator keyStoreBytes(byte[] payload) {
        this.keyStoreBytes = payload.clone();
        this.keyStoreUrl = null;
        this.keyStoreFile = null;
        this.keyStore = null;
        return this;
    }

    /**
     * Set the <em>trust</em> manager factory algorithm.
     *
     * @param algorithm the <em>trust</em> manager factory algorithm.
     * @return updated SSL configurator instance.
     */
    public SslConfigurator trustManagerFactoryAlgorithm(String algorithm) {
        this.trustManagerFactoryAlgorithm = algorithm;
        return this;
    }

    /**
     * Set the <em>key</em> manager factory algorithm.
     *
     * @param algorithm the <em>key</em> manager factory algorithm.
     * @return updated SSL configurator instance.
     */
    public SslConfigurator keyManagerFactoryAlgorithm(String algorithm) {
        this.keyManagerFactoryAlgorithm = algorithm;
        return this;
    }

    /**
     * Set the <em>trust</em> manager factory provider.
     *
     * @param provider the <em>trust</em> manager factory provider.
     * @return updated SSL configurator instance.
     */
    public SslConfigurator trustManagerFactoryProvider(String provider) {
        this.trustManagerFactoryProvider = provider;
        return this;
    }

    /**
     * Set the <em>key</em> manager factory provider.
     *
     * @param provider the <em>key</em> manager factory provider.
     * @return updated SSL configurator instance.
     */
    public SslConfigurator keyManagerFactoryProvider(String provider) {
        this.keyManagerFactoryProvider = provider;
        return this;
    }

    /**
     * Set the SSLContext protocol. The default value is {@code TLS} if this is {@code null}.
     *
     * @param protocol protocol for {@link javax.net.ssl.SSLContext#getProtocol()}.
     * @return updated SSL configurator instance.
     */
    public SslConfigurator securityProtocol(String protocol) {
        this.securityProtocol = protocol;
        return this;
    }

    /**
     * Get the <em>key</em> store instance.
     *
     * @return <em>key</em> store instance or {@code null} if not explicitly set.
     */
    KeyStore getKeyStore() {
        return keyStore;
    }

    /**
     * Set the <em>key</em> store instance.
     * <p>
     * Setting a key store instance resets any {@link #keyStoreFile(String) key store file},
     * {@link #keyStoreBytes(byte[]) key store payload} or {@link #keyStoreUrl(URL) key store url} value previously set.
     * </p>
     *
     * @param keyStore <em>key</em> store instance.
     * @return updated SSL configurator instance.
     */
    public SslConfigurator keyStore(KeyStore keyStore) {
        this.keyStore = keyStore;
        this.keyStoreFile = null;
        this.keyStoreBytes = null;
        this.keyStoreUrl = null;
        return this;
    }

    /**
     * Get the <em>trust</em> store instance.
     *
     * @return <em>trust</em> store instance or {@code null} if not explicitly set.
     */
    KeyStore getTrustStore() {
        return trustStore;
    }

    /**
     * Set the <em>trust</em> store instance.
     * <p>
     * Setting a trust store instance resets any {@link #trustStoreFile(String) trust store file},
     * {@link #trustStoreBytes(byte[]) trust store payload} or {@link #trustStoreUrl(URL) trust store url} value previously set.
     * </p>
     * @param trustStore <em>trust</em> store instance.
     * @return updated SSL configurator instance.
     */
    public SslConfigurator trustStore(KeyStore trustStore) {
        this.trustStore = trustStore;
        this.trustStoreUrl = null;
        this.trustStoreFile = null;
        this.trustStoreBytes = null;
        return this;
    }

    /**
     * Create new SSL context instance using the current SSL context configuration.
     *
     * @return newly configured SSL context instance.
     */
    public SSLContext createSSLContext() {
        TrustManagerFactory trustManagerFactory = null;
        KeyManagerFactory keyManagerFactory = null;

        KeyStore _keyStore = keyStore;
        if (_keyStore == null && (keyStoreBytes != null || keyStoreFile != null || keyStoreUrl != null)) {
            try {
                if (keyStoreProvider != null) {
                    _keyStore = KeyStore.getInstance(
                            keyStoreType != null ? keyStoreType : KeyStore.getDefaultType(), keyStoreProvider);
                } else {
                    _keyStore = KeyStore.getInstance(keyStoreType != null ? keyStoreType : KeyStore.getDefaultType());
                }
                InputStream keyStoreInputStream = null;
                try {
                    if (keyStoreBytes != null) {
                        keyStoreInputStream = new ByteArrayInputStream(keyStoreBytes);
                    } else if (keyStoreUrl != null) {
                      keyStoreInputStream = keyStoreUrl.openStream();
                    } else if (!keyStoreFile.equals("NONE")) {
                        keyStoreInputStream = Files.newInputStream(new File(keyStoreFile).toPath());
                    }
                    _keyStore.load(keyStoreInputStream, keyStorePass);
                } finally {
                    try {
                        if (keyStoreInputStream != null) {
                            keyStoreInputStream.close();
                        }
                    } catch (IOException ignored) {
                    }
                }
            } catch (KeyStoreException e) {
                throw new IllegalStateException(LocalizationMessages.SSL_KS_IMPL_NOT_FOUND(), e);
            } catch (CertificateException e) {
                throw new IllegalStateException(LocalizationMessages.SSL_KS_CERT_LOAD_ERROR(), e);
            } catch (FileNotFoundException e) {
                throw new IllegalStateException(LocalizationMessages.SSL_KS_FILE_NOT_FOUND(keyStoreFile), e);
            } catch (IOException e) {
                throw new IllegalStateException(LocalizationMessages.SSL_KS_LOAD_ERROR(keyStoreFile), e);
            } catch (NoSuchProviderException e) {
                throw new IllegalStateException(LocalizationMessages.SSL_KS_PROVIDERS_NOT_REGISTERED(), e);
            } catch (NoSuchAlgorithmException e) {
                throw new IllegalStateException(LocalizationMessages.SSL_KS_INTEGRITY_ALGORITHM_NOT_FOUND(), e);
            }
        }
        if (_keyStore != null) {
            String kmfAlgorithm = keyManagerFactoryAlgorithm;
            if (kmfAlgorithm == null) {
                kmfAlgorithm = AccessController.doPrivileged(PropertiesHelper.getSystemProperty(
                        KEY_MANAGER_FACTORY_ALGORITHM, KeyManagerFactory.getDefaultAlgorithm()));
            }
            try {
                if (keyManagerFactoryProvider != null) {
                    keyManagerFactory = KeyManagerFactory.getInstance(kmfAlgorithm, keyManagerFactoryProvider);
                } else {
                    keyManagerFactory = KeyManagerFactory.getInstance(kmfAlgorithm);
                }
                final char[] password = keyPass != null ? keyPass : keyStorePass;
                if (password != null) {
                    keyManagerFactory.init(_keyStore, password);
                } else {
                    String ksName =
                            keyStoreProvider != null ? LocalizationMessages.SSL_KMF_NO_PASSWORD_FOR_PROVIDER_BASED_KS()
                                    : keyStoreBytes != null ? LocalizationMessages.SSL_KMF_NO_PASSWORD_FOR_BYTE_BASED_KS()
                                            : keyStoreFile;

                    LOGGER.config(LocalizationMessages.SSL_KMF_NO_PASSWORD_SET(ksName));
                    keyManagerFactory = null;
                }
            } catch (KeyStoreException e) {
                throw new IllegalStateException(LocalizationMessages.SSL_KMF_INIT_FAILED(), e);
            } catch (UnrecoverableKeyException e) {
                throw new IllegalStateException(LocalizationMessages.SSL_KMF_UNRECOVERABLE_KEY(), e);
            } catch (NoSuchAlgorithmException e) {
                throw new IllegalStateException(LocalizationMessages.SSL_KMF_ALGORITHM_NOT_SUPPORTED(), e);
            } catch (NoSuchProviderException e) {
                throw new IllegalStateException(LocalizationMessages.SSL_KMF_PROVIDER_NOT_REGISTERED(), e);
            }
        }

        KeyStore _trustStore = trustStore;
        if (_trustStore == null && (trustStoreBytes != null || trustStoreFile != null || trustStoreUrl != null)) {
            try {
                if (trustStoreProvider != null) {
                    _trustStore = KeyStore.getInstance(
                            trustStoreType != null ? trustStoreType : KeyStore.getDefaultType(), trustStoreProvider);
                } else {
                    _trustStore =
                            KeyStore.getInstance(trustStoreType != null ? trustStoreType : KeyStore.getDefaultType());
                }
                InputStream trustStoreInputStream = null;
                try {
                    if (trustStoreBytes != null) {
                        trustStoreInputStream = new ByteArrayInputStream(trustStoreBytes);
                    } else if (trustStoreUrl != null) {
                      trustStoreInputStream = trustStoreUrl.openStream();
                    } else if (!trustStoreFile.equals("NONE")) {
                        trustStoreInputStream = Files.newInputStream(new File(trustStoreFile).toPath());
                    }
                    _trustStore.load(trustStoreInputStream, trustStorePass);
                } finally {
                    try {
                        if (trustStoreInputStream != null) {
                            trustStoreInputStream.close();
                        }
                    } catch (IOException ignored) {
                    }
                }
            } catch (KeyStoreException e) {
                throw new IllegalStateException(LocalizationMessages.SSL_TS_IMPL_NOT_FOUND(), e);
            } catch (CertificateException e) {
                throw new IllegalStateException(LocalizationMessages.SSL_TS_CERT_LOAD_ERROR(), e);
            } catch (FileNotFoundException e) {
                throw new IllegalStateException(LocalizationMessages.SSL_TS_FILE_NOT_FOUND(trustStoreFile), e);
            } catch (IOException e) {
                throw new IllegalStateException(LocalizationMessages.SSL_TS_LOAD_ERROR(trustStoreFile), e);
            } catch (NoSuchProviderException e) {
                throw new IllegalStateException(LocalizationMessages.SSL_TS_PROVIDERS_NOT_REGISTERED(), e);
            } catch (NoSuchAlgorithmException e) {
                throw new IllegalStateException(LocalizationMessages.SSL_TS_INTEGRITY_ALGORITHM_NOT_FOUND(), e);
            }
        }
        if (_trustStore != null) {
            String tmfAlgorithm = trustManagerFactoryAlgorithm;
            if (tmfAlgorithm == null) {
                tmfAlgorithm = AccessController.doPrivileged(PropertiesHelper.getSystemProperty(
                        TRUST_MANAGER_FACTORY_ALGORITHM, TrustManagerFactory.getDefaultAlgorithm()));
            }

            try {
                if (trustManagerFactoryProvider != null) {
                    trustManagerFactory = TrustManagerFactory.getInstance(tmfAlgorithm, trustManagerFactoryProvider);
                } else {
                    trustManagerFactory = TrustManagerFactory.getInstance(tmfAlgorithm);
                }
                trustManagerFactory.init(_trustStore);
            } catch (KeyStoreException e) {
                throw new IllegalStateException(LocalizationMessages.SSL_TMF_INIT_FAILED(), e);
            } catch (NoSuchAlgorithmException e) {
                throw new IllegalStateException(LocalizationMessages.SSL_TMF_ALGORITHM_NOT_SUPPORTED(), e);
            } catch (NoSuchProviderException e) {
                throw new IllegalStateException(LocalizationMessages.SSL_TMF_PROVIDER_NOT_REGISTERED(), e);
            }
        }

        try {
            String secProtocol = "TLS";
            if (securityProtocol != null) {
                secProtocol = securityProtocol;
            }
            final SSLContext sslContext = SSLContext.getInstance(secProtocol);
            sslContext.init(
                    keyManagerFactory != null ? keyManagerFactory.getKeyManagers() : null,
                    trustManagerFactory != null ? trustManagerFactory.getTrustManagers() : null,
                    null);
            return sslContext;
        } catch (KeyManagementException e) {
            throw new IllegalStateException(LocalizationMessages.SSL_CTX_INIT_FAILED(), e);
        } catch (NoSuchAlgorithmException e) {
            throw new IllegalStateException(LocalizationMessages.SSL_CTX_ALGORITHM_NOT_SUPPORTED(), e);
        }
    }

    /**
     * Retrieve the SSL context configuration from the supplied properties.
     *
     * @param props properties containing the SSL context configuration.
     * @return updated SSL configurator instance.
     */
    public SslConfigurator retrieve(Properties props) {
        trustStoreProvider = props.getProperty(TRUST_STORE_PROVIDER);
        keyStoreProvider = props.getProperty(KEY_STORE_PROVIDER);

        trustManagerFactoryProvider = props.getProperty(TRUST_MANAGER_FACTORY_PROVIDER);
        keyManagerFactoryProvider = props.getProperty(KEY_MANAGER_FACTORY_PROVIDER);

        trustStoreType = props.getProperty(TRUST_STORE_TYPE);
        keyStoreType = props.getProperty(KEY_STORE_TYPE);

        if (props.getProperty(TRUST_STORE_PASSWORD) != null) {
            trustStorePass = props.getProperty(TRUST_STORE_PASSWORD).toCharArray();
        } else {
            trustStorePass = null;
        }

        if (props.getProperty(KEY_STORE_PASSWORD) != null) {
            keyStorePass = props.getProperty(KEY_STORE_PASSWORD).toCharArray();
        } else {
            keyStorePass = null;
        }

        trustStoreFile = props.getProperty(TRUST_STORE_FILE);
        keyStoreFile = props.getProperty(KEY_STORE_FILE);

        keyStoreUrl = null;
        trustStoreUrl = null;

        trustStoreBytes = null;
        keyStoreBytes = null;

        trustStore = null;
        keyStore = null;

        securityProtocol = "TLS";

        return this;
    }

    /**
     * Retrieve the SSL context configuration from the system properties.
     *
     * @return updated SSL configurator instance.
     */
    public SslConfigurator retrieve() {
        trustStoreProvider = AccessController.doPrivileged(
                PropertiesHelper.getSystemProperty(TRUST_STORE_PROVIDER));
        keyStoreProvider = AccessController.doPrivileged(
                PropertiesHelper.getSystemProperty(KEY_STORE_PROVIDER));

        trustManagerFactoryProvider = AccessController.doPrivileged(
                PropertiesHelper.getSystemProperty(TRUST_MANAGER_FACTORY_PROVIDER));
        keyManagerFactoryProvider = AccessController.doPrivileged(
                PropertiesHelper.getSystemProperty(KEY_MANAGER_FACTORY_PROVIDER));

        trustStoreType = AccessController.doPrivileged(PropertiesHelper.getSystemProperty(TRUST_STORE_TYPE));
        keyStoreType = AccessController.doPrivileged(PropertiesHelper.getSystemProperty(KEY_STORE_TYPE));

        final String trustStorePassword = AccessController.doPrivileged(
                PropertiesHelper.getSystemProperty(TRUST_STORE_PASSWORD));
        if (trustStorePassword != null) {
            trustStorePass = trustStorePassword.toCharArray();
        } else {
            trustStorePass = null;
        }

        final String keyStorePassword = AccessController.doPrivileged(
                PropertiesHelper.getSystemProperty(KEY_STORE_PASSWORD));
        if (keyStorePassword != null) {
            keyStorePass = keyStorePassword.toCharArray();
        } else {
            keyStorePass = null;
        }

        trustStoreFile = AccessController.doPrivileged(PropertiesHelper.getSystemProperty(TRUST_STORE_FILE));
        keyStoreFile = AccessController.doPrivileged(PropertiesHelper.getSystemProperty(KEY_STORE_FILE));

        trustStoreUrl = null;
        keyStoreUrl = null;

        trustStoreBytes = null;
        keyStoreBytes = null;

        trustStore = null;
        keyStore = null;

        securityProtocol = "TLS";

        return this;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }
        SslConfigurator that = (SslConfigurator) o;
        return Objects.equals(keyStore, that.keyStore)
                && Objects.equals(trustStore, that.trustStore)
                && Objects.equals(trustStoreProvider, that.trustStoreProvider)
                && Objects.equals(keyStoreProvider, that.keyStoreProvider)
                && Objects.equals(trustStoreType, that.trustStoreType)
                && Objects.equals(keyStoreType, that.keyStoreType)
                && Arrays.equals(trustStorePass, that.trustStorePass)
                && Arrays.equals(keyStorePass, that.keyStorePass)
                && Arrays.equals(keyPass, that.keyPass)
                && Objects.equals(trustStoreFile, that.trustStoreFile)
                && Objects.equals(keyStoreFile, that.keyStoreFile)
                && Objects.equals(trustStoreUrl, that.trustStoreUrl)
                && Objects.equals(keyStoreUrl, that.keyStoreUrl)
                && Arrays.equals(trustStoreBytes, that.trustStoreBytes)
                && Arrays.equals(keyStoreBytes, that.keyStoreBytes)
                && Objects.equals(trustManagerFactoryAlgorithm, that.trustManagerFactoryAlgorithm)
                && Objects.equals(keyManagerFactoryAlgorithm, that.keyManagerFactoryAlgorithm)
                && Objects.equals(trustManagerFactoryProvider, that.trustManagerFactoryProvider)
                && Objects.equals(keyManagerFactoryProvider, that.keyManagerFactoryProvider)
                && Objects.equals(securityProtocol, that.securityProtocol);
    }

    @Override
    public int hashCode() {
        int result = Objects.hash(keyStore, trustStore, trustStoreProvider, keyStoreProvider, trustStoreType, keyStoreType,
                trustStoreFile, keyStoreFile, trustStoreUrl, keyStoreUrl, trustManagerFactoryAlgorithm,
                keyManagerFactoryAlgorithm, trustManagerFactoryProvider, keyManagerFactoryProvider, securityProtocol,
                trustStorePass, keyStorePass, keyPass, trustStoreBytes, keyStoreBytes);
        return result;
    }
}
