Skip to content

On SSL Pinning for Cocoa [Touch]

Moxie Marlinspike, recently-acquired security boffin at Twitter, blogged about SSL pinning. The summary is that relying on the CA trust model to validate SSL certificates introduces some risk into using an app – there are hundreds of trusted roots in an operating system like iOS, and you don’t necessarily want to trust all (or even any) of the keyholders. Where you’re connecting to a specific server under your control, you don’t need anyone else to tell you the server’s identity: you know what server you need to use, you should just look for its certificate. Then it doesn’t matter if someone compromises any CA; you’re not trusting the CAs any more. He calls this SSL pinning, and it’s something I’ve recommended to Fuzzy Aliens clients over the past year. I thought it’d be good to dig into how you do SSL pinning on Mac OS X and iOS.

The first thing you need to do is to tell Foundation not to evaluate the server certificate itself, but to pass the certificate to you for checking. You do this by telling the NSURLConnection that its delegate can authenticate in the “server trust” protection space.

-(BOOL)connection:(NSURLConnection *)connection canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)space {
  return [[space authenticationMethod] isEqualToString: NSURLAuthenticationMethodServerTrust];
}

Now your NSURLConnection delegate will receive an authentication challenge when the SSL connection is negotiated. In this authentication challenge, you evaluate the server trust to discover the certificate chain, then look for your certificate on the chain. Because you know exactly what certificate you’re looking for, you can do a bytewise comparison and don’t need to do anything like checking the common name or extracting the fingerprint: it either is your certificate or it isn’t. In the case below, I look only at the leaf certificate, and I assume that the app has a copy of the server’s cert in the sealed app bundle at MyApp.app/Contents/Resources/servername.example.com.cer.


- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {
  if ([[[challenge protectionSpace] authenticationMethod] isEqualToString: NSURLAuthenticationMethodServerTrust]) {
    SecTrustRef serverTrust = [[challenge protectionSpace] serverTrust];
    (void) SecTrustEvaluate(serverTrust, NULL);
    NSData *localCertificateData = [NSData dataWithContentsOfFile: [[NSBundle mainBundle]
                                                                    pathForResource: serverName
                                                                    ofType: @"cer"]];
    SecCertificateRef remoteVersionOfServerCertificate = SecTrustGetCertificateAtIndex(serverTrust, 0);
    CFDataRef remoteCertificateData = SecCertificateCopyData(remoteVersionOfServerCertificate);
    BOOL certificatesAreTheSame = [localCertificateData isEqualToData: (__bridge NSData *)remoteCertificateData];
    CFRelease(remoteCertificateData);
    if (certificatesAreTheSame) {
      [[challenge sender] useCredential: [NSURLCredential credentialForTrust: serverTrust] forAuthenticationChallenge: challenge];
    }
    else {
      [[challenge sender] cancelAuthenticationChallenge: challenge];
    }
  }
  // fall through for challenges in other spaces - or respond to them if you need to
}

That’s really all there is to it. You may want to change some of the details depending on your organisation: for example you may issue your own intermediate or root certificate and check for its presence while allowing the leaf certificate to vary; however the point is to get away from the SSL certificate authority trust model so I haven’t shown that here.

{ 6 } Comments

  1. Carlton Gibson | December 7, 2011 at 7:31 pm | Permalink

    Graham, awesome thanks.

    Silly question but… what stops Evil Spoofer from cracking open my app’s bundle, getting my cert and putting it on their server?

    (they still have to divert my request to their server but…)

  2. Carlton Gibson | December 7, 2011 at 8:14 pm | Permalink

    No, don’t worry…

    (Cert is used to generate key pair so unless they’ve also got my private key I’m fine.)

    Thanks anyhow!

  3. Graham | December 8, 2011 at 8:48 am | Permalink

    @Carlton that’s it. You still need to keep your private key confidential. However, compare this to the case where you rely on CA trust: you need to keep your private key confidential; and you need EVERY TRUSTED CA to do so too. Any of the trusted roots on the platform can, if compromised, issue a certificate to someone that looks like it’s a valid cert for your organisation. So can any intermediate signing certificate countersigned by any of those trusted roots.

  4. Chris | December 8, 2011 at 9:23 am | Permalink

    The other downside is that when you replace your server’s SSL cert (how long does it last – 1 year?) you need to update your app. All old versions of the app will break.

  5. Graham | December 19, 2011 at 11:37 am | Permalink

    Conversely, all you have to do when you replace your server’s SSL cert is update the app. The certificate lifetime is configurable, but if you are in complete control of the end-to-end use of the certificate you can plan for this well in advance. In the case of accidental certificate death (i.e. loss of the private key) you can push a new update out and automatically everyone distrusts the existing cert: no reliance on kludges like CRLs or OCSP. Also in the common case of app store delivery, the delivery route is separate from the data delivery so that if your private key is compromised the attacker can’t subvert the code delivery mechanism.

  6. Jules | January 12, 2012 at 12:58 am | Permalink

    What if you wanted to do proper public key pinning a-la http://www.imperialviolet.org/2011/05/04/pinning.html ? Is there a way to get the DER encoded SubjectPublicKeyInfo from a SecCertificateRef? You can get a SecKeyRef, but I don’t see how to get the actual bytes.

Switch to our mobile site