Secure Enclave, key generation failure

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}

Accepted Reply

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") }

Replies

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") }

I’m glad you got it working.

However, there’s a misconception I want to correct here. You wrote:

which will create a key inside the secure element

Keys aren’t inside the Secure Enclave [1]. Rather, keys are protected by the SE. This is a common misconception and it leads folks to all sorts of erroneous conclusions [2]. A while back we rewrote Protecting keys with the Secure Enclave to make that clear.

Share and Enjoy

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

[1] Note that the Secure Enclave and the Secure Element are conceptually different things.

[2] I was confused by this myself. At one point I asked the Security framework engineers “What happens if the SE runs out of space for keys?” and they patiently explained this fact to me (-:

Thank you for the clarification Quinn!

While my program is running perfectly fine now, I still have questions regarding its security.

QUESTIONS:

  1. Is there any functionality just with the public key to get an evidence of a corresponding private key to be protected by the Secure Enclave without showing the source code?

  2. Even with the most recent update of iOS 17.4, there is still no way to directly access the functionality of a Secure Element itself, is that right? So far I found a function SecureElementPass, and it seems like it’s the only interaction possible.

  3. What is the difference between using Security API and Apple CryptoKit? I heard some were saying it the matter of habit and device support, but I still would like to hear an opinion of a professional.

Any information regarding that will be helpful. Thank you in advance for your time and effort!

Add a Comment