I am writing a MacOS app that uses the Apple crypto libraries to create, save, and use an RSA key pair. I am not using a Secure Enclave so that the private key can later the retrieved through the keychain. The problem I am running into is that on my and multiple other systems the creation and retrieval works fine. On a different system -- running MacOS 15.3 just like the working systems -- the SecKeyCreateRandomKey
function appears to work fine and I get a key reference back, but on subsequent runs SecItemCopyMatching results in errSecItemNotFound. Why would it appear to save properly on some systems and not others?
var error: Unmanaged<CFError>?
let access = SecAccessControlCreateWithFlags(kCFAllocatorDefault,
kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
.biometryAny,
&error)!
let tag = TAG.data(using: .utf8)! // com.example.myapp.rsakey
let attributes: [String: Any] = [
kSecAttrKeyType as String: KEY_TYPE, // set to kSecAttrKeyTypeRSA
kSecAttrKeySizeInBits as String: 3072,
kSecPrivateKeyAttrs as String: [
kSecAttrIsPermanent as String: true,
kSecAttrApplicationTag as String: tag,
kSecAttrAccessControl as String: access,
],
]
guard let newKey = SecKeyCreateRandomKey(attributes as CFDictionary, &error) else {
throw error!.takeRetainedValue() as Error
}
return newKey
This runs fine on both systems, getting a valid key reference that I can use. But then if I immediately try to pull the key, it works on my system but not the other.
let query = [ kSecClass as String: kSecClassKey,
kSecAttrApplicationTag as String: tag,
kSecReturnRef as String: true, ]
var item: CFTypeRef?
let status = SecItemCopyMatching(query as CFDictionary, &item)
let msg = SecCopyErrorMessageString(status, nil)
if status == errSecItemNotFound {
print("key not found")
}
guard status == errSecSuccess else { print("other retrieval error") }
return item as! SecKey
I've also tried a separate query using the secCall function from here (https://developer.apple.com/forums/thread/710961) that gets ALL kSecClassKey items before and after the "create the key" function and it'll report the same amount of keys before and after on the bugged system. On the other machines where it works, it'll show one more key as expected.
In the Signing & Capabilities section of the project config, I have Keychain Sharing set up with a group like com.example.myapp where my key uses a tag like com.example.myapp.rsakey. The entitlements file has an associated entry for Keychain Access Groups with value $(AppIdentifierPrefix)com.example.myapp
.
It’s hard to say exactly what’s going on here. It very much depends on the context.
First, I recommend that you read TN3137 On Mac keychain APIs and implementations. It describes the difference between the data protection and file-based keychains. If you don’t understand that, none of this makes any sense at all.
Second, is this a Mac Catalyst app? Those always target the data protection keychain [1]. OTOH, standard Mac apps can use both keychain types. So, unless you happen to be using Mac Catalyst, it’s really important that you opt in to the data protection keychain [2].
Finally, my general advice is that you not create a key in the keychain but rather create the key and then add it to the keychain [3]. That gives you more direct control over how the key is added. For example, you can request that the add operation return you a permanent reference, which makes it easy to get back to that key.
This is one of the many suggestions I outline in my two SecItem posts:
I recommend that you read them before continuing.
Share and Enjoy
—
Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"
[1] Likewise for iOS Apps on Mac.
[2] If you can. Some programs, like a launchd
daemon, can only use the file-based keychain )-:
[3] I’ll admit my approach is less secure, but that only really matters if you care about non-extractability.