Ability to retrieve keychain item appears to be lost after restoring an IOS Device

On some production devices our application fails to find the keychain item associated with our application where we store our JWT tokens. We have been unable to reproduce this in house for many months.

Today I restored a phone from a backup using the device to device transfer of data as I replaced my personal phone. On that device now when opened each time I am prompted to login again and it appears my token is never saved to the keychain. Upon every successive reopen of the application I see this error in the console.

Error fetching keychain item - Error Domain=NSOSStatusErrorDomain Code=-25300 "no matching items found" UserInfo={numberOfErrorsDeep=0, NSDescription=no matching items found}

I currently do not see any errors in the console related to the saving of said token.

We access this token with the after first unlock security and we do not allow iCloud backup for these tokens.

Any help here would be appreciated. I'm not sure what would cause an issue like this. Other applications on my device do not seem to have this issue, so Its likely something we're doing code wise that may be different. Any hints as to what to look for here may be of help. The previous device or any device i have not created from a backup works as intended, including about 95% of our production users.

Answered by DTS Engineer in 821562022

The SecItem API is full of weird edge cases that can cause problems like this. I have lots of hints and tips on this topic in

I suspect you might be tripping over an item uniqueness problem, per the Uniqueness section of that first post.

I’m presuming that you can run a debug build on one of these affected devices. If so, add a test button that calls SecItemCopyMatching to dump all of the items of your particular class. For example:

func dump() throws {
    // `secCall(…)` is from https://developer.apple.com/forums/thread/710961
    let items = try secCall { SecItemCopyMatching([
        kSecClass: kSecClassGenericPassword,
        kSecMatchLimit: kSecMatchLimitAll,
        kSecReturnAttributes: true,
    ] as NSDictionary, $0) } as! [[String: Any]]
    for item in items {
        print(item)
    }
}

That’ll tell you whether the items you’re adding are actually being added. If not, you can look at your SecItemAdd copy. If they are, you can look at why you’re not finding them.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

The issue of which keychain items persist across backups is a complex one. I discuss the Finder (né iTunes) backup and iCloud backup cases in detail in this post.

It looks like you’re using Quick Start, which is different again. I’ve never looked into that in detail. However, I’m not sure this matters in your case. The symptoms you’ve described suggest that the failure is tied to how your app is handling this case, not to the behaviour of the system. You need to trace through the code to see what it’s actually doing at the keychain level. Specifically, what keychain APIs are you calling? And what results do you see?

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Thanks for the quick response! I'd like to add more information to clarify my post.

First, it is to my knowledge expected behavior that the access token we are storing would be lost after a device is stored. The problem becomes that I can never seem to find any token stored ever again. Each time a user logs in we would store that token using kSecAdd with a CF dictionary containing a few properties such as

            cfDictionary[kSecReturnData as String] = kCFBooleanTrue
            cfDictionary[kSecReturnRef as String] = kCFBooleanTrue

The next time our app would need to retrieve this token using SecItemCopyMatching() and always returns errSecItemNotFound. This would have occurred after the backup has happened multiple times. This just appears to be the only thing these devices with this problem have in common.

This means that even after storing a new token to the same spot in the keychain which the code generally works correctly, the retrieval will never succeed.

Accepted Answer

The SecItem API is full of weird edge cases that can cause problems like this. I have lots of hints and tips on this topic in

I suspect you might be tripping over an item uniqueness problem, per the Uniqueness section of that first post.

I’m presuming that you can run a debug build on one of these affected devices. If so, add a test button that calls SecItemCopyMatching to dump all of the items of your particular class. For example:

func dump() throws {
    // `secCall(…)` is from https://developer.apple.com/forums/thread/710961
    let items = try secCall { SecItemCopyMatching([
        kSecClass: kSecClassGenericPassword,
        kSecMatchLimit: kSecMatchLimitAll,
        kSecReturnAttributes: true,
    ] as NSDictionary, $0) } as! [[String: Any]]
    for item in items {
        print(item)
    }
}

That’ll tell you whether the items you’re adding are actually being added. If not, you can look at your SecItemAdd copy. If they are, you can look at why you’re not finding them.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Thanks a bunch for your help. Our problem ended up being a uniqueness problem and these methods to debug ended up making it much easier to diagnose the problem.

Ability to retrieve keychain item appears to be lost after restoring an IOS Device
 
 
Q