From f216e72bbad5a5a0b2eff4068f2cae81f0d25e64 Mon Sep 17 00:00:00 2001 From: Josh Date: Tue, 27 Mar 2012 12:35:12 -0400 Subject: [PATCH 1/2] Added back-off persistence to device keychain to handle the case when all OpenUDID apps are uninstalled --- KeychainItemWrapper.h | 74 ++++++++++ KeychainItemWrapper.m | 313 ++++++++++++++++++++++++++++++++++++++++++ OpenUDID.h | 1 + OpenUDID.m | 59 +++++++- 4 files changed, 440 insertions(+), 7 deletions(-) create mode 100644 KeychainItemWrapper.h create mode 100644 KeychainItemWrapper.m mode change 100644 => 100755 OpenUDID.h mode change 100644 => 100755 OpenUDID.m diff --git a/KeychainItemWrapper.h b/KeychainItemWrapper.h new file mode 100644 index 0000000..d044364 --- /dev/null +++ b/KeychainItemWrapper.h @@ -0,0 +1,74 @@ +/* + File: KeychainItemWrapper.h + Abstract: + Objective-C wrapper for accessing a single keychain item. + + Version: 1.2 + + Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple + Inc. ("Apple") in consideration of your agreement to the following + terms, and your use, installation, modification or redistribution of + this Apple software constitutes acceptance of these terms. If you do + not agree with these terms, please do not use, install, modify or + redistribute this Apple software. + + In consideration of your agreement to abide by the following terms, and + subject to these terms, Apple grants you a personal, non-exclusive + license, under Apple's copyrights in this original Apple software (the + "Apple Software"), to use, reproduce, modify and redistribute the Apple + Software, with or without modifications, in source and/or binary forms; + provided that if you redistribute the Apple Software in its entirety and + without modifications, you must retain this notice and the following + text and disclaimers in all such redistributions of the Apple Software. + Neither the name, trademarks, service marks or logos of Apple Inc. may + be used to endorse or promote products derived from the Apple Software + without specific prior written permission from Apple. Except as + expressly stated in this notice, no other rights or licenses, express or + implied, are granted by Apple herein, including but not limited to any + patent rights that may be infringed by your derivative works or by other + works in which the Apple Software may be incorporated. + + The Apple Software is provided by Apple on an "AS IS" basis. APPLE + MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION + THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS + FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND + OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. + + IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL + OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, + MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED + AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), + STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. + + Copyright (C) 2010 Apple Inc. All Rights Reserved. + + */ + +#import + +/* + The KeychainItemWrapper class is an abstraction layer for the iPhone Keychain communication. It is merely a + simple wrapper to provide a distinct barrier between all the idiosyncracies involved with the Keychain + CF/NS container objects. + */ +@interface KeychainItemWrapper : NSObject +{ + NSMutableDictionary *keychainItemData; // The actual keychain item data backing store. + NSMutableDictionary *genericPasswordQuery; // A placeholder for the generic keychain item query used to locate the item. +} + +@property (nonatomic, retain) NSMutableDictionary *keychainItemData; +@property (nonatomic, retain) NSMutableDictionary *genericPasswordQuery; + +// Designated initializer. +- (id)initWithIdentifier: (NSString *)identifier accessGroup:(NSString *) accessGroup; +- (void)setObject:(id)inObject forKey:(id)key; +- (id)objectForKey:(id)key; + +// Initializes and resets the default generic keychain item data. +- (void)resetKeychainItem; + +@end \ No newline at end of file diff --git a/KeychainItemWrapper.m b/KeychainItemWrapper.m new file mode 100644 index 0000000..6851e7c --- /dev/null +++ b/KeychainItemWrapper.m @@ -0,0 +1,313 @@ +/* + File: KeychainItemWrapper.m + Abstract: + Objective-C wrapper for accessing a single keychain item. + + Version: 1.2 + + Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple + Inc. ("Apple") in consideration of your agreement to the following + terms, and your use, installation, modification or redistribution of + this Apple software constitutes acceptance of these terms. If you do + not agree with these terms, please do not use, install, modify or + redistribute this Apple software. + + In consideration of your agreement to abide by the following terms, and + subject to these terms, Apple grants you a personal, non-exclusive + license, under Apple's copyrights in this original Apple software (the + "Apple Software"), to use, reproduce, modify and redistribute the Apple + Software, with or without modifications, in source and/or binary forms; + provided that if you redistribute the Apple Software in its entirety and + without modifications, you must retain this notice and the following + text and disclaimers in all such redistributions of the Apple Software. + Neither the name, trademarks, service marks or logos of Apple Inc. may + be used to endorse or promote products derived from the Apple Software + without specific prior written permission from Apple. Except as + expressly stated in this notice, no other rights or licenses, express or + implied, are granted by Apple herein, including but not limited to any + patent rights that may be infringed by your derivative works or by other + works in which the Apple Software may be incorporated. + + The Apple Software is provided by Apple on an "AS IS" basis. APPLE + MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION + THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS + FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND + OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. + + IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL + OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, + MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED + AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), + STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. + + Copyright (C) 2010 Apple Inc. All Rights Reserved. + + */ + +#import "KeychainItemWrapper.h" +#import + +/* + + These are the default constants and their respective types, + available for the kSecClassGenericPassword Keychain Item class: + + kSecAttrAccessGroup - CFStringRef + kSecAttrCreationDate - CFDateRef + kSecAttrModificationDate - CFDateRef + kSecAttrDescription - CFStringRef + kSecAttrComment - CFStringRef + kSecAttrCreator - CFNumberRef + kSecAttrType - CFNumberRef + kSecAttrLabel - CFStringRef + kSecAttrIsInvisible - CFBooleanRef + kSecAttrIsNegative - CFBooleanRef + kSecAttrAccount - CFStringRef + kSecAttrService - CFStringRef + kSecAttrGeneric - CFDataRef + + See the header file Security/SecItem.h for more details. + + */ + +@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 + +@synthesize keychainItemData, genericPasswordQuery; + +- (id)initWithIdentifier: (NSString *)identifier accessGroup:(NSString *) accessGroup; +{ + if (self = [super init]) + { + // 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:(id)kSecClassGenericPassword forKey:(id)kSecClass]; + [genericPasswordQuery setObject:identifier forKey:(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:(id)kSecAttrAccessGroup]; +#endif + } + + // Use the proper search constants, return only the attributes of the first match. + [genericPasswordQuery setObject:(id)kSecMatchLimitOne forKey:(id)kSecMatchLimit]; + [genericPasswordQuery setObject:(id)kCFBooleanTrue forKey:(id)kSecReturnAttributes]; + + NSDictionary *tempQuery = [NSDictionary dictionaryWithDictionary:genericPasswordQuery]; + + NSMutableDictionary *outDictionary = nil; + + if (! SecItemCopyMatching((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:(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:(id)kSecAttrAccessGroup]; +#endif + } + } + else + { + // load the saved data from Keychain. + self.keychainItemData = [self secItemFormatToDictionary:outDictionary]; + } + + [outDictionary release]; + } + + return self; +} + +- (void)dealloc +{ + [keychainItemData release]; + [genericPasswordQuery release]; + + [super dealloc]; +} + +- (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) + { + self.keychainItemData = [[NSMutableDictionary alloc] init]; + } + else if (keychainItemData) + { + NSMutableDictionary *tempDictionary = [self dictionaryToSecItemFormat:keychainItemData]; + junk = SecItemDelete((CFDictionaryRef)tempDictionary); + NSAssert( junk == noErr || junk == errSecItemNotFound, @"Problem deleting current dictionary." ); + } + + // Default attributes for keychain item. + [keychainItemData setObject:@"" forKey:(id)kSecAttrAccount]; + [keychainItemData setObject:@"" forKey:(id)kSecAttrLabel]; + [keychainItemData setObject:@"" forKey:(id)kSecAttrDescription]; + + // Default data for keychain item. + [keychainItemData setObject:@"" forKey:(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:(id)kSecClassGenericPassword forKey:(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:(id)kSecValueData]; + [returnDictionary setObject:[passwordString dataUsingEncoding:NSUTF8StringEncoding] forKey:(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:(id)kCFBooleanTrue forKey:(id)kSecReturnData]; + [returnDictionary setObject:(id)kSecClassGenericPassword forKey:(id)kSecClass]; + + // Acquire the password data from the attributes. + NSData *passwordData = NULL; + if (SecItemCopyMatching((CFDictionaryRef)returnDictionary, (CFTypeRef *)&passwordData) == noErr) + { + // Remove the search, class, and identifier key/value, we don't need them anymore. + [returnDictionary removeObjectForKey:(id)kSecReturnData]; + + // Add the password to the dictionary, converting from NSData to NSString. + NSString *password = [[[NSString alloc] initWithBytes:[passwordData bytes] length:[passwordData length] + encoding:NSUTF8StringEncoding] autorelease]; + [returnDictionary setObject:password forKey:(id)kSecValueData]; + } + else + { + // Don't do anything if nothing is found. + NSAssert(NO, @"Serious error, no matching item found in the keychain.\n"); + } + + [passwordData release]; + + return returnDictionary; +} + +- (void)writeToKeychain +{ + NSDictionary *attributes = NULL; + NSMutableDictionary *updateItem = NULL; + OSStatus result; + + if (SecItemCopyMatching((CFDictionaryRef)genericPasswordQuery, (CFTypeRef *)&attributes) == noErr) + { + // First we need the attributes from the Keychain. + updateItem = [NSMutableDictionary dictionaryWithDictionary:attributes]; + // Second we need to add the appropriate search key/values. + [updateItem setObject:[genericPasswordQuery objectForKey:(id)kSecClass] forKey:(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:(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:(id)kSecAttrAccessGroup]; +#endif + + // An implicit assumption is that you can only update a single item at a time. + + result = SecItemUpdate((CFDictionaryRef)updateItem, (CFDictionaryRef)tempCheck); + NSAssert( result == noErr, @"Couldn't update the Keychain Item." ); + } + else + { + // No previous item found; add the new one. + result = SecItemAdd((CFDictionaryRef)[self dictionaryToSecItemFormat:keychainItemData], NULL); + NSAssert( result == noErr, @"Couldn't add the Keychain Item." ); + } +} + +@end \ No newline at end of file diff --git a/OpenUDID.h b/OpenUDID.h old mode 100644 new mode 100755 index 26182d4..3c4ccff --- a/OpenUDID.h +++ b/OpenUDID.h @@ -44,6 +44,7 @@ #define kOpenUDIDErrorNone 0 #define kOpenUDIDErrorOptedOut 1 #define kOpenUDIDErrorCompromised 2 +#define kOpenUDIDErrorMultiple 3 @interface OpenUDID : NSObject { } diff --git a/OpenUDID.m b/OpenUDID.m old mode 100644 new mode 100755 index 4b4f7a1..455844c --- a/OpenUDID.m +++ b/OpenUDID.m @@ -35,6 +35,8 @@ this software and associated documentation files (the "Software"), to deal in #import "OpenUDID.h" #import // Need to import for CC_MD5 access +#import "KeychainItemWrapper.h" + #if TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR #import #import @@ -55,6 +57,7 @@ this software and associated documentation files (the "Software"), to deal in static NSString * const kOpenUDIDDomain = @"org.OpenUDID"; static NSString * const kOpenUDIDSlotPBPrefix = @"org.OpenUDID.slot."; static int const kOpenUDIDRedundancySlots = 100; +static KeychainItemWrapper* keychain = nil; @interface OpenUDID (Private) + (void) _setDict:(id)dict forPasteboard:(id)pboard; @@ -170,6 +173,7 @@ + (NSString*) valueWithError:(NSError **)error { BOOL optedOut = NO; BOOL saveLocalDictToDefaults = NO; BOOL isCompromised = NO; + BOOL isMultiples = NO; // Do we have a local copy of the OpenUDID dictionary? // This local copy contains a copy of the openUDID, myRedundancySlotPBid (and unused in this block, the local bundleid, and the timestamp) @@ -226,19 +230,52 @@ + (NSString*) valueWithError:(NSError **)error { NSArray* arrayOfUDIDs = [frequencyDict keysSortedByValueUsingSelector:@selector(compare:)]; NSString* mostReliableOpenUDID = (arrayOfUDIDs!=nil && [arrayOfUDIDs count]>0)? [arrayOfUDIDs lastObject] : nil; OpenUDIDLog(@"Freq Dict = %@\nMost reliable %@",frequencyDict,mostReliableOpenUDID); - + + if( keychain == nil ) + keychain = [[KeychainItemWrapper alloc]initWithIdentifier:kOpenUDIDKey accessGroup:nil]; + + NSString* keyChainUDID = [keychain objectForKey:(id)kSecValueData]; + OpenUDIDLog(@"Keychain UDID = %@", keyChainUDID); + // if openUDID was not retrieved from the local preferences, then let's try to get it from the frequency dictionary above // if (openUDID==nil) { if (mostReliableOpenUDID==nil) { - // this is the case where this app instance is likely to be the first one to use OpenUDID on this device - // we create the OpenUDID, legacy or semi-random (i.e. most certainly unique) + // This is the case where this app instance is likely to be the first one to use OpenUDID on this device. + // First, check to see if THIS app has created an OpenUDID previously and stored it into the keychain. + // If not, we create the new OpenUDID, legacy or semi-random (i.e. most certainly unique) // - openUDID = [OpenUDID _getOpenUDID]; + openUDID = keyChainUDID; + if( [openUDID length] == 0 ) + openUDID = [OpenUDID _getOpenUDID]; + } else { - // or we leverage the OpenUDID shared by other apps that have already gone through the process - // - openUDID = mostReliableOpenUDID; + // Or we leverage the OpenUDID shared by other apps that have already gone through the process + // HOWEVER, if the keychain stored UDID is present, favor that value for consistency across + // application installs in THIS app and mark the value as multiples (for now) + // NOTE: In future, we need to come up with a way to handle this case more elegantly... how to sync + // these values when a new OpenUDID is generated between existing application installs? + // + if( [mostReliableOpenUDID isEqualToString:keyChainUDID] && [keyChainUDID length] > 0 ) + { + // Equal and life is good. + openUDID = mostReliableOpenUDID; + } + else if( [keyChainUDID length] > 0 ) + { + // Keychain value and stored value are different. This could occur if this app was originally installed + // and then removed. Before this app was reinstalled, a new OpenUDID enabled app created a new value. + // Now this app is reinstalled. For consistency in THIS app, deferring to the keychain value. In future + // Need to find some way to handle this case more elegantly. + openUDID = keyChainUDID; + isMultiples = YES; + } + else + { + // Don't have a keychain value yet... so this is the first time this app has been installed + // Defer to the previously saved OpenUDID and store it in the keychain for this app. + openUDID = mostReliableOpenUDID; + } } // then we create a local representation // @@ -286,6 +323,10 @@ + (NSString*) valueWithError:(NSError **)error { // if (localDict && saveLocalDictToDefaults) [defaults setObject:localDict forKey:kOpenUDIDKey]; + + // Save the OpenUDID to the keychain if needed + if( [[keychain objectForKey:(id)kSecValueData]length] == 0 ) + [keychain setObject:openUDID forKey:(id)kSecValueData]; // If the UIPasteboard externa representation marks this app as opted-out, then to respect privacy, we return the ZERO OpenUDID, a sequence of 40 zeros... // This is a *new* case that developers have to deal with. Unlikely, statistically low, but still. @@ -308,6 +349,10 @@ + (NSString*) valueWithError:(NSError **)error { *error = [NSError errorWithDomain:kOpenUDIDDomain code:kOpenUDIDErrorCompromised userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Found a discrepancy between stored OpenUDID (reliable) and redundant copies; one of the apps on the device is most likely corrupting the OpenUDID protocol",@"description", nil]]; + else if( isMultiples ) + *error = [NSError errorWithDomain:kOpenUDIDDomain + code:kOpenUDIDErrorMultiple + userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Found multiple OpenUDID values (keychain vs cross-application stores). This occurs when all OpenUDID-enabled apps are removed, a new one is installed, and then a previous app is reinstalled",@"description", nil]]; else *error = [NSError errorWithDomain:kOpenUDIDDomain code:kOpenUDIDErrorNone From 1639eef2950562aab21202735944ce13395a4332 Mon Sep 17 00:00:00 2001 From: Josh Date: Thu, 29 Mar 2012 12:31:34 -0400 Subject: [PATCH 2/2] Merged Apple's KeychainItemWrapper into OpenUDID source files. --- KeychainItemWrapper.h | 74 --------- KeychainItemWrapper.m | 313 ------------------------------------- OpenUDID.m | 351 +++++++++++++++++++++++++++++++++++++++++- 3 files changed, 350 insertions(+), 388 deletions(-) delete mode 100644 KeychainItemWrapper.h delete mode 100644 KeychainItemWrapper.m diff --git a/KeychainItemWrapper.h b/KeychainItemWrapper.h deleted file mode 100644 index d044364..0000000 --- a/KeychainItemWrapper.h +++ /dev/null @@ -1,74 +0,0 @@ -/* - File: KeychainItemWrapper.h - Abstract: - Objective-C wrapper for accessing a single keychain item. - - Version: 1.2 - - Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple - Inc. ("Apple") in consideration of your agreement to the following - terms, and your use, installation, modification or redistribution of - this Apple software constitutes acceptance of these terms. If you do - not agree with these terms, please do not use, install, modify or - redistribute this Apple software. - - In consideration of your agreement to abide by the following terms, and - subject to these terms, Apple grants you a personal, non-exclusive - license, under Apple's copyrights in this original Apple software (the - "Apple Software"), to use, reproduce, modify and redistribute the Apple - Software, with or without modifications, in source and/or binary forms; - provided that if you redistribute the Apple Software in its entirety and - without modifications, you must retain this notice and the following - text and disclaimers in all such redistributions of the Apple Software. - Neither the name, trademarks, service marks or logos of Apple Inc. may - be used to endorse or promote products derived from the Apple Software - without specific prior written permission from Apple. Except as - expressly stated in this notice, no other rights or licenses, express or - implied, are granted by Apple herein, including but not limited to any - patent rights that may be infringed by your derivative works or by other - works in which the Apple Software may be incorporated. - - The Apple Software is provided by Apple on an "AS IS" basis. APPLE - MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION - THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS - FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND - OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. - - IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL - OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, - MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED - AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), - STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE - POSSIBILITY OF SUCH DAMAGE. - - Copyright (C) 2010 Apple Inc. All Rights Reserved. - - */ - -#import - -/* - The KeychainItemWrapper class is an abstraction layer for the iPhone Keychain communication. It is merely a - simple wrapper to provide a distinct barrier between all the idiosyncracies involved with the Keychain - CF/NS container objects. - */ -@interface KeychainItemWrapper : NSObject -{ - NSMutableDictionary *keychainItemData; // The actual keychain item data backing store. - NSMutableDictionary *genericPasswordQuery; // A placeholder for the generic keychain item query used to locate the item. -} - -@property (nonatomic, retain) NSMutableDictionary *keychainItemData; -@property (nonatomic, retain) NSMutableDictionary *genericPasswordQuery; - -// Designated initializer. -- (id)initWithIdentifier: (NSString *)identifier accessGroup:(NSString *) accessGroup; -- (void)setObject:(id)inObject forKey:(id)key; -- (id)objectForKey:(id)key; - -// Initializes and resets the default generic keychain item data. -- (void)resetKeychainItem; - -@end \ No newline at end of file diff --git a/KeychainItemWrapper.m b/KeychainItemWrapper.m deleted file mode 100644 index 6851e7c..0000000 --- a/KeychainItemWrapper.m +++ /dev/null @@ -1,313 +0,0 @@ -/* - File: KeychainItemWrapper.m - Abstract: - Objective-C wrapper for accessing a single keychain item. - - Version: 1.2 - - Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple - Inc. ("Apple") in consideration of your agreement to the following - terms, and your use, installation, modification or redistribution of - this Apple software constitutes acceptance of these terms. If you do - not agree with these terms, please do not use, install, modify or - redistribute this Apple software. - - In consideration of your agreement to abide by the following terms, and - subject to these terms, Apple grants you a personal, non-exclusive - license, under Apple's copyrights in this original Apple software (the - "Apple Software"), to use, reproduce, modify and redistribute the Apple - Software, with or without modifications, in source and/or binary forms; - provided that if you redistribute the Apple Software in its entirety and - without modifications, you must retain this notice and the following - text and disclaimers in all such redistributions of the Apple Software. - Neither the name, trademarks, service marks or logos of Apple Inc. may - be used to endorse or promote products derived from the Apple Software - without specific prior written permission from Apple. Except as - expressly stated in this notice, no other rights or licenses, express or - implied, are granted by Apple herein, including but not limited to any - patent rights that may be infringed by your derivative works or by other - works in which the Apple Software may be incorporated. - - The Apple Software is provided by Apple on an "AS IS" basis. APPLE - MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION - THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS - FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND - OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. - - IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL - OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, - MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED - AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), - STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE - POSSIBILITY OF SUCH DAMAGE. - - Copyright (C) 2010 Apple Inc. All Rights Reserved. - - */ - -#import "KeychainItemWrapper.h" -#import - -/* - - These are the default constants and their respective types, - available for the kSecClassGenericPassword Keychain Item class: - - kSecAttrAccessGroup - CFStringRef - kSecAttrCreationDate - CFDateRef - kSecAttrModificationDate - CFDateRef - kSecAttrDescription - CFStringRef - kSecAttrComment - CFStringRef - kSecAttrCreator - CFNumberRef - kSecAttrType - CFNumberRef - kSecAttrLabel - CFStringRef - kSecAttrIsInvisible - CFBooleanRef - kSecAttrIsNegative - CFBooleanRef - kSecAttrAccount - CFStringRef - kSecAttrService - CFStringRef - kSecAttrGeneric - CFDataRef - - See the header file Security/SecItem.h for more details. - - */ - -@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 - -@synthesize keychainItemData, genericPasswordQuery; - -- (id)initWithIdentifier: (NSString *)identifier accessGroup:(NSString *) accessGroup; -{ - if (self = [super init]) - { - // 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:(id)kSecClassGenericPassword forKey:(id)kSecClass]; - [genericPasswordQuery setObject:identifier forKey:(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:(id)kSecAttrAccessGroup]; -#endif - } - - // Use the proper search constants, return only the attributes of the first match. - [genericPasswordQuery setObject:(id)kSecMatchLimitOne forKey:(id)kSecMatchLimit]; - [genericPasswordQuery setObject:(id)kCFBooleanTrue forKey:(id)kSecReturnAttributes]; - - NSDictionary *tempQuery = [NSDictionary dictionaryWithDictionary:genericPasswordQuery]; - - NSMutableDictionary *outDictionary = nil; - - if (! SecItemCopyMatching((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:(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:(id)kSecAttrAccessGroup]; -#endif - } - } - else - { - // load the saved data from Keychain. - self.keychainItemData = [self secItemFormatToDictionary:outDictionary]; - } - - [outDictionary release]; - } - - return self; -} - -- (void)dealloc -{ - [keychainItemData release]; - [genericPasswordQuery release]; - - [super dealloc]; -} - -- (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) - { - self.keychainItemData = [[NSMutableDictionary alloc] init]; - } - else if (keychainItemData) - { - NSMutableDictionary *tempDictionary = [self dictionaryToSecItemFormat:keychainItemData]; - junk = SecItemDelete((CFDictionaryRef)tempDictionary); - NSAssert( junk == noErr || junk == errSecItemNotFound, @"Problem deleting current dictionary." ); - } - - // Default attributes for keychain item. - [keychainItemData setObject:@"" forKey:(id)kSecAttrAccount]; - [keychainItemData setObject:@"" forKey:(id)kSecAttrLabel]; - [keychainItemData setObject:@"" forKey:(id)kSecAttrDescription]; - - // Default data for keychain item. - [keychainItemData setObject:@"" forKey:(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:(id)kSecClassGenericPassword forKey:(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:(id)kSecValueData]; - [returnDictionary setObject:[passwordString dataUsingEncoding:NSUTF8StringEncoding] forKey:(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:(id)kCFBooleanTrue forKey:(id)kSecReturnData]; - [returnDictionary setObject:(id)kSecClassGenericPassword forKey:(id)kSecClass]; - - // Acquire the password data from the attributes. - NSData *passwordData = NULL; - if (SecItemCopyMatching((CFDictionaryRef)returnDictionary, (CFTypeRef *)&passwordData) == noErr) - { - // Remove the search, class, and identifier key/value, we don't need them anymore. - [returnDictionary removeObjectForKey:(id)kSecReturnData]; - - // Add the password to the dictionary, converting from NSData to NSString. - NSString *password = [[[NSString alloc] initWithBytes:[passwordData bytes] length:[passwordData length] - encoding:NSUTF8StringEncoding] autorelease]; - [returnDictionary setObject:password forKey:(id)kSecValueData]; - } - else - { - // Don't do anything if nothing is found. - NSAssert(NO, @"Serious error, no matching item found in the keychain.\n"); - } - - [passwordData release]; - - return returnDictionary; -} - -- (void)writeToKeychain -{ - NSDictionary *attributes = NULL; - NSMutableDictionary *updateItem = NULL; - OSStatus result; - - if (SecItemCopyMatching((CFDictionaryRef)genericPasswordQuery, (CFTypeRef *)&attributes) == noErr) - { - // First we need the attributes from the Keychain. - updateItem = [NSMutableDictionary dictionaryWithDictionary:attributes]; - // Second we need to add the appropriate search key/values. - [updateItem setObject:[genericPasswordQuery objectForKey:(id)kSecClass] forKey:(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:(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:(id)kSecAttrAccessGroup]; -#endif - - // An implicit assumption is that you can only update a single item at a time. - - result = SecItemUpdate((CFDictionaryRef)updateItem, (CFDictionaryRef)tempCheck); - NSAssert( result == noErr, @"Couldn't update the Keychain Item." ); - } - else - { - // No previous item found; add the new one. - result = SecItemAdd((CFDictionaryRef)[self dictionaryToSecItemFormat:keychainItemData], NULL); - NSAssert( result == noErr, @"Couldn't add the Keychain Item." ); - } -} - -@end \ No newline at end of file diff --git a/OpenUDID.m b/OpenUDID.m index 455844c..512c2b2 100755 --- a/OpenUDID.m +++ b/OpenUDID.m @@ -33,9 +33,38 @@ this software and associated documentation files (the "Software"), to deal in SOFTWARE. */ +#pragma mark - +#pragma mark Apple KeychainItemWrapper declaration - Included inline + +/* + The KeychainItemWrapper class is an abstraction layer for the iPhone Keychain communication. It is merely a + simple wrapper to provide a distinct barrier between all the idiosyncracies involved with the Keychain + CF/NS container objects. + */ +@interface KeychainItemWrapper : NSObject +{ + NSMutableDictionary *keychainItemData; // The actual keychain item data backing store. + NSMutableDictionary *genericPasswordQuery; // A placeholder for the generic keychain item query used to locate the item. +} + +@property (nonatomic, retain) NSMutableDictionary *keychainItemData; +@property (nonatomic, retain) NSMutableDictionary *genericPasswordQuery; + +// Designated initializer. +- (id)initWithIdentifier: (NSString *)identifier accessGroup:(NSString *) accessGroup; +- (void)setObject:(id)inObject forKey:(id)key; +- (id)objectForKey:(id)key; + +// Initializes and resets the default generic keychain item data. +- (void)resetKeychainItem; + +@end + +#pragma mark - + + #import "OpenUDID.h" #import // Need to import for CC_MD5 access -#import "KeychainItemWrapper.h" #if TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR #import @@ -363,3 +392,323 @@ + (NSString*) valueWithError:(NSError **)error { } @end + +#pragma mark +#pragma mark Apple KeychainItemWrapper implementation + +/* + File: KeychainItemWrapper.m + Abstract: + Objective-C wrapper for accessing a single keychain item. + + Version: 1.2 + + Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple + Inc. ("Apple") in consideration of your agreement to the following + terms, and your use, installation, modification or redistribution of + this Apple software constitutes acceptance of these terms. If you do + not agree with these terms, please do not use, install, modify or + redistribute this Apple software. + + In consideration of your agreement to abide by the following terms, and + subject to these terms, Apple grants you a personal, non-exclusive + license, under Apple's copyrights in this original Apple software (the + "Apple Software"), to use, reproduce, modify and redistribute the Apple + Software, with or without modifications, in source and/or binary forms; + provided that if you redistribute the Apple Software in its entirety and + without modifications, you must retain this notice and the following + text and disclaimers in all such redistributions of the Apple Software. + Neither the name, trademarks, service marks or logos of Apple Inc. may + be used to endorse or promote products derived from the Apple Software + without specific prior written permission from Apple. Except as + expressly stated in this notice, no other rights or licenses, express or + implied, are granted by Apple herein, including but not limited to any + patent rights that may be infringed by your derivative works or by other + works in which the Apple Software may be incorporated. + + The Apple Software is provided by Apple on an "AS IS" basis. APPLE + MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION + THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS + FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND + OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. + + IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL + OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, + MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED + AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), + STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. + + Copyright (C) 2010 Apple Inc. All Rights Reserved. + + */ + +#import + +/* + + These are the default constants and their respective types, + available for the kSecClassGenericPassword Keychain Item class: + + kSecAttrAccessGroup - CFStringRef + kSecAttrCreationDate - CFDateRef + kSecAttrModificationDate - CFDateRef + kSecAttrDescription - CFStringRef + kSecAttrComment - CFStringRef + kSecAttrCreator - CFNumberRef + kSecAttrType - CFNumberRef + kSecAttrLabel - CFStringRef + kSecAttrIsInvisible - CFBooleanRef + kSecAttrIsNegative - CFBooleanRef + kSecAttrAccount - CFStringRef + kSecAttrService - CFStringRef + kSecAttrGeneric - CFDataRef + + See the header file Security/SecItem.h for more details. + + */ + +@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 + +@synthesize keychainItemData, genericPasswordQuery; + +- (id)initWithIdentifier: (NSString *)identifier accessGroup:(NSString *) accessGroup; +{ + if (self = [super init]) + { + // 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:(id)kSecClassGenericPassword forKey:(id)kSecClass]; + [genericPasswordQuery setObject:identifier forKey:(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:(id)kSecAttrAccessGroup]; +#endif + } + + // Use the proper search constants, return only the attributes of the first match. + [genericPasswordQuery setObject:(id)kSecMatchLimitOne forKey:(id)kSecMatchLimit]; + [genericPasswordQuery setObject:(id)kCFBooleanTrue forKey:(id)kSecReturnAttributes]; + + NSDictionary *tempQuery = [NSDictionary dictionaryWithDictionary:genericPasswordQuery]; + + NSMutableDictionary *outDictionary = nil; + + if (! SecItemCopyMatching((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:(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:(id)kSecAttrAccessGroup]; +#endif + } + } + else + { + // load the saved data from Keychain. + self.keychainItemData = [self secItemFormatToDictionary:outDictionary]; + } + + [outDictionary release]; + } + + return self; +} + +- (void)dealloc +{ + [keychainItemData release]; + [genericPasswordQuery release]; + + [super dealloc]; +} + +- (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) + { + self.keychainItemData = [[NSMutableDictionary alloc] init]; + } + else if (keychainItemData) + { + NSMutableDictionary *tempDictionary = [self dictionaryToSecItemFormat:keychainItemData]; + junk = SecItemDelete((CFDictionaryRef)tempDictionary); + NSAssert( junk == noErr || junk == errSecItemNotFound, @"Problem deleting current dictionary." ); + } + + // Default attributes for keychain item. + [keychainItemData setObject:@"" forKey:(id)kSecAttrAccount]; + [keychainItemData setObject:@"" forKey:(id)kSecAttrLabel]; + [keychainItemData setObject:@"" forKey:(id)kSecAttrDescription]; + + // Default data for keychain item. + [keychainItemData setObject:@"" forKey:(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:(id)kSecClassGenericPassword forKey:(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:(id)kSecValueData]; + [returnDictionary setObject:[passwordString dataUsingEncoding:NSUTF8StringEncoding] forKey:(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:(id)kCFBooleanTrue forKey:(id)kSecReturnData]; + [returnDictionary setObject:(id)kSecClassGenericPassword forKey:(id)kSecClass]; + + // Acquire the password data from the attributes. + NSData *passwordData = NULL; + if (SecItemCopyMatching((CFDictionaryRef)returnDictionary, (CFTypeRef *)&passwordData) == noErr) + { + // Remove the search, class, and identifier key/value, we don't need them anymore. + [returnDictionary removeObjectForKey:(id)kSecReturnData]; + + // Add the password to the dictionary, converting from NSData to NSString. + NSString *password = [[[NSString alloc] initWithBytes:[passwordData bytes] length:[passwordData length] + encoding:NSUTF8StringEncoding] autorelease]; + [returnDictionary setObject:password forKey:(id)kSecValueData]; + } + else + { + // Don't do anything if nothing is found. + NSAssert(NO, @"Serious error, no matching item found in the keychain.\n"); + } + + [passwordData release]; + + return returnDictionary; +} + +- (void)writeToKeychain +{ + NSDictionary *attributes = NULL; + NSMutableDictionary *updateItem = NULL; + OSStatus result; + + if (SecItemCopyMatching((CFDictionaryRef)genericPasswordQuery, (CFTypeRef *)&attributes) == noErr) + { + // First we need the attributes from the Keychain. + updateItem = [NSMutableDictionary dictionaryWithDictionary:attributes]; + // Second we need to add the appropriate search key/values. + [updateItem setObject:[genericPasswordQuery objectForKey:(id)kSecClass] forKey:(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:(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:(id)kSecAttrAccessGroup]; +#endif + + // An implicit assumption is that you can only update a single item at a time. + + result = SecItemUpdate((CFDictionaryRef)updateItem, (CFDictionaryRef)tempCheck); + NSAssert( result == noErr, @"Couldn't update the Keychain Item." ); + } + else + { + // No previous item found; add the new one. + result = SecItemAdd((CFDictionaryRef)[self dictionaryToSecItemFormat:keychainItemData], NULL); + NSAssert( result == noErr, @"Couldn't add the Keychain Item." ); + } +} + +@end + +#pragma mark - + +