Why You Should Always Use SSL for API Calls

We recently had an interesting bug report for a project we're working on;

"I was going through attractions in Venice and was adding them to 'my guide', however when I reopened the App the very next day it shows my guide, but it is empty."

This was a bit disconcerting to say the least! When your App suddenly start losing data, alarm bells and blinking lights go off and you scramble to fix it. However, the world of hotspots and 'free' wireless networks is a menagerie that's difficult to reproduce in a lab environment.

In this case, the App resumed normal operation once brought back online - which gives a hint to the cause. The fix was somewhat surprising and discovered by coincidence; You should always use SSL for all API calls! You'll incur increased CPU use for decrypting data, and potentially have to get approval to use it - but it's worth it! Let me explain;

The real culprit in the scenario above is a neat thing called "Captive Portal". You've probably used when you have been out travelling, and purchased internet hours from an overpriced provider - or received a cupon from your friendly hotel clerk. Typically you open a browser to pass the Captive part of the portal. If your device is unfortunate enough to connect through a non-authenticated portal, your App could be in trouble.

In most cases, the portal will immediately present a login dialog when you try to go online - asking you to accept TOC or give them some money. In this case, the platform will recognize that you are not actually online and prevent your App from making API calls (assuming you're guarding for this in your implementation).

- (void)reachabilityChanged:(NSNotification *)notification
  // using Reachability from Apple: http://tinyurl.com/42mxger
  NSUInteger status = [reachability currentReachabilityStatus];

  BOOL isOnline = (NotReachable != status);
  BOOL isUsingWiFi = (ReachableViaWiFi == status);

  // handle appropriately..

- (void)reachabilityChanged:(NSNotification *)notification { // using Reachability from Apple: http://tinyurl.com/42mxger NSUInteger status = [reachability currentReachabilityStatus]; BOOL isOnline = (NotReachable != status); BOOL isUsingWiFi = (ReachableViaWiFi == status); // handle appropriately.. }

Trouble begins when less friendly implementations hijack your DNS queries and present their own content in place of what you're asking for.

In the case of your App, regular HTTP API calls will appear to succeed, and return... garbage. Actually, they will return HTML content from the captive portal - but to your App it's highly toxic garbage. In the scenario above, the App happily eats whatever content is served - promptly concludes there is nothing salient returned from the API call, and fails to display anything.

If you use HTTPS however, you will immediately get a certificate failure and you can handle the situation gracefully;

NSError *error; // out parameter from NSUrlConnection::syndSynchronousRequest

switch (error.code)
  case NSURLErrorServerCertificateHasBadDate:
  case NSURLErrorServerCertificateUntrusted:
  case NSURLErrorServerCertificateHasUnknownRoot:
  case NSURLErrorServerCertificateNotYetValid:
    // Handle the certificate error

.. and present the user with a clarifying dialog;

While I would have loved to go to Venice to dissect and purge the issue - there's an easy way to reproduce this environment using only your local network;

1. Set up a Web server and DNS on your localhost.

Personally I used Mac OS X's Server App to quickly get it up and running, but there are multiple guides out there to do it using a mix of dhcp, dns and apache.

2. After you've set up the DNS, add the hostname for the API calls you wish to block.

Set them to point to your own host's network IP address - and make sure the web server has a 404 page configured.

3. Switch the DNS entry of the wireless network on your device to your host IP address.

4. All calls to the URL will now return your /index.html or alternatively the 404 page you configured.

In the case of using SSL it will provide an untrusted certificate that incorrectly claims to be your API endpoint.

For us, the lesson is that SSL is invaluable in mobile Apps and should be the default when polling data - because it accurately lets you identify that you're receiving data from the correct source, and that it has not been tampered with on the way.