この問題はしばらくの間私を悩ませてきました、そして私は誰かがこれの原因について洞察を持っていることを望みます。基本的に、キーチェーンにアイテムを保存/更新できないユーザーが小さな割合います。問題のある制御フローは次のとおりです。
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
以外のエラーコードはまだ見ていません。
少数のユーザーだけがこの状態に陥るという事実は、私を本当に混乱させます。マルチスレッド、フラッシュ、バックグラウンドアクセスなど、これを引き起こしている可能性のあるキーチェーンに関して考慮する必要がある他の考慮事項はありますか?
感謝します。サードパーティのライブラリを使用する代わりに、Keychain ServicesAPIを使用することに固執したいと思います。ここで根本的な問題を理解したいと思います。
kSecClassGenericPassword
の一意のキーは次のもので構成されます。
kSecAttrAccount
kSecAttrService
その存在を確認するには、これらの属性(kSecReturnAttributes
フラグを含む)のみを使用してキーチェーンストアにクエリを実行します。
kSecAttrLabel
とkSecAttrAccessible
を含めると、同じ一意のキーを持つが属性が異なる既存のアイテムは除外されます。
その(存在しない)ことを確認したら、追加の属性を追加し、追加または更新します。