Self-Signed Certificates and Xamarin.Android

Recently, I had the opportunity to work with Android and self-signed certificates. This is both simple and complex at the same time. In order for Android to be able to handle self-signed certificates, those certificates have to be registered with the SSLSocketFactory.

The HTTP Client

What we will need to do is create a new SSLSocketFactory, and pass that to the HTTP client. In this case, we are using OkHttpClient to communicate over the network. OkHttp is an HTTP+SPDY client for Android applications, and can be found:

Once we have the socket factory, we pass it to the OkHttpClient using the SetSslSocketFactory method:

// get the socket factory
var socketFactory = GetSocketFactory();
// create a new OkHttpClinet 
var client = new OkHttpClient();
// add the socket factory to the client
client.SetSslSocketFactory(socketFactory);
// now we can use the client as usual
Request request = new Request.Builder().Url("https://<yourserver>").Build();
Response response = await client.NewCall(request).ExecuteAsync();
var body = await response.Body().StringAsync();

SSL The Socket Factory

In order to create the socket factory, we will need the self-signed certificate. This can be downloaded from the website, or obtained from the source. Then, we must include it in the app. In this instance, I have added it as a raw resource. Loading the certificate into the socket factory consists of a few steps:

  1. Load the certificate out of the resources/assets and into a Certificate instance
  2. Create a new KeyStore instance, and add the Certificate
  3. Create a new TrustManagerFactory instance from the KeyStore
  4. Get the IX509TrustManager from the TrustManagerFactory
  5. Create the new SSLContext, and initialize with the IX509TrustManager
  6. Get the SocketFactory from the SSLContext

Here is the code that does this:

/// <summary>
/// This method returns the configured SSLSocketFactory that contains
/// the self-signed certificate.
/// </summary>
public static SSLSocketFactory GetSocketFactory()
{
    // Load our certificate from resources (we created this one using OpenSSL and 
    // saved as a .cer using Windows' certlm console)
    var certificateFactory = CertificateFactory.GetInstance("X.509");
    Certificate certificate;
    using (var stream = Application.Context.Resources.OpenRawResource(Resource.Raw.selfsigned)) {
      certificate = certificateFactory.GenerateCertificate(stream);
    }

    // Create a KeyStore containing our trusted CAs
    var keyStore = KeyStore.GetInstance(KeyStore.DefaultType);
    keyStore.Load(null, null);
    keyStore.SetCertificateEntry("ca", certificate);

    // Create a TrustManager that trusts the CAs in our keystore
    var algorithm = TrustManagerFactory.DefaultAlgorithm;
    var trustManagerFactory = TrustManagerFactory.GetInstance(algorithm);
    trustManagerFactory.Init(keyStore);
    var trustManagers = trustManagerFactory.GetTrustManagers();
    var trustManager = trustManagers[0].JavaCast<IX509TrustManager>(); 

    // Create an SSLContext that uses our TrustManager
    var context = SSLContext.GetInstance("TLSv1.2");
    context.Init(null, new ITrustManager[]{ new CompleteX509TrustManager(trustManager) }, null);    

    // return the final socket factory
    return context.SocketFactory;
}

The Trust Manager

Because we are usinmg a custom trust manager with a custom keystore that only contsins the one self-signed certificate, all other certificates will be rejected. To avoid this, we wrap the trust manager in a new trust manager that first tries the default trust manager. Only when the default trust manager fails to verify the certificate, we try the custom trust manager:

/// <summary>
/// This trust manager wraps a custom socket factory and provides a
/// fallback to the default trust manager with the system certificates.
/// This allows the app to communicate not only with a self-signed 
/// server, but also servers with certificates from a CA.
/// </summary>
public class CompleteX509TrustManager : Java.Lang.Object, IX509TrustManager
{
    private readonly IX509TrustManager defaultTrustManager;
    private readonly IX509TrustManager localTrustManager;

    public CompleteX509TrustManager(IX509TrustManager localTrustManager)
    {
        this.localTrustManager = localTrustManager;

        var algorithm = TrustManagerFactory.DefaultAlgorithm;
        var defaultTrustManagerFactory = TrustManagerFactory.GetInstance(algorithm);
        defaultTrustManagerFactory.Init((KeyStore)null);
        var trustManagers = trustManagerFactory.GetTrustManagers();
        defaultTrustManager = trustManagers[0].JavaCast<IX509TrustManager>();
    }

    public void CheckClientTrusted(X509Certificate[] chain, string authType)
    {
        // we are the client
    }

    public void CheckServerTrusted(X509Certificate[] chain, string authType)
    {
        try {
            defaultTrustManager.CheckServerTrusted(chain, authType);
        } catch (CertificateException) {
            localTrustManager.CheckServerTrusted(chain, authType);
        }
    }

    public X509Certificate[] GetAcceptedIssuers()
    {
        // we are not the server
        return null;
    }
}

Some of the namespaces used are in the Java and Javax namespaces:

using Java.Interop;
using Java.Net;
using Java.Security;
using Java.Security.Cert;
using Javax.Net.Ssl;

One thought on “Self-Signed Certificates and Xamarin.Android

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s