升级到iOS 13后,钥匙串查询始终返回errSecItemNotFound [英] Keychain Query Always Returns errSecItemNotFound After Upgrading to iOS 13

查看:122
本文介绍了升级到iOS 13后,钥匙串查询始终返回errSecItemNotFound的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我将密码存储到iOS钥匙串中,然后检索它们以在我的应用程序中实现记住我"(自动登录)功能.

I am storing passwords into the iOS keychain and later retrieving them to implement a "remember me" (auto-login) feature on my app.

我围绕Security.framework函数(SecItemCopyMatching()等)实现了自己的包装器,直到iOS 12为止,它的运行一直很吸引人.

I implemented my own wrapper around the Security.framework functions (SecItemCopyMatching(), etc.), and it was working like a charm up until iOS 12.

现在,我正在测试我的应用不会随着即将推出的iOS 13中断,瞧瞧:

Now I am testing that my app doesn't break with the upcoming iOS 13, and lo and behold:

SecItemCopyMatching()始终返回.errSecItemNotFound

SecItemCopyMatching() always returns .errSecItemNotFound

...即使我以前已经存储了要查询的数据.

...even though I have previously stored the data I am querying.

我的包装器是一个具有静态属性的类,可在组装查询字典时方便地提供kSecAttrServicekSecAttrAccount的值:

My wrapper is a class with static properties to conveniently provide the values of the kSecAttrService and kSecAttrAccount when assembling the query dictionaries:

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" 

// ...

我正在使用以下代码将密码插入到钥匙串中:

I am inserting the password into the keychain with code like the following:

/* 
  - 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?()
}

...然后,我要使用以下方式检索密码:

...and later, I am retrieving the password with:

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 beta)演示问题.

I have set up a repository with a working project (Xcode 11 beta) that demonstrates the problem.

密码存储总是成功;密码加载:

The password storing always succeeds; The password loading:

    在Xcode 10-iOS 12(及更低版本)上,
  • 成功,但
  • 失败,在Xcode 11-iOS 13上出现.errSecItemNotFound失败.
  • Succeeds on Xcode 10 - iOS 12 (and earlier), but
  • Fails with .errSecItemNotFound on Xcode 11 - iOS 13.

更新:我无法在设备上重现该问题,只能在Simulator上重现.在设备上,成功检索到存储的密码. 也许这是针对x86平台的iOS 13 Simulator和/或iOS 13 SDK上的错误或限制.

UPDATE: I can not reproduce the issue on the device, only Simulator. On the device, the stored password is retrieved successfully. Perhaps this is a bug or limitation on the iOS 13 Simulator and/or iOS 13 SDK for the x86 platform.

更新2:如果有人想出了一种解决该问题的替代方法(无论是通过设计还是通过Apple的监督),我将接受它作为答案.

UPDATE 2: If someone comes up with an alternative approach that somehow works around the issue (whether by design or by taking advantage of some oversight by Apple), I will accept it as an answer.

推荐答案

我遇到了类似的问题,我在模拟器上通过任何与钥匙串相关的操作都得到了errSecItemNotFound,但.在真实的设备上,它是完美的,我已经在不同的模拟器上测试了最新的Xcode(beta,GM,稳定版),而给我带来麻烦的是iOS 13.

I've had a similar issue where I was getting errSecItemNotFound with any Keychain-related action but only on a simulator. On real device it was perfect, I've tested with latest Xcodes (beta, GM, stable) on different simulators and the ones that were giving me a hard time were iOS 13 ones.

问题是我在查询属性kSecClass中使用了kSecClassKey,但是没有'required'值(请参阅哪些类与哪些值

The problem was that I was using kSecClassKey in query attribute kSecClass, but without the 'required' values (see what classes go with which values here) for generating a primary key:

  • kSecAttrApplicationLabel
  • kSecAttrApplicationTag
  • kSecAttrKeyType
  • kSecAttrKeySizeInBits
  • kSecAttrEffectiveKeySize
  • kSecAttrApplicationLabel
  • kSecAttrApplicationTag
  • kSecAttrKeyType
  • kSecAttrKeySizeInBits
  • kSecAttrEffectiveKeySize

有用的是为kSecClass 选择kSecClassGenericPassword并提供了生成主键的必需"值:

And what helped was to pick kSecClassGenericPassword for kSecClass and provide the 'required' values for generating a primary key:

  • kSecAttrAccount
  • kSecAttrService
  • kSecAttrAccount
  • kSecAttrService

请参阅此处有关kSecClass类型以及有关内容的更多信息其他属性应该与它们一起使用.

See here on more about kSecClass types and what other attributes should go with them.

通过得出一个结论,我开始了一个新的iOS 13项目,并复制了应用程序中使用的钥匙串包装程序,但效果并不理想,因此我找到了有关使用钥匙串的可爱指南

I came to this conclusion by starting a new iOS 13 project and copying over the Keychain wrapper that was used in our app, as expected that did not work so I've found this lovely guide on using keychain here and tried out their wrapper which no surprise worked, and then went line by line comparing my implementation with theirs.

此问题已在雷达中报告: http://openradar.appspot.com/7251207

This issue already reported in radar: http://openradar.appspot.com/7251207

希望这会有所帮助.

这篇关于升级到iOS 13后,钥匙串查询始终返回errSecItemNotFound的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆