Unexpected Core Foundation attribute types when inspecting an existing Secure Enclave-associated Keychain key on macOS

Hello Apple Developer community,

I'm investigating an unexpected metadata representation returned by Apple's Security framework while inspecting an existing Secure Enclave-associated Keychain item on macOS.

A read-only inspection using SecItemCopyMatching returns one matching key record with a successful status, a Secure Enclave token identifier (com.apple.setoken), and a 256-bit key size.

However, direct Core Foundation type inspection reports:

  • kSecAttrKeyType: CFNumber
  • kSecAttrKeyClass: CFNumber
  • kSecAttrSynchronizable: CFNumber

The raw Core Foundation observations and bridged observations agree.

Our current validator rejects these unexpected types and returns UNSUPPORTED_OR_INCOMPLETE_METADATA.

We have deliberately stopped further key operations pending clarification.

Questions:

  1. Can these CFNumber values legitimately appear in a dictionary returned by SecItemCopyMatching for Secure Enclave-associated key items on macOS?
  2. What returned value types should be expected for these three attributes, and are they dependent on the query or Keychain implementation?
  3. Is there an Apple-recommended way to normalize or validate these attributes without incorrectly accepting incompatible metadata?
  4. How should an application distinguish an existing Secure Enclave-backed key from an incomplete or unsupported Keychain record without performing a private-key operation?
  5. Are there relevant Security framework documentation references or known implementation differences we should consult?

Our aim is to validate the existing record safely without regenerating, deleting, or using the private key.

Thank you for your guidance.

Answered by DTS Engineer in 914775022

I’m sorry but I don’t understand what you’re asking for here. It seems like you’ve got the attributes of a keychain item and some of the properties are numbers. That’s not unexpected. If you look in <Security/SecItem.h> you’ll see a doc comment like this:

    @constant kSecAttrKeyType Specifies a dictionary key whose value is a
        CFNumberRef indicating the algorithm associated with this key
        …

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

I’m sorry but I don’t understand what you’re asking for here. It seems like you’ve got the attributes of a keychain item and some of the properties are numbers. That’s not unexpected. If you look in <Security/SecItem.h> you’ll see a doc comment like this:

    @constant kSecAttrKeyType Specifies a dictionary key whose value is a
        CFNumberRef indicating the algorithm associated with this key
        …

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

Thank you, Quinn. Your clarification is helpful.

I understand now that a numeric value for kSecAttrKeyType is not necessarily unexpected, and that I should distinguish the attribute dictionary key's type from its corresponding value's type.

To clarify the original question: during a read-only inspection of an existing Secure Enclave-related Keychain item, the returned attributes included:

  • kSecAttrKeyType: CFNumber
  • kSecAttrKeyClass: CFNumber
  • kSecAttrSynchronizable: CFNumber

My concern is not simply that numeric attributes exist. I am trying to determine whether these specific returned types are expected, particularly kSecAttrSynchronizable, whose documented value type is CFBoolean.

Could you clarify whether these representations may legitimately differ depending on the Security API or Keychain implementation used, and whether they are sufficient to validate the item's key metadata?

I can provide the exact read-only API call and Core Foundation type IDs for additional context.

Thank you for your guidance.

Written by babaolon in 914847022

I am trying to determine whether these specific returned types are expected … ?

Why is that relevant to the code you’re writing?

My general advice here is that you:

  • Read the documentation for this API, which is reasonably comprehensive;
  • And then read the doc comments in the <Security/SecItem.h>, which clarifies a bunch of minor points;
  • And then read SecItem: Fundamentals and SecItem: Pitfalls and Best Practices, which is my take on how best to use this API.

If you have follow-up questions then I’m happy to answer them here, but you’ll need to provide more context to your question. I’m not able to explain the the API to you one question at a time; that’s what our documentation is for.

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

Hi Quinn,

Thank you for your earlier response and the documentation references.

To clarify the application context, we're developing a macOS research prototype that would use an owner-presence-gated private-key operation to sign local research-run intent records. This is not an account-login flow, and we're not treating Keychain metadata as proof of Secure Enclave backing or cryptographic provenance.

Before performing any signing operation, we need to examine candidate Keychain key items, reject ambiguous or unsupported metadata, and ensure that the key reference eventually used is the exact item whose attributes passed our checks.

Our current prototype uses SecItemCopyMatching with kSecClassKey, the Data Protection Keychain, kSecReturnAttributes, and kSecMatchLimitAll for metadata inspection. A separate query later uses kSecMatchLimitOne and kSecReturnRef to obtain a signing reference.

We recognize that these separate queries do not, by themselves, establish identity continuity.

During an earlier read-only inspection, values for kSecAttrKeyType, kSecAttrKeyClass, and kSecAttrSynchronizable were reported as CFNumber. We've deliberately avoided interpreting these values as proof of key type, key class, synchronization state, or Secure Enclave backing. We also understand that SecItemCopyMatching and SecKeyCopyAttributes have distinct return contracts.

Our main question is:

For a kSecClassKey item in the macOS Data Protection Keychain, is it supported to request kSecReturnAttributes and kSecReturnRef together with kSecMatchLimitAll, inspect each returned result, select exactly one acceptable candidate, and retain that same SecKey reference for a subsequent operation?

If this is not the recommended approach, what documented mechanism should we use to preserve exact item identity between metadata inspection and obtaining the signing reference?

Any clarification about safely interpreting or rejecting the unexpected numeric attribute representations would also be helpful.

We are keeping actual signing disabled until these uncertainties are resolved.

Thank you for your guidance.

Written by babaolon in 915233022

For a kSecClassKey item in the macOS Data Protection Keychain, is it supported to request kSecReturnAttributes and kSecReturnRef together with kSecMatchLimitAll, inspect each returned result, select exactly one acceptable candidate, and retain that same SecKey reference for a subsequent operation?

Sure. That’s a reasonable way to use the API. However, it’s better if you can pre-filter the list by supply more attributes to the query. For example, rather than write this code:

let attributesOfAllKeys = try secCall { SecItemCopyMatching([
    kSecClass: kSecClassKey,
    kSecMatchLimit: kSecMatchLimitAll,
    kSecReturnRef: true,
    kSecReturnAttributes: true,
] as NSDictionary, $0) } as! [[String: Any]]
let attributesOfAllPublicKeys = attributesOfAllKeys.filter { attributes in
    attributes[kSecAttrKeyClass as String] as? String == kSecAttrKeyClassPublic as String
}

it’s better to write this code:

let attributesOfAllPublicKeys = try secCall { SecItemCopyMatching([
    kSecClass: kSecClassKey,
    kSecMatchLimit: kSecMatchLimitAll,
    kSecAttrKeyClass: kSecAttrKeyClassPublic,
    kSecReturnRef: true,
    kSecReturnAttributes: true,
] as NSDictionary, $0) } as! [[String: Any]]

That way the filtering is done is the security daemon, which reduces both the search and IPC costs.

However, there are some cases where it’s necessary to do the filtering client side:

  • When it’s not possible to express your filtering criteria using the very limited query syntax supported by SecItemCopyMatching.
  • When that filtering doesn’t work correctly. I’ve seen examples of this when using the file-based keychain shim on the Mac. See the Careful With that Shim, Mac Developer section of SecItem: Pitfalls and Best Practices for a specific example.

And, to be clear, the cost of doing this filtering client side isn’t onerous. Well, not unless you have a bazillion keychain items, but that’s not a great idea regardless of this issue.

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