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;
import org.apache.commons.httpclient.protocol.ProtocolSocketFactory;
import org.apache.http.conn.ssl.X509HostnameVerifier;
+//import android.os.Build;
import android.util.Log;
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.
*
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();
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,
throw io;
}
}
-
+
}
--- /dev/null
+/* 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;
+ }
+
+}