iOS and SSL

For iOS, communicating with secure server is no different than communicating with an open server; the only difference is the protocol specified. So, if you're part of an organization that has a trusted certificate, the only step required is to change the URL in LocationLoader.m from http to https. That's it! :-)

If, however, you're using a self-signed certificate, it's necessary to add some additional code. Why? Because - by default - iOS will reject all untrusted https connections.

Just to be clear: Rejecting untrusted connections is a very good default behavior; any disabling of this is inherently risky. Accordingly, whenever I override the default behavior, I prefer to provide a visual que to the end user. That way, I don't accidentally publish a version of the app which has mistakenly left this in pace.

In a prior article, I mentioned that the NSURLConnectionDelegate protocol had three key methods. There are actually some additional (optional) methods included in the protocol. And in this article, I'll look at two of these methods:

  1. -(BOOL)connection:(NSURLConnection *)connection canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)protectionSpace;
  2.  
  3. -(void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge;

These two methods allow me to provide my own authentication mechanism for trusted connections. Armed with this knowledge, I've updated ContentBroker.m as follows:

  1. #import "ContentBroker.h"
  2.  
  3. #define TRUSTED_HOST @"192.168.1.2"
  4.  
  5.  
  6. @implementation ContentBroker {
  7. NSMutableData *contentData;
  8. NSURLConnection *conn;
  9. }
  10.  
  11. @synthesize delegate, allowSSLBypass, action;
  12.  
  13. -(void) loadContent {
  14. contentData = [NSMutableData data];
  15. NSString *contentURL = [self getContentURL];
  16. conn = [[NSURLConnection alloc] initWithRequest:
  17. [NSURLRequest requestWithURL:[NSURL URLWithString:contentURL]] delegate:self];
  18.  
  19. }
  20.  
  21. - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
  22. [contentData appendData:data];
  23. }
  24.  
  25. - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
  26. NSLog(@"Bad: %@", [error description]);
  27. ContentResponse *response = [[ContentResponse alloc]initWithRc:-999 andDescr:@"error" andAction:0];
  28. if (delegate != nil) {
  29. [delegate contentLoaded: response];
  30. }
  31.  
  32. conn = nil;
  33. }
  34.  
  35. // ------------ If specified, TEMPORARILY allow all SSL connections ----------
  36. -(BOOL)connection:(NSURLConnection *)connection canAuthenticateAgainstProtectionSpace:
  37. (NSURLProtectionSpace *)protectionSpace {
  38. return [protectionSpace.authenticationMethod
  39. isEqualToString:NSURLAuthenticationMethodServerTrust];
  40. }
  41.  
  42. -(void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:
  43. (NSURLAuthenticationChallenge *)challenge {
  44. if (([challenge.protectionSpace.authenticationMethod
  45. isEqualToString:NSURLAuthenticationMethodServerTrust]) &&
  46. ([self allowSSLBypass])) {
  47. if ([challenge.protectionSpace.host isEqualToString:TRUSTED_HOST]) {
  48. NSLog(@"Allowing bypass...");
  49. NSURLCredential *credential = [NSURLCredential credentialForTrust:
  50. challenge.protectionSpace.serverTrust];
  51. [challenge.sender useCredential:credential
  52. forAuthenticationChallenge:challenge];
  53. }
  54. }
  55. [challenge.sender continueWithoutCredentialForAuthenticationChallenge:challenge];
  56. }
  57. // --------------------------------------------------------------------------------
  58.  
  59.  
  60. - (void)connectionDidFinishLoading:(NSURLConnection *)connection {
  61. NSString *loadedContent = [[NSString alloc] initWithData:
  62. contentData encoding:NSUTF8StringEncoding];
  63. NSLog(@"Loaded content: %@",loadedContent);
  64. ContentResponse *response = [self parseContents:loadedContent];
  65. if (delegate != nil) {
  66. [delegate contentLoaded: response];
  67. }
  68. }
  69.  
  70. // --------- ***** Override these in the subclass ******* ----------------------------
  71.  
  72. -(NSString *) getContentURL {
  73. NSString *contentURL = [NSString stringWithFormat:@"<call get url here %@ %@>", userId, token];
  74. return contentURL;
  75. }
  76.  
  77. -(ContentResponse *) parseContents: (NSString *)rawJSON {
  78. return nil;
  79. }
  80.  
  81. @end

Comments:

  • Line 3: For now, I've hard-coded the IP address of my trusted server.
  • Line 11: I've added an additional property named allowSSLBypass. The header file declares this as a BOOL.
  • Lines 36 - 40: This code simply states that the app is able to handle authentication challenges of type NSURLAuthenticationMethodServerTrust. That's the type of request associated with SSL / HTTPS.
  • Lines 42 - 56: This code seems a bit more complex than it needs to be, but it's simply stating this: If SSL override is true, and it's the right sort of challenge, AND the hostname is the same as the one specified on line 6, then we'll verify that we're okay to proceed. If the situation doesn't match all of these conditions, then provide no additional override.

The code in LocationBroker.m remains the same - other than changing the protocol from http to https.

Now, in AppDelegate.m I need to add code for allowing a bypass of SSL credential checking:

First, I add a flag for this:

  1. #define BYPASS_SSL_CHECK YES

Next, I update the actual loading of location data to include this flag and a visual warning for the user:

  1. // ------- Load data for the map -------------
  2. LocationBroker *ll = [[LocationBroker alloc] init];
  3. [ll setDelegate:fishingMapVC];
  4.  
  5. if (BYPASS_SSL_CHECK) {
  6. [ll setAllowSSLBypass:YES];
  7. UIAlertView *av = [[UIAlertView alloc] initWithTitle:@"SSL Warning"
  8. message:@"SSL Certification Bypass Active"
  9. delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil];
  10. [av show];
  11.  
  12. }
  13.  
  14. [ll loadLocations];

Lines 5-11 are the only significant changes: If the bypass function is enabled, set the appropriate value in the LocationLoader instance, and show an alert to the user.

At this point, I can launch the application, and get the same results as before. And although there's no difference on the screen, you can rest more easily knowing that the communication between the mobile device and the server is secure.

Next, I'll provide the same update to the Android mobile app.