I thought I was done with Keychain access on the iPhone but I hit a problem which made me realise there was still a gap in my knowledge (and in the keychain documentation). The problem I was seeing came when trying to add a second password entry of class kSecClassGenericPassword
. A search for the entry was not finding anything but when I tried to add the new entry using SecItemAdd
I was getting the error errSecDuplicateItem (-25299)
indicating that the item already exists.
When adding an entry to the keychain I was only including the kSecAttrGeneric
attribute as an identifier and relying on the value of this identifier when retrieving the entry from the keychain. The problems is that for a keychain entry of class kSecClassGenericPassword
the attributes that are used to determine a unique entry are kSecAttrAccount
and kSecAttrService
. Since I was not including these entries my second password was not considered to be unique. The fact that it had a different value for kSecAttrGeneric
did not matter as that is not used to determine uniqueness. Of course you can search on all attributes which explains why the search was coming up empty.
In database terms you could think of their being a unique index on the two attributes kSecAttrAccount
, kSecAttrService
requiring the combination of those two attributes to be unique for each entry in the keychain.
Unfortunately the problem with the keychain services is that these finer points are very poorly documented. There is a passing reference to the required attributes in the Keychain Services Programming Guide, in the section Keychain Services Ease of Use (my emphasis):
To create a keychain item and add it to a keychain in Mac OS X, for example, you call one of two functions, depending on whether you want to add an Internet password or some other type of password: SecKeychainAddInternetPassword or SecKeychainAddGenericPassword. In your function call, you pass only those attributes for which there is no obvious default value. For example, you must specify the name of the service and the user’s account name, but you do not have to specify the creation date and creator, because the function can figure those out by itself. You also pass the data (usually a password) that you want to store in the keychain.
You have to be paying pretty close attention to spot that. The best reference I could find was on an Apple CDSA mailing list which put me on the right track but as far as I know the key attributes for each keychain class are not included in any Apple documentation that I have been able to find. I will go back and fix the example code in the previous post but the important piece is when building the dictionary used when searching or adding to the keychain:
static NSString *serviceName = @"com.mycompany.myAppServiceName";
- (NSMutableDictionary *)newSearchDictionary:(NSString *)identifier {
NSMutableDictionary *searchDictionary = [[NSMutableDictionary alloc] init];
[searchDictionary setObject:(id)kSecClassGenericPassword forKey:(id)kSecClass];
NSData *encodedIdentifier = [identifier dataUsingEncoding:NSUTF8StringEncoding];
[searchDictionary setObject:encodedIdentifier forKey:(id)kSecAttrGeneric];
[searchDictionary setObject:encodedIdentifier forKey:(id)kSecAttrAccount];
[searchDictionary setObject:serviceName forKey:(id)kSecAttrService];
return searchDictionary;
}
In this example I have just used a fixed service name and set the account name to the identifier which I expect to be unique for each password stored.