Adding cancellation to uploads (WIP)
[pub/Android/ownCloud.git] / src / com / owncloud / android / network / AdvancedSslSocketFactory.java
index 3c1d68e..755888f 100644 (file)
@@ -24,15 +24,19 @@ import java.net.InetSocketAddress;
 import java.net.Socket;
 import java.net.SocketAddress;
 import java.net.UnknownHostException;
+import java.security.cert.X509Certificate;
 
 import javax.net.SocketFactory;
 import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLException;
+import javax.net.ssl.SSLHandshakeException;
+import javax.net.ssl.SSLPeerUnverifiedException;
+import javax.net.ssl.SSLSession;
 import javax.net.ssl.SSLSocket;
 
 import org.apache.commons.httpclient.ConnectTimeoutException;
 import org.apache.commons.httpclient.params.HttpConnectionParams;
 import org.apache.commons.httpclient.protocol.ProtocolSocketFactory;
-import org.apache.commons.httpclient.protocol.SecureProtocolSocketFactory;
 import org.apache.http.conn.ssl.X509HostnameVerifier;
 
 import android.util.Log;
@@ -49,24 +53,32 @@ public class AdvancedSslSocketFactory implements ProtocolSocketFactory {
     private static final String TAG = AdvancedSslSocketFactory.class.getSimpleName();
     
     private SSLContext mSslContext = null;
-    private X509HostnameVerifier mHostnameVerifier;
+    private AdvancedX509TrustManager mTrustManager = null;
+    private X509HostnameVerifier mHostnameVerifier = null;
 
+    public SSLContext getSslContext() {
+        return mSslContext;
+    }
+    
     /**
      * Constructor for AdvancedSSLProtocolSocketFactory.
      */
-    public AdvancedSslSocketFactory(SSLContext sslContext, X509HostnameVerifier hostnameVerifier) {
+    public AdvancedSslSocketFactory(SSLContext sslContext, AdvancedX509TrustManager trustManager, X509HostnameVerifier hostnameVerifier) {
         if (sslContext == null)
             throw new IllegalArgumentException("AdvancedSslSocketFactory can not be created with a null SSLContext");
+        if (trustManager == null)
+            throw new IllegalArgumentException("AdvancedSslSocketFactory can not be created with a null Trust Manager");
         mSslContext = sslContext;
+        mTrustManager = trustManager;
         mHostnameVerifier = hostnameVerifier;
     }
 
     /**
-     * @see SecureProtocolSocketFactory#createSocket(java.lang.String,int,java.net.InetAddress,int)
+     * @see ProtocolSocketFactory#createSocket(java.lang.String,int,java.net.InetAddress,int)
      */
     public Socket createSocket(String host, int port, InetAddress clientHost, int clientPort) throws IOException, UnknownHostException {
         Socket socket = mSslContext.getSocketFactory().createSocket(host, port, clientHost, clientPort);
-        verifyHostname(host, socket);
+        verifyPeerIdentity(host, port, socket);
         return socket;
     }
 
@@ -94,7 +106,7 @@ public class AdvancedSslSocketFactory implements ProtocolSocketFactory {
         Log.d(TAG, "Creating SSL Socket with remote " + host + ":" + port + ", local " + localAddress + ":" + localPort + ", params: " + params);
         if (params == null) {
             throw new IllegalArgumentException("Parameters may not be null");
-        }
+        } 
         int timeout = params.getConnectionTimeout();
         SocketFactory socketfactory = mSslContext.getSocketFactory();
         Log.d(TAG, " ... with connection timeout " + timeout + " and socket timeout " + params.getSoTimeout());
@@ -104,31 +116,21 @@ public class AdvancedSslSocketFactory implements ProtocolSocketFactory {
         socket.setSoTimeout(params.getSoTimeout());
         socket.bind(localaddr);
         socket.connect(remoteaddr, timeout);
-        verifyHostname(host, socket);
+        verifyPeerIdentity(host, port, socket);
         return socket;
     }
 
     /**
-     * @see SecureProtocolSocketFactory#createSocket(java.lang.String,int)
+     * @see ProtocolSocketFactory#createSocket(java.lang.String,int)
      */
     public Socket createSocket(String host, int port) throws IOException,
             UnknownHostException {
         Log.d(TAG, "Creating SSL Socket with remote " + host + ":" + port);
         Socket socket = mSslContext.getSocketFactory().createSocket(host, port);
-        verifyHostname(host, socket);
+        verifyPeerIdentity(host, port, socket);
         return socket; 
     }
 
-    /**
-     * @see SecureProtocolSocketFactory#createSocket(java.net.Socket,java.lang.String,int,boolean)
-     */
-    /*public Socket createSocket(Socket socket, String host, int port,
-            boolean autoClose) throws IOException, UnknownHostException {
-        Log.d(TAG, "Creating SSL Socket from other shocket " + socket + " to remote " + host + ":" + port);
-        return getSSLContext().getSocketFactory().createSocket(socket, host,
-                port, autoClose);
-    }*/
-
     public boolean equals(Object obj) {
         return ((obj != null) && obj.getClass().equals(
                 AdvancedSslSocketFactory.class));
@@ -149,20 +151,90 @@ public class AdvancedSslSocketFactory implements ProtocolSocketFactory {
     }
     
     /**
-     * Verifies the host name with the content of the server certificate using the current host name verifier, if some
+     * Verifies the identity of the server. 
+     * 
+     * The server certificate is verified first.
+     * 
+     * Then, the host name is compared with the content of the server certificate using the current host name verifier, if any.
      * @param socket
      */
-    private void verifyHostname(String host, Socket socket) throws IOException {
-        if (mHostnameVerifier != null) {
+    private void verifyPeerIdentity(String host, int port, Socket socket) throws IOException {
+        try {
+            CertificateCombinedException failInHandshake = null;
+            /// 1. VERIFY THE SERVER CERTIFICATE through the registered TrustManager (that should be an instance of AdvancedX509TrustManager) 
             try {
-                mHostnameVerifier.verify(host, (SSLSocket) socket);
-            } catch (IOException iox) {
-                try {
-                    socket.close();
-                } catch (Exception x) {}
-                throw iox;
+                SSLSocket sock = (SSLSocket) socket;    // a new SSLSession instance is created as a "side effect" 
+                sock.startHandshake();
+                
+            } catch (RuntimeException e) {
+                
+                if (e instanceof CertificateCombinedException) {
+                    failInHandshake = (CertificateCombinedException) e;
+                } else {
+                    Throwable cause = e.getCause();
+                    Throwable previousCause = null;
+                    while (cause != null && cause != previousCause && !(cause instanceof CertificateCombinedException)) {
+                        previousCause = cause;
+                        cause = cause.getCause();
+                    }
+                    if (cause != null && cause instanceof CertificateCombinedException) {
+                        failInHandshake = (CertificateCombinedException)cause;
+                    }
+                }
+                if (failInHandshake == null) {
+                    throw e;
+                }
+                failInHandshake.setHostInUrl(host);
+                
             }
+            
+            /// 2. VERIFY HOSTNAME
+            SSLSession newSession = null;
+            boolean verifiedHostname = true;
+            if (mHostnameVerifier != null) {
+                if (failInHandshake != null) {
+                    /// 2.1 : a new SSLSession instance was NOT created in the handshake
+                    X509Certificate serverCert = failInHandshake.getServerCertificate();
+                    try {
+                        mHostnameVerifier.verify(host, serverCert);
+                    } catch (SSLException e) {
+                        verifiedHostname = false;
+                    }
+                
+                } else {
+                    /// 2.2 : a new SSLSession instance was created in the handshake
+                    newSession = ((SSLSocket)socket).getSession();
+                    if (!mTrustManager.isKnownServer((X509Certificate)(newSession.getPeerCertificates()[0]))) {
+                        verifiedHostname = mHostnameVerifier.verify(host, newSession); 
+                    }
+                }
+            }
+
+            /// 3. Combine the exceptions to throw, if any
+            if (!verifiedHostname) {
+                SSLPeerUnverifiedException pue = new SSLPeerUnverifiedException("Names in the server certificate do not match to " + host + " in the URL");
+                if (failInHandshake == null) {
+                    failInHandshake = new CertificateCombinedException((X509Certificate) newSession.getPeerCertificates()[0]);
+                    failInHandshake.setHostInUrl(host);
+                }
+                failInHandshake.setSslPeerUnverifiedException(pue);
+                pue.initCause(failInHandshake);
+                throw pue;
+                
+            } else if (failInHandshake != null) {
+                SSLHandshakeException hse = new SSLHandshakeException("Server certificate could not be verified");
+                hse.initCause(failInHandshake);
+                throw hse;
+            }
+            
+        } catch (IOException io) {        
+            try {
+                socket.close();
+            } catch (Exception x) {
+                // NOTHING - irrelevant exception for the caller 
+            }
+            throw io;
         }
     }
-    
-}
\ No newline at end of file
+
+}