Valet icon indicating copy to clipboard operation
Valet copied to clipboard

Expose touchIDAuthenticationAllowableReuseDuration setting for SecureEnclaveValet

Open the-pear opened this issue 6 years ago • 11 comments

Like apple doc says.

This bypasses a scenario where the user unlocks the device and then is almost immediately prompted for another fingerprint.

Some times that scenario is really annoying.

the-pear avatar Feb 27 '19 04:02 the-pear

Seems like something that could be added to SinglePromptSecureEnclaveValet‘s initializer.

That said, I’m not convinced this API is necessary yet: I need to better understand the use case. Are you avoiding keeping the secret you need access to in memory? Would love to get a sense of the exact flow you’re solving for here – it can help us design an API that works

dfed avatar Feb 27 '19 08:02 dfed

I'd like to set touchIDAuthenticationAllowableReuseDuration with accessControl: .userPresence The scenario I want to avoid is like this:

  1. User unlocks their device by touch/face ID or pin code.
  2. User launch our app immediately.

At step 2, there is no point to ask user for touch/face ID again because they just unlocked the device a few seconds before.

the-pear avatar Feb 28 '19 02:02 the-pear

Got it. So this is for the first keychain access your application makes. Seems like a reasonable addition to the API, though we’ll want the naming to reflect that this API works across all user presence tests (we’ll need to test if it works with passcode and Face ID).

dfed avatar Feb 28 '19 03:02 dfed

Btw, I don’t see us adding this to SecureEnclaveValet, but I can see this API on SinglePromptSecureEnclaveValet. Does that work for you?

dfed avatar Feb 28 '19 03:02 dfed

It can be added to SecureEnclaveValet too, Just add a LAContext to keychainQuery in: private init(identifier: Identifier, accessControl: SecureEnclaveAccessControl)

Would be nice if both SecureEnclaveValet & SinglePromptSecureEnclaveValet can config this property.

the-pear avatar Feb 28 '19 03:02 the-pear

My thinking there is that SecureEnclaveValet is built for always prompting, while SinglePromptSecureEnclaveValet is built for prompting less aggressively. Applying the reusable duration to both APIs would make the difference between the two less clear. Also worth remembering that SinglePromptSecureEnclaveValet has a method to require that the next keychain access prompts, so you can make it behave more like a SecureEnclaveValet when desired.

If you think SecureEnclaveValet also needs this API despite the above, I’d love to hear the use case that motivates that thinking.

dfed avatar Feb 28 '19 06:02 dfed

OK, I see your point.

Also worth remembering that ‘SinglePromptSecureEnclaveValet‘ has a method to require that the next keychain access prompts, so you can make it behave more like a ‘SecureEnclaveValet‘ when desired.

Thanks for the tip!

the-pear avatar Feb 28 '19 07:02 the-pear

@the-pear did you ever get touchIDAuthenticationAllowableReuseDuration to work on a Face ID device?

On my Face ID device, I've tried the following configurations:

  • SinglePromptSecureEnclaveValet with userPresence accessControl. When I try to access the value within the reuse direction, I get a errSecAuthFailed. As soon as the reuse duration expires, queries work again (but prompt for Face ID).
  • SinglePromptSecureEnclaveValet with biometricAny accessControl. When I try to access the value within the reuse duration, I still get a Face ID prompt.
  • SinglePromptSecureEnclaveValet with devicePasscode accessControl. When I try to access the value within the reuse duration, I still get a passcode prompt.

It seems possible that this reuse identifier only works on Touch ID devices, and would need to be left off on Face ID devices. I won't have access to a Touch ID device for a couple weeks, so any help you could provide would be great.

dfed avatar Dec 31 '19 17:12 dfed

Unfortunately I don't have any touch-id device at hand...

the-pear avatar Jan 10 '20 05:01 the-pear

SinglePromptSecureEnclaveValet with userPresence accessControl. When I try to access the value within the reuse direction, I get a errSecAuthFailed. As soon as the reuse duration expires, queries work again (but prompt for Face ID).

I am not using Valet now, but working with plain keychain api. touchIDAuthenticationAllowableReuseDuration works fine on Face ID device.

This code works as expected on my iPhone X iOS 13.3.

var query: [String: Any] = [kSecClass as String: kSecClassGenericPassword, kSecAttrService as String: serviceName]
let context = LAContext()
context.touchIDAuthenticationAllowableReuseDuration = gracePeriod
query[kSecUseAuthenticationContext as String] = context
query[kSecAttrAccessControl as String] = access

the-pear avatar Jan 10 '20 05:01 the-pear

Thank you for the information! That helped me track down what was causing my local failures: in order to retrieve a value with touchIDAuthenticationAllowableReuseDuration, the value must have been set with a LAContext that had a non-default touchIDAuthenticationAllowableReuseDuration value.

In other words, the following write works:

var writeQuery: [String: Any] = [kSecClass as String: kSecClassGenericPassword, kSecAttrService as String: "ReuseDurationTest"]
let writeContext = LAContext()
writeContext.touchIDAuthenticationAllowableReuseDuration = 10.
writeQuery[kSecUseAuthenticationContext as String] = writeContext
writeQuery[kSecAttrAccessControl as String] = SecAccessControlCreateWithFlags(nil, kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly, SecAccessControlCreateFlags.userPresence, nil)
writeQuery[kSecAttrAccount as String] = username
writeQuery[kSecValueData as String] = Data(stringToSet.utf8)
print("Delete: \(SecItemDelete(writeQuery as CFDictionary))")
print("Set: \(SecItemAdd(writeQuery as CFDictionary, nil))")

But the following write will fail when read during the reuse duration:

var writeQuery: [String: Any] = [kSecClass as String: kSecClassGenericPassword, kSecAttrService as String: "ReuseDurationTest"]
let writeContext = LAContext()
writeQuery[kSecUseAuthenticationContext as String] = writeContext
writeQuery[kSecAttrAccessControl as String] = SecAccessControlCreateWithFlags(nil, kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly, SecAccessControlCreateFlags.userPresence, nil)
writeQuery[kSecAttrAccount as String] = username
writeQuery[kSecValueData as String] = Data(stringToSet.utf8)
print("Delete: \(SecItemDelete(writeQuery as CFDictionary))")
print("Set: \(SecItemAdd(writeQuery as CFDictionary, nil))")

Similarly, the below write will also fail to be read:

var writeQuery: [String: Any] = [kSecClass as String: kSecClassGenericPassword, kSecAttrService as String: "ReuseDurationTest"]
writeQuery[kSecAttrAccessControl as String] = SecAccessControlCreateWithFlags(nil, kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly, SecAccessControlCreateFlags.userPresence, nil)
writeQuery[kSecAttrAccount as String] = username
writeQuery[kSecValueData as String] = Data(stringToSet.utf8)
print("Delete: \(SecItemDelete(writeQuery as CFDictionary))")
print("Set: \(SecItemAdd(writeQuery as CFDictionary, nil))")

Here's my read code for reference:

var readQuery: [String: Any] = [kSecClass as String: kSecClassGenericPassword, kSecAttrService as String: "ReuseDurationTest"]
let readContext = LAContext()
readContext.touchIDAuthenticationAllowableReuseDuration = 10 // this value does not need to match the value set above
readQuery[kSecUseAuthenticationContext as String] = readContext
readQuery[kSecAttrAccessControl as String] = SecAccessControlCreateWithFlags(nil, kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly, SecAccessControlCreateFlags.userPresence, nil)
readQuery[kSecAttrAccount as String] = username
readQuery[kSecMatchLimit as String] = kSecMatchLimitOne
readQuery[kSecReturnData as String] = true
var result: AnyObject? = nil
print("Query: \(SecItemCopyMatching(readQuery as CFDictionary, &result))")
print("Result: \(result)")

This discovery means that in order to ensure forwards compatibility, I'd likely need to create a new Valet type to support this functionality. Otherwise, values set prior to enabling this functionality wouldn't be readable during the reuse duration, which isn't great.

dfed avatar Jan 27 '20 01:01 dfed