Я храню пароли в связке ключей iOS, а затем извлекаю их для реализации функции "запомнить меня" (автоматический вход в систему) в моем приложении.
Я реализовал свою собственную оболочку для функций Security.framework
(SecItemCopyMatching()
и т.д.), И до iOS 12 она работала как чудо.
Сейчас я проверяю, что мое приложение не ломается с готовящейся к выпуску iOS 13, и вот:
SecItemCopyMatching()
всегда возвращает .errSecItemNotFound
... хотя ранее я уже хранил данные, которые запрашиваю.
Моя оболочка - это класс со статическими свойствами для удобного предоставления значений kSecAttrService
и kSecAttrAccount
при сборке словарей запросов:
class LocalCredentialStore {
private static let serviceName: String = {
guard let name = Bundle.main.object(forInfoDictionaryKey: "CFBundleName") as? String else {
return "Unknown App"
}
return name
}()
private static let accountName = "Login Password"
// ...
Я вставляю пароль в связку ключей с помощью следующего кода:
/*
- NOTE: protectWithPasscode is currently always FALSE, so the password
can later be retrieved programmatically, i.e. without user interaction.
*/
static func storePassword(_ password: String, protectWithPasscode: Bool, completion: (() -> Void)? = nil, failure: ((Error) -> Void)? = nil) {
// Encode payload:
guard let dataToStore = password.data(using: .utf8) else {
failure?(NSError(localizedDescription: ""))
return
}
// DELETE any previous entry:
self.deleteStoredPassword()
// INSERT new value:
let protection: CFTypeRef = protectWithPasscode ? kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly : kSecAttrAccessibleWhenUnlocked
let flags: SecAccessControlCreateFlags = protectWithPasscode ? .userPresence : []
guard let accessControl = SecAccessControlCreateWithFlags(
kCFAllocatorDefault,
protection,
flags,
nil) else {
failure?(NSError(localizedDescription: ""))
return
}
let insertQuery: NSDictionary = [
kSecClass: kSecClassGenericPassword,
kSecAttrAccessControl: accessControl,
kSecValueData: dataToStore,
kSecUseAuthenticationUI: kSecUseAuthenticationUIAllow,
kSecAttrService: serviceName, // These two values identify the entry;
kSecAttrAccount: accountName // together they become the primary key in the Database.
]
let resultCode = SecItemAdd(insertQuery as CFDictionary, nil)
guard resultCode == errSecSuccess else {
failure?(NSError(localizedDescription: ""))
return
}
completion?()
}
... и позже я получаю пароль с помощью:
static func loadPassword(completion: @escaping ((String?) -> Void)) {
// [1] Perform search on background thread:
DispatchQueue.global().async {
let selectQuery: NSDictionary = [
kSecClass: kSecClassGenericPassword,
kSecAttrService: serviceName,
kSecAttrAccount: accountName,
kSecReturnData: true,
kSecUseOperationPrompt: "Please authenticate"
]
var extractedData: CFTypeRef?
let result = SecItemCopyMatching(selectQuery, &extractedData)
// [2] Rendez-vous with the caller on the main thread:
DispatchQueue.main.async {
switch result {
case errSecSuccess:
guard let data = extractedData as? Data, let password = String(data: data, encoding: .utf8) else {
return completion(nil)
}
completion(password) // < SUCCESS
case errSecUserCanceled:
completion(nil)
case errSecAuthFailed:
completion(nil)
case errSecItemNotFound:
completion(nil)
default:
completion(nil)
}
}
}
}
(я не думаю, что какие-либо записи в словарях, которые я использую для обоих вызовов, имеют неуместное значение... но, возможно, я упускаю что-то, что только что "получило пропуск" до сих пор)
Я создал репозиторий с работающим проектом (бета-версия Xcode 11), который демонстрирует проблему.
Хранение пароля всегда успешно; Загрузка пароля:
- Успешно в Xcode 10 - iOS 12 (и более ранних версиях), но
- Сбой с
.errSecItemNotFound
на Xcode 11 - iOS 13.
ОБНОВЛЕНИЕ: Я не могу воспроизвести проблему на устройстве, только симулятор. На устройстве сохраненный пароль успешно восстановлен. Возможно, это ошибка или ограничение в iOS 13 Simulator и/или iOS 13 SDK для платформы x86.
ОБНОВЛЕНИЕ 2: Если кто-то придумает альтернативный подход, который каким-то образом решит проблему (будь то по замыслу или с помощью некоторого контроля со стороны Apple), я приму его в качестве ответа.