Search code examples
ioskeychain

SecItemAdd() Succeeds with kSecAttrAccessibleWhenUnlocked But Fails with kSecAttrAccessibleWhenUnlockedThisDeviceOnly


The Story So Far

Four months ago, I posted this question because the upgrade to iOS 13 was breaking my keychain-related code.

My code stores the user's password in the keychain using class kSecClassGenericPassword and access attribute kSecAttrAccessibleWhenUnlocked. As explained in my own answer to that question, I finally got my code to work also on iOS 13 by cleaning up the query dictionaries a bit.

The Current Problem

A few weeks ago, I was asked to disable backing up of the password data to enhance security, so I changed the access attribute to kSecAttrAccessibleWhenUnlockedThisDeviceOnly (unlike kSecAttrAccessibleWhenUnlocked, the password in the keychain is not transferred to another device during backups).

Now, my code fails and the user has to enter their password every time. (tested on iOS 13.0, iPhone 8 Plus)

When the user logs in using their password, my code first deletes any previously stored password using SecItemDelete(), and then proceeds to store the entered password using SecItemMatch().

Since changing the access attribute to kSecAttrAccessibleWhenUnlockedThisDeviceOnly, SecItemDelete() "succeeds" with errSecItemNotFound (i.e., "Nothing to delete"), but SecItemAdd() fails with errSecDuplicateItem!

Note, this isn't an issue of trying to retrieve a password previously stored with kSecAttrAccessibleWhenUnlocked using kSecAttrAccessibleWhenUnlockedThisDeviceOnly (i.e., different query dictionaries for store and load); I deleted the app from the device and tried from the start with the new code, and SecItemAdd() always fails.

What's Going On?


Solution

  • I assume you've already figured that out, but for the sake of completeness:

    Note, this isn't an issue of trying to retrieve a password previously stored with kSecAttrAccessibleWhenUnlocked using kSecAttrAccessibleWhenUnlockedThisDeviceOnly (i.e., different query dictionaries for store and load); I deleted the app from the device and tried from the start with the new code, and SecItemAdd() always fails.

    This is exactly what is happening to you. Keychain contents survive app deletion and re-install. To fix the issue you need to come up with a migration strategy that will remove items saved with the same primary keys, but different access control setting before trying to save new ones