package com.buddycloud.http; import java.io.IOException; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.Socket; import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.SSLException; import javax.net.ssl.SSLPeerUnverifiedException; import javax.net.ssl.SSLSession; import javax.net.ssl.SSLSocket; import org.apache.http.conn.scheme.LayeredSocketFactory; import org.apache.http.params.HttpParams; import android.annotation.TargetApi; import android.net.SSLCertificateSocketFactory; import android.os.Build; import com.buddycloud.log.Logger; @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1) public class TLSSNISocketFactory implements LayeredSocketFactory { private static final String TAG = "buddycloud.SNISocketFactory"; // "insecure" means that it doesn't verify the host name // we will do this ourselves so we can set up SNI before SSLCertificateSocketFactory sslSocketFactory = (SSLCertificateSocketFactory) SSLCertificateSocketFactory.getInsecure(0, null); // Plain TCP/IP (layer below TLS) @Override public Socket connectSocket(Socket s, String host, int port, InetAddress localAddress, int localPort, HttpParams params) throws IOException { s.connect(new InetSocketAddress(host, port)); return s; } @Override public Socket createSocket() { Socket s = new Socket(); return s; } @Override public boolean isSecure(Socket s) throws IllegalArgumentException { if (s instanceof SSLSocket) return ((SSLSocket)s).isConnected(); return false; } // TLS layer @Override public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException { SSLSocket ssl = (SSLSocket)sslSocketFactory.createSocket(s, host, port, autoClose); // set SNI before the handshake if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { Logger.info(TAG, "Setting SNI hostname"); sslSocketFactory.setHostname(ssl, host); } else { Logger.warn(TAG, "No SNI support below Android 4.2!"); } // now do the TLS handshake ssl.startHandshake(); SSLSession session = ssl.getSession(); if (session == null) throw new SSLException("Cannot verify SSL socket without session"); // verify host name (important!) if (!HttpsURLConnection.getDefaultHostnameVerifier().verify(host, session)) throw new SSLPeerUnverifiedException("Cannot verify hostname: " + host); return ssl; } }