diff --git a/src/main/java/org/mitre/dsmiley/httpproxy/ProxyServlet.java b/src/main/java/org/mitre/dsmiley/httpproxy/ProxyServlet.java index 0ee7c04f..2b6694df 100644 --- a/src/main/java/org/mitre/dsmiley/httpproxy/ProxyServlet.java +++ b/src/main/java/org/mitre/dsmiley/httpproxy/ProxyServlet.java @@ -30,6 +30,7 @@ import org.apache.http.client.utils.URIUtils; import org.apache.http.entity.InputStreamEntity; import org.apache.http.impl.client.DefaultHttpClient; +import org.apache.http.impl.client.HttpClientBuilder; import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager; import org.apache.http.message.BasicHeader; import org.apache.http.message.BasicHttpEntityEnclosingRequest; @@ -168,6 +169,7 @@ protected void initTarget() throws ServletException { * SystemDefaultHttpClient uses PoolingClientConnectionManager. In any case, it should be thread-safe. */ @SuppressWarnings({"unchecked", "deprecation"}) protected HttpClient createHttpClient(HttpParams hcParams) { + /* try { //as of HttpComponents v4.2, this class is better since it uses System // Properties: @@ -182,6 +184,10 @@ protected HttpClient createHttpClient(HttpParams hcParams) { //Fallback on using older client: return new DefaultHttpClient(new ThreadSafeClientConnManager(), hcParams); + */ + + return HttpClientBuilder.create().useSystemProperties().disableCookieManagement().disableRedirectHandling() + .setSSLSocketFactory(SNISSLSocketFactory.createFromSystem()).build(); } /** The http client used. diff --git a/src/main/java/org/mitre/dsmiley/httpproxy/SNISSLSocketFactory.java b/src/main/java/org/mitre/dsmiley/httpproxy/SNISSLSocketFactory.java new file mode 100644 index 00000000..ac291851 --- /dev/null +++ b/src/main/java/org/mitre/dsmiley/httpproxy/SNISSLSocketFactory.java @@ -0,0 +1,123 @@ +/* + * Copyright MITRE + * + * 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 org.mitre.dsmiley.httpproxy; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.http.HttpHost; +import org.apache.http.conn.ssl.SSLConnectionSocketFactory; +import org.apache.http.conn.ssl.X509HostnameVerifier; +import org.apache.http.protocol.HttpContext; +import org.apache.http.util.TextUtils; + +import javax.net.ssl.SSLParameters; +import javax.net.ssl.SSLSocket; +import javax.net.ssl.SSLSocketFactory; +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.util.ArrayList; +import java.util.List; + +/** + * An SSL Socket Factory supporting SNI, + * at least on the Sun/Oracle JDK. {@link SSLConnectionSocketFactory} was introduced in + * HttpClient 4.3; previously this was possible using + * {@link org.apache.http.conn.ssl.SSLSocketFactory} which is deprecated. + */ +public class SNISSLSocketFactory extends SSLConnectionSocketFactory { + /** Use commons-logging because that's what HttpClient uses (no new dependencies). */ + private final Log log = LogFactory.getLog(getClass()); + + public static SNISSLSocketFactory createFromSystem() { + // See HttpClientBuilder.build when it creates an SSLSocketFactory when systemProperties==true + return new SNISSLSocketFactory( + (SSLSocketFactory) SSLSocketFactory.getDefault(), + split(System.getProperty("https.protocols")), + split(System.getProperty("https.cipherSuites")), + null);//hostnameVerifier will default + } + +// public SNISSLSocketFactory(SSLSocketFactory sslSocketFactory, +// String[] supportedProtocols, String[] supportedCipherSuites, +// HostnameVerifier hostnameVerifier) { +// super(sslSocketFactory, supportedProtocols, supportedCipherSuites, hostnameVerifier) +// } + +// copy of HttpClientBuilder.split + private static String[] split(String s) { + return TextUtils.isBlank(s) ? null : s.split(" *, *"); + } + + // note: the constructors of our superclass are all either introduced in v4.4 or are + // v4.4+ + + /** Note: We support HttpClient v4.3 so we must use a deprecated constructor. */ + @SuppressWarnings({"deprecation"}) + public SNISSLSocketFactory(SSLSocketFactory sslSocketFactory, + String[] supportedProtocols, String[] supportedCipherSuites, + X509HostnameVerifier hostnameVerifier) { + super(sslSocketFactory, supportedProtocols, supportedCipherSuites, hostnameVerifier); + } + + + /** + * {@inheritDoc} + */ + @Override + public Socket createSocket(HttpContext context) throws IOException { + return SSLSocketFactory.getDefault().createSocket(); + } + + @Override + public Socket connectSocket( + int connectTimeout, + Socket socket, + HttpHost host, + InetSocketAddress remoteAddress, + InetSocketAddress localAddress, + HttpContext context) throws IOException { + // For SNI support, we call setHost(hostname) on the socket. But this method isn't part of + // the SNLSocket JDK class; it's on a Sun/Oracle implementation class: + // sun.security.ssl.SSLSocketImpl So we invoke it via reflection. + /* + try { + socket.getClass().getDeclaredMethod("setHost", String.class) + .invoke(socket, host.getHostName()); + } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException ex) { + log.debug("Couldn't invoke setHost on " + socket.getClass() + " for SNI support.", ex); + } + */ + if (socket instanceof SSLSocket) { + + // see https://docs.oracle.com/javase/8/docs/technotes/guides/security/jsse/JSSERefGuide.html#SNIExtension + + final SSLSocket sslSocket = (SSLSocket) socket; + final javax.net.ssl.SNIHostName serverName = new javax.net.ssl.SNIHostName(host.getHostName()); + final List serverNames = new ArrayList<>(1); + serverNames.add(serverName); + + final SSLParameters params = sslSocket.getSSLParameters(); + params.setServerNames(serverNames); + sslSocket.setSSLParameters(params); + } + + return super.connectSocket(connectTimeout, socket, host, remoteAddress, + localAddress, context); + } +}