I am new to iOS development, and recently I was trying to build an application, which will create a key inside the secure element, and after - I will sing something with it. While developing I've encountered an issue: the key generation fails if there is a flag .biometryAny or .biometryCurrentSet
The authentication itself is triggered, but the function still throws a mistake.
My setup - Xcode iPhone15 simulator, FaceID enrolled and the animation of it is working.
Ive created the same post on overflow, in case somebody will have the same issues: https://stackoverflow.com/questions/78175858/secure-enclave-key-generation-failure
I've tried deleting the flag, while keeping the manual authorisation, and this approach works, but I still would like have maximum security.
THIS WORKS:
func authenticateUser(completion: @escaping (Bool, Error?) -> Void) {
let context = LAContext()
var error: NSError?
if context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error) {
let reason = "Biometric authentication is needed to access your secure data."
context.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: reason) { success, authenticationError in
DispatchQueue.main.async {
completion(success, authenticationError)
}
}
} else {
// Biometry is not available or not enrolled.
DispatchQueue.main.async {
completion(false, error)
}
}
}
@objc func encryptAction() {
authenticateUser { [weak self] (success, error) in
guard success else {
self?.outputLabel.text = "Authentication failed: \(error?.localizedDescription ?? "Unknown error")"
return
}
guard let randomNumber = self?.inputTextField.text, !randomNumber.isEmpty,
let dataToSign = randomNumber.data(using: .utf8),
let privateKey = self?.generatePrivateKey() else {
self?.outputLabel.text = "Error: Could not generate private key."
return
}
if let signature = self?.signData(privateKey: privateKey, data: dataToSign) {
self?.outputLabel.text = "Signature: \(signature.base64EncodedString())"
} else {
self?.outputLabel.text = "Error: Could not sign data."
}
}
}
func generatePrivateKey() -> SecKey? {
// 1. Create Keys Access Control
guard let accessControl =
SecAccessControlCreateWithFlags(
nil,
kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
[.privateKeyUsage],
nil)
else {
fatalError("cannot set access control")
}
// 2. Create Key Attributes
guard let tag = "com.example.keys.mykey".data(using: .utf8) else {
fatalError("cannot set tag")
}
let attributes: [String: Any] = [
kSecAttrKeyType as String: kSecAttrKeyTypeECSECPrimeRandom,
kSecAttrKeySizeInBits as String: 256,
kSecAttrTokenID as String: kSecAttrTokenIDSecureEnclave,
kSecPrivateKeyAttrs as String: [
kSecAttrIsPermanent as String: true,
kSecAttrApplicationTag as String: tag,
kSecAttrAccessControl as String: accessControl
]
]
// 3. Generate Key Pairs
var error: Unmanaged<CFError>?
guard let privateKey = SecKeyCreateRandomKey(attributes as CFDictionary, &error) else {
if let error = error?.takeRetainedValue() {
print("Error creating a key: \(error)")
}
return nil
}
return privateKey
}
func signData(privateKey: SecKey, data: Data) -> Data? {
let digest = sha256(data: data)
var error: Unmanaged<CFError>?
guard let signature = SecKeyCreateSignature(privateKey,
.ecdsaSignatureMessageX962SHA256,
digest as CFData,
&error) as Data? else {
print(error!.takeRetainedValue() as Error)
return nil
}
return signature
}
}
THIS DOESN'T
guard let accessControl =
SecAccessControlCreateWithFlags(
nil,
kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
[.privateKeyUsage, .biometryCurrentSet],
nil)
else {
info.something file is updated and there is a privacy FaceID field included.
the error is triggered at this part:
var error: Unmanaged<CFError>?
guard let privateKey = SecKeyCreateRandomKey(attributes as CFDictionary, &error) else {
if let error = error?.takeRetainedValue() {
print("Error creating a key: \(error)")
}
return nil
}
The error itself:
Error creating a key: Error Domain=NSOSStatusErrorDomain Code=-25293 "Key generation failed, error -25293" UserInfo={numberOfErrorsDeep=0, NSDescription=Key generation failed, error -25293}
I found an issue. The problem was due to the limitations of the simulator. As soon as I run the app on my mobile phone, everything started to work
PS: The code above will produce double FaceID check :)
That's the correct version in the end:
func generatePrivateKey() -> SecKey? { // 1. Create Keys Access Control guard let accessControl = SecAccessControlCreateWithFlags( nil, kSecAttrAccessibleWhenUnlockedThisDeviceOnly, [.privateKeyUsage, .biometryCurrentSet], nil) else { fatalError("cannot set access control") }