Enable Server Name Indication in HTTPS connections if available in the network implementation under the API

This commit is contained in:
David A. Velasco 2013-11-25 14:43:03 +01:00
parent 59f806f692
commit 7ed9f29973
2 changed files with 194 additions and 3 deletions

View file

@ -24,12 +24,15 @@ import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketAddress;
import java.net.UnknownHostException;
//import java.security.Provider;
import java.security.cert.X509Certificate;
//import java.util.Enumeration;
import javax.net.SocketFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLHandshakeException;
//import javax.net.ssl.SSLParameters;
import javax.net.ssl.SSLPeerUnverifiedException;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocket;
@ -39,6 +42,7 @@ import org.apache.commons.httpclient.params.HttpConnectionParams;
import org.apache.commons.httpclient.protocol.ProtocolSocketFactory;
import org.apache.http.conn.ssl.X509HostnameVerifier;
//import android.os.Build;
import android.util.Log;
@ -84,8 +88,47 @@ public class AdvancedSslSocketFactory implements ProtocolSocketFactory {
return socket;
}
/*
private void logSslInfo() {
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.FROYO) {
Log.v(TAG, "SUPPORTED SSL PARAMETERS");
logSslParameters(mSslContext.getSupportedSSLParameters());
Log.v(TAG, "DEFAULT SSL PARAMETERS");
logSslParameters(mSslContext.getDefaultSSLParameters());
Log.i(TAG, "CURRENT PARAMETERS");
Log.i(TAG, "Protocol: " + mSslContext.getProtocol());
}
Log.i(TAG, "PROVIDER");
logSecurityProvider(mSslContext.getProvider());
}
/**
private void logSecurityProvider(Provider provider) {
Log.i(TAG, "name: " + provider.getName());
Log.i(TAG, "version: " + provider.getVersion());
Log.i(TAG, "info: " + provider.getInfo());
Enumeration<?> keys = provider.propertyNames();
String key;
while (keys.hasMoreElements()) {
key = (String) keys.nextElement();
Log.i(TAG, " property " + key + " : " + provider.getProperty(key));
}
}
private void logSslParameters(SSLParameters params) {
Log.v(TAG, "Cipher suites: ");
String [] elements = params.getCipherSuites();
for (int i=0; i<elements.length ; i++) {
Log.v(TAG, " " + elements[i]);
}
Log.v(TAG, "Protocols: ");
elements = params.getProtocols();
for (int i=0; i<elements.length ; i++) {
Log.v(TAG, " " + elements[i]);
}
}
*/
/**
* Attempts to get a new socket connection to the given host within the
* given time limit.
*
@ -110,6 +153,9 @@ public class AdvancedSslSocketFactory implements ProtocolSocketFactory {
throw new IllegalArgumentException("Parameters may not be null");
}
int timeout = params.getConnectionTimeout();
//logSslInfo();
SocketFactory socketfactory = mSslContext.getSocketFactory();
Log.d(TAG, " ... with connection timeout " + timeout + " and socket timeout " + params.getSoTimeout());
Socket socket = socketfactory.createSocket();
@ -117,12 +163,13 @@ public class AdvancedSslSocketFactory implements ProtocolSocketFactory {
SocketAddress remoteaddr = new InetSocketAddress(host, port);
socket.setSoTimeout(params.getSoTimeout());
socket.bind(localaddr);
ServerNameIndicator.setServerNameIndication(host, (SSLSocket)socket);
socket.connect(remoteaddr, timeout);
verifyPeerIdentity(host, port, socket);
return socket;
}
/**
/**
* @see ProtocolSocketFactory#createSocket(java.lang.String,int)
*/
public Socket createSocket(String host, int port) throws IOException,
@ -238,5 +285,5 @@ public class AdvancedSslSocketFactory implements ProtocolSocketFactory {
throw io;
}
}
}

View file

@ -0,0 +1,144 @@
/* ownCloud Android client application
* Copyright (C) 2012-2013 ownCloud Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.owncloud.android.oc_framework.network;
import java.lang.ref.WeakReference;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.concurrent.atomic.AtomicReference;
import javax.net.ssl.SSLSocket;
import android.util.Log;
/**
* Enables the support of Server Name Indication if existing
* in the underlying network implementation.
*
* Build as a singleton.
*
* @author David A. Velasco
*/
public class ServerNameIndicator {
private static final String TAG = ServerNameIndicator.class.getSimpleName();
private static final AtomicReference<ServerNameIndicator> mSingleInstance = new AtomicReference<ServerNameIndicator>();
private static final String METHOD_NAME = "setHostname";
private final WeakReference<Class<?>> mSSLSocketClassRef;
private final WeakReference<Method> mSetHostnameMethodRef;
/**
* Private constructor, class is a singleton.
*
* @param sslSocketClass Underlying implementation class of {@link SSLSocket} used to connect with the server.
* @param setHostnameMethod Name of the method to call to enable the SNI support.
*/
private ServerNameIndicator(Class<?> sslSocketClass, Method setHostnameMethod) {
mSSLSocketClassRef = new WeakReference<Class<?>>(sslSocketClass);
mSetHostnameMethodRef = (setHostnameMethod == null) ? null : new WeakReference<Method>(setHostnameMethod);
}
/**
* Calls the {@code #setHostname(String)} method of the underlying implementation
* of {@link SSLSocket} if exists.
*
* Creates and initializes the single instance of the class when needed
*
* @param hostname The name of the server host of interest.
* @param sslSocket Client socket to connect with the server.
*/
public static void setServerNameIndication(String hostname, SSLSocket sslSocket) {
final Method setHostnameMethod = getMethod(sslSocket);
if (setHostnameMethod != null) {
try {
setHostnameMethod.invoke(sslSocket, hostname);
Log.i(TAG, "SNI done, hostname: " + hostname);
} catch (IllegalArgumentException e) {
Log.e(TAG, "Call to SSLSocket#setHost(String) failed ", e);
} catch (IllegalAccessException e) {
Log.e(TAG, "Call to SSLSocket#setHost(String) failed ", e);
} catch (InvocationTargetException e) {
Log.e(TAG, "Call to SSLSocket#setHost(String) failed ", e);
}
} else {
Log.i(TAG, "SNI not supported");
}
}
/**
* Gets the method to invoke trying to minimize the effective
* application of reflection.
*
* @param sslSocket Instance of the SSL socket to use in connection with server.
* @return Method to call to indicate the server name of interest to the server.
*/
private static Method getMethod(SSLSocket sslSocket) {
final Class<?> sslSocketClass = sslSocket.getClass();
final ServerNameIndicator instance = mSingleInstance.get();
if (instance == null) {
return initFrom(sslSocketClass);
} else if (instance.mSSLSocketClassRef.get() != sslSocketClass) {
// the underlying class changed
return initFrom(sslSocketClass);
} else if (instance.mSetHostnameMethodRef == null) {
// SNI not supported
return null;
} else {
final Method cachedSetHostnameMethod = instance.mSetHostnameMethodRef.get();
return (cachedSetHostnameMethod == null) ? initFrom(sslSocketClass) : cachedSetHostnameMethod;
}
}
/**
* Singleton initializer.
*
* Uses reflection to extract and 'cache' the method to invoke to indicate the desited host name to the server side.
*
* @param sslSocketClass Underlying class providing the implementation of {@link SSLSocket}.
* @return Method to call to indicate the server name of interest to the server.
*/
private static Method initFrom(Class<?> sslSocketClass) {
Log.i(TAG, "SSLSocket implementation: " + sslSocketClass.getCanonicalName());
Method setHostnameMethod = null;
try {
setHostnameMethod = sslSocketClass.getMethod(METHOD_NAME, String.class);
} catch (SecurityException e) {
Log.e(TAG, "Could not access to SSLSocket#setHostname(String) method ", e);
} catch (NoSuchMethodException e) {
Log.i(TAG, "Could not find SSLSocket#setHostname(String) method - SNI not supported");
}
mSingleInstance.set(new ServerNameIndicator(sslSocketClass, setHostnameMethod));
return setHostnameMethod;
}
}