Ответ 1
Всплывающее окно Touch ID появляется только в том случае, если вы вызываете SecItemCopyMatching()
в фоновом режиме.
Это указано на стр. 118 PDF-презентации
Брелок и аутентификация с сенсорным идентификатором:
Чтение секретов
...dispatch_async(dispatch_get_global_queue(...), ^(void){ CFTypeRef dataTypeRef = NULL; OSStatus status = SecItemCopyMatching((CFDictionaryRef)query, &dataTypeRef); });
В противном случае вы блокируете основной поток, и всплывающее окно не
появляются. SecItemCopyMatching()
, затем сбой (после таймаута) с
код ошибки -25293 = errSecAuthFailed
.
Сбой не сразу проявляется в вашем примере проекта, потому что он печатает неверную переменную в случае ошибки, например
if(status != noErr)
{
print("SELECT Error: \(resultCode)."); // <-- Should be `status`
}
и аналогичным образом для обновления и удаления.
Вот сводная версия вашего образца кода с необходимым отправьте в фоновый режим для извлечения элемента keychain. (Конечно, обновления пользовательского интерфейса должны быть отправлены обратно в основную очередь.)
Он работал, как ожидалось, в моем тесте на iPhone с Touch ID: Появится всплывающее окно Touch ID, и элемент keychain будет восстановлен только после успешная аутентификация.
Идентификация сенсорного идентификатора не работает на симуляторе iOS.
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
// This two values identify the entry, together they become the
// primary key in the database
let myAttrService = "app_name"
let myAttrAccount = "first_name"
// DELETE keychain item (if present from previous run)
let delete_query: NSDictionary = [
kSecClass: kSecClassGenericPassword,
kSecAttrService: myAttrService,
kSecAttrAccount: myAttrAccount,
kSecReturnData: false
]
let delete_status = SecItemDelete(delete_query)
if delete_status == errSecSuccess {
print("Deleted successfully.")
} else if delete_status == errSecItemNotFound {
print("Nothing to delete.")
} else {
print("DELETE Error: \(delete_status).")
}
// INSERT keychain item
let valueData = "The Top Secret Message V1".data(using: .utf8)!
let sacObject =
SecAccessControlCreateWithFlags(kCFAllocatorDefault,
kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
.userPresence,
nil)!
let insert_query: NSDictionary = [
kSecClass: kSecClassGenericPassword,
kSecAttrAccessControl: sacObject,
kSecValueData: valueData,
kSecUseAuthenticationUI: kSecUseAuthenticationUIAllow,
kSecAttrService: myAttrService,
kSecAttrAccount: myAttrAccount
]
let insert_status = SecItemAdd(insert_query as CFDictionary, nil)
if insert_status == errSecSuccess {
print("Inserted successfully.")
} else {
print("INSERT Error: \(insert_status).")
}
DispatchQueue.global().async {
// RETRIEVE keychain item
let select_query: NSDictionary = [
kSecClass: kSecClassGenericPassword,
kSecAttrService: myAttrService,
kSecAttrAccount: myAttrAccount,
kSecReturnData: true,
kSecUseOperationPrompt: "Authenticate to access secret message"
]
var extractedData: CFTypeRef?
let select_status = SecItemCopyMatching(select_query, &extractedData)
if select_status == errSecSuccess {
if let retrievedData = extractedData as? Data,
let secretMessage = String(data: retrievedData, encoding: .utf8) {
print("Secret message: \(secretMessage)")
// UI updates must be dispatched back to the main thread.
DispatchQueue.main.async {
self.messageLabel.text = secretMessage
}
} else {
print("Invalid data")
}
} else if select_status == errSecUserCanceled {
print("User canceled the operation.")
} else {
print("SELECT Error: \(select_status).")
}
}
}