钥匙串:项目报告为 errSecItemNotFound,但在添加时收到 errSecDuplicateItem

Posted

技术标签:

【中文标题】钥匙串:项目报告为 errSecItemNotFound,但在添加时收到 errSecDuplicateItem【英文标题】:Keychain: Item reported as errSecItemNotFound, but receive errSecDuplicateItem on addition 【发布时间】:2014-04-19 16:04:11 【问题描述】:

这个问题困扰了我一段时间,我希望有人能深入了解其原因。本质上,我有 一小部分 的用户无法将项目保存/更新到钥匙串。有问题的控制流程如下:

    我们使用SecItemCopyMatching 检查项目是否存在。这将返回错误代码errSecItemNotFound

    然后我们尝试通过SecItemAdd 添加项目,但这会返回errSecDuplicateItem

因此,我们有一些用户根本无法更新钥匙串项目的子集,要求他们恢复设备以清除钥匙串。这显然是一个不可接受的解决方法。它以前似乎对他们有用,但现在进入了这个不可更新的循环。

经过研究,我发现SecItemCopyMatching 中使用的搜索查询不够具体,但我的代码尽可能使用通用搜索查询。

+ (NSMutableDictionary*)queryForUser:(NSString*)user key:(NSString*)key

    if (!key || !user)  return nil; 

    NSString* bundleId = [[NSBundle mainBundle] bundleIdentifier];
    NSString* prefixedKey = [NSString stringWithFormat:@"%@.%@", bundleId, key];

    NSMutableDictionary* query = [NSMutableDictionary dictionary];
    [query addEntriesFromDictionary:@(__bridge id)kSecClass          : (__bridge id)kSecClassGenericPassword];
    [query addEntriesFromDictionary:@(__bridge id)kSecAttrAccount    : user];
    [query addEntriesFromDictionary:@(__bridge id)kSecAttrService    : prefixedKey];
    [query addEntriesFromDictionary:@(__bridge id)kSecAttrLabel      : prefixedKey];
    [query addEntriesFromDictionary:@(__bridge id)kSecAttrAccessible : (__bridge id)kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly];

    return query;

进行更新/添加的代码如下(抱歉冗长):

// Setup the search query, to return the *attributes* of the found item (for use in SecItemUpdate)
NSMutableDictionary* query = [self queryForUser:username key:key];
[query addEntriesFromDictionary:@(__bridge id)kSecReturnAttributes : (__bridge id)kCFBooleanTrue];

// Prep the dictionary we'll use to update/add the new value
NSDictionary* updateValues = @(__bridge id) kSecValueData : [value dataUsingEncoding:NSUTF8StringEncoding];

// Copy what we (may) already have
CFDictionaryRef resultData = NULL;
OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, (CFTypeRef*)&resultData);

// If it already exists, update it
if (status == noErr) 
    // Create a new query with the found attributes
    NSMutableDictionary* updateQuery = [NSMutableDictionary dictionaryWithDictionary:(__bridge NSDictionary*)resultData];
    [updateQuery addEntriesFromDictionary:@(__bridge id)kSecClass : (__bridge id)kSecClassGenericPassword];

    // Update the item in the keychain
    status = SecItemUpdate((__bridge CFDictionaryRef)updateQuery, (__bridge CFDictionaryRef)updateValues);

    if (status != noErr) 
        // Update failed, I've not seen this case occur as of yet
    

else 
    // Add the value we want as part of our original search query, and add it to the keychain
    [query addEntriesFromDictionary:updateValues];
    [query removeObjectForKey:(__bridge id)kSecReturnAttributes];
    status = SecItemAdd((__bridge CFDictionaryRef)query, NULL);

    if (status != noErr) 
        // Addition failed, this is where I'm seeing errSecDuplicateItem
    

我们尝试使用SecItemDelete 而不是检查/更新,但这也返回了errSecItemNotFound,而SecItemAdd 随即失败。删除代码为:

+ (BOOL)deleteItemForUser:(NSString *)username withKey:(NSString *)itemKey 
    if (!username || !itemKey)  return NO; 

    NSString * bundleId = [[NSBundle mainBundle] bundleIdentifier];
    NSString * prefixedItemKey = [NSString stringWithFormat:@"%@.%@", bundleId, itemKey];

    NSDictionary *query = [NSDictionary dictionaryWithObjectsAndKeys: (__bridge id)kSecClassGenericPassword, kSecClass,
                           username, kSecAttrAccount,
                           prefixedItemKey, kSecAttrService, nil];

    OSStatus status = SecItemDelete((__bridge CFDictionaryRef) query);

    if (status != noErr) 
        // Failed deletion, returning errSecItemNotFound
    

    return (status == noErr);

虽然我们为应用程序定义了 2 个钥匙串访问组,但受影响的钥匙串项没有分配为属性的访问组(根据文档,这意味着将对所有访问组进行搜索)。除了errSecItemNotFounderrSecDuplicateItem 之外,我还没有看到任何其他错误代码。

只有一小部分用户会遇到这种情况,这让我很困惑。关于可能导致此问题的钥匙串,关于多线程、刷新、后台访问等,我是否需要考虑其他任何因素?

帮助非常感谢。我宁愿坚持使用 Keychain Services API 而不是使用 3rd 方库。我想了解这里的根本问题。

【问题讨论】:

我在您的代码中没有看到任何可能导致该行为的错误。您过去是否更新了该实施?用户可能有一些旧条目从未被删除,即使他们更新了您的应用。 我不这么认为。即使那样,为什么搜索查询会说未找到,但具有相同搜索查询的添加失败。哪里会与旧条目发生冲突? 嗯,我不知道,我在更改访问组时遇到了这个问题,但问题可能出在其他地方。你知道如何重现这个错误吗?您没有设置像 kSecMatchLimitOne 这样的 kSecMatch 项,可能会导致意外行为。苹果表示请求(通常?,应该?)有一个搜索键值对。 developer.apple.com/library/mac/documentation/security/… 很遗憾,我们无法重现这一点。至于 match 属性,“默认情况下,此函数仅返回找到的第一个匹配项。要一次获取多个匹配项,请指定大于 1 的值的搜索键 kSecMatchLimit”让我相信它不是必需的。 【参考方案1】:

kSecClassGenericPassword 的唯一键由:

kSecAttrAccount
kSecAttrService

要检查其是否存在,请仅使用这些属性(包括kSecReturnAttributes 标志)查询钥匙串存储。

包含kSecAttrLabelkSecAttrAccessible 将排除具有相同唯一键但具有不同属性的任何现有项目。

确认其(不)存在后,添加其他属性并添加或更新。

【讨论】:

谢谢!这真的很有帮助。我能问一下你怎么知道的吗?我来回阅读文档,从未看到任何对此的引用... @Shad 我也有这个问题。他们的键和值文档有时有点难以遵循。在属性和键部分下,有关于哪些属性可用于哪些类型的钥匙串项的参考。 Items of class kSecClassGenericPassword have this attribute 我可以包含kSecMatchLimit(设置为1)或kSecAttrAccessGroup(与我的其他应用共享)吗? 原来kSecAttrSynchronizable 设置为true 也是密钥唯一性的一部分。我的删除/查询必须包含此内容才能匹配。 我也遇到过这个问题,我会试试@simon 的解决方案。但是,我仍然不确定为什么这会发生在一部分用户身上,而不是所有用户身上。

以上是关于钥匙串:项目报告为 errSecItemNotFound,但在添加时收到 errSecDuplicateItem的主要内容,如果未能解决你的问题,请参考以下文章

使用 Swift 向 iOS 钥匙串添加项目和查询

是否可以更新钥匙串项目的 kSecAttrAccessible 值?

钥匙串项目的默认 kSecAttrAccessible 值?

未设置访问组时共享的钥匙串项目

iCloud 钥匙串在更新时通知

解锁 Osx 本地项目钥匙串