Using Secure Enclave from a Daemon Process

I'm working on a daemon that's going to be controled from launchd. I would like to use a secure enclave key to back the private key this daemon is going to be using to communicate with an external service. I have same simple code put together for how to generate that key:


#include <CoreFoundation/CoreFoundation.h>
#include <Security/Security.h>

void generateSeKey(SecKeyRef *publicKey, SecKeyRef *privateKey) {
  OSStatus rc;
  CFErrorRef err = NULL;
  SecAccessControlRef access = NULL;
  CFMutableDictionaryRef params = NULL;
  CFMutableDictionaryRef privateKeyAttrs = NULL;
  CFMutableDictionaryRef publicKeyAttrs = NULL;

  access = SecAccessControlCreateWithFlags(kCFAllocatorDefault,
       kSecAttrAccessibleAlways,
       kSecAccessControlPrivateKeyUsage,
       &err);
  if (err != NULL) {
       return;
  }

  params = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFCopyStringDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
  CFDictionarySetValue(params, kSecAttrKeyType, kSecAttrKeyTypeECSECPrimeRandom);
  CFDictionarySetValue(params, kSecAttrTokenID, kSecAttrTokenIDSecureEnclave);

  privateKeyAttrs = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFCopyStringDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
  CFDictionarySetValue(privateKeyAttrs, kSecAttrIsPermanent, kCFBooleanFalse);
  CFDictionarySetValue(privateKeyAttrs, kSecAttrAccessControl, access);
  CFRelease(access);
  CFDictionarySetValue(params, kSecPrivateKeyAttrs, privateKeyAttrs);
  CFRelease(privateKeyAttrs);

  // Set keys to NULL before calling
  *publicKey = NULL;
  *privateKey = NULL;
  rc = SecKeyGeneratePair(params, publicKey, privateKey);
  CFRelease(params);
  if (rc != errSecSuccess) {
       printf("Get error from SecKeyGeneratePair: %d\n", rc);
  }
}

int main(int argc, char** argv) {
   SecKeyRef publicKey, privateKey;
   generateSeKey(&publicKey, &privateKey);
   if (publicKey == NULL) {
       printf("Public key is NULL\n");
   } else {
       printf("Public key generated.\n");
   }
   if (privateKey == NULL) {
       printf("Public key is NULL\n");
   } else {
       printf("Private key generated.\n");
   }
   return 0;
}


This code works just fine when I compile and run it (either as myself or as root):

$ gcc simple.c -o se -framework Security -framework CoreFoundation
$ ./se
Public key generated.
Private key generated.
$ sudo ./se
Public key generated.
Private key generated.


However, I have discovered that when the process is launched via launchd (or via logging into a proper root login session) the SecKeyGeneratePair call returns errSecInternal.

$ sudo su -
# cd /tmp/src/se
# ./se
Get error from SecKeyGeneratePair: -26276
Public key is NULL
Public key is NULL


Checking out the system log, I see a few errors like this coming from the setoken process:

> initWithExistingContext -> (null), Error Domain=NSCocoaErrorDomain Code=4099 "The connection to service named com.apple.CoreAuthentication.agent was invalidated." UserInfo={NSDebugDescription=The connection to service named com.apple.CoreAuthentication.agent was invalidated.}


I gather something about the login session is important to making this work, but it's not totally clear to me what it is. So the short question is: is it possible to use a secure enclave key from a launchd daemon process? And if so, what am I doing wrong?

Accepted Reply

either as myself or as root

Running as root using

sudo
is not a great test for this sort of thing, because macOS has critical execution context above and beyond the traditional UNIX {r,e}uid values. This is discussed in gory detail in Technote 2083 Daemons and Agents.

Checking out the system log, I see a few errors like this coming from the

setoken
process:

… "The connection to service named com.apple.CoreAuthentication.agent was invalidated." …

Yeah, that not a good sign. Consider this:

$ plutil -convert xml1 -o /dev/stdout /System/Library/LaunchAgents/com.apple.CoreAuthentication.agent.plist
…
<dict>
    …
    <key>Label</key>
    <string>com.apple.CoreAuthentication.agent</string>
    <key>LimitLoadToSessionType</key>
    <array>
        <string>Aqua</string>
        <string>LoginWindow</string>
        <string>Background</string>
    </array>
    <key>MachServices</key>
    <dict>
        <key>com.apple.CoreAuthentication.agent</key>
        <dict/>
        …
    </dict>
    …
</dict>
</plist>

Note:

  • The path, in

    LaunchAgents
    , indicates that the
    com.apple.CoreAuthentication.agent
    is published by an agent, not a daemon.
  • The

    LimitLoadToSessionType
    value confirms this.

I suspect that there’s no way to make this work, but I can’t be 100% sure without digging into it in much more detail. If you’d like me to do that, you should open a DTS tech support incident so that I can allocate the necessary time.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Replies

either as myself or as root

Running as root using

sudo
is not a great test for this sort of thing, because macOS has critical execution context above and beyond the traditional UNIX {r,e}uid values. This is discussed in gory detail in Technote 2083 Daemons and Agents.

Checking out the system log, I see a few errors like this coming from the

setoken
process:

… "The connection to service named com.apple.CoreAuthentication.agent was invalidated." …

Yeah, that not a good sign. Consider this:

$ plutil -convert xml1 -o /dev/stdout /System/Library/LaunchAgents/com.apple.CoreAuthentication.agent.plist
…
<dict>
    …
    <key>Label</key>
    <string>com.apple.CoreAuthentication.agent</string>
    <key>LimitLoadToSessionType</key>
    <array>
        <string>Aqua</string>
        <string>LoginWindow</string>
        <string>Background</string>
    </array>
    <key>MachServices</key>
    <dict>
        <key>com.apple.CoreAuthentication.agent</key>
        <dict/>
        …
    </dict>
    …
</dict>
</plist>

Note:

  • The path, in

    LaunchAgents
    , indicates that the
    com.apple.CoreAuthentication.agent
    is published by an agent, not a daemon.
  • The

    LimitLoadToSessionType
    value confirms this.

I suspect that there’s no way to make this work, but I can’t be 100% sure without digging into it in much more detail. If you’d like me to do that, you should open a DTS tech support incident so that I can allocate the necessary time.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Thanks for the quick reply! That does sound like this probably won't work as a daemon. I'll spend a little bit of time digging into whether this might work as a user agent for my use-case instead. That technote link is incredibly helpful; I spent most of the weekend reading through it and figuring out how that guidance might apply. Thanks!