Context
I am developing an iOS app, which should be protected by local authentication. The user has to unlock the app by using fingerprint/password/… after opening the app. The app should stay unlocked while the app is running in the foreground. While the app is unlocked, the app has to access several items from the keychain (some items may be accessed multiple times while the app is running).
Keychain access control with .userPresence
Setting the access control of a keychain item to .userPresence
enforces the local authentication before the item is accessed. However, this does not really fit my use case, because the user would have to authenticate every time an item from the keychain is accessed.
// storing keychain items with .userPresence access control
guard let accessControl = SecAccessControlCreateWithFlags(
nil,
kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
.userPresence,
nil
) else {
throw CommonError("Unable to create access control flags")
}
let query = [
kSecClass: kSecClassGenericPassword,
kSecAttrAccount: account,
kSecAttrAccessControl: accessControl,
kSecValueData: ...
] as [String: Any]
SecItemAdd(query as CFDictionary, nil)
To fit my use case, I could implement the local authentication by myself and store the keychain items with kSecAttrAccessible: kSecAttrAccessibleWhenUnlockedThisDeviceOnly
(instead of .userPresence
). I am concerned, that storing the keychain item without .userPresence
could cause security risks. Does it make a difference, if I implement the local authentication by myself and access the keychain items with kSecAttrAccessible: kSecAttrAccessibleWhenUnlockedThisDeviceOnly
instead of using access control with .userPresence
?
// storing keychain items without access control
let query = [
kSecClass: kSecClassGenericPassword,
kSecAttrAccount: account,
kSecAttrAccessible: kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
kSecValueData: ...
] as [String: Any]
SecItemAdd(query as CFDictionary, nil)
I couldn't find any articles or official sources which explain my concerns. I would be grateful if could link any official documentation, or maybe even provide a better solution to my use case.
TL;DR
Does setting the access control of a keychain item to .userPresence
improve the security in any way, or would it make no difference if I implement the local authentication by myself and store the keychain item with kSecAttrAccessible: kSecAttrAccessibleWhenUnlockedThisDeviceOnly
?
Thank you for your help!
CodePudding user response:
On a jailbroken device, or at least with sufficient privilege escalation on a non-jailbroken device, an attacker can masquerade as your app, to get access to your keychain item(s). Having .userPresence
improves the security in the sense that, even in such a scenario where an attacker masquerades as your app, the keychain will enforce the local authentication for that use of your keychain item, i.e., when an attacker tries to use your keychain item, the local authentication will kick in.
Clues on how such an attack might be possible, could be found in analyses of jailbreaking, such as this talk.
If you implemented local authentication yourself, say, in your app, then the attacker can completely bypass it by not even needing to run your app (in the scenario just described). As far as keychain is concerned, when the attacker is requesting access to your keychain item, your app is requesting access to your keychain item.
Besides this, there may be other ways an attacker may attempt to bypass your implementation of the local authentication, e.g., using tools like frida to tamper with the logic you have implemented. As far as I know, it is harder to tamper with the local authentication logic when you use .userPresence
, as that would be controlled by iOS rather than your app logic.
Now, even with .userPresence
enforcing local authentication, attackers may still attempt to trick the user into doing local authentication at that point when they are attempting to access your keychain item, but that is for another discussion. At least, with .userPresence
, it should be harder to prevent the local authentication from happening than with your own implementation.
Ok, so getting back to what you might consider doing in your use case. Two possibilities might be:
- There are various mobile app protection techniques that you can find on GitHub, etc., e.g., to detect if your app is running on a jailbroken device, as well as possibility detect other kinds of tampering to your app (and there are also commercial offerings of such capabilities, and even alternative ways to securely store cryptographic materials). So, if you do your risk assessment and consider that such techniques can sufficiently protect against running on jailbroken devices, then your original approach of implementing local authentication by myself may still be considered a good solution, in conjunction with such techniques.
- You may instead consider using
.userPresence
for security reasons, and re-examine your app flows and UX, for example, to reduce the frequency of needing to do those encryption/decryption operations, batching them, or even showing something in your user interface to explain to the user why they need to do the local authentication each time. Depending on your app's features and how you explain it, perhaps users may even feel more confident in the security of your app if you can help them to understand why they need to do local authentication when they do?