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

时间:2014-03-14 11:28:09

标签: ios security password-protection keychain

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

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

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

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

    经过研究,我发现有关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而不是使用第三方库。我想了解这里的根本问题。

1 个答案:

答案 0 :(得分:23)

kSecClassGenericPassword的唯一键由;

组成
kSecAttrAccount
kSecAttrService

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

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

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