I've been trying to use Keychain from a Daemon for some time now. In the end, I managed to have the System Keychain work for my application and I moved to work on other parts.
I finally went back to dealing with Keychain, but the code I wrote before stopped working. Even the application I wrote to test things out stopped working for me, and now it gives the The authorization was denied.
error.
To give more perspective into what I am doing, I am running a Sandboxed Launch Daemon wrapped in an App-like structure. I register it from my main app via SMAppService API. I also have a System Extension.
My test app was structured in the same way and I used the following code to put a new key into the System Keychain and get its reference:
var err: Unmanaged<CFError>?
let access = SecAccessCreateWithOwnerAndACL(getuid(), getgid(), UInt32(kSecUseOnlyUID | kSecHonorRoot), nil, &err)
if let err = err {
log.error("Failed to create SecAccess: \(err.takeUnretainedValue().localizedDescription)")
}
let request = [
kSecClass: kSecClassGenericPassword,
kSecAttrService: service,
kSecAttrAccount: account,
kSecValueData: passwordData,
kSecAttrAccess: access as Any,
kSecAttrSynchronizable: false,
kSecUseDataProtectionKeychain: false,
kSecReturnPersistentRef: true,
] as [String: Any]
var result: CFTypeRef?
let status = SecItemAdd(request as CFDictionary, &result)
The goal of this was to share some secrets with a System Extension.
The code above worked for me some time ago and I was able to use the System Keychain from my sandboxed daemon.
Am I missing something again? Did something change in the meantime? Or did I do something last time that I haven't noticed?
Should I cut my losses and avoid Keychain since Apple will not support it anyway?
This morning I had some time to research this further on my own.
I wrote a simple command line app that reads and writes to Keychain random strings. I sandboxed it to represent better what I am doing in my other application.
From my user, it worked perfectly. It read and wrote keys to my login keychain just fine.
But from root, it did not work. It failed with the same The authorization was denied. (-60005)
error.
I looked back into Console logs and found this:
Sandbox: KeychainTest(1166) deny(1) authorization-right-obtain system.keychain.create.loginkc
There were also logs from authd similarly saying that sandbox denied authorization.
From what I can understand, it looks like it tries to obtain rights to create a login keychain (create.loginkc)? This is just my guess. I'm probably wrong.
It looked to me that it was trying to access something completely different, so just for a sanity check, I decided to try to explicitly open and use the System Keychain.
This worked perfectly.
For some reason, SecItemAdd is no longer trying to access the System Keychain. To fix my issue, all I had to do, was add this code:
var keychain: SecKeychain?
let openStatus = SecKeychainOpen("/Library/Keychains/System.keychain", &keychain)
guard openStatus == errSecSuccess else {
throw KeychainError.underlyingSecurityError(openStatus)
}
// This is an example. I handle this like the SecAccess object
request[kSecUseKeychain] = keychain as Any
Previously I didn't have to do this. I didn't have to explicitly open the System Keychain to use it. I have no idea what changed, but this put a major wrench in my work.
I understand that the file-based keychains are "deprecated" but to change how SecItem handles them on the fly like this is a bit annoying.
As a note, wrapping a Daemon in an app bundle helps it access the System Keychain. If I didn't do that, I would need to add the com.apple.security.temporary-exception.files.absolute-path.read-write
entitlement for the /Library/Keychains/
directory since Sandbox would deny temporary file creation there.
TLDR
Open the System Keychain explicitly, do not let SecItem choose what to use, even if it worked previously.