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.
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.
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.
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
true
, allows HTTP loads on the given domain.NSIncludesSubdomains
true
, includes all the subdomains of the given domain as an exception from ATS.NSRequiresCertificateTransparency
NSExceptionMinimumTLSVersion
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
>
SecAccessControlCreateWithFlags
function. Pass the value of kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly
as the protection
parameter and the value of SecAccessControlCreateFlags.TouchIDAny
as the flags
parameter.kSecUseAuthenticationUI
and set its value to kSecUseAuthenticationUIAllow
. This allows the user to unlock the secure key with her device passcode or Touch ID.kSecAttrAccessControl
and set its value to the return value of the SecAccessControlCreateWithFlags
function that you called earlier.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
{
(
"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
{
(
"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
{
(
"Could not add the item to the keychain"
)
return
}
(
"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.
LSApplicationQueriesSchemes
in your plist file as an array.canOpenUrl(_:)
method on your shared app.openUrl(_:)
method of the shared app.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
{
(
"Could not open Instagram"
)
return
}
(
"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
>
</
string
>
</
array
>
</
plist
>
SecAccessControlCreateWithFlags
, as you saw in Recipe 11.2.LAContext
.touchIDAuthenticationAllowableReuseDuration
property of your context to LATouchIDAuthenticationMaximumAllowableReuseDuration
, so your context will lock out only after the maximum allowed number of seconds.evaluateAccessControl(_:operation:localizedReason:)
method on your context to get access to the access control.kSecUseAuthenticationContext
key. The value of this key will be your context object.SecItemCopyMatching
function with your dictionary to read a secure object with the given access controls.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
{
(
"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
{
(
"Could not evaluate the access control"
)
if
let
e
=
err
{
(
"Error = (e)"
)
}
return
}
(
"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
(
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
.