📚 App Transport Security


Created: 06.10.2020

In this article I’m trying to untangle a very confusing notion for some people (myself included) - App Transport Security. We will have several examples to work with and then try to construct our own setting according to our requirements.

Overview

NSAppTransportSecurity : Dictionary {
    NSAllowsArbitraryLoads : Boolean
    NSAllowsArbitraryLoadsForMedia : Boolean
    NSAllowsArbitraryLoadsInWebContent : Boolean
    NSAllowsLocalNetworking : Boolean
    NSExceptionDomains : Dictionary {
        <domain-name-string> : Dictionary {
            NSIncludesSubdomains : Boolean
            NSExceptionAllowsInsecureHTTPLoads : Boolean
            NSExceptionMinimumTLSVersion : String
            NSExceptionRequiresForwardSecrecy : Boolean   // Default value is YES
            NSRequiresCertificateTransparency : Boolean
        }
    }
}

The above code snippet from Apple docs shows the object that is responsible for loosening network security. This topic has been very confusing for me, but I’ve finally got myself untangled 🎄 🐈 ðŸ’Ą !

As always, nothing serves better as a good example and a good picture. Here is a picture I consider to be good:

strictguysatapple

First, there are two ways how to enable ATS: do nothing (better one unless absolutely needed) or add NSAppTransportSecurity and set NSAllowsArbitraryLoads to key. But keep in mind that in this case Apple will make you answer for your actions 🚓 in court.

What do we need?

Begore we begin, let’s assume that we own an application which uses API at https://bakerst221b.com. Also, it downloads images from http://rams.bakerst221b.com and also periodically checks weather at http://weather.co.uk. Let’s first see some examples and the decide.

Example 1. Too loose

Here is the first setting:

<key>NSAppTransportSecurity</key>
   <dict>
      <key>NSAllowsArbitraryLoads</key>
      <true/>
   </dict>

This basically means the following: “Hey, System. Allow this application to communicate over HTTP, with any TLS version of its like in case of HTTPS, with or without PFS and to local domains as well (since NSAllowsArbitraryLoads is true).” Sounds pretty bad, but how bad that is in real life? What if the application in question is a web browser? Well, in that case that option above would be the only option at all ðŸĪ·â€â™€ïļ , since if ATS was left with default settings (no NSAppTransportSecurity in Info.plist or in Info.plist but NSAllowsArbitraryLoads set to false), our browser would not be able to connect to many websites, which do not correspond to its high standards.

Example 2. Getting tighter

Now consider the second setting:

<key>NSAppTransportSecurity</key>
<dict>
  <key>NSAllowsArbitraryLoads</key>
  <true/>
  <key>NSExceptionDomains</key>
  <dict>
    <key>bank.com</key>
    <dict>
      <key>NSIncludesSubdomains</key>
      <true/>
      <key>NSExceptionAllowsInsecureHTTPLoads</key>
      <true/>
		</dict>
  </dict>
</dict>

It now says the following: “Turn off ATS, but keep it for bank.com (since NSAllowsArbitraryLoads is true) and its subdomains (since NSIncludesSubdomains is true) only allowing them to communicate over HTTP (since NSExceptionAllowsInsecureHTTPLoads is true)”. When this might be handy? For example, our application communicates with different services which are not very secure 😉, but it wants to communicate securely with its own API (bank.com). But since there sometimes pictures on the same domain, which are loaded via HTTP, the application softens the policy and allows HTTP connection for bank.com, but keep in mind that other ATS restriction for bank.com are still in place: if it’s HTTP - good TLS, PFS and no connection to local domains.

Example 3. Getting really serious

Now consider the following example:

<key>NSAppTransportSecurity</key>
<dict>
  <key>NSAllowsArbitraryLoads</key>
  <false/>
  <key>NSExceptionDomains</key>
  <dict>
    <key>rams.bank.com</key>
    <dict>
      <key>NSExceptionAllowsInsecureHTTPLoads</key>
      <true/>
    </dict>
  </dict>
</dict>

Now the application says to the system: “Hey, buddy, turn on ATS (since NSAllowsArbitraryLoads is false). I know, it’s your default behaviour, but I need to set some exceptions. For rams.bank.com ðŸą only (since NSExceptionDomains is set to rams.bank.com) allow communication over HTTP (since NSExceptionAllowsInsecureHTTPLoads is true), since we don’t care about them much.” This is considered the best option if HTTP connection is mandatory.

So here we observe an opposite behaviour. While in the second example we turned ATS globally (for everyone) except bank.com, now we turn on it globally except for rams.bank.com. You can see that it really work both ways here, in the official docs.

Example 4. This strange world

Let’s assume this setting:

<key>NSAppTransportSecurity</key>
<dict>
  <key>NSAllowsArbitraryLoads</key>
  <true/>
  <key>NSAllowsArbitraryLoadsForMedia</key>
  <true/>
</dict>

The app says: “Hey, System, turn off ATS globally and allow crapy loads for media which uses AVFoundation framework (since NSAllowsArbitraryLoadsForMedia is true) (i.e. I need to download any ðŸ’Đ of my choice).” Right? Well… this new key that we added to the setting is changing that all: NSAllowsArbitraryLoadsForMedia. For iOS 9.0 or macOS 10.11 yes, it just turns on ATS globally and ignores whether NSAllowsArbitraryLoadsForMedia is true or false. For newer versions vice versa: ignores NSAllowsArbitraryLoads set to true and only lossening security for media (NSAllowsArbitraryLoadsForMedia set to true). Domains in NSExceptionDomains will not be affected and stick to their default values.

Other keys with the same behaviour are:

  1. NSAllowsArbitraryLoadsInWebContent - to loosen ATS for WebViews only (WKWebView, UIWebView, WebView for macOS) while for NSURLSession ATS will still be valid. Domains in NSExceptionDomains will not be affected and stick to their default values.
  2. NSAllowsLocalNetworking - allow connection to unqualified domains and .local. If you need connecting via IP, set NSAllowsArbitraryLoads to true instead. In newer versions allowed by default. NSExceptionDomains can be used to specify exceptions for local domains.
<key>NSAppTransportSecurity</key>
<dict>
  <key>NSAllowsArbitraryLoads</key>
  <true/>
  <key>NSAllowsLocalNetworking</key>
  <true/>
</dict>

The above setting is recommended by Apple for backward compatibility. Why? Older versions ignore NSAllowsLocalNetworking set true and allow NSAllowsArbitraryLoads globally instead. Newer version seeing that very same setting (since NSAllowsLocalNetworking is enabled by default, no need to specify in Info.plist) ignores NSAllowsArbitraryLoads set to true and keeps other ATS requierements.

One last example:

<key>NSAppTransportSecurity</key>
<dict>
  <key>NSAllowsArbitraryLoads</key>
  <false/>
  <key>NSAllowsArbitraryLoadsForMedia</key>
  <true/>
  <key>NSExceptionDomains</key>
  <dict>
    <key>bank.com</key>
    <dict>
      <key>NSIncludesSubdomains</key>
      <true/>
    </dict>
  </dict>
</dict>

For newer versions we have the following setting: “Hey, keep ATS turned on globally (NSAllowsArbitraryLoads is false), but allow loading arbitrary media which uses AVFoundation framework (NSAllowsArbitraryLoadsForMedia is true), but don’t allow even that for bank.com (NSExceptionDomains) and its subdomains (NSIncludesSubdomains is true).”

Back to our 🐏

Let’s refresh out requirements:

… let’s assume that we own an application which uses API at https://bakerst221b.com. Also, it downloads images from http://rams.bakerst221b.com and also periodically checks weather at http://weather.co.uk.

Connection to https://bakerst221b.com is secure and all the requirements are met. But connection to a subdomain http://rams.bakerst221b.com is insecure and requires loosening ATS. Also, we have a service which also needs HTTP.

Do we need to enable or disable NSAppTransportSecurity globally? Since we mostly need HTTPS, it makes more sense to keep in enabled globally (NSAllowsArbitraryLoads we set to false). Then, we add a dictionary for NSExceptionDomains and add two domains as exceptions for this policy: rams.bank.com and weather.co.uk. For each we only need to allow HTTP traffic, but keep other security controls, therefore, set NSExceptionAllowsInsecureHTTPLoads for both as true and that’s pretty much it!

<key>NSAppTransportSecurity</key>
<dict>
  <key>NSAllowsArbitraryLoads</key>
  <false/>
  <key>NSExceptionDomains</key>
  <dict>
    <key>rams.bank.com</key>
    <dict>
      <key>NSExceptionAllowsInsecureHTTPLoads</key>
      <true/>
    </dict>
    <key>weather.co.uk</key>
    <dict>
      <key>NSExceptionAllowsInsecureHTTPLoads</key>
      <true/>
    </dict>
  </dict>
</dict>

Conclusion

I hope that will clarify how to use ATS for you app as securely as possible. My advice is to loosen as little, as you can and separate domains, which meet the requirements and will be protected by ATS and those that don’t. To help you with configuring ATS:

nscurl --ats-diagnostics --verbose https://bakerst221b.com