com.gargoylesoftware.htmlunit.httpclient.HtmlUnitSSLConnectionSocketFactory.java Source code

Java tutorial

Introduction

Here is the source code for com.gargoylesoftware.htmlunit.httpclient.HtmlUnitSSLConnectionSocketFactory.java

Source

/*
 * Copyright (c) 2002-2017 Gargoyle Software Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.gargoylesoftware.htmlunit.httpclient;

import java.io.IOException;
import java.lang.reflect.Field;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketAddress;
import java.net.SocketTimeoutException;
import java.security.GeneralSecurityException;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;

import org.apache.http.HttpHost;
import org.apache.http.conn.ConnectTimeoutException;
import org.apache.http.conn.ssl.DefaultHostnameVerifier;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.protocol.HttpContext;
import org.apache.http.ssl.SSLContexts;

import com.gargoylesoftware.htmlunit.WebClientOptions;

/**
 * Socket factory offering facilities for insecure SSL and for SOCKS proxy support.
 * This looks rather like a hack than like clean code but at the time of the writing it seems to
 * be the easiest way to provide SOCKS proxy support for HTTPS.
 *
 * @author Nicolas Belisle
 * @author Ahmed Ashour
 * @author Martin Huber
 * @author Marc Guillemot
 * @author Ronald Brill
 */
public final class HtmlUnitSSLConnectionSocketFactory extends SSLConnectionSocketFactory {
    private static final String SSL3ONLY = "htmlunit.SSL3Only";

    private final boolean useInsecureSSL_;

    /**
     * Enables/Disables the exclusive usage of SSL3.
     * @param httpContext the http context
     * @param ssl3Only true or false
     */
    public static void setUseSSL3Only(final HttpContext httpContext, final boolean ssl3Only) {
        httpContext.setAttribute(SSL3ONLY, ssl3Only);
    }

    static boolean isUseSSL3Only(final HttpContext context) {
        return "TRUE".equalsIgnoreCase((String) context.getAttribute(SSL3ONLY));
    }

    /**
     * Factory method that builds a new SSLConnectionSocketFactory.
     * @param options the current WebClientOptions
     * @return the SSLConnectionSocketFactory
     */
    public static SSLConnectionSocketFactory buildSSLSocketFactory(final WebClientOptions options) {
        try {
            final String[] sslClientProtocols = options.getSSLClientProtocols();
            final String[] sslClientCipherSuites = options.getSSLClientCipherSuites();

            final boolean useInsecureSSL = options.isUseInsecureSSL();

            if (!useInsecureSSL) {
                final KeyStore keyStore = options.getSSLClientCertificateStore();
                final KeyStore trustStore = options.getSSLTrustStore();

                return new HtmlUnitSSLConnectionSocketFactory(keyStore,
                        keyStore == null ? null : options.getSSLClientCertificatePassword(), trustStore,
                        useInsecureSSL, sslClientProtocols, sslClientCipherSuites);
            }

            // we need insecure SSL + SOCKS awareness
            String protocol = options.getSSLInsecureProtocol();
            if (protocol == null) {
                protocol = "SSL";
            }
            final SSLContext sslContext = SSLContext.getInstance(protocol);
            sslContext.init(getKeyManagers(options), new TrustManager[] { new InsecureTrustManager2() }, null);

            final SSLConnectionSocketFactory factory = new HtmlUnitSSLConnectionSocketFactory(sslContext,
                    NoopHostnameVerifier.INSTANCE, useInsecureSSL, sslClientProtocols, sslClientCipherSuites);
            return factory;
        } catch (final GeneralSecurityException e) {
            throw new RuntimeException(e);
        }
    }

    private HtmlUnitSSLConnectionSocketFactory(final SSLContext sslContext, final HostnameVerifier hostnameVerifier,
            final boolean useInsecureSSL, final String[] supportedProtocols, final String[] supportedCipherSuites) {
        super(sslContext, supportedProtocols, supportedCipherSuites, hostnameVerifier);
        useInsecureSSL_ = useInsecureSSL;
    }

    private HtmlUnitSSLConnectionSocketFactory(final KeyStore keystore, final char[] keystorePassword,
            final KeyStore truststore, final boolean useInsecureSSL, final String[] supportedProtocols,
            final String[] supportedCipherSuites)
            throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException {
        super(SSLContexts.custom().loadKeyMaterial(keystore, keystorePassword).loadTrustMaterial(truststore, null)
                .build(), supportedProtocols, supportedCipherSuites, new DefaultHostnameVerifier());
        useInsecureSSL_ = useInsecureSSL;
    }

    private static void configureSocket(final SSLSocket sslSocket, final HttpContext context) {
        if (isUseSSL3Only(context)) {
            sslSocket.setEnabledProtocols(new String[] { "SSLv3" });
        }
    }

    /**
     * Connect via socket.
     * @param connectTimeout the timeout
     * @param socket the socket
     * @param host the host
     * @param remoteAddress the remote address
     * @param localAddress the local address
     * @param context the context
     * @return the created/connected socket
     * @throws IOException in case of problems
     */
    @Override
    public Socket connectSocket(final int connectTimeout, final Socket socket, final HttpHost host,
            final InetSocketAddress remoteAddress, final InetSocketAddress localAddress, final HttpContext context)
            throws IOException {
        final HttpHost socksProxy = SocksConnectionSocketFactory.getSocksProxy(context);
        if (socksProxy != null) {
            final Socket underlying = SocksConnectionSocketFactory.createSocketWithSocksProxy(socksProxy);
            underlying.setReuseAddress(true);

            // TODO: commented out for HttpClient 4.3
            // final int soTimeout = HttpConnectionParams.getSoTimeout(params);

            final SocketAddress socksProxyAddress = new InetSocketAddress(socksProxy.getHostName(),
                    socksProxy.getPort());
            try {
                //underlying.setSoTimeout(soTimeout);
                underlying.connect(remoteAddress, connectTimeout);
            } catch (final SocketTimeoutException ex) {
                throw new ConnectTimeoutException("Connect to " + socksProxyAddress + " timed out");
            }

            final Socket sslSocket = getSSLSocketFactory().createSocket(underlying, socksProxy.getHostName(),
                    socksProxy.getPort(), true);
            configureSocket((SSLSocket) sslSocket, context);
            return sslSocket;
        }
        try {
            return super.connectSocket(connectTimeout, socket, host, remoteAddress, localAddress, context);
        } catch (final IOException e) {
            if (useInsecureSSL_ && "handshake alert:  unrecognized_name".equals(e.getMessage())) {
                setEmptyHostname(host);

                return super.connectSocket(connectTimeout, createSocket(context), host, remoteAddress, localAddress,
                        context);
            }
            throw e;
        }
    }

    private static void setEmptyHostname(final HttpHost host) {
        try {
            final Field field = HttpHost.class.getDeclaredField("hostname");
            field.setAccessible(true);
            field.set(host, "");
        } catch (final Exception e) {
            throw new RuntimeException(e);
        }
    }

    private javax.net.ssl.SSLSocketFactory getSSLSocketFactory() {
        try {
            final Field field = SSLConnectionSocketFactory.class.getDeclaredField("socketfactory");
            field.setAccessible(true);
            return (javax.net.ssl.SSLSocketFactory) field.get(this);
        } catch (final Exception e) {
            throw new RuntimeException(e);
        }
    }

    private static KeyManager[] getKeyManagers(final WebClientOptions options) {
        if (options.getSSLClientCertificateStore() == null) {
            return null;
        }
        try {
            final KeyStore keyStore = options.getSSLClientCertificateStore();
            final KeyManagerFactory keyManagerFactory = KeyManagerFactory
                    .getInstance(KeyManagerFactory.getDefaultAlgorithm());
            keyManagerFactory.init(keyStore, options.getSSLClientCertificatePassword());
            return keyManagerFactory.getKeyManagers();
        } catch (final Exception e) {
            throw new RuntimeException(e);
        }
    }
}

/**
 * A completely insecure (yet very easy to use) x509 trust manager. This manager trusts all servers
 * and all clients, regardless of credentials or lack thereof.
 *
 * @author Daniel Gredler
 * @author Marc Guillemot
 */
class InsecureTrustManager2 implements X509TrustManager {
    private final Set<X509Certificate> acceptedIssuers_ = new HashSet<>();

    /**
     * {@inheritDoc}
     */
    @Override
    public void checkClientTrusted(final X509Certificate[] chain, final String authType)
            throws CertificateException {
        // Everyone is trusted!
        acceptedIssuers_.addAll(Arrays.asList(chain));
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void checkServerTrusted(final X509Certificate[] chain, final String authType)
            throws CertificateException {
        // Everyone is trusted!
        acceptedIssuers_.addAll(Arrays.asList(chain));
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public X509Certificate[] getAcceptedIssuers() {
        // it seems to be OK for Java <= 6 to return an empty array but not for Java 7 (at least 1.7.0_04-b20):
        // requesting an URL with a valid certificate (working without WebClient.setUseInsecureSSL(true)) throws a
        //  javax.net.ssl.SSLPeerUnverifiedException: peer not authenticated
        // when the array returned here is empty
        if (acceptedIssuers_.isEmpty()) {
            return new X509Certificate[0];
        }
        return acceptedIssuers_.toArray(new X509Certificate[acceptedIssuers_.size()]);
    }
}