1/*
2 * Copyright (c) 2003-2007,2009-2010 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
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 applicantPeerInfos = NULL;
106    CFArrayRef peerInfos = NULL;
107
108    SOSCCStatus ccstatus = SOSCCThisDeviceIsInCircle(&error);
109    printerr(CFSTR("ccstatus: %s (%d), error: %@\n"), getSOSCCStatusDescription(ccstatus), ccstatus, error);
110
111    if(ccstatus == kSOSCCError) {
112        printerr(CFSTR("End of Dump - unable to proceed due to ccstatus -\n\t%s\n"), getSOSCCStatusDescription(ccstatus));
113        return;
114    }
115
116    // Now look at current applicants
117    applicantPeerInfos = SOSCCCopyApplicantPeerInfo(&error);
118    if (applicantPeerInfos)
119    {
120        printerr(CFSTR("Applicants: %ld, error: %@\n"), (long)CFArrayGetCount(applicantPeerInfos), error);
121        CFArrayForEach(applicantPeerInfos, ^(const void *value) {
122            SOSPeerInfoRef peer = (SOSPeerInfoRef)value;
123            CFStringRef peerName = SOSPeerInfoGetPeerName(peer);
124            printerr(CFSTR("Applicant: %@ (%@)\n"), peerName, peer);
125        });
126    }
127    else
128        printerr(CFSTR("No applicants, error: %@\n"), error);
129
130
131    peerInfos = SOSCCCopyPeerPeerInfo(&error);
132    if (peerInfos)
133    {
134        printerr(CFSTR("Peers: %ld, error: %@\n"), (long)CFArrayGetCount(peerInfos), error);
135        CFArrayForEach(peerInfos, ^(const void *value) {
136            SOSPeerInfoRef peer = (SOSPeerInfoRef)value;
137            CFStringRef peerName = SOSPeerInfoGetPeerName(peer);
138            printerr(CFSTR("Peer: %@ (%@)\n"), peerName, peer);
139        });
140    }
141    else
142        printerr(CFSTR("No peers, error: %@\n"), error);
143
144    peerInfos = SOSCCCopyConcurringPeerPeerInfo(&error);
145    if (peerInfos)
146    {
147        printerr(CFSTR("Concurring Peers: %ld, error: %@\n"), (long)CFArrayGetCount(peerInfos), error);
148        CFArrayForEach(peerInfos, ^(const void *value) {
149            SOSPeerInfoRef peer = (SOSPeerInfoRef)value;
150            CFStringRef peerName = SOSPeerInfoGetPeerName(peer);
151            printerr(CFSTR("Concurr: %@ (%@)\n"), peerName, peer);
152        });
153    }
154    else
155        printerr(CFSTR("No concurring peers, error: %@\n"), error);
156}
157
158static bool requestToJoinCircle(CFErrorRef *error)
159{
160    // Set the visual state of switch based on membership in circle
161    bool hadError = false;
162    SOSCCStatus ccstatus = SOSCCThisDeviceIsInCircle(error);
163
164    switch (ccstatus)
165    {
166    case kSOSCCCircleAbsent:
167        hadError = !SOSCCResetToOffering(error);
168        break;
169    case kSOSCCNotInCircle:
170        hadError = !SOSCCRequestToJoinCircle(error);
171        break;
172    default:
173        printerr(CFSTR("Request to join circle with bad status:  %@ (%d)\n"), SOSCCGetStatusDescription(ccstatus), ccstatus);
174        break;
175    }
176    return hadError;
177}
178
179static bool setPassword(char *labelAndPassword, CFErrorRef *err)
180{
181    char *last = NULL;
182    char *token0 = strtok_r(labelAndPassword, ":", &last);
183    char *token1 = strtok_r(NULL, "", &last);
184    CFStringRef label = token1 ? CFStringCreateWithCString(NULL, token0, kCFStringEncodingUTF8) : CFSTR("security command line tool");
185    char *password_token = token1 ? token1 : token0;
186    password_token = password_token ? password_token : "";
187    CFDataRef password = CFDataCreate(NULL, (const UInt8*) password_token, strlen(password_token));
188    bool returned = !SOSCCSetUserCredentials(label, password, err);
189    CFRelease(label);
190    CFRelease(password);
191    return returned;
192}
193
194static bool tryPassword(char *labelAndPassword, CFErrorRef *err)
195{
196    char *last = NULL;
197    char *token0 = strtok_r(labelAndPassword, ":", &last);
198    char *token1 = strtok_r(NULL, "", &last);
199    CFStringRef label = token1 ? CFStringCreateWithCString(NULL, token0, kCFStringEncodingUTF8) : CFSTR("security command line tool");
200    char *password_token = token1 ? token1 : token0;
201    password_token = password_token ? password_token : "";
202    CFDataRef password = CFDataCreate(NULL, (const UInt8*) password_token, strlen(password_token));
203    bool returned = !SOSCCTryUserCredentials(label, password, err);
204    CFRelease(label);
205    CFRelease(password);
206    return returned;
207}
208
209static CFTypeRef getObjectsFromCloud(CFArrayRef keysToGet, dispatch_queue_t processQueue, dispatch_group_t dgroup)
210{
211    __block CFTypeRef object = NULL;
212
213    const uint64_t maxTimeToWaitInSeconds = 30ull * NSEC_PER_SEC;
214    dispatch_semaphore_t waitSemaphore = dispatch_semaphore_create(0);
215    dispatch_time_t finishTime = dispatch_time(DISPATCH_TIME_NOW, maxTimeToWaitInSeconds);
216
217    dispatch_group_enter(dgroup);
218
219    CloudKeychainReplyBlock replyBlock =
220    ^ (CFDictionaryRef returnedValues, CFErrorRef error)
221    {
222        secerror("SOSCloudKeychainGetObjectsFromCloud returned: %@", returnedValues);
223        object = returnedValues;
224        if (object)
225            CFRetain(object);
226        if (error)
227        {
228            secerror("SOSCloudKeychainGetObjectsFromCloud returned error: %@", error);
229            //       CFRelease(*error);
230        }
231        dispatch_group_leave(dgroup);
232        secerror("SOSCloudKeychainGetObjectsFromCloud block exit: %@", object);
233        dispatch_semaphore_signal(waitSemaphore);
234    };
235
236    if (!keysToGet)
237        SOSCloudKeychainGetAllObjectsFromCloud(processQueue, replyBlock);
238    else
239        SOSCloudKeychainGetObjectsFromCloud(keysToGet, processQueue, replyBlock);
240
241	dispatch_semaphore_wait(waitSemaphore, finishTime);
242	dispatch_release(waitSemaphore);
243    if (object && (CFGetTypeID(object) == CFNullGetTypeID()))   // return a NULL instead of a CFNull
244    {
245        CFRelease(object);
246        object = NULL;
247    }
248    secerror("returned: %@", object);
249    return object;
250}
251
252static void displayCircles(CFTypeRef objects)
253{
254    // SOSCCCopyApplicantPeerInfo doesn't display all info, e.g. in the case where we are not in circle
255    CFDictionaryForEach(objects, ^(const void *key, const void *value) {
256        if (SOSKVSKeyGetKeyType(key) == kCircleKey)
257        {
258            CFErrorRef localError = NULL;
259            if (isData(value))
260            {
261                SOSCircleRef circle = SOSCircleCreateFromData(NULL, (CFDataRef) value, &localError);
262                printmsg(CFSTR("circle: %@ %@"), key, circle);
263                CFReleaseSafe(circle);
264            }
265            else
266                printmsg(CFSTR("non-circle: %@ %@"), key, value);
267        }
268    });
269}
270
271static bool dumpKVS(char *itemName, CFErrorRef *err)
272{
273    CFArrayRef keysToGet = NULL;
274    if (itemName)
275    {
276        CFStringRef itemStr = CFStringCreateWithCString(kCFAllocatorDefault, itemName, kCFStringEncodingUTF8);
277        printf("Retrieving %s from KVS\n", itemName);
278        keysToGet = CFArrayCreateForCFTypes(kCFAllocatorDefault, itemStr, NULL);
279        CFReleaseSafe(itemStr);
280    }
281    dispatch_queue_t generalq = dispatch_queue_create("general", DISPATCH_QUEUE_SERIAL);
282    dispatch_group_t work_group = dispatch_group_create();
283    CFTypeRef objects = getObjectsFromCloud(keysToGet, generalq, work_group);
284    CFReleaseSafe(keysToGet);
285    printmsg(CFSTR("   : %@\n"), objects);
286    if (objects)
287        displayCircles(objects);
288    printf("\n");
289    return false;
290}
291
292static bool syncAndWait(char *itemName, CFErrorRef *err)
293{
294    CFArrayRef keysToGet = NULL;
295    __block CFTypeRef objects = NULL;
296    if (!itemName)
297    {
298        fprintf(stderr, "No item keys supplied\n");
299        return false;
300    }
301
302    CFStringRef itemStr = CFStringCreateWithCString(kCFAllocatorDefault, itemName, kCFStringEncodingUTF8);
303    printf("Retrieving %s from KVS\n", itemName);
304    keysToGet = CFArrayCreateForCFTypes(kCFAllocatorDefault, itemStr, NULL);
305    CFReleaseSafe(itemStr);
306
307    dispatch_queue_t generalq = dispatch_queue_create("general", DISPATCH_QUEUE_SERIAL);
308
309    const uint64_t maxTimeToWaitInSeconds = 30ull * NSEC_PER_SEC;
310    dispatch_semaphore_t waitSemaphore = dispatch_semaphore_create(0);
311    dispatch_time_t finishTime = dispatch_time(DISPATCH_TIME_NOW, maxTimeToWaitInSeconds);
312
313    CloudKeychainReplyBlock replyBlock = ^ (CFDictionaryRef returnedValues, CFErrorRef error)
314    {
315        secerror("SOSCloudKeychainSynchronizeAndWait returned: %@", returnedValues);
316        if (error)
317            secerror("SOSCloudKeychainSynchronizeAndWait returned error: %@", error);
318        objects = returnedValues;
319        if (objects)
320            CFRetain(objects);
321        secerror("SOSCloudKeychainGetObjectsFromCloud block exit: %@", objects);
322        dispatch_semaphore_signal(waitSemaphore);
323    };
324
325    SOSCloudKeychainSynchronizeAndWait(keysToGet, generalq, replyBlock);
326
327	dispatch_semaphore_wait(waitSemaphore, finishTime);
328	dispatch_release(waitSemaphore);
329
330    CFReleaseSafe(keysToGet);
331    printmsg(CFSTR("   : %@\n"), objects);
332    if (objects)
333        displayCircles(objects);
334    printf("\n");
335    return false;
336}
337
338// enable, disable, accept, reject, status, Reset, Clear
339int
340keychain_sync(int argc, char * const *argv)
341{
342    /*
343     "    -e     Enable Keychain Syncing (join/create circle)\n"
344     "    -d     Disable Keychain Syncing\n"
345     "    -a     Accept all applicants\n"
346     "    -r     Reject all applicants\n"
347     "    -i     Info\n"
348     "    -k     Pend all registered kvs keys\n"
349     "    -s     Schedule sync with all peers\n"
350     "    -R     Reset\n"
351     "    -O     ResetToOffering\n"
352     "    -C     Clear all values from KVS\n"
353     "    -P    [label:]password  Set password (optionally for a given label) for sync\n"
354     "    -D    [itemName]  Dump contents of KVS\n"
355     "    -P    [label:]password  Set password (optionally for a given label) for sync\n"
356     "    -T    [label:]password  Try password (optionally for a given label) for sync\n"
357     "    -U     Purge private key material cache\n"
358     "    -D    [itemName]  Dump contents of KVS\n"
359     "    -W    itemNames  sync and dump\n"
360     "    -X    [limit]  Best effort bail from circle in limit seconds\n"
361     */
362	int ch, result = 0;
363    CFErrorRef error = NULL;
364    bool hadError = false;
365
366	while ((ch = getopt(argc, argv, "edakrisROChP:T:DW:UX:")) != -1)
367	{
368		switch  (ch)
369		{
370        case 'e':
371            printf("Keychain syncing is being turned ON\n");
372            hadError = requestToJoinCircle(&error);
373            break;
374        case 'd':
375            printf("Keychain syncing is being turned OFF\n");
376            hadError = !SOSCCRemoveThisDeviceFromCircle(&error);
377            break;
378        case 'a':
379            {
380                CFArrayRef applicants = SOSCCCopyApplicantPeerInfo(NULL);
381                if (applicants)
382                {
383                    hadError = !SOSCCAcceptApplicants(applicants, &error);
384                    CFRelease(applicants);
385                }
386                else
387                    fprintf(stderr, "No applicants to accept\n");
388            }
389            break;
390        case 'r':
391            {
392                CFArrayRef applicants = SOSCCCopyApplicantPeerInfo(NULL);
393                if (applicants)
394                {
395                    hadError = !SOSCCRejectApplicants(applicants, &error);
396                    CFRelease(applicants);
397                }
398                else
399                    fprintf(stderr, "No applicants to reject\n");
400            }
401            break;
402        case 'i':
403            dumpCircleInfo();
404            break;
405        case 'k':
406            notify_post("com.apple.security.cloudkeychain.forceupdate");
407            break;
408        case 's':
409            //SOSCloudKeychainRequestSyncWithAllPeers(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), NULL);
410            break;
411        case 'R':
412            hadError = !SOSCCResetToEmpty(&error);
413            break;
414        case 'O':
415            hadError = !SOSCCResetToOffering(&error);
416            break;
417        case 'C':
418            hadError = clearAllKVS(&error);
419            break;
420        case 'P':
421            hadError = setPassword(optarg, &error);
422            break;
423        case 'T':
424            hadError = tryPassword(optarg, &error);
425            break;
426        case 'X':
427            {
428            uint64_t limit = strtoul(optarg, NULL, 10);
429            hadError = !SOSCCBailFromCircle_BestEffort(limit, &error);
430            }
431            break;
432        case 'U':
433            hadError = !SOSCCPurgeUserCredentials(&error);
434            break;
435        case 'D':
436            hadError = dumpKVS(optarg, &error);
437            break;
438        case 'W':
439            hadError = syncAndWait(optarg, &error);
440            break;
441		case '?':
442		default:
443			return 2; /* Return 2 triggers usage message. */
444		}
445	}
446
447	argc -= optind;
448	argv += optind;
449
450//	if (argc == 0)
451//		return 2;
452
453	if (hadError)
454        printerr(CFSTR("Error: %@\n"), error);
455
456    // 		sec_perror("SecItemAdd", result);
457
458	return result;
459}
460