Given an X509 certificate not in keychain, how do I detect a corresponding private key in keychain?

I have a DER encoded digital certificate that comes from outside a keychain. I am trying to search the keychain for a matching private key.

I am able to parse the DER certificate and show all the values as follows:

		CFErrorRef error = NULL;

		CFDataRef data = CFDataCreateWithBytesNoCopy(kCFAllocatorDefault, cert->der, cert->len, kCFAllocatorNull);

		SecCertificateRef certref = SecCertificateCreateWithData(kCFAllocatorDefault, data);

		CFDictionaryRef values = SecCertificateCopyValues(certref, NULL, &error);

	    CFShow(values);

I am able to search for keys in the keychain as follows:

    CFTypeRef keys = NULL;

    CFIndex count;
    CFIndex i;

    CFStringRef dictkeys[] = {
        kSecClass,
        kSecMatchLimit,
        kSecReturnRef,
        kSecReturnAttributes
    };

    CFTypeRef dictvalues[] = {
        kSecClassKey,
        kSecMatchLimitAll,
        kCFBooleanTrue,
        kCFBooleanTrue
    };

    CFDictionaryRef query = CFDictionaryCreate(
        NULL,
        (const void **) dictkeys,
        dictvalues,
        sizeof(dictkeys) / sizeof(dictkeys[0]),
        &kCFTypeDictionaryKeyCallBacks,
        &kCFTypeDictionaryValueCallBacks
    );

    OSStatus err = SecItemCopyMatching(query, &keys);

Where I am stuck is how to join the two together.

What value from the certificate should I be extracting to allow a lookup for a link to the private key?

What parameter do I pass into SecItemCopyMatching to search for a matching private key?

Accepted Reply

The best answer to your original question depends on the platform:

  • On macOS you have the handy-dandy SecIdentityCreateWithCertificate call, which does exactly what you want.

  • On other platforms the only good option is to rely on the matching algorithm, based on kSecAttrApplicationLabel and kSecAttrPublicKeyHash, that’s described in SecItem attributes for keys.

In theory I could emulate this code with SecCertificateCopyKey and SecSHA1DigestCreate, but this would then break as soon as Apple used a digest other than SHA1.

I wouldn’t worry about this algorithm breaking. Given that these attributes are baked into a bazillion keychains scattered across a bazillion devices, it’d be hard for Apple to change the algorithm.

As far as implementing this algorithm is concerned, be aware that SecSHA1DigestCreate is not public API. The droids you’re looking for here are:

  • SecCertificateCopyKey

  • SecKeyCopyExternalRepresentation

  • Common Crypto’s CC_SHA1 (Folks using Swift can take advantage o Apple CryptoKit’s much nicer Insecure.SHA1.hash(_:).)

Share and Enjoy

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

Replies

Lots of digging, stumbled on something that said that kSecAttrPublicKeyHash in the certificate is supposed to match kSecAttrApplicationLabel on the key.

It does not however appear possible to retrieve kSecAttrPublicKeyHash from a certificate. The function this is set in is defined below, but appears to be a private API.

https://github.com/apple-opensource/Security/blob/5e9101b3bd1fb096bae4f40e79d50426ba1db8e9/OSX/sec/Security/SecCertificate.c#L5627

In theory I could emulate this code with SecCertificateCopyKey() and SecSHA1DigestCreate(), but this would then break as soon as Apple used a digest other than SHA1.

Am I missing something, or is this a bug?

The best answer to your original question depends on the platform:

  • On macOS you have the handy-dandy SecIdentityCreateWithCertificate call, which does exactly what you want.

  • On other platforms the only good option is to rely on the matching algorithm, based on kSecAttrApplicationLabel and kSecAttrPublicKeyHash, that’s described in SecItem attributes for keys.

In theory I could emulate this code with SecCertificateCopyKey and SecSHA1DigestCreate, but this would then break as soon as Apple used a digest other than SHA1.

I wouldn’t worry about this algorithm breaking. Given that these attributes are baked into a bazillion keychains scattered across a bazillion devices, it’d be hard for Apple to change the algorithm.

As far as implementing this algorithm is concerned, be aware that SecSHA1DigestCreate is not public API. The droids you’re looking for here are:

  • SecCertificateCopyKey

  • SecKeyCopyExternalRepresentation

  • Common Crypto’s CC_SHA1 (Folks using Swift can take advantage o Apple CryptoKit’s much nicer Insecure.SHA1.hash(_:).)

Share and Enjoy

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

SecIdentityCreateWithCertificate() is the secret for MacOS thank you.

In this case, the certificate created with SecCertificateCreateWithData() is passed into SecIdentityCreateWithCertificate(), and you get a SecIdentityRef that you can extract the key from.

Getting the key exported is the next problem, SecKeyCopyExternalRepresentation's output format isn't well defined. Subject for a different post.