iOS keychain randomly returning -25300 error (aka errSecItemNotFound)

Hi everyone

My app is facing strange keychain behaviour: sometimes it returns -25300 status instead of credentials that definitely exists, so logged in users randomly becomes "logged out".

App restart doesn't help, only new user authorization.

Seems like items just disappearing from keychain for some reason.

After looking closer at code that saves and reads data from keychain and comparing it with Apple's sampes, i noticed that we don't use kSecAttrService attribute when saving item of kSecClassGenericPassword class.

Here is some code snippets:

Code Block
func saveData(_ data: Data, for key: String) -> OSStatus {
        let query: [CFString : Any] = [
            kSecClass: kSecClassGenericPassword,
            kSecAttrAccount: key,
            kSecValueData: data,
            kSecAttrAccessible: kSecAttrAccessibleWhenUnlockedThisDeviceOnly
        ]
        return SecItemAdd(query as CFDictionary, nil)
    }


Code Block
func readData(for key: String) -> String? {
        let query: [CFString: Any] = [
            kSecClass: kSecClassGenericPassword,
            kSecAttrAccount: key,
            kSecReturnData: kCFBooleanTrue as Any,
            kSecMatchLimit: kSecMatchLimitOne
        ]
        var result: AnyObject?
        _ = withUnsafeMutablePointer(to: &result) {
            SecItemCopyMatching(query as CFDictionary, UnsafeMutablePointer($0))
        }
        return (result as? Data).flatMap {
            String(data: $0, encoding: .utf8)
        }
    }


So, may the absence of kSecAttrService when saving kSecClassGenericPassword items be the reason why keychain items become unreachable for some reason, or is there any other reason?

Thanks

I would guess that you need this key.
You don’t absolutely need kSecAttrService but it’s a really good idea. That’s because it allows different subsystems within your app to use items in the keychain without stomping on each other. Imagine, for example, you have a framework within your app that stores generic passwords in the keychain. If that framework also skips kSecAttrService, your code and the framework will use the same item and Bad Things™ will ensue.

As to what’s causing the specific problem you’re seeing, it’s hard to say without more info. One obvious problem relates to migration. You set kSecAttrAccessibleWhenUnlockedThisDeviceOnly, so you keychain item can only be used on this specific device. If the user migrates to a new iPhone, all your app data will come across but you keychain item won’t, and thus you’ll be in exactly this situation.

However, that’s just one possibility. It could be something completely different.

Hmmm, the readData(for:) method you posted doesn’t actually do anything with the result from SecItemCopyMatching. How do you know you got errSecItemNotFound?

Can you reproduce this? Or are you only seeing this being reported by users in the field?

You wrote:

App restart doesn't help, only new user authorization.

What about device restart?

Is there any chance your app is running in the background when this error occurs?

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@apple.com"
I'm having the same issue. I have three apps with the same code base. Two of them were approved and the other one was rejected. For some reason when I try to get the info from the keychain using apple sign-in I get no results. Here is my code to load/save data. 

Code Block
class func save(key: String, data: Data) throws {
    let query: [String: Any] = [kSecClass as String: kSecClassGenericPassword,
                                kSecValueData as String: data,
                                kSecAttrAccount as String: key]
    SecItemDelete(query as CFDictionary)
    let status = SecItemAdd(query as CFDictionary, nil)
    if status != errSecSuccess {
      throw error(from: status)
    }
  }
  class func load(key: String) throws -> Data? {
    let query: [String: Any] = [kSecClass as String: kSecClassGenericPassword,
                                kSecMatchLimit as String: kSecMatchLimitOne,
                                kSecReturnData as String: true,
                                kSecAttrAccount as String: key]
    var queryResult: AnyObject?
    let status = withUnsafeMutablePointer(to: &queryResult) {
      SecItemCopyMatching(query as CFDictionary, $0)
    }
    switch status {
    case errSecSuccess:
      let dataType = type(of: queryResult)
      guard let data = queryResult as? Data else {
        throw KeyChainError.dataConversionError(actualType: "'\(dataType)'")
      }
      return data
    case errSecItemNotFound:
      return nil
    default:
      throw error(from: status)
    }
  }

Is there any possible solution for this issue?
iOS keychain randomly returning -25300 error (aka errSecItemNotFound)
 
 
Q