1
votes

For some reason I got an empty dictionary when calling SecItemCopyMatching on OSX 10.8.4. The corresponding item is in the keychain and contains username and password. SecItemCopyMatching founds it (errSecSuccess) but the result dictionary just contains 0 entries. I would expect it to have at least username and password data, so what's wrong with my request?

OSStatus status;
NSMutableDictionary *query = [NSMutableDictionary dictionary];
[query setObject:(id)kSecClassGenericPassword forKey:(id)kSecClass];
[query setObject:(id)kSecReturnAttributes forKey:(id)kCFBooleanTrue];
[query setObject:@"MyService" forKey:(id)kSecAttrService];

CFDictionaryRef dictRef = NULL;
status = SecItemCopyMatching((__bridge CFDictionaryRef)query, (CFTypeRef *)&dictRef);
if (status != errSecSuccess) {
    CFStringRef errorRef = SecCopyErrorMessageString(status, NULL);
    NSLog(@"%s: %@", __FUNCTION__, (__bridge NSString *)errorRef);
    CFRelease(errorRef);
    return nil;
}

// --> dictRef empty

if (dictRef != NULL) CFRelease(dictRef);

enter image description here

1
I can't see at a glance why you're getting an empty dictionary back, but it's not surprising that the password isn't returned. The header documentation for kSecReturnAttributes says: Specifies a dictionary key whose value is of type CFBooleanRef. A value of kCFBooleanTrue indicates that the (non-encrypted) attributes of an item (in a CFDictionaryRef) should be returned. - bdash
you are right, it doesn't asked to allow access to the keychain. And there is a stupid mistake in the request:[query setObject:(id)kCFBooleanTrue forKey:(id)kSecReturnAttributes]; - Gamadril

1 Answers

2
votes

There is a mistake in the request. I've mixed up key and object.

[query setObject:(id)kSecReturnAttributes forKey:(id)kCFBooleanTrue];

must be

[query setObject:(id)kCFBooleanTrue forKey:(id)kSecReturnAttributes];

however as bdash pointed out, it will return only non-encrypted attributes like the username. To get the password there is another request necessary with passing

[query setObject:(id)kCFBooleanTrue forKey:(id)kSecReturnData];

So here what I have now:

OSStatus status;
NSMutableDictionary *query = [NSMutableDictionary dictionary];
[query setObject:(id)kSecClassGenericPassword forKey:(id)kSecClass];
[query setObject:(id)kCFBooleanTrue forKey:(id)kSecReturnAttributes];
[query setObject:(id)kSecMatchLimitOne forKey:(id)kSecMatchLimit];
[query setObject:@"MyService" forKey:(id)kSecAttrService];

// get username
CFDictionaryRef dictRef = NULL;
status = SecItemCopyMatching((__bridge CFDictionaryRef)query, (CFTypeRef *)&dictRef);
if (status != errSecSuccess) {
    CFStringRef errorRef = SecCopyErrorMessageString(status, NULL);
    NSLog(@"%s: %@", __FUNCTION__, (__bridge NSString *)errorRef);
    CFRelease(errorRef);
    return nil;
}

NSString *username = (__bridge NSString *)CFDictionaryGetValue(dictRef, kSecAttrAccount);
CFRelease(dictRef);

// get password
[query removeObjectForKey:(id)kSecReturnAttributes];
[query setObject:(id)kCFBooleanTrue forKey:(id)kSecReturnData];
CFDataRef dataRef = NULL;
status = SecItemCopyMatching((__bridge CFDictionaryRef)query, (CFTypeRef *)&dataRef);
if (status != errSecSuccess) {
    CFStringRef errorRef = SecCopyErrorMessageString(status, NULL);
    NSLog(@"%s: %@", __FUNCTION__, (__bridge NSString *)errorRef);
    CFRelease(errorRef);
    return nil;
}

NSString *password = [[NSString alloc] initWithData:(__bridge NSData *)(dataRef) encoding:NSUTF8StringEncoding];
CFRelease(dataRef);