tl;dr: The title and/or can I even add a keychain entitlement to a cli app?
I'm trying to store a generated private key and certificate properly in a CLI app. The call to SecItemAdd always results in an error with message A required entitlement isn't present.
I assume this is errSecMissingEntitlement, and its docs say it happens "when you specify an access group to which your app doesn’t belong".
But I'm not even specifying one. Here's a small excerpt (I know it's not a MVCE but the question is pretty general anyway):
func storeCert(_ cert: Data) throws {
let addQuery =
[
kSecClass: kSecClassCertificate,
kSecValueRef: cert,
kSecAttrLabel: CERT_USER_LABEL,
kSecAttrApplicationLabel: CERT_APP_LABEL
] as [String: Any]
let status = SecItemAdd(addQuery as CFDictionary, nil)
guard status == errSecSuccess else {
let msg = SecCopyErrorMessageString(status, nil) as String? ?? ""
throw MyErr.generic(message: "Unable to store cert: \(msg)")
}
}
I can't add the keychain entitlement to my CLI target, it doesn't show as an option in the add capability window.
Disclaimer: I'm quite new to macOS / Apple development, so if there's something obvious I'm missing, my bad.
Let’s start with some terminology. On Apple platforms we typically use the terms app and application to refer to things with a GUI that the user launches from the Finder (on macOS), Home screen (on iOS), and so on. If you’re building a program that you expect the user to run in Terminal, that’s a command-line tool.
Using the keychain from a command-line tool is a bit tricky because:
- We generally recommend that folks using the data protection keychain rather than the file-base keychain.
- Access to the data protection keychain is mediated by restricted entitlements, that is, entitlements that must be authorised by a provisioning profile.
- It’s hard to sign a command-line tool with such entitlements because there’s no obvious place to put said provisioning profile.
Now, all of that is gonna sound like word salad if you’re new to the Mac, so lemme drop in some links:
- TN3127 Inside Code Signing: Requirements explains the different keychain implementations on macOS.
- TN3125 Inside Code Signing: Provisioning Profiles explains what provisioning profiles do.
- SecItem: Fundamentals and SecItem: Pitfalls and Best Practices talk about the keychain API in general.
So, what should you do? Well, it depends on your circumstances
If your not a member of a paid developer team then the data protection keychain is going to cause you grief. While it is possible to use free provisioning and thus gain access to the data protection keychain — in Xcode this shows up as a Personal Team — those profiles expire very quickly (after a week IIRC) and that’s not much fun.
So, if that’s the case then I recommend that you stick with the file-based keychain.
OTOH, if you are a member of a paid team then you the choice between the file-base keychain and data protection keychain. It’s possible to use the data protection keychain by embedding your command-line tool in an app-like wrapper. See Signing a daemon with a restricted entitlement.
Note This is cast in terms of a launchd daemon but the process works the same for a command-line tool.
The main drawback to this approach is that the tool is embedded in a wrapper, so it’s a pain for the user to run it from Terminal. My general advice on that front is to install the wrapper elsewhere and then put a symlink to the tool within wrapper in the place that the user expects to find it, like ~/bin.
But if this wrapper approach is unacceptable to you then you kinda have to fall back to use the file-based keychain.
Regardless of what keychain you use, it’s vital that your code be signed with a stable code-signing identity. So, if you’re currently signing it as ad hoc (Xcode shows this as Sign to Run Locally) then you need to fix that.
Share and Enjoy
—
Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"