Determine if Touch ID-Protected Keychain Item Exists?
Asked Answered
D

2

19

Is there a way to determine if an item (password, token, etc.) has been set in the iOS keychain using Touch ID access control without prompting the user for Touch ID? We have a need to determine if the credential has already been saved to the keychain (with Touch ID protection) before performing an operation, but we don't want to interrupt the user with the Touch ID prompt.

I've tried the following...

NSMutableDictionary *query = ...
query[(__bridge id)kSecUseNoAuthenticationUI] = (__bridge id)kCFBooleanTrue;

OSStatus opStatus = SecItemCopyMatching((__bridge CFDictionaryRef)query, NULL);

...

However, when this code gets called the user still sees the Touch ID prompt. We don't want ANYTHING to be displayed on the UI, and just want an error returned in the OSStatus if Touch ID would have been required.

Any thoughts?

Diabetic answered 10/12, 2015 at 17:11 Comment(0)
O
13
NSDictionary *query = @{
                        (__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
                        (__bridge id)kSecAttrService: @"SampleService",
                        (__bridge id)kSecUseNoAuthenticationUI: @YES
                        };

dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    CFTypeRef dataTypeRef = NULL;
    OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)(query), &dataTypeRef);
    if (status == errSecInteractionNotAllowed) {
        NSLog(@"ITEM EXIST");
    } else if (status == errSecItemNotFound) {
        NSLog(@"ITEM DOES NOT EXIST");
    } else {
        NSLog(@"status: %@", @(status));
    }
});
Odoacer answered 5/2, 2016 at 9:24 Comment(0)
U
16

based on neoneye's code and Swift 3. I've added errSecAuthFailed.

    query[kSecClass as String] : kSecClassGenericPassword,
    query[kSecAttrService as String] : "serviceName"    
    query[kSecUseAuthenticationUI as String] = kSecUseAuthenticationUIFail

    DispatchQueue.global().async {

        var result : AnyObject?
        let status = SecItemCopyMatching(query as CFDictionary, &result)

        if status == errSecInteractionNotAllowed {

            DispatchQueue.main.async {

                // item exists
            }
        } else if status == errSecAuthFailed {

            DispatchQueue.main.async {

                // item exists but someone removed the touch id or passcode
            }
        } else if status == errSecItemNotFound {

            DispatchQueue.main.async {

                // it does not exist
            }
        } else {

            DispatchQueue.main.async {

                // another OSStatus
            }
        }
    }
Upthrow answered 22/7, 2017 at 23:2 Comment(2)
Not to those wondering why kSecUseNoAuthenticationUI : @YES in @neoneye's answer was changed to kSecUseAuthenticationUI : kSecUseAuthenticationUIFail: The former has been deprecated in favor of the latter. See: developer.apple.com/documentation/security/…Omophagia
btw. you can get errSecAuthFailed if Touch ID is locked by iOS due to too many failed attempts (requires user to go through passcode screen to restore)Bacolod
O
13
NSDictionary *query = @{
                        (__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
                        (__bridge id)kSecAttrService: @"SampleService",
                        (__bridge id)kSecUseNoAuthenticationUI: @YES
                        };

dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    CFTypeRef dataTypeRef = NULL;
    OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)(query), &dataTypeRef);
    if (status == errSecInteractionNotAllowed) {
        NSLog(@"ITEM EXIST");
    } else if (status == errSecItemNotFound) {
        NSLog(@"ITEM DOES NOT EXIST");
    } else {
        NSLog(@"status: %@", @(status));
    }
});
Odoacer answered 5/2, 2016 at 9:24 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.