Skip to content

Instantly share code, notes, and snippets.

@hideki
Created January 16, 2015 00:01
Show Gist options
  • Save hideki/fa362a590e4b96800762 to your computer and use it in GitHub Desktop.
Save hideki/fa362a590e4b96800762 to your computer and use it in GitHub Desktop.
Sample code how to set custom SSLSocketFactory for Couchbase Lite Android/Java
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