]> git.saurik.com Git - apple/security.git/blob - SecurityTool/sharedTool/keychain_find.m
Security-59306.11.20.tar.gz
[apple/security.git] / SecurityTool / sharedTool / keychain_find.m
1 /*
2 * Copyright (c) 2003-2007,2009-2010,2013-2014 Apple Inc. All Rights Reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
11 * file.
12 *
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 *
23 * keychain_find.c
24 */
25
26 #include <CoreFoundation/CoreFoundation.h>
27 #import <Foundation/Foundation.h>
28
29 #include <Security/SecItem.h>
30 #include <Security/SecItemPriv.h>
31
32 #include "SecurityTool/sharedTool/tool_errors.h"
33 #include "SecurityTool/sharedTool/readline.h"
34
35 #include <utilities/SecCFWrappers.h>
36
37 #include "SecurityCommands.h"
38
39 #include "keychain_util.h"
40 #include <Security/SecAccessControl.h>
41 #include <Security/SecAccessControlPriv.h>
42
43 #import <SecurityFoundation/SFKeychain.h>
44
45 //
46 // Craptastic hacks.
47 #ifndef _SECURITY_SECKEYCHAIN_H_
48 typedef uint32_t SecProtocolType;
49 typedef uint32_t SecAuthenticationType;
50 #endif
51
52
53 static CFMutableDictionaryRef
54 keychain_create_query_from_string(const char *query) {
55 CFMutableDictionaryRef q;
56
57 q = CFDictionaryCreateMutable(0, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
58 if (!keychain_query_parse_cstring(q, query)) {
59 CFReleaseNull(q);
60 }
61 return q;
62 }
63
64 static void add_key(const void *key, const void *value, void *context) {
65 CFArrayAppendValue(context, key);
66 }
67
68 static bool isPrintableString(CFStringRef theString){
69 bool result = false;
70 CFCharacterSetRef controlSet = CFCharacterSetGetPredefined(kCFCharacterSetControl);
71 CFCharacterSetRef newlineSet = CFCharacterSetGetPredefined(kCFCharacterSetNewline);
72 CFCharacterSetRef illegalSet = CFCharacterSetGetPredefined(kCFCharacterSetIllegal);
73
74 CFMutableCharacterSetRef unacceptable = CFCharacterSetCreateMutableCopy(kCFAllocatorDefault, controlSet);
75 CFCharacterSetUnion(unacceptable, newlineSet);
76 CFCharacterSetUnion(unacceptable, illegalSet);
77 result = CFStringFindCharacterFromSet(theString, unacceptable, CFRangeMake(0, CFStringGetLength(theString)), 0, NULL);
78 CFReleaseNull(unacceptable);
79 return !result;
80 }
81
82 static void display_item(const void *v_item, void *context) {
83 CFDictionaryRef item = (CFDictionaryRef)v_item;
84 CFIndex dict_count, key_ix, key_count;
85 CFMutableArrayRef keys = NULL;
86 CFIndex maxWidth = 10; /* Maybe precompute this or grab from context? */
87
88 dict_count = CFDictionaryGetCount(item);
89 keys = CFArrayCreateMutable(kCFAllocatorDefault, dict_count,
90 &kCFTypeArrayCallBacks);
91 CFDictionaryApplyFunction(item, add_key, keys);
92 key_count = CFArrayGetCount(keys);
93 CFArraySortValues(keys, CFRangeMake(0, key_count),
94 (CFComparatorFunction)CFStringCompare, 0);
95
96 for (key_ix = 0; key_ix < key_count; ++key_ix) {
97 CFStringRef key = (CFStringRef)CFArrayGetValueAtIndex(keys, key_ix);
98 CFTypeRef value = CFDictionaryGetValue(item, key);
99 CFMutableStringRef line = CFStringCreateMutable(NULL, 0);
100
101 CFStringAppend(line, key);
102 CFIndex jx;
103 for (jx = CFStringGetLength(key);
104 jx < maxWidth; ++jx) {
105 CFStringAppend(line, CFSTR(" "));
106 }
107 CFStringAppend(line, CFSTR(" : "));
108 if (CFStringGetTypeID() == CFGetTypeID(value)) {
109 CFStringAppend(line, (CFStringRef)value);
110 } else if (CFNumberGetTypeID() == CFGetTypeID(value)) {
111 CFNumberRef v_n = (CFNumberRef)value;
112 CFStringAppendFormat(line, NULL, CFSTR("%@"), v_n);
113 } else if (CFDateGetTypeID() == CFGetTypeID(value)) {
114 CFDateRef v_d = (CFDateRef)value;
115 CFStringAppendFormat(line, NULL, CFSTR("%@"), v_d);
116 } else if (CFDataGetTypeID() == CFGetTypeID(value)) {
117 CFDataRef v_d = (CFDataRef)value;
118 CFStringRef v_s = CFStringCreateFromExternalRepresentation(
119 kCFAllocatorDefault, v_d, kCFStringEncodingUTF8);
120
121 if (v_s) {
122 if(!isPrintableString(v_s))
123 CFStringAppend(line, CFSTR("not printable "));
124 else{
125 CFStringAppend(line, CFSTR("/"));
126 CFStringAppend(line, v_s);
127 CFStringAppend(line, CFSTR("/ "));
128 }
129 }
130 CFReleaseNull(v_s);
131
132 const uint8_t *bytes = CFDataGetBytePtr(v_d);
133 CFIndex len = CFDataGetLength(v_d);
134 for (jx = 0; jx < len; ++jx) {
135 CFStringAppendFormat(line, NULL, CFSTR("%.02X"), bytes[jx]);
136 }
137 } else if (SecAccessControlGetTypeID() == CFGetTypeID(value)) {
138 display_sac_line((SecAccessControlRef)value, line);
139 } else {
140 CFStringAppendFormat(line, NULL, CFSTR("%@"), value);
141 }
142
143 CFStringWriteToFileWithNewline(line, stdout);
144
145 CFRelease(line);
146 }
147 CFRelease(keys);
148
149 CFStringWriteToFileWithNewline(CFSTR("===="), stdout);
150
151 //CFShow(item);
152 }
153
154
155 static void display_results(CFTypeRef results) {
156 if (results && CFGetTypeID(results) == CFArrayGetTypeID()) {
157 CFArrayRef r_a = (CFArrayRef)results;
158 CFArrayApplyFunction(r_a, CFRangeMake(0, CFArrayGetCount(r_a)),
159 display_item, NULL);
160 } else if (results && CFGetTypeID(results) == CFDictionaryGetTypeID()) {
161 display_item(results, NULL);
162 } else {
163 fprintf(stderr, "SecItemCopyMatching returned unexpected results:");
164 CFShow(results);
165 }
166 }
167
168 static NSDictionary* cleanNSDictionaryForJSON(NSDictionary* dict) {
169 if(!dict) {
170 return nil;
171 }
172 NSMutableDictionary* mutDict = [NSMutableDictionary dictionary];
173 for(id key in dict.allKeys) {
174 id obj = dict[key];
175
176 if([obj isKindOfClass:[NSDictionary class]]) {
177 mutDict[key] = cleanNSDictionaryForJSON(obj);
178
179 } else if([NSJSONSerialization isValidJSONObject:obj]) {
180 mutDict[key] = obj;
181
182 } else if([obj isKindOfClass:[NSString class]]) {
183 mutDict[key] = obj;
184
185 } else if([obj isKindOfClass:[NSNumber class]]) {
186 mutDict[key] = obj;
187
188 } else if([obj isKindOfClass:[NSData class]]) {
189 mutDict[key] = [(NSData*)obj base64EncodedStringWithOptions:0];
190
191 } else if([obj isKindOfClass:[NSDate class]]) {
192 NSISO8601DateFormatter* dateFormat = [[NSISO8601DateFormatter alloc] init];
193 mutDict[key] = [dateFormat stringFromDate:obj];
194
195 } else if (SecAccessControlGetTypeID() == CFGetTypeID((__bridge CFTypeRef)obj)) {
196 NSMutableString* str = [NSMutableString string];
197 display_sac_line((__bridge SecAccessControlRef)obj, (__bridge CFMutableStringRef)str);
198 mutDict[key] = str;
199
200 } else {
201 NSLog(@"can't jsonify: %@ %@ %@", key, obj, [obj class]);
202 }
203 }
204 return mutDict;
205 }
206
207 static void display_results_json(CFTypeRef cfitem) {
208 id item = (__bridge id)cfitem;
209
210 if([item isKindOfClass:[NSArray class]]) {
211 NSArray* array = (NSArray*)item;
212 NSMutableArray* cleanArray = [NSMutableArray array];
213
214 for(id x in array) {
215 NSDictionary* cleanDictionary = cleanNSDictionaryForJSON((NSDictionary*)x);
216 [cleanArray addObject:cleanDictionary];
217
218 if(![NSJSONSerialization isValidJSONObject:cleanDictionary]) {
219 fprintf(stderr, "%s", [[NSString stringWithFormat:@"Can't JSONify: %@", x] UTF8String]);
220 }
221 }
222
223 NSError* error = nil;
224 NSData *json = [NSJSONSerialization dataWithJSONObject:cleanArray
225 options:(NSJSONWritingPrettyPrinted | NSJSONWritingSortedKeys)
226 error:&error];
227 if (!json) {
228 fprintf(stderr, "%s", [[NSString stringWithFormat:@"error: %@", error.localizedDescription] UTF8String]);
229 } else {
230 printf("%s\n", [[[NSString alloc] initWithData:json encoding:NSUTF8StringEncoding] UTF8String]);
231 }
232
233 } else if([item isKindOfClass:[NSDictionary class]]) {
234 NSError* error = nil;
235 NSData *json = [NSJSONSerialization dataWithJSONObject:cleanNSDictionaryForJSON(item)
236 options:(NSJSONWritingPrettyPrinted | NSJSONWritingSortedKeys)
237 error:&error];
238 if (!json) {
239 fprintf(stderr, "%s", [[NSString stringWithFormat:@"error: %@", error.localizedDescription] UTF8String]);
240 } else {
241 printf("%s\n", [[[NSString alloc] initWithData:json encoding:NSUTF8StringEncoding] UTF8String]);
242 }
243
244 } else {
245 fprintf(stderr, "SecItemCopyMatching returned unexpected results (can't JSONify):");
246 CFShow(cfitem);
247 }
248
249 }
250
251 static OSStatus do_find_or_delete(CFDictionaryRef query, bool do_delete) {
252 OSStatus result;
253 if (do_delete) {
254 result = SecItemDelete(query);
255 if (result) {
256 sec_perror("SecItemDelete", result);
257 }
258 } else {
259 CFTypeRef results = NULL;
260 result = SecItemCopyMatching(query, &results);
261 if (result) {
262 sec_perror("SecItemCopyMatching", result);
263 } else {
264 display_results(results);
265 }
266 CFReleaseSafe(results);
267 }
268 return result;
269 }
270
271 static int
272 do_keychain_find_or_delete_internet_password(Boolean do_delete,
273 const char *serverName, const char *securityDomain,
274 const char *accountName, const char *path, UInt16 port,
275 SecProtocolType protocol, SecAuthenticationType authenticationType,
276 Boolean get_password)
277 {
278 OSStatus result;
279 CFDictionaryRef query = NULL;
280 const void *keys[11], *values[11];
281 CFIndex ix = 0;
282
283 if (do_delete && !serverName && !securityDomain && !accountName && !path && !port && !protocol && !authenticationType) {
284 return SHOW_USAGE_MESSAGE;
285 }
286
287 keys[ix] = kSecClass;
288 values[ix++] = kSecClassInternetPassword;
289 if (serverName) {
290 keys[ix] = kSecAttrServer;
291 values[ix++] = CFStringCreateWithCStringNoCopy(NULL, serverName,
292 kCFStringEncodingUTF8, kCFAllocatorNull);
293 }
294 if (securityDomain) {
295 keys[ix] = kSecAttrSecurityDomain;
296 values[ix++] = CFStringCreateWithCStringNoCopy(NULL, securityDomain,
297 kCFStringEncodingUTF8, kCFAllocatorNull);
298 }
299 if (accountName) {
300 keys[ix] = kSecAttrAccount;
301 values[ix++] = CFStringCreateWithCStringNoCopy(NULL, accountName,
302 kCFStringEncodingUTF8, kCFAllocatorNull);
303 }
304 if (path) {
305 keys[ix] = kSecAttrPath;
306 values[ix++] = CFStringCreateWithCStringNoCopy(NULL, path,
307 kCFStringEncodingUTF8, kCFAllocatorNull);
308 }
309 if (port != 0) {
310 keys[ix] = kSecAttrPort;
311 values[ix++] = CFNumberCreate(NULL, kCFNumberSInt16Type, &port);
312 }
313 if (protocol != 0) {
314 /* Protocol is a 4 char code, perhaps we should use a string rep
315 instead. */
316 keys[ix] = kSecAttrProtocol;
317 values[ix++] = CFNumberCreate(NULL, kCFNumberSInt32Type, &protocol);
318 }
319 if (authenticationType != 0) {
320 keys[ix] = kSecAttrAuthenticationType;
321 values[ix++] = CFNumberCreate(NULL, kCFNumberSInt32Type,
322 &authenticationType);
323 }
324 if (get_password) {
325 /* Only ask for the data if so required. */
326 keys[ix] = kSecReturnData;
327 values[ix++] = kCFBooleanTrue;
328 }
329 keys[ix] = kSecReturnAttributes;
330 values[ix++] = kCFBooleanTrue;
331 if (!do_delete) {
332 /* If we aren't deleting ask for all items. */
333 keys[ix] = kSecMatchLimit;
334 values[ix++] = kSecMatchLimitAll;
335 }
336
337 query = CFDictionaryCreate(NULL, keys, values, ix,
338 &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
339 result = do_find_or_delete(query, do_delete);
340 CFReleaseSafe(query);
341
342 return result;
343 }
344
345 static int
346 parse_fourcharcode(const char *name, uint32_t *code)
347 {
348 /* @@@ Check for errors. */
349 char *p = (char *)code;
350 strncpy(p, name, 4);
351 return 0;
352 }
353
354 static int
355 keychain_find_or_delete_internet_password(Boolean do_delete, int argc, char * const *argv)
356 {
357 char *serverName = NULL, *securityDomain = NULL, *accountName = NULL, *path = NULL;
358 UInt16 port = 0;
359 SecProtocolType protocol = 0;
360 SecAuthenticationType authenticationType = 0;
361 int ch, result = 0;
362 Boolean get_password = FALSE;
363
364 while ((ch = getopt(argc, argv, "a:d:hgp:P:r:s:t:")) != -1)
365 {
366 switch (ch)
367 {
368 case 'a':
369 accountName = optarg;
370 break;
371 case 'd':
372 securityDomain = optarg;
373 break;
374 case 'g':
375 if (do_delete)
376 return SHOW_USAGE_MESSAGE;
377 get_password = TRUE;
378 break;
379 case 'p':
380 path = optarg;
381 break;
382 case 'P':
383 port = atoi(optarg);
384 break;
385 case 'r':
386 result = parse_fourcharcode(optarg, &protocol);
387 if (result)
388 goto loser;
389 break;
390 case 's':
391 serverName = optarg;
392 break;
393 case 't':
394 result = parse_fourcharcode(optarg, &authenticationType);
395 if (result)
396 goto loser;
397 break;
398 case '?':
399 default:
400 return SHOW_USAGE_MESSAGE;
401 }
402 }
403
404 result = do_keychain_find_or_delete_internet_password(do_delete, serverName, securityDomain,
405 accountName, path, port, protocol,authenticationType, get_password);
406
407
408 loser:
409
410 return result;
411 }
412
413 int
414 keychain_find_internet_password(int argc, char * const *argv) {
415 return keychain_find_or_delete_internet_password(0, argc, argv);
416 }
417
418 int
419 keychain_delete_internet_password(int argc, char * const *argv) {
420 return keychain_find_or_delete_internet_password(1, argc, argv);
421 }
422
423 static int
424 do_keychain_find_or_delete_generic_password(Boolean do_delete,
425 const char *serviceName, const char *accountName,
426 Boolean get_password)
427 {
428 OSStatus result;
429 CFDictionaryRef query = NULL;
430 const void *keys[6], *values[6];
431 CFIndex ix = 0;
432
433 if (do_delete && !serviceName && !accountName) {
434 return SHOW_USAGE_MESSAGE;
435 }
436
437 keys[ix] = kSecClass;
438 values[ix++] = kSecClassGenericPassword;
439 if (serviceName) {
440 keys[ix] = kSecAttrService;
441 values[ix++] = CFStringCreateWithCStringNoCopy(NULL, serviceName,
442 kCFStringEncodingUTF8, kCFAllocatorNull);
443 }
444 if (accountName) {
445 keys[ix] = kSecAttrAccount;
446 values[ix++] = CFStringCreateWithCStringNoCopy(NULL, accountName,
447 kCFStringEncodingUTF8, kCFAllocatorNull);
448 }
449 if (get_password) {
450 /* Only ask for the data if so required. */
451 keys[ix] = kSecReturnData;
452 values[ix++] = kCFBooleanTrue;
453 }
454 keys[ix] = kSecReturnAttributes;
455 values[ix++] = kCFBooleanTrue;
456 if (!do_delete) {
457 /* If we aren't deleting ask for all items. */
458 keys[ix] = kSecMatchLimit;
459 values[ix++] = kSecMatchLimitAll;
460 }
461
462 query = CFDictionaryCreate(NULL, keys, values, ix,
463 &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
464
465 result = do_find_or_delete(query, do_delete);
466
467 CFReleaseSafe(query);
468
469 return result;
470 }
471
472 static SFServiceIdentifier* serviceIdentifierInQuery(NSDictionary* query)
473 {
474 NSString* domain = query[@"domain"];
475 NSString* bundleID = query[@"bundleID"];
476 NSString* accessGroup = query[@"accessGroup"];
477 NSString* customServiceID = query[@"customServiceID"];
478
479 SFServiceIdentifier* serviceIdentifier = nil;
480 if (domain) {
481 serviceIdentifier = [[SFServiceIdentifier alloc] initWithServiceID:domain forType:SFServiceIdentifierTypeDomain];
482 }
483 else if (bundleID) {
484 serviceIdentifier = [[SFServiceIdentifier alloc] initWithServiceID:bundleID forType:SFServiceIdentifierTypeBundleID];
485 }
486 else if (accessGroup) {
487 serviceIdentifier = [[SFServiceIdentifier alloc] initWithServiceID:accessGroup forType:SFServiceIdentifierTypeAccessGroup];
488 }
489 else if (customServiceID) {
490 serviceIdentifier = [[SFServiceIdentifier alloc] initWithServiceID:customServiceID forType:SFServiceIdentifierTypeCustom];
491 }
492 if (!serviceIdentifier) {
493 sec_error("need service identifier");
494 }
495
496 return serviceIdentifier;
497 }
498
499 static SFPasswordCredential* lookupCredential(SFServiceIdentifier* serviceIdentifier, NSString* username)
500 {
501 __block SFPasswordCredential* result = nil;
502 dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
503 [[SFCredentialStore defaultCredentialStore] lookupCredentialsForServiceIdentifiers:@[serviceIdentifier] withResultHandler:^(NSArray<SFCredential*>* results, NSError* error) {
504 if (error) {
505 sec_error("error looking up credentials: %s", error.description.UTF8String);
506 }
507 else {
508 bool foundCredential = false;
509 for (SFPasswordCredential* credential in results) {
510 if ([credential.username isEqualToString:username]) {
511 result = credential;
512 dispatch_semaphore_signal(semaphore);
513 return;
514 }
515 }
516
517 if (!foundCredential) {
518 sec_error("did not find credential");
519 }
520 }
521
522 dispatch_semaphore_signal(semaphore);
523 }];
524 if (dispatch_semaphore_wait(semaphore, dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC))) {
525 sec_error("timed out trying to communicate with credential store");
526 }
527
528 return result;
529 }
530
531 static SFPasswordCredential* credentialFromQuery(NSDictionary* query)
532 {
533 NSString* username = query[@"username"];
534 NSData* passwordData = query[(__bridge id)kSecValueData];
535 NSString* password = [[NSString alloc] initWithData:passwordData encoding:NSUTF8StringEncoding];
536 if (!username) {
537 sec_error("need username");
538 return nil;
539 }
540 if (!password) {
541 sec_error("need password");
542 return nil;
543 }
544
545 SFServiceIdentifier* serviceIdentifier = serviceIdentifierInQuery(query);
546
547 if (username && password && serviceIdentifier) {
548 return [[SFPasswordCredential alloc] initWithUsername:username password:password primaryServiceIdentifier:serviceIdentifier];
549 }
550 else {
551 return nil;
552 }
553 }
554
555 static SFAccessPolicy* accessPolicyInQuery(NSDictionary* query)
556 {
557 CFStringRef accessibility = (__bridge CFStringRef)query[(__bridge id)kSecAttrAccessible];
558 if (!accessibility) {
559 accessibility = kSecAttrAccessibleWhenUnlocked;
560 }
561
562 SFAccessPolicy* accessPolicy = [SFAccessPolicy accessPolicyWithSecAccessibility:accessibility error:nil];
563 NSNumber* synchronizableValue = query[(__bridge id)kSecAttrSynchronizable];
564 if (accessPolicy.sharingPolicy == SFSharingPolicyWithTrustedDevices && synchronizableValue && synchronizableValue.boolValue == NO) {
565 accessPolicy.sharingPolicy = SFSharingPolicyWithBackup;
566 }
567 if (!accessPolicy) {
568 sec_error("need access policy");
569 }
570
571 return accessPolicy;
572 }
573
574 int keychain_item(int argc, char * const *argv) {
575 int ch = 0;
576 __block int result = 0;
577 CFMutableDictionaryRef query, update = NULL;
578 bool get_password = false;
579 bool do_delete = false;
580 bool do_add = false;
581 bool verbose = false;
582 bool json = false;
583 int limit = 0;
584
585 query = CFDictionaryCreateMutable(0, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
586 #if TARGET_OS_OSX
587 CFDictionarySetValue(query, kSecUseDataProtectionKeychain, kCFBooleanTrue);
588 #endif
589
590 while ((ch = getopt(argc, argv, "ad:Df:jgq:u:vl:")) != -1)
591 {
592 switch (ch)
593 {
594 case 'a':
595 do_add = true;
596 break;
597 case 'D':
598 do_delete = true;
599 break;
600 case 'd':
601 {
602 CFStringRef dataString = CFStringCreateWithCString(0, optarg, kCFStringEncodingUTF8);
603 if (dataString) {
604 CFDataRef data = CFStringCreateExternalRepresentation(kCFAllocatorDefault, dataString, kCFStringEncodingUTF8, 0);
605 if (data) {
606 CFDictionarySetValue(update ? update : query, kSecValueData, data);
607 CFRelease(data);
608 }
609 CFRelease(dataString);
610 } else {
611 result = 1;
612 goto out;
613 }
614 break;
615 }
616 case 'f':
617 {
618 CFDataRef data = copyFileContents(optarg);
619 CFDictionarySetValue(update ? update : query, kSecValueData, data);
620 CFRelease(data);
621 break;
622 }
623 case 'j':
624 json = true;
625 break;
626 case 'g':
627 get_password = true;
628 break;
629 case 'q':
630 if (!keychain_query_parse_cstring(query, optarg)) {
631 result = 1;
632 goto out;
633 }
634 break;
635 case 'u':
636 {
637 bool success = true;
638 if (!update)
639 update = keychain_create_query_from_string(optarg);
640 else
641 success = keychain_query_parse_cstring(update, optarg);
642 if (update == NULL || !success) {
643 result = 1;
644 goto out;
645 }
646 }
647 break;
648 case 'v':
649 verbose = true;
650 break;
651 case 'l':
652 limit = atoi(optarg);
653 break;
654 case '?':
655 default:
656 /* Return 2 triggers usage message. */
657 result = 2;
658 goto out;
659 }
660 }
661
662 if (((do_add || do_delete) && (get_password || update)) || !query) {
663 result = 2;
664 goto out;
665 }
666
667 argc -= optind;
668 argv += optind;
669
670 int ix;
671 for (ix = 0; ix < argc; ++ix) {
672 if (!keychain_query_parse_cstring(query, argv[ix])) {
673 result = 1;
674 goto out;
675 }
676 }
677
678 if (!update && !do_add && !do_delete) {
679 CFDictionarySetValue(query, kSecReturnAttributes, kCFBooleanTrue);
680 if(limit) {
681 CFNumberRef cfLimit = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &limit);
682 CFDictionarySetValue(query, kSecMatchLimit, cfLimit);
683 CFReleaseSafe(cfLimit);
684 } else {
685 CFDictionarySetValue(query, kSecMatchLimit, kSecMatchLimitAll);
686 }
687 if (get_password)
688 CFDictionarySetValue(query, kSecReturnData, kCFBooleanTrue);
689 }
690
691 if (verbose) {
692 if(json) {
693 display_results_json(query);
694 } else {
695 CFShow(query);
696 }
697 }
698
699 OSStatus error;
700 bool useCredentialStore = [(__bridge id)CFDictionaryGetValue(query, kSecClass) isEqual:@"credential"];
701 if (do_add) {
702 if (useCredentialStore) {
703 SFPasswordCredential* credential = credentialFromQuery((__bridge NSDictionary*)query);
704 SFAccessPolicy* accessPolicy = accessPolicyInQuery((__bridge NSDictionary*)query);
705 if (credential && accessPolicy) {
706 dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
707 [[SFCredentialStore defaultCredentialStore] addCredential:credential withAccessPolicy:accessPolicy resultHandler:^(NSString* persistentIdentifier, NSError* error) {
708 if (error) {
709 sec_error("error adding credential to credential store: %s", error.description.UTF8String);
710 result = 1;
711 }
712 dispatch_semaphore_signal(semaphore);
713 }];
714 if (dispatch_semaphore_wait(semaphore, dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC))) {
715 sec_error("timed out waiting for response from credential store");
716 result = 1;
717 }
718 }
719 else {
720 sec_error("need username, password, service identiifer, and access policy to create a credential");
721 result = 1;
722 }
723 }
724 else {
725 error = SecItemAdd(query, NULL);
726 if (error) {
727 sec_perror("SecItemAdd", error);
728 result = 1;
729 }
730 }
731 } else if (update) {
732 if (useCredentialStore) {
733 NSString* username = (__bridge NSString*)CFDictionaryGetValue(query, CFSTR("username"));
734 SFServiceIdentifier* serviceIdentifier = serviceIdentifierInQuery((__bridge NSDictionary*)query);
735 SFPasswordCredential* credential = lookupCredential(serviceIdentifier, username);
736 if (credential) {
737 SFPasswordCredential* updatedCredential = credentialFromQuery((__bridge NSDictionary*)update);
738 dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
739 [[SFCredentialStore defaultCredentialStore] replaceOldCredential:credential withNewCredential:updatedCredential resultHandler:^(NSString* newPersistentIdentifier, NSError* error) {
740 if (error) {
741 sec_error("error updating credential: %s", error.description.UTF8String);
742 result = 1;
743 }
744 dispatch_semaphore_signal(semaphore);
745 }];
746 if (dispatch_semaphore_wait(semaphore, dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC))) {
747 sec_error("timed out trying to communicate with credential store");
748 result = 1;
749 }
750 }
751 else {
752 result = 1;
753 }
754 }
755 else {
756 error = SecItemUpdate(query, update);
757 if (error) {
758 sec_perror("SecItemUpdate", error);
759 result = 1;
760 }
761 }
762 } else if (do_delete) {
763 if (useCredentialStore) {
764 NSString* username = (__bridge NSString*)CFDictionaryGetValue(query, CFSTR("username"));
765 SFServiceIdentifier* serviceIdentifier = serviceIdentifierInQuery((__bridge NSDictionary*)query);
766 SFPasswordCredential* credential = lookupCredential(serviceIdentifier, username);
767 if (credential) {
768 dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
769 [[SFCredentialStore defaultCredentialStore] removeCredentialWithPersistentIdentifier:credential.persistentIdentifier withResultHandler:^(BOOL success, NSError* error) {
770 if (!success) {
771 sec_error("failed to remove credential with error: %s", error.description.UTF8String);
772 result = 1;
773 }
774 dispatch_semaphore_signal(semaphore);
775 }];
776 if (dispatch_semaphore_wait(semaphore, dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC))) {
777 sec_error("timed out trying to communicate with credential store");
778 result = 1;
779 }
780 }
781 else {
782 result = 1;
783 }
784 }
785 else {
786 error = SecItemDelete(query);
787 if (error) {
788 sec_perror("SecItemDelete", error);
789 result = 1;
790 }
791 }
792 } else {
793 if (useCredentialStore) {
794 NSString* username = (__bridge NSString*)CFDictionaryGetValue(query, CFSTR("username"));
795 SFServiceIdentifier* serviceIdentifier = serviceIdentifierInQuery((__bridge NSDictionary*)query);
796 SFPasswordCredential* credential = lookupCredential(serviceIdentifier, username);
797 if (credential) {
798 CFStringWriteToFileWithNewline((__bridge CFStringRef)credential.description, stdout);
799 }
800 else {
801 result = 1;
802 }
803 }
804 else {
805 if (!do_delete && CFDictionaryGetValue(query, kSecUseAuthenticationUI) == NULL) {
806 CFDictionarySetValue(query, kSecUseAuthenticationUI, kSecUseAuthenticationUISkip);
807 }
808
809 CFTypeRef results = NULL;
810 OSStatus status = SecItemCopyMatching(query, &results);
811 if (status) {
812 sec_perror("SecItemCopyMatching", status);
813 } else if(json) {
814 display_results_json(results);
815 } else {
816 display_results(results);
817 }
818 CFReleaseNull(results);
819 }
820 }
821
822 out:
823 CFReleaseSafe(query);
824 CFReleaseSafe(update);
825 return result;
826 }
827
828 static int
829 keychain_find_or_delete_generic_password(Boolean do_delete,
830 int argc, char * const *argv)
831 {
832 char *serviceName = NULL, *accountName = NULL;
833 int ch, result = 0;
834 Boolean get_password = FALSE;
835
836 while ((ch = getopt(argc, argv, "a:s:g")) != -1)
837 {
838 switch (ch)
839 {
840 case 'a':
841 accountName = optarg;
842 break;
843 case 'g':
844 if (do_delete)
845 return SHOW_USAGE_MESSAGE;
846 get_password = TRUE;
847 break;
848 case 's':
849 serviceName = optarg;
850 break;
851 case '?':
852 default:
853 return SHOW_USAGE_MESSAGE;
854 }
855 }
856
857 result = do_keychain_find_or_delete_generic_password(do_delete,
858 serviceName, accountName, get_password);
859
860 return result;
861 }
862
863 int
864 keychain_find_generic_password(int argc, char * const *argv) {
865 return keychain_find_or_delete_generic_password(0, argc, argv);
866 }
867
868 int
869 keychain_delete_generic_password(int argc, char * const *argv) {
870 return keychain_find_or_delete_generic_password(1, argc, argv);
871 }
872
873 int keychain_item_digest(int argc, char * const *argv) {
874 NSString *itemClass = @"inet";
875 NSString *accessGroup = @"com.apple.ProtectedCloudStorage";
876
877 dispatch_semaphore_t sema = dispatch_semaphore_create(0);
878
879 if (argc == 3) {
880 itemClass = [NSString stringWithUTF8String:argv[1]];
881 accessGroup = [NSString stringWithUTF8String:argv[2]];
882 }
883
884 _SecItemFetchDigests(itemClass, accessGroup, ^(NSArray *items, NSError *error) {
885 for (NSDictionary *item in items) {
886 for (NSString *key in item) {
887 printf("%s\n", [[NSString stringWithFormat:@"%@\t\t%@", key, item[key]] UTF8String]);
888 }
889 }
890 if (error) {
891 printf("%s\n", [[NSString stringWithFormat:@"Failed to find items (%@/%@): %@", itemClass, accessGroup, error] UTF8String]);
892 }
893 dispatch_semaphore_signal(sema);
894 });
895 dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
896
897 return 0;
898 }
899
900
901 #if TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR
902
903 int
904 keychain_roll_keys(int argc, char * const *argv) {
905 int ch, result = 0;
906 bool force = false;
907
908 while ((ch = getopt(argc, argv, "f")) != -1)
909 {
910 switch (ch)
911 {
912 case 'f':
913 force = true;
914 break;
915 default:
916 return SHOW_USAGE_MESSAGE;
917 }
918 }
919 // argc -= optind;
920 // argv += optind;
921
922 (void) argc; // These are set so folks could use them
923 (void) argv; // silence the analyzer since they're not used
924
925 CFErrorRef error = NULL;
926 bool ok = _SecKeychainRollKeys(force, &error);
927
928 fprintf(stderr, "Keychain keys up to date: %s\n", ok ? "yes" : "no");
929 if (!ok && error) {
930 result = (int)CFErrorGetCode(error);
931 CFShow(error);
932 }
933
934 return result;
935 }
936
937 #endif