com.eucalyptus.crypto.util.SslSetup.java Source code

Java tutorial

Introduction

Here is the source code for com.eucalyptus.crypto.util.SslSetup.java

Source

/*************************************************************************
 * Copyright 2009-2014 Eucalyptus Systems, Inc.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; version 3 of the License.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see http://www.gnu.org/licenses/.
 *
 * Please contact Eucalyptus Systems, Inc., 6755 Hollister Ave., Goleta
 * CA 93117, USA or visit http://www.eucalyptus.com/licenses/ if you need
 * additional information or have any questions.
 *
 * This file may incorporate work covered under the following copyright
 * and permission notice:
 *
 *   Software License Agreement (BSD License)
 *
 *   Copyright (c) 2008, Regents of the University of California
 *   All rights reserved.
 *
 *   Redistribution and use of this software in source and binary forms,
 *   with or without modification, are permitted provided that the
 *   following conditions are met:
 *
 *     Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *
 *     Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer
 *     in the documentation and/or other materials provided with the
 *     distribution.
 *
 *   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 *   "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 *   LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 *   FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 *   COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 *   INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 *   BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 *   LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 *   CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 *   LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
 *   ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 *   POSSIBILITY OF SUCH DAMAGE. USERS OF THIS SOFTWARE ACKNOWLEDGE
 *   THE POSSIBLE PRESENCE OF OTHER OPEN SOURCE LICENSED MATERIAL,
 *   COPYRIGHTED MATERIAL OR PATENTED MATERIAL IN THIS SOFTWARE,
 *   AND IF ANY SUCH MATERIAL IS DISCOVERED THE PARTY DISCOVERING
 *   IT MAY INFORM DR. RICH WOLSKI AT THE UNIVERSITY OF CALIFORNIA,
 *   SANTA BARBARA WHO WILL THEN ASCERTAIN THE MOST APPROPRIATE REMEDY,
 *   WHICH IN THE REGENTS' DISCRETION MAY INCLUDE, WITHOUT LIMITATION,
 *   REPLACEMENT OF THE CODE SO IDENTIFIED, LICENSING OF THE CODE SO
 *   IDENTIFIED, OR WITHDRAWAL OF THE CODE CAPABILITY TO THE EXTENT
 *   NEEDED TO COMPLY WITH ANY SUCH LICENSES OR RIGHTS.
 ************************************************************************/

package com.eucalyptus.crypto.util;

import static java.lang.System.getProperty;
import static com.eucalyptus.crypto.util.SslUtils.getEnabledCipherSuites;
import static com.google.common.base.Throwables.propagate;
import static com.google.common.collect.Iterables.toArray;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.Socket;
import java.net.URLConnection;
import java.security.GeneralSecurityException;
import java.security.InvalidAlgorithmParameterException;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.Principal;
import java.security.PrivateKey;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactorySpi;
import javax.net.ssl.ManagerFactoryParameters;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.TrustManagerFactorySpi;
import javax.net.ssl.X509ExtendedKeyManager;
import org.apache.http.conn.ssl.AllowAllHostnameVerifier;
import org.apache.http.conn.ssl.BrowserCompatHostnameVerifier;
import org.apache.log4j.Logger;
import com.eucalyptus.component.ComponentIds;
import com.eucalyptus.component.auth.SystemCredentials;
import com.eucalyptus.component.id.Eucalyptus;
import com.eucalyptus.configurable.ConfigurableClass;
import com.eucalyptus.configurable.ConfigurableField;
import com.eucalyptus.configurable.ConfigurableFieldType;
import com.eucalyptus.configurable.ConfigurableProperty;
import com.eucalyptus.configurable.ConfigurablePropertyException;
import com.eucalyptus.configurable.PropertyChangeListener;
import com.eucalyptus.configurable.PropertyChangeListeners;
import com.eucalyptus.crypto.Crypto;
import com.eucalyptus.system.SubDirectory;
import com.eucalyptus.util.CollectionUtils;
import com.eucalyptus.util.IO;
import com.eucalyptus.util.Pair;
import com.google.common.base.Objects;
import com.google.common.base.Predicates;
import com.google.common.base.Supplier;
import com.google.common.base.Suppliers;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.ObjectArrays;
import com.sun.net.ssl.internal.ssl.X509ExtendedTrustManager;

@ConfigurableClass(root = "bootstrap.webservices.ssl", description = "Parameters controlling the SSL configuration for the web services endpoint.")
public class SslSetup {
    private static final Logger LOG = Logger.getLogger(SslSetup.class);
    private static final String PROTOCOL = "TLS";
    private static SSLContext SERVER_CONTEXT = null;
    private static String[] SERVER_SUPPORTED_CIPHERS = null;
    private static SSLContext CLIENT_CONTEXT = null;
    @ConfigurableField(description = "Alias of the certificate entry in euca.p12 to use for SSL for webservices.", changeListener = SslCertChangeListener.class)
    public static String SERVER_ALIAS = ComponentIds.lookup(Eucalyptus.class).name();
    @ConfigurableField(description = "Password of the private key corresponding to the specified certificate for SSL for webservices.", type = ConfigurableFieldType.KEYVALUEHIDDEN, changeListener = SslPasswordChangeListener.class)
    public static String SERVER_PASSWORD = ComponentIds.lookup(Eucalyptus.class).name();
    @ConfigurableField(description = "SSL ciphers for webservices.")
    public static String SERVER_SSL_CIPHERS = "RSA:DSS:ECDSA:+3DES:TLS_EMPTY_RENEGOTIATION_INFO_SCSV:!NULL:!EXPORT:!EXPORT1024:!MD5:!DES:!RC4";
    @ConfigurableField(description = "SSL protocols for webservices.")
    public static String SERVER_SSL_PROTOCOLS = "SSLv2Hello,TLSv1,TLSv1.1,TLSv1.2";
    @ConfigurableField(description = "Use default CAs with SSL for external use.", changeListener = PropertyChangeListeners.IsBoolean.class)
    public static Boolean USER_SSL_DEFAULT_CAS = true;
    @ConfigurableField(description = "SSL ciphers for external use.")
    public static String USER_SSL_CIPHERS = "RSA:DSS:ECDSA:+3DES:TLS_EMPTY_RENEGOTIATION_INFO_SCSV:!NULL:!EXPORT:!EXPORT1024:!MD5:!DES:!RC4";
    @ConfigurableField(description = "SSL protocols for external use.")
    public static String USER_SSL_PROTOCOLS = "SSLv2Hello,TLSv1,TLSv1.1,TLSv1.2";
    @ConfigurableField(description = "SSL hostname validation for external use.", changeListener = PropertyChangeListeners.IsBoolean.class)
    public static Boolean USER_SSL_ENABLE_HOSTNAME_VERIFICATION = true;

    public static class SslCertChangeListener implements PropertyChangeListener<String> {

        @Override
        public void fireChange(ConfigurableProperty t, String newValue) throws ConfigurablePropertyException {
            if (SERVER_ALIAS != null && !SERVER_ALIAS.equals(newValue)) {
                try {
                    String oldValue = SERVER_ALIAS;
                    SSLContext newContext = createServerContext();
                    SERVER_ALIAS = newValue;
                    SERVER_CONTEXT = newContext;
                    SERVER_SUPPORTED_CIPHERS = SERVER_CONTEXT.createSSLEngine().getSupportedCipherSuites();
                } catch (Exception ex) {
                    throw new ConfigurablePropertyException(ex);
                }
            }
        }

    }

    public static class SslPasswordChangeListener implements PropertyChangeListener<String> {

        @Override
        public void fireChange(ConfigurableProperty t, String newValue) throws ConfigurablePropertyException {
            if (SERVER_PASSWORD != null && !SERVER_PASSWORD.equals(newValue)) {
                try {
                    String oldValue = SERVER_PASSWORD;
                    SSLContext newContext = createServerContext();
                    SERVER_PASSWORD = newValue;
                    SERVER_CONTEXT = newContext;
                    SERVER_SUPPORTED_CIPHERS = SERVER_CONTEXT.createSSLEngine().getSupportedCipherSuites();
                } catch (Exception ex) {
                    throw new ConfigurablePropertyException(ex);
                }
            }
        }

    }

    static {
        BCSslSetup.initBouncyCastleDHParams();
        SSLContext serverContext;
        SSLContext clientContext;
        System.setProperty("javax.net.ssl.trustStore", SubDirectory.KEYS.toString() + File.separator + "euca.p12");
        System.setProperty("javax.net.ssl.keyStore", SubDirectory.KEYS.toString() + File.separator + "euca.p12");
        System.setProperty("javax.net.ssl.trustStoreType", "PKCS12");
        System.setProperty("javax.net.ssl.keyStoreType", "PKCS12");
        System.setProperty("javax.net.ssl.trustStorePassword", ComponentIds.lookup(Eucalyptus.class).name());
        System.setProperty("javax.net.ssl.keyStorePassword", ComponentIds.lookup(Eucalyptus.class).name());
        //    System.setProperty( "javax.net.debug", "ssl" );//set this to "ssl" for debugging.
        try {
            serverContext = createServerContext();
        } catch (Exception e) {
            LOG.debug(e, e);
            throw new Error("Failed to initialize the server-side SSLContext", e);
        }

        try {
            clientContext = SSLContext.getInstance(PROTOCOL);
            clientContext.init(SslSetup.ClientKeyManager.getKeyManagers(),
                    SslSetup.ClientTrustManager.getTrustManagers(), null);
        } catch (Exception e) {
            LOG.debug(e, e);
            throw new Error("Failed to initialize the client-side SSLContext", e);
        }

        SERVER_CONTEXT = serverContext;
        SERVER_SUPPORTED_CIPHERS = SERVER_CONTEXT.createSSLEngine().getSupportedCipherSuites();
        CLIENT_CONTEXT = clientContext;
    }

    private static SSLContext createServerContext() throws NoSuchAlgorithmException, KeyManagementException {
        SSLContext serverContext;
        serverContext = SSLContext.getInstance(PROTOCOL);
        serverContext.init(SslSetup.ServerKeyManager.getKeyManagers(),
                SslSetup.ServerTrustManager.getTrustManagers(), null);
        return serverContext;
    }

    public static SSLContext getServerContext() {
        return SERVER_CONTEXT;
    }

    public static SSLEngine getServerEngine() {//TODO:GRZE: @Configurability
        final SSLEngine engine = SERVER_CONTEXT.createSSLEngine();
        engine.setUseClientMode(false);
        engine.setWantClientAuth(false);
        engine.setNeedClientAuth(false);
        engine.setEnabledProtocols(
                SslUtils.getEnabledProtocols(SERVER_SSL_PROTOCOLS, engine.getSupportedProtocols()));
        engine.setEnabledCipherSuites(
                SslUtils.getEnabledCipherSuites(SERVER_SSL_CIPHERS, SERVER_SUPPORTED_CIPHERS));
        return engine;
    }

    /**
     * Configure an unconnected HttpsURLConnection for trust.
     */
    public static URLConnection configureHttpsUrlConnection(final URLConnection urlConnection) {
        if (urlConnection instanceof HttpsURLConnection) {
            final HttpsURLConnection httpsURLConnection = (HttpsURLConnection) urlConnection;
            httpsURLConnection
                    .setHostnameVerifier(Objects.firstNonNull(USER_SSL_ENABLE_HOSTNAME_VERIFICATION, Boolean.TRUE)
                            ? new BrowserCompatHostnameVerifier()
                            : new AllowAllHostnameVerifier());
            httpsURLConnection.setSSLSocketFactory(UserSSLSocketFactory.get());
        }
        return urlConnection;
    }

    public static SSLContext getClientContext() {
        return CLIENT_CONTEXT;
    }

    static class ClientKeyManager extends KeyManagerFactorySpi {
        private static KeyManager singleton = new ClientPKCS12KeyManager();

        public static KeyManager[] getKeyManagers() {
            return new KeyManager[] { singleton };
        }

        @Override
        protected KeyManager[] engineGetKeyManagers() {
            return new KeyManager[] { singleton };
        }

        @Override
        protected void engineInit(ManagerFactoryParameters spec) throws InvalidAlgorithmParameterException {
        }

        @Override
        protected void engineInit(KeyStore ks, char[] password)
                throws KeyStoreException, NoSuchAlgorithmException, UnrecoverableKeyException {
        }

        static class ClientPKCS12KeyManager extends X509ExtendedKeyManager {

            @Override
            public String chooseClientAlias(String[] arg0, Principal[] arg1, Socket arg2) {
                return ComponentIds.lookup(Eucalyptus.class).name();
            }

            @Override
            public String chooseServerAlias(String arg0, Principal[] arg1, Socket arg2) {
                return ComponentIds.lookup(Eucalyptus.class).name();
            }

            @Override
            public X509Certificate[] getCertificateChain(String arg0) {
                if (ComponentIds.lookup(Eucalyptus.class).name().equals(arg0)) {
                    return trustedCerts;
                } else {
                    return null;
                }
            }

            @Override
            public String[] getClientAliases(String arg0, Principal[] arg1) {
                return new String[] { ComponentIds.lookup(Eucalyptus.class).name() };
            }

            @Override
            public PrivateKey getPrivateKey(String arg0) {
                if (ComponentIds.lookup(Eucalyptus.class).name().equals(arg0)) {
                    return trustedKey;
                } else {
                    return null;
                }
            }

            @Override
            public String[] getServerAliases(String arg0, Principal[] arg1) {
                return new String[] { ComponentIds.lookup(Eucalyptus.class).name() };
            }

            @Override
            public String chooseEngineClientAlias(String[] keyType, Principal[] issuers, SSLEngine engine) {
                return ComponentIds.lookup(Eucalyptus.class).name();
            }

            @Override
            public String chooseEngineServerAlias(String keyType, Principal[] issuers, SSLEngine engine) {
                return ComponentIds.lookup(Eucalyptus.class).name();
            }

        }

    }

    static class ServerKeyManager extends KeyManagerFactorySpi {
        private static KeyManager singleton = new ServerPKCS12KeyManager();

        public static KeyManager[] getKeyManagers() {
            return new KeyManager[] { singleton };
        }

        @Override
        protected KeyManager[] engineGetKeyManagers() {
            return new KeyManager[] { singleton };
        }

        @Override
        protected void engineInit(ManagerFactoryParameters spec) throws InvalidAlgorithmParameterException {
        }

        @Override
        protected void engineInit(KeyStore ks, char[] password)
                throws KeyStoreException, NoSuchAlgorithmException, UnrecoverableKeyException {
        }

        static class ServerPKCS12KeyManager extends X509ExtendedKeyManager {

            @Override
            public String chooseClientAlias(String[] arg0, Principal[] arg1, Socket arg2) {
                return SslSetup.SERVER_ALIAS;
            }

            @Override
            public String chooseServerAlias(String arg0, Principal[] arg1, Socket arg2) {
                return SslSetup.SERVER_ALIAS;
            }

            @Override
            public X509Certificate[] getCertificateChain(String arg0) {
                if (SslSetup.SERVER_ALIAS.equals(arg0)) {
                    return memoizedServerCertSupplier.get();
                } else {
                    return null;
                }
            }

            @Override
            public String[] getClientAliases(String arg0, Principal[] arg1) {
                return new String[] { SslSetup.SERVER_ALIAS };
            }

            @Override
            public PrivateKey getPrivateKey(String arg0) {
                if (SslSetup.SERVER_ALIAS.equals(arg0)) {
                    return memoizedPrivateKeySupplier.get();
                } else {
                    return null;
                }
            }

            @Override
            public String[] getServerAliases(String arg0, Principal[] arg1) {
                return new String[] { SslSetup.SERVER_ALIAS };
            }

            @Override
            public String chooseEngineClientAlias(String[] keyType, Principal[] issuers, SSLEngine engine) {
                return SslSetup.SERVER_ALIAS;
            }

            @Override
            public String chooseEngineServerAlias(String keyType, Principal[] issuers, SSLEngine engine) {
                return SslSetup.SERVER_ALIAS;
            }

        }

    }

    private static final Supplier<PrivateKey> serverPrivateKeySupplier = new Supplier<PrivateKey>() {

        @Override
        public PrivateKey get() {
            try {
                return SystemCredentials.getKeyStore().getKeyPair(SslSetup.SERVER_ALIAS, SslSetup.SERVER_ALIAS)
                        .getPrivate();
            } catch (GeneralSecurityException ex) {
                LOG.error(ex, ex);
                return null;
            }
        }
    };
    private static final Supplier<X509Certificate[]> serverCertSupplier = new Supplier<X509Certificate[]>() {

        @Override
        public X509Certificate[] get() {
            X509Certificate[] certs = ObjectArrays.newArray(X509Certificate.class, 1);
            try {
                certs[0] = SystemCredentials.getKeyStore().getCertificate(SslSetup.SERVER_ALIAS);
                return certs;
            } catch (GeneralSecurityException ex) {
                LOG.error(ex, ex);
                return certs;
            }
        }
    };
    private static Supplier<PrivateKey> memoizedPrivateKeySupplier = Suppliers
            .memoizeWithExpiration(serverPrivateKeySupplier, 15l, TimeUnit.SECONDS);
    private static Supplier<X509Certificate[]> memoizedServerCertSupplier = Suppliers
            .memoizeWithExpiration(serverCertSupplier, 15l, TimeUnit.SECONDS);
    private static PrivateKey trustedKey = getTrustedKey();
    private static X509Certificate[] trustedCerts = getTrustedCertificates();

    private static X509Certificate[] getTrustedCertificates() {
        try {
            synchronized (SslSetup.class) {
                if (trustedCerts == null) {
                    trustedCerts = SystemCredentials.getKeyStore().getAllCertificates()
                            .toArray(new X509Certificate[0]);
                }
                return trustedCerts;
            }
        } catch (Exception e) {
            LOG.error(e, e);
            throw new RuntimeException(e);
        }
    }

    private static PrivateKey getTrustedKey() {
        try {
            synchronized (SslSetup.class) {
                if (trustedKey == null) {
                    trustedKey = SystemCredentials.getKeyStore()
                            .getKeyPair(ComponentIds.lookup(Eucalyptus.class).name(),
                                    ComponentIds.lookup(Eucalyptus.class).name())
                            .getPrivate();
                }
                return trustedKey;
            }
        } catch (Exception e) {
            LOG.error(e, e);
            throw new RuntimeException(e);
        }
    }

    public static class ClientTrustManager extends TrustManagerFactorySpi {

        private static final TrustManager singleton = new SimpleX509TrustManager();

        public static TrustManager[] getTrustManagers() {
            return new TrustManager[] { singleton };
        }

        @Override
        protected TrustManager[] engineGetTrustManagers() {
            return getTrustManagers();
        }

        @Override
        protected void engineInit(KeyStore keystore) throws KeyStoreException {
        }

        @Override
        protected void engineInit(ManagerFactoryParameters managerFactoryParameters)
                throws InvalidAlgorithmParameterException {
        }

        public static class SimpleX509TrustManager extends X509ExtendedTrustManager {

            @Override
            public void checkClientTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {
            }

            @Override
            public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
            }

            @Override
            public X509Certificate[] getAcceptedIssuers() {
                return trustedCerts;
            }

            @Override
            public void checkClientTrusted(X509Certificate[] arg0, String arg1, String arg2, String arg3)
                    throws CertificateException {
            }

            @Override
            public void checkServerTrusted(X509Certificate[] arg0, String arg1, String arg2, String arg3)
                    throws CertificateException {
            }
        }

    }

    static class ServerTrustManager extends TrustManagerFactorySpi {

        private static final TrustManager singleton = new SimpleX509TrustManager();

        public static TrustManager[] getTrustManagers() {
            return new TrustManager[] { singleton };
        }

        @Override
        protected TrustManager[] engineGetTrustManagers() {
            return getTrustManagers();
        }

        @Override
        protected void engineInit(KeyStore keystore) throws KeyStoreException {
        }

        @Override
        protected void engineInit(ManagerFactoryParameters managerFactoryParameters)
                throws InvalidAlgorithmParameterException {
        }

        public static class SimpleX509TrustManager extends X509ExtendedTrustManager {

            @Override
            public void checkClientTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {
            }

            @Override
            public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
            }

            @Override
            public X509Certificate[] getAcceptedIssuers() {
                return serverCertSupplier.get();
            }

            @Override
            public void checkClientTrusted(X509Certificate[] arg0, String arg1, String arg2, String arg3)
                    throws CertificateException {
            }

            @Override
            public void checkServerTrusted(X509Certificate[] arg0, String arg1, String arg2, String arg3)
                    throws CertificateException {
            }
        }

    }

    public static class UserSSLSocketFactory extends SSLSocketFactoryWrapper {
        private static final String PROP_USER_SSL_PROVIDER = "com.eucalyptus.crypto.util.userSslProvider";
        private static final String PROP_USER_SSL_PROTOCOL = "com.eucalyptus.crypto.util.userSslProtocol";
        private static final String PROP_USER_SSL_TRUSTSTORE_PASSWORD = "com.eucalyptus.crypto.util.userSslTrustStorePassword";
        private static final String PROP_USER_SSL_TRUSTSTORE_TYPE = "com.eucalyptus.crypto.util.userSslTrustStoreType";
        private static final String PROP_USER_SSL_TRUSTSTORE_PATH = "com.eucalyptus.crypto.util.userSslTrustStorePath";
        private static final String DEFAULT_PROTOCOL = "TLS";
        private static final String DEFAULT_TRUSTSTORE_PASSWORD = "changeit";
        private static final String DEFAULT_TRUSTSTORE_PATH = "lib/security/cacerts";
        private final List<String> cipherSuites;
        private final static AtomicReference<Pair<Long, KeyStore>> sslTrustStore = new AtomicReference<>();

        private static final Supplier<UserSSLSocketFactory> supplier = new Supplier<UserSSLSocketFactory>() {
            @Override
            public UserSSLSocketFactory get() {
                return new UserSSLSocketFactory();
            }
        };

        private static final Supplier<UserSSLSocketFactory> memoizedSupplier = Suppliers
                .memoizeWithExpiration(supplier, 60l, TimeUnit.SECONDS);

        public static UserSSLSocketFactory get() {
            return memoizedSupplier.get();
        }

        public UserSSLSocketFactory() {
            super(Suppliers.ofInstance(buildDelegate()));
            this.cipherSuites = ImmutableList
                    .copyOf(getEnabledCipherSuites(USER_SSL_CIPHERS, getSupportedCipherSuites()));
        }

        @Override
        protected Socket notifyCreated(final Socket socket) {
            if (socket instanceof SSLSocket) {
                final SSLSocket sslSocket = (SSLSocket) socket;
                sslSocket.setEnabledCipherSuites(toArray(cipherSuites, String.class));
                sslSocket.setEnabledProtocols(
                        SslUtils.getEnabledProtocols(USER_SSL_PROTOCOLS, sslSocket.getSupportedProtocols()));
            }
            return socket;
        }

        private static KeyStore getTrustStore(final File trustStore) throws GeneralSecurityException, IOException {
            final Pair<Long, KeyStore> currentTrustStore = sslTrustStore.get();
            InputStream trustStoreIn = null;
            if (currentTrustStore != null && currentTrustStore.getLeft() == trustStore.lastModified()) {
                return currentTrustStore.getRight();
            } else
                try {
                    final String trustStoreType = getProperty(PROP_USER_SSL_TRUSTSTORE_TYPE,
                            KeyStore.getDefaultType());
                    final String trustStorePassword = getProperty(PROP_USER_SSL_TRUSTSTORE_PASSWORD,
                            DEFAULT_TRUSTSTORE_PASSWORD);
                    final KeyStore userTrustStore = KeyStore.getInstance(trustStoreType);
                    userTrustStore.load(trustStoreIn = new FileInputStream(trustStore),
                            trustStorePassword.toCharArray());
                    sslTrustStore.set(Pair.pair(trustStore.lastModified(), userTrustStore));
                    return userTrustStore;
                } finally {
                    IO.close(trustStoreIn);
                }
        }

        private static List<TrustManager> trustManagersForStore(final KeyStore trustStore)
                throws GeneralSecurityException {
            final TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance("PKIX", "SunJSSE");
            trustManagerFactory.init(trustStore);
            return Arrays.asList(trustManagerFactory.getTrustManagers());
        }

        private static SSLSocketFactory buildDelegate() {
            final String provider = getProperty(PROP_USER_SSL_PROVIDER);
            final String protocol = getProperty(PROP_USER_SSL_PROTOCOL, DEFAULT_PROTOCOL);
            final String trustStorePath = getProperty(PROP_USER_SSL_TRUSTSTORE_PATH, DEFAULT_TRUSTSTORE_PATH);

            final SSLContext sslContext;
            try {
                sslContext = provider != null ? SSLContext.getInstance(protocol, provider)
                        : SSLContext.getInstance(protocol);

                final List<TrustManager> trustManagers = Lists.newArrayList();
                trustManagers.addAll(
                        trustManagersForStore(((AbstractKeyStore) SystemCredentials.getKeyStore()).getKeyStore()));
                final File trustStore = new File(System.getProperty("java.home"), trustStorePath);
                if (Objects.firstNonNull(USER_SSL_DEFAULT_CAS, false) && trustStore.isFile()) {
                    trustManagers.addAll(trustManagersForStore(getTrustStore(trustStore)));
                }

                sslContext
                        .init(null,
                                new TrustManager[] { new DelegatingX509ExtendedTrustManager(Iterables.transform(
                                        Iterables.filter(trustManagers,
                                                Predicates
                                                        .instanceOf(javax.net.ssl.X509ExtendedTrustManager.class)),
                                        CollectionUtils.cast(javax.net.ssl.X509ExtendedTrustManager.class))) },
                                Crypto.getSecureRandomSupplier().get());
                return sslContext.getSocketFactory();
            } catch (final GeneralSecurityException | IOException e) {
                throw propagate(e);
            }
        }
    }
}