MacOS Data Protection Keychain, SecItemDelete fails with errSecItemNotFound (-25300) while everything else works.

Hello, I have a problem with SecItemDelete when trying to delete a private key stored using the data protection keychain on macOS 13.5 (testing on MB Pro 2023).

Everything else works:

  • adding the item (using SecKeyCreateRandomKey)
  • getting a reference to the item using SecItemCopyMatching and reading the data

But calling SecItemDelete with kSecValueRef and a reference received from SecItemCopyMatching fails with errSecItemNotFound. What's even more interesting, the call to SecItemDelete fails with that error even if passed the exact same search dictionary (omitting the kSecReturnRef key) that results in successfully getting an item reference when passed to SecItemCopyMatching (the documentation of SecItemDelete refers to the SecItemCopyMatching documentation on construction a search dictionary, so, in theory, it should accept the same parameters).

I am at a dead end here honestly, and, despite knowing better, I'm starting to suspect this might be a bug in the API implementation. Any help is appreciated.


SecItemCopyMatching call:

auto params = cf::create_dict({{kSecClass, kSecClassKey},
                                   {kSecAttrKeyClass, kSecAttrKeyClassPrivate},
                                   {kSecAttrLabel, cf::from_string(create_key_name(name))},
                                   {kSecReturnRef, kCFBooleanTrue}});

SecKeyRef key_ref{nullptr};
auto status = ::SecItemCopyMatching(params.get(), (CFTypeRef*)&key_ref);

SecItemDeleteCall with ref from SecItemCopyMatching:

auto query = cf::create_dict({{kSecValueRef, key}});
sec::throw_if_fail(::SecItemDelete(query));

SecItemDeleteCall with search by key label:

auto query = cf::create_dict({{kSecClass, kSecClassKey},
                                   {kSecAttrKeyClass, kSecAttrKeyClassPrivate},
                                   {kSecAttrLabel, cf::from_string(create_key_name(name))}
                       });
sec::throw_if_fail(::SecItemDelete(query));

Private key creation params for SecKeyCreateRandomKey:

{kSecClass, kSecClassKey},
{kSecAttrKeyType, kSecAttrKeyTypeECSECPrimeRandom},
{kSecAttrKeySizeInBits, cf::make_guarded(::CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &bit_length.at(key_spec.curve)))},
{kSecAttrIsPermanent, kCFBooleanTrue},
{kSecUseDataProtectionKeychain, kCFBooleanTrue},
{kSecAttrLabel, cf::from_string(create_key_name(name))},
{kSecAttrIsExtractable, storage_spec.exportable ? kCFBooleanTrue : kCFBooleanFalse}

EDIT: Searching by label (same params as SecItemCopyMatching does indeed work, this was an error from my side, however I can't for the life of me figure out why using kSecValueRef doesn't work. It does work when targeting the file based keychain.

I can't for the life of me figure out why using kSecValueRef doesn't work.

Hmmm, this is working for me:

func create() throws {
    let key = try secCall { SecKeyCreateRandomKey([
        kSecAttrKeyType: kSecAttrKeyTypeECSECPrimeRandom,
        kSecAttrKeySizeInBits: 256,
        kSecAttrIsPermanent: true,
        kSecUseDataProtectionKeychain: true,
    ] as NSDictionary, $0) }
    print(key)
}

func list() throws {
    let attrsForKeys = try secCall { SecItemCopyMatching([
        kSecClass: kSecClassKey,
        kSecUseDataProtectionKeychain: true,
        kSecMatchLimit: kSecMatchLimitAll,
        kSecAttrAccessGroup: "SKMME9E2Y8.com.example.apple-samplecode.Test735284",
        kSecReturnAttributes: true,
    ] as NSDictionary, $0) } as! [[String: Any]]
    print(attrsForKeys.count)
}

func deleteOne() throws {
    let key = try secCall { SecItemCopyMatching([
        kSecClass: kSecClassKey,
        kSecUseDataProtectionKeychain: true,
        kSecAttrAccessGroup: "SKMME9E2Y8.com.example.apple-samplecode.Test735284",
        kSecReturnRef: true,
    ] as NSDictionary, $0) } as! SecKey
    try secCall { SecItemDelete([
        kSecValueRef: key,
        kSecUseDataProtectionKeychain: true,
    ] as NSDictionary) }
}

Note This uses the helpers from here.

I wired up buttons to each of these methods and clicked the Create button a few times. The List button then prints a count of the keys that I created. Clicking Delete works, and clicking List again shows the decreased count.

Oh, I had to specify a kSecAttrAccessGroup because otherwise the SecItemCopyMatching would return keys in a hardware token associated with my work Mac, and I didn’t want to accidentally delete one of those O-:

Share and Enjoy

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

MacOS Data Protection Keychain, SecItemDelete fails with errSecItemNotFound (-25300) while everything else works.
 
 
Q