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:
- In the NuGet Gallery – Square.OkHttp
- In the Xamarin Component Store – Square.OkHttp
- As source on GitHub – Square-Bindings
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:
- Load the certificate out of the resources/assets and into a
Certificate
instance - Create a new
KeyStore
instance, and add theCertificate
- Create a new
TrustManagerFactory
instance from theKeyStore
- Get the
IX509TrustManager
from theTrustManagerFactory
- Create the new
SSLContext
, and initialize with theIX509TrustManager
- Get the
SocketFactory
from theSSLContext
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;