org.codice.ddf.cxf.client.impl.SecureCxfClientFactoryImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.codice.ddf.cxf.client.impl.SecureCxfClientFactoryImpl.java

Source

/**
 * Copyright (c) Codice Foundation
 *
 * <p>This is free software: you can redistribute it and/or modify it under the terms of the GNU
 * Lesser General Public License as published by the Free Software Foundation, either version 3 of
 * the License, or any later version.
 *
 * <p>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 Lesser General Public License for more details. A copy of the GNU Lesser General Public
 * License is distributed along with this program and can be found at
 * <http://www.gnu.org/licenses/lgpl.html>.
 */
package org.codice.ddf.cxf.client.impl;

import ddf.security.SecurityConstants;
import ddf.security.common.audit.SecurityLogger;
import ddf.security.liberty.paos.Request;
import ddf.security.liberty.paos.Response;
import ddf.security.liberty.paos.impl.RequestBuilder;
import ddf.security.liberty.paos.impl.RequestMarshaller;
import ddf.security.liberty.paos.impl.RequestUnmarshaller;
import ddf.security.liberty.paos.impl.ResponseBuilder;
import ddf.security.liberty.paos.impl.ResponseMarshaller;
import ddf.security.liberty.paos.impl.ResponseUnmarshaller;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.net.URI;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.AccessController;
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.PrivilegedAction;
import java.security.SecurityPermission;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.List;
import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509KeyManager;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.cxf.Bus;
import org.apache.cxf.configuration.jsse.TLSClientParameters;
import org.apache.cxf.interceptor.Interceptor;
import org.apache.cxf.interceptor.LoggingInInterceptor;
import org.apache.cxf.interceptor.LoggingOutInterceptor;
import org.apache.cxf.jaxrs.client.ClientConfiguration;
import org.apache.cxf.jaxrs.client.JAXRSClientFactory;
import org.apache.cxf.jaxrs.client.JAXRSClientFactoryBean;
import org.apache.cxf.jaxrs.client.WebClient;
import org.apache.cxf.message.Message;
import org.apache.cxf.phase.Phase;
import org.apache.cxf.transport.http.HTTPConduit;
import org.apache.cxf.transports.http.configuration.HTTPClientPolicy;
import org.apache.shiro.subject.Subject;
import org.apache.wss4j.common.saml.OpenSAMLUtil;
import org.codice.ddf.configuration.PropertyResolver;
import org.codice.ddf.configuration.SystemBaseUrl;
import org.codice.ddf.cxf.client.SecureCxfClientFactory;
import org.codice.ddf.cxf.paos.PaosInInterceptor;
import org.codice.ddf.cxf.paos.PaosOutInterceptor;
import org.codice.ddf.security.common.jaxrs.RestSecurity;
import org.opensaml.core.config.ConfigurationService;
import org.opensaml.core.xml.config.XMLObjectProviderRegistry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * This factory helps construct clients for secure restful communication. For now, the getForSubject
 * methods should only be used to support DDF<->DDF interop, because:
 *
 * <ol>
 *   <li>Most non-DDF systems do not know how to handle SAML assertions in the auth header.
 * </ol>
 */
public class SecureCxfClientFactoryImpl<T> implements SecureCxfClientFactory<T> {

    private static final Logger LOGGER = LoggerFactory.getLogger(SecureCxfClientFactoryImpl.class);
    public static final String HTTPS = "https";

    private final JAXRSClientFactoryBean clientFactory;

    private final boolean disableCnCheck;

    private final boolean allowRedirects;

    private final Class<T> interfaceClass;

    private static final Integer DEFAULT_CONNECTION_TIMEOUT = 30000;

    private static final Integer DEFAULT_RECEIVE_TIMEOUT = 60000;

    // A default small value greater than one to handle loopbacks after validation.  Configurable
    private static final Integer SAME_URI_REDIRECT_MAX = 3;

    private static final String AUTO_REDIRECT_ALLOW_REL_URI = "http.redirect.relative.uri";

    private static final String AUTO_REDIRECT_MAX_SAME_URI_COUNT = "http.redirect.max.same.uri.count";

    private static final SecurityPermission CREATE_CLIENT_PERMISSION = new SecurityPermission("createCxfClient");

    private Integer sameUriRedirectMax = SAME_URI_REDIRECT_MAX;

    private boolean basicAuth = false;

    private Integer connectionTimeout;

    private Integer receiveTimeout;

    private ClientKeyInfo keyInfo = null;

    private String sslProtocol;

    static {
        OpenSAMLUtil.initSamlEngine();
        XMLObjectProviderRegistry xmlObjectProviderRegistry = ConfigurationService
                .get(XMLObjectProviderRegistry.class);
        xmlObjectProviderRegistry.registerObjectProvider(Request.DEFAULT_ELEMENT_NAME, new RequestBuilder(),
                new RequestMarshaller(), new RequestUnmarshaller());
        xmlObjectProviderRegistry.registerObjectProvider(Response.DEFAULT_ELEMENT_NAME, new ResponseBuilder(),
                new ResponseMarshaller(), new ResponseUnmarshaller());
    }

    /**
     * @see #SecureCxfClientFactoryImpl(String, Class, java.util.List, Interceptor, boolean, boolean)
     */
    public SecureCxfClientFactoryImpl(String endpointUrl, Class<T> interfaceClass) {
        this(endpointUrl, interfaceClass, null, null, false, false);
    }

    public SecureCxfClientFactoryImpl(String endpointUrl, Class<T> interfaceClass, String username,
            String password) {
        this(endpointUrl, interfaceClass, null, null, false, false, null, null, username, password);
    }

    public SecureCxfClientFactoryImpl(String endpointUrl, Class<T> interfaceClass, List<?> providers,
            Interceptor<? extends Message> interceptor, boolean disableCnCheck, boolean allowRedirects) {
        this(endpointUrl, interfaceClass, providers, interceptor, disableCnCheck, allowRedirects,
                new PropertyResolver(endpointUrl));
    }

    /**
     * Constructs a factory that will return security-aware cxf clients. Once constructed, use the
     * getClient* methods to retrieve a fresh client with the same configuration. Providing {@link
     * WebClient} to interfaceClass will create a generic web client.
     *
     * <p>This factory can and should be cached. The clients it constructs should not be.
     *
     * @param endpointUrl the remote url to connect to
     * @param interfaceClass an interface representing the resource at the remote url
     * @param providers optional list of providers to further configure the client
     * @param interceptor optional message interceptor for the client
     * @param disableCnCheck disable ssl check for common name / host name match
     * @param allowRedirects allow this client to follow redirects
     */
    public SecureCxfClientFactoryImpl(String endpointUrl, Class<T> interfaceClass, List<?> providers,
            Interceptor<? extends Message> interceptor, boolean disableCnCheck, boolean allowRedirects,
            PropertyResolver propertyResolver) {
        if (StringUtils.isEmpty(endpointUrl) || interfaceClass == null) {
            throw new IllegalArgumentException("Called without a valid URL, will not be able to connect.");
        }

        if (propertyResolver != null) {
            endpointUrl = propertyResolver.getResolvedString();
        } else {
            LOGGER.warn("Called without a valid propertyResolver, system properties in URI may not resolve.");
        }

        this.interfaceClass = interfaceClass;
        this.disableCnCheck = disableCnCheck;
        this.allowRedirects = allowRedirects;

        JAXRSClientFactoryBean jaxrsClientFactoryBean = new JAXRSClientFactoryBean();
        jaxrsClientFactoryBean.setServiceClass(interfaceClass);
        jaxrsClientFactoryBean.setAddress(endpointUrl);
        jaxrsClientFactoryBean.setClassLoader(interfaceClass.getClassLoader());
        jaxrsClientFactoryBean.getInInterceptors().add(new LoggingInInterceptor());
        jaxrsClientFactoryBean.getOutInterceptors().add(new LoggingOutInterceptor());

        if (!basicAuth && StringUtils.startsWithIgnoreCase(endpointUrl, HTTPS)) {
            jaxrsClientFactoryBean.getInInterceptors().add(new PaosInInterceptor(Phase.RECEIVE));
            jaxrsClientFactoryBean.getOutInterceptors().add(new PaosOutInterceptor(Phase.POST_LOGICAL));
        }

        if (CollectionUtils.isNotEmpty(providers)) {
            jaxrsClientFactoryBean.setProviders(providers);
        }

        if (interceptor != null) {
            jaxrsClientFactoryBean.getInInterceptors().add(interceptor);
        }

        this.clientFactory = jaxrsClientFactoryBean;
    }

    /**
     * Constructs a factory that will return security-aware cxf clients. Once constructed, use the
     * getClient* methods to retrieve a fresh client with the same configuration. Providing {@link
     * WebClient} to interfaceClass will create a generic web client.
     *
     * <p>This factory can and should be cached. The clients it constructs should not be.
     *
     * @param endpointUrl the remote url to connect to
     * @param interfaceClass an interface representing the resource at the remote url
     * @param providers optional list of providers to further configure the client
     * @param interceptor optional message interceptor for the client
     * @param disableCnCheck disable ssl check for common name / host name match
     * @param allowRedirects allow this client to follow redirects
     * @param connectionTimeout timeout for the connection
     * @param receiveTimeout timeout for receiving responses
     */
    @SuppressWarnings("squid:S00107")
    public SecureCxfClientFactoryImpl(String endpointUrl, Class<T> interfaceClass, List<?> providers,
            Interceptor<? extends Message> interceptor, boolean disableCnCheck, boolean allowRedirects,
            Integer connectionTimeout, Integer receiveTimeout) {

        this(endpointUrl, interfaceClass, providers, interceptor, disableCnCheck, allowRedirects);

        this.connectionTimeout = connectionTimeout;

        this.receiveTimeout = receiveTimeout;
    }

    /**
     * Constructs a factory that will return security-aware cxf clients. Once constructed, use the
     * getClient* methods to retrieve a fresh client with the same configuration. Providing {@link
     * WebClient} to interfaceClass will create a generic web client.
     *
     * <p>This factory can and should be cached. The clients it constructs should not be.
     *
     * @param endpointUrl the remote url to connect to
     * @param interfaceClass an interface representing the resource at the remote url
     * @param providers optional list of providers to further configure the client
     * @param interceptor optional message interceptor for the client
     * @param disableCnCheck disable ssl check for common name / host name match
     * @param allowRedirects allow this client to follow redirects
     * @param connectionTimeout timeout for the connection
     * @param receiveTimeout timeout for receiving responses
     * @param keyInfo client key info for 2-way ssl
     * @param sslProtocol SSL protocol to use (e.g. TLSv1.2)
     */
    @SuppressWarnings("squid:S00107")
    public SecureCxfClientFactoryImpl(String endpointUrl, Class<T> interfaceClass, List<?> providers,
            Interceptor<? extends Message> interceptor, boolean disableCnCheck, boolean allowRedirects,
            Integer connectionTimeout, Integer receiveTimeout, ClientKeyInfo keyInfo, String sslProtocol) {

        this(endpointUrl, interfaceClass, providers, interceptor, disableCnCheck, allowRedirects);

        this.connectionTimeout = connectionTimeout;

        this.receiveTimeout = receiveTimeout;

        this.keyInfo = keyInfo;

        this.sslProtocol = sslProtocol;
    }

    /**
     * Constructs a factory that will return security-aware cxf clients. Once constructed, use the
     * getClient* methods to retrieve a fresh client with the same configuration. Providing {@link
     * WebClient} to interfaceClass will create a generic web client.
     *
     * <p>This factory can and should be cached. The clients it constructs should not be.
     *
     * <p>This constructor represents a quick fix only.
     *
     * @param endpointUrl the remote url to connect to
     * @param interfaceClass an interface representing the resource at the remote url
     * @param providers optional list of providers to further configure the client
     * @param interceptor optional message interceptor for the client
     * @param disableCnCheck disable ssl check for common name / host name match
     * @param allowRedirects allow this client to follow redirects
     * @param connectionTimeout timeout for the connection
     * @param receiveTimeout timeout for receiving responses
     * @param username a String representing the username
     * @param password a String representing a password
     */
    @SuppressWarnings("squid:S00107")
    public SecureCxfClientFactoryImpl(String endpointUrl, Class<T> interfaceClass, List<?> providers,
            Interceptor<? extends Message> interceptor, boolean disableCnCheck, boolean allowRedirects,
            Integer connectionTimeout, Integer receiveTimeout, String username, String password) {

        this(endpointUrl, interfaceClass, providers, interceptor, disableCnCheck, allowRedirects, connectionTimeout,
                receiveTimeout);

        this.clientFactory.setPassword(password);
        this.clientFactory.setUsername(username);
        this.basicAuth = true;
    }

    public T getClient() {
        return getClientForSubject(null);
    }

    public WebClient getWebClient() {
        return getWebClientForSubject(null);
    }

    /**
     * Clients produced by this method will be secured with two-way ssl and the provided security
     * subject.
     *
     * <p>The returned client should NOT be reused between requests! This method should be called for
     * each new request in order to ensure that the security token is up-to-date each time.
     */
    public final T getClientForSubject(Subject subject) {
        final java.lang.SecurityManager security = System.getSecurityManager();

        if (security != null) {
            security.checkPermission(CREATE_CLIENT_PERMISSION);
        }

        return AccessController.doPrivileged((PrivilegedAction<T>) () -> {
            String asciiString = clientFactory.getAddress();

            T newClient = getNewClient();

            if (!basicAuth && StringUtils.startsWithIgnoreCase(asciiString, HTTPS)) {
                if (subject instanceof ddf.security.Subject) {
                    RestSecurity.setSubjectOnClient((ddf.security.Subject) subject, WebClient.client(newClient));
                }
            }

            auditRemoteConnection(asciiString);

            return newClient;
        });
    }

    public Integer getSameUriRedirectMax() {
        return sameUriRedirectMax;
    }

    public void setSameUriRedirectMax(Integer sameUriRedirectMax) {
        if (sameUriRedirectMax != null && sameUriRedirectMax > 0) {
            this.sameUriRedirectMax = sameUriRedirectMax;
        } else {
            LOGGER.warn("Cannot set redirect to invalid value: {}", sameUriRedirectMax);
        }
    }

    private void auditRemoteConnection(String asciiString) {
        try {
            URI uri = new URI(asciiString);
            String host = uri.getHost();
            InetAddress inetAddress = InetAddress.getByName(host);
            SecurityLogger.audit("Setting up remote connection to federated node [{}].",
                    inetAddress.getHostAddress());
        } catch (Exception e) {
            LOGGER.debug(
                    "Unhandled exception while attempting to determine the IP address for a federated node, might be a DNS issue.",
                    e);
            SecurityLogger.audit(
                    "Unable to determine the IP address for a federated node [{}], might be a DNS issue.",
                    asciiString);
        }
    }

    /**
     * Convenience method to get a {@link WebClient} instead of a {@link
     * org.apache.cxf.jaxrs.client.ClientProxyImpl ClientProxyImpl}.
     *
     * @see #getClientForSubject(Subject subject)
     */
    public WebClient getWebClientForSubject(Subject subject) {
        return getWebClient(getClientForSubject(subject));
    }

    private WebClient getWebClient(Object client) {
        return WebClient.fromClient(WebClient.client(client), true);
    }

    private T getNewClient() {
        T clientImpl = interfaceClass.equals(WebClient.class) ? (T) clientFactory.create()
                : JAXRSClientFactory.fromClient(clientFactory.create(), interfaceClass);

        ClientConfiguration clientConfig = WebClient.getConfig(clientImpl);
        clientConfig.getRequestContext().put(Message.MAINTAIN_SESSION, Boolean.TRUE);

        configureConduit(clientConfig);
        configureTimeouts(clientConfig, connectionTimeout, receiveTimeout);
        return clientImpl;
    }

    @SuppressWarnings("squid:S3776")
    private void configureConduit(ClientConfiguration clientConfig) {
        HTTPConduit httpConduit = clientConfig.getHttpConduit();
        if (httpConduit == null) {
            LOGGER.info("HTTPConduit was null for {}. Unable to configure security.", this);
            return;
        }

        if (allowRedirects) {
            HTTPClientPolicy clientPolicy = httpConduit.getClient();
            if (clientPolicy != null) {
                clientPolicy.setAutoRedirect(true);
                Bus bus = clientConfig.getBus();
                if (bus != null) {
                    bus.getProperties().put(AUTO_REDIRECT_ALLOW_REL_URI, true);
                    bus.getProperties().put(AUTO_REDIRECT_MAX_SAME_URI_COUNT, getSameUriRedirectMax());
                }
            }
        }

        TLSClientParameters tlsParams = httpConduit.getTlsClientParameters();
        if (tlsParams == null) {
            tlsParams = new TLSClientParameters();
        }

        tlsParams.setDisableCNCheck(disableCnCheck);

        tlsParams.setUseHttpsURLConnectionDefaultHostnameVerifier(!disableCnCheck);
        String cipherSuites = System.getProperty("https.cipherSuites");
        if (cipherSuites != null) {
            tlsParams.setCipherSuites(Arrays.asList(cipherSuites.split(",")));
        }

        KeyStore keyStore = null;
        KeyStore trustStore = null;
        try {
            keyStore = SecurityConstants.newKeystore();
            trustStore = SecurityConstants.newTruststore();
        } catch (KeyStoreException e) {
            LOGGER.debug("Unable to create keystore instance of type {}",
                    System.getProperty(SecurityConstants.KEYSTORE_TYPE), e);
        }
        Path keyStoreFile;
        if (keyInfo != null && StringUtils.isNotBlank(keyInfo.getKeystorePath())) {
            keyStoreFile = Paths.get(keyInfo.getKeystorePath());
        } else {
            keyStoreFile = Paths.get(SecurityConstants.getKeystorePath());
        }

        Path trustStoreFile = Paths.get(SecurityConstants.getTruststorePath());
        String ddfHome = System.getProperty("ddf.home");
        if (ddfHome != null) {
            Path ddfHomePath = Paths.get(ddfHome);
            if (!keyStoreFile.isAbsolute()) {
                keyStoreFile = Paths.get(ddfHomePath.toString(), keyStoreFile.toString());
            }
            if (!trustStoreFile.isAbsolute()) {
                trustStoreFile = Paths.get(ddfHomePath.toString(), trustStoreFile.toString());
            }
        }
        String keyStorePassword = SecurityConstants.getKeystorePassword();
        String trustStorePassword = SecurityConstants.getTruststorePassword();
        if (!Files.isReadable(keyStoreFile) || !Files.isReadable(trustStoreFile)) {
            LOGGER.debug("Unable to read system key/trust store files: [ {} ] [ {} ]", keyStoreFile,
                    trustStoreFile);
            return;
        }
        try (InputStream kfis = Files.newInputStream(keyStoreFile)) {
            if (keyStore != null) {
                keyStore.load(kfis, keyStorePassword.toCharArray());
            }
        } catch (NoSuchAlgorithmException | CertificateException | IOException e) {
            LOGGER.debug("Unable to load system key file.", e);
        }
        try (InputStream tfis = Files.newInputStream(trustStoreFile)) {
            if (trustStore != null) {
                trustStore.load(tfis, trustStorePassword.toCharArray());
            }
        } catch (NoSuchAlgorithmException | CertificateException | IOException e) {
            LOGGER.debug("Unable to load system trust file.", e);
        }

        KeyManager[] keyManagers = null;
        try {
            KeyManagerFactory keyManagerFactory = KeyManagerFactory
                    .getInstance(KeyManagerFactory.getDefaultAlgorithm());
            keyManagerFactory.init(keyStore, keyStorePassword.toCharArray());
            keyManagers = keyManagerFactory.getKeyManagers();
            tlsParams.setKeyManagers(keyManagers);
        } catch (NoSuchAlgorithmException | KeyStoreException | UnrecoverableKeyException e) {
            LOGGER.debug("Unable to initialize KeyManagerFactory.", e);
        }

        TrustManager[] trustManagers = null;
        try {
            TrustManagerFactory trustManagerFactory = TrustManagerFactory
                    .getInstance(TrustManagerFactory.getDefaultAlgorithm());
            trustManagerFactory.init(trustStore);
            trustManagers = trustManagerFactory.getTrustManagers();
            tlsParams.setTrustManagers(trustManagers);
        } catch (NoSuchAlgorithmException | KeyStoreException e) {
            LOGGER.debug("Unable to initialize TrustManagerFactory.", e);
        }

        if (keyInfo != null) {
            LOGGER.trace("Using keystore file: {}, alias: {}", keyStoreFile, keyInfo.getAlias());
            tlsParams.setUseHttpsURLConnectionDefaultSslSocketFactory(false);
            tlsParams.setCertAlias(keyInfo.getAlias());
            try {
                if (keyManagers == null) {
                    throw new KeyManagementException("keyManagers was null");
                }

                boolean validProtocolFound = false;
                String validProtocolsStr = System.getProperty("jdk.tls.client.protocols");
                if (StringUtils.isNotBlank(validProtocolsStr)) {
                    String[] validProtocols = validProtocolsStr.split(",");
                    for (String validProtocol : validProtocols) {
                        if (validProtocol.equals(sslProtocol)) {
                            validProtocolFound = true;
                            break;
                        }
                    }
                    if (!validProtocolFound) {
                        LOGGER.error("{} is not in list of valid SSL protocols {}", sslProtocol, validProtocolsStr);
                    }

                } else {
                    validProtocolFound = true;
                }
                if (validProtocolFound) {
                    tlsParams.setSSLSocketFactory(
                            getSSLSocketFactory(sslProtocol, keyInfo.getAlias(), keyManagers, trustManagers));
                }
            } catch (KeyManagementException | NoSuchAlgorithmException e) {
                LOGGER.debug("Unable to override default SSL Socket Factory", e);
            }
        } else {
            tlsParams.setUseHttpsURLConnectionDefaultSslSocketFactory(true);
            tlsParams.setCertAlias(SystemBaseUrl.INTERNAL.getHost());
        }

        httpConduit.setTlsClientParameters(tlsParams);
    }

    /**
     * Configures the connection and receive timeouts. If any of the parameters are null, the timeouts
     * will be set to the system default.
     *
     * @param clientConfiguration Client configuration used for outgoing requests.
     * @param connectionTimeout Connection timeout in milliseconds.
     * @param receiveTimeout Receive timeout in milliseconds.
     */
    protected void configureTimeouts(ClientConfiguration clientConfiguration, Integer connectionTimeout,
            Integer receiveTimeout) {

        HTTPConduit httpConduit = clientConfiguration.getHttpConduit();
        if (httpConduit == null) {
            LOGGER.info("HTTPConduit was null for {}. Unable to configure timeouts", this);
            return;
        }
        HTTPClientPolicy httpClientPolicy = httpConduit.getClient();

        if (httpClientPolicy == null) {
            httpClientPolicy = new HTTPClientPolicy();
        }

        if (connectionTimeout != null) {
            httpClientPolicy.setConnectionTimeout(connectionTimeout);
        } else {
            httpClientPolicy.setConnectionTimeout(DEFAULT_CONNECTION_TIMEOUT);
        }

        if (receiveTimeout != null) {
            httpClientPolicy.setReceiveTimeout(receiveTimeout);
        } else {
            httpClientPolicy.setReceiveTimeout(DEFAULT_RECEIVE_TIMEOUT);
        }

        if (httpClientPolicy.isSetConnectionTimeout()) {
            LOGGER.debug("Connection timeout has been set.");
        } else {
            LOGGER.debug("Connection timeout has NOT been set.");
        }
        if (httpClientPolicy.isSetReceiveTimeout()) {
            LOGGER.debug("Receive timeout has been set.");
        } else {
            LOGGER.debug("Receive timeout has NOT been set.");
        }

        httpConduit.setClient(httpClientPolicy);
    }

    public void addOutInterceptors(Interceptor<? extends Message> inteceptor) {
        this.clientFactory.getOutInterceptors().add(inteceptor);
    }

    private SSLSocketFactory getSSLSocketFactory(String sslProtocol, String alias, KeyManager[] keyManagers,
            TrustManager[] trustManagers) throws KeyManagementException, NoSuchAlgorithmException {

        if (ArrayUtils.isNotEmpty(keyManagers)) {
            for (int i = 0; i < keyManagers.length; i++) {
                if (keyManagers[i] instanceof X509KeyManager) {
                    keyManagers[i] = new AliasSelectorKeyManager((X509KeyManager) keyManagers[i], alias);
                }
            }
        }

        SSLContext context = SSLContext.getInstance(sslProtocol);
        context.init(keyManagers, trustManagers, null);

        return context.getSocketFactory();
    }

    /**
     * X509 certificate selector for retrieving certificate for a specific alias. Based off of code
     * from
     * https://alesaudate.wordpress.com/2010/08/09/how-to-dynamically-select-a-certificate-alias-when-invoking-web-services/
     */
    public static class AliasSelectorKeyManager implements X509KeyManager {

        private X509KeyManager keyManager;
        private String alias;

        public AliasSelectorKeyManager(X509KeyManager keyManager, String alias) {
            this.keyManager = keyManager;
            this.alias = alias;
        }

        public String chooseClientAlias(String[] keyTypes, Principal[] issuers, Socket socket) {
            if (keyManager == null) {
                return null;
            }

            if (alias == null) {
                return keyManager.chooseClientAlias(keyTypes, issuers, socket);
            }

            for (String keyType : keyTypes) {
                String[] validAliases = keyManager.getClientAliases(keyType, issuers);
                if (validAliases != null) {
                    for (String validAlias : validAliases) {
                        if (validAlias.equals(alias)) {
                            return alias;
                        }
                    }
                }
            }

            return null;
        }

        public String chooseServerAlias(String keyType, Principal[] issuers, Socket socket) {
            return keyManager.chooseServerAlias(keyType, issuers, socket);
        }

        public X509Certificate[] getCertificateChain(String alias) {
            return keyManager.getCertificateChain(alias);
        }

        public String[] getClientAliases(String keyType, Principal[] issuers) {
            return keyManager.getClientAliases(keyType, issuers);
        }

        public PrivateKey getPrivateKey(String alias) {
            return keyManager.getPrivateKey(alias);
        }

        public String[] getServerAliases(String keyType, Principal[] issuers) {
            return keyManager.getServerAliases(keyType, issuers);
        }
    }
}