Monthly Archives: February 2014

SSL Pinning for NSURLSession

One thing that really bothers me when I review or test apps is that they are so easy to subvert using an SSL Man-in-the-Middle attack.   I’m no security expert (although luckily I count one as a good friend - )

This is a good start – but it doesn’t take into account iOS7’s new NSURLSession API.

The first thing to do with NSURLSession is to implement the NSURLSessionDelegate protocol.  Even if you plan on using the NSURLSession block helper methods, having a class in your project to manages all the calls to NSURLSession makes sense and and enables you to implement that NSURLSessionDelegate on that class – as long as you pass the same object to each block call on NSURLSession, they can all use the same NSURLSessionDelegate class (or instance if you are into using static classes , which I use quite often – but not going to get into the whole “statics are always evil” discussion here – maybe in another post).

This code gets called each time an SSL handshake happens.  It is pretty simple, it turns the cert presented by the server into a NSData object.  It then loads the cert from the bundle, and compares them.  If the comparison succeeds – we know that the cert presented by the server is the same as the cert we are looking for.  This of course means we need the cert to be a resource in the app.

We also need to make sure to fire the completionHandler passed into this method.  It is only with a “use” or “reject” enum passed to the completionHandler – will the NSURLSession know what to do.  If you forget to do this – all your networking basically just shuts down.

I’m also defining another delegate – so that based on the results, I can notify another part of the app to do something useful.  If  the cert is rejected – I know that the cert isn’t the right one, and likely there is a MTM attack going on.  You can test this by running your app through a proxy that does SSL decryption – like http://www.charlesproxy.com/.

//this will be in an imported header file:
//this is my definition of a delegate that my "ServerConnenctionManager" uses to notify interested parties if a connection fails
typedef NS_ENUM(NSUInteger,ServerConnectionManagerStatus) {

	ServerConnectionManagerStatusNoConnection,
	ServerConnectionManagerStatusWrongSSLCert
};

@protocol ServerConnectionManagerDelegate
-(void)connectionFailure:(ServerConnectionManagerStatus)status;
@end
//this will be in the class that implements the 
- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential *))completionHandler{

	if ([[[challenge protectionSpace] authenticationMethod] isEqualToString: NSURLAuthenticationMethodServerTrust]) {
		SecTrustRef serverTrust = [[challenge protectionSpace] serverTrust];
		(void) SecTrustEvaluate(serverTrust, NULL);
		NSData *localCertificateData = [NSData dataWithContentsOfFile: [[NSBundle mainBundle]
																		pathForResource: SSL_CERT_NAME
																		ofType: @"crt"]];
		SecCertificateRef remoteVersionOfServerCertificate = SecTrustGetCertificateAtIndex(serverTrust, 0);
		CFDataRef remoteCertificateData = SecCertificateCopyData(remoteVersionOfServerCertificate);
		BOOL certificatesAreTheSame = [localCertificateData isEqualToData: (__bridge NSData *)remoteCertificateData];
		CFRelease(remoteCertificateData);
		NSURLCredential* cred  = [NSURLCredential credentialForTrust: serverTrust];
#ifdef DEBUG
		certificatesAreTheSame = YES;
#endif

		if (certificatesAreTheSame) {
			completionHandler(NSURLSessionAuthChallengeUseCredential,cred);		}
		else {
			completionHandler(NSURLSessionAuthChallengeRejectProtectionSpace,nil);
			[self.delegate connectionFailure:ServerConnectionManagerStatusWrongSSLCert];
		}

	}

}