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

WDU*_*DUK 31 security keychain password-protection ios

这个问题一直困扰着我,我希望有人能够深入了解这一问题.从本质上讲,我有一小部分用户无法将项目保存/更新到钥匙串.有问题的控制流程如下:

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

  2. 然后我们尝试添加项目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;
}
Run Code Online (Sandbox Code Playgroud)

进行更新/添加的代码如下(对于详细程度而言):

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

我们尝试使用SecItemDelete而不是检查/更新,但这也是errSecItemNotFoundSecItemAdd失败后直接返回.删除代码是:

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

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

只有少数用户进入这种情况的事实确实让我感到困惑.对于可能导致此问题的钥匙串,我是否需要考虑其他因素,包括多线程,刷新,后台访问等等?

非常感谢.我宁愿坚持使用Keychain Services API而不是使用第三方库.我想了解这里的根本问题.

Sim*_*mon 24

唯一的关键kSecClassGenericPassword是由;

kSecAttrAccount
kSecAttrService
Run Code Online (Sandbox Code Playgroud)

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

包括kSecAttrLabelkSecAttrAccessible将排除具有相同的唯一密钥的任何现有项目,但具有不同的属性.

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

  • 谢谢!这确实有帮助.我可以问_how_你知道吗?我向后和向前阅读文档,从未看到任何对此的引用...... (2认同)
  • 事实证明,将 `kSecAttrSynchronizable` 设置为 `true` 也是密钥唯一性的一部分。我的删除/查询必须包含此匹配。 (2认同)