Подтвердить что ты не робот

Брелок для ключей: элемент, указанный как errSecItemNotFound, но получает errSecDuplicateItem при добавлении

Эта проблема давно искала меня, и я надеюсь, что у кого-то есть представление о причине этого. По сути, у меня есть небольшой процент пользователей, которые не могут сохранить/обновить элементы в цепочке ключей. Проблемный поток управления выглядит следующим образом:

  • Мы проверяем наличие элемента с помощью SecItemCopyMatching. Это возвращает код ошибки errSecItemNotFound

  • Затем мы пытаемся добавить элемент через SecItemAdd, но это возвращает errSecDuplicateItem.

Из-за этого у нас есть некоторые пользователи, которые вообще не могут обновить подмножество элементов keychain, требуя, чтобы они восстановили свое устройство, чтобы очистить цепочку ключей. Это, очевидно, неприемлемое обходное решение. Раньше они работали для них, но теперь вошли в этот не обновляемый цикл.

После исследования я видел проблемы, связанные с поисковым запросом, используемым в 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 группы доступа к цепочке ключей для приложения, затронутые элементы keychain не имеют группы доступа, назначенной как атрибут (который по документации означает, что поиск будет выполнен для всех групп доступа). Я еще не вижу другого кода ошибки, кроме errSecItemNotFound и errSecDuplicateItem.

Тот факт, что только небольшой набор пользователей попадает в это условие, действительно меня смущает. Есть ли какие-либо другие соображения, которые мне нужно учитывать в отношении связки ключей, которые могут быть причиной этого, в отношении многопоточности, очистки, доступа к фоновому эффекту и т.д.??

Помогите оценить. Я предпочел бы использовать API-интерфейс Keychain Services вместо использования сторонней библиотеки. Я хотел бы понять основную проблему здесь.

4b9b3361

Ответ 1

Уникальный ключ для kSecClassGenericPassword состоит из:

kSecAttrAccount
kSecAttrService

Чтобы проверить его существование, запросите хранилище keychain только с этими атрибутами (включая флаг kSecReturnAttributes).

Включение kSecAttrLabel и kSecAttrAccessible исключает любой существующий элемент с тем же уникальным ключом, но с разными атрибутами.

Как только вы подтвердите свое (не) существование, добавьте дополнительные атрибуты и добавьте или обновите.