]>
Commit | Line | Data |
---|---|---|
d8f41ccd A |
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 |