Browse Source

Merge pull request #321 from owncloud/sni_support_based_on_network_implementation_built_in

Sni support based on network implementation built in
masensio 11 years ago
parent
commit
a9da7583d6

+ 1 - 1
AndroidManifest.xml

@@ -113,7 +113,7 @@
         <service
             android:name=".syncadapter.FileSyncService"
             android:exported="true" 
-            android:process=":sync">
+            >
             <intent-filter>
                 <action android:name="android.content.SyncAdapter" />
             </intent-filter>

+ 50 - 3
oc_framework/src/com/owncloud/android/oc_framework/network/AdvancedSslSocketFactory.java

@@ -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;
         }
     }
-
+    
 }

+ 144 - 0
oc_framework/src/com/owncloud/android/oc_framework/network/ServerNameIndicator.java

@@ -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;
+	}
+
+}