Retrieve username stored in Keychain using only ServiceName? OR: Where should you store your username?

So, Keychain OS X has three pieces of information:

  • ServiceName (name of my application)
  • Username
  • Password

I obviously always know ServiceName. Is there a way to find any stored username for this ServiceName? (Finding a password is easy if you know the username.)

I would rather use a good Cocoa wrapper such as EMKeychain to do this. But EMKeychain requires UserName to receive a keychain element!

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

How do you expect to take full advantage of the save credentials in Keychain if you need a username to find credentials? Is it best to save the username in a .plist file or something like that?

+4
source share
3 answers

SecKeychainFindGenericPassword returns only one keychain element. To find all the common passwords for a particular service, you need to run the query in the keychain. There are several ways to do this based on which version of OS X you are targeting.

If you need to run 10.5 or lower, you will need to use SecKeychainSearchCreateFromAttributes . This is a pretty awful API. Here is a rough section of a method that returns a dictionary matching usernames with passwords.

 - (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; } 

For 10.6 and later, you can use the somewhat less awkward 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; } 

For 10.7 or later, you can use my wonderful LKKeychain framework (PLUG!). It does not support attribute-based query building, but you can simply list all passwords and filter out the ones you don't need.

 - (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; } 

(I did not try to run or even compile any of the above code samples, sorry for any typos.)

+6
source

You do not need a username. You do this with EMKeychain, but this is an artificial difference that this class imposes; Keychain Services main function does not require the username to find the keychain element.

When using SecKeychainFindGenericPassword directly, go 0 and NULL for username parameters. It will return the keychain element that exists in this service.

However, this will return only one element. If the user has several keychain elements on the same service, you will not recognize this or the one you have (the documentation says that it returns the "first" matching element, without specifying what it considers to be "first") . If you need all the elements for this service, you must create a search and use this.

+2
source

Shared passwords have a unique key for the service name and username. Thus, in order to get one common entry in the key chain, you need to provide both. However, you can iterate over all shared entries in the keychain for a given service using the SecKeychainFindGenericPassword function.

(Disclaimer: I don't know anything about this at EMKeychain.)

0
source

Source: https://habr.com/ru/post/1385596/


All Articles