Эта проблема беспокоила меня некоторое время, и я надеюсь, что кто-то знает причину этого. По сути, у меня есть небольшой процент пользователей, которые не могут сохранять/обновлять элементы в связке ключей. Проблемный поток управления выглядит следующим образом:
Проверяем наличие элемента с помощью
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 группы доступа к цепочке для ключей для приложения, затронутые элементы цепочки для ключей не имеют группы доступа, назначенной в качестве атрибута (что, согласно документации, означает, что поиск будет выполняться для всех групп доступа). Я еще не видел никаких других кодов ошибок, кроме errSecItemNotFound
и errSecDuplicateItem
.
Меня действительно смущает тот факт, что в такое состояние попадает лишь небольшой набор пользователей. Есть ли какие-либо другие соображения, которые мне нужно принять во внимание в отношении цепочки для ключей, которая может быть причиной этого, в отношении многопоточности, очистки, фонового доступа и т. д.?
Помощь очень ценится. Я бы предпочел использовать API Keychain Services вместо использования сторонней библиотеки. Я хотел бы понять основную проблему здесь.
Items of class kSecClassGenericPassword have this attribute
19.05.2014kSecMatchLimit
(установить на 1) илиkSecAttrAccessGroup
(чтобы поделиться с другими моими приложениями)? 28.07.2015kSecAttrSynchronizable
, установленный вtrue
, является частью уникальности ключа. Мои запросы на удаление/запросы должны были включать это, чтобы соответствовать. 15.04.2017errSecDuplicateItem
, в которой перечислены первичные ключи для каждого класса: developer.apple.com/documentation/security/errsecduplicateitem 28.04.2020kSecAttrGeneric
(везде, где он появляется) наkSecAttrAccount
28.04.2020