keychain iOS touchId prompted twice

988 Views Asked by At

I would like to add an item to keychain if it does not exists or update if it does exist. I call SecItemCopyMatching to check if the item exists passing the following query:

  NSDictionary *query = @{
                            (__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
                            (__bridge id)kSecAttrService: @"myservice",
                            (__bridge id)kSecReturnData: @NO
                            };    

SecItemCopyMatching prompts the user for the touchid/passcode. After that I need to update the keychain which prompts the user again. I can store a BOOL in my program that saves the state but that may be out of sync with the value in keychain so I would rather query the keychain itself if the item exists or not but I don't want the user to get double prompted. Is there any way to do that?

1

There are 1 best solutions below

0
On BEST ANSWER

A workaround here would be to call update first. It won't prompt you unless the item exists. If the update call returns an errSecItemNotFound, then add the data. This should only prompt you at most once. If you add an item, it shouldn't prompt you at all.

class func updateData(value: NSData, forKey keyName: String) -> Bool {
    let keychainQueryDictionary: NSMutableDictionary = self.setupKeychainQueryDictionaryForKey(keyName)
    let updateDictionary = [SecValueData:value]

    let status: OSStatus = SecItemUpdate(keychainQueryDictionary, updateDictionary)

    if status == errSecSuccess {
        return true
    } else if status == errSecItemNotFound {
        return setData(value, forKey: keyName)
    } else {
        return false
    }
}

class func setData(value: NSData, forKey keyName: String) -> Bool {
    var keychainQueryDictionary: NSMutableDictionary = self.setupKeychainQueryDictionaryForKey(keyName)

    keychainQueryDictionary[SecValueData] = value

    var error:Unmanaged<CFErrorRef>?
    let sacObject = SecAccessControlCreateWithFlags(kCFAllocatorDefault, kSecAttrAccessibleAfterFirstUnlock, SecAccessControlCreateFlags.UserPresence, &error)
    keychainQueryDictionary[SecAttrAccessControl] = sacObject.takeRetainedValue()

    let status: OSStatus = SecItemAdd(keychainQueryDictionary, nil)

    if status == errSecSuccess {
        return true
    } else {
        return false
    }
}

private class func setupKeychainQueryDictionaryForKey(keyName: String) -> NSMutableDictionary {
    var attributes = NSMutableDictionary()
    attributes[SecClass] = kSecClassGenericPassword as String
    attributes[SecAttrService] = "Sample Service 1"
    attributes[SecUseOperationPrompt] = "Operation prompt goes here"
    attributes[SecAttrAccount] = keyName

    return attributes
}

This code is modified from Jason Rendel's KeychainWrapper.