Chapter 11. Security

iOS 9 didn’t change much with regard to the Security framework. A few things were added, mainly about the keychain. There are also some additions that are about Application Transport Security, or ATS. ATS is now incorporated into iOS 9, so all apps compiled with Xcode 7, linked against iOS 9, and running under iOS 9 will by default use HTTPS for all their network traffic. This is really good, and not so good. It is good because it strongly encourages the use of secure connections for everything, but sometimes it can be annoying to force using a secure connection for everything!

There are also some changes that affect the way we can store values in the keychain, but overall, not much to worry about.

11.1 Protecting Your Network Connections with ATS

Problem

You want to control the details about the HTTPS channels through which your network connections go, or use a non-secure channel (HTTP).

I do not personally suggest using non-secure connections. However, in some cases, if you are using a backend that does not provide an HTTPS variant, you will be eventually forced to go through HTTP. In this chapter, I’ll help you figure out how to do that as well.

Solution

As I said, by default, all domain names that you use in your URLs will be going through secure channels. But you can indicate specific exceptions. ATS has a dictionary key in your Info.plist file called NSAppTransportSecurity. Under that, you have another dictionary key called NSExceptionDomains. Under this key you can list specific domain names that don’t use ATS.

Discussion

If you want to disable ATS entirely so that all your network connections go through channels specified in your code, simply insert the NSAllowsArbitraryLoads key under the NSExceptionDomains key. The NSAllowsArbitraryLoads key accepts a Boolean value. If set to true, your HTTP connections will be HTTP and HTTPS will be HTTPS.

Alternatively, under the NSExceptionDomains key, you can specify the name of your domain and set its data type to be a dictionary. Under this dictionary, you can have the following keys:

NSExceptionAllowsInsecureHTTPLoads
If set to true, allows HTTP loads on the given domain.
NSIncludesSubdomains
If set to true, includes all the subdomains of the given domain as an exception from ATS.
NSRequiresCertificateTransparency
Dictates that the SSL certificate of the given URL has to include certificate-transparency information. Check certificate transparency out on the Web for more information.
NSExceptionMinimumTLSVersion
This is a key to which you assign a string value to specify the minimum TLS version for the connection. Values can be TLSv1.0, TLSv1.1, or TLSv1.2.

So if I want to disable ATS completely, my plist will look like this:

<plist version="1.0">
<dict>
	<key>NSExceptionDomains</key>
	<dict>
		<key>NSAllowsArbitraryLoads</key>
		<true/>
	</dict>
</dict>
</plist>

How about if I want to have ATS enabled but not for mydomain.com? I’d also like to request certificate transparency and I’d like ATS to be disabled for subdomains as well:

<plist version="1.0">
<dict>
	<key>NSExceptionDomains</key>
	<dict>
		<key>NSAllowsArbitraryLoads</key>
		<false/>
		<key>mydomain.com</key>
		<dict>
			<key>NSExceptionAllowsInsecureHTTPLoads</key>
			<true/>
			<key>NSIncludesSubdomains</key>
			<true/>
			<key>NSRequiresCertificateTransparency</key>
			<true/>
		</dict>
	</dict>
</dict>
</plist>

How about if I want to enable ATS only for mydomain.com?

<plist version="1.0">
<dict>
	<key>NSExceptionDomains</key>
	<dict>
		<key>NSAllowsArbitraryLoads</key>
		<true/>
		<key>mydomain.com</key>
		<dict>
			<key>NSExceptionAllowsInsecureHTTPLoads</key>
			<false/>
			<key>NSIncludesSubdomains</key>
			<true/>
		</dict>
	</dict>
</dict>
</plist>

See Also

Recipe 3.6

11.2 Binding Keychain Items to Passcode and Touch ID

Problem

You want to create a secure item in the keychain that is accessible only if the user has set a passcode on her device and has enrolled into using the device with Touch ID. So at least one finger has to have been registered.

Solution

Follow these steps:

  1. Create your access control flags with the SecAccessControlCreateWithFlags function. Pass the value of kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly as the protection parameter and the value of SecAccessControlCreateFlags.TouchIDAny as the flags parameter.
  2. In your secure dictionary, add a key named kSecUseAuthenticationUI and set its value to kSecUseAuthenticationUIAllow. This allows the user to unlock the secure key with her device passcode or Touch ID.
  3. In your secure dictionary, add a key named kSecAttrAccessControl and set its value to the return value of the SecAccessControlCreateWithFlags function that you called earlier.

Discussion

For extra security, you might want to sometimes bind secure items in the keychain to Touch ID and a passcode on a device. As explained before, you’d have to first create your access control flags with the SecAccessControlCreateWithFlags function and then proceed to use the SecItemAdd function as you normally would, to add the secure item to the keychain.

The following example saves a string (as a password) into the keychain, and binds it to the user’s passcode and Touch ID. First, start off by creating the access control flags:

    guard let flags =
      SecAccessControlCreateWithFlags(kCFAllocatorDefault,
        kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly,
        SecAccessControlCreateFlags.TouchIDAny, nil) else{
          print("Could not create the access control flags")
          return
    }

Then define the data that you want to store in the keychain:

    let password = "some string"

    guard let data = password.dataUsingEncoding(NSUTF8StringEncoding) else{
      print("Could not get data from string")
      return
    }

The next step is to create the dictionary that you need to pass to the SecItemAdd function later with all your flags:

    let service = "onlinePasswords"

    let attrs = [
      kSecClass.str() : kSecClassGenericPassword.str(),
      kSecAttrService.str() : service,
      kSecValueData.str() : data,
      kSecUseAuthenticationUI.str() : kSecUseAuthenticationUIAllow.str(),
      kSecAttrAccessControl.str() : flags,
    ]

Last but not least, asynchronously add the item to the keychain:

    NSOperationQueue().addOperationWithBlock{
      guard SecItemAdd(attrs, nil) == errSecSuccess else{
        print("Could not add the item to the keychain")
        return
      }

      print("Successfully added the item to keychain")
    }

Earlier, we used the value of SecAccessControlCreateFlags.TouchIDAny in the flags parameter of the SecAccessControlCreateWithFlags function to specify that we need Touch ID to be enabled on the current device before our secure item can be read. There is another value in SecAccessControlCreateFlags that you might find useful: TouchIDCurrentSet. If you use this value, your secure item will still require Touch ID, but it will be invalidated by a change to the current set of enrolled Touch ID fingers. If the user adds a new finger to Touch ID or removes an existing one, your item will be invalidated and won’t be readable.

11.3 Opening URLs Safely

Problem

You want to find out whether an app on the user’s device can open a specific URL.

Solution

Follow these steps:

  1. Define the key of LSApplicationQueriesSchemes in your plist file as an array.
  2. Under that array, define your URL schemes as strings. These are the URL schemes that you want your app to be able to open.
  3. In your app, issue the canOpenUrl(_:) method on your shared app.
  4. If you can open the URL, proceed to open it using the openUrl(_:) method of the shared app.
  5. If you cannot open the URL, offer an alternative to your user if possible.

Discussion

In iOS, previously, apps could issue the canOpenUrl(_:) call to find out whether a URL could be opened on the device by another application. For instance, I could find out whether I can open “instagram://app” (see iPhone Hooks : Instagram Documentation). If that’s possible, I would know that Instagram is installed on the user’s device. This technique was used by some apps to find which other apps are installed on the user’s device. This information was then used for marketing, among other things.

In iOS 9, you need to use the plist file to define the URLs that you want to be able to open or to check whether URLs can be opened. If you define too many APIs or unrelated APIs, your app might get rejected. If you try to open a URL that you have not defined in the plist, you will get a failure. You can use canOpenUrl(_:) to check whether you can access a URL before trying to open it: the method returns true if you have indicated that you can open that kind of URL, and false otherwise.

Let’s check out an example. I’ll try to find first whether I can open the Instagram app on the user’s device:

    guard let url = NSURL(string: "instagram://app") where
      UIApplication.sharedApplication().canOpenURL(url) else{
      return
    }

Now that I know I can open the URL, I’ll proceed to do so:

    guard UIApplication.sharedApplication().openURL(url) else{
      print("Could not open Instagram")
      return
    }

    print("Successfully opened Instagram")

I’ll then go into the plist file and tell iOS that I want to open URL schemes starting with “instagram”:

<plist version="1.0">
<array>
	<string>instagram</string>
</array>
</plist>

11.4 Authenticating the User with Touch ID and Timeout

Problem

You want to ask the user for permission to read secure content in the keychain. This includes setting a timeout after which you will no longer have access.

Solution

Follow these steps:

  1. Create your access control flags with SecAccessControlCreateWithFlags, as you saw in Recipe 11.2.
  2. Instantiate a context object of type LAContext.
  3. Set the touchIDAuthenticationAllowableReuseDuration property of your context to LATouchIDAuthenticationMaximumAllowableReuseDuration, so your context will lock out only after the maximum allowed number of seconds.
  4. Call the evaluateAccessControl(_:operation:localizedReason:) method on your context to get access to the access control.
  5. If you gain access, create your keychain request dictionary and include the kSecUseAuthenticationContext key. The value of this key will be your context object.
  6. Use the SecItemCopyMatching function with your dictionary to read a secure object with the given access controls.

Discussion

Whenever you write an item to the keychain, you can do so with the access controls as we saw in Recipe 11.2. So assume that your item requires Touch ID. If you want to read that item now, you need to request permission to do so. Let’s define our context and the reason why want to read the item:

  let context = LAContext()
  let reason = "To unlock previously stored security phrase"

Then define your access controls as before:

    guard let flags =
      SecAccessControlCreateWithFlags(kCFAllocatorDefault,
        kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly,
        SecAccessControlCreateFlags.TouchIDAny, nil) else{
          print("Could not create the access control flags")
          return
    }

Also specify how long you can get access. After this time passes, the user will be forced to use Touch ID again to unlock the context:

    context.touchIDAuthenticationAllowableReuseDuration =
    LATouchIDAuthenticationMaximumAllowableReuseDuration

Last but not least, gain access to the given access controls and read the item if possible:

    context.evaluateAccessControl(flags,
      operation: LAAccessControlOperation.UseItem,
      localizedReason: reason) {[unowned context] succ, err in

        guard succ && err == nil else {
          print("Could not evaluate the access control")
          if let e = err {
            print("Error = (e)")
          }
          return
        }

        print("Successfully evaluated the access control")

        let service = "onlinePasswords"

        let attrs = [
          kSecClass.str() : kSecClassGenericPassword.str(),
          kSecAttrService.str() : service,
          kSecUseAuthenticationUI.str() : kSecUseAuthenticationUIAllow.str(),
          kSecAttrAccessControl.str() : flags,
          kSecReturnData.str() : kCFBooleanTrue,
          kSecUseAuthenticationContext.str() : context,
        ]

        //now attempt to use the attrs with SecItemCopyMatching

        print(attrs)

    }

The operation argument of the evaluateAccessControl(_:operation:localizedReason:) method takes in a value of type LAAccessControlOperation that indicates the type of operation you want to perform. Some of the values that you can use are UseItem, CreateItem, CreateKey, and UseKeySign.

See Also

Recipe 11.2

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset