Skip to content

Instantly share code, notes, and snippets.

@mattleibow
Last active January 19, 2017 18:52
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mattleibow/c8abfa323db094b820cc to your computer and use it in GitHub Desktop.
Save mattleibow/c8abfa323db094b820cc to your computer and use it in GitHub Desktop.
Using TLS 1.2 on old Androids as well as demonstrating certificate pinning
using Android.App;
using Android.OS;
using Java.Net;
using Java.Security;
using Java.Security.Cert;
using Javax.Net.Ssl;
using Square.OkHttp;
using System.Collections.Generic;
using Java.Interop;
namespace OkHttpExceptions
{
// Usage:
//
// // if we want to use the defaults
// SSLManager.SetupDefault()
//
// // if we want to use the client with certificate from an unknown CA
// SSLManager.SetupUnknownAuthority()
//
// // if we want to use the client with our self-signed certificate
// SSLManager.SetupSelfSigned()
//
// // if we want to use the allow-all client
// SSLManager.SetupDangerous()
//
// // now we get the client
// var client = SSLManager.GetHttpClient();
//
public static class SSLManager
{
private static IHostnameVerifier hostnameVerifier;
private static SSLSocketFactory socketFactory;
/// <summary>
/// Setups the default socket factory using the system certificates
/// </summary>
public static void SetupDefault ()
{
// apply the default context
Setup (HttpsURLConnection.DefaultSSLSocketFactory, null);
}
/// <summary>
/// Setups the socket factory to accept a certificate from custom
/// CA (https://certs.cac.washington.edu/CAtest/) as well as any
/// default certificates.
/// </summary>
public static void SetupUnknownAuthority ()
{
// Load our certificate from resources (this one was downloaded from https://certs.cac.washington.edu/CAtest)
var certificateFactory = CertificateFactory.GetInstance ("X.509");
Certificate certificate;
using (var stream = Application.Context.Resources.OpenRawResource (Resource.Raw.washington)) {
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 trustManagerFactory = TrustManagerFactory.GetInstance (TrustManagerFactory.DefaultAlgorithm);
trustManagerFactory.Init (keyStore);
// Create an SSLContext that uses our TrustManager
var context = SSLContext.GetInstance ("TLSv1.2");
context.Init (null, trustManagerFactory.GetTrustManagers (), null);
// apply the new context
Setup (context.SocketFactory, null);
}
/// <summary>
/// Setups the socket factory to accept our self-signed certificate as well as
/// any default certificates.
/// </summary>
public static void SetupSelfSigned ()
{
// 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 trustManagerFactory = TrustManagerFactory.GetInstance (TrustManagerFactory.DefaultAlgorithm);
trustManagerFactory.Init (keyStore);
var trustManager = trustManagerFactory.GetTrustManagers () [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);
// apply the new context
Setup (context.SocketFactory, null);
}
/// <summary>
/// Sets up the socket factory and hostname verifier to allow all
/// certificates for all servers.
/// </summary>
public static void SetupDangerous ()
{
// we want to use a new trust manager (high risk!)
var context = SSLContext.GetInstance ("TLSv1.2");
context.Init (null, new ITrustManager[]{ new NullX509TrustManager () }, new SecureRandom ());
// apply the new context
Setup (context.SocketFactory, new NullHostnameVerifier ());
}
/// <summary>
/// Sets up the socket factory and the hostname verifier for the next time a
/// client is requested.
/// </summary>
private static void Setup (SSLSocketFactory factory, IHostnameVerifier verifier)
{
// create our custom socket factory to handle TLS v1.2 on older devices
// although we can actually use it on any Android version as it is just
// a proxy class that makes sure all supported protocols are enabled
if (Android.OS.Build.VERSION.SdkInt < BuildVersionCodes.Lollipop) {
socketFactory = new CompleteSSLSocketFactory (factory);
} else {
socketFactory = factory;
}
// set the hostname verifer
hostnameVerifier = verifier;
}
/// <summary>
/// Provides an OkHttpClient that is set up with the requested
/// socket factory and hostname provider (if any).
/// </summary>
public static OkHttpClient GetHttpClient ()
{
// create a new client
var client = new OkHttpClient ();
// add the socket factory
client.SetSslSocketFactory (socketFactory);
// make sure we use the hostname verifier
if (hostnameVerifier != null) {
client.SetHostnameVerifier (hostnameVerifier);
}
return client;
}
private class CompleteSSLSocketFactory : SSLSocketFactory
{
private readonly SSLSocketFactory innerFactory;
public CompleteSSLSocketFactory (SSLSocketFactory innerFactory)
{
this.innerFactory = innerFactory;
}
public override string[] GetDefaultCipherSuites ()
{
return innerFactory.GetDefaultCipherSuites ();
}
public override string[] GetSupportedCipherSuites ()
{
return innerFactory.GetSupportedCipherSuites ();
}
public override Socket CreateSocket ()
{
return MakeSocketSafe (innerFactory.CreateSocket ());
}
public override Socket CreateSocket (Socket s, string host, int port, bool autoClose)
{
return MakeSocketSafe (innerFactory.CreateSocket (s, host, port, autoClose));
}
public override Socket CreateSocket (string host, int port)
{
return MakeSocketSafe (innerFactory.CreateSocket (host, port));
}
public override Socket CreateSocket (string host, int port, InetAddress localHost, int localPort)
{
return MakeSocketSafe (innerFactory.CreateSocket (host, port, localHost, localPort));
}
public override Socket CreateSocket (InetAddress host, int port)
{
return MakeSocketSafe (innerFactory.CreateSocket (host, port));
}
public override Socket CreateSocket (InetAddress address, int port, InetAddress localAddress, int localPort)
{
return MakeSocketSafe (innerFactory.CreateSocket (address, port, localAddress, localPort));
}
private Socket MakeSocketSafe (Socket socket)
{
var sslSocket = socket as SSLSocket;
if (sslSocket != null) {
// enable all supported protocols for this socket
sslSocket.SetEnabledProtocols (sslSocket.GetSupportedProtocols ());
sslSocket.SetEnabledCipherSuites (sslSocket.GetSupportedCipherSuites ());
}
return socket;
}
}
/// <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>
private class CompleteX509TrustManager : Java.Lang.Object, IX509TrustManager
{
private readonly IX509TrustManager defaultTrustManager;
private readonly IX509TrustManager localTrustManager;
public CompleteX509TrustManager (IX509TrustManager localTrustManager)
{
this.localTrustManager = localTrustManager;
var defaultTrustManagerFactory = TrustManagerFactory.GetInstance (TrustManagerFactory.DefaultAlgorithm);
defaultTrustManagerFactory.Init ((KeyStore)null);
defaultTrustManager = defaultTrustManagerFactory.GetTrustManagers () [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;
}
}
/// <summary>
/// This trust manager treats all certificates as valid, without doing
/// any checks.
/// </summary>
private class NullX509TrustManager : Java.Lang.Object, IX509TrustManager
{
public void CheckClientTrusted (X509Certificate[] chain, string authType)
{
// we are the client
}
public void CheckServerTrusted (X509Certificate[] chain, string authType)
{
// don't do any verification
// all certificates are valid
}
public X509Certificate[] GetAcceptedIssuers ()
{
// we are not the server
return null;
}
}
/// <summary>
/// This hostname verifier permits all host names to accessed, even if
/// there is no valid certificate for it.
/// </summary>
private class NullHostnameVerifier : Java.Lang.Object, IHostnameVerifier
{
public bool Verify (string hostname, ISSLSession session)
{
// everything goes through
// all host names are valid
return true;
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment