< Previous PageNext Page > Hide TOC

Keychain Services Tasks for iPhone OS

This chapter describes and illustrates the use of basic Keychain Services functions in iPhone OS. For the use of Keychain Services in Mac OS X, see “Mac OS X 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 Keychain Services to Your Application

Most iPhone OS applications need to use Keychain Services only to add a new password to a keychain, modify an existing keychain item, or retrieve a password when needed. Keychain Services provides the following 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). In iPhone OS Keychain Services, certificates, keys, and identities are stored and retrieved in exactly the same way as passwords, except that they have different attributes.

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 iPhone Keychain Services

FLowchart showing use of iPhone Keychain Services to store an Internet password

The user of the application starts by selecting a File Transfer Protocol (FTP) server. The application calls SecItemCopyMatching, passing it a dictionary containing attributes that identify the keychain item. 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. 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 SecItemCopyMatching 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 SecItemAdd function (if this is a new keychain item) or the SecItemUpdate function (to update an existing keychain item) before ending the routine.

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.

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

#import <UIKit/UIKit.h>
#import <Security/Security.h>
 
//Define an Objective-C wrapper class to hold Keychain Services code.
@interface KeychainWrapper : NSObject {
    NSMutableDictionary        *keychainData;
    NSMutableDictionary        *genericPasswordQuery;
}
 
@property (nonatomic, retain) NSMutableDictionary *keychainData;
@property (nonatomic, retain) NSMutableDictionary *genericPasswordQuery;
 
- (void)mySetObject:(id)inObject forKey:(id)key;
- (id)myObjectForKey:(id)key;
 
@end
 
/* ********************************************************************** */
//Unique string used to identify the keychain item:
static const UInt8 kKeychainItemIdentifier[]    = "com.apple.dts.KeychainUI\0";
 
@interface KeychainWrapper (PrivateMethods)
 
 
//The following two methods translate dictionaries between the format used by
// the view controller (NSString *) and the Keychain Services API:
- (NSMutableDictionary *)secItemFormatToDictionary:(NSDictionary *)dictionaryToConvert;
- (NSMutableDictionary *)dictionaryToSecItemFormat:(NSDictionary *)dictionaryToConvert;
// Method used to write data to the keychain:
- (void)writeToKeychain;
 
@end
 
@implementation KeychainWrapper
 
//Synthesize the getter and setter:
@synthesize keychainData, genericPasswordQuery;
 
- (id)init
{
    (self = [super init])
 
        OSStatus keychainErr = noErr;
        // Set up the keychain search dictionary:
        genericPasswordQuery = [[NSMutableDictionary alloc] init];
        // This keychain item is a generic password.
        [genericPasswordQuery setObject:(id)kSecClassGenericPassword
                                 forKey:(id)kSecClass];
        // The kSecAttrGeneric attribute is used to store a unique string that is used
        // to easily identify and find this keychain item. The string is first
        // converted to an NSData object:
        NSData *keychainItemID = [NSData dataWithBytes:kKeychainItemIdentifier
                                  length:strlen((const char *)kKeychainItemIdentifier)];
        [genericPasswordQuery setObject:keychainItemID forKey:(id)kSecAttrGeneric];
        // Return the attributes of the first match only:
        [genericPasswordQuery setObject:(id)kSecMatchLimitOne forKey:(id)kSecMatchLimit];
        // Return the attributes of the keychain item (the password is
        //  acquired in the secItemFormatToDictionary: method):
        [genericPasswordQuery setObject:(id)kCFBooleanTrue
                                 forKey:(id)kSecReturnAttributes];
 
        //Initialize the dictionary used to hold return data from the keychain:
        NSMutableDictionary *outDictionary = nil;
        // If the keychain item exists, return the attributes of the item: 
        keychainErr = SecItemCopyMatching((CFDictionaryRef)genericPasswordQuery,
                                         (CFTypeRef *)&outDictionary);
        if keychainErr == errSecItemNotFound
        {
            // Put default values into the keychain if no matching
            // keychain item is found:
            [self resetKeychainItem];
        }
        else
        {
            // Any other error is unexpected.
            if ! (keychainErr == noErr)
            NSAssert(NO, @"Serious error.\n");
        }
        else
        {
            // Convert the data dictionary into the format used by the view controller:
            self.keychainData = [self secItemFormatToDictionary:outDictionary];
        }
        [outDictionary release];
    return self;
}
 
- (void)dealloc
{
    [keychainData release];
    [genericPasswordQuery release];
    [super dealloc];
}
 
// Implement the mySetObject:forKey method, which writes attributes to the keychain:
- (void)mySetObject:(id)inObject forKey:(id)key
{
    if (inObject == nil) return;
    id currentObject = [keychainData objectForKey:key];
    if (![currentObject isEqual:inObject])
    {
        [keychainData setObject:inObject forKey:key];
        [self writeToKeychain];
    }
}
 
// Implement the myObjectForKey: method, which reads an attribute value from a dictionary:
- (id)myObjectForKey:(id)key
{
    return [keychainData objectForKey:key];
}
 
// Reset the values in the keychain item, or create a new item if it
// doesn't already exist:
 
- (void)resetKeychainItem
{
    if (!keychainData) //Allocate the keychainData dictionary if it doesn't exist yet.
    {
        self.keychainData = [[NSMutableDictionary alloc] init];
    }
    else if (keychainData)
    {
    // Format the data in the keychainData dictionary into the format needed for a query
    //  and put it into tmpDictionary:
        NSMutableDictionary *tmpDictionary =
                            [self dictionaryToSecItemFormat:keychainData];
    // Delete the keychain item in preparation for resetting the values:
        NSAssert(SecItemDelete((CFDictionaryRef)tmpDictionary) == noErr,
                                             @"Problem deleting current keychain item." );
    }
 
    // Default generic data for Keychain Item:
    [keychainData setObject:@"Item label" forKey:(id)kSecAttrLabel];
    [keychainData setObject:@"Item description" forKey:(id)kSecAttrDescription];
    [keychainData setObject:@"Account" forKey:(id)kSecAttrAccount];
    [keychainData setObject:@"Service" forKey:(id)kSecAttrService];
    [keychainData setObject:@"Your comment here." forKey:(id)kSecAttrComment];
    [keychainData setObject:@"password" forKey:(id)kSecValueData];
}
 
// Implement the dictionaryToSecItemFormat: method, which takes the attributes that
//   you want to add to the keychain item and sets up a dictionary in the format
//  needed by Keychain Services:
- (NSMutableDictionary *)dictionaryToSecItemFormat:(NSDictionary *)dictionaryToConvert
{
    // This method must be called with a properly populated dictionary
    // containing all the right key/value pairs for a keychain item search.
 
    // Create the return dictionary:
    NSMutableDictionary *returnDictionary =
                   [NSMutableDictionary dictionaryWithDictionary:dictionaryToConvert];
 
    // Add the keychain item class and the generic attribute:
    NSData *keychainItemID = [NSData dataWithBytes:kKeychainItemIdentifier
                              length:strlen((const char *)kKeychainItemIdentifier)];
    [returnDictionary setObject:keychainItemID forKey:(id)kSecAttrGeneric];
    [returnDictionary setObject:(id)kSecClassGenericPassword forKey:(id)kSecClass];
 
    // Convert the password NSString to NSData to fit the API paradigm:
    NSString *passwordString = [dictionaryToConvert objectForKey:(id)kSecValueData];
    [returnDictionary setObject:[passwordString dataUsingEncoding:NSUTF8StringEncoding]
                                                           forKey:(id)kSecValueData];
    return returnDictionary;
}
 
// Implement the secItemFormatToDictionary: method, which takes the attribute dictionary
//  obtained from the keychain item, acquires the password from the keychain, and
//  adds it to the attribute dictionary:
- (NSMutableDictionary *)secItemFormatToDictionary:(NSDictionary *)dictionaryToConvert
{
    // This method must be called with a properly populated dictionary
    // containing all the right key/value pairs for the keychain item.
 
    // Create a return dictionary populated with the attributes:
    NSMutableDictionary *returnDictionary = [NSMutableDictionary
                                          dictionaryWithDictionary:dictionaryToConvert];
 
    // To acquire the password data from the keychain item,
    // first add the search key and class attribute required to obtain the password:
    [returnDictionary setObject:(id)kCFBooleanTrue forKey:(id)kSecReturnData];
    [returnDictionary setObject:(id)kSecClassGenericPassword forKey:(id)kSecClass];
    // Then call Keychain Services to get the password:
    NSData *passwordData = NULL;
    OSError keychainError = noErr; //
    keychainError = SecItemCopyMatching((CFDictionaryRef)returnDictionary,
                                       (CFTypeRef *)&passwordData);
    if keychainError == noErr
    {
        // Remove the kSecReturnData key; we don't need it anymore:
        [returnDictionary removeObjectForKey:(id)kSecReturnData];
 
        // Convert the password to an NSString and add it to the return dictionary:
        NSString *password = [[[NSString alloc] initWithBytes:[passwordData bytes]
                 length:[passwordData length] encoding:NSUTF8StringEncoding] autorelease];
        [returnDictionary setObject:password forKey:(id)kSecValueData];
    }
    // Don't do anything if nothing is found.
        else
        {
            if (keychainError == errSecItemNotFound)
            NSAssert(NO, @"Nothing was found in the keychain.\n");
        }
    // Any other error is unexpected.
    else
    {
        NSAssert(NO, @"Serious error.\n");
    }
 
    [passwordData release];
    return returnDictionary;
}
 
// Implement the writeToKeychain method, which is called by the mySetObject routine,
//   which in turn is called by the UI when there is new data for the keychain. This
//   method modifies an existing keychain item, or--if the item does not already
//   exist--creates a new keychain item with the new attribute value plus
//  default values for the other attributes.
- (void)writeToKeychain
{
    NSDictionary *attributes = NULL;
    NSMutableDictionary *updateItem = NULL;
 
    // If the keychain item already exists, modify it:
    if (SecItemCopyMatching((CFDictionaryRef)genericPasswordQuery,
                           (CFTypeRef *)&attributes) == noErr)
    {
        // First, get the attributes returned from the keychain and add them to the
        // dictionary that controls the update:
        updateItem = [NSMutableDictionary dictionaryWithDictionary:attributes];
 
        // Second, get the class value from the generic password query dictionary and
        // add it to the updateItem dictionary:
        [updateItem setObject:[genericPasswordQuery objectForKey:(id)kSecClass]
                                                          forKey:(id)kSecClass];
 
        // Finally, set up the dictionary that contains new values for the attributes:
        NSMutableDictionary *tempCheck = [self dictionaryToSecItemFormat:keychainData];
        //Remove the class--it's not a keychain attribute:
        [tempCheck removeObjectForKey:(id)kSecClass];
 
       // You can update only a single keychain item at a time.
        NSAssert(SecItemUpdate((CFDictionaryRef)updateItem,
                (CFDictionaryRef)tempCheck) == noErr,
                @"Couldn't update the Keychain Item." );
    }
    else
    {
// No previous item found; add the new item.
// The new value was added to the keychainData dictionary in the mySetObject routine,
//  and the other values were added to the keychainData dictionary previously.
 
   // No pointer to the newly-added items is needed, so pass NULL for the second parameter:
        NSAssert(SecItemAdd((CFDictionaryRef)[self dictionaryToSecItemFormat:keychainData],
                 NULL) == noErr, @"Couldn't add the Keychain Item." );
    }
}
 
#endif
 
@end

This example follows the same general sequence that was shown in Figure 3-1. In this sample, the generic attribute is used to create a unique string that can be used to easily identify the keychain item. If you wish, you can use standard attributes such as the service name and user name for the same purpose.



< Previous PageNext Page > Hide TOC


© 2003, 2009 Apple Inc. All Rights Reserved. (Last updated: 2009-03-12)


Did this document help you?
Yes: Tell us what works for you.
It’s good, but: Report typos, inaccuracies, and so forth.
It wasn’t helpful: Tell us what would have helped.