We are having similar challenge. It seems, that something ist missing in the API.
Use Case
Only registered App-Instances are allowed to connect to the API (we use DCAppAttest durcing the registration, out of scope here)
Communication between App and the API is authenticated using mutual TLS
The private Key is held in the Secure Enclave and is not transferable
Certificate ist issued by the Backend API upon the successful registration
Implementation
Keypair is generated in the Secure Enclave:
var error: Unmanaged<CFError>? = nil;
guard let accessControl = SecAccessControlCreateWithFlags(kCFAllocatorDefault, kSecAttrAccessibleWhenUnlockedThisDeviceOnly, [.privateKeyUsage], &error) else {
throw error!.takeRetainedValue() as Error
}
self.privateKey = try SecureEnclave.P256.Signing.PrivateKey.init(
accessControl: accessControl
);
Certificate Signing Request ist created using CryptoKit Certificates:
let privateKeyCertificate = Certificate.PrivateKey(privateKey)
let attributes = CertificateSigningRequest.Attributes()
let csr = try CertificateSigningRequest(version: .v1, subject: subject, privateKey: privateKeyCertificate, attributes: attributes, signatureAlgorithm: .ecdsaWithSHA256)
let csrAsPEM = try csr.serializeAsPEM(discriminator: CertificateSigningRequest.defaultPEMDiscriminator).pemString
CSR is then sent to Backend für validation, if everything checks up server signs the CSR and returns the certificate.
We then store the SecureEnclave.P256.Signing.PrivateKey in the Keychain as described here: Storing CryptoKit Keys in the Keychain using GenericPasswordConvertible.
The Certificate is also stored in the Keychain:
let addQuery: [String: Any] = [
kSecClass as String: kSecClassCertificate,
kSecValueRef as String: cert,
kSecAttrLabel as String: "My Certificate"
]
let status = SecItemAdd(addQuery as CFDictionary, nil)
We are stuck in the last Step: retrieve the SecIdentity from the KeyChain. We get Error -25300, no matter what. Lates query is like this:
let query: [String: Any] = [
kSecClass as String: kSecClassIdentity,
kSecReturnData as String: kCFBooleanTrue,
kSecReturnAttributes as String: kCFBooleanTrue,
kSecReturnRef as String: kCFBooleanTrue,
kSecMatchLimit as String: kSecMatchLimitAll
]
var identityItem: CFTypeRef?
let status = SecItemCopyMatching(query as CFDictionary, &identityItem)