//
//  ========================================================================
//  Copyright (c) 1995-2019 Mort Bay Consulting Pty. Ltd.
//  ------------------------------------------------------------------------
//  All rights reserved. This program and the accompanying materials
//  are made available under the terms of the Eclipse Public License v1.0
//  and Apache License v2.0 which accompanies this distribution.
//
//      The Eclipse Public License is available at
//      http://www.eclipse.org/legal/epl-v10.html
//
//      The Apache License v2.0 is available at
//      http://www.opensource.org/licenses/apache2.0.php
//
//  You may elect to redistribute this code under either of these licenses.
//  ========================================================================
//

package org.eclipse.jetty.util.ssl;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.security.KeyStore;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.Security;
import java.security.cert.CRL;
import java.security.cert.CertStore;
import java.security.cert.Certificate;
import java.security.cert.CollectionCertStoreParameters;
import java.security.cert.PKIXBuilderParameters;
import java.security.cert.PKIXCertPathChecker;
import java.security.cert.X509CertSelector;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.Consumer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.net.ssl.CertPathTrustManagerParameters;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SNIHostName;
import javax.net.ssl.SNIMatcher;
import javax.net.ssl.SNIServerName;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLParameters;
import javax.net.ssl.SSLPeerUnverifiedException;
import javax.net.ssl.SSLServerSocket;
import javax.net.ssl.SSLServerSocketFactory;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSessionContext;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.StandardConstants;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509ExtendedKeyManager;
import javax.net.ssl.X509TrustManager;

import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.annotation.ManagedAttribute;
import org.eclipse.jetty.util.annotation.ManagedObject;
import org.eclipse.jetty.util.component.AbstractLifeCycle;
import org.eclipse.jetty.util.component.Dumpable;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.util.security.CertificateUtils;
import org.eclipse.jetty.util.security.CertificateValidator;
import org.eclipse.jetty.util.security.Password;

/**
 * SslContextFactory is used to configure SSL connectors
 * as well as HttpClient. It holds all SSL parameters and
 * creates SSL context based on these parameters to be
 * used by the SSL connectors.
 */
@ManagedObject
public class SslContextFactory extends AbstractLifeCycle implements Dumpable
{
    public final static TrustManager[] TRUST_ALL_CERTS = new X509TrustManager[]{new X509TrustManager()
    {
        @Override
        public java.security.cert.X509Certificate[] getAcceptedIssuers()
        {
            return new java.security.cert.X509Certificate[]{};
        }

        @Override
        public void checkClientTrusted(java.security.cert.X509Certificate[] certs, String authType)
        {
        }

        @Override
        public void checkServerTrusted(java.security.cert.X509Certificate[] certs, String authType)
        {
        }
    }};

    private static final Logger LOG = Log.getLogger(SslContextFactory.class);
    private static final Logger LOG_CONFIG = LOG.getLogger("config");

    public static final String DEFAULT_KEYMANAGERFACTORY_ALGORITHM =
            (Security.getProperty("ssl.KeyManagerFactory.algorithm") == null ?
                    KeyManagerFactory.getDefaultAlgorithm() : Security.getProperty("ssl.KeyManagerFactory.algorithm"));

    public static final String DEFAULT_TRUSTMANAGERFACTORY_ALGORITHM =
            (Security.getProperty("ssl.TrustManagerFactory.algorithm") == null ?
                    TrustManagerFactory.getDefaultAlgorithm() : Security.getProperty("ssl.TrustManagerFactory.algorithm"));

    /** String name of key password property. */
    public static final String KEYPASSWORD_PROPERTY = "org.eclipse.jetty.ssl.keypassword";

    /** String name of keystore password property. */
    public static final String PASSWORD_PROPERTY = "org.eclipse.jetty.ssl.password";

    /** Default Excluded Protocols List */
    private static final String[] DEFAULT_EXCLUDED_PROTOCOLS = {"SSL", "SSLv2", "SSLv2Hello", "SSLv3"};

    /** Default Excluded Cipher Suite List */
    private static final String[] DEFAULT_EXCLUDED_CIPHER_SUITES = {
            // Exclude weak / insecure ciphers
            "^.*_(MD5|SHA|SHA1)$",
            // Exclude ciphers that don't support forward secrecy
            "^TLS_RSA_.*$",
            // The following exclusions are present to cleanup known bad cipher
            // suites that may be accidentally included via include patterns.
            // The default enabled cipher list in Java will not include these
            // (but they are available in the supported list).
            "^SSL_.*$",
            "^.*_NULL_.*$",
            "^.*_anon_.*$"
    };

    private final Set<String> _excludeProtocols = new LinkedHashSet<>();
    private final Set<String> _includeProtocols = new LinkedHashSet<>();
    private final Set<String> _excludeCipherSuites = new LinkedHashSet<>();
    private final List<String> _includeCipherSuites = new ArrayList<>();
    private final Map<String, X509> _aliasX509 = new HashMap<>();
    private final Map<String, X509> _certHosts = new HashMap<>();
    private final Map<String, X509> _certWilds = new HashMap<>();
    private String[] _selectedProtocols;
    private boolean _useCipherSuitesOrder = true;
    private Comparator<String> _cipherComparator;
    private String[] _selectedCipherSuites;
    private Resource _keyStoreResource;
    private String _keyStoreProvider;
    private String _keyStoreType = "JKS";
    private String _certAlias;
    private Resource _trustStoreResource;
    private String _trustStoreProvider;
    private String _trustStoreType;
    private boolean _needClientAuth = false;
    private boolean _wantClientAuth = false;
    private Password _keyStorePassword;
    private Password _keyManagerPassword;
    private Password _trustStorePassword;
    private String _sslProvider;
    private String _sslProtocol = "TLS";
    private String _secureRandomAlgorithm;
    private String _keyManagerFactoryAlgorithm = DEFAULT_KEYMANAGERFACTORY_ALGORITHM;
    private String _trustManagerFactoryAlgorithm = DEFAULT_TRUSTMANAGERFACTORY_ALGORITHM;
    private boolean _validateCerts;
    private boolean _validatePeerCerts;
    private int _maxCertPathLength = -1;
    private String _crlPath;
    private boolean _enableCRLDP = false;
    private boolean _enableOCSP = false;
    private String _ocspResponderURL;
    private KeyStore _setKeyStore;
    private KeyStore _setTrustStore;
    private boolean _sessionCachingEnabled = true;
    private int _sslSessionCacheSize = -1;
    private int _sslSessionTimeout = -1;
    private SSLContext _setContext;
    private String _endpointIdentificationAlgorithm = "HTTPS";
    private boolean _trustAll;
    private boolean _renegotiationAllowed = true;
    private int _renegotiationLimit = 5;
    private Factory _factory;
    private PKIXCertPathChecker _pkixCertPathChecker;
    private HostnameVerifier _hostnameVerifier;

    /**
     * Construct an instance of SslContextFactory
     * Default constructor for use in XmlConfiguration files
     */
    public SslContextFactory()
    {
        this(false);
    }

    /**
     * Construct an instance of SslContextFactory
     * Default constructor for use in XmlConfiguration files
     *
     * @param trustAll whether to blindly trust all certificates
     * @see #setTrustAll(boolean)
     */
    public SslContextFactory(boolean trustAll)
    {
        this(trustAll, null);
    }

    /**
     * Construct an instance of SslContextFactory
     *
     * @param keyStorePath default keystore location
     */
    public SslContextFactory(String keyStorePath)
    {
        this(false, keyStorePath);
    }

    private SslContextFactory(boolean trustAll, String keyStorePath)
    {
        setTrustAll(trustAll);
        setExcludeProtocols(DEFAULT_EXCLUDED_PROTOCOLS);
        setExcludeCipherSuites(DEFAULT_EXCLUDED_CIPHER_SUITES);

        if (keyStorePath != null)
            setKeyStorePath(keyStorePath);
    }

    /**
     * Creates the SSLContext object and starts the lifecycle
     */
    @Override
    protected void doStart() throws Exception
    {
        super.doStart();
        synchronized (this)
        {
            load();
        }

        secureConfigurationCheck();
    }

    protected void secureConfigurationCheck()
    {
        if (isTrustAll())
            LOG_CONFIG.warn("Trusting all certificates configured for {}",this);
        if (getEndpointIdentificationAlgorithm()==null)
            LOG_CONFIG.warn("No Client EndPointIdentificationAlgorithm configured for {}",this);

        SSLEngine engine = _factory._context.createSSLEngine();
        customize(engine);
        SSLParameters supported = engine.getSSLParameters();

        for (String protocol : supported.getProtocols())
        {
            for (String excluded : DEFAULT_EXCLUDED_PROTOCOLS)
            {
                if (excluded.equals(protocol))
                    LOG_CONFIG.warn("Protocol {} not excluded for {}", protocol, this);
            }
        }

        for (String suite : supported.getCipherSuites())
        {
            for (String excludedSuiteRegex : DEFAULT_EXCLUDED_CIPHER_SUITES)
            {
                if (suite.matches(excludedSuiteRegex))
                    LOG_CONFIG.warn("Weak cipher suite {} enabled for {}", suite, this);
            }
        }
    }

    private void load() throws Exception
    {
        SSLContext context = _setContext;
        KeyStore keyStore = _setKeyStore;
        KeyStore trustStore = _setTrustStore;

        if (context == null)
        {
            // Is this an empty factory?
            if (keyStore == null && _keyStoreResource == null && trustStore == null && _trustStoreResource == null)
            {
                TrustManager[] trust_managers = null;

                if (isTrustAll())
                {
                    if (LOG.isDebugEnabled())
                        LOG.debug("No keystore or trust store configured.  ACCEPTING UNTRUSTED CERTIFICATES!!!!!");
                    // Create a trust manager that does not validate certificate chains
                    trust_managers = TRUST_ALL_CERTS;
                }

                String algorithm = getSecureRandomAlgorithm();
                SecureRandom secureRandom = algorithm == null ? null : SecureRandom.getInstance(algorithm);
                context = _sslProvider == null ? SSLContext.getInstance(_sslProtocol) : SSLContext.getInstance(_sslProtocol, _sslProvider);
                context.init(null, trust_managers, secureRandom);
            }
            else
            {
                if (keyStore == null)
                    keyStore = loadKeyStore(_keyStoreResource);
                if (trustStore == null)
                    trustStore = loadTrustStore(_trustStoreResource);

                Collection<? extends CRL> crls = loadCRL(getCrlPath());

                // Look for X.509 certificates to create alias map
                if (keyStore != null)
                {
                    for (String alias : Collections.list(keyStore.aliases()))
                    {
                        Certificate certificate = keyStore.getCertificate(alias);
                        if (certificate != null && "X.509".equals(certificate.getType()))
                        {
                            X509Certificate x509C = (X509Certificate)certificate;

                            // Exclude certificates with special uses
                            if (X509.isCertSign(x509C))
                            {
                                if (LOG.isDebugEnabled())
                                    LOG.debug("Skipping " + x509C);
                                continue;
                            }
                            X509 x509 = new X509(alias, x509C);
                            _aliasX509.put(alias, x509);

                            if (isValidateCerts())
                            {
                                CertificateValidator validator = new CertificateValidator(trustStore, crls);
                                validator.setMaxCertPathLength(getMaxCertPathLength());
                                validator.setEnableCRLDP(isEnableCRLDP());
                                validator.setEnableOCSP(isEnableOCSP());
                                validator.setOcspResponderURL(getOcspResponderURL());
                                validator.validate(keyStore, x509C); // TODO what about truststore?
                            }

                            LOG.info("x509={} for {}", x509, this);

                            for (String h : x509.getHosts())
                                _certHosts.put(h, x509);
                            for (String w : x509.getWilds())
                                _certWilds.put(w, x509);
                        }
                    }
                }

                // Instantiate key and trust managers
                KeyManager[] keyManagers = getKeyManagers(keyStore);
                TrustManager[] trustManagers = getTrustManagers(trustStore, crls);

                // Initialize context
                SecureRandom secureRandom = (_secureRandomAlgorithm == null) ? null : SecureRandom.getInstance(_secureRandomAlgorithm);
                context = _sslProvider == null ? SSLContext.getInstance(_sslProtocol) : SSLContext.getInstance(_sslProtocol, _sslProvider);
                context.init(keyManagers, trustManagers, secureRandom);
            }
        }

        // Initialize cache
        SSLSessionContext serverContext = context.getServerSessionContext();
        if (serverContext != null)
        {
            if (getSslSessionCacheSize() > -1)
                serverContext.setSessionCacheSize(getSslSessionCacheSize());
            if (getSslSessionTimeout() > -1)
                serverContext.setSessionTimeout(getSslSessionTimeout());
        }

        // select the protocols and ciphers
        SSLParameters enabled = context.getDefaultSSLParameters();
        SSLParameters supported = context.getSupportedSSLParameters();
        selectCipherSuites(enabled.getCipherSuites(), supported.getCipherSuites());
        selectProtocols(enabled.getProtocols(), supported.getProtocols());

        _factory = new Factory(keyStore, trustStore, context);
        if (LOG.isDebugEnabled())
        {
            LOG.debug("Selected Protocols {} of {}", Arrays.asList(_selectedProtocols), Arrays.asList(supported.getProtocols()));
            LOG.debug("Selected Ciphers   {} of {}", Arrays.asList(_selectedCipherSuites), Arrays.asList(supported.getCipherSuites()));
        }
    }
    
    @Override
    public String dump()
    {
        return Dumpable.dump(this);
    }
    
    @Override
    public void dump(Appendable out, String indent) throws IOException
    {
        try
        {
            SSLEngine sslEngine = SSLContext.getDefault().createSSLEngine();
            Dumpable.dumpObjects(out, indent, this, "trustAll=" + _trustAll,
                new SslSelectionDump("Protocol",
                    sslEngine.getSupportedProtocols(),
                    sslEngine.getEnabledProtocols(),
                    getExcludeProtocols(),
                    getIncludeProtocols()),
                new SslSelectionDump("Cipher Suite",
                    sslEngine.getSupportedCipherSuites(),
                    sslEngine.getEnabledCipherSuites(),
                    getExcludeCipherSuites(),
                    getIncludeCipherSuites()));
        }
        catch (NoSuchAlgorithmException ignore)
        {
            LOG.ignore(ignore);
        }
    }

    List<SslSelectionDump> selectionDump() throws NoSuchAlgorithmException
    {
        /* Use a pristine SSLEngine (not one from this SslContextFactory).
         * This will allow for proper detection and identification
         * of JRE/lib/security/java.security level disabled features
         */
        SSLEngine sslEngine = SSLContext.getDefault().createSSLEngine();

        List<SslSelectionDump> selections = new ArrayList<>();

        // protocols
        selections.add(new SslSelectionDump("Protocol",
                sslEngine.getSupportedProtocols(),
                sslEngine.getEnabledProtocols(),
                getExcludeProtocols(),
                getIncludeProtocols()));

        // ciphers
        selections.add(new SslSelectionDump("Cipher Suite",
                sslEngine.getSupportedCipherSuites(),
                sslEngine.getEnabledCipherSuites(),
                getExcludeCipherSuites(),
                getIncludeCipherSuites()));

        return selections;
    }
    
    @Override
    protected void doStop() throws Exception
    {
        synchronized (this)
        {
            unload();
        }
        super.doStop();
    }

    private void unload()
    {
        _factory = null;
        _selectedProtocols = null;
        _selectedCipherSuites = null;
        _aliasX509.clear();
        _certHosts.clear();
        _certWilds.clear();
    }

    @ManagedAttribute(value = "The selected TLS protocol versions", readonly = true)
    public String[] getSelectedProtocols()
    {
        return Arrays.copyOf(_selectedProtocols, _selectedProtocols.length);
    }

    @ManagedAttribute(value = "The selected cipher suites", readonly = true)
    public String[] getSelectedCipherSuites()
    {
        return Arrays.copyOf(_selectedCipherSuites, _selectedCipherSuites.length);
    }

    public Comparator<String> getCipherComparator()
    {
        return _cipherComparator;
    }

    public void setCipherComparator(Comparator<String> cipherComparator)
    {
        if (cipherComparator != null)
            setUseCipherSuitesOrder(true);
        _cipherComparator = cipherComparator;
    }

    public Set<String> getAliases()
    {
        return Collections.unmodifiableSet(_aliasX509.keySet());
    }

    public X509 getX509(String alias)
    {
        return _aliasX509.get(alias);
    }

    /**
     * @return The array of protocol names to exclude from
     * {@link SSLEngine#setEnabledProtocols(String[])}
     */
    @ManagedAttribute("The excluded TLS protocols")
    public String[] getExcludeProtocols()
    {
        return _excludeProtocols.toArray(new String[0]);
    }

    /**
     * @param protocols The array of protocol names to exclude from
     *                  {@link SSLEngine#setEnabledProtocols(String[])}
     */
    public void setExcludeProtocols(String... protocols)
    {
        _excludeProtocols.clear();
        _excludeProtocols.addAll(Arrays.asList(protocols));
    }

    /**
     * @param protocol Protocol names to add to {@link SSLEngine#setEnabledProtocols(String[])}
     */
    public void addExcludeProtocols(String... protocol)
    {
        _excludeProtocols.addAll(Arrays.asList(protocol));
    }

    /**
     * @return The array of protocol names to include in
     * {@link SSLEngine#setEnabledProtocols(String[])}
     */
    @ManagedAttribute("The included TLS protocols")
    public String[] getIncludeProtocols()
    {
        return _includeProtocols.toArray(new String[0]);
    }

    /**
     * @param protocols The array of protocol names to include in
     *                  {@link SSLEngine#setEnabledProtocols(String[])}
     */
    public void setIncludeProtocols(String... protocols)
    {
        _includeProtocols.clear();
        _includeProtocols.addAll(Arrays.asList(protocols));
    }

    /**
     * @return The array of cipher suite names to exclude from
     * {@link SSLEngine#setEnabledCipherSuites(String[])}
     */
    @ManagedAttribute("The excluded cipher suites")
    public String[] getExcludeCipherSuites()
    {
        return _excludeCipherSuites.toArray(new String[0]);
    }

    /**
     * You can either use the exact cipher suite name or a a regular expression.
     *
     * @param cipherSuites The array of cipher suite names to exclude from
     *                     {@link SSLEngine#setEnabledCipherSuites(String[])}
     */
    public void setExcludeCipherSuites(String... cipherSuites)
    {
        _excludeCipherSuites.clear();
        _excludeCipherSuites.addAll(Arrays.asList(cipherSuites));
    }

    /**
     * @param cipher Cipher names to add to {@link SSLEngine#setEnabledCipherSuites(String[])}
     */
    public void addExcludeCipherSuites(String... cipher)
    {
        _excludeCipherSuites.addAll(Arrays.asList(cipher));
    }

    /**
     * @return The array of cipher suite names to include in
     * {@link SSLEngine#setEnabledCipherSuites(String[])}
     */
    @ManagedAttribute("The included cipher suites")
    public String[] getIncludeCipherSuites()
    {
        return _includeCipherSuites.toArray(new String[0]);
    }

    /**
     * You can either use the exact cipher suite name or a a regular expression.
     *
     * @param cipherSuites The array of cipher suite names to include in
     *                     {@link SSLEngine#setEnabledCipherSuites(String[])}
     */
    public void setIncludeCipherSuites(String... cipherSuites)
    {
        _includeCipherSuites.clear();
        _includeCipherSuites.addAll(Arrays.asList(cipherSuites));
    }

    @ManagedAttribute("Whether to respect the cipher suites order")
    public boolean isUseCipherSuitesOrder()
    {
        return _useCipherSuitesOrder;
    }

    public void setUseCipherSuitesOrder(boolean useCipherSuitesOrder)
    {
        _useCipherSuitesOrder = useCipherSuitesOrder;
    }

    /**
     * @return The file or URL of the SSL Key store.
     */
    @ManagedAttribute("The keyStore path")
    public String getKeyStorePath()
    {
        return Objects.toString(_keyStoreResource, null);
    }

    /**
     * @param keyStorePath The file or URL of the SSL Key store.
     */
    public void setKeyStorePath(String keyStorePath)
    {
        try
        {
            _keyStoreResource = Resource.newResource(keyStorePath);
        }
        catch (Exception e)
        {
            throw new IllegalArgumentException(e);
        }
    }

    /**
     * @return The provider of the key store
     */
    @ManagedAttribute("The keyStore provider name")
    public String getKeyStoreProvider()
    {
        return _keyStoreProvider;
    }

    /**
     * @param keyStoreProvider The provider of the key store
     */
    public void setKeyStoreProvider(String keyStoreProvider)
    {
        _keyStoreProvider = keyStoreProvider;
    }

    /**
     * @return The type of the key store (default "JKS")
     */
    @ManagedAttribute("The keyStore type")
    public String getKeyStoreType()
    {
        return (_keyStoreType);
    }

    /**
     * @param keyStoreType The type of the key store (default "JKS")
     */
    public void setKeyStoreType(String keyStoreType)
    {
        _keyStoreType = keyStoreType;
    }

    /**
     * @return Alias of SSL certificate for the connector
     */
    @ManagedAttribute("The certificate alias")
    public String getCertAlias()
    {
        return _certAlias;
    }

    /**
     * Set the default certificate Alias.
     * <p>This can be used if there are multiple non-SNI certificates
     * to specify the certificate that should be used, or with SNI
     * certificates to set a certificate to try if no others match
     * </p>
     *
     * @param certAlias Alias of SSL certificate for the connector
     */
    public void setCertAlias(String certAlias)
    {
        _certAlias = certAlias;
    }

    @ManagedAttribute("The trustStore path")
    public String getTrustStorePath()
    {
        return Objects.toString(_trustStoreResource, null);
    }

    /**
     * @param trustStorePath The file name or URL of the trust store location
     */
    public void setTrustStorePath(String trustStorePath)
    {
        try
        {
            _trustStoreResource = Resource.newResource(trustStorePath);
        }
        catch (Exception e)
        {
            throw new IllegalArgumentException(e);
        }
    }

    /**
     * @return The provider of the trust store
     */
    @ManagedAttribute("The trustStore provider name")
    public String getTrustStoreProvider()
    {
        return _trustStoreProvider;
    }

    /**
     * @param trustStoreProvider The provider of the trust store
     */
    public void setTrustStoreProvider(String trustStoreProvider)
    {
        _trustStoreProvider = trustStoreProvider;
    }

    /**
     * @return The type of the trust store
     */
    @ManagedAttribute("The trustStore type")
    public String getTrustStoreType()
    {
        return _trustStoreType;
    }

    /**
     * @param trustStoreType The type of the trust store
     */
    public void setTrustStoreType(String trustStoreType)
    {
        _trustStoreType = trustStoreType;
    }

    /**
     * @return True if SSL needs client authentication.
     * @see SSLEngine#getNeedClientAuth()
     */
    @ManagedAttribute("Whether client authentication is needed")
    public boolean getNeedClientAuth()
    {
        return _needClientAuth;
    }

    /**
     * @param needClientAuth True if SSL needs client authentication.
     * @see SSLEngine#getNeedClientAuth()
     */
    public void setNeedClientAuth(boolean needClientAuth)
    {
        _needClientAuth = needClientAuth;
    }

    /**
     * @return True if SSL wants client authentication.
     * @see SSLEngine#getWantClientAuth()
     */
    @ManagedAttribute("Whether client authentication is wanted")
    public boolean getWantClientAuth()
    {
        return _wantClientAuth;
    }

    /**
     * @param wantClientAuth True if SSL wants client authentication.
     * @see SSLEngine#getWantClientAuth()
     */
    public void setWantClientAuth(boolean wantClientAuth)
    {
        _wantClientAuth = wantClientAuth;
    }

    /**
     * @return true if SSL certificate has to be validated
     */
    @ManagedAttribute("Whether certificates are validated")
    public boolean isValidateCerts()
    {
        return _validateCerts;
    }

    /**
     * @param validateCerts true if SSL certificates have to be validated
     */
    public void setValidateCerts(boolean validateCerts)
    {
        _validateCerts = validateCerts;
    }

    /**
     * @return true if SSL certificates of the peer have to be validated
     */
    @ManagedAttribute("Whether peer certificates are validated")
    public boolean isValidatePeerCerts()
    {
        return _validatePeerCerts;
    }

    /**
     * @param validatePeerCerts true if SSL certificates of the peer have to be validated
     */
    public void setValidatePeerCerts(boolean validatePeerCerts)
    {
        _validatePeerCerts = validatePeerCerts;
    }

    /**
     * @param password The password for the key store.  If null is passed and
     *                 a keystore is set, then
     *                 the {@link #getPassword(String)} is used to
     *                 obtain a password either from the {@value #PASSWORD_PROPERTY}
     *                 system property or by prompting for manual entry.
     */
    public void setKeyStorePassword(String password)
    {
        if (password == null)
        {
            if (_keyStoreResource != null)
                _keyStorePassword = getPassword(PASSWORD_PROPERTY);
            else
                _keyStorePassword = null;
        }
        else
        {
            _keyStorePassword = newPassword(password);
        }
    }

    /**
     * @param password The password (if any) for the specific key within the key store.
     *                 If null is passed and the {@value #KEYPASSWORD_PROPERTY} system property is set,
     *                 then the {@link #getPassword(String)} is used to
     *                 obtain a password from the {@value #KEYPASSWORD_PROPERTY} system property.
     */
    public void setKeyManagerPassword(String password)
    {
        if (password == null)
        {
            if (System.getProperty(KEYPASSWORD_PROPERTY) != null)
                _keyManagerPassword = getPassword(KEYPASSWORD_PROPERTY);
            else
                _keyManagerPassword = null;
        }
        else
        {
            _keyManagerPassword = newPassword(password);
        }
    }

    /**
     * @param password The password for the truststore. If null is passed and a truststore is set
     *                 that is different from the keystore, then
     *                 the {@link #getPassword(String)} is used to
     *                 obtain a password either from the {@value #PASSWORD_PROPERTY}
     *                 system property or by prompting for manual entry.
     */
    public void setTrustStorePassword(String password)
    {
        if (password == null)
        {
            if (_trustStoreResource != null && !_trustStoreResource.equals(_keyStoreResource))
                _trustStorePassword = getPassword(PASSWORD_PROPERTY);
            else
                _trustStorePassword = null;
        }
        else
        {
            _trustStorePassword = newPassword(password);
        }
    }

    /**
     * @return The SSL provider name, which if set is passed to
     * {@link SSLContext#getInstance(String, String)}
     */
    @ManagedAttribute("The provider name")
    public String getProvider()
    {
        return _sslProvider;
    }

    /**
     * @param provider The SSL provider name, which if set is passed to
     *                 {@link SSLContext#getInstance(String, String)}
     */
    public void setProvider(String provider)
    {
        _sslProvider = provider;
    }

    /**
     * @return The SSL protocol (default "TLS") passed to
     * {@link SSLContext#getInstance(String, String)}
     */
    @ManagedAttribute("The TLS protocol")
    public String getProtocol()
    {
        return _sslProtocol;
    }

    /**
     * @param protocol The SSL protocol (default "TLS") passed to
     *                 {@link SSLContext#getInstance(String, String)}
     */
    public void setProtocol(String protocol)
    {
        _sslProtocol = protocol;
    }

    /**
     * @return The algorithm name, which if set is passed to
     * {@link SecureRandom#getInstance(String)} to obtain the {@link SecureRandom} instance passed to
     * {@link SSLContext#init(javax.net.ssl.KeyManager[], javax.net.ssl.TrustManager[], SecureRandom)}
     */
    @ManagedAttribute("The SecureRandom algorithm")
    public String getSecureRandomAlgorithm()
    {
        return _secureRandomAlgorithm;
    }

    /**
     * @param algorithm The algorithm name, which if set is passed to
     *                  {@link SecureRandom#getInstance(String)} to obtain the {@link SecureRandom} instance passed to
     *                  {@link SSLContext#init(javax.net.ssl.KeyManager[], javax.net.ssl.TrustManager[], SecureRandom)}
     */
    public void setSecureRandomAlgorithm(String algorithm)
    {
        _secureRandomAlgorithm = algorithm;
    }

    /**
     * @return The algorithm name (default "SunX509") used by the {@link KeyManagerFactory}
     */
    @ManagedAttribute("The KeyManagerFactory algorithm")
    public String getKeyManagerFactoryAlgorithm()
    {
        return _keyManagerFactoryAlgorithm;
    }

    /**
     * @param algorithm The algorithm name (default "SunX509") used by the {@link KeyManagerFactory}
     */
    public void setKeyManagerFactoryAlgorithm(String algorithm)
    {
        _keyManagerFactoryAlgorithm = algorithm;
    }

    /**
     * @return The algorithm name (default "SunX509") used by the {@link TrustManagerFactory}
     */
    @ManagedAttribute("The TrustManagerFactory algorithm")
    public String getTrustManagerFactoryAlgorithm()
    {
        return _trustManagerFactoryAlgorithm;
    }

    /**
     * @return True if all certificates should be trusted if there is no KeyStore or TrustStore
     */
    @ManagedAttribute("Whether certificates should be trusted even if they are invalid")
    public boolean isTrustAll()
    {
        return _trustAll;
    }

    /**
     * @param trustAll True if all certificates should be trusted if there is no KeyStore or TrustStore
     */
    public void setTrustAll(boolean trustAll)
    {
        _trustAll = trustAll;
        if (trustAll)
            setEndpointIdentificationAlgorithm(null);
    }

    /**
     * @param algorithm The algorithm name (default "SunX509") used by the {@link TrustManagerFactory}
     *                  Use the string "TrustAll" to install a trust manager that trusts all.
     */
    public void setTrustManagerFactoryAlgorithm(String algorithm)
    {
        _trustManagerFactoryAlgorithm = algorithm;
    }

    /**
     * @return whether TLS renegotiation is allowed (true by default)
     */
    @ManagedAttribute("Whether renegotiation is allowed")
    public boolean isRenegotiationAllowed()
    {
        return _renegotiationAllowed;
    }

    /**
     * @param renegotiationAllowed whether TLS renegotiation is allowed
     */
    public void setRenegotiationAllowed(boolean renegotiationAllowed)
    {
        _renegotiationAllowed = renegotiationAllowed;
    }

    /**
     * @return The number of renegotiations allowed for this connection.  When the limit
     * is 0 renegotiation will be denied. If the limit is less than 0 then no limit is applied. 
     */
    @ManagedAttribute("The max number of renegotiations allowed")
    public int getRenegotiationLimit()
    {
        return _renegotiationLimit;
    }

    /**
     * @param renegotiationLimit The number of renegotions allowed for this connection.  
     * When the limit is 0 renegotiation will be denied. If the limit is less than 0 then no limit is applied.
     * Default 5.
     */
    public void setRenegotiationLimit(int renegotiationLimit)
    {
        _renegotiationLimit = renegotiationLimit;
    }
    
    /**
     * @return Path to file that contains Certificate Revocation List
     */
    @ManagedAttribute("The path to the certificate revocation list file")
    public String getCrlPath()
    {
        return _crlPath;
    }

    /**
     * @param crlPath Path to file that contains Certificate Revocation List
     */
    public void setCrlPath(String crlPath)
    {
        _crlPath = crlPath;
    }

    /**
     * @return Maximum number of intermediate certificates in
     * the certification path (-1 for unlimited)
     */
    @ManagedAttribute("The maximum number of intermediate certificates")
    public int getMaxCertPathLength()
    {
        return _maxCertPathLength;
    }

    /**
     * @param maxCertPathLength maximum number of intermediate certificates in
     *                          the certification path (-1 for unlimited)
     */
    public void setMaxCertPathLength(int maxCertPathLength)
    {
        _maxCertPathLength = maxCertPathLength;
    }

    /**
     * @return The SSLContext
     */
    public SSLContext getSslContext()
    {
        if (!isStarted())
            return _setContext;

        synchronized (this)
        {
            return _factory._context;
        }
    }

    /**
     * @param sslContext Set a preconfigured SSLContext
     */
    public void setSslContext(SSLContext sslContext)
    {
        _setContext = sslContext;
    }

    /**
     * @return the endpoint identification algorithm
     */
    @ManagedAttribute("The endpoint identification algorithm")
    public String getEndpointIdentificationAlgorithm()
    {
        return _endpointIdentificationAlgorithm;
    }

    /**
     * When set to "HTTPS" hostname verification will be enabled.
     * Deployments can be vulnerable to a man-in-the-middle attack if a EndpointIndentificationAlgorithm
     * is not set.
     * @param endpointIdentificationAlgorithm Set the endpointIdentificationAlgorithm
     */
    public void setEndpointIdentificationAlgorithm(String endpointIdentificationAlgorithm)
    {
        _endpointIdentificationAlgorithm = endpointIdentificationAlgorithm;
    }

    public PKIXCertPathChecker getPkixCertPathChecker()
    {
        return _pkixCertPathChecker;
    }

    public void setPkixCertPathChecker(PKIXCertPathChecker pkixCertPatchChecker)
    {
        _pkixCertPathChecker = pkixCertPatchChecker;
    }

    /**
     * Override this method to provide alternate way to load a keystore.
     *
     * @param resource the resource to load the keystore from
     * @return the key store instance
     * @throws Exception if the keystore cannot be loaded
     */
    protected KeyStore loadKeyStore(Resource resource) throws Exception
    {
        String storePassword = Objects.toString(_keyStorePassword, null);
        return CertificateUtils.getKeyStore(resource, getKeyStoreType(), getKeyStoreProvider(), storePassword);
    }

    /**
     * Override this method to provide alternate way to load a truststore.
     *
     * @param resource the resource to load the truststore from
     * @return the key store instance
     * @throws Exception if the truststore cannot be loaded
     */
    protected KeyStore loadTrustStore(Resource resource) throws Exception
    {
        String type = Objects.toString(getTrustStoreType(), getKeyStoreType());
        String provider = Objects.toString(getTrustStoreProvider(), getKeyStoreProvider());
        Password passwd = _trustStorePassword;
        if (resource == null || resource.equals(_keyStoreResource))
        {
            resource = _keyStoreResource;
            if (passwd == null)
                passwd = _keyStorePassword;
        }
        return CertificateUtils.getKeyStore(resource, type, provider, Objects.toString(passwd, null));
    }

    /**
     * Loads certificate revocation list (CRL) from a file.
     * <p>
     * Required for integrations to be able to override the mechanism used to
     * load CRL in order to provide their own implementation.
     *
     * @param crlPath path of certificate revocation list file
     * @return Collection of CRL's
     * @throws Exception if the certificate revocation list cannot be loaded
     */
    protected Collection<? extends CRL> loadCRL(String crlPath) throws Exception
    {
        return CertificateUtils.loadCRL(crlPath);
    }

    protected KeyManager[] getKeyManagers(KeyStore keyStore) throws Exception
    {
        KeyManager[] managers = null;

        if (keyStore != null)
        {
            KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(getKeyManagerFactoryAlgorithm());
            keyManagerFactory.init(keyStore, _keyManagerPassword == null ? (_keyStorePassword == null ? null : _keyStorePassword.toString().toCharArray()) : _keyManagerPassword.toString().toCharArray());
            managers = keyManagerFactory.getKeyManagers();

            if (managers != null)
            {
                String alias = getCertAlias();
                if (alias != null)
                {
                    for (int idx = 0; idx < managers.length; idx++)
                    {
                        if (managers[idx] instanceof X509ExtendedKeyManager)
                            managers[idx] = new AliasedX509ExtendedKeyManager((X509ExtendedKeyManager)managers[idx], alias);
                    }
                }

                // Is SNI needed to select a certificate?
                if (!_certWilds.isEmpty() || _certHosts.size()>1 || _certHosts.size()==1 && _aliasX509.size()>1)
                {
                    for (int idx = 0; idx < managers.length; idx++)
                    {
                        if (managers[idx] instanceof X509ExtendedKeyManager)
                            managers[idx] = new SniX509ExtendedKeyManager((X509ExtendedKeyManager)managers[idx]);
                    }
                }
            }
        }

        if (LOG.isDebugEnabled())
            LOG.debug("managers={} for {}", managers, this);

        return managers;
    }

    protected TrustManager[] getTrustManagers(KeyStore trustStore, Collection<? extends CRL> crls) throws Exception
    {
        TrustManager[] managers = null;
        if (trustStore != null)
        {
            // Revocation checking is only supported for PKIX algorithm
            if (isValidatePeerCerts() && "PKIX".equalsIgnoreCase(getTrustManagerFactoryAlgorithm()))
            {
                PKIXBuilderParameters pbParams = newPKIXBuilderParameters(trustStore, crls);

                TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(_trustManagerFactoryAlgorithm);
                trustManagerFactory.init(new CertPathTrustManagerParameters(pbParams));

                managers = trustManagerFactory.getTrustManagers();
            }
            else
            {
                TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(_trustManagerFactoryAlgorithm);
                trustManagerFactory.init(trustStore);

                managers = trustManagerFactory.getTrustManagers();
            }
        }

        return managers;
    }

    protected PKIXBuilderParameters newPKIXBuilderParameters(KeyStore trustStore, Collection<? extends CRL> crls) throws Exception
    {
        PKIXBuilderParameters pbParams = new PKIXBuilderParameters(trustStore, new X509CertSelector());

        // Set maximum certification path length
        pbParams.setMaxPathLength(_maxCertPathLength);

        // Make sure revocation checking is enabled
        pbParams.setRevocationEnabled(true);
        
        if (_pkixCertPathChecker!=null)
        pbParams.addCertPathChecker(_pkixCertPathChecker);

        if (crls != null && !crls.isEmpty())
        {
            pbParams.addCertStore(CertStore.getInstance("Collection", new CollectionCertStoreParameters(crls)));
        }

        if (_enableCRLDP)
        {
            // Enable Certificate Revocation List Distribution Points (CRLDP) support
            System.setProperty("com.sun.security.enableCRLDP", "true");
        }

        if (_enableOCSP)
        {
            // Enable On-Line Certificate Status Protocol (OCSP) support
            Security.setProperty("ocsp.enable", "true");

            if (_ocspResponderURL != null)
            {
                // Override location of OCSP Responder
                Security.setProperty("ocsp.responderURL", _ocspResponderURL);
            }
        }
        
        return pbParams;
    }
    
    /**
     * Select protocols to be used by the connector
     * based on configured inclusion and exclusion lists
     * as well as enabled and supported protocols.
     *
     * @param enabledProtocols   Array of enabled protocols
     * @param supportedProtocols Array of supported protocols
     */
    public void selectProtocols(String[] enabledProtocols, String[] supportedProtocols)
    {
        Set<String> selected_protocols = new LinkedHashSet<>();

        // Set the starting protocols - either from the included or enabled list
        if (!_includeProtocols.isEmpty())
        {
            // Use only the supported included protocols
            for (String protocol : _includeProtocols)
            {
                if (Arrays.asList(supportedProtocols).contains(protocol))
                    selected_protocols.add(protocol);
                else
                    LOG.info("Protocol {} not supported in {}", protocol, Arrays.asList(supportedProtocols));
            }
        }
        else
            selected_protocols.addAll(Arrays.asList(enabledProtocols));

        // Remove any excluded protocols
        selected_protocols.removeAll(_excludeProtocols);

        if (selected_protocols.isEmpty())
            LOG.warn("No selected protocols from {}", Arrays.asList(supportedProtocols));

        _selectedProtocols = selected_protocols.toArray(new String[0]);
    }

    /**
     * Select cipher suites to be used by the connector
     * based on configured inclusion and exclusion lists
     * as well as enabled and supported cipher suite lists.
     *
     * @param enabledCipherSuites   Array of enabled cipher suites
     * @param supportedCipherSuites Array of supported cipher suites
     */
    protected void selectCipherSuites(String[] enabledCipherSuites, String[] supportedCipherSuites)
    {
        List<String> selected_ciphers = new ArrayList<>();

        // Set the starting ciphers - either from the included or enabled list
        if (_includeCipherSuites.isEmpty())
            selected_ciphers.addAll(Arrays.asList(enabledCipherSuites));
        else
            processIncludeCipherSuites(supportedCipherSuites, selected_ciphers);

        removeExcludedCipherSuites(selected_ciphers);

        if (selected_ciphers.isEmpty())
            LOG.warn("No supported ciphers from {}", Arrays.asList(supportedCipherSuites));

        Comparator<String> comparator = getCipherComparator();
        if (comparator != null)
        {
            if (LOG.isDebugEnabled())
                LOG.debug("Sorting selected ciphers with {}", comparator);
            selected_ciphers.sort(comparator);
        }

        _selectedCipherSuites = selected_ciphers.toArray(new String[0]);
    }

    protected void processIncludeCipherSuites(String[] supportedCipherSuites, List<String> selected_ciphers)
    {
        for (String cipherSuite : _includeCipherSuites)
        {
            Pattern p = Pattern.compile(cipherSuite);
            boolean added = false;
            for (String supportedCipherSuite : supportedCipherSuites)
            {
                Matcher m = p.matcher(supportedCipherSuite);
                if (m.matches())
                {
                    added = true;
                    selected_ciphers.add(supportedCipherSuite);
                }

            }
            if (!added)
                LOG.info("No Cipher matching '{}' is supported", cipherSuite);
        }
    }

    protected void removeExcludedCipherSuites(List<String> selected_ciphers)
    {
        for (String excludeCipherSuite : _excludeCipherSuites)
        {
            Pattern excludeCipherPattern = Pattern.compile(excludeCipherSuite);
            for (Iterator<String> i = selected_ciphers.iterator(); i.hasNext(); )
            {
                String selectedCipherSuite = i.next();
                Matcher m = excludeCipherPattern.matcher(selectedCipherSuite);
                if (m.matches())
                    i.remove();
            }
        }
    }

    /**
     * Check if the lifecycle has been started and throw runtime exception
     */
    private void checkIsStarted()
    {
        if (!isStarted())
            throw new IllegalStateException("!STARTED: " + this);
    }

    /**
     * @return true if CRL Distribution Points support is enabled
     */
    @ManagedAttribute("Whether certificate revocation list distribution points is enabled")
    public boolean isEnableCRLDP()
    {
        return _enableCRLDP;
    }

    /**
     * Enables CRL Distribution Points Support
     *
     * @param enableCRLDP true - turn on, false - turns off
     */
    public void setEnableCRLDP(boolean enableCRLDP)
    {
        _enableCRLDP = enableCRLDP;
    }

    /**
     * @return true if On-Line Certificate Status Protocol support is enabled
     */
    @ManagedAttribute("Whether online certificate status protocol support is enabled")
    public boolean isEnableOCSP()
    {
        return _enableOCSP;
    }

    /**
     * Enables On-Line Certificate Status Protocol support
     *
     * @param enableOCSP true - turn on, false - turn off
     */
    public void setEnableOCSP(boolean enableOCSP)
    {
        _enableOCSP = enableOCSP;
    }

    /**
     * @return Location of the OCSP Responder
     */
    @ManagedAttribute("The online certificate status protocol URL")
    public String getOcspResponderURL()
    {
        return _ocspResponderURL;
    }

    /**
     * Set the location of the OCSP Responder.
     *
     * @param ocspResponderURL location of the OCSP Responder
     */
    public void setOcspResponderURL(String ocspResponderURL)
    {
        _ocspResponderURL = ocspResponderURL;
    }

    /**
     * Set the key store.
     *
     * @param keyStore the key store to set
     */
    public void setKeyStore(KeyStore keyStore)
    {
        _setKeyStore = keyStore;
    }

    public KeyStore getKeyStore()
    {
        if (!isStarted())
            return _setKeyStore;

        synchronized (this)
        {
            return _factory._keyStore;
        }
    }

    /**
     * Set the trust store.
     *
     * @param trustStore the trust store to set
     */
    public void setTrustStore(KeyStore trustStore)
    {
        _setTrustStore = trustStore;
    }

    public KeyStore getTrustStore()
    {
        if (!isStarted())
            return _setTrustStore;

        synchronized (this)
        {
            return _factory._trustStore;
        }
    }

    /**
     * Set the key store resource.
     *
     * @param resource the key store resource to set
     */
    public void setKeyStoreResource(Resource resource)
    {
        _keyStoreResource = resource;
    }

    public Resource getKeyStoreResource()
    {
        return _keyStoreResource;
    }

    /**
     * Set the trust store resource.
     *
     * @param resource the trust store resource to set
     */
    public void setTrustStoreResource(Resource resource)
    {
        _trustStoreResource = resource;
    }

    public Resource getTrustStoreResource()
    {
        return _trustStoreResource;
    }

    /**
     * @return true if SSL Session caching is enabled
     */
    @ManagedAttribute("Whether TLS session caching is enabled")
    public boolean isSessionCachingEnabled()
    {
        return _sessionCachingEnabled;
    }

    /**
     * Set the flag to enable SSL Session caching.
     * If set to true, then the {@link SSLContext#createSSLEngine(String, int)} method is
     * used to pass host and port information as a hint for session reuse.  Note that
     * this is only a hint and session may not be reused. Moreover, the hint is typically
     * only used on client side implementations and setting this to false does not
     * stop a server from accepting an offered session ID to reuse.
     *
     * @param enableSessionCaching the value of the flag
     */
    public void setSessionCachingEnabled(boolean enableSessionCaching)
    {
        _sessionCachingEnabled = enableSessionCaching;
    }

    /**
     * Get SSL session cache size.
     * Passed directly to {@link SSLSessionContext#setSessionCacheSize(int)}
     *
     * @return SSL session cache size
     */
    @ManagedAttribute("The maximum TLS session cache size")
    public int getSslSessionCacheSize()
    {
        return _sslSessionCacheSize;
    }

    /**
     * Set SSL session cache size.
     * <p>Set the max cache size to be set on {@link SSLSessionContext#setSessionCacheSize(int)}
     * when this factory is started.</p>
     *
     * @param sslSessionCacheSize SSL session cache size to set. A value  of -1 (default) uses
     *                            the JVM default, 0 means unlimited and positive number is a max size.
     */
    public void setSslSessionCacheSize(int sslSessionCacheSize)
    {
        _sslSessionCacheSize = sslSessionCacheSize;
    }

    /**
     * Get SSL session timeout.
     *
     * @return SSL session timeout
     */
    @ManagedAttribute("The TLS session cache timeout, in seconds")
    public int getSslSessionTimeout()
    {
        return _sslSessionTimeout;
    }

    /**
     * Set SSL session timeout.
     * <p>Set the timeout in seconds to be set on {@link SSLSessionContext#setSessionTimeout(int)}
     * when this factory is started.</p>
     *
     * @param sslSessionTimeout SSL session timeout to set in seconds. A value of -1 (default) uses
     *                          the JVM default, 0 means unlimited and positive number is a timeout in seconds.
     */
    public void setSslSessionTimeout(int sslSessionTimeout)
    {
        _sslSessionTimeout = sslSessionTimeout;
    }

    /**
     * @return the HostnameVerifier used by a client to verify host names in the server certificate
     */
    public HostnameVerifier getHostnameVerifier()
    {
        return _hostnameVerifier;
    }

    /**
     * <p>Sets a {@code HostnameVerifier} used by a client to verify host names in the server certificate.</p>
     * <p>The {@code HostnameVerifier} works in conjunction with {@link #setEndpointIdentificationAlgorithm(String)}.</p>
     * <p>When {@code endpointIdentificationAlgorithm=="HTTPS"} (the default) the JDK TLS implementation
     * checks that the host name indication set by the client matches the host names in the server certificate.
     * If this check passes successfully, the {@code HostnameVerifier} is invoked and the application
     * can perform additional checks and allow/deny the connection to the server.</p>
     * <p>When {@code endpointIdentificationAlgorithm==null} the JDK TLS implementation will not check
     * the host names, and any check is therefore performed only by the {@code HostnameVerifier.}</p>
     *
     * @param hostnameVerifier the HostnameVerifier used by a client to verify host names in the server certificate
     */
    public void setHostnameVerifier(HostnameVerifier hostnameVerifier)
    {
        _hostnameVerifier = hostnameVerifier;
    }

    /**
     * Returns the password object for the given realm.
     *
     * @param realm the realm
     * @return the Password object
     */
    protected Password getPassword(String realm)
    {
        return Password.getPassword(realm, null, null);
    }

    /**
     * Creates a new Password object.
     *
     * @param password the password string
     * @return the new Password object
     */
    public Password newPassword(String password)
    {
        return new Password(password);
    }

    public SSLServerSocket newSslServerSocket(String host, int port, int backlog) throws IOException
    {
        checkIsStarted();

        SSLContext context = getSslContext();
        SSLServerSocketFactory factory = context.getServerSocketFactory();
        SSLServerSocket socket =
                (SSLServerSocket)(host == null ?
                        factory.createServerSocket(port, backlog) :
                        factory.createServerSocket(port, backlog, InetAddress.getByName(host)));
        socket.setSSLParameters(customize(socket.getSSLParameters()));

        return socket;
    }

    public SSLSocket newSslSocket() throws IOException
    {
        checkIsStarted();

        SSLContext context = getSslContext();
        SSLSocketFactory factory = context.getSocketFactory();
        SSLSocket socket = (SSLSocket)factory.createSocket();
        socket.setSSLParameters(customize(socket.getSSLParameters()));

        return socket;
    }

    /**
     * Factory method for "scratch" {@link SSLEngine}s, usually only used for retrieving configuration
     * information such as the application buffer size or the list of protocols/ciphers.
     * <p>
     * This method should not be used for creating {@link SSLEngine}s that are used in actual socket
     * communication.
     *
     * @return a new, "scratch" {@link SSLEngine}
     */
    public SSLEngine newSSLEngine()
    {
        checkIsStarted();

        SSLContext context = getSslContext();
        SSLEngine sslEngine = context.createSSLEngine();
        customize(sslEngine);

        return sslEngine;
    }

    /**
     * General purpose factory method for creating {@link SSLEngine}s, although creation of
     * {@link SSLEngine}s on the server-side should prefer {@link #newSSLEngine(InetSocketAddress)}.
     *
     * @param host the remote host
     * @param port the remote port
     * @return a new {@link SSLEngine}
     */
    public SSLEngine newSSLEngine(String host, int port)
    {
        checkIsStarted();

        SSLContext context = getSslContext();
        SSLEngine sslEngine = isSessionCachingEnabled() ?
                context.createSSLEngine(host, port) :
                context.createSSLEngine();
        customize(sslEngine);

        return sslEngine;
    }

    /**
     * Server-side only factory method for creating {@link SSLEngine}s.
     * <p>
     * If the given {@code address} is null, it is equivalent to {@link #newSSLEngine()}, otherwise
     * {@link #newSSLEngine(String, int)} is called.
     * <p>
     * Clients that wish to create {@link SSLEngine} instances must use {@link #newSSLEngine(String, int)}.
     *
     * @param address the remote peer address
     * @return a new {@link SSLEngine}
     */
    public SSLEngine newSSLEngine(InetSocketAddress address)
    {
        if (address == null)
            return newSSLEngine();
        return newSSLEngine(address.getHostString(), address.getPort());
    }

    /**
     * Customize an SslEngine instance with the configuration of this factory,
     * by calling {@link #customize(SSLParameters)}
     *
     * @param sslEngine the SSLEngine to customize
     */
    public void customize(SSLEngine sslEngine)
    {
        if (LOG.isDebugEnabled())
            LOG.debug("Customize {}", sslEngine);

        sslEngine.setSSLParameters(customize(sslEngine.getSSLParameters()));
    }

    /**
     * Customize an SslParameters instance with the configuration of this factory.
     *
     * @param sslParams The parameters to customize
     * @return The passed instance of sslParams (returned as a convenience)
     */
    public SSLParameters customize(SSLParameters sslParams)
    {
        sslParams.setEndpointIdentificationAlgorithm(getEndpointIdentificationAlgorithm());
        sslParams.setUseCipherSuitesOrder(isUseCipherSuitesOrder());
        if (!_certHosts.isEmpty() || !_certWilds.isEmpty())
            sslParams.setSNIMatchers(Collections.singletonList(new AliasSNIMatcher()));
        if (_selectedCipherSuites != null)
            sslParams.setCipherSuites(_selectedCipherSuites);
        if (_selectedProtocols != null)
            sslParams.setProtocols(_selectedProtocols);
        if (getWantClientAuth())
            sslParams.setWantClientAuth(true);
        if (getNeedClientAuth())
            sslParams.setNeedClientAuth(true);
        return sslParams;
    }

    public void reload(Consumer<SslContextFactory> consumer) throws Exception
    {
        synchronized (this)
        {
            consumer.accept(this);
            unload();
            load();
        }
    }

    public static X509Certificate[] getCertChain(SSLSession sslSession)
    {
        try
        {
            Certificate[] javaxCerts = sslSession.getPeerCertificates();
            if (javaxCerts == null || javaxCerts.length == 0)
                return null;

            int length = javaxCerts.length;
            X509Certificate[] javaCerts = new X509Certificate[length];

            java.security.cert.CertificateFactory cf = java.security.cert.CertificateFactory.getInstance("X.509");
            for (int i = 0; i < length; i++)
            {
                byte bytes[] = javaxCerts[i].getEncoded();
                ByteArrayInputStream stream = new ByteArrayInputStream(bytes);
                javaCerts[i] = (X509Certificate)cf.generateCertificate(stream);
            }

            return javaCerts;
        }
        catch (SSLPeerUnverifiedException pue)
        {
            return null;
        }
        catch (Exception e)
        {
            LOG.warn(Log.EXCEPTION, e);
            return null;
        }
    }

    /**
     * Given the name of a TLS/SSL cipher suite, return an int representing it effective stream
     * cipher key strength. i.e. How much entropy material is in the key material being fed into the
     * encryption routines.
     * <p>
     * This is based on the information on effective key lengths in RFC 2246 - The TLS Protocol
     * Version 1.0, Appendix C. CipherSuite definitions:
     * <pre>
     *                         Effective
     *     Cipher       Type    Key Bits
     *
     *     NULL       * Stream     0
     *     IDEA_CBC     Block    128
     *     RC2_CBC_40 * Block     40
     *     RC4_40     * Stream    40
     *     RC4_128      Stream   128
     *     DES40_CBC  * Block     40
     *     DES_CBC      Block     56
     *     3DES_EDE_CBC Block    168
     * </pre>
     *
     * @param cipherSuite String name of the TLS cipher suite.
     * @return int indicating the effective key entropy bit-length.
     */
    public static int deduceKeyLength(String cipherSuite)
    {
        // Roughly ordered from most common to least common.
        if (cipherSuite == null)
            return 0;
        else if (cipherSuite.contains("WITH_AES_256_"))
            return 256;
        else if (cipherSuite.contains("WITH_RC4_128_"))
            return 128;
        else if (cipherSuite.contains("WITH_AES_128_"))
            return 128;
        else if (cipherSuite.contains("WITH_RC4_40_"))
            return 40;
        else if (cipherSuite.contains("WITH_3DES_EDE_CBC_"))
            return 168;
        else if (cipherSuite.contains("WITH_IDEA_CBC_"))
            return 128;
        else if (cipherSuite.contains("WITH_RC2_CBC_40_"))
            return 40;
        else if (cipherSuite.contains("WITH_DES40_CBC_"))
            return 40;
        else if (cipherSuite.contains("WITH_DES_CBC_"))
            return 56;
        else
            return 0;
    }

    @Override
    public String toString()
    {
        return String.format("%s@%x[provider=%s,keyStore=%s,trustStore=%s]",
                getClass().getSimpleName(),
                hashCode(),
                _sslProvider,
                _keyStoreResource,
                _trustStoreResource);
    }

    class Factory
    {
        private final KeyStore _keyStore;
        private final KeyStore _trustStore;
        private final SSLContext _context;

        Factory(KeyStore keyStore, KeyStore trustStore, SSLContext context)
        {
            super();
            _keyStore = keyStore;
            _trustStore = trustStore;
            _context = context;
        }
    }

    class AliasSNIMatcher extends SNIMatcher
    {
        private String _host;
        private X509 _x509;

        AliasSNIMatcher()
        {
            super(StandardConstants.SNI_HOST_NAME);
        }

        @Override
        public boolean matches(SNIServerName serverName)
        {
            if (LOG.isDebugEnabled())
                LOG.debug("SNI matching for {}", serverName);

            if (serverName instanceof SNIHostName)
            {
                String host = _host = ((SNIHostName)serverName).getAsciiName();
                host = StringUtil.asciiToLowerCase(host);

                // Try an exact match
                _x509 = _certHosts.get(host);

                // Else try an exact wild match
                if (_x509 == null)
                {
                    _x509 = _certWilds.get(host);

                    // Else try an 1 deep wild match
                    if (_x509 == null)
                    {
                        int dot = host.indexOf('.');
                        if (dot >= 0)
                        {
                            String domain = host.substring(dot + 1);
                            _x509 = _certWilds.get(domain);
                        }
                    }
                }

                if (LOG.isDebugEnabled())
                    LOG.debug("SNI matched {}->{}", host, _x509);
            }
            else
            {
                if (LOG.isDebugEnabled())
                    LOG.debug("SNI no match for {}", serverName);
            }

            // Return true and allow the KeyManager to accept or reject when choosing a certificate.
            // If we don't have a SNI host, or didn't see any certificate aliases,
            // just say true as it will either somehow work or fail elsewhere.
            return true;
        }

        public String getHost()
        {
            return _host;
        }

        public X509 getX509()
        {
            return _x509;
        }
    }
}
