升级到iOS 13后,钥匙串查询始终返回errSecItemNotFound

Nic*_*ari 22 keychain security-framework ios ios13

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

我围绕Security.framework功能(SecItemCopyMatching(),等等)实现了自己的包装器,直到iOS 12为止,它的运行都像是一种魅力。

现在,我正在测试我的应用程序不会与即将推出的iOS 13兼容,瞧瞧:

SecItemCopyMatching() 总是回来 .errSecItemNotFound

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

我的包装是用静态特性方便地提供的价值类kSecAttrServicekSecAttrAccount组装查询字典时:

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" 

// ...
Run Code Online (Sandbox Code Playgroud)

使用以下代码密码插入钥匙串:

/* 
  - 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?()
}
Run Code Online (Sandbox Code Playgroud)

...然后,我通过以下方式检索密码:

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)
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

(我不认为我用来打任何电话的词典中的任何条目都具有不合适的价值……但也许直到现在我仍遗漏了一些只是为了“获得通过”的东西)

我已经建立了一个可以正常工作的项目(Xcode 11 beta)来说明问题的存储库

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

  • 成功在Xcode中10 - 12的iOS(或更早),但
  • .errSecItemNotFound在Xcode 11-iOS 13上无法使用。

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

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

小智 6

我遇到了类似的问题,我遇到errSecItemNotFound了任何与钥匙串相关的操作,但仅限于模拟器。在真实设备上它是完美的,我已经在不同的模拟器上使用最新的 Xcode(测试版、通用版、稳定版)进行了测试,而那些让我感到困难的是 iOS 13 的。

问题是,我使用kSecClassKey的查询属性kSecClass,但没有“必要”值(见什么类与价值观去这里),用于生成主键:

  • kSecAttrApplicationLabel
  • kSecAttrApplicationTag
  • kSecAttrKeyType
  • kSecAttrKeySizeInBits
  • kSecAttrEffectiveKeySize

什么帮助是挑选kSecClassGenericPasswordkSecClass ,并生成一个主键提供“必要”值:

  • kSecAttrAccount
  • kSecAttrService

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

我来到这个结论通过启动一个新的iOS 13个项目,并复制了这是在我们的应用程序使用的钥匙扣包装,如预期那样没有工作,所以我发现使用钥匙扣这个可爱指南这里,并试图从他们的包装物不惊喜工作,然后逐行比较我的实现和他们的实现。

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

希望这可以帮助。

  • 我认为这里真正的问题是使用 SecAccessControlCreateFlags 进行身份验证策略(例如 userPresence)时 iOS 13 模拟器中的某种错误。我还没有看到任何解决办法,而且我认为不可能修复它。恕我直言,苹果需要修复这个问题。 (3认同)

小智 5

经过半天的实验,我发现使用 kSecClassGenericPassword 的一个非常基本的实例,我在模拟器和真实硬件上都遇到了问题。阅读完文档后,我注意到 kSecAttrSynchronized 有一个 kSecAttrSynchronizedAny。要接受任何其他属性的任何值,您只需不将其包含在查询中即可。这是一个线索。

我发现当我将 kSecAttrSynchronized 设置为 kSecAttrSynchronizedAny 时,所有查询都有效。当然,如果我确实想过滤该值,我也可以将其设置为 kCFBooleanTrue (或 *False)。

考虑到这个属性,一切似乎都按我的预期进行。希望这能节省其他人半天的时间来处理测试代码。