19. Using Keychain and Touch ID to Secure and Access Data

Securing sensitive user data is a critical and often-overlooked step of mobile development. The technology press is constantly plagued by stories of large companies storing password or credit-card information in plain text. Users put their trust in developers to treat sensitive information with the care and respect it deserves. This includes encrypting remote and local copies of that information to prevent unauthorized access. It is the duty of every developer to treat users’ data as they would like their own confidential information to be handled.

Apple has long provided a security framework called Keychain to store encrypted information on an iOS device. The Keychain also has several additional benefits beyond standard application and data security. Information stored in the Keychain persists even after an app has been deleted from the device, and Keychain information can even be shared among multiple apps by the same developer.

This chapter demonstrates the use of Apple’s KeychainItemWrapper class (version 1.2) to secure and retrieve sensitive information. Although it is completely acceptable and occasionally required to write a Keychain wrapper from the ground up, leveraging Apple’s libraries can be a tremendous time-saver and will often provide all the functionality required. This chapter does not cover creating a custom Keychain wrapper class but leverages Apple’s provided code to quickly add Keychains to an iOS app. Keychain interaction can be complex, and small mistakes might cause the data to not truly be secure, but using Apple’s class minimizes these risk.


Tip

The most up-to-date version of Apple’s KeychainItemWrapper class can be found at http://developer.apple.com/library/ios/#samplecode/GenericKeychain/Listings/Classes_KeychainItemWrapper_m.html.


It is important to remember that while securing information on disk, it is only a small part of complete app security; other factors, such as transmitting data over a network, remote storage, and password enforcement, are just as critical in providing a well-rounded secure app.

The Sample App

The Keychain sample app is a single-view app that will secure a credit card number along with relevant user information, such as name and expiration date. To access the information, the user sets a PIN on first launch. Both the PIN and the credit card information are secured using the Keychain.


Note

The Keychain does not work on the iOS simulator as of iOS 8. The wrapper class provided by Apple and used in this chapter does make considerable efforts to properly simulate the Keychain behaviors. In addition, since code being executed on the simulator is not code signed, it is important to keep in mind that there are no restrictions on which apps can access Keychain items. It is highly recommended that Keychain development be thoroughly debugged on the device after it’s working correctly on the simulator.


The sample app itself is simple. It consists of four text fields and a button. The majority of the sample code not directly relating to the Keychain handles laying out information.


Note

Deleting the app from a device does not remove the Keychain for that app, which can make debugging considerably more difficult. The simulator does have a Reset Contents and Settings option, which will wipe the Keychain. It is highly recommended that a Keychain app not be debugged on a device until it is functional on the simulator due to the hassle of returning to a clean state.


Setting Up and Using Keychain

Keychain is part of the Security.framework and has been available for iOS starting with the initial SDK release. Keychain has its roots in Mac OS X development, where it was first introduced with OS X 10.2. However, Keychain’s history predates even OS X with roots back into OS 8.6. Keychain was initially developed for Apple’s email system called PowerTalk. This makes Keychain one of the oldest available frameworks on iOS.

Keychain can be used to secure small amounts of data such as passwords, keys, certificates, and notes. However, if an app is securing large amounts of information such as encoded images or videos, implementing a third-party encryption library is usually a better fit than Keychain. Core data also provides encryption capabilities, and is worth exploring if the app will be Core Data–based.

Before work can be done with Keychain, the Security.framework must be added to the project and <Security/Security.h> needs to be imported to any classes directly accessing Keychain methods and functions.

Setting Up a New KeychainItemWrapper

iOS Keychains are unlocked based on the code signing of the app that is requesting it. Since there is no systemwide password as seen on OS X, there needs to be an additional step to secure data. Since the app controls which Keychain data can be accessed to truly secure information, the app itself should be password protected. This is done through the sample app using a PIN entry system.

When the app is launched for the first time, it will prompt the user to enter a new PIN and repeat it. To securely store the PIN, a new KeychainItemWrapper is created.

pinWrapper = [[KeychainItemWrapper alloc]initWithIdentifier:@"com.ICF.Keychain.pin" accessGroup:nil];

Creating a new KeychainItemWrapper is done using two attributes. The first attribute is an identifier for that Keychain item. It is recommended that a reverse DNS approach be used here, such as com.company.app.id. accessGroup is set to nil in this example, and the accessGroup parameter is used for sharing Keychains across multiple apps. Refer to the section “Sharing a Keychain Between Apps” for more information on accessGroups.

The next attribute that needs be set on the new KeychainItemWrapper is the kSecAttr-Accessible. This controls when the data will be unlocked. In the sample app the data becomes available when the device is unlocked, securing the data for a locked device. There are several possible options for this parameter, as detailed in Table 19.1.

[pinWrapper setObject:kSecAttrAccessibleWhenUnlocked forKey: (id)kSecAttrAccessible];

Image

Table 19.1 All Possible Constants and Associated Descriptions to Be Supplied to kSecAttrAccessible

The app now knows the identifier for the Keychain as well the security level that is required. However, an additional parameter needs to be set before data can begin to be stored. The kSecAttrService is used to store a username for the password pair that will be used for the PIN. A PIN does not have an associated password; for the purposes of the sample app, pinIdentifer is used here. Although Keychains will often work while the kSecAttr-Service is omitted, having a value set here corrects many hard-to-reproduce failures, and is recommended.

[pinWrapper setObject:@"pinIdentifer" forKey: (id)kSecAttrAccount];

Storing and Retrieving the PIN

After a new KeychainItemWrapper has been configured in the manner described in the preceding section, data can be stored into it. Storing information in a Keychain is similar to storing data in a dictionary. The sample app first checks to make sure that both of the PIN text fields match; then it calls setObject: on the pinWrapper that was created in the preceding section. For the key identifier kSecValueData is used. This item is covered more in depth in the section “Keychain Attribute Keys”; for now, however, it is important to use this constant.

if([pinField.text isEqualToString: pinFieldRepeat.text])
{
    [pinWrapper setObject:[pinField text] forKey:kSecValueData];
}

After a new value has been stored into the Keychain, it can be retrieved in the same fashion. To test whether the user has entered the correct PIN in the sample app, the following code is used:

if([pinField.text isEqualToString: [pinWrapper objectForKey:kSecValueData]])

After the PIN number being entered has been confirmed as the PIN number stored in the Keychain, the user is allowed to access the next section of the app, described in the section “Securing a Dictionary.”

Keychain Attribute Keys

Keychain items are stored similar to NSDictionaries; however, they have very specific keys that can be associated with them. Unlike an NSDictionary, a Keychain cannot use any random string for a key value. Each Keychain is associated with a Keychain class; if using Apple’s KeychainItemWrapper, it defaults to using CFTypeRef kSecClassGenericPassword. However, other options exist for kSecClassInternetPassword, kSecClassCertificate, kSecClassKey, and kSecClassIdentity. Each class has different associated values attached to it. For the purposes of this chapter as well as for the KeychainItemWrapper, the focus will be on kSecClassGenericPassword.

kSecClassGenericPassword contains 14 possible keys for storing and accessing data, as described in Table 19.2. It is important to keep in mind that these keys are optional and are not required to be populated in order to function correctly.

Image
Image

Table 19.2 Keychain Attribute Keys Available When Working with kSecClassGenericPassword

Securing a Dictionary

Securing a more complex data type such as a dictionary follows the same approach taken to secure the PIN in earlier sections. The Keychain wrapper allows for the storage only of strings; to secure a dictionary, it is first turned into a string. The approach chosen for the sample code is to first save the dictionary to a JSON string using the NSJSONSerialization class. (See Chapter 9, “Working with and Parsing JSON,” for more info.)

NSMutableDictionary *secureDataDict = [[[NSMutableDictionary alloc] init] autorelease];

NSError *error = nil;

if(numberTextField.text)
    [secureDataDict setObject:numberTextField.text forKey:@"numberTextField"];

if(expDateTextField.text)
    [secureDataDict setObject:expDateTextField.text forKey:@"expDateTextField"];

if(CV2CodeTextField.text)
    [secureDataDict setObject:CV2CodeTextField.text forKey:@"CV2CodeTextField"];

if(nameTextField.text)
    [secureDataDict setObject:nameTextField.text forKey:@"nameTextField"];

NSData *rawData = [NSJSONSerialization dataWithJSONObject:secureDataDict
             options:0
               error:&error];

if(error != nil)
{
    NSLog(@"An error occurred: %@", [error localizedDescription]);
}

NSString *dataString = [[NSString alloc] initWithData:rawData encoding:NSUTF8StringEncoding];

After the value of the dictionary has been converted into a string representation of the dictionary data, it can be added to the Keychain in the same fashion as previously discussed.

KeychainItemWrapper *secureDataKeychain = [[KeychainItemWrapper alloc] initWithIdentifier:@"com.ICF.keychain.securedData" accessGroup:nil];

[secureDataKeychain setObject:@"secureDataIdentifer" forKey: (id)kSecAttrAccount];

[secureDataKeychain setObject:kSecAttrAccessibleWhenUnlocked forKey: (id)kSecAttrAccessible];

[secureDataKeychain setObject:dataString forKey:kSecValueData];

To retrieve the data in the form of a dictionary, the steps must be followed in reverse. Starting with an NSString from the Keychain, it is turned into an NSData value. The NSData is used with NSJSONSerialization to retrieve the original dictionary value. After the dictionary is re-created, the text fields that display the user’s credit card information are populated.

KeychainItemWrapper *secureDataKeychain = [[KeychainItemWrapper alloc] initWithIdentifier:@"com.ICF.keychain.securedData" accessGroup:nil];

NSString *secureDataString = [secureDataKeychain objectForKey:kSecValueData];

if([secureDataString length] != 0)
{
    NSData* data = [secureDataString dataUsingEncoding:NSUTF8StringEncoding];

    NSError *error = nil;

    NSDictionary *secureDataDictionary = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:&error];

    if(error != nil)
    {
         NSLog(@"An error occurred: %@", [error localizedDescription]);
    }

    numberTextField.text = [secureDataDictionary objectForKey:@"numberTextField"];

    expDateTextField.text = [secureDataDictionary objectForKey:@"expDateTextField"];

    CV2CodeTextField.text = [secureDataDictionary objectForKey:@"CV2CodeTextField"];

    nameTextField.text = [secureDataDictionary objectForKey:@"nameTextField"];
}

else
{
    NSLog(@"No Keychain data stored yet");
}

Resetting a Keychain Item

At times, it might be necessary to wipe out the data in a Keychain while not replacing it with another set of user data. This can be done using Apple’s library by invoking the reset-KeyChainItem method on the Keychain wrapper that needs to be reset.

[pinWrapper resetKeychainItem];

Sharing a Keychain Between Apps

A Keychain can be shared across multiple iOS apps if they are published by the same developer and under specific conditions. The most important requirement for sharing Keychain data between two apps is that both apps must have the same bundle seed. For example, consider two apps with the bundle identifiers 659823F3DC53.com.ICF.firstapp and 659823F3DC53.com.ICF.secondapp. These apps would be able to access and modify each other’s Keychain data. Keychain sharing with a wildcard ID does not seem to work, although the official documentation remains quiet on this situation. Bundle seeds can be configured from the developer portal when new apps are created.

When you have two apps that share the same bundle seed, each app will need to have its entitlements configured to allow for a Keychain access group. Keychain groups are configured from the summary tab of the target, as shown in Figure 19.1.

Image

Figure 19.1 Setting up a new Keychain group using Xcode 6.

For the shared Keychain to be accessed, the Keychain group first needs to be set. With a modification of the PIN example from earlier in the chapter, it would look like the following code snippet:

[pinWrapper setObject:@"659823F3DC53.com.ICF.appgroup" forKey:(id)kSecAttrAccessGroup];


Note

Remember that when setting the access group in Xcode, you do not need to specify the bundle seed. However, when you are setting the kSecAttrAccessGroup property, the bundle seed needs to be specified and the bundle seeds of both apps must match.


After an access group has been set on a KeychainItemWrapper, it can be created, modified, and deleted in the typical fashion discussed throughout this chapter.

Keychain Error Codes

The Keychain can return several specialized error codes depending on any issues encountered at runtime. These errors are described in Table 19.3.

Image

Table 19.3 Keychain Error Codes

Implementing Touch ID

The iPhone 5s was the first iOS device to feature hardware-level fingerprint recognition. This technology, collectively referred to as Touch ID, enables the user to authenticate and make purchases using only their fingerprint. Third-party developers can implement Touch ID to authenticate a user. It is important to know that the fingerprint data itself is stored on the A7 chip and cannot be accessed outside of Touch ID. Whenever you are working with Touch ID, it is important to realize that not all users will have a device capable of using fingerprint authentication, nor will everyone who is capable opt into using the technology.

Touch ID enables the user to authenticate with a fingerprint, enter her password, or cancel the authentication action. If a user chooses not to authenticate with her fingerprint, the app must supply a fallback method to conform to Apple’s Review Guidelines. To begin using Touch ID in your app, first add the Local Authentication Framework. The <LocalAuthentication/LocalAuthentication.h> header must also be imported.

A new local Authentication Context is created.

LAContext *myContext = [[LAContext alloc] init];

The app then needs to check to make sure that Touch ID exists and is enabled for the device. This is done using the canEvaluatePolicy: method. If the app allows for Touch ID authentication, evaluatePolicy: can be called; otherwise, the app should fall back to alternative authentication methods.

NSError *authError = nil;
NSString *myReasonString = @"Human readable string for reason access is being requested";

if ([myContext canEvaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics error:&authError])
{
    [myContext evaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics localizedReason:myReasonString reply:^(BOOL succes, NSError *error)
{
        if (success)
        {
                // Authenticated successfully
        }
        else
        {
                // Authenticate failed
        }
    }];
}
else
{
    // Could not evaluate policy; check authError
}

The LAContext can return several possible errors on failure; they are detailed in Table 19.4.

Image

Table 19.4 Touch ID Error Codes

Summary

This chapter covered using Keychain to secure small amounts of app data. The sample app covered setting and checking a PIN number for access to an app on launch. It also covered the storage and retrieval of multiple fields of credit card data. The chapter also introduced Touch ID, which is supported on the iPhone 5s and newer to enable the user to use his fingerprint to authenticate.

Keychain and data security is a large topic, and this chapter merely touches the tip of the iceberg. The development community is also seeking security professionals, especially in the mobile marketplace. Keychain is an exciting and vast topic that should now be much less intimidating. Hopefully, this introduction to securing data with Keychain will set you as a developer down a path of conscious computer security and prevent yet another story in the news about the loss of confidential information by a careless developer.

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

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