Thanks for being a part of WWDC25!

How did we do? We’d love to know your thoughts on this year’s conference. Take the survey here

MacOS Authorsation Plugin and Protected System Keychain Entry.

I am developing a MacOS Authorisation Plugin, I have username and password entry items and utilising SFAuthorizationPluginView to display that. I am able to do so.

Requirement is I have to store ed25519 private key in PEM format in System Keychain as I need to read this entry before login to sign a request to a remote server.

I only want my authorisation plugin to access this private key in System Keychain.

I am looking up resources on the internet but I could not find specific to macOS Authorisation plugin, many are specific to iOS and some point at using entitlements and app group, but I doubt that applies to macOS authorisation plugin.

I'll really appreciate if some pointers are shared how can I store a private credential in System Keychain so that it can be used by only my plugin only, and this is before I have logged into the system.

Answered by DTS Engineer in 828579022

This is tricky.

To start, make sure you read TN3137 On Mac keychain APIs and implementations. Nothing I say here will make any sense without that background.

An authorisation plug-in is loaded by a host process. You have no control over the entitlements of that process. That means you can’t use the data protection keychain in your plug-in. You have to use the file-based keychain.

Your plug-in can be hosted by two different process, depending on whether the system is running a privileged mechanism or not. For the current host names, see this post. In the privileged case, the host is running as root. In the non-privileged case, it’s running as _securityagent. That affects what keychain you can use:

  • In the privileged case, you only have access to the System keychain.

  • In the non-privileged case you could use the login keychain for _securityagent. You can also read from the System keychain.

My advice is that you not do the latter. If you need this item in a non-privileged mechanism, have a privileged mechanism that gets this keychain item and put its into the authorisation context.

Alternatively, you could create a launchd daemon and have it manage the credential on your behalf. Honestly, this isn’t a bad option, because it means you can do work in a known environment, rather than the limited environment in which your authorisation plug-in is loaded.

IMPORTANT This won’t let you use the data protection keychain though. TN3137 makes it clear that a daemon can only use the file-base keychain.

Requirement is I have to store ed25519 private key in PEM format

The keychain does not support PEM format directly. And it doesn’t really support Curve 25519. If you really want PEM, store that in a generic password keychain item (kSecClassGenericPassword). If you’re prepared to convert it to DER, you could use a key item (kSecClassKey) but setting the key type will be tricky. Honestly, if you’re required to use Curve 25519, I’d use a generic password in either case.

so that it can be used by only my plugin only

That’s not really possible. Remember that macOS enforces privileges at the process boundary, and all authorisation plug-ins are loaded by the same host process. Even if you manage the item with a launchd daemon, you can’t tell whether the XPC connection to operate on that item is coming from your plug-in loaded in the host process or some other plug-in loaded in the host process.

Note XPC is Apple’s preferred IPC mechanism. See XPC Resources for links to documentation and more.

Finally, before you try to use the SecItem API, read these:

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Accepted Answer

This is tricky.

To start, make sure you read TN3137 On Mac keychain APIs and implementations. Nothing I say here will make any sense without that background.

An authorisation plug-in is loaded by a host process. You have no control over the entitlements of that process. That means you can’t use the data protection keychain in your plug-in. You have to use the file-based keychain.

Your plug-in can be hosted by two different process, depending on whether the system is running a privileged mechanism or not. For the current host names, see this post. In the privileged case, the host is running as root. In the non-privileged case, it’s running as _securityagent. That affects what keychain you can use:

  • In the privileged case, you only have access to the System keychain.

  • In the non-privileged case you could use the login keychain for _securityagent. You can also read from the System keychain.

My advice is that you not do the latter. If you need this item in a non-privileged mechanism, have a privileged mechanism that gets this keychain item and put its into the authorisation context.

Alternatively, you could create a launchd daemon and have it manage the credential on your behalf. Honestly, this isn’t a bad option, because it means you can do work in a known environment, rather than the limited environment in which your authorisation plug-in is loaded.

IMPORTANT This won’t let you use the data protection keychain though. TN3137 makes it clear that a daemon can only use the file-base keychain.

Requirement is I have to store ed25519 private key in PEM format

The keychain does not support PEM format directly. And it doesn’t really support Curve 25519. If you really want PEM, store that in a generic password keychain item (kSecClassGenericPassword). If you’re prepared to convert it to DER, you could use a key item (kSecClassKey) but setting the key type will be tricky. Honestly, if you’re required to use Curve 25519, I’d use a generic password in either case.

so that it can be used by only my plugin only

That’s not really possible. Remember that macOS enforces privileges at the process boundary, and all authorisation plug-ins are loaded by the same host process. Even if you manage the item with a launchd daemon, you can’t tell whether the XPC connection to operate on that item is coming from your plug-in loaded in the host process or some other plug-in loaded in the host process.

Note XPC is Apple’s preferred IPC mechanism. See XPC Resources for links to documentation and more.

Finally, before you try to use the SecItem API, read these:

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Quinn, Thanks for the response and I could follow it well. Taking your advice I am using following option.

My advice is that you not do the latter. If you need this item in a non-privileged mechanism, have a privileged mechanism that gets this keychain item and put its into the authorisation context.

However, I am finding it difficult to quickly prototype (command line) logic to add entry into System Keychain.

I found some examples but it seems to be using deprecated API.

Only thing that seems to be working is using security command line tool as. I plan to run the below command as simple Process#launch. I tried it in authorisation plugin to run something else which gave result, hoping will work fine for below command.

sudo security add-generic-password -a root -s "MySecureEntry" -w "my-secret-value" -T "" /Library/Keychains/System.keychain

Note, -T ""

Without giving it, the below program can read the value without sudo.

I can read that entry in root context as follows (command line app run with sudo).

func getKeychainEntry(service: String, account: String) -> String? {
    let query: [String: Any] = [
        kSecClass as String: kSecClassGenericPassword,
        kSecAttrService as String: service,
        kSecAttrAccount as String: account,
        kSecReturnData as String: true
    ]

    var dataRef: AnyObject?
    let status = SecItemCopyMatching(query as CFDictionary, &dataRef)

    if status == errSecSuccess, let data = dataRef as? Data {
        return String(data: data, encoding: .utf8)
    } else {
        print("Failed to retrieve from Keychain: \(status)")
        return nil
    }
}

I am not feeling good about what I am doing above, is there a better way? Apologies if question feels silly.

Oh, there are so many pitfalls here )-:

The System keychain is a filed-based keychain. To modify it you must have file system permissions to change the file. That means running as root.

Using sudo for this is tricky because of execution context issues. There are two relavent bits of execution context here: BSD and security. The sudo command changes the first but not the second. That can result in a lot of ‘fun’ edge cases. In this specific situation I think the only relevant edge case is the default keychain, which is easy to work around. However, keep an eye out for this in other situations where you use the security-related APIs from sudo.

Note TN2083 Daemons and Agents talks about execution contexts in some detail.

File-based keychains use an access control system based on ACLs. If program A adds a keychain item and program B tries to access it, one of two things happens by default:

  • If B is in a GUI context, the user gets a dialog asking whether to allow this.

  • If not, the access fails.

Your -T argument is adding a wildcard ACL entry. That’s fine for this test, but not what you want in a real product.

One important thing about keychain item ACLs is that changing the ACL requires user approval. The only way to avoid ACL problems is for program A to include program B in the ACL when it creates the item. Oh, and both programs have to be from the same team (-:

This is one of the reasons why I suggested that you might use a launchd daemon to manage the credential. With that, there’s only one program working with the keychain item, so this ACL issue goes away. Without that, whoever creates the item has to include the authorisation plug-in host in the item’s ACL. That’s quite brittle.

And that brings me back to this point:

create a launchd daemon … because it means you can do work in a known environment

If you decide to create a launchd daemon, it would make sense to offload as much work as possible from your authorisation plug-in to that daemon. For example, rather than have the daemon manage this keychain item, have the daemon manage all your networking. Or manage your entire state, and just have the authorisation plug-in act as a simple view on to that state.

Debugging code within a daemon isn’t easy, but it’s a lot easier than debugging code within an authorisation plug-in (-:

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Oops, I misread your statement

My advice is that you not do the latter.

My bad, went on the wrong route. I'll try to follow as you mentioned earlier.

Please ignore I was not looking into it correctly, -w option of security command I missed.

I am able to get a launch daemon going and talk to it over XPC. However I am still struggling with Key Chain entry. I am using below code as one of the XPC endpoints.

let query: [String: Any] = [
            kSecClass as String: kSecClassGenericPassword,
            kSecAttrAccount as String: "testDaemon",
            kSecAttrService as String: "com.garrow.cred.daemon.xpc",
            kSecValueData as String: "mypass"
        ]

 // Remove for now
  SecItemDelete(query as CFDictionary)

  et status = SecItemAdd(query as CFDictionary, nil)

This works, I mean no errors.

sudo security find-generic-password -a testDaemon -s com.garrow.cred.daemon.xpc shows entry is in System Keychain.

keychain: "/Library/Keychains/System.keychain"
version: 256

But I can access it without sudo as follows too.

security find-generic-password -a testDaemon -s com.garrow.cred.daemon.xpc

Question: I have no clue how to block this. I am fine someone can access it with sudo or as discussed anything run as root, but even non root access, how to stop this?

I searched over internet but most examples cover scenario where Key Chain access is all about in reference to an iOS or macOS application. People have logged in and they want to protect passwords for their app, System Keychain I cannot find any good resource.

Some suggest to use security command as last discussed but I don't want to and that too with -T "".

Please advice.

But I can access it without sudo as follows too.

But that’s not accessing the password, right? Rather, it’s just displaying the item’s attributes. To display the password you pass in the -g or -w flags, and that’ll prompt for user authorisation.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

I realised it latter, yes it was showing only attributes, needed -w flag. Thanks.

MacOS Authorsation Plugin and Protected System Keychain Entry.
 
 
Q