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_add.c
24 */
25
26
27#include <stdio.h>
28#include <stdlib.h>
29#include <string.h>
30#include <unistd.h>
31
32#include <Security/SecItem.h>
33
34#include <CoreFoundation/CFNumber.h>
35#include <CoreFoundation/CFString.h>
36
37#include <SecureObjectSync/SOSCloudCircle.h>
38#include <SecureObjectSync/SOSCloudCircleInternal.h>
39#include <SecureObjectSync/SOSPeerInfo.h>
40#include <SecureObjectSync/SOSKVSKeys.h>
41#include <securityd/SOSCloudCircleServer.h>
42
43#include <CKBridge/SOSCloudKeychainClient.h>
44
45#include <utilities/SecCFWrappers.h>
46#include <utilities/debugging.h>
47
48#include <SecurityTool/readline.h>
49#include <notify.h>
50
51#include "SOSCommands.h"
52
53#define printmsg(format, ...) _printcfmsg(stdout, format, __VA_ARGS__)
54#define printerr(format, ...) _printcfmsg(stderr, format, __VA_ARGS__)
55
56static void _printcfmsg(FILE *ff, CFStringRef format, ...)
57{
58    va_list args;
59    va_start(args, format);
60    CFStringRef message = CFStringCreateWithFormatAndArguments(kCFAllocatorDefault, NULL, format, args);
61    va_end(args);
62    CFStringPerformWithCString(message, ^(const char *utf8String) { fprintf(ff, utf8String, ""); });
63    CFRelease(message);
64}
65
66static bool clearAllKVS(CFErrorRef *error)
67{
68    __block bool result = false;
69    const uint64_t maxTimeToWaitInSeconds = 30ull * NSEC_PER_SEC;
70    dispatch_queue_t processQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
71    dispatch_semaphore_t waitSemaphore = dispatch_semaphore_create(0);
72    dispatch_time_t finishTime = dispatch_time(DISPATCH_TIME_NOW, maxTimeToWaitInSeconds);
73
74    SOSCloudKeychainClearAll(processQueue, ^(CFDictionaryRef returnedValues, CFErrorRef cerror)
75    {
76        result = (cerror != NULL);
77        dispatch_semaphore_signal(waitSemaphore);
78    });
79
80	dispatch_semaphore_wait(waitSemaphore, finishTime);
81    dispatch_release(waitSemaphore);
82
83    return result;
84}
85
86static const char *getSOSCCStatusDescription(SOSCCStatus ccstatus)
87{
88    switch (ccstatus)
89    {
90        case kSOSCCInCircle:        return "In Circle";
91        case kSOSCCNotInCircle:     return "Not in Circle";
92        case kSOSCCRequestPending:  return "Request pending";
93        case kSOSCCCircleAbsent:    return "Circle absent";
94        case kSOSCCError:           return "Circle error";
95
96        default:
97            return "<unknown ccstatus>";
98            break;
99    }
100}
101
102static void dumpCircleInfo()
103{
104    CFErrorRef error = NULL;
105    CFArrayRef peerPeerInfos = NULL;
106    CFArrayRef applicants = NULL;
107    CFArrayRef retirees = NULL;
108    CFArrayRef peerInfos = NULL;
109    CFArrayRef generations = NULL;
110    bool is_user_public_trusted = false;
111    __block int count = 0;
112
113    SOSCCStatus ccstatus = SOSCCThisDeviceIsInCircle(&error);
114    printmsg(CFSTR("ccstatus: %s (%d), error: %@\n"), getSOSCCStatusDescription(ccstatus), ccstatus, error);
115
116    if(ccstatus == kSOSCCError) {
117        printmsg(CFSTR("End of Dump - unable to proceed due to ccstatus -\n\t%s\n"), getSOSCCStatusDescription(ccstatus));
118        return;
119    }
120
121    is_user_public_trusted = SOSCCValidateUserPublic(&error);
122    if(is_user_public_trusted)
123        printmsg(CFSTR("Account user public is trusted%@"),CFSTR("\n"));
124    else
125        printmsg(CFSTR("Account user public is not trusted%@"),CFSTR("\n"));
126
127    // Now look at current peers
128    peerPeerInfos = SOSCCCopyValidPeerPeerInfo(&error);
129
130    if (peerPeerInfos)
131    {
132        printmsg(CFSTR("Valid Peers: %ld, error: %@\n"), (long)CFArrayGetCount(peerPeerInfos), error);
133        CFArrayForEach(peerPeerInfos, ^(const void *value) {
134            SOSPeerInfoRef peer = (SOSPeerInfoRef)value;
135            CFStringRef peerName = SOSPeerInfoGetPeerName(peer);
136            printmsg(CFSTR("Valid Peer: %@ (%@)\n"), peerName, peer);
137        });
138    }
139    else
140        printmsg(CFSTR("No peers, error: %@\n"), error);
141
142    CFReleaseNull(peerPeerInfos);
143    peerPeerInfos = SOSCCCopyNotValidPeerPeerInfo(&error);
144
145    if (peerPeerInfos)
146    {
147        printmsg(CFSTR("Non Valid Peers: %ld, error: %@\n"), (long)CFArrayGetCount(peerPeerInfos), error);
148        CFArrayForEach(peerPeerInfos, ^(const void *value) {
149            SOSPeerInfoRef peer = (SOSPeerInfoRef)value;
150            CFStringRef peerName = SOSPeerInfoGetPeerName(peer);
151            printmsg(CFSTR("Not Valid Peer: %@ (%@)\n"), peerName, peer);
152        });
153    }
154    CFReleaseNull(peerPeerInfos);
155
156    applicants = SOSCCCopyApplicantPeerInfo(&error);
157    if (applicants)
158    {
159        printmsg(CFSTR("Applicants: %ld, error: %@\n"), (long)CFArrayGetCount(applicants), error);
160        CFArrayForEach(applicants, ^(const void *value) {
161            SOSPeerInfoRef peer = (SOSPeerInfoRef)value;
162            CFStringRef peerName = SOSPeerInfoGetPeerName(peer);
163            printmsg(CFSTR("Applicant: %@ (%@)\n"), peerName, peer);
164        });
165    }
166    else
167        printmsg(CFSTR("No applicants, error: %@\n"), error);
168    CFReleaseNull(applicants);
169
170
171    peerInfos = SOSCCCopyConcurringPeerPeerInfo(&error);
172    if (peerInfos)
173    {
174        printmsg(CFSTR("Concurring Peers: %ld, error: %@\n"), (long)CFArrayGetCount(peerInfos), error);
175        CFArrayForEach(peerInfos, ^(const void *value) {
176            SOSPeerInfoRef peer = (SOSPeerInfoRef)value;
177            CFStringRef peerName = SOSPeerInfoGetPeerName(peer);
178            printmsg(CFSTR("Concurr: %@ (%@)\n"), peerName, peer);
179        });
180    }
181    else
182        printmsg(CFSTR("No concurring peers, error: %@\n"), error);
183
184    CFReleaseNull(peerInfos);
185    retirees = SOSCCCopyRetirementPeerInfo(&error);
186    if (retirees)
187    {
188        printmsg(CFSTR("Retired Peers: %ld, error: %@\n"), (long)CFArrayGetCount(retirees), error);
189        CFArrayForEach(retirees, ^(const void *value) {
190            SOSPeerInfoRef peer = (SOSPeerInfoRef)value;
191            CFStringRef peerName = SOSPeerInfoGetPeerName(peer);
192            printmsg(CFSTR("Retiree: %@ (%@)\n"), peerName, peer);
193        });
194    }
195    else
196        printmsg(CFSTR("No retired peers, error: %@\n"), error);
197    CFReleaseNull(retirees);
198
199    generations = SOSCCCopyGenerationPeerInfo(&error);
200    if(generations)
201    {
202        CFArrayForEach(generations, ^(const void *value) {
203            count++;
204            if(count%2 != 0)
205                printmsg(CFSTR("Circle name: %@, "),value);
206
207            if(count%2 == 0)
208                printmsg(CFSTR("Generation Count: %@\n"), value);
209        });
210    }
211    else
212        printmsg(CFSTR("No generation count: %@\n"), error);
213    CFReleaseNull(generations);
214}
215
216static bool requestToJoinCircle(CFErrorRef *error)
217{
218    // Set the visual state of switch based on membership in circle
219    bool hadError = false;
220    SOSCCStatus ccstatus = SOSCCThisDeviceIsInCircle(error);
221
222    switch (ccstatus)
223    {
224    case kSOSCCCircleAbsent:
225        hadError = !SOSCCResetToOffering(error);
226        break;
227    case kSOSCCNotInCircle:
228        hadError = !SOSCCRequestToJoinCircle(error);
229        break;
230    default:
231        printerr(CFSTR("Request to join circle with bad status:  %@ (%d)\n"), SOSCCGetStatusDescription(ccstatus), ccstatus);
232        break;
233    }
234    return hadError;
235}
236
237static bool setPassword(char *labelAndPassword, CFErrorRef *err)
238{
239    char *last = NULL;
240    char *token0 = strtok_r(labelAndPassword, ":", &last);
241    char *token1 = strtok_r(NULL, "", &last);
242    CFStringRef label = token1 ? CFStringCreateWithCString(NULL, token0, kCFStringEncodingUTF8) : CFSTR("security command line tool");
243    char *password_token = token1 ? token1 : token0;
244    password_token = password_token ? password_token : "";
245    CFDataRef password = CFDataCreate(NULL, (const UInt8*) password_token, strlen(password_token));
246    bool returned = !SOSCCSetUserCredentials(label, password, err);
247    CFRelease(label);
248    CFRelease(password);
249    return returned;
250}
251
252static bool tryPassword(char *labelAndPassword, CFErrorRef *err)
253{
254    char *last = NULL;
255    char *token0 = strtok_r(labelAndPassword, ":", &last);
256    char *token1 = strtok_r(NULL, "", &last);
257    CFStringRef label = token1 ? CFStringCreateWithCString(NULL, token0, kCFStringEncodingUTF8) : CFSTR("security command line tool");
258    char *password_token = token1 ? token1 : token0;
259    password_token = password_token ? password_token : "";
260    CFDataRef password = CFDataCreate(NULL, (const UInt8*) password_token, strlen(password_token));
261    bool returned = !SOSCCTryUserCredentials(label, password, err);
262    CFRelease(label);
263    CFRelease(password);
264    return returned;
265}
266
267static CFTypeRef getObjectsFromCloud(CFArrayRef keysToGet, dispatch_queue_t processQueue, dispatch_group_t dgroup)
268{
269    __block CFTypeRef object = NULL;
270
271    const uint64_t maxTimeToWaitInSeconds = 30ull * NSEC_PER_SEC;
272    dispatch_semaphore_t waitSemaphore = dispatch_semaphore_create(0);
273    dispatch_time_t finishTime = dispatch_time(DISPATCH_TIME_NOW, maxTimeToWaitInSeconds);
274
275    dispatch_group_enter(dgroup);
276
277    CloudKeychainReplyBlock replyBlock =
278    ^ (CFDictionaryRef returnedValues, CFErrorRef error)
279    {
280        secerror("SOSCloudKeychainGetObjectsFromCloud returned: %@", returnedValues);
281        object = returnedValues;
282        if (object)
283            CFRetain(object);
284        if (error)
285        {
286            secerror("SOSCloudKeychainGetObjectsFromCloud returned error: %@", error);
287            //       CFRelease(*error);
288        }
289        dispatch_group_leave(dgroup);
290        secerror("SOSCloudKeychainGetObjectsFromCloud block exit: %@", object);
291        dispatch_semaphore_signal(waitSemaphore);
292    };
293
294    if (!keysToGet)
295        SOSCloudKeychainGetAllObjectsFromCloud(processQueue, replyBlock);
296    else
297        SOSCloudKeychainGetObjectsFromCloud(keysToGet, processQueue, replyBlock);
298
299	dispatch_semaphore_wait(waitSemaphore, finishTime);
300	dispatch_release(waitSemaphore);
301    if (object && (CFGetTypeID(object) == CFNullGetTypeID()))   // return a NULL instead of a CFNull
302    {
303        CFRelease(object);
304        object = NULL;
305    }
306    secerror("returned: %@", object);
307    return object;
308}
309
310static void displayCircles(CFTypeRef objects)
311{
312    // SOSCCCopyApplicantPeerInfo doesn't display all info, e.g. in the case where we are not in circle
313    CFDictionaryForEach(objects, ^(const void *key, const void *value) {
314        if (SOSKVSKeyGetKeyType(key) == kCircleKey)
315        {
316            CFErrorRef localError = NULL;
317            if (isData(value))
318            {
319                SOSCircleRef circle = SOSCircleCreateFromData(NULL, (CFDataRef) value, &localError);
320                printmsg(CFSTR("circle: %@ %@"), key, circle);
321                CFReleaseSafe(circle);
322            }
323            else
324                printmsg(CFSTR("non-circle: %@ %@"), key, value);
325        }
326    });
327}
328
329static bool dumpKVS(char *itemName, CFErrorRef *err)
330{
331    CFArrayRef keysToGet = NULL;
332    if (itemName)
333    {
334        CFStringRef itemStr = CFStringCreateWithCString(kCFAllocatorDefault, itemName, kCFStringEncodingUTF8);
335        printf("Retrieving %s from KVS\n", itemName);
336        keysToGet = CFArrayCreateForCFTypes(kCFAllocatorDefault, itemStr, NULL);
337        CFReleaseSafe(itemStr);
338    }
339    dispatch_queue_t generalq = dispatch_queue_create("general", DISPATCH_QUEUE_SERIAL);
340    dispatch_group_t work_group = dispatch_group_create();
341    CFTypeRef objects = getObjectsFromCloud(keysToGet, generalq, work_group);
342    CFReleaseSafe(keysToGet);
343    printmsg(CFSTR("   : %@\n"), objects);
344    if (objects)
345        displayCircles(objects);
346    printf("\n");
347    return false;
348}
349
350static bool syncAndWait(char *itemName, CFErrorRef *err)
351{
352    CFArrayRef keysToGet = NULL;
353    __block CFTypeRef objects = NULL;
354    if (!itemName)
355    {
356        fprintf(stderr, "No item keys supplied\n");
357        return false;
358    }
359
360    CFStringRef itemStr = CFStringCreateWithCString(kCFAllocatorDefault, itemName, kCFStringEncodingUTF8);
361    printf("Retrieving %s from KVS\n", itemName);
362    keysToGet = CFArrayCreateForCFTypes(kCFAllocatorDefault, itemStr, NULL);
363    CFReleaseSafe(itemStr);
364
365    dispatch_queue_t generalq = dispatch_queue_create("general", DISPATCH_QUEUE_SERIAL);
366
367    const uint64_t maxTimeToWaitInSeconds = 30ull * NSEC_PER_SEC;
368    dispatch_semaphore_t waitSemaphore = dispatch_semaphore_create(0);
369    dispatch_time_t finishTime = dispatch_time(DISPATCH_TIME_NOW, maxTimeToWaitInSeconds);
370
371    CloudKeychainReplyBlock replyBlock = ^ (CFDictionaryRef returnedValues, CFErrorRef error)
372    {
373        secerror("SOSCloudKeychainSynchronizeAndWait returned: %@", returnedValues);
374        if (error)
375            secerror("SOSCloudKeychainSynchronizeAndWait returned error: %@", error);
376        objects = returnedValues;
377        if (objects)
378            CFRetain(objects);
379        secerror("SOSCloudKeychainGetObjectsFromCloud block exit: %@", objects);
380        dispatch_semaphore_signal(waitSemaphore);
381    };
382
383    SOSCloudKeychainSynchronizeAndWait(keysToGet, generalq, replyBlock);
384
385	dispatch_semaphore_wait(waitSemaphore, finishTime);
386	dispatch_release(waitSemaphore);
387
388    CFReleaseSafe(keysToGet);
389    printmsg(CFSTR("   : %@\n"), objects);
390    if (objects)
391        displayCircles(objects);
392    printf("\n");
393    return false;
394}
395
396// enable, disable, accept, reject, status, Reset, Clear
397int
398keychain_sync(int argc, char * const *argv)
399{
400    /*
401     "    -e     Enable Keychain Syncing (join/create circle)\n"
402     "    -d     Disable Keychain Syncing\n"
403     "    -a     Accept all applicants\n"
404     "    -r     Reject all applicants\n"
405     "    -i     Info\n"
406     "    -k     Pend all registered kvs keys\n"
407     "    -s     Schedule sync with all peers\n"
408     "    -E     Ensure Fresh Parameters\n"
409     "    -R     Reset\n"
410     "    -O     ResetToOffering\n"
411     "    -q     Sign out of Circle\n"
412     "    -C     Clear all values from KVS\n"
413     "    -P    [label:]password  Set password (optionally for a given label) for sync\n"
414     "    -D    [itemName]  Dump contents of KVS\n"
415     "    -P    [label:]password  Set password (optionally for a given label) for sync\n"
416     "    -T    [label:]password  Try password (optionally for a given label) for sync\n"
417     "    -U     Purge private key material cache\n"
418     "    -D    [itemName]  Dump contents of KVS\n"
419     "    -W    itemNames  sync and dump\n"
420     "    -X    [limit]  Best effort bail from circle in limit seconds\n"
421     "    -p     Retrieve IDS Device ID\n"
422     "    -g     Set IDS Device ID\n"
423     */
424	int ch, result = 0;
425    CFErrorRef error = NULL;
426    bool hadError = false;
427
428	while ((ch = getopt(argc, argv, "pedakrisEROChP:T:DW:UX:g:q:")) != -1)
429	{
430		switch  (ch)
431		{
432        case 'q':
433            {
434                printf("Signing out of circle\n");
435                bool immediately = false;
436                CFStringRef leaveImmediately = CFStringCreateWithCString(kCFAllocatorDefault, (char *)optarg, kCFStringEncodingUTF8);
437                if(CFStringCompare(CFSTR("true"), leaveImmediately, 0) == 0){
438                    immediately = true;
439                }
440                else if(CFStringCompare(CFSTR("false"), leaveImmediately, 0) == 0){
441                    immediately = false;
442                }
443                else{
444                    printf("Please provide a \"true\" or \"false\" whether you'd like to leave the circle immediately\n");
445                }
446                hadError = !SOSCCSignedOut(immediately, &error);
447            }
448                break;
449        case 'p':
450            {
451                printf("Grabbing DS ID\n");
452                CFStringRef deviceID = SOSCCRequestDeviceID(&error);
453                if(error){
454                    hadError = true;
455                    break;
456                }
457                if(!isNull(deviceID)){
458                    const char* id = CFStringGetCStringPtr(deviceID, kCFStringEncodingUTF8);
459                    if(id)
460                        printf("IDS Device ID: %s\n", id);
461                    else
462                        printf("IDS Device ID is null!\n");
463                }
464            }
465            break;
466        case 'g':
467            {
468                CFStringRef deviceID = CFStringCreateWithCString(kCFAllocatorDefault, (char *)optarg, kCFStringEncodingUTF8);
469                printf("Setting DS ID: %s\n", optarg);
470                hadError = SOSCCSetDeviceID(deviceID, &error);
471                CFReleaseNull(deviceID);
472            }
473            break;
474        case 'e':
475            printf("Keychain syncing is being turned ON\n");
476            hadError = requestToJoinCircle(&error);
477            break;
478        case 'd':
479            printf("Keychain syncing is being turned OFF\n");
480            hadError = !SOSCCRemoveThisDeviceFromCircle(&error);
481            break;
482        case 'a':
483            {
484                CFArrayRef applicants = SOSCCCopyApplicantPeerInfo(NULL);
485                if (applicants)
486                {
487                    hadError = !SOSCCAcceptApplicants(applicants, &error);
488                    CFRelease(applicants);
489                }
490                else
491                    fprintf(stderr, "No applicants to accept\n");
492            }
493            break;
494        case 'r':
495            {
496                CFArrayRef applicants = SOSCCCopyApplicantPeerInfo(NULL);
497                if (applicants)
498                {
499                    hadError = !SOSCCRejectApplicants(applicants, &error);
500                    CFRelease(applicants);
501                }
502                else
503                    fprintf(stderr, "No applicants to reject\n");
504            }
505            break;
506        case 'i':
507            dumpCircleInfo();
508            break;
509        case 'k':
510            notify_post("com.apple.security.cloudkeychain.forceupdate");
511                break;
512            case 's':
513                //SOSCloudKeychainRequestSyncWithAllPeers(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), NULL);
514                break;
515            case 'E':
516            {
517                printf("Ensuring Fresh Parameters\n");
518                bool result = SOSCCRequestEnsureFreshParameters(&error);
519                if(error){
520                    hadError = true;
521                    break;
522                }
523                if(result){
524                    printf("Refreshed Parameters Ensured!\n");
525                }
526                else{
527                    printf("Problem trying to ensure fresh parameters\n");
528                }
529            }
530                break;
531            case 'R':
532                hadError = !SOSCCResetToEmpty(&error);
533                break;
534            case 'O':
535                hadError = !SOSCCResetToOffering(&error);
536            break;
537        case 'C':
538            hadError = clearAllKVS(&error);
539            break;
540        case 'P':
541            hadError = setPassword(optarg, &error);
542            break;
543        case 'T':
544            hadError = tryPassword(optarg, &error);
545            break;
546        case 'X':
547            {
548            uint64_t limit = strtoul(optarg, NULL, 10);
549            hadError = !SOSCCBailFromCircle_BestEffort(limit, &error);
550            }
551            break;
552        case 'U':
553            hadError = !SOSCCPurgeUserCredentials(&error);
554            break;
555        case 'D':
556            hadError = dumpKVS(optarg, &error);
557            break;
558        case 'W':
559            hadError = syncAndWait(optarg, &error);
560            break;
561		case '?':
562		default:
563			return 2; /* Return 2 triggers usage message. */
564		}
565	}
566
567	//argc -= optind;
568	//argv += optind;
569
570//	if (argc == 0)
571//		return 2;
572
573	if (hadError)
574        printerr(CFSTR("Error: %@\n"), error);
575
576    // 		sec_perror("SecItemAdd", result);
577
578	return result;
579}
580