Secure Enclave: Supporting Signing-Only, Key-Agreement-Only, and Dual-Purpose Keys

Hello everyone,

I'm trying to understand the actual limitations of the Secure Enclave (SEP) regarding cryptographic key capabilities, and more specifically how to generate and store the following key types in the keychain:

  • Sign only: The key can only be used for digital signatures.
  • Key agreement only: The key can only be used for key agreement operations.
  • All operations: The key can be used for both signing and key agreement.

I've noticed some apparent differences between Apple's Security framework and CryptoKit, and I'm trying to understand whether these differences reflect actual SEP limitations or simply framework-level restrictions.

1. Security framework: dual purpose keys

I'm currently using SecKeyCreateRandomKey to generate Secure Enclave-backed keys.

Here is the relevant code:

NSDictionary *privateAttributes = @{
    (__bridge id)kSecAttrIsPermanent :    @YES,
    (__bridge id)kSecAttrAccessControl :  ...,
    (__bridge id)kSecAttrApplicationTag : ...,
    // key agreement only
    (__bridge id)kSecAttrCanSign: @NO,
    (__bridge id)kSecAttrCanDerive: @YES
};

NSMutableDictionary *attributes = [ @{
    (__bridge id)kSecAttrKeyType :       (__bridge id)kSecAttrKeyTypeECSECPrimeRandom,
    (__bridge id)kSecAttrTokenID :       (__bridge id)kSecAttrTokenIDSecureEnclave,
    (__bridge id)kSecAttrKeySizeInBits : @256,
    (__bridge id)kSecPrivateKeyAttrs :   privateAttributes
} mutableCopy ];

CFErrorRef generationError = nullptr;
SecKeyRef privateKey = SecKeyCreateRandomKey(
    (__bridge CFDictionaryRef)attributes,
    &generationError
);

However, when experimenting with these attributes, I encountered the following error:

SecKeyCreateRandomKey failed: Error Domain=NSOSStatusErrorDomain Code=-50 "Failed to generate keypair"
Underlying error: Error Domain=CryptoTokenKit Code=-8 "sepkey: sign requires '1' value when present"

Using signing-only attributes results in the same kind of error.
This makes me wonder whether the Security framework actually allows generating (and adding entries in the keyChain) only keys with both capabilities enabled.

  • It is possible to create siging only key or key agreement only key with Security framework?
2. CryptoKit: Separate key types

On the other hand, CryptoKit seems to expose two distinct Secure Enclave key types:

P256.SecureEnclave.Signing.PrivateKey
P256.SecureEnclave.KeyAgreement.PrivateKey

From the API design, it appears that CryptoKit only allows creating keys dedicated to one operation or the other. For example, a Signing.PrivateKey cannot be used for key agreement, and a KeyAgreement.PrivateKey cannot be used for signing.

  • Is it possible to create both capabilities keys with CryptoKit framework?
3. Actual Secure Enclave limitations
  • What are the actual cryptographic capability limitations imposed by the SEP itself?

I'd particularly like to understand which restrictions originate from:

  • The Secure Enclave hardware.
  • Security and CryptoKit frameworks.

Is there a documented way to generate and store in the keychain:

  • A signing-only Secure Enclave key?
  • A key-agreement-only Secure Enclave key?
  • A Secure Enclave key supporting both operations?

Thanks in advance,
Idopte

Answered by DTS Engineer in 915015022
Written by idopte in 855633021
  • A signing-only Secure Enclave key?
  • A key-agreement-only Secure Enclave key?

One option here is to store the key bytes in a generic password item, as explained (for different purposes) in other keys types discussion in Storing CryptoKit Keys in the Keychain. I’m not sure that buys you much, as I explain below, but the mechanics are sound.

Written by idopte in 855633021
  • A Secure Enclave key supporting both operations?

It looks like you’ve found that already, namely, to use the SecKey API.


AFAICT the strong types used by Apple CryptoKit are there to promote best crypto practice rather than because of capabilities of the SE. Consider this:

let k1 = try SecureEnclave.P256.KeyAgreement.PrivateKey()
let k1d = k1.dataRepresentation
let k2 = try SecureEnclave.P256.KeyAgreement.PrivateKey(dataRepresentation: k1d)
_ = k2
let k3 = try SecureEnclave.P256.Signing.PrivateKey(dataRepresentation: k1d)

let d = Data("Hello Cruel World!".utf8)
let sig = try k3.signature(for: d)
let p3 = k3.publicKey
print(p3.isValidSignature(sig, for: d)) // -> true

That is, I can create an SE-protected key (k1), get its data representation (k1d), and then create both KeyAgreement and Signing variants (k2 and k3) from the same bytes. Moreover, the signing variant actually works for signing.

Now, I’m not suggesting that you actually use this ability in a shipping product, but it does provide insight into the underlying implementation.

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

Written by idopte in 855633021
  • A signing-only Secure Enclave key?
  • A key-agreement-only Secure Enclave key?

One option here is to store the key bytes in a generic password item, as explained (for different purposes) in other keys types discussion in Storing CryptoKit Keys in the Keychain. I’m not sure that buys you much, as I explain below, but the mechanics are sound.

Written by idopte in 855633021
  • A Secure Enclave key supporting both operations?

It looks like you’ve found that already, namely, to use the SecKey API.


AFAICT the strong types used by Apple CryptoKit are there to promote best crypto practice rather than because of capabilities of the SE. Consider this:

let k1 = try SecureEnclave.P256.KeyAgreement.PrivateKey()
let k1d = k1.dataRepresentation
let k2 = try SecureEnclave.P256.KeyAgreement.PrivateKey(dataRepresentation: k1d)
_ = k2
let k3 = try SecureEnclave.P256.Signing.PrivateKey(dataRepresentation: k1d)

let d = Data("Hello Cruel World!".utf8)
let sig = try k3.signature(for: d)
let p3 = k3.publicKey
print(p3.isValidSignature(sig, for: d)) // -> true

That is, I can create an SE-protected key (k1), get its data representation (k1d), and then create both KeyAgreement and Signing variants (k2 and k3) from the same bytes. Moreover, the signing variant actually works for signing.

Now, I’m not suggesting that you actually use this ability in a shipping product, but it does provide insight into the underlying implementation.

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