Cocoaphony

Stop mutating, evolve

Pinning Your SSL Certs

Short version: If you want to pin your SSL certs easily, go get RNPinnedCertValidator.

If your app uses SSL to communicate with your server (and it should), you generally don’t need to trust every certificate that Apple trusts. You should just trust the specific certificate of your server, or maybe your own root signing certificate. But there’s certainly no reason to trust the over 200 certificates in iOS 7’s root store.

The practice of trusting only your own certificate is called “pinning,” and I’ve discussed it several times at conferences. I then say something like, “It’s easy. You just do this:”

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
...
    NSError *error = NULL;
    NSString *path = [[NSBundle mainBundle] pathForResource:@"www.google.com"
                                                     ofType:@"cer"];
    NSData *certData = [NSData dataWithContentsOfFile:path
                                              options:0
                                                error:&error];

    if (! certData) {
      // Handle error reading
    }

    SecCertificateRef
    certificate = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certData);

    if (!certificate) {
      // Handle error parsing
    }

    self.anchors = [NSArray arrayWithObject:CFBridgingRelease(certificate)];
...

- (void)connection:(NSURLConnection *)connection
willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {

  SecTrustRef trust = challenge.protectionSpace.serverTrust;

  SecTrustSetAnchorCertificates(trust, (__bridge CFArrayRef)self.anchors);
  SecTrustSetAnchorCertificatesOnly(trust, true);

  SecTrustResultType result;
  OSStatus status = SecTrustEvaluate(trust, &result);
  if (status == errSecSuccess &&
      (result == kSecTrustResultProceed ||
       result == kSecTrustResultUnspecified)) {

    NSURLCredential *cred = [NSURLCredential credentialForTrust:trust];
    [challenge.sender useCredential:cred forAuthenticationChallenge:challenge];
  }
  else {
    [challenge.sender cancelAuthenticationChallenge:challenge];
  }
}

I mean, really, what could be simpler. OK, maybe it could be simpler. Maybe if it were a little simpler, everyone would do it. So how about this?

1
2
3
4
5
6
7
- (void)connection:(NSURLConnection *)connection
willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {
  NSString *path = [[NSBundle mainBundle] pathForResource:@"mycert"
                                                   ofType:@"cer"];
  RNPinnedCertValidator *validator = [[RNPinnedCertValidator alloc] initWithCertificatePath:path];
  [validator validateChallenge:challenge];
}

Is that simple enough? Then go grab RNPinnedCertValidator.