From a7a483144d466d151ab646523a22af47de10bc18 Mon Sep 17 00:00:00 2001 From: Jorge Ruesga Date: Fri, 28 Mar 2014 00:08:51 +0100 Subject: [PATCH] email: add support for Server Name Indication (SNI) Change the underlaying socket factory reference to SSLCertificateSocketFactory which has support for SNI since 4.2, and remove the access to the obsolete methods from the old factory reference. This change will setup the socket with a proper hostname prior to the ssl handshake. Change-Id: Ie537b1d8c3da33de3665e481320be134939155ca Signed-off-by: Jorge Ruesga --- emailcommon/Android.mk | 2 +- .../emailcommon/utility/SSLSocketFactory.java | 76 +++++++++++-------- 2 files changed, 44 insertions(+), 34 deletions(-) diff --git a/emailcommon/Android.mk b/emailcommon/Android.mk index 654e20d60..14ea55078 100644 --- a/emailcommon/Android.mk +++ b/emailcommon/Android.mk @@ -42,7 +42,7 @@ LOCAL_SRC_FILES += $(call all-java-files-under, $(apache_src_dir)) LOCAL_SRC_FILES += $(imported_unified_email_files) LOCAL_SRC_FILES += $(call all-java-files-under, $(unified_email_src_dir)/com/android/emailcommon) -LOCAL_SDK_VERSION := 14 +LOCAL_SDK_VERSION := 17 LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res diff --git a/emailcommon/src/com/android/emailcommon/utility/SSLSocketFactory.java b/emailcommon/src/com/android/emailcommon/utility/SSLSocketFactory.java index b7a59b81d..433cef870 100644 --- a/emailcommon/src/com/android/emailcommon/utility/SSLSocketFactory.java +++ b/emailcommon/src/com/android/emailcommon/utility/SSLSocketFactory.java @@ -33,6 +33,10 @@ package com.android.emailcommon.utility; +import android.annotation.TargetApi; +import android.net.SSLCertificateSocketFactory; +import android.os.Build; + import org.apache.http.conn.scheme.HostNameResolver; import org.apache.http.conn.scheme.LayeredSocketFactory; import org.apache.http.conn.ssl.AllowAllHostnameVerifier; @@ -42,7 +46,6 @@ import org.apache.http.conn.ssl.X509HostnameVerifier; import org.apache.http.params.HttpConnectionParams; import org.apache.http.params.HttpParams; -import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.KeyManager; import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; @@ -156,21 +159,9 @@ public class SSLSocketFactory implements LayeredSocketFactory { public static final X509HostnameVerifier STRICT_HOSTNAME_VERIFIER = new StrictHostnameVerifier(); - /** - * The factory using the default JVM settings for secure connections. - */ - private static final SSLSocketFactory DEFAULT_FACTORY = new SSLSocketFactory(); - - /** - * Gets an singleton instance of the SSLProtocolSocketFactory. - * @return a SSLProtocolSocketFactory - */ - public static SSLSocketFactory getSocketFactory() { - return DEFAULT_FACTORY; - } private final SSLContext sslcontext; - private final javax.net.ssl.SSLSocketFactory socketfactory; + private final SSLCertificateSocketFactory socketfactory; private final HostNameResolver nameResolver; private X509HostnameVerifier hostnameVerifier = BROWSER_COMPATIBLE_HOSTNAME_VERIFIER; @@ -197,7 +188,7 @@ public class SSLSocketFactory implements LayeredSocketFactory { } sslcontext = SSLContext.getInstance(algorithm); sslcontext.init(keymanagers, trustmanagers, random); - socketfactory = sslcontext.getSocketFactory(); + socketfactory = (SSLCertificateSocketFactory) sslcontext.getSocketFactory(); this.nameResolver = nameResolver; } @@ -226,25 +217,13 @@ public class SSLSocketFactory implements LayeredSocketFactory { * Constructs an HttpClient SSLSocketFactory backed by the given JSSE * SSLSocketFactory. */ - public SSLSocketFactory(javax.net.ssl.SSLSocketFactory socketfactory) { + public SSLSocketFactory(SSLCertificateSocketFactory socketfactory) { super(); sslcontext = null; this.socketfactory = socketfactory; nameResolver = null; } - /** - * Creates the default SSL socket factory. - * This constructor is used exclusively to instantiate the factory for - * {@link #getSocketFactory getSocketFactory}. - */ - private SSLSocketFactory() { - super(); - sslcontext = null; - socketfactory = HttpsURLConnection.getDefaultSSLSocketFactory(); - nameResolver = null; - } - private static KeyManager[] createKeyManagers(final KeyStore keystore, final String password) throws KeyStoreException, NoSuchAlgorithmException, UnrecoverableKeyException { if (keystore == null) { @@ -280,6 +259,7 @@ public class SSLSocketFactory implements LayeredSocketFactory { // non-javadoc, see interface org.apache.http.conn.SocketFactory @Override + @TargetApi(17) public Socket connectSocket( final Socket sock, final String host, @@ -323,6 +303,12 @@ public class SSLSocketFactory implements LayeredSocketFactory { sslsock.connect(remoteAddress, connTimeout); sslsock.setSoTimeout(soTimeout); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { + // Turn on Server Name Indication (SNI) + socketfactory.setHostname(sslsock, host); + } + try { hostnameVerifier.verify(host, sslsock); // verifyHostName() didn't blowup - good! @@ -374,19 +360,43 @@ public class SSLSocketFactory implements LayeredSocketFactory { // non-javadoc, see interface LayeredSocketFactory @Override + @TargetApi(17) public Socket createSocket( final Socket socket, final String host, final int port, final boolean autoClose ) throws IOException, UnknownHostException { + // Close the plain socket if requested. The underlaying socket factory will + // create a new socket. + if (autoClose) { + socket.close(); + } + + // We don't want to verify the hostname from the previous socket here (we must call + // setHostname in order to proper get SNI working), so just create a new ssl socket + // based in the previous socket SSLSocket sslSocket = (SSLSocket) socketfactory.createSocket( - socket, - host, - port, - autoClose + socket.getInetAddress(), + port ); - hostnameVerifier.verify(host, sslSocket); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { + // Turn on Server Name Indication (SNI) + socketfactory.setHostname(sslSocket, host); + } + + try { + hostnameVerifier.verify(host, sslSocket); + // verifyHostName() didn't blowup - good! + } catch (IOException iox) { + // close the socket before re-throwing the exception + if (autoClose) { + try { sslSocket.close(); } catch (Exception x) { /*ignore*/ } + } + throw iox; + } + // verifyHostName() didn't blowup - good! return sslSocket; }