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.)