Wednesday, June 7, 2017

Enable TLS 1.2 & 1.1 Xamarin.Android JellyBean & KitKat



  TLS or Transport Layer Security is a cryptographic protocol that provides communication security over a computer network. If you develop for both iOS and Android you probably know that on iOS by default enforces TLS v1.2 for any application (that, for the moment, you can disable).
  When developing for iOS and Android using Xamarin you will probably end up using the HttpClient class. The default implementation of HttpClient on Mono only has built-in support for TLS v1.0. This is why, in order to use the newer versions of TLS, you would had to implement a class derived from HttpClientHandler that under the hood uses the native classes included in the platform that already support  newer TLS versions . The first choice, for a while, was the ModerHttpClient that NSUrlSession class on iOS and OkHttp on Android.
   Recently Xamarin added its own native HttpClientHandler in both iOS and Android. You can are free to switch from the fully managed one or the native one (which imho is recommended). To set the default implementation you will have to go in the project's settings dialog.

    There a small detail for the Android native implementation.  The Xamarin TLS document tells you that v1.2 is only supported for Android >= 5.0 . On the other side if you go to Wikipidia TLS Document you will see that TLS v.1.1 & v1.2 are supported by Android >= 4.1 but they are not enabled by default until Android 5.0.
  If you try to use TLS v1.1& 1.2 on an Android device version >4.0 & <5.0, as these protocols are not enabled by default you will get an "Connection closed by peer" exception.
     There is actually a simple solution for this problem: in order to enable TLS v1.1 & 1.2 in Android JellyBean & KitKat you will have to implement a class derived from SSLSocketFactory and enable the protocols and than change the factory that AndroidClientHandler uses by default. This can easily be achieved by using a implementing a class derived from  AndroidClientHandler where you will override the method SetupRequest and change the SSLSocketFactory with our own implementation that enables TLS v1.1 & 1.2 on the SSLSocket it creates:


  public class AndroidCustomClientHandler:AndroidClientHandler
    {
        CustomTlsSSLSocketFactory _customTlsSSLSocketFactory=new CustomTlsSSLSocketFactory();
        protected override System.Threading.Tasks.Task SetupRequest(System.Net.Http.HttpRequestMessage request, Java.Net.HttpURLConnection conn)
        {
            if (conn is HttpsURLConnection)
            {
                if (Android.OS.Build.VERSION.SdkInt < Android.OS.BuildVersionCodes.Lollipop)
                    //Enable support for TLS v1.2
                    ((HttpsURLConnection)conn).SSLSocketFactory = _customTlsSSLSocketFactory;
            }
            return base.SetupRequest(request, conn);
        } 
    }


where CustomTlsSSLSocketFactory is this:


public class CustomTlsSSLSocketFactory : SSLSocketFactory
 {
  readonly SSLSocketFactory factory = (SSLSocketFactory)Default;

  public override string[] GetDefaultCipherSuites()
  {
   return factory.GetDefaultCipherSuites();
  }

  public override string[] GetSupportedCipherSuites()
  {
   return factory.GetSupportedCipherSuites();
  }
  public override Java.Net.Socket CreateSocket(Java.Net.InetAddress address, int port, Java.Net.InetAddress localAddress, int localPort)
  {
   SSLSocket socket = (SSLSocket)factory.CreateSocket(address, port, localAddress, localPort);
   socket.SetEnabledProtocols(socket.GetSupportedProtocols());

   return socket;
  }

  public override Java.Net.Socket CreateSocket(Java.Net.InetAddress host, int port)
  {
   SSLSocket socket = (SSLSocket)factory.CreateSocket(host, port);
   socket.SetEnabledProtocols(socket.GetSupportedProtocols());

   return socket;
  }

  public override Java.Net.Socket CreateSocket(string host, int port, Java.Net.InetAddress localHost, int localPort)
  {
   SSLSocket socket = (SSLSocket)factory.CreateSocket(host, port, localHost, localPort);
   socket.SetEnabledProtocols(socket.GetSupportedProtocols());

   return socket;
  }

  public override Java.Net.Socket CreateSocket(string host, int port)
  {
   SSLSocket socket = (SSLSocket)factory.CreateSocket(host, port);
   socket.SetEnabledProtocols(socket.GetSupportedProtocols());

   return socket;
  }

  public override Java.Net.Socket CreateSocket(Java.Net.Socket s, string host, int port, bool autoClose)
  {
   SSLSocket socket = (SSLSocket)factory.CreateSocket(s, host, port, autoClose);
   socket.SetEnabledProtocols(socket.GetSupportedProtocols());

   return socket;
  }

  protected override void Dispose(bool disposing)
  {
   factory.Dispose();
   base.Dispose(disposing);
  }

  public override Java.Net.Socket CreateSocket()
  {
   SSLSocket socket = (SSLSocket)factory.CreateSocket();
   socket.SetEnabledProtocols(socket.GetSupportedProtocols());

   return socket;
  }
 }


You can see that I've chosen to enable all the supported protocols, but a better approach would be to enable just the ones that you actually use.

Hope you will find it useful!

I almost forgot the most important part: When you initialize your HttpClient you will have to pass an instance of your AndroidCustomClientHandler class:


 HttpClient _client = new HttpClient(new AndroidCustomClientHandler()) { BaseAddress = _baseAddr };