From 1f9898495624dbd10d5c12ae4e0ea9ea50071c85 Mon Sep 17 00:00:00 2001 From: Michael Locher Date: Thu, 25 Oct 2012 02:35:14 +0200 Subject: [PATCH] HTTPCLIENT-1119: support SNI on Java 7 via #setHost of SSLSocketImpl --- .../org/apache/http/conn/ssl/SSLSocketFactory.java | 82 ++++++++++++++++++++++ 1 file changed, 82 insertions(+) diff --git a/httpclient/src/main/java/org/apache/http/conn/ssl/SSLSocketFactory.java b/httpclient/src/main/java/org/apache/http/conn/ssl/SSLSocketFactory.java index 644b196..e0b2da4 100644 --- a/httpclient/src/main/java/org/apache/http/conn/ssl/SSLSocketFactory.java +++ b/httpclient/src/main/java/org/apache/http/conn/ssl/SSLSocketFactory.java @@ -50,6 +50,9 @@ import javax.net.ssl.X509TrustManager; import java.io.File; import java.io.FileInputStream; import java.io.IOException; +import java.lang.ref.WeakReference; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.Socket; @@ -63,6 +66,7 @@ import java.security.NoSuchProviderException; import java.security.SecureRandom; import java.security.UnrecoverableKeyException; import java.security.cert.CertificateException; +import java.util.concurrent.atomic.AtomicReference; /** * Layered socket factory for TLS/SSL connections. @@ -569,6 +573,7 @@ public class SSLSocketFactory implements SchemeLayeredSocketFactory, } if (this.hostnameVerifier != null) { try { + HostNameSetter.setServerNameIndication(hostname, sslsock); this.hostnameVerifier.verify(hostname, sslsock); // verifyHostName() didn't blowup - good! } catch (IOException iox) { @@ -580,6 +585,83 @@ public class SSLSocketFactory implements SchemeLayeredSocketFactory, return sslsock; } + /** + * Uses the underlying implementation to support Server Name Indication (SNI). + * @author Michael Locher + */ + private static class HostNameSetter { + + private static final AtomicReference CURRENT = new AtomicReference(); + + private final WeakReference> cls; + private final WeakReference setter; + + private HostNameSetter(Class clazz, Method setter) { + this.cls = new WeakReference>(clazz); + this.setter = setter == null ? null : new WeakReference(setter); + } + + private static Method init(Class cls) { + Method s = null; + try { + s = cls.getMethod("setHost", String.class); + } catch (SecurityException e) { + initFail(e); + } catch (NoSuchMethodException e) { + initFail(e); + } + CURRENT.set(new HostNameSetter(cls, s)); + return s; + } + + private static void initFail(Exception e) { + // ignore + } + + private Method reuse(Class cls) { + final boolean wrongClass = this.cls.get() != cls; + if (wrongClass) { + return init(cls); + } + + final boolean setterNotSupported = this.setter == null; + if (setterNotSupported) { + return null; + } + + final Method s = setter.get(); + final boolean setterLost = s == null; + return setterLost ? init(cls) : s; + } + + /** + * Invokes the {@code #setName(String)} method if one is present. + * + * @param hostname the name to set + * @param sslsock the socket + */ + public static void setServerNameIndication(String hostname, SSLSocket sslsock) { + final Class cls = sslsock.getClass(); + final HostNameSetter current = CURRENT.get(); + final Method setter = (current == null) ? init(cls) : current.reuse(cls); + if (setter != null) { + try { + setter.invoke(sslsock, hostname); + } catch (IllegalArgumentException e) { + setServerNameIndicationFail(e); + } catch (IllegalAccessException e) { + setServerNameIndicationFail(e); + } catch (InvocationTargetException e) { + setServerNameIndicationFail(e); + } + } + } + + private static void setServerNameIndicationFail(Exception e) { + // ignore + } + } + /** * Checks whether a socket connection is secure. -- 1.7.12.3