We have a Launch Daemon which (necessarily, for various reasons) runs as root, and which communicates with a server component via the network. It needs to authenticate with the service, so when it first obtains the password, we save it to the system keychain. On subsequent launches, the idea is to retrieve the password from the keychain and use it to authenticate with the network service.
This has been working fine, but on macOS 10.12 the existing code stopped working, and we've been entirely stumped on how to fix this. It boils down to this:
Regardless of whether we're saving a new password or retrieving an old one, we obtain a reference to the system keychain using this:
SecKeychainCopyDomainDefault(kSecPreferencesDomainSystem, &system_keychain);
We also disable user interaction for good measure, although we'd expect it to already be off in the context of a daemon.
SecKeychainSetUserInteractionAllowed(false);
When saving a new password to the keychain, we use
OSStatus status = SecKeychainAddInternetPassword(
system_keychain,
urlLength, server_base_url,
0, NULL,
usernameLength, username,
0, NULL,
0,
kSecProtocolTypeAny, kSecAuthenticationTypeAny,
passwordLength, password,
NULL);
This much works. Success is reported, and I can see the item in the "system" keychain in Keychain Access.app.
Retrieving it on subsequent runs of our daemon is done with this line:
status = SecKeychainFindInternetPassword(
system_keychain,
urlLength, url,
0, NULL,
usernameLength, username,
0, NULL,
0,
kSecProtocolTypeAny, kSecAuthenticationTypeAny,
&passwordLength, &password_data,
NULL);
Unfortunately, this has started returning errSecAuthFailed
for reasons that are unclear to us.
A few additional details we've checked and things we've tried, to no avail:
- The daemon binary is signed with a Developer Id certificate.
- The daemon binary contains an embedded Info.plist section with a bundle ID and version.
- I can see the daemon binary in the "Always allow access by these applications" list in the "Access Control" tab of the password item in Keychain Access.app.
- If I manually switch to "Allow all applications to access this item" in Keychain Access, it works. This somewhat defeats the point of saving the password in the keychain, however.
- We've tried playing around with the parameters to
SecKeychainAddInternetPassword
, but this doesn't seem to have made any difference. - We've tried explicitly unlocking the keychain with
SecKeychainUnlock()
, but as the documentation suggests, this seems to be superfluous. - Deleting the item in
Keychain Access.app
causesSecKeychainFindInternetPassword()
to yielderrSecItemNotFound
, as you'd expect. So it can definitely find the saved item, it just isn't allowed to read it.
The keychain documentation isn't exactly easy to read and in parts rather tautological. ("In order to do Y, you need to do Y," without mentioning why you'd want to do Y.) Nevertheless, I think I've made it through and have understood most of it. Various aspects of our particular setup aren't covered in detail (access from a daemon), but it seems pretty clear that accessing an item previously saved by the same app should not require any special authorisation or authentication. Which is in direct contradiction to the behaviour we're seeing.
Any ideas?