Failed to generate a private key

I'm trying to generate a private key with and without the Security Enclave like this:


     CFErrorRef error = NULL;
     SecAccessControlRef sacObject = SecAccessControlCreateWithFlags(kCFAllocatorDefault,
                                                       kSecAttrAccessibleWhenUnlocked,
                                                       kSecAccessControlDevicePasscode, &error);

        NSDictionary *attributes = @{
    //               (__bridge id)kSecAttrTokenID: (__bridge id)kSecAttrTokenIDSecureEnclave,
               (__bridge id)kSecAttrKeyType: (__bridge id)kSecAttrKeyTypeEC,
               (__bridge id)kSecAttrKeySizeInBits: @256,
               (__bridge id)kSecPrivateKeyAttrs: @{
                   (__bridge id)kSecAttrAccessControl: (__bridge_transfer id)sacObject,
                   (__bridge id)kSecAttrIsPermanent: @YES,
                   (__bridge id)kSecAttrLabel: @"TestKey",
               },
           };
        SecKeyRef privateKey = SecKeyCreateRandomKey((__bridge CFDictionaryRef)attributes, &error);
        if (!privateKey) {
            NSError *err = CFBridgingRelease(error);  // ARC takes ownership
            // Handle the error. . .
        }


SecKeyCreateRandomKey succeeds only when when I set the proper entitlement (Keychain Access Groups) for both cases, which works perfectly for an UI app.


The problem is that I need to generate the key from a service-console application. Thus, when I add the entitlement (Keychain Access Groups) to my console app, it simply doesn't start.


Any ideas how to fix that?

Thanks!

Replies

OK, I assume you’re developing for the Mac here.

You wrote:

The problem is that I need to generate the key from a service-console application.

What exactly is a “service-console application”? Specifically, during deployment (as opposed to development), how is this program started?

Share and Enjoy

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

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

Hi Quinn,


You wrote:

What exactly is a “service-console application”? Specifically, during deployment (as opposed to development), how is this program started?


I've already mentioned the structure of my daemon in one of my previews question regarding the "Input Monitoring" prompt. It's still a console app, but now it's also packed inside an app to get the "Input Monitoring" prompt. Also this daemon interrogates with a custom Authorization Plug-in.


Btw, thank you very much for your suggestions regarding "Input Monitoring" 🙂.


I have a Launch Daemon with a plist file located in /Library/LaunchDaemons.

The plist is quite straightforward:


<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN"

"http://www.apple.com/DTDs/PropertyList-1.0.dtd">

<plist version="1.0">

<dict>

<key>Label</key>

<string>com.test.hardwareservice</string>

<key>ProgramArguments</key>

<array>

<string>/Library/Application Support/[Vendor]/HardwareService.app/Contents/MacOS/HardwareService</string>

<string>start</string>

</array>

<key>RunAtLoad</key>

<true/>

<key>KeepAlive</key>

<true/>

</dict>

</plist>


HardwareService is based on the POCO library. It uses a ServerApplication class, and can work from the command line or as

a service or daemon. Technically, HardwareService is a console application.

Technically, HardwareService is a console application.

No, that term simply doesn’t exist on Apple’s platforms.

HardwareService
is an executable that either runs as an app or as a
launchd
daemon.

Still, I have what I need to answer your question.

If you want to use the Secure Enclave, you must use the iOS-style keychain. Doing that requires entitlements that must be whitelisted by a provisioning profile. You can’t attached a provisioning profile to a command-line tool directly. However, if you put the command-line tool in an app-like structure and then place the profile in the same place it’s found for an app (

Contents/embedded.provisionprofile
) the system will pick it up from there.

Based on your

launchd
property list, it see seems like your daemon’s executable is always packaged in a app-like structure. If you add the provisioning profile to that, does it work?

Share and Enjoy

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

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

)Hi Quinn,


I tried with the provisioning profile, but it's still doesn't work (now with a different error). When the service is launched via launchctl (as a LaunchDaemon), I get the following error:

SecKeyCreateRandomKey failed: Error Domain=NSOSStatusErrorDomain Code=-25291 "failed to generate asymmetric keypair" (errKCNotAvailable / errSecNotAvailable: / No trust results are available.


But when the service is launched as an app or as a root (from the Contents/MacOS/*** folder) it works as expected.


The service bundle has normal structure:

Contents 
     embedded.provisionprofile
     MacOS
          MyService

In Xcode I added the "keychain sharing" Capability (with the empty keychain groups)

When run the service app itsef, like:

open MyService.app

or as a root as:

sudo MyService/Contents/MacOS/MyService

SecKeyCreateRandomKey works as expected


But when I load the service launchctl it fails with two different erros:

sudo launchctl load /Library/LaunchDaemons/com.company.myservice.plist


<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs$

<plist version="1.0">

<dict>

<key>Label</key>

<string>com.netiq.deviceservice</string>

<key>ProgramArguments</key>

<array>

<string>/Library/Application Support/Company/MyService.app/Contents/MacOS/MyService</string>

<string>start</string>

</array>

<key>RunAtLoad</key>

<true/>

<key>KeepAlive</key>

<true/>

</dict>

</plist>


My Code:

        SecAccessControlRef sacObject = SecAccessControlCreateWithFlags(kCFAllocatorDefault,
                                                        kSecAttrAccessibleWhenUnlocked,
                                                        kSecAccessControlUserPresence  , &error);


        NSDictionary *attributes = @{
            (__bridge id)kSecAttrTokenID: (__bridge id)kSecAttrTokenIDSecureEnclave,
            (__bridge id)kSecAttrKeyType: (__bridge id)kSecAttrKeyTypeEC,
            (__bridge id)kSecAttrKeySizeInBits: @256,
            (__bridge id)kSecPrivateKeyAttrs: @{
            (__bridge id)kSecAttrAccessControl: (__bridge_transfer id)sacObject,
            (__bridge id)kSecAttrIsPermanent: @YES,
            (__bridge id)kSecAttrLabel: [NSString stringWithUTF8String:KEY_NAME],
            },
        };
        SecItemDelete((__bridge CFDictionaryRef)attributes);

        SecKeyRef privateKey = SecKeyCreateRandomKey((__bridge CFDictionaryRef)attributes, &error);

With SecurityEnclave on

(__bridge id)kSecAttrTokenID: (__bridge id)kSecAttrTokenIDSecureEnclave,

I receive the error:

SecKeyCreateRandomKey failed: Error Domain=NSOSStatusErrorDomain Code=-26276 "failed to generate asymmetric keypair" UserInfo={NSDescription=failed to generate asymmetric keypair}


Without SecurityEnclave the error is different:

SecKeyCreateRandomKey failed: Error Domain=NSOSStatusErrorDomain Code=-25291 "failed to generate asymmetric keypair" (errKCNotAvailable / errSecNotAvailable: / No trust results are


And these two errors happen only when I launch the service through launchctl as a launch daemon.


If to remove

(__bridge id)kSecAttrAccessControl: (__bridge_transfer id)sacObject,

SecKeyCreateRandomKey works properly, but that's not what it need.


Any ideas what's wrong? Thanks.

Any ideas what's wrong?

Check out my Packaging a Daemon with a Provisioning Profile post. It has a code snippet that shows how you can get your own entitlements, and I recommend that you use that to confirm that your entitlements are working properly.

If that doesn’t fix things, it’s possible that this is failing because of the context you’re running in (daemons run in a global context and iOS-style keychain comes from an app-only world). In that case I’m going to recommend that you open a DTS tech support incident so that I can dig into this in depth.

Share and Enjoy

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

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

//Check out my Packaging a Daemon with a Provisioning Profile post.

I modified my daemon as described in the post, but that didn't fix the issue.

SecKeyCreateRandomKey never works for me when kSecAttrAccessControl is set from the daemon context.

I've submitted a TSI (Technical Support Incident, Case ID: 733082779), so could you take a look?