KeychainItemWrapper.m 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314
  1. /*
  2. File: KeychainItemWrapper.m
  3. Abstract:
  4. Objective-C wrapper for accessing a single keychain item.
  5. Version: 1.2
  6. Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple
  7. Inc. ("Apple") in consideration of your agreement to the following
  8. terms, and your use, installation, modification or redistribution of
  9. this Apple software constitutes acceptance of these terms. If you do
  10. not agree with these terms, please do not use, install, modify or
  11. redistribute this Apple software.
  12. In consideration of your agreement to abide by the following terms, and
  13. subject to these terms, Apple grants you a personal, non-exclusive
  14. license, under Apple's copyrights in this original Apple software (the
  15. "Apple Software"), to use, reproduce, modify and redistribute the Apple
  16. Software, with or without modifications, in source and/or binary forms;
  17. provided that if you redistribute the Apple Software in its entirety and
  18. without modifications, you must retain this notice and the following
  19. text and disclaimers in all such redistributions of the Apple Software.
  20. Neither the name, trademarks, service marks or logos of Apple Inc. may
  21. be used to endorse or promote products derived from the Apple Software
  22. without specific prior written permission from Apple. Except as
  23. expressly stated in this notice, no other rights or licenses, express or
  24. implied, are granted by Apple herein, including but not limited to any
  25. patent rights that may be infringed by your derivative works or by other
  26. works in which the Apple Software may be incorporated.
  27. The Apple Software is provided by Apple on an "AS IS" basis. APPLE
  28. MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION
  29. THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS
  30. FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND
  31. OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS.
  32. IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL
  33. OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
  34. SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
  35. INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION,
  36. MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED
  37. AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE),
  38. STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE
  39. POSSIBILITY OF SUCH DAMAGE.
  40. Copyright (C) 2010 Apple Inc. All Rights Reserved.
  41. */
  42. #import "KeychainItemWrapper.h"
  43. #import <Security/Security.h>
  44. /*
  45. These are the default constants and their respective types,
  46. available for the kSecClassGenericPassword Keychain Item class:
  47. kSecAttrAccessGroup - CFStringRef
  48. kSecAttrCreationDate - CFDateRef
  49. kSecAttrModificationDate - CFDateRef
  50. kSecAttrDescription - CFStringRef
  51. kSecAttrComment - CFStringRef
  52. kSecAttrCreator - CFNumberRef
  53. kSecAttrType - CFNumberRef
  54. kSecAttrLabel - CFStringRef
  55. kSecAttrIsInvisible - CFBooleanRef
  56. kSecAttrIsNegative - CFBooleanRef
  57. kSecAttrAccount - CFStringRef
  58. kSecAttrService - CFStringRef
  59. kSecAttrGeneric - CFDataRef
  60. See the header file Security/SecItem.h for more details.
  61. */
  62. @interface KeychainItemWrapper (PrivateMethods)
  63. /*
  64. The decision behind the following two methods (secItemFormatToDictionary and dictionaryToSecItemFormat) was
  65. to encapsulate the transition between what the detail view controller was expecting (NSString *) and what the
  66. Keychain API expects as a validly constructed container class.
  67. */
  68. - (NSMutableDictionary *)secItemFormatToDictionary:(NSDictionary *)dictionaryToConvert;
  69. - (NSMutableDictionary *)dictionaryToSecItemFormat:(NSDictionary *)dictionaryToConvert;
  70. // Updates the item in the keychain, or adds it if it doesn't exist.
  71. - (void)writeToKeychain;
  72. @end
  73. @implementation KeychainItemWrapper
  74. @synthesize keychainItemData, genericPasswordQuery;
  75. - (id)initWithIdentifier: (NSString *)identifier accessGroup:(NSString *) accessGroup;
  76. {
  77. if (self = [super init])
  78. {
  79. // Begin Keychain search setup. The genericPasswordQuery leverages the special user
  80. // defined attribute kSecAttrGeneric to distinguish itself between other generic Keychain
  81. // items which may be included by the same application.
  82. genericPasswordQuery = [[NSMutableDictionary alloc] init];
  83. [genericPasswordQuery setObject:(id)kSecClassGenericPassword forKey:(id)kSecClass];
  84. [genericPasswordQuery setObject:identifier forKey:(id)kSecAttrGeneric];
  85. // The keychain access group attribute determines if this item can be shared
  86. // amongst multiple apps whose code signing entitlements contain the same keychain access group.
  87. if (accessGroup != nil)
  88. {
  89. #if TARGET_IPHONE_SIMULATOR
  90. // Ignore the access group if running on the iPhone simulator.
  91. //
  92. // Apps that are built for the simulator aren't signed, so there's no keychain access group
  93. // for the simulator to check. This means that all apps can see all keychain items when run
  94. // on the simulator.
  95. //
  96. // If a SecItem contains an access group attribute, SecItemAdd and SecItemUpdate on the
  97. // simulator will return -25243 (errSecNoAccessForItem).
  98. #else
  99. [genericPasswordQuery setObject:accessGroup forKey:(id)kSecAttrAccessGroup];
  100. #endif
  101. }
  102. // Use the proper search constants, return only the attributes of the first match.
  103. [genericPasswordQuery setObject:(id)kSecMatchLimitOne forKey:(id)kSecMatchLimit];
  104. [genericPasswordQuery setObject:(id)kCFBooleanTrue forKey:(id)kSecReturnAttributes];
  105. NSDictionary *tempQuery = [NSDictionary dictionaryWithDictionary:genericPasswordQuery];
  106. NSMutableDictionary *outDictionary = nil;
  107. if (! SecItemCopyMatching((CFDictionaryRef)tempQuery, (CFTypeRef *)&outDictionary) == noErr)
  108. {
  109. // Stick these default values into keychain item if nothing found.
  110. [self resetKeychainItem];
  111. // Add the generic attribute and the keychain access group.
  112. [keychainItemData setObject:identifier forKey:(id)kSecAttrGeneric];
  113. if (accessGroup != nil)
  114. {
  115. #if TARGET_IPHONE_SIMULATOR
  116. // Ignore the access group if running on the iPhone simulator.
  117. //
  118. // Apps that are built for the simulator aren't signed, so there's no keychain access group
  119. // for the simulator to check. This means that all apps can see all keychain items when run
  120. // on the simulator.
  121. //
  122. // If a SecItem contains an access group attribute, SecItemAdd and SecItemUpdate on the
  123. // simulator will return -25243 (errSecNoAccessForItem).
  124. #else
  125. [keychainItemData setObject:accessGroup forKey:(id)kSecAttrAccessGroup];
  126. #endif
  127. }
  128. }
  129. else
  130. {
  131. // load the saved data from Keychain.
  132. self.keychainItemData = [self secItemFormatToDictionary:outDictionary];
  133. }
  134. [outDictionary release];
  135. }
  136. return self;
  137. }
  138. - (void)dealloc
  139. {
  140. [keychainItemData release];
  141. [genericPasswordQuery release];
  142. [super dealloc];
  143. }
  144. - (void)setObject:(id)inObject forKey:(id)key
  145. {
  146. if (inObject == nil) return;
  147. id currentObject = [keychainItemData objectForKey:key];
  148. if (![currentObject isEqual:inObject])
  149. {
  150. [keychainItemData setObject:inObject forKey:key];
  151. [self writeToKeychain];
  152. }
  153. }
  154. - (id)objectForKey:(id)key
  155. {
  156. return [keychainItemData objectForKey:key];
  157. }
  158. - (void)resetKeychainItem
  159. {
  160. OSStatus junk = noErr;
  161. if (!keychainItemData)
  162. {
  163. self.keychainItemData = [[NSMutableDictionary alloc] init];
  164. }
  165. else if (keychainItemData)
  166. {
  167. NSMutableDictionary *tempDictionary = [self dictionaryToSecItemFormat:keychainItemData];
  168. junk = SecItemDelete((CFDictionaryRef)tempDictionary);
  169. NSAssert( junk == noErr || junk == errSecItemNotFound, @"Problem deleting current dictionary." );
  170. }
  171. // Default attributes for keychain item.
  172. [keychainItemData setObject:@"" forKey:(id)kSecAttrAccount];
  173. [keychainItemData setObject:@"" forKey:(id)kSecAttrLabel];
  174. [keychainItemData setObject:@"" forKey:(id)kSecAttrDescription];
  175. // Default data for keychain item.
  176. [keychainItemData setObject:@"" forKey:(id)kSecValueData];
  177. }
  178. - (NSMutableDictionary *)dictionaryToSecItemFormat:(NSDictionary *)dictionaryToConvert
  179. {
  180. // The assumption is that this method will be called with a properly populated dictionary
  181. // containing all the right key/value pairs for a SecItem.
  182. // Create a dictionary to return populated with the attributes and data.
  183. NSMutableDictionary *returnDictionary = [NSMutableDictionary dictionaryWithDictionary:dictionaryToConvert];
  184. // Add the Generic Password keychain item class attribute.
  185. [returnDictionary setObject:(id)kSecClassGenericPassword forKey:(id)kSecClass];
  186. // Convert the NSString to NSData to meet the requirements for the value type kSecValueData.
  187. // This is where to store sensitive data that should be encrypted.
  188. NSString *passwordString = [dictionaryToConvert objectForKey:(id)kSecValueData];
  189. [returnDictionary setObject:[passwordString dataUsingEncoding:NSUTF8StringEncoding] forKey:(id)kSecValueData];
  190. return returnDictionary;
  191. }
  192. - (NSMutableDictionary *)secItemFormatToDictionary:(NSDictionary *)dictionaryToConvert
  193. {
  194. // The assumption is that this method will be called with a properly populated dictionary
  195. // containing all the right key/value pairs for the UI element.
  196. // Create a dictionary to return populated with the attributes and data.
  197. NSMutableDictionary *returnDictionary = [NSMutableDictionary dictionaryWithDictionary:dictionaryToConvert];
  198. // Add the proper search key and class attribute.
  199. [returnDictionary setObject:(id)kCFBooleanTrue forKey:(id)kSecReturnData];
  200. [returnDictionary setObject:(id)kSecClassGenericPassword forKey:(id)kSecClass];
  201. // Acquire the password data from the attributes.
  202. NSData *passwordData = NULL;
  203. if (SecItemCopyMatching((CFDictionaryRef)returnDictionary, (CFTypeRef *)&passwordData) == noErr)
  204. {
  205. // Remove the search, class, and identifier key/value, we don't need them anymore.
  206. [returnDictionary removeObjectForKey:(id)kSecReturnData];
  207. // Add the password to the dictionary, converting from NSData to NSString.
  208. NSString *password = [[[NSString alloc] initWithBytes:[passwordData bytes] length:[passwordData length]
  209. encoding:NSUTF8StringEncoding] autorelease];
  210. [returnDictionary setObject:password forKey:(id)kSecValueData];
  211. }
  212. else
  213. {
  214. // Don't do anything if nothing is found.
  215. NSAssert(NO, @"Serious error, no matching item found in the keychain.\n");
  216. }
  217. [passwordData release];
  218. return returnDictionary;
  219. }
  220. - (void)writeToKeychain
  221. {
  222. NSDictionary *attributes = NULL;
  223. NSMutableDictionary *updateItem = NULL;
  224. OSStatus result;
  225. if (SecItemCopyMatching((CFDictionaryRef)genericPasswordQuery, (CFTypeRef *)&attributes) == noErr)
  226. {
  227. // First we need the attributes from the Keychain.
  228. updateItem = [NSMutableDictionary dictionaryWithDictionary:attributes];
  229. // Second we need to add the appropriate search key/values.
  230. [updateItem setObject:[genericPasswordQuery objectForKey:(id)kSecClass] forKey:(id)kSecClass];
  231. // Lastly, we need to set up the updated attribute list being careful to remove the class.
  232. NSMutableDictionary *tempCheck = [self dictionaryToSecItemFormat:keychainItemData];
  233. [tempCheck removeObjectForKey:(id)kSecClass];
  234. #if TARGET_IPHONE_SIMULATOR
  235. // Remove the access group if running on the iPhone simulator.
  236. //
  237. // Apps that are built for the simulator aren't signed, so there's no keychain access group
  238. // for the simulator to check. This means that all apps can see all keychain items when run
  239. // on the simulator.
  240. //
  241. // If a SecItem contains an access group attribute, SecItemAdd and SecItemUpdate on the
  242. // simulator will return -25243 (errSecNoAccessForItem).
  243. //
  244. // The access group attribute will be included in items returned by SecItemCopyMatching,
  245. // which is why we need to remove it before updating the item.
  246. [tempCheck removeObjectForKey:(id)kSecAttrAccessGroup];
  247. #endif
  248. // An implicit assumption is that you can only update a single item at a time.
  249. result = SecItemUpdate((CFDictionaryRef)updateItem, (CFDictionaryRef)tempCheck);
  250. NSAssert( result == noErr, @"Couldn't update the Keychain Item." );
  251. }
  252. else
  253. {
  254. // No previous item found; add the new one.
  255. result = SecItemAdd((CFDictionaryRef)[self dictionaryToSecItemFormat:keychainItemData], NULL);
  256. NSAssert( result == noErr, @"Couldn't add the Keychain Item." );
  257. }
  258. }
  259. @end