Question

I am trying to search for certificates directly within the keychain using an email address. This is what I have now:

OSStatus status = errSecSuccess;


CFMutableDictionaryRef query = CFDictionaryCreateMutable(kCFAllocatorDefault, 4, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
CFDictionaryAddValue(query, kSecReturnRef, kCFBooleanTrue);
CFDictionaryAddValue(query, kSecMatchLimit, kSecMatchLimitAll);
CFDictionaryAddValue(query, kSecClass, classType);
CFDictionaryAddValue(query, kSecMatchEmailAddressIfPresent, (__bridge const void *)(emailAddress));

CFArrayRef result = nil;
status = SecItemCopyMatching(query, (CFTypeRef *)&result);
NSLog(@"Retrieved Item from Keychain With PersistedRef - Status: %@", [self tradeStatusForString:status]);


if (query)
    CFRelease(query);

if(status != errSecSuccess)
    return nil;
else
    return result;

However, I get all certificates instead of the certificate with an email address. I know for a fact that those certificates have email addresses so I'm not sure what could be incorrect here.

Any help is appreciated, thank you!

Was it helpful?

Solution

I could never get kSecMatchEmailAddressIfPresent to work properly but found an alternative solution.

There are attributes that can be set when storing items in the keychain. Therefore when I am storing a certificate, I set kSecAttrLabel to the email address.

NSMutableDictionary *query = [[NSMutableDictionary alloc] init];
[query setValue:(__bridge id)kCFBooleanTrue forKey:(__bridge id)kSecReturnPersistentRef];
[query setValue:(__bridge id)item forKey:(__bridge id)kSecValueRef];
[query setValue:(id)emailAddress forKey:(__bridge id)kSecAttrLabel];

NSData *returnData = nil;
status = SecItemAdd((__bridge CFDictionaryRef)query, (void *)&returnData);

Then when looking up the certificate, I would include the kSecAttrLabel as a search parameter.

NSMutableDictionary *query = [[NSMutableDictionary alloc] init];
[query setValue:(__bridge id)kSecMatchLimitAll forKey:(__bridge id)kSecMatchLimit];
[query setValue:(__bridge id)kCFBooleanTrue forKey:(__bridge id)kSecReturnPersistentRef];
[query setValue:(__bridge id)classType forKey:(__bridge id)kSecClass];
[query setValue:(id)emailAddress forKey:(__bridge id)kSecAttrLabel];

CFArrayRef result = nil;
status = SecItemCopyMatching((__bridge CFDictionaryRef)(query), (CFTypeRef *)&result);

This works for me because I already know the email address that the certificate is tied to before hand. However if you didn't know the email before hand, you might be able to extract it from kSecPolicyName (Security Policy Keys) or with openssl.

OTHER TIPS

I have no experience with the Keychain Services API, but I noticed that for kSecMatchEmailAddressIfPresent the API docs state that:

[...] returned certificates or identities are limited to those that either contain the address or do not contain any email address.

[emphasis mine]

To me it is therefore not surprising that your search results contain more certificates than just the ones that contain the email address you are looking for.

Unfortunately, it seems as if the API has no way to exclude certificates from the search results which have no email address at all (at least I did not find anything that looks useful), so you will probably have to check the items in the search result array manually.

If you specify kSecMatchItemList you can feed the search result array back into SecItemCopyMatching. This might be useful to narrow down the search iteratively, but only if you can come up with additional clever search criteria.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top