Client certificate using CryptoKit

I'm trying to implement client certificate authentication in the URLSessionDelegate.urlSession(:didReceive:completionHandler:) by using the URLCredential(identity:certificates:persistence:) and CryptoKit, but it appears this is currently not supported. On my client I generate a key and a CSR and after I sent that to the server, I receive an X.509 certificate which I store in the Keychain.

I used to create the keys as SecKey objects, which were also stored in the Keychain; this works fine. Now I would like to use the CryptoKit keys, preferably the SecureEnclave.P256 ones. It appears that storing these keys, as suggested by Apple (generic passwords), does not create the SecIdentity I'm after that's needed to create the URLCredential, nor have I been able to transform a SecKey created in the Secure Enclave to a CryptoKit.SecureEnclave.P256 key. The SecKeyCopyExternalRepresentation function simply returns an error telling me that I cannot export Secure Enclave keys (which is weird, as CryptoKit's implementation does somewhat allow this).

Also, the URLCredential.init does not allow you to add a private key directly, nor does the SecIdentity provide any way of manually creating it, by supplying the right keys and the right certificates.

How can I use CryptoKit for client certificate authentication or how can I use CryptoKit or the older SecKey implementations to create SecIdentity or URLCredential objects that will work?

Replies

You’ll need to use Security framework for this. CryptoKit doesn’t have any support for certificates, and hence digital identities, and that’s absolutely crucial for this task.

Now I would like to use the CryptoKit keys, preferably the SecureEnclave.P256 ones

The Security framework does let you protect your private key using the SE, and a digital identity that includes such a private key should work with URLSession.

Share and Enjoy

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

Right, so I did some more experimenting with both of the keys. Transforming the SecureEnclave key into a SecKey will always generate a new private key in the Secure Enclave, causing the initial Secure Enclave key and the SecKey to be non identical:

let privateKey = try SecureEnclave.P256.Signing.PrivateKey()

let attributes: [CFString: Any] = [
    kSecClass: kSecClassKey,
    kSecAttrKeyType: kSecAttrKeyTypeECSECPrimeRandom,
    kSecAttrKeyClass: kSecAttrKeyClassPrivate,
    kSecAttrTokenID: kSecAttrTokenIDSecureEnclave
]

let secKey = SecKeyCreateWithData(privateKey.dataRepresentation as CFData, attributes as CFDictionary, nil)
// let secKey = SecKeyCreateWithData(Data() as CFData, attributes as CFDictionary, nil)

Interestingly enough, it doesn't really matter what Data you provide to the SecKeyCreateWithData function when you set the kSecAttrTokenID field, odd... We do however get a valid SecKey reference which can be used, but as mentioned, it's a different one from the privateKey we created before. I checked this by comparing the public keys of both of these private keys, which do not match.

Now, normally speaking, the SecKeyCopyExternalRepresentation function will result in an error for Secure Enclave SecKey objects, which makes sense, it's a non extractable key, but the SecureEnclave enum exposes the Data through the dataRepresentation. This appears to simply be an ASN.1 octet stream containing the X9.62 export of the actual Secure Enclave private key. So I decided to do some more digging and found out that we can get the same data through from the SecKey, only at key generation time.

let attributes: [CFString: Any] = [
    kSecClass: kSecClassKey,
    kSecAttrKeyType: kSecAttrKeyTypeECSECPrimeRandom,
    kSecAttrKeyClass: kSecAttrKeyClassPrivate,
    kSecAttrTokenID: kSecAttrTokenIDSecureEnclave
]

let secKey = SecKeyCreateRandomKey(attributes as CFDictionary, nil)
let secAttributes = (SecKeyCopyAttributes(secKey!) as! [CFString: Any])
let secBytes = secAttributes["toid" as CFString] as! Data

let privateKey = try SecureEnclave.P256.Signing.PrivateKey(dataRepresentation: secBytes)

The toid field is available right after we create the key. It exposes the same data as the SecureEnclave enum (dataRepresentation) and we can actually create the SecureEnclave` key if we use this data.

Now I end up with a couple more questions:

  1. Is there no way to convert a SecureEnclave key into a SecKey somehow?
  2. Is the dataRepresentation of the SecureEnclave insecure after all, as one can easily extract X9.62 data from it? (And therefor also the toid field in the attributes)?
  3. Is the toid field something I can use to continue (also in a production app)?
  • Ah, this worked, for now, only on a simulator. I tested this on a real device, and now the dataRepresentation is something completely different...

Add a Comment

I tested this on a real device, and now the dataRepresentation is something completely different

Right. If you poke around in the bytes it’s pretty clear that this the raw key bytes that have been wrapped by the SE.

Is there no way to convert a SecureEnclave key into a SecKey somehow?

I’m pretty sure I’ve done that by passing those bytes to SecKeyCreateWithData. However, that doesn’t help because what you need for URLSession is a digital identity (SecIdentity) and there’s no way to create one of those from a SecKey and SecCertificate combo. There’s only two ways to create a digital identity on iOS:

  • Using SecPKCS12Import, which isn’t useful here

  • Using SecItemCopyMatching, which gets it from the keychain

Share and Enjoy

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

Is the process for generating a CSR using a SecKey documented anywhere? Creating SecKeys backed by the Secure Enclave seems easy enough, just like you said. However, none of the APIs in Security framework are obviously useful for generating a CSR I can sign externally and re-import like this thread originally describes.