Wednesday, May 14, 2014

How to use Self-signed certificate in your Android app to communicate with the server

I have written a preliminary article to describe how SSL, Man-in-the-middle attack works plus what are the various options for you in case you want to use a self-signed certificate on your android app. In case you missed it, here's the link.

We have ODK-Collect that uses HTTPS connection to connect to a server and we were porting ODK to our OpenXdata server. ODK essentially doesn't allow HTTP connection to a server (unless they are on ports 443 or 8443). We also wanted to use SSL for security and for testing purposes we were using a self-signed certificate on the server. So lets begin with steps on how to get it to work on ODK-Collect (Please note that the same steps need to be followed in any other android app)

Step 1: Create your self-signed certificate and create .bks file

Use keytool to generate your key. It is in your Java bin path. In case, you already generated your keystore file, you can skip the first command.

keytool -genkey -alias handsrel -keystore ~/handsrelssl.keystore -validity 365

It creates a key with alias as handsrel, name of file as handsrelssl.keystore. It will ask for various things like passwords for the key and the keystore, the SSL details. Please note that Common name will be your hostname, in our case it was: handsrel.com

keytool -export -alias handsrel -keystore ~/handsrelssl.keystore -file ~/handsrelsslcert.cer

This command exports the key from .keystore file to .cer file.

keytool -import -alias handsrel -file ~/handsrelsslcert.cer -keystore ~/handsrelssl.bks -storetype BKS -providerClass org.bouncycastle.jce.provider.BouncyCastleProvider -providerpath ~/Downloads/bcprov-jdk15on-146.jar

Here, you need to download the Bouncy Castle Provider jar file and give the correct location of where the jar is located.
NOTE: I had issues with the latest BouncyCastle jar file, since it produced an error. Reverting back to previous version i.e. 146 worked for me. You may need to do the same.

For windows users, replace ~/ with something like C:\somedir\mykeystore.keystore etc.

Success! We have got the .bks file which we will put in our Android app, which will allow our app to communicate with our server which has the Self-signed SSL certificate.

Step 2: Code changes

First, we need to put our .bks file in /androidappdir/res/raw/

We will write a new class that we will call as MyHttpClient which will extend DefaultHttpClient. This class will load our own trust store to check the SSL certificates, rather than android default trust store. Only when the certificate here matches on the server, will it work correctly. Here's how it looks like:

package org.odk.collect.android.utilities;

import java.io.InputStream;
import java.security.KeyStore;

import org.odk.collect.android.R;
import org.odk.collect.android.activities.MainMenuActivity;
import org.opendatakit.httpclientandroidlib.conn.ClientConnectionManager;
import org.opendatakit.httpclientandroidlib.conn.scheme.PlainSocketFactory;
import org.opendatakit.httpclientandroidlib.conn.scheme.Scheme;
import org.opendatakit.httpclientandroidlib.conn.scheme.SchemeRegistry;
import org.opendatakit.httpclientandroidlib.conn.ssl.SSLSocketFactory;
import org.opendatakit.httpclientandroidlib.impl.client.DefaultHttpClient;
import org.opendatakit.httpclientandroidlib.impl.conn.SingleClientConnManager;
import org.opendatakit.httpclientandroidlib.params.HttpParams;

import android.content.Context;

public class MyHttpClient extends DefaultHttpClient {
  
 private static Context context;
 
 public static void setContext(Context context) {
  MyHttpClient.context = context;
 }

 public MyHttpClient(HttpParams params) {
  super(params);
 }

 public MyHttpClient(ClientConnectionManager httpConnectionManager, HttpParams params) {
  super(httpConnectionManager, params);
 }

 @Override
    protected ClientConnectionManager createClientConnectionManager() {
        SchemeRegistry registry = new SchemeRegistry();
        registry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
        // Register for port 443 our SSLSocketFactory with our keystore
        // to the ConnectionManager
        registry.register(new Scheme("https", newSslSocketFactory(), 443));
        return new SingleClientConnManager(getParams(), registry);
    }
 
    private SSLSocketFactory newSslSocketFactory() {
        try {
            // Get an instance of the Bouncy Castle KeyStore format
            KeyStore trusted = KeyStore.getInstance("BKS");
            // Get the raw resource, which contains the keystore with
            // your trusted certificates (root and any intermediate certs)
            InputStream in = MyHttpClient.context.getResources().openRawResource(R.raw.handsrelssl); //name of your keystore file here
            try {
                // Initialize the keystore with the provided trusted certificates
                // Provide the password of the keystore
                trusted.load(in, "YourKeystorePassword".toCharArray());
            } finally {
                in.close();
            }
            // Pass the keystore to the SSLSocketFactory. The factory is responsible
            // for the verification of the server certificate.
            SSLSocketFactory sf = new SSLSocketFactory(trusted);
            // Hostname verification from certificate
            // http://hc.apache.org/httpcomponents-client-ga/tutorial/html/connmgmt.html#d4e506
            sf.setHostnameVerifier(SSLSocketFactory.STRICT_HOSTNAME_VERIFIER); // This can be changed to less stricter verifiers, according to need
            return sf;
        } catch (Exception e) {
            throw new AssertionError(e);
        }
    }
}
On ODK, in the MainMenuActivity.java, we need to set the context. So the following statement should be defined in the onCreate() method:

MyHttpClient.setContext(getBaseContext());

Additionally, the WebUtils.java file is where the Http connections are created. Here you need to replace DefaultHttpClient with MyHttpClient everywhere.

You are now ready to run ODK-Collect with your server. In case you are using this technique in some other android app, you may write the code that you normally write for creating an Http connection, only replace DefaultHttpClient with MyHttpClient everywhere. Plus set the context on MyHttpClient. That's it.

If you have any questions or problems while implementing the same, please let me know in the comments. In case you are curious, you can try out our product Form Factor where we have used the same technique to connect our fork of ODK-collect to our server.

No comments:

Post a Comment