Ad

Our DNA is written in Swift
Jump

Keychain Management Revamped

For the past several years I have been using an ugly hacked together class AccountManager in several projects for saving and retrieving generic passwords on the keychain. A couple of months ago I finally got around to replacing this with a well-designed thought-through component, DTKeychain, which works equally well on iOS and OS X.

You might wonder: why the hell is there a need for yet another new Keychain wrapper? For the simple reason that I like to understand what’s going on under the hood. Another reason is the fact that I am offering Non-Attribution Licenses for all my open source components and this makes it awkward if the components are referencing other peoples open source projects. I have no right to waive the attribution requirements for other people.

The impetus for creating DTKeychain was that for the open source iOS SDK for ProductLayer I needed to persist authentication tokens. Since both are available via CocoaPods the dependency is easily added.

Security.framework

With the previous AccountManager I tried to be too smart for my own good by having it do some unnecessary state management. Interaction with the keychain at the lowest level is done via multiple functions prefixed SecItem*. All of them return a status code.

So my goal was to have Objective-C equivalents to these functions following a different pattern we are used there:

- (NSArray *)keychainItemsMatchingQuery:(NSDictionary *)query 
                                  error:(NSError *)error

If this method returns nil then the error object is returned with the NSError object being filled in. In the case that truly no keychain items were found then an empty array is being returned.

I made this an instance method – as opposed to a class method – because this allows for the keychain class to be mocked in unit tests should you so choose. That’s much harder to do if all methods are class methods.

Keychains can hold different kinds of items but generally people only seem to be needing the Generic Password type. All keychain item fields are public except one: kSecAttrGeneric. This gets encrypted with a strong key that is tied to the user’s passcode.

OS X versus iOS

On OS X here also lies one major difference to iOS. On Macs you can query all fields of all keychain items without user interaction. But the first time to access the generic attribute value, there is a popup asking the user if he wants to permit that. This is why there is a separate function for querying that.

- (BOOL)retrieveSecuredDataForKeychainItem:(DTKeychainItem *)keychainItem
                                     error:(NSError *)error;

You never need to call this on iOS since there the retrieved items always contain the decrypted passwords. On OS X though, you need to call this method at that latest possible moment. Your app is halted while the prompt is showing.

Another difference concerns Sandboxing. iOS automatically limits access to keychains with the same application identifier prefix. This allows you to keep one keychain for multiple apps of yours.

If you run a wild card query for generic passwords on your Mac you will probably get hundreds of passwords, for websites, WiFi routers and many more. But fret not, this is not a security leak, since the user would have to approve access to the encrypted passwords for each app that is trying to access any item.

On most other keychain wrappers you cannot do such an enlightening wildcard query because they add a unique identifier (usually the application identifier) to all queries. The reason for this is to make keychain queries act the same one both OSes. I’d rather use a good qualifier for the service field (e.g. com.cocoanetics.MyService) to restrict queries to that.

Queries

A common pattern that I’ve seen in the code of all keychain wrappers I looked at related to the way of identifying keychain items. One way is by specifying a query. This is a dictionary with keys and values. Matching items are returned and/or affected by the function.

Those other wrappers would always use a query for the primary key. For generic passwords the key fields are kSecAttrAccount and kSecAttrService. This leads to the complexity of having to distinguish of old and new values for these key fields when those get modified.

I am only using these queries for doing wild card searches on the keychain. For subsequent updating or deletion of those items, I instead use the permanent reference kSecValuePersistentRef.

To query for all generic passwords for the foo service:

// get shared instance
DTKeychain *keychain = [DTKeychain sharedInstance];
 
// create a keychain query for generic passwords
NSDictionary *query = [DTKeychainGenericPassword
                       keychainItemQueryForService:@"foo" 
                                           account:nil];
 
// retrieve matching keychain items
NSError *error;
NSArray *items = [keychain keychainItemsMatchingQuery:query error:&error];
 
if (!items)
{
   NSLog(@"%@", [error localizedDescription]);
}

For such an item changing the stored password is as simple as:

pass.password = @"different";
 
NSError *error;
if (![keychain writeKeychainItem:pass error:&error])
{
   NSLog(@"%@", [error localizedDescription]);
}

This makes the usage of DTKeychain simple and intuitive, because it follows the same paradigms Apple is using for other methods.

In particular I would like to direct your attention to the _errorForOSStatus: method which creates properly worded NSError objects for the integer coming out of the SecItem* functions. This is also something I have not seen before elsewhere.

Conclusion

By not blindly copying code from previously existing keychain wrappers I was able to wrap my head around how the underlying keychain functions are really working. So can you if you look at my code.

Feel free to use DTKeychain in your own apps via Cocoapods or git submodule. This is my method of choice now for interacting with the keychains of iOS and OS X. Of course it has unit tests and appledoc-style documentation comments.

If you appreciate my work, please also check out recently released book.


Categories: Q&A

1 Comment »

Trackbacks

  1. DTKeychain 1.0.2 | Cocoanetics

Leave a Comment