OS X Keychain Services Tasks

This chapter describes and illustrates the use of basic Keychain Services functions in OS X. For the use of Keychain Services in iOS, see “iOS Keychain Services Tasks.”

The functions described in this chapter enable you to:

“Keychain Services Concepts” provides an introduction to the concepts and terminology of Keychain Services. For detailed information about all Keychain Services functions, see Keychain Services Reference.

Adding Simple Keychain Services to Your Application

Most applications need to use Keychain Services only to add a new password to a keychain or retrieve a password when needed. Keychain Services provides the following pairs of functions for accomplishing these tasks:

You use Internet passwords for accessing servers and websites over the Internet, and generic passwords for any other password-protected service (such as a database or scheduling application). AppleShare passwords (that is, keychain items with a class code of kSecAppleSharePasswordItemClass) are stored as generic passwords.

The “find” functions retrieve information (attributes or secure data) from an item in the keychain. The “add” functions add an item to a keychain. These functions call other Keychain Services functions to accomplish their tasks. Because Keychain Services allocates the buffers in which the item data and attributes are returned, you must call SecKeychainItemFreeContent to free these buffers after using one of the find functions to retrieve attributes or secure data from a keychain item. (If Keychain Services does not find the item or fails to return any data for some other reason, it does not allocate any buffers and you should not call the SecKeychainItemFreeContent function.)

Figure 3-1 shows a flowchart of how an application might use these functions to gain access to an Internet FTP server.

Figure 3-1  Accessing an Internet server using OS X Keychain Services
Accessing an Internet server using Keychain Services

The user of the application starts by selecting a File Transfer Protocol (FTP) server. The application calls SecKeychainFindInternetPassword, passing it attributes that identify the service and the user to seek. If the password is on the keychain, the function returns the password to the application, which sends it to the FTP server to authenticate the user. The application then calls SecKeychainItemFreeContent to free the data buffer allocated for the password (note that you should not call this function if no data is returned). If the authentication succeeds, the routine is finished. If the authentication fails, the application displays a dialog to request the user name and password.

If the password is not on the keychain, then SecKeychainFindInternetPassword returns the errSecItemNotFound result code. In this case as well, the application displays a dialog to request the user name and password. (This dialog should also include a Cancel button, but that choice was omitted from the figure to keep the flowchart from becoming overly complex.)

Having obtained the password from the user, the application proceeds to authenticate the user to the FTP server. When the authentication has succeeded, the application can assume that the information entered by the user was valid. The application then displays another dialog asking the user whether to save the password on the keychain. If the user selects No, then the routine is finished. If the user selects Yes, then the application calls the SecKeychainAddInternetPassword function (if this is a new keychain item), or the SecKeychainItemModifyAttributesAndData function (to update an existing keychain item) before ending the routine.

If there is no keychain, the SecKeychainFindInternetPassword or SecKeychainAddInternetPassword function displays a dialog allowing the user to “reset to defaults” (see Figure 1-5), which creates a new keychain named login.keychain with the user’s login account password. If the keychain is locked, the function displays a dialog requesting the user to enter a password to unlock the keychain (Figure 1-6). The user can cancel the operation at this time as well.

Listing 3-1 shows how a typical application might use Keychain Services functions to get and set passwords for generic items. You can get and set keychain item attributes (such as user name or service name) using these same functions; see Listing 3-2 for an example.

Listing 3-1  Getting and setting passwords in OS X Keychain Services

 
 
#include <CoreFoundation/CoreFoundation.h>
#include <Security/Security.h>
#include <CoreServices/CoreServices.h>
 
 
//Call SecKeychainAddGenericPassword to add a new password to the keychain:
OSStatus StorePasswordKeychain (void* password,UInt32 passwordLength)
{
    OSStatus status;
    status = SecKeychainAddGenericPassword (
                NULL,            // default keychain
                10,              // length of service name
                "SurfWriter",    // service name
                10,              // length of account name
                "MyUserAcct",    // account name
                passwordLength,  // length of password
                password,        // pointer to password data
                NULL             // the item reference
    );
    return (status);
}
 
//Call SecKeychainFindGenericPassword to get a password from the keychain:
OSStatus GetPasswordKeychain (void **passwordData,UInt32 *passwordLength,
                                                SecKeychainItemRef *itemRef)
{
    OSStatus status1 ;
 
 
    status1 = SecKeychainFindGenericPassword (
                 NULL,           // default keychain
                 10,             // length of service name
                 "SurfWriter",   // service name
                 10,             // length of account name
                 "MyUserAcct",   // account name
                 passwordLength,  // length of password
                 passwordData,   // pointer to password data
                 itemRef         // the item reference
    );
    return (status1);
}
 
//Call SecKeychainItemModifyAttributesAndData to change the password for
// an item already in the keychain:
OSStatus ChangePasswordKeychain (SecKeychainItemRef itemRef)
{
    OSStatus status;
    void * password = "myNewP4sSw0rD";
 
    size_t passwordLength = strlen(password);
    assert(passwordLength <= 0xffffffff);
 
    status = SecKeychainItemModifyAttributesAndData (
                 itemRef,         // the item reference
                 NULL,            // no change to attributes
                 (UInt32)passwordLength,  // length of password
                 password         // pointer to password data
    );
    return (status);
}
 
 
/* ********************************************************************** */
 
int tryIt(void)
{
    OSStatus status;
 
    void * myPassword = "myP4sSw0rD";
 
    size_t myPasswordLength = strlen(myPassword);
    assert(myPasswordLength <= 0xffffffff);
 
    void *passwordData = NULL; // will be allocated and filled in by
                               //SecKeychainFindGenericPassword
    SecKeychainItemRef itemRef = NULL;
 
    UInt32 passwordLength = 0;
 
    // Call SecKeychainFindGenericPassword
    status = GetPasswordKeychain (&passwordData,&passwordLength,&itemRef);
 
    if (status == noErr)       //If call was successful, authenticate user
                                    //and continue.
    {
        //Free the data allocated by SecKeychainFindGenericPassword:
        status = SecKeychainItemFreeContent (
                 NULL,           //No attribute data to release
                 passwordData    //Release data buffer allocated by
                 //SecKeychainFindGenericPassword
        );
    } else if (status == errSecItemNotFound) { //Is password on keychain?
        /*
           If password is not on keychain, display dialog to prompt user
           for name and password.
 
           Authenticate user.
 
           If unsuccessful, prompt user again for name and password.
           If successful, ask user whether to store new password on keychain.
 
           If no, return.
           If yes, store password by calling SecKeychainAddGenericPassword.
        */
        status = StorePasswordKeychain (myPassword,(UInt32)myPasswordLength);
 
        return (status);
    }
 
    /*
       If password is on keychain, authenticate user.
 
       If authentication succeeds, return.
 
       If authentication fails, prompt user for new user name and password and
       authenticate again.
 
       If unsuccessful, prompt again.
       If successful, ask whether to update keychain with new information.
 
       If no, return.
       If yes, store new information by calling SecKeychainItemModifyAttributesAndData.
    */
    status = status ?: ChangePasswordKeychain (itemRef);
    if (itemRef) CFRelease(itemRef);
    return (status);
 
 }

This example follows the same general sequence that was shown in Figure 3-1; however, unlike the figure, the example illustrates the use of generic passwords rather than Internet passwords.

Although the example in Listing 3-1 is written in procedural C, you can call these same functions using Objective C. Listing 3-2 shows how you might create an Internet password item if you wanted some custom attribute values. Note that attribute strings should all be encoded in UTF-8 format.

Advanced Topics

Most applications need only the Keychain Services functions described in “Adding Simple Keychain Services to Your Application.” However, in certain circumstances you might want to use some of the other functions provided by Keychain Services. For example, you might want to display a custom label for your application in the Keychain Access utility. Or, if you are writing a server, you might want to disable the automatic display of dialogs and instead take care of unlocking the keychain within your application.

Creating a Custom Keychain Item

This section discusses how to create a keychain item if you want lower-level control than is provided by SecKeychainAddInternetPassword or SecKeychainAddGenericPassword. For example, you might want to have the Keychain Access application display a custom label for your keychain item or you might want to specify more than one trusted application that can access the item. Specifically, this section illustrates the use of the SecKeychainItemCreateFromContent function to create a keychain item and the SecAccessCreate function to set up an access list. For more details about these and other low-level functions, see Keychain Services Reference.

When you use SecKeychainAddInternetPassword or SecKeychainAddGenericPassword, the function creates a label for the keychain item automatically. For an Internet password, it uses the URL, minus the scheme name, colon, and leading slashes. For example, the label (displayed in the Name field in Keychain Access) for a new Internet password for the URL http://www.apple.com becomes www.apple.com. For a generic password, the function uses the service attribute for the label. In both cases, the Name and Where fields in Keychain access are identical. If you want a unique name for the keychain item that is different from the URL or service name, you can specify the label (kSecLabelItemAttr) attribute when you call SecKeychainItemCreateFromContent.

The SecKeychainAddInternetPassword and SecKeychainAddGenericPassword functions create an initial access list for you. This default access list includes only one trusted application (that is, one application that can access the keychain item whenever the keychain is unlocked), namely the application that created the keychain item. The SecKeychainItemCreateFromContent function accepts an access list as input. However, if you pass NULL, this function creates an access list consisting of the single application calling the function. Therefore, you must call the SecAccessCreate function before calling the SecKeychainItemCreateFromContent function if you want an access list with more than one trusted application. Alternatively, you can alter the access list of an existing keychain item; see “Modifying the Access List of an Existing Keychain Item.”

Listing 3-2 illustrates the creation of an Internet keychain item with a custom label and an access list that includes two trusted applications. This listing also illustrates the use of the Keychain Services API from an Objective-C application.

Listing 3-2  Creating a keychain item with custom attributes

#import <Foundation/Foundation.h>
 
#include <Security/Security.h>
 
SecAccessRef createAccess(NSString *accessLabel)
{
    OSStatus err;
    SecAccessRef access=nil;
    NSArray *trustedApplications=nil;
 
    //Make an exception list of trusted applications; that is,
    // applications that are allowed to access the item without
    // requiring user confirmation:
    SecTrustedApplicationRef myself, someOther;
 
    //Create trusted application references; see SecTrustedApplications.h:
    err = SecTrustedApplicationCreateFromPath(NULL, &myself);
    err = err ?: SecTrustedApplicationCreateFromPath("/Applications/Mail.app",
                                                            &someOther);
 
    if (err == noErr) {
        trustedApplications = [NSArray arrayWithObjects:(__bridge_transfer id)myself,
                                                    (__bridge_transfer id)someOther, nil];
    }
 
    //Create an access object:
    err = err ?: SecAccessCreate((__bridge CFStringRef)accessLabel,
                            (__bridge CFArrayRef)trustedApplications, &access);
    if (err) return nil;
 
    return access;
}
 
 
OSStatus addInternetPassword(NSString *password, NSString *account,
                    NSString *server, NSString *itemLabel, NSString *path,
                    SecProtocolType protocol, int port)
{
    OSStatus err;
    SecKeychainItemRef item = nil;
    const char *pathUTF8 = [path UTF8String];
    const char *serverUTF8 = [server UTF8String];
    const char *accountUTF8 = [account UTF8String];
    const char *passwordUTF8 = [password UTF8String];
    const char *itemLabelUTF8 = [itemLabel UTF8String];
 
    //Create initial access control settings for the item:
    SecAccessRef access = createAccess(itemLabel);
 
    //Following is the lower-level equivalent to the
    // SecKeychainAddInternetPassword function:
 
    assert(strlen(itemLabelUTF8) <= 0xffffffff);
    assert(strlen(accountUTF8) <= 0xffffffff);
    assert(strlen(serverUTF8) <= 0xffffffff);
    assert(strlen(pathUTF8) <= 0xffffffff);
 
    //Set up the attribute vector (each attribute consists
    // of {tag, length, pointer}):
    SecKeychainAttribute attrs[] = {
        { kSecLabelItemAttr, (UInt32)strlen(itemLabelUTF8), (char *)itemLabelUTF8 },
        { kSecAccountItemAttr, (UInt32)strlen(accountUTF8), (char *)accountUTF8 },
        { kSecServerItemAttr, (UInt32)strlen(serverUTF8), (char *)serverUTF8 },
        { kSecPortItemAttr, sizeof(int), (int *)&port },
        { kSecProtocolItemAttr, sizeof(SecProtocolType),
                                        (SecProtocolType *)&protocol },
        { kSecPathItemAttr, (UInt32)strlen(pathUTF8), (char *)pathUTF8 }
    };
    SecKeychainAttributeList attributes = { sizeof(attrs) / sizeof(attrs[0]),
                                            attrs };
 
 
    assert(strlen(passwordUTF8) <= 0xffffffff);
 
    err = SecKeychainItemCreateFromContent(
        kSecInternetPasswordItemClass,
        &attributes,
        (UInt32)strlen(passwordUTF8),
        passwordUTF8,
        NULL, // use the default keychain
        access,
        &item);
 
    if (access) CFRelease(access);
    if (item) CFRelease(item);
 
    return err;
}
 
 
int tryAdd(void)
{
         //Add an example password to the keychain:
    return addInternetPassword(@"sample password", @"sample account",
            @"samplehost.apple.com", @"sampleName", @"cgi-bin/bogus/testpath",
                                                kSecProtocolTypeHTTP, 8080);
}

Modifying the Access List of an Existing Keychain Item

By default, a keychain item’s access list contains only the application that created the keychain item. Listing 3-2 illustrates the creation of an Internet keychain item with an access list that includes two trusted applications. Listing 3-3 illustrates how to modify an existing keychain item. The listing finds a specific keychain item, extracts the access object, including the list of trusted applications, authorization tags, and other information (see “ACL Entries”), adds a trusted application to the list, reconstitutes the access object, and writes the new access object back to the keychain item. Although this sample illustrates modifying the list of trusted applications, you can use a similar sequence to modify any of the information in the access object—for example, to add or remove an authorization tag.

Listing 3-3 starts by calling the SecKeychainSearchCreateFromAttributes function to create a search reference. In this example, the code looks for a keychain item by its label (as seen in the Keychain Access application); you can search for other attributes, however, such as modification date. Using this search reference, the sample calls SecKeychainSearchCopyNext to find the keychain item.

The listing next calls SecKeychainItemCopyAccess to retrieve the keychain item’s access object. As discussed in “Keychain Access Controls,” the access object includes one or more ACL entries. The listing calls the SecAccessCopySelectedACLList function and passes it an authorization tag value of CSSM_ACL_AUTHORIZATION_DECRYPT. This authorization tag is one of the tags normally associated with the access list used for sensitive operations and is the list displayed by the Keychain Access application. To retrieve all the ACL entries for an access object, use the SecAccessCopyACLList function instead.

The SecAccessCopySelectedACLList function returns a CFArrayRef object containing all of the ACL entries that meet the selection criteria. In this case, the code is assuming that there should be only one ACL entry that employs the CSSM_ACL_AUTHORIZATION_DECRYPT tag. The listing calls the CFArrayGetValues function to create a C array of SecACLRef objects from the CFArrayRef and then calls the SecACLCopySimpleContents function, passing it the first (and presumably only) item in the array. The SecACLCopySimpleContents function also retrieves a CFArrayRef containing the list of trusted applications, the keychain item description string, and the prompt selector flag. These values are needed in order to reconstitute the ACL entry after adding a trusted application to the list.

Next, the listing uses the CFArrayGetValues function again, this time to extract the array of trusted applications from the CFArrayRef. The listing calls the SecTrustedApplicationCreateFromPath function to create a new trusted application object, appends the new application to the list, and calls CFArrayCreate to create a new CFArrayRef.

Before adding a new ACL entry to the access object, the listing calls the SecACLGetAuthorizations function to get the list of authorization tags from the old access object. Then, having extracted all the information from the old ACL entry, the code calls the SecACLRemove function to remove the old ACL entry from the access object. The listing then calls the SecACLCreateFromSimpleContents function to create a new ACL entry for the access object. This function automatically adds the entry to the access object; there is no separate call for that purpose. The new ACL entry has only the default authorization list, however, so the listing calls the SecACLSetAuthorizations function, passing in the authorization list extracted from the old ACL entry.

The access object is now complete, with the new ACL entry containing all of the trusted applications in the old entry plus the new one added here. Only two steps remain: First, the listing calls the SecKeychainItemSetAccess function to replace the access object in the keychain item with the new access object; then the listing calls the CFRelease function for each core foundation object that is no longer needed, in order to release the memory.

Note that this sample routine causes the user to be prompted twice for permission: once when the old ACL entry is deleted from the access object, and once when the new access object is written to the keychain item.

Listing 3-3  Modifying a keychain item access list

#include <CoreFoundation/CoreFoundation.h>
#include <Security/Security.h>
#include <CoreServices/CoreServices.h>
 
 
//Get an ACL out of a CFArray:
SecACLRef GetACL (CFIndex numACLs, CFArrayRef ACLList,
                CFArrayRef *applicationList, CFStringRef *description,
                SecKeychainPromptSelector *promptSelector)
{
    OSStatus status;
    //Because we limited our search to ACLs used for decryption, we
    // expect only one ACL for this item. Therefore, we extract the
    // application list from the first ACL in the array.
    const SecACLRef acl = (SecACLRef) CFArrayGetValueAtIndex(ACLList, 0);
    status = SecACLCopyContents (
        acl,                    // the ACL from which to extract
                                //  the list of trusted apps
        applicationList,        // the list of trusted apps
        description,            // the description string
        promptSelector          // the value of the prompt selector flag
                                        );
 
    if (status == noErr) {
        return acl;
    } else {
        return NULL;
    }
}
 
 
int modifyTheACL(void)
{
    OSStatus status;
 
    SecKeychainSearchRef searchReference = NULL;
    SecKeychainItemRef itemRef = NULL;
 
    SecAccessRef itemAccess = NULL;
    SecACLRef oldACL = NULL, newACL = NULL;
 
    CFIndex arrayCount;
    CFRange arrayRange;
    SecTrustedApplicationRef trustedAppArray[10];
    SecKeychainPromptSelector promptSelector;
    CFStringRef description = NULL;
    CFArrayRef newTrustedAppArray = NULL;
 
    const char *path = "/Applications/Mail.app";       //path to
                                        // trusted app to add to ACL
    SecTrustedApplicationRef trustedApp = NULL;
 
    /* Construct a search dictionary to find the desired item. */
    const void *keys[] = {
    	kSecAttrLabel,
        kSecReturnRef, /* return the item. */
        NULL
    };
    const void *values[] = {
        /* kSecAttrLabel => */ CFSTR("www.TestItem.com"),
        /* kSecReturnRef => */ kCFBooleanTrue,
        NULL
    };
 
    CFDictionaryRef searchDict = CFDictionaryCreate(kCFAllocatorDefault,
        keys,
        values,
        sizeof(keys) / sizeof(keys[0]),
        &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
 
     CFArrayRef aclList = NULL;
     CFIndex numACLs = 0;
     CFArrayRef applicationList;
     CFTypeRef authorizationTag =
        kSecACLAuthorizationDecrypt;  // the authorization tag
                                        //  to search for.
 
     // Find the keychain item and obtain a keychain item reference object.
    // This returns a SecKeychainItemRef, which we must release when
    //  we're finished using it.
    status = SecItemCopyMatching(searchDict, (CFTypeRef *)&itemRef);
 
    if (status == noErr)
        {
        // Obtain the access reference object for the keychain item.
        // This returns a SecAccessRef, which we must release when
        //  we're finished using it.
        status = SecKeychainItemCopyAccess (itemRef, &itemAccess);
        // Obtain an array of ACL entry objects for the access object.
        // Limit the search to ACL entries with the specified
        //  authorization tag.
        aclList = SecAccessCopyMatchingACLList(itemAccess,
                                     authorizationTag);
        numACLs = CFArrayGetCount (aclList);
        // Extract the ACL entry object from the array of ACL entries,
        //  along with the ACL entry's list of trusted applications,
        //  its description, and its prompt selector flag setting.
        // This returns a SecACLRef and a CFArrayRef, which we must
        //  release we're finished using them.
        oldACL = GetACL (numACLs, aclList, &applicationList,
                                    &description, &promptSelector);
        if (oldACL) { CFRetain(oldACL); }
        arrayCount = CFArrayGetCount (applicationList);
 
        //  The application list is a CFArray.  Extract the list of
        //  applications from the CFArray.
        arrayRange.location = (CFIndex) 0;
        arrayRange.length = arrayCount;
        CFArrayGetValues (applicationList, arrayRange,
                                            (void *) trustedAppArray);
        // Create a new trusted application reference object for
        //  the application to be added to the list.
        status = status ?: SecTrustedApplicationCreateFromPath (path, &trustedApp);
        if (status == noErr)   // the function fails if the application is
                                // not found.
        {
            // Append the new application to the array and create a
            //  new CFArray.
            trustedAppArray[arrayCount] = trustedApp;
            newTrustedAppArray = CFArrayCreate (NULL,
                                (void *)trustedAppArray, arrayCount+1,
                                                &kCFTypeArrayCallBacks);
            // Get the authorizations from the old ACL.
            CFArrayRef authorizations = SecACLCopyAuthorizations(oldACL);
 
            // Delete the old ACL from the access object. The user is
            // prompted for permission to alter the keychain item.
            status = status ?: SecACLRemove (oldACL);
 
            // Create a new ACL with the same attributes as the old
            // one, except use the new CFArray of trusted applications.
            status = status ?: SecACLCreateWithSimpleContents (itemAccess,
                        newTrustedAppArray, description, promptSelector,
                                                            &newACL);
            // Set the authorizations for the new ACL to be the same as
            //  those for the old ACL.
            status = status ?: SecACLUpdateAuthorizations (newACL, authorizations);
 
            // Replace the access object in the keychain item with the
            //  new access object. The user is prompted for permission
            //  to alter the keychain item.
            status = status ?: SecKeychainItemSetAccess (itemRef, itemAccess);
 
            CFRelease(authorizations);
        }
        else {
            // Handle the error if the application was not found.
            // ...
        }
 
    // Release the objects we allocated or retrieved
    if (searchReference)
        CFRelease(searchReference);     //SecKeychainSearchRef
    if (itemRef)
        CFRelease(itemRef);             //SecKeychainItemRef
    if (itemAccess)
        CFRelease(itemAccess);          //SecAccessRef
    if (oldACL)
        CFRelease(oldACL);              //SecACLRef
    if (newACL)
        CFRelease(newACL);              //SecACLRef
    if (description)
        CFRelease(description);         //CFStringRef
    if (newTrustedAppArray)
        CFRelease(newTrustedAppArray);  //CFArrayRef
    if (trustedApp)
        CFRelease(trustedApp);          //SecTrustedApplicationRef
    if (aclList)
        CFRelease(aclList);             //CFArrayRef
    if (applicationList)
        CFRelease(applicationList);     //CFArrayRef
    }
    return (status);
 
}

Servers and the Keychain

Several Keychain Services functions automatically display a dialog requesting that the user unlock the keychain or create a new keychain when necessary. Keychain Services also displays dialogs to confirm that the user wants the application to access the keychain (Figure 3-2) and for other reasons. If you are writing a server application that must run unattended, you might want to disable the automatic display of dialogs and programatically unlock or create keychains.

Figure 3-2  Confirm access dialog
Confirm access dialog

Use the SecKeychainSetUserInteractionAllowed function to enable or disable the automatic display of dialogs. When you call this function with a value of FALSE for the state parameter, any Keychain Services functions that ordinarily open dialogs instead return immediately with the result errSecInteractionRequired. You can use the SecKeychainGetStatus function to find out whether the keychain exists and is unlocked. You can then use the SecKeychainUnlock function to unlock the keychain, or the SecKeychainCreate function to create a new keychain, as necessary.

Adding, Removing, and Working With Keys and Certificates

In addition to passwords, a keychain can also hold encryption keys and certificates. When working with keys and certificates, you should use the following functions:

For examples of these functions, see “iOS Keychain Services Tasks.” You can also learn more about these functions in Certificate, Key, and Trust Services Programming Guide.