2 * Copyright (c) 2003-2007,2009-2010,2013-2014 Apple Inc. All Rights Reserved.
4 * @APPLE_LICENSE_HEADER_START@
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
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.
21 * @APPLE_LICENSE_HEADER_END@
26 #include <CoreFoundation/CoreFoundation.h>
27 #import <Foundation/Foundation.h>
29 #include <Security/SecItem.h>
30 #include <Security/SecItemPriv.h>
32 #include <SecurityTool/tool_errors.h>
33 #include <SecurityTool/readline.h>
35 #include <utilities/SecCFWrappers.h>
37 #include "SecurityCommands.h"
39 #include "keychain_util.h"
40 #include <Security/SecAccessControl.h>
41 #include <Security/SecAccessControlPriv.h>
43 #import <SecurityFoundation/SFKeychain.h>
47 #ifndef _SECURITY_SECKEYCHAIN_H_
48 typedef uint32_t SecProtocolType;
49 typedef uint32_t SecAuthenticationType;
53 static CFMutableDictionaryRef
54 keychain_create_query_from_string(const char *query) {
55 CFMutableDictionaryRef q;
57 q = CFDictionaryCreateMutable(0, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
58 if (!keychain_query_parse_cstring(q, query)) {
64 static void add_key(const void *key, const void *value, void *context) {
65 CFArrayAppendValue(context, key);
68 static bool isPrintableString(CFStringRef theString){
70 CFCharacterSetRef controlSet = CFCharacterSetGetPredefined(kCFCharacterSetControl);
71 CFCharacterSetRef newlineSet = CFCharacterSetGetPredefined(kCFCharacterSetNewline);
72 CFCharacterSetRef illegalSet = CFCharacterSetGetPredefined(kCFCharacterSetIllegal);
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);
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? */
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);
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);
101 CFStringAppend(line, key);
103 for (jx = CFStringGetLength(key);
104 jx < maxWidth; ++jx) {
105 CFStringAppend(line, CFSTR(" "));
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);
122 if(!isPrintableString(v_s))
123 CFStringAppend(line, CFSTR("not printable "));
125 CFStringAppend(line, CFSTR("/"));
126 CFStringAppend(line, v_s);
127 CFStringAppend(line, CFSTR("/ "));
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]);
137 } else if (SecAccessControlGetTypeID() == CFGetTypeID(value)) {
138 display_sac_line((SecAccessControlRef)value, line);
140 CFStringAppendFormat(line, NULL, CFSTR("%@"), value);
143 CFStringWriteToFileWithNewline(line, stdout);
149 CFStringWriteToFileWithNewline(CFSTR("===="), stdout);
155 static void display_results(CFTypeRef results) {
156 if (CFGetTypeID(results) == CFArrayGetTypeID()) {
157 CFArrayRef r_a = (CFArrayRef)results;
158 CFArrayApplyFunction(r_a, CFRangeMake(0, CFArrayGetCount(r_a)),
160 } else if (CFGetTypeID(results) == CFDictionaryGetTypeID()) {
161 display_item(results, NULL);
163 fprintf(stderr, "SecItemCopyMatching returned unexpected results:");
168 static OSStatus do_find_or_delete(CFDictionaryRef query, bool do_delete) {
171 result = SecItemDelete(query);
173 sec_perror("SecItemDelete", result);
176 CFTypeRef results = NULL;
177 result = SecItemCopyMatching(query, &results);
179 sec_perror("SecItemCopyMatching", result);
181 display_results(results);
183 CFReleaseSafe(results);
189 do_keychain_find_or_delete_internet_password(Boolean do_delete,
190 const char *serverName, const char *securityDomain,
191 const char *accountName, const char *path, UInt16 port,
192 SecProtocolType protocol, SecAuthenticationType authenticationType,
193 Boolean get_password)
196 CFDictionaryRef query = NULL;
197 const void *keys[11], *values[11];
200 if (do_delete && !serverName && !securityDomain && !accountName && !path && !port && !protocol && !authenticationType) {
201 return SHOW_USAGE_MESSAGE;
204 keys[ix] = kSecClass;
205 values[ix++] = kSecClassInternetPassword;
207 keys[ix] = kSecAttrServer;
208 values[ix++] = CFStringCreateWithCStringNoCopy(NULL, serverName,
209 kCFStringEncodingUTF8, kCFAllocatorNull);
211 if (securityDomain) {
212 keys[ix] = kSecAttrSecurityDomain;
213 values[ix++] = CFStringCreateWithCStringNoCopy(NULL, securityDomain,
214 kCFStringEncodingUTF8, kCFAllocatorNull);
217 keys[ix] = kSecAttrAccount;
218 values[ix++] = CFStringCreateWithCStringNoCopy(NULL, accountName,
219 kCFStringEncodingUTF8, kCFAllocatorNull);
222 keys[ix] = kSecAttrPath;
223 values[ix++] = CFStringCreateWithCStringNoCopy(NULL, path,
224 kCFStringEncodingUTF8, kCFAllocatorNull);
227 keys[ix] = kSecAttrPort;
228 values[ix++] = CFNumberCreate(NULL, kCFNumberSInt16Type, &port);
231 /* Protocol is a 4 char code, perhaps we should use a string rep
233 keys[ix] = kSecAttrProtocol;
234 values[ix++] = CFNumberCreate(NULL, kCFNumberSInt32Type, &protocol);
236 if (authenticationType != 0) {
237 keys[ix] = kSecAttrAuthenticationType;
238 values[ix++] = CFNumberCreate(NULL, kCFNumberSInt32Type,
239 &authenticationType);
242 /* Only ask for the data if so required. */
243 keys[ix] = kSecReturnData;
244 values[ix++] = kCFBooleanTrue;
246 keys[ix] = kSecReturnAttributes;
247 values[ix++] = kCFBooleanTrue;
249 /* If we aren't deleting ask for all items. */
250 keys[ix] = kSecMatchLimit;
251 values[ix++] = kSecMatchLimitAll;
254 query = CFDictionaryCreate(NULL, keys, values, ix,
255 &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
256 result = do_find_or_delete(query, do_delete);
257 CFReleaseSafe(query);
263 parse_fourcharcode(const char *name, uint32_t *code)
265 /* @@@ Check for errors. */
266 char *p = (char *)code;
272 keychain_find_or_delete_internet_password(Boolean do_delete, int argc, char * const *argv)
274 char *serverName = NULL, *securityDomain = NULL, *accountName = NULL, *path = NULL;
276 SecProtocolType protocol = 0;
277 SecAuthenticationType authenticationType = 0;
279 Boolean get_password = FALSE;
281 while ((ch = getopt(argc, argv, "a:d:hgp:P:r:s:t:")) != -1)
286 accountName = optarg;
289 securityDomain = optarg;
293 return SHOW_USAGE_MESSAGE;
303 result = parse_fourcharcode(optarg, &protocol);
311 result = parse_fourcharcode(optarg, &authenticationType);
317 return SHOW_USAGE_MESSAGE;
321 result = do_keychain_find_or_delete_internet_password(do_delete, serverName, securityDomain,
322 accountName, path, port, protocol,authenticationType, get_password);
331 keychain_find_internet_password(int argc, char * const *argv) {
332 return keychain_find_or_delete_internet_password(0, argc, argv);
336 keychain_delete_internet_password(int argc, char * const *argv) {
337 return keychain_find_or_delete_internet_password(1, argc, argv);
341 do_keychain_find_or_delete_generic_password(Boolean do_delete,
342 const char *serviceName, const char *accountName,
343 Boolean get_password)
346 CFDictionaryRef query = NULL;
347 const void *keys[6], *values[6];
350 if (do_delete && !serviceName && !accountName) {
351 return SHOW_USAGE_MESSAGE;
354 keys[ix] = kSecClass;
355 values[ix++] = kSecClassGenericPassword;
357 keys[ix] = kSecAttrService;
358 values[ix++] = CFStringCreateWithCStringNoCopy(NULL, serviceName,
359 kCFStringEncodingUTF8, kCFAllocatorNull);
362 keys[ix] = kSecAttrAccount;
363 values[ix++] = CFStringCreateWithCStringNoCopy(NULL, accountName,
364 kCFStringEncodingUTF8, kCFAllocatorNull);
367 /* Only ask for the data if so required. */
368 keys[ix] = kSecReturnData;
369 values[ix++] = kCFBooleanTrue;
371 keys[ix] = kSecReturnAttributes;
372 values[ix++] = kCFBooleanTrue;
374 /* If we aren't deleting ask for all items. */
375 keys[ix] = kSecMatchLimit;
376 values[ix++] = kSecMatchLimitAll;
379 query = CFDictionaryCreate(NULL, keys, values, ix,
380 &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
382 result = do_find_or_delete(query, do_delete);
384 CFReleaseSafe(query);
389 static SFServiceIdentifier* serviceIdentifierInQuery(NSDictionary* query)
391 NSString* domain = query[@"domain"];
392 NSString* bundleID = query[@"bundleID"];
393 NSString* accessGroup = query[@"accessGroup"];
394 NSString* customServiceID = query[@"customServiceID"];
396 SFServiceIdentifier* serviceIdentifier = nil;
398 serviceIdentifier = [[SFServiceIdentifier alloc] initWithServiceID:domain forType:SFServiceIdentifierTypeDomain];
401 serviceIdentifier = [[SFServiceIdentifier alloc] initWithServiceID:bundleID forType:SFServiceIdentifierTypeBundleID];
403 else if (accessGroup) {
404 serviceIdentifier = [[SFServiceIdentifier alloc] initWithServiceID:accessGroup forType:SFServiceIdentifierTypeAccessGroup];
406 else if (customServiceID) {
407 serviceIdentifier = [[SFServiceIdentifier alloc] initWithServiceID:customServiceID forType:SFServiceIdentifierTypeCustom];
409 if (!serviceIdentifier) {
410 sec_error("need service identifier");
413 return serviceIdentifier;
416 static SFPasswordCredential* lookupCredential(SFServiceIdentifier* serviceIdentifier, NSString* username)
418 __block SFPasswordCredential* result = nil;
419 dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
420 [[SFCredentialStore defaultCredentialStore] lookupCredentialsForServiceIdentifiers:@[serviceIdentifier] withResultHandler:^(NSArray<SFCredential*>* results, NSError* error) {
422 sec_error("error looking up credentials: %s", error.description.UTF8String);
425 bool foundCredential = false;
426 for (SFPasswordCredential* credential in results) {
427 if ([credential.username isEqualToString:username]) {
429 dispatch_semaphore_signal(semaphore);
434 if (!foundCredential) {
435 sec_error("did not find credential");
439 dispatch_semaphore_signal(semaphore);
441 if (dispatch_semaphore_wait(semaphore, dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC))) {
442 sec_error("timed out trying to communicate with credential store");
448 static SFPasswordCredential* credentialFromQuery(NSDictionary* query)
450 NSString* username = query[@"username"];
451 NSData* passwordData = query[(__bridge id)kSecValueData];
452 NSString* password = [[NSString alloc] initWithData:passwordData encoding:NSUTF8StringEncoding];
454 sec_error("need username");
458 sec_error("need password");
462 SFServiceIdentifier* serviceIdentifier = serviceIdentifierInQuery(query);
464 if (username && password && serviceIdentifier) {
465 return [[SFPasswordCredential alloc] initWithUsername:username password:password primaryServiceIdentifier:serviceIdentifier];
472 static SFAccessPolicy* accessPolicyInQuery(NSDictionary* query)
474 CFStringRef accessibility = (__bridge CFStringRef)query[(__bridge id)kSecAttrAccessible];
475 if (!accessibility) {
476 accessibility = kSecAttrAccessibleWhenUnlocked;
479 SFAccessPolicy* accessPolicy = [SFAccessPolicy accessPolicyWithSecAccessibility:accessibility error:nil];
480 NSNumber* synchronizableValue = query[(__bridge id)kSecAttrSynchronizable];
481 if (accessPolicy.sharingPolicy == SFSharingPolicyWithTrustedDevices && synchronizableValue && synchronizableValue.boolValue == NO) {
482 accessPolicy.sharingPolicy = SFSharingPolicyWithBackup;
485 sec_error("need access policy");
491 int keychain_item(int argc, char * const *argv) {
493 __block int result = 0;
494 CFMutableDictionaryRef query, update = NULL;
495 bool get_password = false;
496 bool do_delete = false;
498 bool verbose = false;
501 query = CFDictionaryCreateMutable(0, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
503 CFDictionarySetValue(query, kSecAttrNoLegacy, kCFBooleanTrue);
506 while ((ch = getopt(argc, argv, "ad:Df:gq:u:vl:")) != -1)
518 CFStringRef dataString = CFStringCreateWithCString(0, optarg, kCFStringEncodingUTF8);
520 CFDataRef data = CFStringCreateExternalRepresentation(kCFAllocatorDefault, dataString, kCFStringEncodingUTF8, 0);
522 CFDictionarySetValue(update ? update : query, kSecValueData, data);
525 CFRelease(dataString);
534 CFDataRef data = copyFileContents(optarg);
535 CFDictionarySetValue(update ? update : query, kSecValueData, data);
543 if (!keychain_query_parse_cstring(query, optarg)) {
552 update = keychain_create_query_from_string(optarg);
554 success = keychain_query_parse_cstring(update, optarg);
555 if (update == NULL || !success) {
565 limit = atoi(optarg);
569 /* Return 2 triggers usage message. */
575 if (((do_add || do_delete) && (get_password || update)) || !query) {
584 for (ix = 0; ix < argc; ++ix) {
585 if (!keychain_query_parse_cstring(query, argv[ix])) {
591 if (!update && !do_add && !do_delete) {
592 CFDictionarySetValue(query, kSecReturnAttributes, kCFBooleanTrue);
594 CFNumberRef cfLimit = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &limit);
595 CFDictionarySetValue(query, kSecMatchLimit, cfLimit);
596 CFReleaseSafe(cfLimit);
598 CFDictionarySetValue(query, kSecMatchLimit, kSecMatchLimitAll);
601 CFDictionarySetValue(query, kSecReturnData, kCFBooleanTrue);
608 bool useCredentialStore = [(__bridge id)CFDictionaryGetValue(query, kSecClass) isEqual:@"credential"];
610 if (useCredentialStore) {
611 SFPasswordCredential* credential = credentialFromQuery((__bridge NSDictionary*)query);
612 SFAccessPolicy* accessPolicy = accessPolicyInQuery((__bridge NSDictionary*)query);
613 if (credential && accessPolicy) {
614 dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
615 [[SFCredentialStore defaultCredentialStore] addCredential:credential withAccessPolicy:accessPolicy resultHandler:^(NSString* persistentIdentifier, NSError* error) {
617 sec_error("error adding credential to credential store: %s", error.description.UTF8String);
620 dispatch_semaphore_signal(semaphore);
622 if (dispatch_semaphore_wait(semaphore, dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC))) {
623 sec_error("timed out waiting for response from credential store");
628 sec_error("need username, password, service identiifer, and access policy to create a credential");
633 error = SecItemAdd(query, NULL);
635 sec_perror("SecItemAdd", error);
640 if (useCredentialStore) {
641 NSString* username = (__bridge NSString*)CFDictionaryGetValue(query, CFSTR("username"));
642 SFServiceIdentifier* serviceIdentifier = serviceIdentifierInQuery((__bridge NSDictionary*)query);
643 SFPasswordCredential* credential = lookupCredential(serviceIdentifier, username);
645 SFPasswordCredential* updatedCredential = credentialFromQuery((__bridge NSDictionary*)update);
646 dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
647 [[SFCredentialStore defaultCredentialStore] replaceOldCredential:credential withNewCredential:updatedCredential resultHandler:^(NSString* newPersistentIdentifier, NSError* error) {
649 sec_error("error updating credential: %s", error.description.UTF8String);
652 dispatch_semaphore_signal(semaphore);
654 if (dispatch_semaphore_wait(semaphore, dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC))) {
655 sec_error("timed out trying to communicate with credential store");
664 error = SecItemUpdate(query, update);
666 sec_perror("SecItemUpdate", error);
670 } else if (do_delete) {
671 if (useCredentialStore) {
672 NSString* username = (__bridge NSString*)CFDictionaryGetValue(query, CFSTR("username"));
673 SFServiceIdentifier* serviceIdentifier = serviceIdentifierInQuery((__bridge NSDictionary*)query);
674 SFPasswordCredential* credential = lookupCredential(serviceIdentifier, username);
676 dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
677 [[SFCredentialStore defaultCredentialStore] removeCredentialWithPersistentIdentifier:credential.persistentIdentifier withResultHandler:^(BOOL success, NSError* error) {
679 sec_error("failed to remove credential with error: %s", error.description.UTF8String);
682 dispatch_semaphore_signal(semaphore);
684 if (dispatch_semaphore_wait(semaphore, dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC))) {
685 sec_error("timed out trying to communicate with credential store");
694 error = SecItemDelete(query);
696 sec_perror("SecItemDelete", error);
701 if (useCredentialStore) {
702 NSString* username = (__bridge NSString*)CFDictionaryGetValue(query, CFSTR("username"));
703 SFServiceIdentifier* serviceIdentifier = serviceIdentifierInQuery((__bridge NSDictionary*)query);
704 SFPasswordCredential* credential = lookupCredential(serviceIdentifier, username);
706 CFStringWriteToFileWithNewline((__bridge CFStringRef)credential.description, stdout);
713 if (!do_delete && CFDictionaryGetValue(query, kSecUseAuthenticationUI) == NULL) {
714 CFDictionarySetValue(query, kSecUseAuthenticationUI, kSecUseAuthenticationUISkip);
716 do_find_or_delete(query, do_delete);
721 CFReleaseSafe(query);
722 CFReleaseSafe(update);
727 keychain_find_or_delete_generic_password(Boolean do_delete,
728 int argc, char * const *argv)
730 char *serviceName = NULL, *accountName = NULL;
732 Boolean get_password = FALSE;
734 while ((ch = getopt(argc, argv, "a:s:g")) != -1)
739 accountName = optarg;
743 return SHOW_USAGE_MESSAGE;
747 serviceName = optarg;
751 return SHOW_USAGE_MESSAGE;
755 result = do_keychain_find_or_delete_generic_password(do_delete,
756 serviceName, accountName, get_password);
762 keychain_find_generic_password(int argc, char * const *argv) {
763 return keychain_find_or_delete_generic_password(0, argc, argv);
767 keychain_delete_generic_password(int argc, char * const *argv) {
768 return keychain_find_or_delete_generic_password(1, argc, argv);
771 int keychain_item_digest(int argc, char * const *argv) {
772 NSString *itemClass = @"inet";
773 NSString *accessGroup = @"com.apple.ProtectedCloudStorage";
775 dispatch_semaphore_t sema = dispatch_semaphore_create(0);
778 itemClass = [NSString stringWithUTF8String:argv[1]];
779 accessGroup = [NSString stringWithUTF8String:argv[2]];
782 _SecItemFetchDigests(itemClass, accessGroup, ^(NSArray *items, NSError *error) {
783 for (NSDictionary *item in items) {
784 for (NSString *key in item) {
785 printf("%s\n", [[NSString stringWithFormat:@"%@\t\t%@", key, item[key]] UTF8String]);
789 printf("%s\n", [[NSString stringWithFormat:@"Failed to find items (%@/%@): %@", itemClass, accessGroup, error] UTF8String]);
791 dispatch_semaphore_signal(sema);
793 dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
799 #if TARGET_OS_EMBEDDED
802 keychain_roll_keys(int argc, char * const *argv) {
806 while ((ch = getopt(argc, argv, "f")) != -1)
814 return SHOW_USAGE_MESSAGE;
820 (void) argc; // These are set so folks could use them
821 (void) argv; // silence the analyzer since they're not used
823 CFErrorRef error = NULL;
824 bool ok = _SecKeychainRollKeys(force, &error);
826 fprintf(stderr, "Keychain keys up to date: %s\n", ok ? "yes" : "no");
828 result = (int)CFErrorGetCode(error);