Apple demo code for using kSecAttrLabel to retrieve a certificate doesn't work

I am following the code in the linked documentation below to add and retrieve a SecCertificate from the login keychain using the kSecAttrLabel attribute. The demo code in the linked page below isn't complete but the relevant parts in my code are lifted almost verbatim. Am I doing something wrong or is this a bug or is something deprecated?

https://developer.apple.com/documentation/security/certificate_key_and_trust_services/certificates/storing_a_certificate_in_the_keychain

The add portion of the code works fine but the retrieval fails with the error: The specified item could not be found in the keychain.. Visual inspection of the keychain shows the certificate in the login keychain under the 'Certificates' tab as expected. An alternative query to get the certificate back using the kSecMatchSubjectContains attribute works fine. I'm very new to Swift so pardon any terrible formatting or naming faux pas.

In case its relevant I'm using: macOS 11.5.2 Xcode 12.5.1 Apple Swift version 5.4 (swiftlang-1205.0.26.9 clang-1205.0.19.55) Target: arm64-apple-darwin20.6.0

Demo code and data is below:

import Foundation

// a label to use in the storage and retrieval queries for SecItemAdd and SecItemCopyMatching
let certLabel = "specialtestlabel"

// a pem formatted certificate that we can use to add and try to retrieve from the default (login) keychain
// note that I have deleted the private key and certificate so no security issues for me posting it here
let certPEM = """
-----BEGIN CERTIFICATE-----
MIIE9zCCA1+gAwIBAgIQc1P13Pj0F7xIYs9PVe5KgDANBgkqhkiG9w0BAQsFADCB
kzEeMBwGA1UEChMVbWtjZXJ0IGRldmVsb3BtZW50IENBMTQwMgYDVQQLDCtqc0BK
b3Nocy1NYWNCb29rLUFpci5sb2NhbCAoSm9zaCBTY2hsZXNzZXIpMTswOQYDVQQD
DDJta2NlcnQganNASm9zaHMtTWFjQm9vay1BaXIubG9jYWwgKEpvc2ggU2NobGVz
c2VyKTAeFw0yMTA4MjQyMDEzMDhaFw0zMTA4MjQyMDEzMDhaMIGTMR4wHAYDVQQK
ExVta2NlcnQgZGV2ZWxvcG1lbnQgQ0ExNDAyBgNVBAsMK2pzQEpvc2hzLU1hY0Jv
b2stQWlyLmxvY2FsIChKb3NoIFNjaGxlc3NlcikxOzA5BgNVBAMMMm1rY2VydCBq
c0BKb3Nocy1NYWNCb29rLUFpci5sb2NhbCAoSm9zaCBTY2hsZXNzZXIpMIIBojAN
BgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAwyDRTPGMvJwre8j73QnTLsjYBmmF
3HQmtp/pFDUqwuj16rMgh7jSjpXXLjqJfeCmPuuPFUTltkuINsnbycHuebJiirr5
gSw5tm/Yf25mZj4x+E3bGqo7t8z4IhSz9Qkx3Da03Mb9FnCs0eQx4B3SgH2ZA3Fa
OznnD+5XBEnfHa6AzA1xxaFEtbS5FStYEysbpssEWmiflpBYIFfMqqLkup7SkFdm
vDhpCrOlg9vxC4FaWExgVwgRFZvyrvZuwX0jwp09QZY7T8SLAoMRbv2ucSaD9PaC
uwobdNn2p14qHQqTlPE5kj/SIWziUEgkAwDp4kVoz/jtlW/lTowobvtcDEAt5l7W
zicVKxYGPJz1pkmFD9EBsKuOLPqdmsOP860d0naqM6qv08LJTSVpYc8l7w/8gwdi
WT2Z+thdPke364gDzpOjl6CooKrE4lAgclShwHHr2XFgEZ7fSLcQivJAVPFr8o3f
ZKcfEZgT4Njg5TjayacVTCatDQjWTphhe9u3AgMBAAGjRTBDMA4GA1UdDwEB/wQE
AwICBDASBgNVHRMBAf8ECDAGAQH/AgEAMB0GA1UdDgQWBBTgt9SlhsH0usZCeGF2
stxXP3TNiDANBgkqhkiG9w0BAQsFAAOCAYEAXvltDX5c9yiCLCw6RmAGRnk49YgF
**8W7wLQn8NdudEGEtOtaPjFRjCa+QxIGMnYcYDhOQIqa6VrxyHLF4HvfuBveLP6
kQ0mYh8h41l7KSHSRqzHODPzoIEQQ4CQf9z0akpB2934ZfS7JT59eYBn+elAqKRh
WHAW9cG+i+04FfQUrTDRjyIwTtfKNFR4eqombYzP9ozvZwmyTb2jDxk4l/KimBkB
MmXYLehnPf8ombBAMoYOAS8XdUY21Wz3+F4uE4WyUse8pawdDWf8eVsRW3DW0wvd
B75hZz7EVbXSj1xmrNRB3QWY6gleZH7lucrt/cqEHlV9MfWLkq85HAzRv3ZUSd32
GDuDcv6pwVPRUceO/flBr5nFqlr5ZOW6LxXVG7GV+hKGfJPv31jm4yntbkbDA5sp
YcbEMUQP+4ZB9srHHYDG18ZOgnFW7YZuCLb32VS8L5hogAqYJJpwuL8gT2z2y3GP
6GBE9JdgjQwq1REgNTSr8Ry8ZimwlHfi7nk1
-----END CERTIFICATE-----
"""

enum CertError: Error {
    case PEMtoSecCertificateError
    case addTokeychainError
    case getFromkeychainError
}

// create a SecCertificate from the PEM string
let PEMNoHeader = certPEM.split(separator: "\n").dropFirst().dropLast().joined(separator: "")
let CertDER = Data(base64Encoded: PEMNoHeader)!

guard let certificate = SecCertificateCreateWithData(nil, CertDER as CFData) else {
    print("There was an error creating the certificate")
    throw CertError.PEMtoSecCertificateError
}

let addquery: [String: Any] = [kSecClass as String: kSecClassCertificate,
                            kSecValueRef as String: certificate,
                            kSecAttrLabel as String: certLabel]

let addStatus = SecItemAdd(addquery as CFDictionary, nil)

guard addStatus == errSecSuccess else {
    print(SecCopyErrorMessageString(addStatus, nil)!)
    throw CertError.addTokeychainError
}

// for some reason this query doesnt work... :(
let getQuery: [String: Any] = [kSecClass as String: kSecClassCertificate,
                               kSecAttrLabel as String: certLabel,
                               kSecReturnRef as String: kCFBooleanTrue!]

// note that we arent using this here but if you use the below otherGetQuery, everything works as expected
// i used mkcert to generate the certificate, it has been deleted off of my system
let otherGetQuery: [String: Any] = [kSecClass as String: kSecClassCertificate,
                                    kSecMatchSubjectContains as String: "mkcert",
                                    kSecReturnRef as String: kCFBooleanTrue!]

// lets try to get the certificate back from the keychain
var item: CFTypeRef?
let copyStatus = SecItemCopyMatching(getQuery as CFDictionary, &item)
guard copyStatus == errSecSuccess else {
    print(SecCopyErrorMessageString(copyStatus, nil)!)
    throw CertError.getFromkeychainError
}

// if we make it to here, everything below this comment works as expected
let certificateKeychain = item as! SecCertificate

let CertDERKeychain = SecCertificateCopyData(certificateKeychain) as Data

print(CertDER.base64EncodedString())
print(CertDERKeychain.base64EncodedString())

Does this work if you target the data protection keychain?

Share and Enjoy

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

I am going to start by addressing some up front items here and then let's see where this get's us. The referenced link runs code that expects a DER based certificate, or a .cer file on disk somewhere. If you try it from that context instead of with the PEM, does this get you any farther?

Matt Eaton
DTS Engineering, CoreOS
meaton3@apple.com

I tried to run your code but the Base64 data you posted is invalid (it contains * characters). I suspect it got munged by the posting process )-:

Can you tweak your code to not relying on Base64. In a situation like this I’d use the openssl tool to convert the certificate to DER, then dump the DER as hex, then munge the hex bytes into a Swift array:

let certBytes: [UInt8] = [
    0x01, 0x02, … and so on …
]
let certData = Data(certBytes)

That’ll have the added benefit of reducing your snippet substantially, meaning there’s less scope for problems.

Share and Enjoy

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

Thanks Quinn, Yes, the certificate got munged in the copy/paste. The new code is larger than the answer character limit, so it's attached as a file. I had to put a .txt extension on it to get it to upload.

Quick note for anybody who comes upon this in the future, here are the shell commands I used to transform a PEM formatted file "test.pem" into the format Quinn requested, replace filenames as appropriate. You can copy and paste the results from the terminal right into a swift array in Xcode between the brackets.

openssl x509 -in test.pem -out test.der -outform DER

hexdump -v -e '1/1 "0x%02, "' test.der

Also of note for any future readers, this code contains a self signed CA certificate(root). I have deleted the private key, but why should you trust me? When you run the code it will import the certificate, but it will be untrusted. Remember to use Keychain Access to delete the certificate after running the code. If it were to become trusted somehow it would be a big security hole on your system If I still had the private key.

Accepted Answer

Thanks for the new test code.

Remember to use Keychain Access to delete the certificate after running the code.

Or do what I do, and run tests like this in a VM. That way you don’t have to worry about the test changing your system state.

[Back in the day I once ran a keychain test that, due to my misunderstanding of how the SecItem API works, ended up deleting all the private and public keys in my keychain. Ouch! I restored them from a backup but, yeah, since then I’ve been a bit more circumspect (-: ]

So I ran your code and then dumped the keychain:

% security dump-Keychain
…
keychain: "/Users/quinn/Library/Keychains/login.keychain-db"
version: 512
class: 0x80001000 
attributes:
    …
    "labl"<blob>="mkcert js@Joshs-MacBook-Air.local (…)"
    …
…

Note that the kSecAttrLabel (labl) property is present, it just has the wrong value. This problem crops up from time to time. It’s caused by this code:

let addQuery: NSDictionary = [
    kSecClass: kSecClassCertificate,
    kSecValueRef: certificate,
    kSecAttrLabel: certLabel
]

Note I’ve changed this to use an NSDictionary, which gets rid of a bunch of boilerplate.

You can think of the keychain as a database where each keychain item class is a table, each attribute is a column in that table, and each item of that class is a row. When you add a keychain item using kSecValueRef, the keychain derives the attributes from the ‘ref’ object you passed in. That set of attributes includes a value for kSecAttrLabel, and that overrides the value that you passed in explicitly. Thus, the keychain items ends up with the label derived from the certificate (mkcert js@Joshs-MacBook-Air.local (…)) rather than the label you passed in (specialtestlabel).

This is less than ideal. The data protection keychain does this correctly, which suggests that there’s an issue in the SecItem shim that targets the traditional file-based keychain. Feel free to file a bug about that.

Do you have to use the file-based keychain? If not, the best solution here is to switch to the data protection keychain. It fixes this problem and it’s also our preferred keychain moving forward.

The data protection keychain (kSecUseDataProtectionKeychain) is available to code running in a user context (that is, not a daemon) on macOS 10.15 and later (10.9 if you use kSecAttrSynchronizable).

Share and Enjoy

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

Apple demo code for using kSecAttrLabel to retrieve a certificate doesn't work
 
 
Q