5 // Created by John Hurley on 10/3/12.
6 // Copyright (c) 2012 john. All rights reserved.
10 #import <Security/Security.h>
11 #import <Security/SecItemPriv.h>
12 #import <utilities/debugging.h>
14 const NSString *kItemPasswordKey = @"ItemPasswordKey";
15 const NSString *kItemAccountKey = @"ItemAccountKey";
16 const NSString *kItemNameKey = @"ItemNameKey";
18 #define KCSCOPE "mykeychain"
22 @implementation MyKeychain
24 static MyKeychain *sharedInstance = nil;
26 + (MyKeychain *) sharedInstance
29 sharedInstance = [[self alloc] init];
31 return sharedInstance;
34 // Translate status messages into return strings
35 - (NSString *) fetchStatus : (OSStatus) status
41 case errSecNotAvailable:
42 return(@"No trust results are available.!");
43 case errSecItemNotFound:
44 return(@"The item cannot be found.");
46 return(@"Parameter error.");
48 return(@"Memory allocation error. Failed to allocate memory.");
49 case errSecInteractionNotAllowed:
50 return(@"User interaction is not allowed.");
51 case errSecUnimplemented:
52 return(@"Function is not implemented");
53 case errSecDuplicateItem:
54 return(@"The item already exists.");
56 return(@"Unable to decode the provided data.");
59 return([NSString stringWithFormat:@"Function returned: %ld", (long)status]);
62 return @"can't happen...";
65 #define ACCOUNT @"Keychain Sync Test Account"
66 #define SERVICE @"Keychain Sync Test Service"
67 #define PWKEY @"Keychain Sync Test Password Data"
69 // Return a base dictionary
70 - (NSMutableDictionary *) baseDictionary
72 NSMutableDictionary *md = [[NSMutableDictionary alloc] init];
74 // Password identification keys
75 NSData *identifier = [PWKEY dataUsingEncoding:NSUTF8StringEncoding];
76 [md setObject:identifier forKey:(__bridge id)kSecAttrGeneric];
77 [md setObject:ACCOUNT forKey:(__bridge id)kSecAttrAccount];
78 [md setObject:SERVICE forKey:(__bridge id)kSecAttrService];
79 [md setObject:(__bridge id)kSecClassGenericPassword forKey:(__bridge id)kSecClass];
80 [md setObject:(__bridge id)kCFBooleanTrue forKey:(__bridge id)kSecAttrSynchronizable];
85 // Return a keychain-style dictionary populated with the password
86 - (NSMutableDictionary *) buildDictForPassword:(NSString *) password
88 NSMutableDictionary *passwordDict = [self baseDictionary];
91 NSData *passwordData = [password dataUsingEncoding:NSUTF8StringEncoding];
92 [passwordDict setObject:passwordData forKey:(__bridge id)kSecValueData]; // password
97 // Build a search query based
98 - (NSMutableDictionary *) buildSearchQuery
100 NSMutableDictionary *genericPasswordQuery = [self baseDictionary];
102 // Add the search constraints
103 [genericPasswordQuery setObject:(__bridge id)kSecMatchLimitOne forKey:(__bridge id)kSecMatchLimit];
104 [genericPasswordQuery setObject:(__bridge id)kCFBooleanTrue
105 forKey:(__bridge id)kSecReturnAttributes];
106 [genericPasswordQuery setObject:(__bridge id)kCFBooleanTrue
107 forKey:(__bridge id)kSecReturnData];
109 return genericPasswordQuery;
112 // retrieve data dictionary from the keychain
113 - (NSMutableArray *) fetchDictionaryWithQuery:(NSMutableDictionary *)query
115 NSMutableDictionary *genericPasswordQuery = query;
117 // secerror("Query: %@", query);
118 CFTypeRef cfresult = nil;
119 OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)genericPasswordQuery, &cfresult);
121 // secerror( "FETCH: %s\n", [[self fetchStatus:status] UTF8String]);
123 if (status == errSecItemNotFound)
126 if (CFGetTypeID(cfresult) == CFArrayGetTypeID())
128 NSMutableArray *result = [NSMutableArray arrayWithCapacity:0];
129 [result addObjectsFromArray:CFBridgingRelease(cfresult)];
133 // If it is a single result, embed it in an array because callers expect it
134 if (CFGetTypeID(cfresult) == CFDictionaryGetTypeID())
135 return [NSMutableArray arrayWithObject:CFBridgingRelease(cfresult)];
140 - (NSMutableArray *)fetchDictionary
142 return [self fetchDictionaryWithQuery:[self buildSearchQuery]];
145 // create a new keychain entry
146 - (BOOL) createKeychainValue:(NSString *) password
148 NSMutableDictionary *md = [self buildDictForPassword:password];
149 OSStatus status = SecItemAdd((__bridge CFDictionaryRef)md, NULL);
151 secerror( "CREATE: %s\n", [[self fetchStatus:status] UTF8String]);
153 if (status == errSecSuccess) return YES; else return NO;
156 // remove a keychain entry
157 - (void) clearKeychain
159 NSMutableDictionary *genericPasswordQuery = [self baseDictionary];
161 OSStatus status = SecItemDelete((__bridge CFDictionaryRef) genericPasswordQuery);
162 secerror( "DELETE: %s\n", [[self fetchStatus:status] UTF8String]);
165 // update a keychain entry
166 - (BOOL) updateKeychainValue:(NSString *)password
168 NSMutableDictionary *genericPasswordQuery = [self baseDictionary];
170 NSMutableDictionary *attributesToUpdate = [[NSMutableDictionary alloc] init];
171 NSData *passwordData = [password dataUsingEncoding:NSUTF8StringEncoding];
172 [attributesToUpdate setObject:passwordData forKey:(__bridge id)kSecValueData];
174 OSStatus status = SecItemUpdate((__bridge CFDictionaryRef)genericPasswordQuery, (__bridge CFDictionaryRef)attributesToUpdate);
175 secerror( "UPDATE: %s\n", [[self fetchStatus:status] UTF8String]);
177 if (status == 0) return YES; else return NO;
180 // fetch a keychain value
181 - (NSString *) fetchPassword
183 NSMutableArray *allItems = [self fetchDictionary];
187 // This is used for a single item, so take first item in array
188 NSData *passData = [allItems[0] objectForKey:(__bridge id)kSecValueData];
190 return [[NSString alloc] initWithData:passData encoding:NSUTF8StringEncoding];
195 - (void)setPassword: (NSString *) thePassword
197 if (![self createKeychainValue:thePassword])
198 [self updateKeychainValue:thePassword];
201 - (void)setItem:(NSDictionary *)newItem
203 OSStatus status = errSecSuccess;
204 NSMutableDictionary *passwordDict = [self baseDictionary];
207 NSData *passwordData = [[newItem objectForKey:kItemPasswordKey] dataUsingEncoding:NSUTF8StringEncoding];
208 [passwordDict setObject:passwordData forKey:(__bridge id)kSecValueData]; // password
210 [passwordDict setObject:[newItem objectForKey:kItemAccountKey] forKey:(__bridge id)kSecAttrAccount];
211 [passwordDict setObject:[newItem objectForKey:kItemNameKey] forKey:(__bridge id)kSecAttrService];
213 // Try to add first; if error, then try to update
214 status = SecItemAdd((__bridge CFDictionaryRef)passwordDict, NULL);
215 secerror( "SecItemAdd result: %@ (%ld)", [self fetchStatus:status], (long)status);
216 NSLog(@"SecItemAdd result: %@ (%ld)", [self fetchStatus:status], (long)status);
221 // We only want to update the password data, so delete the other keys
223 // NSArray *keysToRemove = [NSArray arrayWithObjects:kItemAccountKey, kItemNameKey, nil];
224 [passwordDict removeObjectsForKeys:@[(__bridge id)kSecValueData]];
226 NSDictionary *itemsToUpdate = @{ (__bridge id)kSecValueData : passwordData };
227 // [genericPasswordQuery setObject:[newItem objectForKey:kItemAccountKey] forKey:(__bridge id)kSecAttrAccount];
228 // [genericPasswordQuery setObject:[newItem objectForKey:kItemNameKey] forKey:(__bridge id)kSecAttrService];
229 status = SecItemUpdate((__bridge CFDictionaryRef)passwordDict, (__bridge CFDictionaryRef)(itemsToUpdate));
230 secerror( "SecItemUpdate result: %@ (%ld)", [self fetchStatus:status], (long)status);
231 NSLog(@"SecItemUpdate result: %@ (%ld)", [self fetchStatus:status], (long)status);
235 - (NSMutableArray *)fetchDictionaryAll
237 NSMutableDictionary *query = [[NSMutableDictionary alloc] init];
238 [query setObject:(__bridge id)kSecClassGenericPassword forKey:(__bridge id)kSecClass];
239 [query setObject:(__bridge id)kCFBooleanTrue forKey:(__bridge id)kSecAttrSynchronizable];
241 // Add the search constraints
242 [query setObject:(__bridge id)kSecMatchLimitAll forKey:(__bridge id)kSecMatchLimit];
243 [query setObject:(__bridge id)kCFBooleanTrue
244 forKey:(__bridge id)kSecReturnAttributes];
245 [query setObject:(__bridge id)kCFBooleanTrue
246 forKey:(__bridge id)kSecReturnData];
248 NSMutableArray *genericItems = [self fetchDictionaryWithQuery:query];
250 // Now look for internet items
251 [query setObject:(__bridge id)kSecClassInternetPassword forKey:(__bridge id)kSecClass];
252 NSMutableArray *internetItems = [self fetchDictionaryWithQuery:query];
254 [genericItems addObjectsFromArray:internetItems];
258 // MARK: ----- Full routines -----
260 // Return a base dictionary
261 - (NSMutableDictionary *) baseDictionaryFull:(NSString *)account service:(NSString *)service
263 NSMutableDictionary *md = [[NSMutableDictionary alloc] init];
265 // Password identification keys
266 NSData *identifier = [PWKEY dataUsingEncoding:NSUTF8StringEncoding];
267 [md setObject:identifier forKey:(__bridge id)kSecAttrGeneric];
268 [md setObject:account forKey:(__bridge id)kSecAttrAccount];
269 [md setObject:service forKey:(__bridge id)kSecAttrService];
270 [md setObject:(__bridge id)kSecClassGenericPassword forKey:(__bridge id)kSecClass];
271 [md setObject:(__bridge id)kCFBooleanTrue forKey:(__bridge id)kSecAttrSynchronizable];
273 // return [md autorelease];
277 - (BOOL) createKeychainValueFull:(NSString *)account service:(NSString *)service password:(NSString *)password
279 NSMutableDictionary *md = [self buildDictForPasswordFull:account service:service password:password];
280 OSStatus status = SecItemAdd((__bridge CFDictionaryRef)md, NULL);
281 secerror( "CREATE: %s\n", [[self fetchStatus:status] UTF8String]);
283 if (status == errSecSuccess) return YES; else return NO;
286 - (BOOL) updateKeychainValueFull:(NSString *)account service:(NSString *)service password:(NSString *)password
288 NSMutableDictionary *genericPasswordQuery = [self baseDictionaryFull:account service:service];
290 NSMutableDictionary *attributesToUpdate = [[NSMutableDictionary alloc] init];
291 NSData *passwordData = [password dataUsingEncoding:NSUTF8StringEncoding];
292 [attributesToUpdate setObject:passwordData forKey:(__bridge id)kSecValueData];
294 OSStatus status = SecItemUpdate((__bridge CFDictionaryRef)genericPasswordQuery, (__bridge CFDictionaryRef)attributesToUpdate);
295 secerror( "UPDATE: %s\n", [[self fetchStatus:status] UTF8String]);
297 if (status == 0) return YES; else return NO;
300 - (void)setPasswordFull:(NSString *)account service:(NSString *)service password:(NSString *) thePassword
302 secerror( "setPasswordFull account: %@, service: %@, password: %@", account, service, thePassword);
303 if (![self createKeychainValueFull:account service:service password:thePassword])
304 [self updateKeychainValueFull:account service:service password:thePassword];
307 // Return a keychain-style dictionary populated with the password
308 - (NSMutableDictionary *) buildDictForPasswordFull:(NSString *)account service:(NSString *)service password:(NSString *)password
310 NSMutableDictionary *passwordDict = [self baseDictionaryFull:account service:service];
313 NSData *passwordData = [password dataUsingEncoding:NSUTF8StringEncoding];
314 [passwordDict setObject:passwordData forKey:(__bridge id)kSecValueData]; // password
319 - (void)clearAllKeychainItems
322 OSStatus status = errSecSuccess;
324 NSArray *allItems = (NSArray *)[[MyKeychain sharedInstance] fetchDictionaryAll];
325 top = [allItems count];
326 secerror( "Deleting %ld items", (long)top);
328 for (ix=0; ix<top && !status; ix++)
330 NSDictionary *item = [allItems objectAtIndex:ix];
331 NSMutableDictionary *query = [[NSMutableDictionary alloc] init];
333 NSData *identifier = [PWKEY dataUsingEncoding:NSUTF8StringEncoding];
334 [query setObject:identifier forKey:(__bridge id)kSecAttrGeneric];
335 [query setObject:(__bridge id)kSecClassGenericPassword forKey:(__bridge id)kSecClass];
336 [query setObject:(__bridge id)kCFBooleanTrue forKey:(__bridge id)kSecAttrSynchronizable];
338 [query setObject:[item objectForKey:(__bridge id)(kSecAttrAccount)] forKey:(__bridge id)kSecAttrAccount];
339 [query setObject:[item objectForKey:(__bridge id)(kSecAttrService)] forKey:(__bridge id)kSecAttrService];
341 status = SecItemDelete((__bridge CFDictionaryRef) query);
342 secerror( "DELETE: %s\n", [[self fetchStatus:status] UTF8String]);