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))
}
}
}
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"