Import resulting certificate to associate with private key

On macOS I am creating a public/private key pair (ECDSA 256), will be secure enclave in the long run, just trying to baseline some functionality right now using file store (currently running in a system daemon). I can sign data using the private key with no problem, and export the public key. However, after generating a cert chain via CSR externally, I’m trying to figure out the proper incantation to import that cert/chain and have it recognized as matching the existing private key, but so far I can’t figure out the correct attributes to get it to pair up.

Do you have any sample code that might have the right SecAddItem (or such) calls that would make this work as expected?

will be secure enclave [SE] in the long run

Some comments about that:

  • Make sure you read TN3137 On Mac keychain APIs and implementations, as it explains the terminology I’m going to use.

  • Keys are not stored in the (SE), they are protected by the SE. When you create an SE-protected keychain item, the actual key’s bytes are stored in the normal data protection keychain, wrapped in a way that prevents anyone but the SE from using them.

  • As mentioned in TN3137, the data protection keychain is only available to code running in a user context. You can’t use it from a daemon.

  • Not mentioned there (but definitely still true ’cause I checked it this week :-) is that SE-protected keys are not available to a daemon at all. So, for example, you can’t use the Apple CryptoKit SecureEnclave type from a daemon.

However, after generating a cert chain via CSR externally … I can’t figure out the correct attributes to get it to pair up.

Security framework forms digital identities by matching keys and certificates using specific attributes. See SecItem attributes for keys.

ps I added the Security tag to your thread. When you start a thread here, remember to set your tags correctly. Without that, it’s easy for me to miss it. Tip 4 in Quinn’s Top Ten DevForums Tips.

Share and Enjoy

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

Thanks Quinn, will take a look. A teammate indicated that we are wrapping our daemon in an app bundle per https://developer.apple.com/documentation/xcode/signing-a-daemon-with-a-restricted-entitlement - would that allow us to use the DPK/SE capabilities?

would that allow us to use the DPK/SE capabilities?

Nope. That’s the thing I specifically tested earlier this week (-:

The situation with the data protection keychain is a bit tricky, but the situation with the SE is very clear. When you try to talk to the SE using Apple CryptoKit from a daemon, you see a system log entry complaining about its inability to access the com.apple.ctkd.token-client service:

type: default
time: 2023-06-26 13:58:21.291434 +0100
process: launchd
subsystem: system
message: failed lookup: name = com.apple.ctkd.token-client, requestor = DaemonSEKey[57606], error = 3: No such process

That service is provided by an agent, and thus inaccessible to a daemon:

% grep com.apple.ctkd.token-client -r -l /System/Library/Launch*
/System/Library/LaunchAgents/com.apple.ctkd.plist

Share and Enjoy

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

Hi Quinn, so I am still having an issue with SecItemAdd() with the resulting certificate returned by the CA. I have created the public and private keys in the (file-based) keychain, and I can find them using SecItemCopyMatching by searching via kSecAttrApplicationLabel being the public key hash (kSecAttrCanSign=kCFBooleanTrue for the private key ref, kSecAttrCanSign=kCFBooleanFalse for the public key). However, SecItemAdd() with the cert data itself returns errSecDuplicateItem, even though kSecClass=kSecClassCertificate and the certificate certainly does not exist. I have tried specifying kSecAttrApplicationLabel and also without, no difference. What am I missing here? The intermediate certificate gets added with no problem, so it must have something to do with the existing keys...

Ok, there was a residual certificate with the same subject (but different key and serial number), still trying to figure out why it thinks it's a duplicate as it has a different kSecAttrApplicationLabel. I'm experimenting with additional fields like kSecAttrApplicationTag, as it's not clear what is required to be set for (1) the private/public keys, and (b) the certificate, will update tomorrow.

still trying to figure out why it thinks it's a duplicate

The rules for keychain item uniqueness are documented on the errSecDuplicateItem page.

Oh, and I have a couple of high-level posts that can help in situations like this:

Share and Enjoy

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

Hi Quinn,

This just keeps getting weirder and weirder. I found another post you had made (https://developer.apple.com/forums/thread/69642) that states to set the public key hash into the private key kSecAttrLabel and kSecAttrApplicationLabel (which is weird since the kSecAttrLabel is expected to be text), and into kSecAttrPublicKeyHash in the certificate. In other Apple docs you pointed to it is stated that the kSecAttrPublicKeyHash attribute is read only, which is what was throwing me off.

Once I set that in the SecItemAdd dictionary for the cert, then it seems to associate correctly. So to experiment I found that SecItemCopyMatching with kSecClassCertificate, kSecReturnAttributes and kSecMatchLimitAll returns lots of certificates, almost all of which have many attributes set, e.g. class, cenc, ctyp, subj, issr, labl, pkhh, slnr, etc. In my case I wasn't specifying these, assuming the OS would set them, and my cert just reported a minimal set: class = cert, ctyp = 0, pkhh = {length = 20, bytes = 0x8a95...c955}. Then I went to my dictionary and started adding the others, like cenc, ctyp, subj, etc, but then I got error -67866 = errSecFieldSpecifiedMultiple (Too many fields were specified). I started taking some off, and it seems some attributes always give me that error, like ctyp, issr, srln. I can set the cenc, subj, labl and pkhh ok, but that's about it.

And then when I go to import the intermediate, setting the same set of attributes (but different values from the leaf cert for the labl, subject and pkhh), I get errSecDuplicateItem. Your pointers mention setting kSecAttrApplicationLabel and I've tried making that unique for the leaf and intermediate, but no effect; I don't see why it's indicating duplicate. Here are the results of querying after adding; the intermediate is the result only after I delete the leaf right before adding it in order to get it to add.

Printing description of ((__NSDictionaryM *)0x0000600000227780): { cenc = 3; class = cert; ctyp = 0; labl = "Leaf Certificate"; pkhh = {length = 20, bytes = 0xec127a9329e63d6733672bbb40aaecafd091ec79}; subj = {length = 84, bytes = 0x30523150 304e0603 5504030c 4775726e ... 62754861 6a706145 }; }

Printing description of ((__NSDictionaryM *)0x000060000023a400): { cenc = 3; class = cert; ctyp = 0; labl = "Intermediate Certificate"; pkhh = {length = 20, bytes = 0x1b73c2a805958e4698d7607b66b23a080f94703e}; subj = {length = 54, bytes = 0x30343132 30300603 5504030c 29444556 ... 4570686d 6572616c }; }

Note that all of this is using the file-based keychain and currently in user space.

What I usually do is create a certificate object (SecCertificate) and add that using kSecValueRef. The system then infers all the right attributes from the object, so I don’t have to mess with the specific attributes.

The only gotcha I specifically remember hitting in this space is that I had to delete the public key before Security framework would form an identity. This was due to a bug in the way that keys were generated, where the public key was accidentally marked as a private key (r. 15615260). I’m not sure if that’s still an issue these days.


Consider this program:

import Foundation

… secCall(…) helpers from <https://developer.apple.com/forums/thread/710961> …

func main() {
    let u = URL(fileURLWithPath: "/Users/quinn/Benjy.cer")
    let d = try! Data(contentsOf: u)
    let c = SecCertificateCreateWithData(nil, d as NSData)!
    let attrs = try! secCall {
        SecItemAdd([
            kSecValueRef: c,
            kSecReturnAttributes: true,
        ] as NSDictionary, $0)
    } as! [[String: Any]]
    print(attrs)
}

main()

When I run it from Terminal I see this:

% ./Test732783
[
    [
        "subj": <301d310e 300c0603 5504030c 0542656e 6a79310b 30090603 55040613 024742>,
        "slnr": <0b>,
        "issr": <301f3110 300e0603 5504030c 074d6f75 73654341 310b3009 06035504 06130247 42>,
        "labl": Benjy,
        "class": cert,
        "pkhh": <9afe2160 d114e9bd 9bf3a840 f9f5e45b fb8296d2>,
        "ctyp": 1,
        "cenc": 3
    ]
]

Note I’ve reformatted this to be easier to read.

As you can see, the public key hash ends up in pkhh, that is, kSecAttrPublicKeyHash.

Oh, wait, this illustrates one other file-based keychain oddity: When adding a single item, it returns an array of dictionaries, not just a single dictionary (r. 21111543).

Share and Enjoy

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

Hi Quinn,

I ended up importing via SecPKCS12Import() instead. After stepping back a bit I decided to use OpenSSL to generate the keypair, and then PKCS12_create() to package it with the leaf cert and intermediate CAs, and extract the DER into a CFData and SecPKCS12Import() was quite happy with that, setting all the proper attributes for the key and certs. I then went further and found that even if I create the private key in the keychain directly using SecKeyCreateRandomKey() that I could generate the PKCS12 data without the key (it's just a container) and SecPKCS12Import() still created all the right bits for the certs.

The biggest quirk I see is the inconsistency in that I need to use kSecAttrApplicationLabel for keys and kSecAttrPublicKeyHash for the cert when querying (and deleting!) by the public key hash (which is what we persist as its alias). Mess that up and you quickly find yourself deleting other keys unintentionally (I still need to recover my keychain)...

Thanks, Erik

Import resulting certificate to associate with private key
 
 
Q