iOS and Keychain Migration and Data Protection - Part 3

This is part 3 of a series of posts on the data protection classes used by the iOS Keychain services (iOS 4.0 and later). Part 1 covered the key concepts, part 2 introduced an example app that demonstrated setting the data protection classes. This final part digs into the detailed implementation of the UYLPasswordManager class that acts as a wrapper around the keychain services.

The UYLPasswordManager class

I have tried to keep the interface to the UYLPasswordManager class as simple as possible. Ideally I want the most common use cases to be baked into the class by default with a minimum of properties and methods to store and retrieve passwords to the keychain. The actual interface definition is show below and I will step through each of these methods to show the detailed implementation. For an example of how to use this class refer back to part 2 of this series of posts.

@interface UYLPasswordManager : NSObject;

@property (nonatomic,assign) BOOL migrate;
@property (nonatomic,assign) UYLPMAccessMode accessMode;

+ (UYLPasswordManager *)sharedInstance;
+ (void)dropShared;

- (void)purge;

- (void)registerKey:(NSString *)key forIdentifier:(NSString *)identifier
                                          inGroup:(NSString *)group;
- (void)deleteKeyForIdentifier:(NSString *)identifier
                       inGroup:(NSString *)group;
- (BOOL)validKey:(NSString *)key forIdentifier:(NSString *)identifier
                                       inGroup:(NSString *)group;

- (void)registerKey:(NSString *)key forIdentifier:(NSString *)identifier;
- (void)deleteKeyForIdentifier:(NSString *)identifier;
- (BOOL)validKey:(NSString *)key forIdentifier:(NSString *)identifier;
@end

The only prerequisites to using the class are that you link with the security framework (“Security.framework”) and include the class header file. It requires a minimum of iOS 4.0.

Creating the shared instance

I use a shared instance of the UYLPasswordManager to allow the results of a keychain operation to be cached. The shared instance is created the first time it is accessed with subsequent accesses returning a reference to the instance. This is not quite the same as a singleton instance as I take no steps to prevent other instances of UYLPasswordManager objects from being created by direct calls to alloc and init. However I find for iOS apps the simpler approach of a shared instance is sufficient. The code to create and drop the shared instance are shown below:

static UYLPasswordManager *_sharedInstance = nil;

+ (UYLPasswordManager *)sharedInstance {

  if (_sharedInstance == nil) {
    _sharedInstance = [[self alloc] init];
  }
  return _sharedInstance;
}

+ (void)dropShared {
  if (_sharedInstance) {
      [_sharedInstance release];
      _sharedInstance = nil;
  }
}

Hopefully this is fairly self explanatory, the sharedInstance method checks to see if we already have an object allocated in which case it is returned otherwise we allocate and init a new object. The dropShared method provides a way to release the shared instance though in practise this should not be necessary since the idea of the shared instance is that once created it lives for the life of the application.

Properties

Before looking at the class initialiser we will do a quick tour of the public and private properties of the class along with their accessor methods. There are two public properties that the user of the class can use to control whether a keychain item is migratable and which data protection class it will use (always available, available after first unlock or only available when the device is unlocked). Refer back to the first post in this series for a full discussion of these options. There is nothing special about these two properties which are declared in the public interface we saw earlier. The getter/setter methods are synthesized in the class implementation. The only piece I did not show is the definition of the enumerated type UYPMAccessMode which is used for the accessMode property. This is defined in the interface file as follows:

typedef enum _UYLPMAccessMode {
  UYLPMAccessibleWhenUnlocked = 0,
  UYLPMAccessibleAfterFirstUnlock = 1,
  UYLPMAccessibleAlways = 2
} UYLPMAccessMode;

The class has three other properties which are defined as part of the private interface inside the class implementation file as follows:

@property (nonatomic,retain) NSString *keychainValue;
@property (nonatomic,copy) NSString *keychainAccessGroup;
@property (nonatomic,copy) NSString *keychainIdentifier;

The keychain value property is used to store the value of the item that is retrieved from a search of the keychain. The other two properties are used to copy the keychain access group and identifier that are parsed as arguments by the caller in many of the class instance methods as we will see shortly.The getter/setter methods of all of these methods are synthesized by the compiler with the exception of the setter for the last two where we specify our own implementation. Here is the setter for the keychainIdentifier property (the keychainAccessGroup setter is very similar so I will omit it):

- (void)setKeychainIdentifier:(NSString *)newValue {

  if (!_keychainIdentifier && !newValue)  {
    return;
  }

  [_keychainIdentifier release];
  _keychainIdentifier = nil;
  self.keychainValue = nil;

  if (newValue) {
    _keychainIdentifier = [newValue copy];
  }
}

This is pretty much the same as a standard setter with the exception that when a new identifier is set we clear (and release) the previously cached keychainValue instance variable to force a new search of the keychain.

The Initialiser

The initialiser for a new instance of the class is not very exciting, after taking care of the standard stuff of calling the initialiser of its superclass it sets some defaults for the keychain attributes as follows:

- (id)init {
  self = [super init];
  if (self) {
    _migrate = YES;
    _accessMode = UYLPMAccessibleWhenUnlocked;
  }
  return self;
}

So by default all items added or updated to the keychain will be set to be migratable and will use the most protected data class which ensures they can only be accessed when the device is unlocked. Most of the time these defaults should be correct so it keeps things nice and simple for the user of the class.

The Public Methods

The public instance methods are fairly simple as the hard work of interacting with the keychain services is performed by a number of private helper methods. The methods to register, search for and delete a key are all available in two forms the only difference being that one set of methods omits the access group argument. I have previously discussed keychain group access to share keys between applications but since my guess is that most of the time it will not be used I opted to create a set of convenience methods without a group argument. Since these methods just call the fuller method with the group argument set to nil I will not show them here.

To get started we will take a look at the implementation of the method to register a new key with the keychain service. To store an item in the keychain you need to supply an identifier, most commonly a username or some other means of identifying the key, and the actual key which is the password or thing that you want to keep secret. The implementation is as follows:

- (void)registerKey:(NSString *)key forIdentifier:(NSString *)identifier
                                          inGroup:(NSString *)group {

  self.keychainAccessGroup = group;
  self.keychainIdentifier = identifier;
  [self searchKeychain];

  if (self.keychainValue == nil) {
    [self createKeychainValue:key];
  } else {
    [self updateKeychainValue:key];
  }

  [self searchKeychain];
}

This method starts by copying the access group and identifier into our instance variables for future reference. It then calls a helper method which we will see shortly to search the keychain to determine if we already have an entry for the specified identifier. Depending on the results of the search we then either call a method to create a new entry to store the key or update the existing entry. Finally we perform a keychain search to ensure our cached keychain value is valid.

The method to search the keychain accepts the usual arguments of an identifier and key and optionally an access group and returns a boolean to indicate if the search was a success or not.

- (BOOL)validKey:(NSString *)key forIdentifier:(NSString *)identifier
                                       inGroup:(NSString *)group {
  BOOL result = NO;

  if (identifier) {
    self.keychainAccessGroup = group;
    self.keychainIdentifier = identifier;
    [self searchKeychain];

    if (self.keychainValue != nil) {
      if (key != nil) {
        if ([self.keychainValue isEqual:key]) { 
          result = YES;
        }
      } else {
        result = YES;
      }
    }
  }
  return result;
}

As before the access group and identifier arguments are copied into our instance variables and then the method to search the keychain is called which will result in the keychainValue being set if the identifier was found. If we do not get a keychainValue back we return NO to the caller.

In some cases I am using the keychain to register that the user has completed an action, for example that they have registered a product. In that situation I do not really have a password key to store but it is useful to register an identifier in the keychain with some arbitrary key (the application name for example). Since in those situations I really only want to check to see if the identifier exists in the keychain I allow the key argument to validKey:forIdentifier:inGroup to be nil and do not bother to check that the key value actually matches.

The method to delete a key is shown below and simply calls the deleteKeychainValue private method after storing the arguments as usual:

- (void)deleteKeyForIdentifier:(NSString *)identifier
                       inGroup:(NSString *)group {
  self.keychainAccessGroup = group;
  self.keychainIdentifier = identifier;
  [self deleteKeychainValue];
}

The final public method is the purge method which is used to empty the cached keychainValue instance variable in situations where the device is about to be locked or the application is moving to the background. Refer to post 2 in this series for a more detailed discussion for how and why you may want to do this.

- (void)purge {
  self.keychainAccessGroup = nil;
  self.keychainIdentifier = nil;
  self.keychainValue = nil;
}

Private Methods

The heavy lifting of interacting with the keychain services is performed by a number of private methods. These methods are similar to ones I have discussed in earlier posts on the keychain so I will keep some of the explanations brief. If you are wondering about the various keychain classes and attributes refer back to that post for the full details. All of the interactions with the keychain API take a dictionary of attributes that indicates the class of item you want to store. We will be storing a generic password in the keychain so we need to initialise a dictionary as follows:

- (NSMutableDictionary *)newSearchDictionary {

  NSMutableDictionary *searchDictionary = nil;
  if (self.keychainIdentifier) {
    NSData *encodedIdentifier = [self.keychainIdentifier
                                 dataUsingEncoding:NSUTF8StringEncoding];

    searchDictionary = [[NSMutableDictionary alloc] init];
    [searchDictionary setObject:(id)kSecClassGenericPassword
                         forKey:(id)kSecClass];
    [searchDictionary setObject:encodedIdentifier
                         forKey:(id)kSecAttrGeneric];
    [searchDictionary setObject:encodedIdentifier
                         forKey:(id)kSecAttrAccount];
    [searchDictionary setObject:kKeychainService
                         forKey:(id)kSecAttrService];

    if (self.keychainAccessGroup) {
      [searchDictionary setObject:self.keychainAccessGroup
                           forKey:(id)kSecAttrAccessGroup];
    }
  }

  return searchDictionary;
}

The keychainIdentifier instance variable is encoded and used as the keychain account name. Note the use of a keychain service (kSecAttrService) and account name (kSecAttrAccount) attribute are required to uniquely identify a generic password item in the keychain (see this post on duplicate items for the details).

The methods to create, update and delete a keychain entry are then just variations of the same basic theme. First here is the method to search the keychain for the identifier stored in the private instance variable keychainIdentifier:

- (void)searchKeychain {

  if (self.keychainValue == nil) {
    NSMutableDictionary *searchDictionary = [self newSearchDictionary];

    [searchDictionary setObject:(id)kSecMatchLimitOne
                         forKey:(id)kSecMatchLimit];
    [searchDictionary setObject:(id)kCFBooleanTrue
                         forKey:(id)kSecReturnData];

    NSData *result = nil;
    OSStatus status = SecItemCopyMatching((CFDictionaryRef)searchDictionary,
                                          (CFTypeRef *)&result);
    [searchDictionary release];

    if (result) {
       self.keychainValue = [[NSString alloc] initWithData:result
                              encoding:NSUTF8StringEncoding];
      [result release];
    }
  }
}

The search method uses the Keychain function SecItemCopyMatching to perform the search with the attributes kSecMatchLimit and kSecReturnData to indicate we only want the first entry returned. If we find a value it is decoded and stored in the keychainValue instance variable.

The method to create keychain item uses the SecItemAdd function. The update method is very similar so I will omit it here for brevity but it makes use of the SecItemUpdate fucntion.

- (BOOL)createKeychainValue:(NSString *)password {
  NSMutableDictionary *dictionary = [self newSearchDictionary];

  NSData *passwordData = [password dataUsingEncoding:NSUTF8StringEncoding];
  [dictionary setObject:passwordData forKey:(id)kSecValueData];
  [dictionary setObject:(id)[self attributeAccess]
                 forKey:(id)kSecAttrAccessible];

  OSStatus status = SecItemAdd((CFDictionaryRef)dictionary, NULL);
  [dictionary release];

  if (status == errSecSuccess) {
    return YES;
  }

  return NO;
}

The interesting thing to note here is the kSecAttrAccessible attribute which is used to set the data protection class of the keychain item. The Keychain Service data protection class is determined from a combination of the two public properties (migrate and accessMode) of the UYLPasswordManager class. The data protection class is calculated by the method attributeAccess as follows:

- (CFTypeRef)attributeAccess {

  switch (self.accessMode) {
  case UYLPMAccessibleWhenUnlocked:
    return self.migrate ? kSecAttrAccessibleWhenUnlocked :
                          kSecAttrAccessibleWhenUnlockedThisDeviceOnly;
    break;
  case UYLPMAccessibleAfterFirstUnlock:
    return self.migrate ? kSecAttrAccessibleAfterFirstUnlock :
                          kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly;
    break;
  case UYLPMAccessibleAlways:
    return self.migrate ? kSecAttrAccessibleAlways :
                          kSecAttrAccessibleAlwaysThisDeviceOnly;
    break;
  default:
    return self.migrate ? kSecAttrAccessibleWhenUnlocked :
                          kSecAttrAccessibleWhenUnlockedThisDeviceOnly;
    break;
  }
}

So using the default settings of an item that is migratable and which can be accessed only when the device is unlocked we end up with a data protection class of kSecAttrAccessibleWhenUnlocked.

Finally the method to delete a keychain item which uses SecItemDelete:

- (void)deleteKeychainValue {

  NSMutableDictionary *searchDictionary = [self newSearchDictionary];

  if (searchDictionary) {
    OSStatus status = SecItemDelete((CFDictionaryRef)searchDictionary);
    [searchDictionary release];
  }
  self.keychainValue = nil;
}

Wrapping Up

This has been a long series of posts, so congratulations if you made it this far! I find the keychain service to be one of the hardest parts of iOS to understand which is a shame given that it serves such an important role. My intention was to take all of the tricky keychain code and wrap it with an easy to understand and hopefully easy to use class.

You can find the full code in the GitHub repository: