SecItemCopyMatching can't read iCloud keychain
Asked Answered
N

1

6

I grabbed code off Stack Overflow to access some web browser passwords. It works great as long as the passwords are in the login keychain. At some point, the particular account I am interested in got moved to the iCloud keychain and no longer exists in the login keychain. SecItemCopyMatching can't find it. It returns OSStatus -23500 which is "item not found". How can I access those entries?

CFArrayRef result = NULL;
NSDictionary *params = @{ (__bridge id)kSecClass            : (__bridge id)kSecClassInternetPassword,
                        (__bridge id)kSecMatchLimit       : (__bridge id)kSecMatchLimitAll,
                        (__bridge id)kSecReturnAttributes : (__bridge id)kCFBooleanTrue,
                        (__bridge id)kSecAttrProtocol     : (__bridge id)kSecAttrProtocolHTTPS,
                        (__bridge id)kSecAttrServer       : @"accounts.mydomain.com"
                        };
OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)(params), (CFTypeRef *) &result);

if (status == noErr) {
   // item found!
}
Nicety answered 7/5, 2014 at 23:58 Comment(0)
P
0

Though on macOS you could see both login and iCloud keychains, former is macOS-style keychain while latter is iOS-style keychain. macOS-style keychain items can be freely accessed by apps as long as the user consents. iOS-style items follow access group policies strictly.

Every key in the iCloud keychain is under an access group. An app. calling SecItemCopyMatching should've said access group as an entitlement for a key to turn up in the query results.

Once you're sure the searched key's access group is among one of the access groups listed under your app's entitlements, there're a couple of attributes you've to pass to SecItemCopyMatching query dictionary (in addition to the mandatory ones like kSecClass):

  1. kSecAttrAccessGroup
  2. kSecAttrSynchronizable

If your app. is entitled to more than one access group, set the right group through kSecAttrAccessGroup. Set kSecAttrSynchronizable to kCFBooleanTrue to search only iOS-style items (which are synchronized); setting it to kSecAttrSynchronizableAny searches both synced and not-synced keys (like the ones in login keychain). Default is to search only local keys (kCFBooleanFalse).

Example Query

This returns an array of dictionaries; one dictionary per key; all iCloud keys your app. has access to turn up. The key-value pairs in each dictionary are corresponding key's properties; this doesn't return the key's value 1

    NSString* account = @"my_account";
    NSString* service = @"some_service";

    NSDictionary* query = @{
        (id)kSecClass: (id) kSecClassGenericPassword,
        // skip as this example app has only one access group
        // (id)kSecAttrAccessGroup: (id) keychainAccessGroup,
        (id)kSecAttrSynchronizable: (id)kCFBooleanTrue,
        (id)kSecAttrAccount: (id) account,
        (id)kSecAttrService: (id) service,
        (id)kSecReturnAttributes: (id)kCFBooleanTrue,
        (id)kSecMatchLimit: (id)kSecMatchLimitAll,
    };

    CFTypeRef result;
    OSStatus status = SecItemCopyMatching((CFDictionaryRef)query, &result);
    if (status != errSecSuccess) {
        CFStringRef err = SecCopyErrorMessageString(status, nil);
        NSLog(@"Error: %@", err);
        CFRelease(err);
    } else {
        NSArray* attributes_of_keys = (__bridge NSArray*)result;
    }

Inspecting attributes_of_keys under the debugger should tell you plenty.

Source: Apple Developer forum thread, SecItemCopyMatching and beyond.

Apple has done a shoddy job of documenting the SecItem* API family. Information is there but is strewn across different (irrelevant) places (some even outside the official documentation).

1: pass (id)kSecReturnData: (id)kCFBooleanTrue to get key's data; however you can't use kSecReturnData and kSecMatchLimitAll together

Pony answered 12/7, 2021 at 9:36 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.