]> git.saurik.com Git - apple/security.git/blob - Keychain/MyKeychain.m
Security-57031.30.12.tar.gz
[apple/security.git] / Keychain / MyKeychain.m
1 //
2 // MyKeychain.m
3 // KCSync
4 //
5 // Created by John Hurley on 10/3/12.
6 // Copyright (c) 2012 john. All rights reserved.
7 //
8
9 #import "MyKeychain.h"
10 #import <Security/Security.h>
11 #import <Security/SecItemPriv.h>
12 #import <utilities/debugging.h>
13
14 const NSString *kItemPasswordKey = @"ItemPasswordKey";
15 const NSString *kItemAccountKey = @"ItemAccountKey";
16 const NSString *kItemNameKey = @"ItemNameKey";
17
18 #define KCSCOPE "mykeychain"
19
20 // secdebug(KCSCOPE,
21
22 @implementation MyKeychain
23
24 static MyKeychain *sharedInstance = nil;
25
26 + (MyKeychain *) sharedInstance
27 {
28 if (!sharedInstance)
29 sharedInstance = [[self alloc] init];
30
31 return sharedInstance;
32 }
33
34 // Translate status messages into return strings
35 - (NSString *) fetchStatus : (OSStatus) status
36 {
37 switch (status)
38 {
39 case 0:
40 return(@"Success!");
41 case errSecNotAvailable:
42 return(@"No trust results are available.!");
43 case errSecItemNotFound:
44 return(@"The item cannot be found.");
45 case errSecParam:
46 return(@"Parameter error.");
47 case errSecAllocate:
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.");
55 case errSecDecode:
56 return(@"Unable to decode the provided data.");
57
58 default:
59 return([NSString stringWithFormat:@"Function returned: %ld", (long)status]);
60 break;
61 }
62 return @"can't happen...";
63 }
64
65 #define ACCOUNT @"Keychain Sync Test Account"
66 #define SERVICE @"Keychain Sync Test Service"
67 #define PWKEY @"Keychain Sync Test Password Data"
68
69 // Return a base dictionary
70 - (NSMutableDictionary *) baseDictionary
71 {
72 NSMutableDictionary *md = [[NSMutableDictionary alloc] init];
73
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];
81
82 return md;
83 }
84
85 // Return a keychain-style dictionary populated with the password
86 - (NSMutableDictionary *) buildDictForPassword:(NSString *) password
87 {
88 NSMutableDictionary *passwordDict = [self baseDictionary];
89
90 // Add the password
91 NSData *passwordData = [password dataUsingEncoding:NSUTF8StringEncoding];
92 [passwordDict setObject:passwordData forKey:(__bridge id)kSecValueData]; // password
93
94 return passwordDict;
95 }
96
97 // Build a search query based
98 - (NSMutableDictionary *) buildSearchQuery
99 {
100 NSMutableDictionary *genericPasswordQuery = [self baseDictionary];
101
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];
108
109 return genericPasswordQuery;
110 }
111
112 // retrieve data dictionary from the keychain
113 - (NSMutableArray *) fetchDictionaryWithQuery:(NSMutableDictionary *)query
114 {
115 NSMutableDictionary *genericPasswordQuery = query;
116
117 // secerror("Query: %@", query);
118 CFTypeRef cfresult = nil;
119 OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)genericPasswordQuery, &cfresult);
120
121 // secerror( "FETCH: %s\n", [[self fetchStatus:status] UTF8String]);
122
123 if (status == errSecItemNotFound)
124 return NULL;
125
126 if (CFGetTypeID(cfresult) == CFArrayGetTypeID())
127 {
128 NSMutableArray *result = [NSMutableArray arrayWithCapacity:0];
129 [result addObjectsFromArray:CFBridgingRelease(cfresult)];
130 return result;
131 }
132
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)];
136
137 return NULL;
138 }
139
140 - (NSMutableArray *)fetchDictionary
141 {
142 return [self fetchDictionaryWithQuery:[self buildSearchQuery]];
143 }
144
145 // create a new keychain entry
146 - (BOOL) createKeychainValue:(NSString *) password
147 {
148 NSMutableDictionary *md = [self buildDictForPassword:password];
149 OSStatus status = SecItemAdd((__bridge CFDictionaryRef)md, NULL);
150
151 secerror( "CREATE: %s\n", [[self fetchStatus:status] UTF8String]);
152
153 if (status == errSecSuccess) return YES; else return NO;
154 }
155
156 // remove a keychain entry
157 - (void) clearKeychain
158 {
159 NSMutableDictionary *genericPasswordQuery = [self baseDictionary];
160
161 OSStatus status = SecItemDelete((__bridge CFDictionaryRef) genericPasswordQuery);
162 secerror( "DELETE: %s\n", [[self fetchStatus:status] UTF8String]);
163 }
164
165 // update a keychain entry
166 - (BOOL) updateKeychainValue:(NSString *)password
167 {
168 NSMutableDictionary *genericPasswordQuery = [self baseDictionary];
169
170 NSMutableDictionary *attributesToUpdate = [[NSMutableDictionary alloc] init];
171 NSData *passwordData = [password dataUsingEncoding:NSUTF8StringEncoding];
172 [attributesToUpdate setObject:passwordData forKey:(__bridge id)kSecValueData];
173
174 OSStatus status = SecItemUpdate((__bridge CFDictionaryRef)genericPasswordQuery, (__bridge CFDictionaryRef)attributesToUpdate);
175 secerror( "UPDATE: %s\n", [[self fetchStatus:status] UTF8String]);
176
177 if (status == 0) return YES; else return NO;
178 }
179
180 // fetch a keychain value
181 - (NSString *) fetchPassword
182 {
183 NSMutableArray *allItems = [self fetchDictionary];
184 if (!allItems)
185 return NULL;
186
187 // This is used for a single item, so take first item in array
188 NSData *passData = [allItems[0] objectForKey:(__bridge id)kSecValueData];
189 if (passData)
190 return [[NSString alloc] initWithData:passData encoding:NSUTF8StringEncoding];
191
192 return NULL;
193 }
194
195 - (void)setPassword: (NSString *) thePassword
196 {
197 if (![self createKeychainValue:thePassword])
198 [self updateKeychainValue:thePassword];
199 }
200
201 - (void)setItem:(NSDictionary *)newItem
202 {
203 OSStatus status = errSecSuccess;
204 NSMutableDictionary *passwordDict = [self baseDictionary];
205
206 // Add the password
207 NSData *passwordData = [[newItem objectForKey:kItemPasswordKey] dataUsingEncoding:NSUTF8StringEncoding];
208 [passwordDict setObject:passwordData forKey:(__bridge id)kSecValueData]; // password
209
210 [passwordDict setObject:[newItem objectForKey:kItemAccountKey] forKey:(__bridge id)kSecAttrAccount];
211 [passwordDict setObject:[newItem objectForKey:kItemNameKey] forKey:(__bridge id)kSecAttrService];
212
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);
217
218 if (status)
219 {
220 // Try update
221 // We only want to update the password data, so delete the other keys
222
223 // NSArray *keysToRemove = [NSArray arrayWithObjects:kItemAccountKey, kItemNameKey, nil];
224 [passwordDict removeObjectsForKeys:@[(__bridge id)kSecValueData]];
225
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);
232 }
233 }
234
235 - (NSMutableArray *)fetchDictionaryAll
236 {
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];
240
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];
247
248 NSMutableArray *genericItems = [self fetchDictionaryWithQuery:query];
249
250 // Now look for internet items
251 [query setObject:(__bridge id)kSecClassInternetPassword forKey:(__bridge id)kSecClass];
252 NSMutableArray *internetItems = [self fetchDictionaryWithQuery:query];
253 if (internetItems)
254 [genericItems addObjectsFromArray:internetItems];
255 return genericItems;
256 }
257
258 // MARK: ----- Full routines -----
259
260 // Return a base dictionary
261 - (NSMutableDictionary *) baseDictionaryFull:(NSString *)account service:(NSString *)service
262 {
263 NSMutableDictionary *md = [[NSMutableDictionary alloc] init];
264
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];
272
273 // return [md autorelease];
274 return md;
275 }
276
277 - (BOOL) createKeychainValueFull:(NSString *)account service:(NSString *)service password:(NSString *)password
278 {
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]);
282
283 if (status == errSecSuccess) return YES; else return NO;
284 }
285
286 - (BOOL) updateKeychainValueFull:(NSString *)account service:(NSString *)service password:(NSString *)password
287 {
288 NSMutableDictionary *genericPasswordQuery = [self baseDictionaryFull:account service:service];
289
290 NSMutableDictionary *attributesToUpdate = [[NSMutableDictionary alloc] init];
291 NSData *passwordData = [password dataUsingEncoding:NSUTF8StringEncoding];
292 [attributesToUpdate setObject:passwordData forKey:(__bridge id)kSecValueData];
293
294 OSStatus status = SecItemUpdate((__bridge CFDictionaryRef)genericPasswordQuery, (__bridge CFDictionaryRef)attributesToUpdate);
295 secerror( "UPDATE: %s\n", [[self fetchStatus:status] UTF8String]);
296
297 if (status == 0) return YES; else return NO;
298 }
299
300 - (void)setPasswordFull:(NSString *)account service:(NSString *)service password:(NSString *) thePassword
301 {
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];
305 }
306
307 // Return a keychain-style dictionary populated with the password
308 - (NSMutableDictionary *) buildDictForPasswordFull:(NSString *)account service:(NSString *)service password:(NSString *)password
309 {
310 NSMutableDictionary *passwordDict = [self baseDictionaryFull:account service:service];
311
312 // Add the password
313 NSData *passwordData = [password dataUsingEncoding:NSUTF8StringEncoding];
314 [passwordDict setObject:passwordData forKey:(__bridge id)kSecValueData]; // password
315
316 return passwordDict;
317 }
318
319 - (void)clearAllKeychainItems
320 {
321 CFIndex ix, top;
322 OSStatus status = errSecSuccess;
323
324 NSArray *allItems = (NSArray *)[[MyKeychain sharedInstance] fetchDictionaryAll];
325 top = [allItems count];
326 secerror( "Deleting %ld items", (long)top);
327
328 for (ix=0; ix<top && !status; ix++)
329 {
330 NSDictionary *item = [allItems objectAtIndex:ix];
331 NSMutableDictionary *query = [[NSMutableDictionary alloc] init];
332
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];
337
338 [query setObject:[item objectForKey:(__bridge id)(kSecAttrAccount)] forKey:(__bridge id)kSecAttrAccount];
339 [query setObject:[item objectForKey:(__bridge id)(kSecAttrService)] forKey:(__bridge id)kSecAttrService];
340
341 status = SecItemDelete((__bridge CFDictionaryRef) query);
342 secerror( "DELETE: %s\n", [[self fetchStatus:status] UTF8String]);
343 }
344 }
345
346
347 @end