问题
I save the password to keychain with Touch ID flags:
+ (void)setPasscode:(NSString *)passcode
{
CFErrorRef error = NULL;
SecAccessControlRef sacObject;
sacObject = SecAccessControlCreateWithFlags(kCFAllocatorDefault,
kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly,
kSecAccessControlUserPresence, &error);
if(sacObject == NULL || error != NULL)
{
DLog(@"can't create sacObject: %@", error);
return;
}
NSDictionary *attributes = @{
(__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
(__bridge id)kSecAttrService: kKeychainServiceName,
(__bridge id)kSecValueData: [passcode dataUsingEncoding:NSUTF8StringEncoding],
(__bridge id)kSecUseNoAuthenticationUI: @YES,
(__bridge id)kSecAttrAccessControl: (__bridge_transfer id)sacObject
};
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
SecItemAdd((__bridge CFDictionaryRef)attributes, nil);
});
}
And retrieving it:
+ (void)getCurrentPasscodeWithSuccess:(void (^)(NSString *))success failure:(void (^)(OSStatus))failure
{
NSDictionary *query = @{
(__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
(__bridge id)kSecAttrService: kKeychainServiceName,
(__bridge id)kSecReturnData: @YES,
(__bridge id)kSecUseOperationPrompt: kOperationPrompt
};
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
CFTypeRef dataTypeRef = NULL;
OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)(query), &dataTypeRef);
if (status == errSecSuccess)
{
if (success) {
NSData *resultData = ( __bridge_transfer NSData *)dataTypeRef;
NSString *result = [[NSString alloc] initWithData:resultData encoding:NSUTF8StringEncoding];
success(result);
}
} else {
if (failure) {
failure(status);
}
}
});
}
This works fine. But if you disable Touch ID, and re-enable it on device, SecItemCopyMatching returns OSStatus -25300 (errSecItemNotFound). Problem is that item still exists there (I think). Because when I try tio access it, Touch ID prompt comes up.
I tried to check if item exists with the following method:
+ (void)checkIfPasscodeExistsInKeychainWithCompletion:(void (^)(BOOL))completion
{
NSDictionary *query = @{
(__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
(__bridge id)kSecAttrService: kKeychainServiceName,
};
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
CFTypeRef dataTypeRef = NULL;
OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)(query), &dataTypeRef);
BOOL exists = status != errSecItemNotFound;
if (completion) {
completion(exists);
}
});
}
And this triggers the Touch ID prompt, and then returns error that it doesn't exist when touch is provided.
But if I remove (__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword line, I get the status that it exists in keychain.
回答1:
You are using kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly as the Accessiblity constant for your item. This means that the item can only be added when there is a passcode set on the device, with the addition of the TouchID. If you remove your device passcode (or in your case TouchID) after your item is saved in the keychain, this item will no longer be available.
Even if you add the device passcode (or TouchID) back, this item is no longer available. You have to create a duplicate copy of the same item.
For more information on kSecAttrAccessible constant, refer the docs: Keychain Item Accessibility Constants.
For information on how the whole process works, refer Apple's security whitepaper
回答2:
It looks like an Apple bug, I've opened rdar://24237713 some time ago.
SecItemCopyMatching with queries containing match limit other than kSecMatchLimitAll will return incorrect results after device passcode was turned on & off. It seems like old items which are not accessible anymore aren't deleted from internal storage but only marked as unavailable, but they are still participating in creating result set.
Test project to demonstrate the issue: https://github.com/mndgs/TestKeychainBug
来源:https://stackoverflow.com/questions/32721826/keychain-error-25300-errsecitemnotfound-when-disabling-and-re-enabling-touch