CFHipsterRef Low-Level Programming on iOS & Mac OS X

Keychain Services

Keychain is the password management system on iOS & OS X. It stores certificates and private keys, as well as passwords for websites, servers, wireless networks, and other encrypted volumes.

Interactions with the Keychain are mediated through queries, rather than direct manipulation. The queries themselves can be quite complicated, and cumbersome with a C API.

A query is a dictionary consisting of the following components:

  • The class of item to search for, either "Generic Password", "Internet Password", "Certificate", "Key", or "Identity".

  • The return type for the query, either "Data", "Attributes", "Reference", or "Persistent Reference".

  • One or more attribute key-value pairs to match on.

  • One or more search key-value pairs to further refine the results, such as whether to match strings with case sensitivity, only match trusted certificates, or limit to just one result or return all.

Getting Keychain Items

NSString *service = @"com.example.app";
NSString *account = @"username";
NSDictionary *query =
@{
    (__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword, (__bridge id)kSecAttrService: service,
    (__bridge id)kSecAttrAccount: key,
    (__bridge id)kSecMatchLimit: kSecMatchLimitOne,
};

CFTypeRef result;
OSStatus status =
SecItemCopyMatching((__bridge CFDictionaryRef)query, &result);

In this example, the query tells the keychain to find all generic password items for the service com.example.app with the matching username. kSecAttrService defines the scope of credentials, while kSecAttrAccount acts as a unique identifier. e search option kSecMatchLimitOne is passed to ensure that only the first match is returned, if any.

If status is equal to errSecSuccess (0), then result should be populated with the matching credential.

Adding and Updating Keychain Items

NSData *data = ...;

if (status == errSecSuccess)
{
    NSDictionary *updatedAttributes = @{(__bridge id)kSecValueData: data};

    SecItemUpdate((__bridge CFDictionaryRef)query, (__bridge CFDictionaryRef)updatedAttributes);
}
else
{
    NSMutableDictionary *attributes = [query mutableCopy];
    attributes[(__bridge id)kSecValueData] = data;
    attributes[(__bridge id)kSecAttrAccessible] = (__bridge id)kSecAttrAccessibleAfterFirstUnlock;
    SecItemAdd((__bridge CFDictionaryRef)attributes, NULL);
}