Hi
I have created a private key in the Keychain with kSecAttrAccessibleWhenUnlockedThisDeviceOnly. When I attempt to access the key to perform a signing operation, the Touch ID dialog will sometimes appear, but in most cases I have to touch the biometry sensor, then the dialog is displayed. Here's the code I'm using to create the key and access the key in a sign operation.
public static func create(with name: String, authenticationRequired: SecAccessControlCreateFlags? = nil) -> Bool
{
guard !name.isEmpty else
{
return false
}
var error: Unmanaged<CFError>?
// Private key parameters
var privateKeyParams: [String: Any] = [
kSecAttrIsPermanent as String: true,
kSecAttrApplicationTag as String: name
]
// If we are using a biometric sensor to access the key, we need to create an SecAccessControl instance.
if authenticationRequired != nil
{
guard let access = SecAccessControlCreateWithFlags(kCFAllocatorDefault, kSecAttrAccessibleWhenUnlockedThisDeviceOnly, authenticationRequired!, &error) else
{
return false
}
privateKeyParams[kSecAttrAccessControl as String] = access
}
// Global parameters for our key generation
let parameters: [String: Any] = [
kSecAttrKeyType as String: kSecAttrKeyTypeRSA,
kSecAttrKeySizeInBits as String: 2048,
kSecPrivateKeyAttrs as String: privateKeyParams
]
// Generate the keys.
guard let privateKey = SecKeyCreateRandomKey(parameters as CFDictionary, &error) else
{
return false
}
// Private key created!
return true
}
This is the code to sign the data that should prompt for the biometry sensor (Touch ID or Face ID).
public static func sign(using name: String, value: String, localizedReason: String? = nil, base64EncodingOptions: Data.Base64EncodingOptions = []) -> String?
{
guard !name.isEmpty else
{
return nil
}
guard !value.isEmpty else
{
return nil
}
// Check if the private key exists in the chain, otherwise return
guard let privateKey: SecKey = getPrivateKey(name, localizedReason: localizedReason ?? "") else
{
return nil
}
let data = value.data(using: .utf8)!
var error: Unmanaged<CFError>?
guard let signedData = SecKeyCreateSignature(privateKey,
rsaSignatureMessagePKCS1v15SHA512,
data as CFData,
&error) as Data? else
{
return nil
}
return signedData.base64EncodedString(options: base64EncodingOptions)
}
fileprivate static func getPrivateKey(_ name: String, localizedReason: String) -> SecKey?
{
let query: [String: Any] = [
kSecClass as String: kSecClassKey,
kSecAttrKeyType as String: kSecAttrKeyTypeRSA,
kSecAttrApplicationTag as String: name,
kSecReturnRef as String: true,
kSecUseOperationPrompt as String : localizedReason
]
var item: CFTypeRef? = nil
let status = SecItemCopyMatching(query as CFDictionary, &item)
guard status == errSecSuccess else
{
if status == errSecUserCanceled
{
print("\tError: Accessing private key failed: The user cancelled (%@).", "\(status)")
}
else if status == errSecDuplicateItem
{
print("\tError: The specified item already exists in the keychain (%@).", "\(status)")
}
else if status == errSecItemNotFound
{
print("\tError: The specified item could not be found in the keychain (%@).", "\(status)")
}
else if status == errSecInvalidItemRef
{
print("\tError: The specified item is no longer valid. It may have been deleted from the keychain (%@).", "\(status)")
}
else
{
print("\tError: Accessing private key failed (%@).", "\(status)")
}
return nil
}
return (item as! SecKey)
}
Then in my app, I would simply call
guard let result = sign("mykey", "helloworld") else
{
print("failed to sign")
return
}
print(result)
So the getPrivateKey function is the one that calls SecKeyCopyingMatching, the 3 methods are in a helper class; what's the best approach to reliabably display the biometry dialog?
Thanks