I’ve been successfully able to implement TouchID with keychain as well as Keychain Sharing (syncing keychain items between multiple devices) separately. When I try to do them both, I get an error “-50“ which is invalid parameters. From the below code, removing either kSecAttrAccessControl or kSecAttrSynchronizable works as expected.
Based on my experience (read - a few days of frustration) so far, and based on the capabilities of some keychain API simplification tools like UICKeychainStore, it seems like if I use Touch ID Authentication, Keychain Sharing wouldn’t work and vice versa. I’m looking for an Apple documentation that would state that, but unable to find it.
I’ve gone through Apple’s SecItem.h page, and a useful info I found states the following about kSecAttrAccessible and kSecAttrSynchronizable: “If both attributes are specified on either OS X or iOS, the value for the kSecAttrAccessible key may only be one whose name does not end with “ThisDeviceOnly", as those cannot sync to another device.” However, I'm not using "ThisDeviceOnly" (I'm currently using kSecAttrAccessibleAlways for testing purposes)
Can you help in pointing out if and where Apple has documented this limitation? That would help me document it for the records, and move on. Thanks.
- (void)addKeychainItemWithIdentifier:(NSString *)identifier andData:(NSData *)data {
CFErrorRef error = NULL;
SecAccessControlRef sacObject;
sacObject = SecAccessControlCreateWithFlags(kCFAllocatorDefault,
kSecAttrAccessibleAlways,
kSecAccessControlUserPresence, &error);
if(sacObject == NULL || error != NULL)
{
NSString *msg0 = [NSString stringWithFormat:NSLocalizedString(@"SEC_ITEM_ADD_CAN_CREATE_OBJECT", nil), error];
[self printResultWithMessage:msg0];
return;
}
NSDictionary *attributes = @{
(__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
(__bridge id)kSecValueData: data,
(__bridge id)kSecAttrAccessible:(__bridge id)kSecAttrAccessibleAlways,
(__bridge id)kSecAttrService: identifier,
(__bridge id)kSecAttrSynchronizable:(__bridge id)kCFBooleanTrue,
(__bridge id)kSecAttrAccessControl: (__bridge_transfer id)sacObject
};
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
OSStatus status = SecItemAdd((__bridge CFDictionaryRef)attributes, nil);
NSError *statuserror = [NSError errorWithDomain:NSOSStatusErrorDomain code:status userInfo:nil];
[self printResultWithMessage:[self keychainErrorToString:status]];
});
}