Keychain Identity backed by key generated in secure enclave

Docs suggest that an identity consists of a private key packaged with the certificate that contains and vouches for the corresponding public key.

Is it possible to create an identity backed by a private key generated in secure enclave?

Is it possible to create an identity backed by a private key generated in secure enclave?

Yes. The process here is:

  1. Generate a permanent (that is, in the keychain) private key that’s protected by the SE.

  2. Get the public key from that private key.

  3. Pass that public key to whatever system you have for wrapping it in a certificate.

  4. Add the resulting certificate to the keychain.

If the public key in the certificate matches the private key, a keychain query for digital identities (kSecClassIdentity) will return one.


Keep in mind that a digital identity isn’t really in the keychain. It’s actually a combination of a certificate and the private key that matches the public key within that certificate. The SecItem API has a digital identity keychain item class, namely kSecClassIdentity. However, the keychain does not store digital identities. When you add a digital identity to the keychain, the system stores its components, the certificate and the private key, separately, using kSecClassCertificate and kSecClassKey respectively.

This has a number of non-obvious effects. For example:

  • Adding a certificate can ‘add’ a digital identity. If the new certificate happens to match a private key that’s already in the keychain, the keychain treats that pair as a digital identity.

  • Likewise when you add a private key.

  • Similarly, removing a certificate or private key can ‘remove’ a digital identity.

  • Adding a digital identity will either add a private key, or a certificate, or both, depending on what’s already in the keychain.

  • Removing a digital identity removes its certificate. It might also remove the private key, depending on whether that private key is used by a different digital identity.

Share and Enjoy

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

Hey @eskimo thank you for your reply!

I followed your instructions, but identity does not appear in a keychain. I manually compared public portion of a key generated in Secure Enclave with a public key of a certificate surfaced in Keychain Access. They match, but I cannot see the certificate in a list of all available identities.

I also tried to manually create and add identity to the keychain. In this case SecItemAdd fails with -50 errSecParam.

Here is the code I'm using:

func run() {
 let key = genKey()
  
 let publicKey = SecKeyCopyPublicKey(key!)
 let d = Data(referencing: SecKeyCopyExternalRepresentation(publicKey!, nil)!)
 print("publicKey:", d.hexEncodedString()) // compare with a public key of a cert in Keychain Access app
  
 let certificate = genCert(key: key!)
 addCertificateToKeychain(certificate: certificate!)
 // addIdentityToKeychain(certificate: certificate!)
 listIdentities()
}

func genKey() -> SecKey? {
 let access = SecAccessControlCreateWithFlags(
  kCFAllocatorDefault,
  kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly,
  .privateKeyUsage,
  nil)!
  
 let attributes: NSDictionary = [
  kSecAttrKeyType: kSecAttrKeyTypeECSECPrimeRandom,
  kSecAttrKeySizeInBits: 256,
  kSecAttrTokenID: kSecAttrTokenIDSecureEnclave,
  kSecPrivateKeyAttrs: [
   kSecAttrIsPermanent: true,
   kSecAttrAccessControl: access
  ]
 ]
  
 return SecKeyCreateRandomKey(attributes, nil)
}

func genCert(key: SecKey) -> SecCertificate? {
  // generate and return x509 certificate
}

func addCertificateToKeychain(certificate: SecCertificate) {
 let addquery: [String: Any] = [kSecClass as String: kSecClassCertificate,
                 kSecValueRef as String: certificate]
 let status = SecItemAdd(addquery as CFDictionary, nil)
 print("SecItemAdd Certificate", status) // status 0
}

func addIdentityToKeychain(certificate: SecCertificate) {
 var identity: SecIdentity?
 let statusIDCreate = SecIdentityCreateWithCertificate(nil, certificate, &identity)
 print("SecItemIdentityCreate", statusIDCreate) // status 0
  
 let addquery: [String: Any] = [kSecClass as String: kSecClassIdentity,
                 kSecValueRef as String: identity!]
 let status = SecItemAdd(addquery as CFDictionary, nil)
 print("SecItemAdd Identity", status) // status -50 errSecParam
}

func listIdentities() {
 let query: [String: Any] = [kSecClass as String: kSecClassIdentity,
               kSecMatchLimit as String: kSecMatchLimitAll,
               kSecReturnRef as String: true]
 var item: CFTypeRef?
 SecItemCopyMatching(query as CFDictionary, &item)
  
 for id in item as! Array<SecIdentity> {
  var certificate: SecCertificate?
  SecIdentityCopyCertificate(id, &certificate)
  let desc = SecCertificateCopyLongDescription(kCFAllocatorDefault, certificate!, nil)
  print(desc!)
 }
}

extension Data {
 func hexEncodedString() -> String {
  return self.map { String(format: "%02hhX ", $0) }.joined()
 }
}

Am I doing something wrong?

Thank you

Accepted Answer

The code snippet you posted contains a called to SecIdentityCreateWithCertificate. That’s only available on macOS. Are you doing this on the Mac?

If so, be aware that SE keys always end up in the data protection keychain. If you want such a key to match up with a certificate to form a digital identity, you have to add the certificate to the data protection keychain as well. By default certificates you add go into the file-based keychain.

Also, when working with the data protection keychain it’s likely that macOS-only functions, like SecIdentityCreateWithCertificate, won’t work as you expect. Stick to stuff that’s available on both macOS and iOS.

For more background to the above, read TN3137 On Mac keychain APIs and implementations.

When debugging problems like this I usually do the following:

  1. Call SecItemCopyMatching to get the attributes of the key.

  2. Call SecItemCopyMatching to get the attributes of the certificate.

  3. Make sure that public key hash values line up. See SecItem attributes for keys for the details.

Share and Enjoy

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

Hey!

Yes, I'm developing an app for mac, with no intentions to run it on iOS ever. My bad, should've mentioned that in the beginning.

Is it even possible to add a certificate to the data protection keychain? SecItemAdd attributes do not allow to specify a keychain.

There is SecItemImport method that has importKeychain: SecKeychain? attribute, but I didn't find a way to get a reference to data protection keychain. Besides, most methods in SecKeychain are deprecated.

Thank you!

Found kSecUseDataProtectionKeychain, it is not listed in SecItemAdd attributes section, but it works! Thank you

Keychain Identity backed by key generated in secure enclave
 
 
Q