Android – TLS/SSL Mutual Authentication

November 24th, 2011 by Marcus Krantz

Due to the explosion of smart phones on the market, the need for exposing existing enterprise systems through the mobile channel is growing rapidly. One of the first questions that will come up is how we can establish a secure communication channel with the existing enterprise system.

In this article, I will cover both how to trust a server certificate for secure communication with the server, as well as providing a client certificate to the server for mutual authentication. The client certificate is bundled with the app and of course, the server needs to trust this certificate.

Setup

Before we can start, keys and certificates need to be in place. My article “Creating self-signed certificates for use on Android” covers how to create keys and certificates as well as importing them into supported key stores. If you don’t have any keys or certificates, create the required files by reading that article.

Android and self-signed certificates

I assume that you already figured out how to create a simple Android app. However, I do not assume that you have put clienttruststore.bks or client.bks in your Android project’s res/raw directory. Before you continue, make sure the two files are there. These files were created in “Creating self-signed certificates for use on Android” but you can replace them with your own, just make sure that your keystores uses the Bouncy Castle Provider.

Implementation

This section covers how you can implement a custom HttpClient by registering a scheme for https communication and load a keystore and a truststore into a SSLSocketFactory.

Extend HttpClient

Android uses Apache Commons HttpClient but since we want communicate securely we must extend the DefaultHttpClient with some custom behavior (load our keystores). Therefore, we start by creating SecureHttpClient.java and extend the HttpClient.

public class SecureHttpClient extends DefaultHttpClient {
    private int securePort;

    public SecureHttpClient(final int port) {
        this.securePort = port;
    }
}

Simply put, we need to do two things to get our mutual authentication to work. 1) Create an SSLSocketFactory where we load our keystores and 2) Register a scheme for https communication that uses our custom SSLSocketFactory. This method will load our keystores.

Create SSLSocketFactory and load the keystores

Extend the SecureHttpClient with the two new methods found below. If you do not want to use mutual authentication you can just load the trust store in which the server’s certificate is. The resulting SSLSocketFactory will later be used when creating a scheme for https-communication.

private SSLSocketFactory createSSLSocketFactory(final Context context) {
    Log.d(TAG, "Creating SSL socket factory");

    final KeyStore truststore = this.loadStore(context.getResources().openRawResource(R.raw.clienttruststore, "password", "BKS");
    final KeyStore keystore = this.loadStore(context.getResources().openRawResource(R.raw.client, "password", "BKS");

    return this.createFactory(keystore, this.keystorePassword, truststore);
}

private SSLSocketFactory createFactory(final KeyStore keystore, final String keystorePassword, final KeyStore truststore) {

    SSLSocketFactory factory;
    try {
        factory = new SSLSocketFactory(keystore, this.getKeystorePassword(), truststore);
        factory.setHostnameVerifier((X509HostnameVerifier) SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
    } catch (Exception e) {
        Log.e(TAG, "Caught exception when trying to create ssl socket factory. Reason: " + e.getMessage());
        throw new RuntimeException(e);
    }

    return factory;
}

We now have a method that loads our keystores and return an SSLSocketFactory.

Register a scheme for https

Before we can communicate with our server, we must register a scheme for https communication that uses our SSLSocketFactory. The createConnectionManager() in HttpClient allows us to register our scheme. Override the createConnectionManager() method and provide the following implementation:

@Override
protected ClientConnectionManager createClientConnectionManager() {

    Log.d(TAG, "Creating client connection manager");

    final SchemeRegistry registry = new SchemeRegistry();

    Log.d(TAG, "Adding https scheme for port " + securePort);
    registry.register(new Scheme("https", this.createSSLSocketFactory(), this.securePort));

    return new SingleClientConnManager(getParams(), registry);
}

That’s about it. We now have the SecureHttpClient class that we can use from our Android app to communicate over Https.

Using the Secure client

It is time to use our SecureHttpClient and make a call to a web server. Since this is just a test, we can basically make the call from anywhere in the app and I choose to make it in an Activity’s onCreate()-method. The test server I used is a simple Jetty server (jetty-maven-plugin) where I added configuration for SSL containing a trust store and the server’s certificate. You can see how a simple server with SSL can be setup in the article: “Quick Start – Jetty, Maven and SSL”.

To make a https call to the server, simply provide an implementation like the one below:

@Override
public void onCreate(Bundle savedInstanceState) {
    final HttpClient client = new SecureHttpClient(443);

    // Provide ip or address to where your test server is runnning
    final HttpGet request = new HttpGet("https://192.168.0.10:8443/example-server")
    final HttpResponse response = client.execute(request);

    Log.d("ExampleActivity", "Response code: " + response.getStatusLine().getStatusCode());
}

21 Responses to “Android – TLS/SSL Mutual Authentication”

  1. Kate says:

    Hi, Marcus! What API for HttpClient did you use in the example above? I have tried a lot of HttpClient jars, but I can’t find the appropriate HttpClient class, which contains createClientConnectionManager() method. Also jars downloaded from http://hc.apache.org/httpclient-3.x/ site contain only HttpClient interface.
    Thanks.

  2. Marcus says:

    Hi Kate, Android uses v4 of Apache’s HttpClient. Here is a link to the documentation: http://hc.apache.org/httpcomponents-client-ga/index.html

    /Marcus

  3. Hi Marcus

    Great post BTW

    I’m not sure if your example could build since Apache HttpClient is an interface both at Android (http://developer.android.com/reference/org/apache/http/client/HttpClient.html) and Apache (http://hc.apache.org/httpcomponents-client-ga/httpclient/apidocs/index.html)

    Could you share the source of this project (at least this class :)

    Thank you.

  4. Marcus Krantz says:

    Hi Nabil,
    I’m glad you appreciate it! Instead of using the interface use the DefaultHttpClient class. I will update the post with correct information. Thanks a lot for pointing it out.

    /Marcus

  5. Sirack says:

    Hi Marcus,

    What a great post.
    One question. What would happen if the certificate on the server expires or be updated? Shouldn’t the app update its resources? Would it still be valid? This could be a real problem if you ship the certificate with the app. Users may be forced to update the app.

    Thanks for attention.

  6. Marcus Krantz says:

    Since the certificate is bundled within the apk, you will need to do a new release with the updated certificate if it expires, or if the server certificate gets updated.

  7. Rajeev says:

    Hi Marcus,
    Great Post. I followed the example of “Creating Self-signed certificates” and then, this one to achieve the https functionality but I’m not able to load the Keystore. It gives “java.io.IOException: Wrong version of key store.”

    here is what I’m doing for loading it
    try {
    keystore = KeyStore.getInstance(“BKS”);
    InputStream ksStream = context.getResources().openRawResource(R.raw.client);
    try {
    keystore.load(ksStream, KEYSTORE_PWD.toCharArray());
    } finally {
    ksStream.close();
    }
    } catch (Exception e) {
    throw new AssertionError(e);
    }

    Is it something to do with Java and BouncyCastle version? I was trying with the latest bouncycastle, namely “bcprov-ext-jdk15on-147.jar” and JDK1.6/JRE6. Can you give pointers for this issue?

    Thanks

  8. Marcus Krantz says:

    Hi Rajeev,
    When I wrote the post I used Bouncy Castle Provider 1.46 for JDK 1.6 but that file does not seem to be available any longer. However, can you list the contents of your keystore using:
    keytool -list -v -keystore client.bks -storetype bks -providerpath

  9. Rajeev says:

    oh great…! it says BKS not found (while creating the bks file it didn’t give any error). Here is the detailed content report

    keytool error: java.security.KeyStoreException: BKS not found
    java.security.KeyStoreException: BKS not found
    at java.security.KeyStore.getInstance(KeyStore.java:587)
    at sun.security.tools.KeyTool.doCommands(KeyTool.java:594)
    at sun.security.tools.KeyTool.run(KeyTool.java:172)
    at sun.security.tools.KeyTool.main(KeyTool.java:166)
    Caused by: java.security.NoSuchAlgorithmException: BKS KeyStore not available
    at sun.security.jca.GetInstance.getInstance(GetInstance.java:142)
    at java.security.Security.getImpl(Security.java:659)
    at java.security.KeyStore.getInstance(KeyStore.java:584)
    … 3 more

    Thanks,
    Rajeev

  10. Philipp says:

    @Rajeev: I face the same problem, but got a step further. I guess you have to add “-provider org.bouncycastle.jce.provider.BouncyCastleProvider” to the keytool -list command to list the contents of your created store.

    It looks all good for me, but I get the “java.io.IOException: Wrong version of key store.” on the keyStore.load() call :(

  11. iKhan says:

    @Philipp: Even i am facing same issue. Did you found solution for the “java.io.IOException: Wrong version of key store” on keyStore.load() call ??

    I am using Bounty castle provider 1.47 and jdk 1.7.

  12. Anil says:

    In response to iKhan, Philipp, and Rajeev: you need to run:

    keytool -list -v -keystore mykeystore.bks -storetype bks -providerpath bcprov-jdk15on-147.jar -provider org.bouncycastle.jce.provider.BouncyCastleProvider

  13. Fock says:

    I found the solution for the “Wrong version of key store” : You juste need the bcpov-jdk16-145.jar . I don’t know why, but it is not on BouncyCastle website, and none of those which are proposed there are working.

    So download this version : http://www.java2s.com/Code/Jar/b/Downloadbcprovjdk16145jar.htm

    And it should be working.

  14. Fock says:

    I forgot to say that I’m using jre6.

  15. mail80 says:

    @Fock: Could please explain your java setup.

    I have tried all java version available on my PC with various versions of bouncy providers(.jars) still results in “java.io.IOException: Wrong version of key store” on calling keyStore.load()

    Also the “keytool list…. ” confirms that cert added to .bks file. I’m using galaxy s-I9000(Froyo-2.2) and galaxy s-I9100(GB-2.3.4)

    Java version:
    0. /usr/lib/jvm/java-6-openjdk/jre/bin/java
    1. /usr/lib/jvm/java-1.5.0-sun/jre/bin/java
    2. /usr/lib/jvm/java-6-openjdk/jre/bin/java
    3. /usr/lib/jvm/java-6-sun/jre/bin/java

    bouncy providers(jars):
    1. bcprov-jdk15-134.jar
    2. bcprov-jdk15on-146.jar
    3. bcprov-jdk15on-147.jar
    4. bcprov-jdk16-145.jar

    one more thing, how do you check the bks version on the device.

    Thanks in advance

  16. Ravi Teja N says:

    Hi,

    Thanks for the good post.

    I am using apache server can you please tell me where i need to place the servertruststore.jks and server.jks . I mean in the which folder.

    Thanks for advance

  17. Marcus Krantz says:

    Hi,
    You will need to config mod_ssl for your Apache httpd instance.
    http://httpd.apache.org/docs/2.2/mod/mod_ssl.html

    More specifically the
    SSLCertificateFile directive
    SSLCertificateKeyFile directive
    SSLCACertificatePath directive

    However since Apache does not work directly with Java Keystore’s you need to have your certificates in PEM-format.

  18. Jack says:

    Hi Marcus, good tutorial. This might not be related to your post, but I’m just wondering you have any idea to make this work with SOAP web services ? or particularly android ksoap2 library? I managed to establish a connection via https but cant seem to request SOAP from a web services.

  19. Tee says:

    Hi

    Am new to android application and have being trying to implement the sample code you gave but not working yet. Have created the bks and jks. my first question is how do you specifiy two jks keystore in the server side? and second question is should i implemet the above code with my main activity?
    Thanks

  20. Paul says:

    Unfortunately the code is incomplete and you cut things out; that’s a pity because the tutorial is actually good. Where is the “loadStore” function defined? You even cut out some brackets in the code that is using thit function.

  21. Marcus Krantz says:

    Hi Paul,
    Here is an implementation of the loadstore()-method.

    
    public KeyStore loadStore(InputStream stream, String keystorePw) throws Exception {
      try {
        trustStore.load(stream, keystorePw.toCharArray());
      }
      finally {
        instream.close();
      }
      return trustStore;
    }
    

Leave a Reply