PUBLIC OBJECT

Pinning SSL Certificates

I was planning two TLS code samples for OkHttp's recipes page. One to make development easier by disabling certificate checks and another to make deployment more secure by pinning TLS certificates.

Disabling Certificate Checks

It's a hassle to get TLS going on a local development server. Disabling certificate checking is unfortunate, but productive. The most important part about disabling security in development is remembering to re-enable it for production! And to test that yes, it really is secure in production.

Pinning Certificates

Disabling certificate checks trades generally-unnecessary security for developer productivity. Pinning certificates is the opposite: spend more time setting it up, and get even more security.

The extra security you gain is defense against rogue certificate authorities. For example, in 2011 DigiNotar signed fraudulent certificates for google.com, leaving web users vulnerable to man-in-the-middle attacks.

I wrote up a fun little program that captures the server's SSL certificate and generates copy-and-pasteable code with your pinned certificate:

  /**
   * Pinned certificate chain for google.com.
   * Expires Tue Aug 19 20:00:00 EDT 2014.
   */
  public static final SSLContext SSL_CONTEXT_GOOGLE_COM_EXPIRES_20140819 
      = new SslContextBuilder()
      // CN=*.google.com, O=Google Inc, L=Mountain View, ST=California
      .addCertificate(""
          + "M2DNSYlXEjlHxLfN23DIk7xnThH4RTk4bhhtWtBTyR9G5aOCBBkwgg"
          + "MIIGgTCCBWmgAwIBAgIIoSeucwDQYJKoZIhvcNAwSTELMAkGA1UEBh"
          + ...
          + "FdDynG494MhWpmLdY5xzOWqb7+xmPdo3947O2Mg==")
      // CN=GeoTrust Global CA, O=GeoTrust Inc., C=US
      .addCertificate(""
          + "MIIDfTCCAuagAwIBAgID0GCSqGEBBQUAME4xCzAJBgNVBAYTAlVTMR"
          + "uXNBSqltLroxwUCEm2u+ravHNjkOR/ez4iyz0H7V84dJzjA1BOoa+Y"
          + ...
          + "7mHyhD8S")
      .build();

Copy this into your program and you get a pinned certificate. No Bouncy Castle jar files, no shell scripts, no getResourceAsStream() nonsense.

Unfortunately, the code is a time bomb.

TLS certificates have expiration dates. If your pinned certificate expires, it doesn't matter that your server has been updated. Any clients sitting on the stale pinned certificates are broken. You'll wake up one day and discover that your app doesn't work. You need to ship an updated APK to everyone Right Fucking Now because your app refuses to trust your webserver's updated certificate.

Many teams are diligent and have processes that prevent this kind of emergency from occurring. To those teams: please pin your certificates! You can use SSLContextBuilder.java to do so.

For everyone else, please turn on HTTPS. And relax, knowing that it's somebody else's job to babysit the certificates.

Update

Nikolay Elenkov pointed me at another approach with most of the benefits and few of the drawbacks.