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: