Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Check if item exists in keychain without Touch ID auth #159

Closed
ky1ejs opened this issue Oct 27, 2015 · 9 comments
Closed

Check if item exists in keychain without Touch ID auth #159

ky1ejs opened this issue Oct 27, 2015 · 9 comments

Comments

@ky1ejs
Copy link
Contributor

ky1ejs commented Oct 27, 2015

Is this possible? Is it possible to see if a value exists at a given key in a keychain secured with Touch ID without prompting the user for their finger print?

If not, is there a way to check if the keychain, as a whole, exists without prompting for authentication?

I was torn as to whether I should ask this on here or on StackOverflow. I hope you don't mind me asking here? 😇 😅. I figured since I'm using your framework and couldn't find anything about my question in the README, I'd be coming back to you with any answer I got on SO to see how I do it via KeychainAccess.

Many thanks!

@kishikawakatsumi
Copy link
Owner

@kylejm I've tried a various way of that, there is no way to check whether the value exists or not, I think 😢 . If there is a possibility that the value would be protected with Touch ID, you should always execute keychain method asynchronously.

@ky1ejs
Copy link
Contributor Author

ky1ejs commented Oct 28, 2015

Ah that's lame. No worries, I've worked around the problem by storing a flag to mark that the credential is stored. I hate this solution! 😩. Thanks for the reply :)

@ky1ejs ky1ejs closed this as completed Oct 28, 2015
@ChristopherCarranza
Copy link

@kishikawakatsumi would something like this be acceptable? Keeping in mind the below code is just a generic keychain query for example purposes, also the code below is specific to something we know is locked behind TouchID in the keychain. We currently use this to solve that problem. I'd def like to be using this as part of the framework.

public func contains(_ key: String) -> Bool {
        // We spcify kSecUseAuthenticationUIFail so that the error
        // errSecInteractionNotAllowed will be returned if an item needs
        // to authenticate with UI and the authentication UI will not be presented.
        let keychainQuery: [AnyHashable: Any] = [
            kSecClass as AnyHashable: kSecClassGenericPassword,
            kSecAttrService as AnyHashable: Bundle.main.bundleIdentifier!,
            kSecAttrAccount as AnyHashable: key,
            kSecUseAuthenticationUI as AnyHashable: kSecUseAuthenticationUIFail
        ]
        
        var result: AnyObject?
        let status = SecItemCopyMatching(keychainQuery as CFDictionary, &result)

        // If that status is errSecInteractionNotAllowed, then
        // we know that the key is present, but you cannot interact with
        // it without authentication. Otherwise, we assume the key is not present.
        return status == errSecInteractionNotAllowed
    }

Maybe something like this in Keychain

public func contains(_ key: String) throws -> Bool {
        var query = options.query()
        query[AttributeAccount] = key
        query[UseAuthenticationUI] = UseAuthenticationUIFail
        
        let status = SecItemCopyMatching(query as CFDictionary, nil)
        switch status {
        case errSecSuccess, errSecInteractionNotAllowed:
            return true
        case errSecItemNotFound:
            return false
        default:
            throw securityError(status: status)
        }
    }

@MikeBenton2
Copy link

@ChristopherCarranza Great thinking! This was an amazing workaround!

@hmlongco
Copy link

Noting that UseAuthenticationUIFail is deprecated as of iOS 14.

I'm using something like the following...

    func has(service: String, account: String, options: [Options] = []) -> Bool {
        var query = self.query(service: service, account: account, options: options)
        query[kSecUseAuthenticationContext as String] = internalContext

        var secItemResult: CFTypeRef?
        status = SecItemCopyMatching(query as CFDictionary, &secItemResult)

        if status == errSecSuccess || status == errSecInteractionNotAllowed {
            return true
        }

        return false
    }

    private var internalContext: LAContext? = {
        let context = LAContext()
        context.interactionNotAllowed = true
        return context
    }()

@lolgear
Copy link

lolgear commented Jan 25, 2021

@hmlongco
Thanks for your example.
I have this comment above query setup.

        // specify kSecUseAuthenticationUIFail so that the error
        // errSecInteractionNotAllowed will be returned if an item needs
        // to authenticate with UI and the authentication UI will not be presented.

Will this behavior persist if I change query to LAContext-based query with context.interactionNotAllowed = true?

@fritzfr
Copy link

fritzfr commented Jan 5, 2022

The code from @ChristopherCarranza above works, but you should also check for errSecSuccess:

private func checkForKeychainItemExistence(itemName: String, completion: @escaping (Bool) -> Void) {
	let keychainQuery: [AnyHashable: Any] = [
		kSecClass as AnyHashable: kSecClassGenericPassword,
		kSecAttrService as AnyHashable: "KEYCHAIN_SERVICE_NAME_HERE",
		kSecAttrAccount as AnyHashable: itemName,
		kSecUseAuthenticationUI as AnyHashable: kSecUseAuthenticationUIFail
	]
	
	var result: AnyObject?
	let status = SecItemCopyMatching(keychainQuery as CFDictionary, &result)

        // also check for errSecSuccess here!
	completion(status == errSecInteractionNotAllowed || status == errSecSuccess)
}

@fritzfr
Copy link

fritzfr commented Jan 25, 2022

After playing around with it a lot I noticed some inconsistent behaviour of the SecItemCopyMatching status response.

Sometimes, it returns 0, meaning errSecSuccess. So to truly check if an item is there, you should also check for that code.

@hoanglk25
Copy link

The code from @ChristopherCarranza above works, but you should also check for errSecSuccess:

private func checkForKeychainItemExistence(itemName: String, completion: @escaping (Bool) -> Void) {
	let keychainQuery: [AnyHashable: Any] = [
		kSecClass as AnyHashable: kSecClassGenericPassword,
		kSecAttrService as AnyHashable: "KEYCHAIN_SERVICE_NAME_HERE",
		kSecAttrAccount as AnyHashable: itemName,
		kSecUseAuthenticationUI as AnyHashable: kSecUseAuthenticationUIFail
	]
	
	var result: AnyObject?
	let status = SecItemCopyMatching(keychainQuery as CFDictionary, &result)

        // also check for errSecSuccess here!
	completion(status == errSecInteractionNotAllowed || status == errSecSuccess)
}

KEYCHAIN_SERVICE_NAME_HERE what that ?, I can't find service name

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

8 participants