I am developing a daemon-based product that needs a cryptographic, non-spoofable proof of machine identity so a remote management server can grant permissions based on the physical machine.
I was thinking to create a signing key in the Secure Enclave and use a certificate signed by that key as the machine identity. The problem is that the Secure Enclave key I can create is only accessible from user context, while my product runs as a system daemon and must not rely on user processes or launchAgents.
Could you please advise on the recommended Apple-supported approaches for this use case ?
Specifically, Is there a supported way for a system daemon to generate and use an unremovable Secure Enclave key during phases like the pre-logon, that doesn't have non user context (only the my application which created this key/certificate will have permission to use/delete it)
If Secure Enclave access from a daemon is not supported, what Apple-recommended alternatives exist for providing a hardware-backed machine identity for system daemons? I'd rather avoid using system keychain, as its contents may be removed or used by root privileged users.
The ideal solution would be that each Apple product, would come out with a non removable signing certificate, that represent the machine itself (lets say that the cetificate name use to represent the machine ID), and can be validated by verify that the root signer is "Apple Root CA"
Somewhat to my own surprise, I haven’t let this slip through the cracks (-: I’ve been researching it in the background over the past week or so. I now have some answers, although not all the answers.
In the beginning, the only way to protect a key with the Secure Enclave (SE) was via the data protection keychain. See Protecting keys with the Secure Enclave.
As explained in TN3137 On Mac keychain APIs and implementations, the data protection keychain is not available to third-party launchd daemons. That meant that the SE was not available to daemons, something called out in TN3137 itself.
However, that calculus changed with the introduction of Apple CryptoKit. With CryptoKit it’s possible to create an SE-protected key directly via the types underneath the SecureEnclave type. Thus, the old answer of “No, because of the data protection keychain constraint” is no longer valid.
This raises two questions:
- Does this work?
- Is it supported?
Lemme tackle the first one first: AFAICT this does actually work. However, there’s one significant wrinkle. The standard way of creating an SE-protected key is with code like this:
let key = try SecureEnclave.P256.Signing.PrivateKey()
This skips a key detail, namely that the magic of default arguments means that the code actually looks like this:
let ac = SecAccessControlCreateWithFlags(
nil,
kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly,
[],
nil
)!
let key = try SecureEnclave.P256.Signing.PrivateKey(
compactRepresentable: true,
accessControl: ac,
authenticationContext: nil
)
Note I’ve introduced the ac temporary to make it easier to read the snippet.
Consider that kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly argument. This configures the key so that it’s only available after the first user has logged in. This might be problematic for a daemon, depending on what the daemon does.
IMPORTANT This is a particularly tricky issue because it’s hard to see the problem in practice. If you create a test daemon and start it from Terminal, you won’t see this problem because you have to log in to use Terminal. And this isn’t just about GUI logins. If you SSH into the Mac, that’s also a login. To test this I had to create a daemon that, on startup, waits a bit and then creates a key and logs the result. I then logged in and saw the problem in the system log. Specifically, the key creation code failed with errSecInteractionNotAllowed (-25308) error, with an underlying error of kIOReturnNotPermitted (0xe00002e2).
If your daemon needs to use this key before any user has logged in, create your key using the code snippet above, changing kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly to kSecAttrAccessibleAlways.
Note This will generate a deprecation warning, and Swift has no good way to silence those (r. 31131633). Sorry.
But only do it in this case. If your daemon only operates in response to requests from logged in users, it makes sense to stick with kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly.
So, AFAICT this works, and has worked this way since the introduction of Apple CryptoKit. However, that brings me to the second question: Is it supported? And I don’t have an official answer to that. I can’t see any obvious flaws in the approach, but without documentation you’re essentially trading in implementation details.
My advice is that you file a bug against the docs, and specifically Protecting keys with the Secure Enclave, requesting that it be enhanced to cover:
- Apple CryptoKit in general
- This specific issue.
Please post your bug number, just for the record.
Share and Enjoy
—
Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"