Я сохраняю пароли для цепочки ключей 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)
}
}
}
}
(Я не думаю, что какие-либо записи словарей, которые я использую для обоих вызовов, имеют неуместное значение ... но, возможно, я упускаю что-то, что только что произошло, чтобы "получить пропуск"до сих пор)
Я установил репозиторий с работающим профессионаломject (Xcode 11 beta), который демонстрирует проблему.
Сохранение пароля всегда успешно;Загрузка пароля:
- Успешно на Xcode 10 - iOS 12 (и более ранних версиях), но
- Сбой при
.errSecItemNotFound
наXcode 11 - iOS 13.
ОБНОВЛЕНИЕ: Не могу воспроизвести проблему на устройстве, только симулятор.На устройстве сохраненный пароль успешно восстановлен.Возможно, это ошибка или ограничение на iOS 13 Simulator и iOS 13 SDK для платформы x86.