iOS Keychain Services: разрешены только определенные значения для ключа kSecAttrGeneric? - PullRequest
38 голосов
/ 03 февраля 2011

Я пытаюсь использовать класс KeychainWrapper, представленный в этом примере кода Apple: https://developer.apple.com/library/content/samplecode/GenericKeychain/

В примере приложения класс имеет этот метод init, который начинается как:

- (id)initWithIdentifier: (NSString *)identifier accessGroup:(NSString *) accessGroup;
{
    if (self = [super init])
    {
        // Begin Keychain search setup. The genericPasswordQuery leverages the special user
        // defined attribute kSecAttrGeneric to distinguish itself between other generic Keychain
        // items which may be included by the same application.
        genericPasswordQuery = [[NSMutableDictionary alloc] init];

        [genericPasswordQuery setObject:(id)kSecClassGenericPassword forKey:(id)kSecClass];
        [genericPasswordQuery setObject:identifier forKey:(id)kSecAttrGeneric];

В примере приложения оно использует два значения для строки идентификатора. «Пароль» и «Номер счета». При реализации класса в моем коде я использовал несколько пользовательских идентификаторов, и код не работал. Вызов SecItemAdd () не удался. После некоторого тестирования кажется, что использование значений, отличных от «Пароль» и «Номер счета» для идентификатора не работает.

Кто-нибудь знает, какие значения разрешены и / или возможно ли иметь собственные идентификаторы для элементов цепочки для ключей?

Ответы [ 3 ]

61 голосов
/ 15 февраля 2011

Хорошо, я нашел решение в этом сообщении в блоге Двойной элемент цепочки для ключей при добавлении пароля

Подводя итог, можно сказать, что проблема в том, что пример приложения GenericKeychain использует значение, хранящееся в ключе kSecAttrGeneric, в качестве идентификатора для элемента цепочки для ключей, хотя фактически это не то, что API использует для определения уникального элемента цепочки для ключей. Ключами, которые необходимо установить с уникальными значениями, являются ключ kSecAttrAccount и / или ключ kSecAttrService.

Вы можете переписать инициализатор KeychainItemWrapper, так что вам не нужно изменять какой-либо другой код, изменив эти строки:

Изменение:

[genericPasswordQuery setObject:identifier forKey:(id)kSecAttrGeneric];

до:

[genericPasswordQuery setObject:identifier forKey:(id)kSecAttrAccount];

и изменить:

[keychainItemData setObject:identifier forKey:(id)kSecAttrGeneric];

до:

[keychainItemData setObject:identifier forKey:(id)kSecAttrAccount];

Или вы можете сделать то, что я сделал, и написать новый инициализатор, который принимает оба идентифицирующих ключа:

Редактировать: Для людей, использующих ARC (вы должны быть в настоящее время), отметьте nycynik's answer для всех правильных обозначений моста

- (id)initWithAccount:(NSString *)account service:(NSString *)service accessGroup:(NSString *) accessGroup;
{
    if (self = [super init])
    {
        NSAssert(account != nil || service != nil, @"Both account and service are nil.  Must specifiy at least one.");
        // Begin Keychain search setup. The genericPasswordQuery the attributes kSecAttrAccount and 
       // kSecAttrService are used as unique identifiers differentiating keychain items from one another
        genericPasswordQuery = [[NSMutableDictionary alloc] init];

        [genericPasswordQuery setObject:(id)kSecClassGenericPassword forKey:(id)kSecClass];

        [genericPasswordQuery setObject:account forKey:(id)kSecAttrAccount];
        [genericPasswordQuery setObject:service forKey:(id)kSecAttrService];

        // The keychain access group attribute determines if this item can be shared
        // amongst multiple apps whose code signing entitlements contain the same keychain access group.
        if (accessGroup != nil)
        {
#if TARGET_IPHONE_SIMULATOR
            // Ignore the access group if running on the iPhone simulator.
            // 
            // Apps that are built for the simulator aren't signed, so there's no keychain access group
            // for the simulator to check. This means that all apps can see all keychain items when run
            // on the simulator.
            //
            // If a SecItem contains an access group attribute, SecItemAdd and SecItemUpdate on the
            // simulator will return -25243 (errSecNoAccessForItem).
#else            
            [genericPasswordQuery setObject:accessGroup forKey:(id)kSecAttrAccessGroup];
#endif
        }

        // Use the proper search constants, return only the attributes of the first match.
        [genericPasswordQuery setObject:(id)kSecMatchLimitOne forKey:(id)kSecMatchLimit];
        [genericPasswordQuery setObject:(id)kCFBooleanTrue forKey:(id)kSecReturnAttributes];

        NSDictionary *tempQuery = [NSDictionary dictionaryWithDictionary:genericPasswordQuery];

        NSMutableDictionary *outDictionary = nil;

        if (! SecItemCopyMatching((CFDictionaryRef)tempQuery, (CFTypeRef *)&outDictionary) == noErr)
        {
            // Stick these default values into keychain item if nothing found.
            [self resetKeychainItem];

            //Adding the account and service identifiers to the keychain
            [keychainItemData setObject:account forKey:(id)kSecAttrAccount];
            [keychainItemData setObject:service forKey:(id)kSecAttrService];

            if (accessGroup != nil)
            {
#if TARGET_IPHONE_SIMULATOR
                // Ignore the access group if running on the iPhone simulator.
                // 
                // Apps that are built for the simulator aren't signed, so there's no keychain access group
                // for the simulator to check. This means that all apps can see all keychain items when run
                // on the simulator.
                //
                // If a SecItem contains an access group attribute, SecItemAdd and SecItemUpdate on the
                // simulator will return -25243 (errSecNoAccessForItem).
#else            
                [keychainItemData setObject:accessGroup forKey:(id)kSecAttrAccessGroup];
#endif
            }
        }
        else
        {
            // load the saved data from Keychain.
            self.keychainItemData = [self secItemFormatToDictionary:outDictionary];
        }

        [outDictionary release];
    }

    return self;
}

Надеюсь, это поможет кому-то еще!

10 голосов
/ 09 января 2013

То же, что и выше, но оно работает для ARC.Спасибо, Саймон

- (id)initWithAccount:(NSString *)account service:(NSString *)service accessGroup:(NSString *) accessGroup;

{
    if (self = [super init])
    {
        NSAssert(account != nil || service != nil, @"Both account and service are nil.  Must specifiy at least one.");
        // Begin Keychain search setup. The genericPasswordQuery the attributes kSecAttrAccount and
        // kSecAttrService are used as unique identifiers differentiating keychain items from one another
        genericPasswordQuery = [[NSMutableDictionary alloc] init];

        [genericPasswordQuery setObject:(__bridge id)kSecClassGenericPassword forKey:(__bridge id)kSecClass];

        [genericPasswordQuery setObject:account forKey:(__bridge id)kSecAttrAccount];
        [genericPasswordQuery setObject:service forKey:(__bridge id)kSecAttrService];

        // The keychain access group attribute determines if this item can be shared
        // amongst multiple apps whose code signing entitlements contain the same keychain access group.
        if (accessGroup != nil)
        {
#if TARGET_IPHONE_SIMULATOR
            // Ignore the access group if running on the iPhone simulator.
            //
            // Apps that are built for the simulator aren't signed, so there's no keychain access group
            // for the simulator to check. This means that all apps can see all keychain items when run
            // on the simulator.
            //
            // If a SecItem contains an access group attribute, SecItemAdd and SecItemUpdate on the
            // simulator will return -25243 (errSecNoAccessForItem).
#else
            [genericPasswordQuery setObject:accessGroup forKey:(__bridge id)kSecAttrAccessGroup];
#endif
        }

        // Use the proper search constants, return only the attributes of the first match.
        [genericPasswordQuery setObject:(__bridge id)kSecMatchLimitOne forKey:(__bridge id)kSecMatchLimit];
        [genericPasswordQuery setObject:(__bridge id)kCFBooleanTrue forKey:(__bridge id)kSecReturnAttributes];

        NSDictionary *tempQuery = [NSDictionary dictionaryWithDictionary:genericPasswordQuery];

        CFMutableDictionaryRef outDictionary = NULL;

        if (! SecItemCopyMatching((__bridge CFDictionaryRef)tempQuery, (CFTypeRef *)&outDictionary) == noErr)
        {
            // Stick these default values into keychain item if nothing found.
            [self resetKeychainItem];

            //Adding the account and service identifiers to the keychain
            [keychainItemData setObject:account forKey:(__bridge id)kSecAttrAccount];
            [keychainItemData setObject:service forKey:(__bridge id)kSecAttrService];

            if (accessGroup != nil)
            {
#if TARGET_IPHONE_SIMULATOR
                // Ignore the access group if running on the iPhone simulator.
                //
                // Apps that are built for the simulator aren't signed, so there's no keychain access group
                // for the simulator to check. This means that all apps can see all keychain items when run
                // on the simulator.
                //
                // If a SecItem contains an access group attribute, SecItemAdd and SecItemUpdate on the
                // simulator will return -25243 (errSecNoAccessForItem).
#else
                [keychainItemData setObject:accessGroup forKey:(__bridge id)kSecAttrAccessGroup];
#endif
            }
        }
        else
        {
            // load the saved data from Keychain.
            keychainItemData = [self secItemFormatToDictionary:(__bridge NSDictionary *)outDictionary];
        }

        if(outDictionary) CFRelease(outDictionary);
    }

    return self;
}
1 голос
/ 19 декабря 2012

Саймон почти исправил мою проблему, потому что после изменения KeychainItemWrapper.m у меня были проблемы с получением и настройкой данных в и из цепочки для ключей. Поэтому после добавления этого в KeychainItemWrapper.m я использовал это для получения и хранения элементов:

KeychainItemWrapper *keychainItem = [[KeychainItemWrapper alloc] initWithAccount:@"Identfier" service:@"AppName" accessGroup:nil];
[keychainItem setObject:@"some value" forKey:(__bridge id)kSecAttrGeneric];
NSString *value = [keychainItem objectForKey: (__bridge id)kSecAttrGeneric];

Поскольку [keychainItem objectForKey: (__bridge id)kSecAttrService] возвращает учетную запись (в этом примере @"Identifier"), что имеет смысл, но мне потребовалось некоторое время, прежде чем я понял, что мне нужно использовать kSecAttrGeneric для получения данных из оболочки.

...