Search code examples
iosobjective-cencryptionkeychain

iOS: Decryption not supported on key stored in secure enclave


Compiling for iOS 11 (which allegedly solves all the secure enclave related bugs), I'm trying to create a key pair stored in the secure enclave to use for encrypting/decrypting data, but somewhere along the line the key gets corrupted:

CFErrorRef error = NULL;
NSError *gen_error = nil;

SecAccessControlRef sacObject = SecAccessControlCreateWithFlags(kCFAllocatorDefault, kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly, kSecAccessControlBiometryAny, &error);

if (sacObject == NULL || error != NULL) { /* handled */ }

NSDictionary *keyAttributes = @{
    (id)kSecClass: (id)kSecClassKey,
    (id)kSecAttrKeyType: (id)kSecAttrKeyTypeEC,
    (id)kSecAttrKeySizeInBits: @256,
    (id)kSecAttrTokenID: (id)kSecAttrTokenIDSecureEnclave,
    (id)kSecAttrIsPermanent: @YES,
    (id)kSecAttrApplicationTag: biometricKeyTag, //static between all calls
    (id)kSecAttrAccessControl: (__bridge_transfer id)sacObject,
};

SecKeyRef privateKey = (__bridge SecKeyRef) CFBridgingRelease(SecKeyCreateRandomKey((__bridge CFDictionaryRef)keyAttributes, (void *)&gen_error));

if (gen_error != nil || privateKey == nil) { /* handled */ }

SecKeyRef publicKey = SecKeyCopyPublicKey(privateKey);

Boolean algorithmDSupported = SecKeyIsAlgorithmSupported(privateKey, kSecKeyOperationTypeDecrypt, kSecKeyAlgorithmECIESEncryptionCofactorX963SHA256AESGCM);
Boolean algorithmESupported = SecKeyIsAlgorithmSupported(publicKey, kSecKeyOperationTypeEncrypt, kSecKeyAlgorithmECIESEncryptionCofactorX963SHA256AESGCM);
// Returns true for both

// OK so far!

// Now retrieve the key just create to mock up using it later

NSDictionary *query = @{
    (id)kSecClass: (id)kSecClassKey,
    (id)kSecAttrApplicationTag: biometricKeyTag,
    (id)kSecAttrKeyType: (id)kSecAttrKeyTypeEC,
    (id)kSecReturnRef: @YES,
    (id)kSecUseOperationPrompt: @""
};
OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, (CFTypeRef *)&privateKey);

if (status != noErr || privateKey == NULL) { /* handled */ }

publicKey = SecKeyCopyPublicKey(privateKey);
Boolean algorithmDSupported2 = SecKeyIsAlgorithmSupported(privateKey, kSecKeyOperationTypeDecrypt, kSecKeyAlgorithmECIESEncryptionCofactorX963SHA256AESGCM);
Boolean algorithmESupported2 = SecKeyIsAlgorithmSupported(publicKey, kSecKeyOperationTypeEncrypt, kSecKeyAlgorithmECIESEncryptionCofactorX963SHA256AESGCM);
// Encryption on the public key is supported, but algorithmDSupported2 is false – cannot decrypt with the key any more

Using the key retrieved with SecItemCopyMatching for encryption with SecKeyCreateEncryptedData seems to work, but SecKeyIsAlgorithmSupported on …TypeDecrypt returns false for these keys, and attempts to decrypt anyway unsurprisingly crashes the app.

How/why does the key not work after retrieval?


Solution

  • What solved it in the end was

    • Changing the SecAccessControl flags to kSecAccessControlBiometryAny|kSecAccessControlPrivateKeyUsage
    • Changing key type to kSecAttrKeyTypeECSECPrimeRandom
    • Moving the key attributes under the kSecPrivateKeyAttrs key

    No idea why Apple lets you shoot yourself in the foot and doesn't properly validate key generation parameters.