App Transport Security

Update 18-Sep-2015: The behaviour of ATS has changed between the iOS 9 GM and the final public release. This article was written using the GM release and required an exception. Testing again with the public release no longer requires the ATS exception. I will post a more detailed follow up in the next few days but this looks like a bug as the Twitter search API was at the time of writing still using a weakly signed certificate.

Once you build with iOS 9 any connections you create using NSURLConnection or NSURLSession use App Transport Security which, by default, will reject insecure connections. Since there are many useful web services that are not secure it is possible to disable or override this default behaviour for specific domains. In this post I look at debugging and creating an exception for the Twitter Search API.

Searching Twitter

The Social framework has existed since iOS 5 and makes it easy to interact with Twitter, Facebook and Weibo social networks. You can find some more details in this old post on Migrating to the New Twitter Search API. In that post I used an NSURLConnection to retrieve tweets for the Twitter search API using the URL:

NSURL *url = [NSURL URLWithString:@"https://api.twitter.com/1.1/search/tweets.json"];

At first glance, this looks like it should work fine with iOS 9 as it is using https. Unfortunately it fails with an unhelpful error message and alert:

SSL Error

NSURLSession/NSURLConnection HTTP load failed (kCFStreamErrorDomainSSL, -9802)

(The error code -9802 is defined in SecureTransport.h and indicates a fatal alert)

Not Just HTTPS

To understand why this fails you first need to understand when App Transport Security considers a connection to be “secure”. The full details are in the App Transport Security Technote but I will summarise below:

  • Server must support at least Transport Layer Security (TLS) 1.2
  • Connection ciphers must provide forward secrecy.
  • Certificates must be signed with SHA256 or better with at least 2048 bits for RSA or 256 bits for Elliptic Curve keys.

Unless you are a security expert this may not mean much to you. If you want to dig deeper I recommend Bulletproof SSL and TLS but here are some hints to debug a failing connection.

Debugging App Transport Security

CFNetwork Logging

As a first step you can turn on CFNetwork Diagnostic Logging. Edit the Xcode scheme and add the CFNETWORK_DIAGNOSTICS environment variable. I set the logging level to 3 which is the most verbose:

CFNETWORK_DIAGNOSTICS

The Xcode console shows the location of the log file:

CFNetwork diagnostics log file created at: /private/var/mobile/Containers/
Data/Application/B378AC40-A5E1-44D0-A7F1-B3BB254D1405/Library/Logs/
CrashReporter/CFNetwork_com.useyourloaf.TwitterSearch_703.nwlrb.log

If you run on the Simulator you will find the log file in the OS X file system so you can open it directly. If you run on a device you first need to the download the application container from the Xcode Devices window and then open the package contents with the Finder.

I am not going to bother pasting the log contents here as they are not of much help in this case. Our problem is not at the network level but with App Transport Security which is deciding the connection security does not make the grade. To understand why I find two other techniques quicker and more useful:

Using Curl (or NSCurl)

A better approach than CFNetwork logging is to use NSCurl which provides some App Transport Security specific diagnostics.

$ nscurl --ats-diagnostics https://api.twitter.com/1.1/search/tweets.json

Unfortunately this is new in OS X El Capitan 10.11 and I am still using OS X Yosemite 10.10 so I will stick with plain old curl for now:

$ curl -v https://api.twitter.com/1.1/search/tweets.json
*   Trying 185.45.5.33...
* Connected to api.twitter.com (185.45.5.33) port 443 (#0)
* TLS 1.2 connection using TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA
* Server certificate: api.twitter.com
* Server certificate: VeriSign Class 3 Secure Server CA - G3
* Server certificate: VeriSign Class 3 Public Primary Certification Authority - G5

This tells us that Twitter is using TLS 1.2 which is necessary for App Transport Security. It also tells us the cipher suite that it is using (TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA). You can check this against the supported cipher suites in the App Transport Security Technote. The ECDHE part tells us this is an Ephemeral elliptic curve Diffie-Hellman key exchange which sounds cool but also means forward secrecy - another requirement for App Transport Security.

If you look back to what we needed for App Transport Security you will see we have ticked two out of three of the requirements (TLS 1.2 and a cipher with forward secrecy). What we are missing is how Twitter has signed the server certificates.

Testing with SSL Labs

You can get a more detailed analysis using the free site testing tool from Qualys SSL Labs. Testing the Twitter Search API URL shows us the problem. The site gets an “A” rating but there is a warning about a certificate with a weak signature.

Certificate uses a weak signature. When renewing, ensure you upgrade to SHA2.

The detailed report on the certificate confirms the SHA1 algorithm which is now considered to be weak.

Signature algorithm SHA1withRSA WEAK

Enabling Exceptions to App Transport Security

Knowing that our problem is a weakly signed certificate is interesting but not so helpful when the web service is owned by Twitter. Until Twitter upgrades the certificate security we need to create an exception to the default policy.

A quick fix is to globally disable App Transport Security. In the App Info.plist file add the dictionary key NSAppTransportSecurity with a boolean key NSAllowsArbitraryLoads set to YES:

This gets us a working connection to the Twitter API but has the disadvantage of disabling App Transport Security for any other connections the App creates. A better approach is to only create exceptions for the insecure domains.

To create a domain specific exception add the dictionary key NSExceptionDomains to the NSAppTransportSecurity dictionary. Then add a dictionary entry for each domain that needs an exception. The key for the entry should be the domain name (e.g. api.twitter.com). You specify the details of the exception with one or more keys:

  • NSIncludesSubdomains (Boolean): Use YES if the exception applies to all subdomains.
  • NSExceptionMinimumTLSVersion (String): Valid values are “TLSv1.0”, “TLSv1.1” and the default “TLS1.2”.
  • NSExceptionRequiresForwardSecrecy (Boolean): Use NO if you need to use a cipher suite that does not support forward secrecy. This allows some extra cipher suites for the widely used RSA key exchange algorithm - see the App Transport Security Technote for details.
  • NSExceptionAllowsInsecureHTTPLoads (Boolean): Use YES for connections not using HTTPS or with incorrectly or weakly signed certificates.

There are also versions of the above keys for domains owned by a third party:

  • NSThirdPartyExceptionMinimumTLSVersion
  • NSThirdPartyExceptionRequiresForwardSecrecy
  • NSThirdPartyExceptionAllowsInsecureHTTPLoads

The problem with the api.twitter.com service is a weakly signed certificate so we can create an exception for that domain using the NSThirdPartyExceptionAllowsInsecureHTTPLoads key:

This fixes the problem and keeps App Transport Security active for any other connections the App might use in the future. An updated version of the Twitter search example app that works with iOS 9 is in my Code Examples GitHub repository.

Further Reading