2013-08-21 31 views
1

我正在使用apple提供的keychainwrapper示例代碼來存儲我在應用程序授權中獲得的NSDictionary數據。我從SecItemAdd API接收errSecParam(-50)作爲錯誤代碼。 下面是keychainwrapper.m如何將nsdictionary存儲在keychainwrapper中

#import "KeychainItemWrapper.h" 
#import "SynthesizeSingleton.h" 
#import <Security/Security.h> 

@interface KeychainItemWrapper (PrivateMethods) 
/* 
The decision behind the following two methods (secItemFormatToDictionary and dictionaryToSecItemFormat) was 
to encapsulate the transition between what the detail view controller was expecting (NSString *) and what the 
Keychain API expects as a validly constructed container class. 
*/ 
- (NSMutableDictionary *)secItemFormatToDictionary:(NSDictionary *)dictionaryToConvert; 
- (NSMutableDictionary *)dictionaryToSecItemFormat:(NSDictionary *)dictionaryToConvert; 

// Updates the item in the keychain, or adds it if it doesn't exist. 
- (void)writeToKeychain; 

@end 

@implementation KeychainItemWrapper 
{ 
    NSMutableDictionary *keychainItemData;  // The actual keychain item data backing store. 
    NSMutableDictionary *genericPasswordQuery; // A placeholder for the generic keychain item query used to locate the item. 
} 

SYNTHESIZE_SINGLETON_FOR_CLASS(KeychainItemWrapper); 

#pragma mark singleton implementation 
+(KeychainItemWrapper *) sharedInstance 
{ 
    return [self sharedKeychainItemWrapper]; 
} 

- (id) init 
{ 
    self = [super init]; 
    if (self) { 
     // Do Nothing 
    } 
    return self; 
} 

- (void)createKeychainItemWithIdentifier: (NSString *)identifier accessGroup:(NSString *) accessGroup; 
{ 
    // Begin Keychain search setup. The genericPasswordQuery leverages the special user 
    // defined attribute kSecAttrGeneric to distinguish itself between other generic Keychain 
    // items which may be included by the same application. 
    genericPasswordQuery = [[NSMutableDictionary alloc] init]; 

    [genericPasswordQuery setObject:(__bridge id)kSecClassGenericPassword forKey:(__bridge id)kSecClass]; 
    [genericPasswordQuery setObject:identifier forKey:(__bridge id)kSecAttrGeneric]; 

    // The keychain access group attribute determines if this item can be shared 
    // amongst multiple apps whose code signing entitlements contain the same keychain access group. 
    if (accessGroup != nil) 
    { 
#if TARGET_IPHONE_SIMULATOR 
     // Ignore the access group if running on the iPhone simulator. 
     // 
     // Apps that are built for the simulator aren't signed, so there's no keychain access group 
     // for the simulator to check. This means that all apps can see all keychain items when run 
     // on the simulator. 
     // 
     // If a SecItem contains an access group attribute, SecItemAdd and SecItemUpdate on the 
     // simulator will return -25243 (errSecNoAccessForItem). 
#else 
     [genericPasswordQuery setObject:accessGroup forKey:(__bridge id)kSecAttrAccessGroup]; 
#endif 
    } 

    // Use the proper search constants, return only the attributes of the first match. 
    [genericPasswordQuery setObject:[NSNumber numberWithBool:YES] forKey:(__bridge id)kSecMatchLimitOne]; 
    [genericPasswordQuery setObject:[NSNumber numberWithBool:YES] forKey:(__bridge id)kSecReturnAttributes]; 

    NSDictionary *tempQuery = [NSDictionary dictionaryWithDictionary:genericPasswordQuery]; 

    CFMutableDictionaryRef outDictionary = NULL; 

    if (!SecItemCopyMatching((__bridge CFDictionaryRef)tempQuery, (CFTypeRef *)&outDictionary) == noErr) 
    { 
     // Stick these default values into keychain item if nothing found. 
     [self resetKeychainItem]; 

     // Add the generic attribute and the keychain access group. 
     [keychainItemData setObject:identifier forKey:(__bridge id)kSecAttrGeneric]; 
     if (accessGroup != nil) 
     { 
#if TARGET_IPHONE_SIMULATOR 
      // Ignore the access group if running on the iPhone simulator. 
      // 
      // Apps that are built for the simulator aren't signed, so there's no keychain access group 
      // for the simulator to check. This means that all apps can see all keychain items when run 
      // on the simulator. 
      // 
      // If a SecItem contains an access group attribute, SecItemAdd and SecItemUpdate on the 
      // simulator will return -25243 (errSecNoAccessForItem). 
#else 
      [keychainItemData setObject:accessGroup forKey:(__bridge id)kSecAttrAccessGroup]; 
#endif 
     } 
    } 
    else 
    { 
     // load the saved data from Keychain. 
     keychainItemData = [self secItemFormatToDictionary:(__bridge NSDictionary *)outDictionary]; 
    } 
    if(outDictionary) CFRelease(outDictionary); 
} 

- (void)setObject:(id)inObject forKey:(id)key 
{ 
    if (inObject == nil) return; 
    id currentObject = [keychainItemData objectForKey:key]; 
    if (![currentObject isEqual:inObject]) 
    { 
     [keychainItemData setObject:inObject forKey:key]; 
     [self writeToKeychain]; 
    } 
} 

- (id)objectForKey:(id)key 
{ 
    return [keychainItemData objectForKey:key]; 
} 

- (void)resetKeychainItem 
{ 
    OSStatus junk = noErr; 
    if (!keychainItemData) 
    { 
     keychainItemData = [[NSMutableDictionary alloc] init]; 
    } 
    else if (keychainItemData) 
    { 
     NSMutableDictionary *tempDictionary = [self dictionaryToSecItemFormat:keychainItemData]; 
     junk = SecItemDelete((__bridge CFDictionaryRef)tempDictionary); 
     NSAssert(junk == noErr || junk == errSecItemNotFound, @"Problem deleting current dictionary."); 
    } 
    // Default attributes for keychain item. 
    [keychainItemData setObject:@"" forKey:(__bridge id)kSecAttrAccount]; 
    [keychainItemData setObject:@"" forKey:(__bridge id)kSecAttrLabel]; 
    [keychainItemData setObject:@"" forKey:(__bridge id)kSecAttrDescription]; 
    // Default data for keychain item. 
    [keychainItemData setObject:@"" forKey:(__bridge id)kSecValueData]; 
// [keychainItemData setObject:[NSData data] forKey:(__bridge id)kSecValueData]; 
} 

- (NSMutableDictionary *)dictionaryToSecItemFormat:(NSDictionary *)dictionaryToConvert 
{ 
    // The assumption is that this method will be called with a properly populated dictionary 
    // containing all the right key/value pairs for a SecItem. 
    // Create a dictionary to return populated with the attributes and data. 
    NSMutableDictionary *returnDictionary = [NSMutableDictionary dictionaryWithDictionary:dictionaryToConvert]; 

    // Add the Generic Password keychain item class attribute. 
    [returnDictionary setObject:(__bridge id)kSecClassGenericPassword forKey:(__bridge id)kSecClass]; 

    // Convert the NSString to NSData to meet the requirements for the value type kSecValueData. 
    // This is where to store sensitive data that should be encrypted. 
    NSString *passwordString = [dictionaryToConvert objectForKey:(__bridge id)kSecValueData]; 
    [returnDictionary setObject:[passwordString dataUsingEncoding:NSUTF8StringEncoding] forKey:(__bridge id)kSecValueData]; 
    return returnDictionary; 
} 

- (NSMutableDictionary *)secItemFormatToDictionary:(NSDictionary *)dictionaryToConvert 
{ 
    // The assumption is that this method will be called with a properly populated dictionary 
    // containing all the right key/value pairs for the UI element. 

    // Create a dictionary to return populated with the attributes and data. 
    NSMutableDictionary *returnDictionary = [NSMutableDictionary dictionaryWithDictionary:dictionaryToConvert]; 

    // Add the proper search key and class attribute. 
    [returnDictionary setObject:(__bridge id)kCFBooleanTrue forKey:(__bridge id)kSecReturnData]; 
    [returnDictionary setObject:(__bridge id)kSecClassGenericPassword forKey:(__bridge id)kSecClass]; 

    // Acquire the password data from the attributes. 
    CFDataRef passwordData = NULL; 
    if (SecItemCopyMatching((__bridge CFDictionaryRef)returnDictionary, (CFTypeRef *)&passwordData) == noErr) 
    { 
     // Remove the search, class, and identifier key/value, we don't need them anymore. 
     [returnDictionary removeObjectForKey:(__bridge id)kSecReturnData]; 

     // Add the password to the dictionary, converting from NSData to NSString. 
     NSString *password = [[NSString alloc] initWithBytes:[(__bridge NSData *)passwordData bytes] length:[(__bridge NSData *)passwordData length] 
                encoding:NSUTF8StringEncoding]; 
     [returnDictionary setObject:password forKey:(__bridge id)kSecValueData]; 
    } 
    else 
    { 
     // Don't do anything if nothing is found. 
     NSAssert(NO, @"Serious error, no matching item found in the keychain.\n"); 
    } 
    if(passwordData) CFRelease(passwordData); 

    return returnDictionary; 
} 

- (void)writeToKeychain 
{ 
    CFDictionaryRef attributes = NULL; 
    NSMutableDictionary *updateItem = nil; 
    OSStatus result; 

    if (SecItemCopyMatching((__bridge CFDictionaryRef)genericPasswordQuery, (CFTypeRef *)&attributes) == noErr) 
    { 
     // First we need the attributes from the Keychain. 
     updateItem = [NSMutableDictionary dictionaryWithDictionary:(__bridge NSDictionary *)attributes]; 
     // Second we need to add the appropriate search key/values. 
     [updateItem setObject:[genericPasswordQuery objectForKey:(__bridge id)kSecClass] forKey:(__bridge id)kSecClass]; 

     // Lastly, we need to set up the updated attribute list being careful to remove the class. 
     NSMutableDictionary *tempCheck = [self dictionaryToSecItemFormat:keychainItemData]; 
     [tempCheck removeObjectForKey:(__bridge id)kSecClass]; 

#if TARGET_IPHONE_SIMULATOR 
     // Remove the access group if running on the iPhone simulator. 
     // 
     // Apps that are built for the simulator aren't signed, so there's no keychain access group 
     // for the simulator to check. This means that all apps can see all keychain items when run 
     // on the simulator. 
     // 
     // If a SecItem contains an access group attribute, SecItemAdd and SecItemUpdate on the 
     // simulator will return -25243 (errSecNoAccessForItem). 
     // 
     // The access group attribute will be included in items returned by SecItemCopyMatching, 
     // which is why we need to remove it before updating the item. 
     [tempCheck removeObjectForKey:(__bridge id)kSecAttrAccessGroup]; 
#endif 

     // An implicit assumption is that you can only update a single item at a time. 

     result = SecItemUpdate((__bridge CFDictionaryRef)updateItem, (__bridge CFDictionaryRef)tempCheck); 
     NSAssert(result == noErr, @"Couldn't update the Keychain Item."); 
    } 
    else 
    { 
     // No previous item found; add the new one. 
     result = SecItemAdd((__bridge CFDictionaryRef)[self dictionaryToSecItemFormat:keychainItemData], NULL); 
     NSAssert(result == noErr, @"Couldn't add the Keychain Item."); 
    } 

    if(attributes) CFRelease(attributes); 
} 

@end 

的代碼,而使用這個..

KeychainItemWrapper *secClientIDMapping = [KeychainItemWrapper sharedInstance]; 
    [secClientIDMapping createKeychainItemWithIdentifier:@"com.xxx.ClientID" accessGroup:nil]; 

    NSString *error; 

    NSData *dictionaryRep = [NSPropertyListSerialization dataFromPropertyList:clientIDMapping format:NSPropertyListXMLFormat_v1_0 errorDescription:&error]; 

    [secClientIDMapping setObject:dictionaryRep forKey:@"com.xxx.ClientID"]; 

無法從早晨解決這個問題。基本上,我得到如何存儲一個字符串,但不是一個字典對象。

回答

3

由於沒有人回答這個問題,我自己發佈它,因爲我找到了它的方式。當添加到鑰匙串時,如果我們傳遞的是除NSData之外的NSString,我們需要在添加到鑰匙串之前序列化數據。

下面是答案..

NSDictionary *secTokenDic = [dictionaryToConvert objectForKey:(__bridge id)kSecValueData]; 
     NSString *error; 

NSData *dictionaryRep = [NSPropertyListSerialization dataFromPropertyList:secTokenDic format:NSPropertyListXMLFormat_v1_0 errorDescription:&error]; 

[returnDictionary setObject:dictionaryRep forKey:(__bridge id)kSecValueData]; 

而且同樣檢索需要反序列化NSData的時候。希望這可以幫助別人解決類似的問題。

相關問題