Created
January 16, 2015 00:01
-
-
Save hideki/fa362a590e4b96800762 to your computer and use it in GitHub Desktop.
Sample code how to set custom SSLSocketFactory for Couchbase Lite Android/Java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.couchbase.lite; | |
import android.net.SSLCertificateSocketFactory; | |
import android.os.Build; | |
import android.test.AndroidTestCase; | |
import android.util.Log; | |
import com.couchbase.lite.android.AndroidContext; | |
import com.couchbase.lite.replicator.Replication; | |
import com.couchbase.lite.support.CouchbaseLiteHttpClientFactory; | |
import com.couchbase.lite.support.PersistentCookieStore; | |
import org.apache.http.conn.ssl.SSLSocketFactory; | |
import org.apache.http.conn.ssl.StrictHostnameVerifier; | |
import org.apache.http.params.HttpParams; | |
import java.io.IOException; | |
import java.net.InetAddress; | |
import java.net.Socket; | |
import java.net.URL; | |
import java.security.KeyManagementException; | |
import java.security.KeyStore; | |
import java.security.KeyStoreException; | |
import java.security.NoSuchAlgorithmException; | |
import java.security.UnrecoverableKeyException; | |
import javax.net.ssl.HostnameVerifier; | |
import javax.net.ssl.SSLPeerUnverifiedException; | |
import javax.net.ssl.SSLSession; | |
import javax.net.ssl.SSLSocket; | |
/** | |
* Sample code how to set custom SSLSocketFactory for Couchbase Lite Android/Java | |
* | |
* https://github.com/couchbase/couchbase-lite-java-core/issues/330 | |
* Replication support for SNI (Server Name Indication) with https | |
*/ | |
public class Issue330Test extends AndroidTestCase { | |
public void testCustomHttpClientFactory() throws Exception{ | |
// create manager | |
Manager manager = new Manager(new AndroidContext(this.getContext()), Manager.DEFAULT_OPTIONS); | |
// create database | |
Database database = manager.getDatabase("test"); | |
// create CouchbaseLiteHttpClientFactory | |
PersistentCookieStore cookieStore = database.getPersistentCookieStore(); | |
CouchbaseLiteHttpClientFactory cblHttpClientfactory = new CouchbaseLiteHttpClientFactory(cookieStore); | |
// set custom SSLSocketFactory into CouchbaseHttpClientFactory | |
KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType()); | |
trustStore.load(null, null); | |
TlsSniSocketFactory socketFactory = new TlsSniSocketFactory(trustStore); | |
cblHttpClientfactory.setSSLSocketFactory(socketFactory); | |
// set CouchbaseHttpClientFactory to Manager | |
manager.setDefaultHttpClientFactory(cblHttpClientfactory); | |
// create replicator | |
URL url = new URL("https://example.com/test/"); | |
Replication pull = database.createPullReplication(url); | |
pull.setContinuous(true); | |
pull.start(); | |
} | |
/** | |
* https://github.com/Crazyphil/acra/blob/9a28add2971b4e6469b267c367ec5c00022b7715/src/main/java/org/acra/util/TlsSniSocketFactory.java | |
* | |
* Note: base class is changed to SSLSocketFactory from LayeredSocketFactory interface | |
*/ | |
public class TlsSniSocketFactory extends SSLSocketFactory { | |
private final String TAG = TlsSniSocketFactory.class.getSimpleName(); | |
HostnameVerifier hostnameVerifier = new StrictHostnameVerifier(); | |
public TlsSniSocketFactory(KeyStore truststore) throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException { | |
super(truststore); | |
} | |
// Plain TCP/IP (layer below TLS) | |
@Override | |
public Socket connectSocket(Socket s, String host, int port, InetAddress localAddress, int localPort, HttpParams params) throws IOException { | |
return null; | |
} | |
@Override | |
public Socket createSocket() throws IOException { | |
return null; | |
} | |
@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 plainSocket, String host, int port, boolean autoClose) throws IOException { | |
if (autoClose) { | |
// we don't need the plainSocket | |
plainSocket.close(); | |
} | |
// create and connect SSL socket, but don't do hostname/certificate verification yet | |
SSLCertificateSocketFactory sslSocketFactory = (SSLCertificateSocketFactory)SSLCertificateSocketFactory.getDefault(0); | |
SSLSocket ssl = (SSLSocket)sslSocketFactory.createSocket(InetAddress.getByName(host), port); | |
// enable TLSv1.1/1.2 if available | |
// (see https://github.com/rfc2822/davdroid/issues/229) | |
ssl.setEnabledProtocols(ssl.getSupportedProtocols()); | |
// set up SNI before the handshake | |
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { | |
Log.i(TAG, "Setting SNI hostname"); | |
sslSocketFactory.setHostname(ssl, host); | |
} else { | |
Log.d(TAG, "No documented SNI support on Android <4.2, trying with reflection"); | |
try { | |
java.lang.reflect.Method setHostnameMethod = ssl.getClass().getMethod("setHostname", String.class); | |
setHostnameMethod.invoke(ssl, host); | |
} catch (Exception e) { | |
Log.w(TAG, "SNI not useable", e); | |
} | |
} | |
// verify hostname and certificate | |
SSLSession session = ssl.getSession(); | |
if (!hostnameVerifier.verify(host, session)) | |
throw new SSLPeerUnverifiedException("Cannot verify hostname: " + host); | |
Log.i(TAG, "Established " + session.getProtocol() + " connection with " + session.getPeerHost() + | |
" using " + session.getCipherSuite()); | |
return ssl; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment