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@
31 #include <sys/utsname.h>
35 #include <readpassphrase.h>
37 #include <Security/SecItem.h>
39 #include <CoreFoundation/CFNumber.h>
40 #include <CoreFoundation/CFString.h>
42 #include <Security/SecureObjectSync/SOSCloudCircle.h>
43 #include <Security/SecureObjectSync/SOSCloudCircleInternal.h>
44 #include <Security/SecureObjectSync/SOSPeerInfo.h>
45 #include "keychain/SecureObjectSync/SOSPeerInfoPriv.h"
46 #include "keychain/SecureObjectSync/SOSPeerInfoV2.h"
47 #include "keychain/SecureObjectSync/SOSUserKeygen.h"
48 #include "keychain/SecureObjectSync/SOSKVSKeys.h"
49 #include "keychain/securityd/SOSCloudCircleServer.h"
50 #include <Security/SecureObjectSync/SOSBackupSliceKeyBag.h>
51 #include <Security/SecOTRSession.h>
52 #include "keychain/SecureObjectSync/CKBridge/SOSCloudKeychainClient.h"
54 #include <utilities/SecCFWrappers.h>
55 #include <utilities/debugging.h>
57 #include "SecurityTool/sharedTool/readline.h"
60 #include "keychain_sync.h"
61 #include "keychain_log.h"
63 #include "secToolFileIO.h"
64 #include "secViewDisplay.h"
65 #include "accountCirclesViewsPrint.h"
67 #include <Security/SecPasswordGenerate.h>
69 #define MAXKVSKEYTYPE kUnknownKey
70 #define DATE_LENGTH 18
73 static bool clearAllKVS(CFErrorRef *error)
75 __block bool result = false;
76 const uint64_t maxTimeToWaitInSeconds = 30ull * NSEC_PER_SEC;
77 dispatch_queue_t processQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
78 dispatch_semaphore_t waitSemaphore = dispatch_semaphore_create(0);
79 dispatch_time_t finishTime = dispatch_time(DISPATCH_TIME_NOW, maxTimeToWaitInSeconds);
81 secnotice("circleOps", "security tool called SOSCloudKeychainClearAll to clear KVS");
82 SOSCloudKeychainClearAll(processQueue, ^(CFDictionaryRef returnedValues, CFErrorRef cerror)
84 result = (cerror != NULL);
85 dispatch_semaphore_signal(waitSemaphore);
88 dispatch_semaphore_wait(waitSemaphore, finishTime);
93 static bool enableDefaultViews()
96 CFMutableSetRef viewsToEnable = SOSViewCopyViewSet(kViewSetV0);
97 CFMutableSetRef viewsToDisable = CFSetCreateMutable(NULL, 0, NULL);
99 result = SOSCCViewSet(viewsToEnable, viewsToDisable);
100 CFRelease(viewsToEnable);
101 CFRelease(viewsToDisable);
105 static bool requestToJoinCircle(CFErrorRef *error)
107 // Set the visual state of switch based on membership in circle
108 bool hadError = false;
109 SOSCCStatus ccstatus = SOSCCThisDeviceIsInCircle(error);
113 case kSOSCCCircleAbsent:
114 hadError = !SOSCCResetToOffering(error);
115 hadError &= enableDefaultViews();
117 case kSOSCCNotInCircle:
118 hadError = !SOSCCRequestToJoinCircle(error);
119 hadError &= enableDefaultViews();
122 printerr(CFSTR("Request to join circle with bad status: %@ (%d)\n"), SOSCCGetStatusDescription(ccstatus), ccstatus);
128 static bool setPassword(char *labelAndPassword, CFErrorRef *err)
131 char *token0 = strtok_r(labelAndPassword, ":", &last);
132 char *token1 = strtok_r(NULL, "", &last);
133 CFStringRef label = token1 ? CFStringCreateWithCString(NULL, token0, kCFStringEncodingUTF8) : CFSTR("security command line tool");
134 char *password_token = token1 ? token1 : token0;
135 password_token = password_token ? password_token : "";
136 CFDataRef password = CFDataCreate(NULL, (const UInt8*) password_token, strlen(password_token));
137 bool returned = !SOSCCSetUserCredentials(label, password, err);
143 static bool tryPassword(char *labelAndPassword, CFErrorRef *err)
146 char *token0 = strtok_r(labelAndPassword, ":", &last);
147 char *token1 = strtok_r(NULL, "", &last);
148 CFStringRef label = token1 ? CFStringCreateWithCString(NULL, token0, kCFStringEncodingUTF8) : CFSTR("security command line tool");
149 char *password_token = token1 ? token1 : token0;
150 password_token = password_token ? password_token : "";
151 CFDataRef password = CFDataCreate(NULL, (const UInt8*) password_token, strlen(password_token));
152 bool returned = !SOSCCTryUserCredentials(label, password, err);
159 * Prompt user, call SOSCCTryUserCredentials.
160 * Does not support optional label syntax like -T/-P.
161 * Returns true on success.
164 promptAndTryPassword(CFErrorRef *error)
166 bool success = false;
170 if (readpassphrase("iCloud password: ", passbuf, sizeof(passbuf), RPP_REQUIRE_TTY) != NULL) {
171 password = CFDataCreate(NULL, (const UInt8 *)passbuf, strlen(passbuf));
172 if (password != NULL) {
173 success = SOSCCTryUserCredentials(CFSTR("security command line tool"), password, error);
174 CFReleaseNull(password);
181 static bool syncAndWait(CFErrorRef *err)
183 __block CFTypeRef objects = NULL;
185 dispatch_queue_t generalq = dispatch_queue_create("general", DISPATCH_QUEUE_SERIAL);
187 const uint64_t maxTimeToWaitInSeconds = 30ull * NSEC_PER_SEC;
188 dispatch_semaphore_t waitSemaphore = dispatch_semaphore_create(0);
189 dispatch_time_t finishTime = dispatch_time(DISPATCH_TIME_NOW, maxTimeToWaitInSeconds);
191 CloudKeychainReplyBlock replyBlock = ^ (CFDictionaryRef returnedValues, CFErrorRef error)
193 secinfo("sync", "SOSCloudKeychainSynchronizeAndWait returned: %@", returnedValues);
195 secerror("SOSCloudKeychainSynchronizeAndWait returned error: %@", error);
196 objects = CFRetainSafe(returnedValues);
198 secinfo("sync", "SOSCloudKeychainGetObjectsFromCloud block exit: %@", objects);
199 dispatch_semaphore_signal(waitSemaphore);
202 SOSCloudKeychainSynchronizeAndWait(generalq, replyBlock);
204 dispatch_semaphore_wait(waitSemaphore, finishTime);
206 (void)SOSCCDumpCircleKVSInformation(NULL);
207 fprintf(outFile, "\n");
211 static void dumpStringSet(CFStringRef label, CFSetRef s) {
212 if(!s || !label) return;
214 printmsg(CFSTR("%@: { "), label);
215 __block bool first = true;
216 CFSetForEach(s, ^(const void *p) {
217 CFStringRef fmt = CFSTR(", %@");
221 CFStringRef string = (CFStringRef) p;
222 printmsg(fmt, string);
225 printmsg(CFSTR(" }\n"), NULL);
228 static bool dumpMyPeer(CFErrorRef *error) {
229 SOSPeerInfoRef myPeer = SOSCCCopyMyPeerInfo(error);
231 if (!myPeer) return false;
233 CFStringRef peerID = SOSPeerInfoGetPeerID(myPeer);
234 CFStringRef peerName = SOSPeerInfoGetPeerName(myPeer);
235 CFIndex peerVersion = SOSPeerInfoGetVersion(myPeer);
236 bool retirement = SOSPeerInfoIsRetirementTicket(myPeer);
238 printmsg(CFSTR("Peer Name: %@ PeerID: %@ Version: %d\n"), peerName, peerID, peerVersion);
240 CFDateRef retdate = SOSPeerInfoGetRetirementDate(myPeer);
241 printmsg(CFSTR("Retired: %@\n"), retdate);
245 if(peerVersion >= 2) {
246 CFMutableSetRef views = SOSPeerInfoV2DictionaryCopySet(myPeer, sViewsKey);
247 CFStringRef serialNumber = SOSPeerInfoV2DictionaryCopyString(myPeer, sSerialNumberKey);
248 CFBooleanRef preferIDS = SOSPeerInfoV2DictionaryCopyBoolean(myPeer, sPreferIDS);
249 CFBooleanRef preferIDSFragmentation = SOSPeerInfoV2DictionaryCopyBoolean(myPeer, sPreferIDSFragmentation);
250 CFBooleanRef preferIDSACKModel = SOSPeerInfoV2DictionaryCopyBoolean(myPeer, sPreferIDSACKModel);
251 CFStringRef transportType = SOSPeerInfoV2DictionaryCopyString(myPeer, sTransportType);
252 CFStringRef idsDeviceID = SOSPeerInfoV2DictionaryCopyString(myPeer, sDeviceID);
254 printmsg(CFSTR("Serial#: %@ PrefIDS#: %@ PrefFragmentation#: %@ PrefACK#: %@ transportType#: %@ idsDeviceID#: %@\n"),
255 serialNumber, preferIDS, preferIDSFragmentation, preferIDSACKModel, transportType, idsDeviceID);
257 printmsg(CFSTR("Serial#: %@\n"),
259 dumpStringSet(CFSTR(" Views: "), views);
262 CFReleaseSafe(serialNumber);
263 CFReleaseSafe(preferIDS);
264 CFReleaseSafe(preferIDSFragmentation);
265 CFReleaseSafe(views);
266 CFReleaseSafe(transportType);
267 CFReleaseSafe(idsDeviceID);
270 bool ret = myPeer != NULL;
271 CFReleaseNull(myPeer);
275 static bool setBag(char *itemName, CFErrorRef *err)
277 __block bool success = false;
278 __block CFErrorRef error = NULL;
280 CFStringRef random = SecPasswordCreateWithRandomDigits(10, NULL);
282 CFStringPerformWithUTF8CFData(random, ^(CFDataRef stringAsData) {
283 if (0 == strncasecmp(optarg, "single", 6) || 0 == strncasecmp(optarg, "all", 3)) {
284 bool includeV0 = (0 == strncasecmp(optarg, "all", 3));
285 printmsg(CFSTR("Setting iCSC single using entropy from string: %@\n"), random);
286 CFDataRef aks_bag = SecAKSCopyBackupBagWithSecret(CFDataGetLength(stringAsData), (uint8_t*)CFDataGetBytePtr(stringAsData), &error);
289 success = SOSCCRegisterSingleRecoverySecret(aks_bag, includeV0, &error);
291 printmsg(CFSTR("Failed registering single secret %@"), error);
292 CFReleaseNull(aks_bag);
295 printmsg(CFSTR("Failed to create aks_bag: %@"), error);
297 CFReleaseNull(aks_bag);
298 } else if (0 == strncasecmp(optarg, "device", 6)) {
299 printmsg(CFSTR("Setting Device Secret using entropy from string: %@\n"), random);
301 SOSPeerInfoRef me = SOSCCCopyMyPeerWithNewDeviceRecoverySecret(stringAsData, &error);
303 success = me != NULL;
306 printmsg(CFSTR("Failed: %@\n"), err);
309 printmsg(CFSTR("Unrecognized argument to -b %s\n"), optarg);
317 static void prClientViewState(char *label, bool result) {
318 fprintf(outFile, "Sync Status for %s: %s\n", label, (result) ? "enabled": "not enabled");
321 static bool clientViewStatus(CFErrorRef *error) {
322 prClientViewState("KeychainV0", SOSCCIsIcloudKeychainSyncing());
323 prClientViewState("Safari", SOSCCIsSafariSyncing());
324 prClientViewState("AppleTV", SOSCCIsAppleTVSyncing());
325 prClientViewState("HomeKit", SOSCCIsHomeKitSyncing());
326 prClientViewState("Wifi", SOSCCIsWiFiSyncing());
327 prClientViewState("AlwaysOnNoInitialSync", SOSCCIsContinuityUnlockSyncing());
332 #pragma mark --remove-peer
335 add_matching_peerinfos(CFMutableArrayRef list, CFArrayRef spids, CFArrayRef (*copy_peer_func)(CFErrorRef *))
343 peers = copy_peer_func(&error);
345 for (i = 0; i < CFArrayGetCount(peers); i++) {
346 pi = (SOSPeerInfoRef)CFArrayGetValueAtIndex(peers, i);
347 for (j = 0; j < CFArrayGetCount(spids); j++) {
348 spid = (CFStringRef)CFArrayGetValueAtIndex(spids, j);
349 if (CFStringGetLength(spid) < 8) {
352 if (CFStringHasPrefix(SOSPeerInfoGetPeerID(pi), spid)) {
353 CFArrayAppendValue(list, pi);
366 copy_peerinfos(CFArrayRef spids)
368 CFMutableArrayRef matches;
370 matches = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
371 add_matching_peerinfos(matches, spids, SOSCCCopyValidPeerPeerInfo);
372 add_matching_peerinfos(matches, spids, SOSCCCopyNotValidPeerPeerInfo);
373 add_matching_peerinfos(matches, spids, SOSCCCopyRetirementPeerInfo);
379 doRemovePeers(CFArrayRef peerids, CFErrorRef *error)
381 bool success = false;
382 CFArrayRef peers = NULL;
383 CFErrorRef localError = NULL;
387 peers = copy_peerinfos(peerids);
388 if (peers == NULL || CFArrayGetCount(peers) == 0) {
389 fprintf(stdout, "No matching peers to remove.\n");
394 fprintf(stdout, "Matched the following devices:\n");
395 for (i = 0; i < CFArrayGetCount(peers); i++) {
397 CFShow(CFArrayGetValueAtIndex(peers, i));
400 if (readpassphrase("Confirm removal (y/N): ", buf, sizeof(buf), RPP_ECHO_ON | RPP_FORCEUPPER) == NULL) {
409 success = SOSCCRemovePeersFromCircle(peers, &localError);
410 if (!success && isSOSErrorCoded(localError, kSOSErrorPrivateKeyAbsent)) {
411 CFReleaseNull(localError);
413 success = promptAndTryPassword(&localError);
415 success = SOSCCRemovePeersFromCircle(peers, &localError);
420 CFReleaseNull(peers);
422 if (!success && error != NULL) {
425 CFReleaseNull(localError);
433 // enable, disable, accept, reject, status, Reset, Clear
435 keychain_sync(int argc, char * const *argv)
440 " -e enable (join/create circle)"
441 " -i info (current status)"
444 "Account/Circle Management"
445 " -a accept all applicants"
446 " -r reject all applicants"
447 " -b device|all|single Register a backup bag - THIS RESETS BACKUPS!\n"
449 " -N (re-)set to new account (USE WITH CARE: device will not leave circle before resetting account!)"
450 " -O reset to offering"
452 " -o list view unaware peers in circle"
453 " -0 boot view unaware peers from circle"
454 " -5 cleanup old KVS keys in KVS"
457 " --remove-peer SPID Remove a peer identified by the first 8 or more\n"
458 " characters of its spid. Specify multiple times to\n"
459 " remove more than one peer.\n"
461 " -P [label:]password set password (optionally for a given label) for sync"
462 " -T [label:]password try password (optionally for a given label) for sync"
465 " -k pend all registered kvs keys"
466 " -C clear all values from KVS"
467 " -D [itemName] dump contents of KVS"
471 " -v [enable|disable|query:viewname] enable, disable, or query my PeerInfo's view set"
472 " viewnames are: keychain|masterkey|iclouddrive|photos|cloudkit|escrow|fde|maildrop|icloudbackup|notes|imessage|appletv|homekit|"
473 " wifi|passwords|creditcards|icloudidentity|othersyncable"
474 " -L list all known view and their status"
475 " -U purge private key material cache\n"
476 " -V Report View Sync Status on all known clients.\n"
482 const struct option longopts[] = {
483 { "remove-peer", required_argument, &action, SYNC_REMOVE_PEER, },
484 { NULL, 0, NULL, 0, },
487 CFErrorRef error = NULL;
488 bool hadError = false;
489 CFMutableArrayRef peers2remove = NULL;
490 SOSLogSetOutputTo(NULL, NULL);
492 while ((ch = getopt_long(argc, argv, "ab:deikmorv:NCDLOP:RT:UWV05", longopts, NULL)) != -1) {
496 CFArrayRef applicants = SOSCCCopyApplicantPeerInfo(NULL);
498 hadError = !SOSCCAcceptApplicants(applicants, &error);
499 CFRelease(applicants);
501 fprintf(errFile, "No applicants to accept\n");
507 hadError = setBag(optarg, &error);
512 fprintf(outFile, "Turning OFF keychain syncing\n");
513 hadError = !SOSCCRemoveThisDeviceFromCircle(&error);
518 fprintf(outFile, "Turning ON keychain syncing\n");
519 hadError = requestToJoinCircle(&error);
524 SOSCCDumpCircleInformation();
525 SOSCCDumpEngineInformation();
530 notify_post("com.apple.security.cloudkeychain.forceupdate");
535 hadError = !dumpMyPeer(&error);
540 SOSCCDumpViewUnwarePeers();
545 CFArrayRef applicants = SOSCCCopyApplicantPeerInfo(NULL);
547 hadError = !SOSCCRejectApplicants(applicants, &error);
548 CFRelease(applicants);
550 fprintf(errFile, "No applicants to reject\n");
556 hadError = !viewcmd(optarg, &error);
561 hadError = clearAllKVS(&error);
566 (void)SOSCCDumpCircleKVSInformation(optarg);
571 hadError = !listviewcmd(&error);
576 hadError = !SOSCCAccountSetToNew(&error);
578 notify_post(kSOSCCCircleChangedNotification);
583 hadError = !SOSCCResetToOffering(&error);
588 hadError = setPassword(optarg, &error);
593 hadError = !SOSCCResetToEmpty(&error);
598 hadError = tryPassword(optarg, &error);
603 hadError = !SOSCCPurgeUserCredentials(&error);
608 hadError = clientViewStatus(&error);
613 hadError = syncAndWait(&error);
618 CFArrayRef unawares = SOSCCCopyViewUnawarePeerInfo(&error);
620 hadError = !SOSCCRemovePeersFromCircle(unawares, &error);
624 CFReleaseNull(unawares);
629 bool result = SOSCCCleanupKVSKeys(&error);
632 printmsg(CFSTR("Got all the keys from KVS %d\n"), result);
640 if (action == SYNC_REMOVE_PEER) {
641 CFStringRef optstr = CFStringCreateWithCString(NULL, optarg, kCFStringEncodingUTF8);
642 if (peers2remove == NULL) {
643 peers2remove = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
645 CFArrayAppendValue(peers2remove, optstr);
646 CFReleaseNull(optstr);
648 return SHOW_USAGE_MESSAGE;
654 return SHOW_USAGE_MESSAGE;
658 if (peers2remove != NULL) {
659 hadError = !doRemovePeers(peers2remove, &error);
660 CFRelease(peers2remove);
664 printerr(CFSTR("Error: %@\n"), error);