Получить имена пользователей, хранящиеся в связке ключей, используя только имя_службы? ИЛИ: Где вы должны хранить имя пользователя? - PullRequest
4 голосов
/ 10 декабря 2011

Итак, OS X Keychain содержит три фрагмента информации:

  • ServiceName (имя моего приложения)
  • Имя пользователя
  • Пароль

Я, очевидно, всегда знаю ServiceName.Есть ли способ найти любое сохраненное имя пользователя для этого ServiceName?(Найти пароль легко, если вы знаете имя пользователя.)

Я бы предпочел использовать для этого красивую оболочку Cocoa, например EMKeychain .Но EMKeychain требует UserName, чтобы получить любой элемент цепочки для ключей!

+ (EMGenericKeychainItem *)genericKeychainItemForService:(NSString *)serviceNameString withUsername:(NSString *)usernameString;

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

Ответы [ 3 ]

6 голосов
/ 12 декабря 2011

SecKeychainFindGenericPassword возвращает только один элемент цепочки для ключей. Чтобы найти все общие пароли для конкретной службы, вам нужно выполнить запрос в цепочке для ключей. Есть несколько способов сделать это, в зависимости от того, на какую версию OS X вы нацелены.

Если вам нужно работать на 10.5 или ниже, вам нужно будет использовать SecKeychainSearchCreateFromAttributes. Это довольно ужасный API. Вот пример метода, который возвращает словарь, отображающий имена пользователей в пароли.

- (NSDictionary *)genericPasswordsWithService:(NSString *)service {
    OSStatus status;

    // Construct a query.
    const char *utf8Service = [service UTF8String];
    SecKeychainAttribute attr = { .tag = kSecServiceItemAttr, 
                                  .length = strlen(utf8Service), 
                                  .data = (void *)utf8Service };
    SecKeychainAttribute attrList = { .count = 1, .attr = &attr };
    SecKeychainSearchRef *search = NULL;
    status = SecKeychainSearchCreateFromAttributes(NULL, kSecGenericPasswordItemClass, &attrList, &search);
    if (status) {
        report(status);
        return nil;
    }

    // Enumerate results.
    NSMutableDictionary *result = [NSMutableDictionary dictionary];
    while (1) {
        SecKeychainItemRef item = NULL;
        status = SecKeychainSearchCopyNext(search, &item);
        if (status)
            break;

        // Find 'account' attribute and password value.
        UInt32 tag = kSecAccountItemAttr;
        UInt32 format = CSSM_DB_ATTRIBUTE_FORMAT_STRING;
        SecKeychainAttributeInfo info = { .count = 1, .tag = &tag, .format = &format };
        SecKeychainAttributeList *attrList = NULL;
        UInt32 length = 0;
        void *data = NULL;
        status = SecKeychainItemCopyAttributesAndData(item, &info, NULL, &attrList, &length, &data);
        if (status) {
            CFRelease(item);
            continue;
        }

        NSAssert(attrList->count == 1 && attrList->attr[0].tag == kSecAccountItemAttr, @"SecKeychainItemCopyAttributesAndData is messing with us");
        NSString *account = [[[NSString alloc] initWithBytes:attrList->attr[0].data length:attrList->attr[0].length encoding:NSUTF8StringEncoding] autorelease];
        NSString *password = [[[NSString alloc] initWithBytes:data length:length encoding:NSUTF8StringEncoding] autorelease];
        [result setObject:password forKey:account];

        SecKeychainItemFreeAttributesAndData(attrList, data);
        CFRelease(item);
    }
    CFRelease(search);
    return result;
}

Для 10.6 и более поздних версий вы можете использовать несколько менее неудобный SecItemCopyMatching API:

- (NSDictionary *)genericPasswordsWithService:(NSString *)service {
    NSDictionary *query = [NSDictionary dictionaryWithObjectsAndKeys:
                           kSecClassGenericPassword, kSecClass,
                           (id)kCFBooleanTrue, kSecReturnData,
                           (id)kCFBooleanTrue, kSecReturnAttributes,
                           kSecMatchLimitAll, kSecMatchLimit,
                           service, kSecAttrService,
                           nil];
    NSArray *itemDicts = nil;
    OSStatus status = SecItemCopyMatching((CFDictionaryRef)q, (CFTypeRef *)&itemDicts);
    if (status) {
        report(status);
        return nil;
    }
    NSMutableDictionary *result = [NSMutableDictionary dictionary];
    for (NSDictionary *itemDict in itemDicts) {
        NSData *data = [itemDict objectForKey:kSecValueData];
        NSString *password = [[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] autorelease];
        NSString *account = [itemDict objectForKey:kSecAttrAccount];
        [result setObject:password forKey:account];
    }
    [itemDicts release];
    return result;
}

Для 10.7 или более поздней версии вы можете использовать мой замечательный LKKeychain framework (PLUG!). Он не поддерживает создание запросов на основе атрибутов, но вы можете просто перечислить все пароли и отфильтровать те, которые вам не нужны.

- (NSDictionary *)genericPasswordsWithService:(NSString *)service {
    LKKCKeychain *keychain = [LKKCKeychain defaultKeychain];
    NSMutableDictionary *result = [NSMutableDictionary dictionary];
    for (LKKCGenericPassword *item in [keychain genericPasswords]) {
        if ([service isEqualToString:item.service]) {
            [result setObject:item.password forKey:item.account];
        }
    }
    return result;
}

(Я не пытался запустить или даже скомпилировать ни один из приведенных выше примеров кода; извините за любые опечатки.)

2 голосов
/ 10 декабря 2011

Вам не нужно имя пользователя. Вы делаете с EMKeychain, но это искусственное различие, которое навязывает этот класс; базовая функция Keychain Services не требует имени пользователя для поиска элемента цепочки для ключей.

При непосредственном использовании SecKeychainFindGenericPassword передайте 0 и NULL в качестве параметров имени пользователя. Он вернет элемент цепочки для ключей, который существует в этой службе.

Однако, это вернет только один элемент. Если у пользователя есть несколько элементов цепочки для ключей в одной и той же службе, вы не будете знать, какой из них вы получили (в документации сказано, что он возвращает «первый» соответствующий элемент, без указания того, что он считает «первым»). Если вы хотите, чтобы для этой услуги были какие-либо и все элементы, вы должны создать поиск и использовать его.

0 голосов
/ 10 декабря 2011

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

(Отказ от ответственности: я ничего не знаю об этом в EMKeychain.)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...