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;
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;
}
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());
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));
}
/**
- * 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
+
+}