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

Expose touchIDAuthenticationAllowableReuseDuration setting for SecureEnclaveValet #167

Closed
the-pear opened this issue Feb 27, 2019 · 12 comments

Comments

@the-pear
Copy link

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.

@dfed
Copy link
Collaborator

dfed commented Feb 27, 2019

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

@the-pear
Copy link
Author

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.

@dfed
Copy link
Collaborator

dfed commented Feb 28, 2019

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
Copy link
Collaborator

dfed commented Feb 28, 2019

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

@the-pear
Copy link
Author

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.

@dfed
Copy link
Collaborator

dfed commented Feb 28, 2019

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.

@the-pear
Copy link
Author

the-pear commented Feb 28, 2019

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!

@dfed
Copy link
Collaborator

dfed commented Dec 31, 2019

@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.

@the-pear
Copy link
Author

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

@the-pear
Copy link
Author

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

@dfed
Copy link
Collaborator

dfed commented Jan 27, 2020

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
Copy link
Collaborator

dfed commented Apr 22, 2024

This issue is five years old and hasn't gotten traction, so I'm closing it out. If folk want this, please do leave a comment.

@dfed dfed closed this as completed Apr 22, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants