SecItemCopyMatching doesn't find key stored with SecItemAdd

I'm writing an app that uses the App Store Connect API and would like to store the private key contained in the .p8 file downloaded from the website in the keychain.

The following code successfully stores a key in the keychain with SecItemAdd, then tries to read it immediately, but without success (the error code of SecItemCopyMatching is errSecItemNotFound and the console outputs nil). Running the code a second time causes SecItemAdd to fail with code errSecDuplicateItem, and SecItemCopyMatching again with code errSecItemNotFound.

What am I doing wrong?

class AppDelegate: NSObject, NSApplicationDelegate {

    private let secApplicationTag = "com.example.app".data(using: .utf8)!

    func applicationDidFinishLaunching(_ aNotification: Notification) {
        do {
            try storeKey("asdf")
            print(try readKey() as Any)
        } catch {
            print(error)
        }
    }
    
    private func storeKey(_ key: String) throws {
        guard let data = Data(base64Encoded: key) else {
            fatalError()
        }
        let status = SecItemAdd([kSecClass as String: kSecClassKey, kSecAttrLabel as String: "Asdf", kSecAttrApplicationTag as String: secApplicationTag, kSecAttrKeyClass as String: kSecAttrKeyClassPrivate, kSecValueData as String: data, kSecAttrSynchronizable as String: true] as [String: Any] as CFDictionary, nil)
        if status != errSecSuccess {
            throw NSError(domain: NSOSStatusErrorDomain, code: Int(status))
        }
    }
    
    private func readKey() throws -> String? {
        var item: CFTypeRef?
        let status = SecItemCopyMatching([kSecClass as String: kSecClassKey, kSecAttrApplicationTag as String: secApplicationTag, kSecAttrKeyClass as String: kSecAttrKeyClassPrivate, kSecReturnData as String: true] as [String: Any] as CFDictionary, &item)
        switch status {
        case errSecSuccess:
            let data = item as! Data
            return (data as Data).base64EncodedString()
        case errSecItemNotFound:
            return nil
        default:
            throw NSError(domain: NSOSStatusErrorDomain, code: Int(status))
        }
    }

}
Answered by DTS Engineer in 751751022

By setting kSecAttrSynchronizable on the add, you’re causing it to target the data protection keychain. However, on the copy you’re targeting the file-based keychain, and so it doesn’t find anything. See TN3137 On Mac keychain APIs and implementations for a detailed explanation.

I also recommend that you check out SecItem: Fundamentals and its companion SecItem: Pitfalls and Best Practices.

Share and Enjoy

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

Accepted Answer

By setting kSecAttrSynchronizable on the add, you’re causing it to target the data protection keychain. However, on the copy you’re targeting the file-based keychain, and so it doesn’t find anything. See TN3137 On Mac keychain APIs and implementations for a detailed explanation.

I also recommend that you check out SecItem: Fundamentals and its companion SecItem: Pitfalls and Best Practices.

Share and Enjoy

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

SecItemCopyMatching doesn't find key stored with SecItemAdd
 
 
Q