Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Get keychain item with LAContext and touchIDAuthenticationAllowableReuseDuration

I'm trying to add the functionality of touchIDAuthenticationAllowableReuseDuration to my app. I use Touch ID to authenticate a user into the app and at the same time recover an item from the keychain. Before I tried to add this there was no problem, it always asked for Touch ID or if not available for the device passcode. Now so far I've managed to make it so that it does the same thing, and when opening the app within the specified timeout it doesn't show the Touch ID prompt as it should, if all I was doing was authenticating the user I'd be done, but the problem that I'm having is that I also want to recover an item from the keychain, and when the prompt is bypassed with success, but once I call SecItemCopyMatching(…) I don't get the item back, instead I keep getting errSecAuthFailed.

I've looked online everywhere and the best thing I've found yet is Apple's sample code KeychainTouchID, but again, it doesn't do both authentication and getting an item from the keychain at the same time, I tried to add that to their code and I kept getting the same error as well.

Has anyone tried something like this before? How did you make it work? This is the code I have right now:

SecAccessControlRef sacObject = SecAccessControlCreateWithFlags(kCFAllocatorDefault, kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly, kSecAccessControlTouchIDAny, nil);

NSString *localizedReason = NSLocalizedString(@"Authenticate to access app", nil);

LAContext *context = [[LAContext alloc] init];
context.touchIDAuthenticationAllowableReuseDuration = 5;

[context evaluateAccessControl:sacObject operation:LAAccessControlOperationUseItem localizedReason:localizedReason reply:^(BOOL success, NSError *error) {
    if (success) {
        NSDictionary *query = @{(__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
                                (__bridge id)kSecAttrService: PASSCODE_KEY,
                                (__bridge id)kSecReturnData: @YES,
                                (__bridge id)kSecUseOperationPrompt: localizedReason,
                                (__bridge id)kSecUseAuthenticationUI: (__bridge id)kSecUseAuthenticationUIAllow,
                                (__bridge id)kSecAttrAccessControl: (__bridge_transfer id)sacObject,
                                (__bridge id)kSecUseAuthenticationContext: context
                                };

        CFTypeRef dataTypeRef = NULL;

        OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)(query), &dataTypeRef);
        // This works when using Touch ID / passcode, but I get errSecAuthFailed when the prompt isn't shown because of the reuse duration.
        if (status == errSecSuccess) {
            NSData *resultData = (__bridge_transfer NSData *)dataTypeRef;
            NSString *result = [[NSString alloc] initWithData:resultData encoding:NSUTF8StringEncoding];
            self.recoveredString = result;

        } else {
            self.recoveredString = @"";
        }
    } else {
        self.recoveredString = @"";
        CFRelease(sacObject);
    }
}];
like image 866
manolosavi Avatar asked Nov 08 '22 13:11

manolosavi


1 Answers

Don't create LAContext object each time. Just hold onto the LAContext object on which evaluateAccessControl has succeeded. That way you don't need to set touchIDAuthenticationAllowableReuseDuration. If you call evaluateAccessControl on a LAContext object on which evaluateAccessControl has already succeeded then reply callback is called immediately with success without user being asked to auth again

And when you want user to auth again, just invalidate the LAContext object.

like image 105
Animesh Das Avatar answered Nov 15 '22 04:11

Animesh Das