com.microsoft.tfs.core.config.httpclient.internal.DefaultSSLProtocolSocketFactory.java Source code

Java tutorial

Introduction

Here is the source code for com.microsoft.tfs.core.config.httpclient.internal.DefaultSSLProtocolSocketFactory.java

Source

// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See License.txt in the repository root.

package com.microsoft.tfs.core.config.httpclient.internal;

import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.util.ArrayList;
import java.util.List;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLParameters;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.microsoft.tfs.core.config.EnvironmentVariables;
import com.microsoft.tfs.core.httpclient.ConnectTimeoutException;
import com.microsoft.tfs.core.httpclient.params.HttpConnectionParams;
import com.microsoft.tfs.core.httpclient.protocol.SecureProtocolSocketFactory;
import com.microsoft.tfs.util.Check;
import com.microsoft.tfs.util.StringUtil;

/**
 * An SSL socket factory for HTTPClient that tweaks certificate validation,
 * either providing the ability to accept self-signed certificates, or one that
 * includes additional certificates (notably newer well-trusted certificates
 * like the Microsoft Internet Authority certificate, vital for connecting to
 * Azure-hosted TFS servers on platforms that lack the updated Microsoft
 * Internet Authority cert.)
 */
public class DefaultSSLProtocolSocketFactory implements SecureProtocolSocketFactory {
    public static final String ACCEPT_UNTRUSTED_CERTIFICATES_PARAMETER = "DefaultSSLProtocolSocketFactory.acceptUntrustedCertificates"; //$NON-NLS-1$

    /**
     * Name of the Java system property used to disable modifying the
     * {@link SSLSocketFactory} from the default. If this is set (to any value),
     * we will always use the standard system {@link SSLSocketFactory}.
     */
    public static final String DISABLE_PROPERTY_NAME = "com.microsoft.tfs.core.config.httpclient.sslsocketfactory.disable"; //$NON-NLS-1$

    /**
     * Name of the SSL protocol to be used to create the
     * {@link SSLSocketFactory}. Valid protocol names: SSL, SSLv3, TLS, TLSv1,
     * TLSv1.1, TLSv1.2
     */
    public static final String SSL_PROTOCOL_PROPERTY_NAME = "com.microsoft.tfs.core.config.httpclient.sslsocketfactory.sslprotocol"; //$NON-NLS-1$

    private static final Log log = LogFactory.getLog(DefaultSSLProtocolSocketFactory.class);

    private static final Object lock = new Object();

    private static SSLSocketFactory standardSocketFactory;
    private static SSLSocketFactory selfSignedSocketFactory;

    /**
     * {@inheritDoc}
     */
    @Override
    public Socket createSocket(final String host, final int port, final InetAddress localAddress,
            final int localPort, final HttpConnectionParams params)
            throws IOException, UnknownHostException, ConnectTimeoutException {
        Check.notNull(params, "params"); //$NON-NLS-1$

        final int timeout = params.getConnectionTimeout();

        final Socket socket = getSocketFactory(params).createSocket();
        configureSNI(socket, host);
        socket.bind(new InetSocketAddress(localAddress, localPort));
        socket.connect(new InetSocketAddress(host, port), timeout);
        return socket;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Socket createSocket(final Socket socket, final String host, final int port,
            final HttpConnectionParams params, final boolean autoClose) throws IOException, UnknownHostException {
        Check.notNull(params, "params"); //$NON-NLS-1$

        final Socket ssocket = getSocketFactory(params).createSocket(socket, host, port, autoClose);
        configureSNI(ssocket, host);
        return ssocket;
    }

    private SSLSocketFactory getSocketFactory(final HttpConnectionParams params) {
        if (isEnabled()) {
            try {
                SSLSocketFactory socketFactory;

                if (params.getBooleanParameter(ACCEPT_UNTRUSTED_CERTIFICATES_PARAMETER, false)) {
                    socketFactory = getSelfSignedSocketFactory(params);
                } else {
                    socketFactory = getStandardSocketFactory(params);
                }

                if (socketFactory != null) {
                    return socketFactory;
                }
            } catch (final Exception e) {
                log.warn("Could not create SSL socket factory, falling back to default", e); //$NON-NLS-1$
            }
        } else {
            log.info("SSLSocketFactory is disabled, falling back to system"); //$NON-NLS-1$
        }

        return (SSLSocketFactory) SSLSocketFactory.getDefault();
    }

    private boolean isEnabled() {
        return (System.getProperty(DISABLE_PROPERTY_NAME) == null);
    }

    private SSLContext getSSLContext() throws NoSuchAlgorithmException {

        final String requestedProtocol = getRequestedProtocol();

        try {
            return SSLContext.getInstance(requestedProtocol);
        } catch (final NoSuchAlgorithmException e) {
            log.error("Cannot create SSL context with the requested protocol " + requestedProtocol, e); //$NON-NLS-1$
            log.info("Using SSL context with the default protocol TLS"); //$NON-NLS-1$

            return SSLContext.getInstance("TLS"); //$NON-NLS-1$
        }
    }

    private String getRequestedProtocol() {
        final String protocol = System.getProperty(SSL_PROTOCOL_PROPERTY_NAME);
        if (StringUtil.isNullOrEmpty(protocol)) {
            return EnvironmentVariables.getString(EnvironmentVariables.SSL_PROTOCOL_NAME, "TLS"); //$NON-NLS-1$
        }
        return protocol;
    }

    private void configureSNI(final Socket socket, final String host) {

        if (System.getProperty("java.version").compareTo("1.8") < 0) { //$NON-NLS-1$ //$NON-NLS-2$
            return;
        }

        /*
         * Classes used to configure Server Name client-hello extension were
         * introduced in Java 8. So, we neither can use nor compile this code
         * using Java 6-7. Thus, let's use reflection.
         */

        try {
            final SSLSocket sslSocket = (SSLSocket) socket;
            final SSLParameters params = sslSocket.getSSLParameters();

            final Class<?> sniHostNameClass = Class.forName("javax.net.ssl.SNIHostName"); //$NON-NLS-1$
            final Constructor<?> sniHostNameClassConstructor = sniHostNameClass.getConstructor(String.class);

            final Object serverName = sniHostNameClassConstructor.newInstance(host);
            final List<Object> serverNames = new ArrayList<Object>(1);
            serverNames.add(serverName);

            final Class<?> paramsClass = params.getClass();
            final Method setServerNames = paramsClass.getMethod("setServerNames", List.class); //$NON-NLS-1$
            setServerNames.invoke(params, serverNames);

            sslSocket.setSSLParameters(params);
        } catch (final Exception e) {
            log.error("Eror configuring SSL socket with SNI cipher extension:", e); //$NON-NLS-1$
        }
    }

    /**
     * Create a new SSL socket factory that is tolerant of self-signed
     * certificates.
     *
     * @throws IOException
     * @throws CertificateException
     */
    private SSLSocketFactory getStandardSocketFactory(final HttpConnectionParams params)
            throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, CertificateException,
            IOException {

        synchronized (lock) {
            if (standardSocketFactory == null) {
                final SSLContext context = getSSLContext();

                /* Use the default x509 trust manager. */
                context.init(null, new TrustManager[] { new DefaultX509TrustManager(null) }, null);

                standardSocketFactory = context.getSocketFactory();
            }

            return standardSocketFactory;
        }
    }

    /**
     * Create a new SSL socket factory that is tolerant of self-signed
     * certificates.
     *
     * @throws NoSuchAlgorithmException
     * @throws KeyStoreException
     * @throws KeyManagementException
     */
    private SSLSocketFactory getSelfSignedSocketFactory(final HttpConnectionParams params)
            throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException {
        synchronized (lock) {
            if (selfSignedSocketFactory == null) {
                final SSLContext context = getSSLContext();

                /* Use the self-signed x509 trust manager. */
                context.init(null, new TrustManager[] { new SelfSignedX509TrustManager(null) }, null);

                selfSignedSocketFactory = context.getSocketFactory();
            }

            return selfSignedSocketFactory;
        }
    }
}