1/*
2 * Copyright (c) 2012-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
24//
25//  main.m
26//  ckd-xpc
27//
28//
29
30/*
31    This XPC service is essentially just a proxy to iCloud KVS, which exists since
32    the main security code cannot link against Foundation.
33
34    See sendTSARequestWithXPC in tsaSupport.c for how to call the service
35
36    send message to app with xpc_connection_send_message
37
38    For now, build this with:
39
40        ~rc/bin/buildit .  --rootsDirectory=/var/tmp -noverify -offline -target CloudKeychainProxy
41
42    and install or upgrade with:
43
44        darwinup install /var/tmp/sec.roots/sec~dst
45        darwinup upgrade /var/tmp/sec.roots/sec~dst
46
47    You must use darwinup during development to update system caches
48*/
49
50//------------------------------------------------------------------------------------------------
51
52#include <AssertMacros.h>
53
54#import <Foundation/Foundation.h>
55#import <Security/Security.h>
56#import <utilities/SecCFRelease.h>
57#import <xpc/xpc.h>
58#import <xpc/private.h>
59#import <CoreFoundation/CFXPCBridge.h>
60#import <sysexits.h>
61#import <syslog.h>
62#import <CommonCrypto/CommonDigest.h>
63#include <utilities/SecXPCError.h>
64#include <TargetConditionals.h>
65
66#import "SOSCloudKeychainConstants.h"
67#import "CKDKVSProxy.h"
68
69void finalize_connection(void *not_used);
70void handle_connection_event(const xpc_connection_t peer);
71static void cloudkeychainproxy_peer_dictionary_handler(const xpc_connection_t peer, xpc_object_t event);
72
73static bool operation_put_dictionary(xpc_object_t event);
74static bool operation_get_v2(xpc_object_t event);
75
76int ckdproxymain(int argc, const char *argv[]);
77
78#define PROXYXPCSCOPE "xpcproxy"
79
80static void describeXPCObject(char *prefix, xpc_object_t object)
81{
82//#ifndef NDEBUG
83    // This is useful for debugging.
84    if (object)
85    {
86        char *desc = xpc_copy_description(object);
87        secdebug(PROXYXPCSCOPE, "%s%s\n", prefix, desc);
88        free(desc);
89    }
90    else
91        secdebug(PROXYXPCSCOPE, "%s<NULL>\n", prefix);
92
93//#endif
94}
95
96static void cloudkeychainproxy_peer_dictionary_handler(const xpc_connection_t peer, xpc_object_t event)
97{
98    bool result = false;
99    int err = 0;
100
101    require_action_string(xpc_get_type(event) == XPC_TYPE_DICTIONARY, xit, err = -51, "expected XPC_TYPE_DICTIONARY");
102
103    const char *operation = xpc_dictionary_get_string(event, kMessageKeyOperation);
104    require_action(operation, xit, result = false);
105
106    // Check protocol version
107    uint64_t version = xpc_dictionary_get_uint64(event, kMessageKeyVersion);
108    secdebug(PROXYXPCSCOPE, "Reply version: %lld\n", version);
109    require_action(version == kCKDXPCVersion, xit, result = false);
110
111    // Operations
112    secdebug(PROXYXPCSCOPE, "Handling %s operation", operation);
113
114
115    if (operation && !strcmp(operation, kOperationPUTDictionary))
116    {
117        operation_put_dictionary(event);
118    }
119    else if (operation && !strcmp(operation, kOperationGETv2))
120    {
121        operation_get_v2(event);
122    }
123    else if (operation && !strcmp(operation, kOperationClearStore))
124    {
125        [[UbiqitousKVSProxy sharedKVSProxy] clearStore];
126        xpc_object_t replyMessage = xpc_dictionary_create_reply(event);
127        if (replyMessage)   // Caller wanted an ACK, so give one
128        {
129            xpc_dictionary_set_string(replyMessage, kMessageKeyValue, "ACK");
130            xpc_connection_send_message(peer, replyMessage);
131        }
132    }
133    else if (operation && !strcmp(operation, kOperationRemoveObjectForKey))
134    {
135        const char *keyToRemove = xpc_dictionary_get_string(event, kMessageKeyKey);
136        [[UbiqitousKVSProxy sharedKVSProxy] removeObjectForKey:[NSString stringWithCString:keyToRemove encoding:NSUTF8StringEncoding]];
137        xpc_object_t replyMessage = xpc_dictionary_create_reply(event);
138        if (replyMessage)   // Caller wanted an ACK, so give one
139        {
140            xpc_dictionary_set_string(replyMessage, kMessageKeyValue, "ACK");
141            xpc_connection_send_message(peer, replyMessage);
142        }
143    }
144    else if (operation && !strcmp(operation, kOperationSynchronize))
145    {
146        [[UbiqitousKVSProxy sharedKVSProxy] requestSynchronization:YES];
147        xpc_object_t replyMessage = xpc_dictionary_create_reply(event);
148        if (replyMessage)   // Caller wanted an ACK, so give one
149        {
150            xpc_dictionary_set_string(replyMessage, kMessageKeyValue, "ACK");
151            xpc_connection_send_message(peer, replyMessage);
152        }
153    }
154    else if (operation && !strcmp(operation, kOperationSynchronizeAndWait))
155    {
156        xpc_object_t xkeysToGetDict = xpc_dictionary_get_value(event, kMessageKeyValue);
157        xpc_object_t replyMessage = xpc_dictionary_create_reply(event);
158        xpc_object_t xkeys = xpc_dictionary_get_value(xkeysToGetDict, kMessageKeyKeysToGet);
159        NSArray *keysToGet = (__bridge_transfer NSArray *)(_CFXPCCreateCFObjectFromXPCObject(xkeys));
160        secnotice(XPROXYSCOPE, "%s XPC request: %s", kWAIT2MINID, kOperationSynchronizeAndWait);
161
162        [[UbiqitousKVSProxy sharedKVSProxy] waitForSynchronization:keysToGet
163                                                           handler:^(NSDictionary *values, NSError *err) {
164                                                               if (replyMessage)   // Caller wanted an ACK, so give one
165                                                               {
166                                                                   secnotice(PROXYXPCSCOPE, "%s Result from [[UbiqitousKVSProxy sharedKVSProxy] waitForSynchronization:]: %@", kWAIT2MINID, err);
167                                                                   xpc_object_t xobject = values ? _CFXPCCreateXPCObjectFromCFObject((__bridge CFTypeRef)(values)) : xpc_null_create();
168                                                                   xpc_dictionary_set_value(replyMessage, kMessageKeyValue, xobject);
169                                                                   if (err)
170                                                                   {
171                                                                       xpc_object_t xerrobj = SecCreateXPCObjectWithCFError((__bridge CFErrorRef)(err));
172                                                                       xpc_dictionary_set_value(replyMessage, kMessageKeyError, xerrobj);
173                                                                   }
174                                                                   xpc_connection_send_message(peer, replyMessage);
175                                                               }
176                                                               else {
177                                                                   secerror("%s XPC request: %s - No replyMessage: %@", kWAIT2MINID, kOperationSynchronizeAndWait, err);
178                                                               }
179                                                           }];
180    }
181    else if (operation && !strcmp(operation, kOperationRegisterKeys))
182    {
183        xpc_object_t xkeysToRegisterDict = xpc_dictionary_get_value(event, kMessageKeyValue);
184        //        describeXPCObject("xkeysToRegister: ", xkeysToRegisterDict);
185
186        // KTR = keysToRegister
187        xpc_object_t xKTRallkeys = xpc_dictionary_get_value(xkeysToRegisterDict, kMessageAllKeys);
188
189        NSDictionary *KTRallkeys = (__bridge_transfer NSDictionary *)(_CFXPCCreateCFObjectFromXPCObject(xKTRallkeys));
190
191        [[UbiqitousKVSProxy sharedKVSProxy] registerKeys: KTRallkeys];
192
193        xpc_object_t replyMessage = xpc_dictionary_create_reply(event);
194        xpc_dictionary_set_string(replyMessage, kMessageKeyValue, "ACK");
195        xpc_connection_send_message(peer, replyMessage);
196
197        secdebug(PROXYXPCSCOPE, "RegisterKeys message sent");
198    }
199    else if (operation && !strcmp(operation, kOperationUILocalNotification))
200    {
201        xpc_object_t xLocalNotificationDict = xpc_dictionary_get_value(event, kMessageKeyValue);
202        //        describeXPCObject("xLocalNotificationDict: ", xLocalNotificationDict);
203        NSDictionary *localNotificationDict = (__bridge_transfer NSDictionary *)(_CFXPCCreateCFObjectFromXPCObject(xLocalNotificationDict));
204        int64_t outFlags = 0;
205        id object = [[UbiqitousKVSProxy sharedKVSProxy] localNotification:localNotificationDict outFlags:&outFlags];
206        secdebug(PROXYXPCSCOPE, "Result from [[UbiqitousKVSProxy sharedKVSProxy] localNotification:]: %@", object);
207        xpc_object_t xobject = object ? _CFXPCCreateXPCObjectFromCFObject((__bridge CFTypeRef)(object)) : xpc_null_create();
208        xpc_object_t replyMessage = xpc_dictionary_create_reply(event);
209        xpc_dictionary_set_int64(xobject, kMessageKeyNotificationFlags, outFlags);
210        xpc_dictionary_set_value(replyMessage, kMessageKeyValue, xobject);
211        xpc_connection_send_message(peer, replyMessage);
212        secdebug(PROXYXPCSCOPE, "localNotification reply sent");
213    }
214    else if (operation && !strcmp(operation, kOperationRequestSyncWithAllPeers))
215    {
216        [[UbiqitousKVSProxy sharedKVSProxy] requestSyncWithAllPeers];
217        xpc_object_t replyMessage = xpc_dictionary_create_reply(event);
218        if (replyMessage)   // Caller wanted an ACK, so give one
219        {
220            xpc_dictionary_set_string(replyMessage, kMessageKeyValue, "ACK");
221            xpc_connection_send_message(peer, replyMessage);
222        }
223        secdebug(PROXYXPCSCOPE, "RequestSyncWithAllPeers reply sent");
224    }
225    else if (operation && !strcmp(operation, kOperationRequestEnsurePeerRegistration))
226    {
227        [[UbiqitousKVSProxy sharedKVSProxy] requestEnsurePeerRegistration];
228        xpc_object_t replyMessage = xpc_dictionary_create_reply(event);
229        if (replyMessage)   // Caller wanted an ACK, so give one
230        {
231            xpc_dictionary_set_string(replyMessage, kMessageKeyValue, "ACK");
232            xpc_connection_send_message(peer, replyMessage);
233        }
234        secdebug(PROXYXPCSCOPE, "RequestEnsurePeerRegistration reply sent");
235    }
236    else if (operation && !strcmp(operation, kOperationFlush))
237    {
238        [[UbiqitousKVSProxy sharedKVSProxy] doAfterFlush:^{
239            xpc_object_t replyMessage = xpc_dictionary_create_reply(event);
240            if (replyMessage)   // Caller wanted an ACK, so give one
241            {
242                xpc_dictionary_set_string(replyMessage, kMessageKeyValue, "ACK");
243                xpc_connection_send_message(peer, replyMessage);
244            }
245            secdebug(PROXYXPCSCOPE, "flush reply sent");
246        }];
247    }
248    else
249    {
250        char *description = xpc_copy_description(event);
251        secdebug(PROXYXPCSCOPE, "Unknown op=%s request from pid %d: %s", operation, xpc_connection_get_pid(peer), description);
252        free(description);
253    }
254    result = true;
255xit:
256    if (!result)
257        describeXPCObject("handle_operation fail: ", event);
258}
259
260void finalize_connection(void *not_used)
261{
262    secdebug(PROXYXPCSCOPE, "finalize_connection");
263    [[UbiqitousKVSProxy sharedKVSProxy] requestSynchronization:YES];
264	xpc_transaction_end();
265}
266
267static bool operation_put_dictionary(xpc_object_t event)
268{
269    // PUT a set of objects into the KVS store. Return false if error
270
271    describeXPCObject("operation_put_dictionary event: ", event);
272    xpc_object_t xvalue = xpc_dictionary_get_value(event, kMessageKeyValue);
273    if (!xvalue)
274        return false;
275
276    CFTypeRef cfvalue = _CFXPCCreateCFObjectFromXPCObject(xvalue);
277    if (cfvalue && (CFGetTypeID(cfvalue)==CFDictionaryGetTypeID()))
278    {
279        [[UbiqitousKVSProxy sharedKVSProxy] setObjectsFromDictionary:(__bridge NSDictionary *)cfvalue];
280        CFReleaseSafe(cfvalue);
281        return true;
282    }
283    else{
284        describeXPCObject("operation_put_dictionary unable to convert to CF: ", xvalue);
285        CFReleaseSafe(cfvalue);
286    }
287    return false;
288}
289
290static bool operation_get_v2(xpc_object_t event)
291{
292    // GET a set of objects from the KVS store. Return false if error
293    describeXPCObject("operation_get_v2 event: ", event);
294    xpc_connection_t peer = xpc_dictionary_get_remote_connection(event);
295    describeXPCObject("operation_get_v2: peer: ", peer);
296
297    xpc_object_t replyMessage = xpc_dictionary_create_reply(event);
298    if (!replyMessage)
299    {
300        secdebug(PROXYXPCSCOPE, "can't create replyMessage");
301        assert(true);   //must have a reply handler
302        return false;
303    }
304    xpc_object_t returnedValues = xpc_dictionary_create(NULL, NULL, 0);
305    if (!returnedValues)
306    {
307        secdebug(PROXYXPCSCOPE, "can't create returnedValues");
308        assert(true);   // must have a spot for the returned values
309        return false;
310    }
311
312    xpc_object_t xvalue = xpc_dictionary_get_value(event, kMessageKeyValue);
313    if (!xvalue)
314    {
315        secdebug(PROXYXPCSCOPE, "missing \"value\" key");
316        return false;
317    }
318
319    xpc_object_t xkeystoget = xpc_dictionary_get_value(xvalue, kMessageKeyKeysToGet);
320    if (xkeystoget)
321    {
322        secdebug(PROXYXPCSCOPE, "got xkeystoget");
323        CFTypeRef keystoget = _CFXPCCreateCFObjectFromXPCObject(xkeystoget);
324        if (!keystoget || (CFGetTypeID(keystoget)!=CFArrayGetTypeID()))     // not "getAll", this is an error of some kind
325        {
326            secdebug(PROXYXPCSCOPE, "can't convert keystoget or is not an array");
327            CFReleaseSafe(keystoget);
328            return false;
329        }
330
331        [(__bridge NSArray *)keystoget enumerateObjectsUsingBlock: ^ (id obj, NSUInteger idx, BOOL *stop)
332        {
333            NSString *key = (NSString *)obj;
334            id object = [[UbiqitousKVSProxy sharedKVSProxy] get:key];
335            secdebug(PROXYXPCSCOPE, "[UbiqitousKVSProxy sharedKVSProxy] get: key: %@, object: %@", key, object);
336            xpc_object_t xobject = object ? _CFXPCCreateXPCObjectFromCFObject((__bridge CFTypeRef)object) : xpc_null_create();
337            xpc_dictionary_set_value(returnedValues, [key UTF8String], xobject);
338            describeXPCObject("operation_get_v2: value from kvs: ", xobject);
339        }];
340    }
341    else    // get all values from kvs
342    {
343        secdebug(PROXYXPCSCOPE, "get all values from kvs");
344        NSDictionary *all = [[UbiqitousKVSProxy sharedKVSProxy] getAll];
345        [all enumerateKeysAndObjectsUsingBlock: ^ (id key, id obj, BOOL *stop)
346        {
347            xpc_object_t xobject = obj ? _CFXPCCreateXPCObjectFromCFObject((__bridge CFTypeRef)obj) : xpc_null_create();
348            xpc_dictionary_set_value(returnedValues, [(NSString *)key UTF8String], xobject);
349        }];
350    }
351
352    xpc_dictionary_set_uint64(replyMessage, kMessageKeyVersion, kCKDXPCVersion);
353    xpc_dictionary_set_value(replyMessage, kMessageKeyValue, returnedValues);
354    xpc_connection_send_message(peer, replyMessage);
355
356    return true;
357}
358
359static void initializeProxyObjectWithConnection(const xpc_connection_t connection)
360{
361    [[UbiqitousKVSProxy sharedKVSProxy] setItemsChangedBlock:^CFArrayRef(CFDictionaryRef values)
362        {
363            secdebug(PROXYXPCSCOPE, "UbiqitousKVSProxy called back");
364            xpc_object_t xobj = _CFXPCCreateXPCObjectFromCFObject(values);
365            xpc_object_t message = xpc_dictionary_create(NULL, NULL, 0);
366            xpc_dictionary_set_uint64(message, kMessageKeyVersion, kCKDXPCVersion);
367            xpc_dictionary_set_string(message, kMessageKeyOperation, kMessageOperationItemChanged);
368            xpc_dictionary_set_value(message, kMessageKeyValue, xobj?xobj:xpc_null_create());
369            xpc_connection_send_message(connection, message);  // Send message; don't wait for a reply
370            return NULL;
371        }];
372}
373
374static void cloudkeychainproxy_peer_event_handler(xpc_connection_t peer, xpc_object_t event)
375{
376    describeXPCObject("peer: ", peer);
377	xpc_type_t type = xpc_get_type(event);
378	if (type == XPC_TYPE_ERROR) {
379		if (event == XPC_ERROR_CONNECTION_INVALID) {
380			// The client process on the other end of the connection has either
381			// crashed or cancelled the connection. After receiving this error,
382			// the connection is in an invalid state, and you do not need to
383			// call xpc_connection_cancel(). Just tear down any associated state
384			// here.
385		} else if (event == XPC_ERROR_TERMINATION_IMMINENT) {
386			// Handle per-connection termination cleanup.
387		}
388	} else {
389		assert(type == XPC_TYPE_DICTIONARY);
390		// Handle the message.
391    //    describeXPCObject("dictionary:", event);
392        dispatch_async(dispatch_get_main_queue(), ^{
393            cloudkeychainproxy_peer_dictionary_handler(peer, event);
394        });
395	}
396}
397
398static void cloudkeychainproxy_event_handler(xpc_connection_t peer)
399{
400	// By defaults, new connections will target the default dispatch
401	// concurrent queue.
402
403    if (xpc_get_type(peer) != XPC_TYPE_CONNECTION)
404    {
405        secdebug(PROXYXPCSCOPE, "expected XPC_TYPE_CONNECTION");
406        return;
407    }
408    initializeProxyObjectWithConnection(peer);
409	xpc_connection_set_event_handler(peer, ^(xpc_object_t event)
410    {
411        cloudkeychainproxy_peer_event_handler(peer, event);
412	});
413
414	// This will tell the connection to begin listening for events. If you
415	// have some other initialization that must be done asynchronously, then
416	// you can defer this call until after that initialization is done.
417	xpc_connection_resume(peer);
418}
419
420static void diagnostics(int argc, const char *argv[])
421{
422    @autoreleasepool
423    {
424        NSDictionary *all = [[UbiqitousKVSProxy sharedKVSProxy] getAll];
425        NSLog(@"All: %@",all);
426    }
427}
428
429int ckdproxymain(int argc, const char *argv[])
430{
431    secdebug(PROXYXPCSCOPE, "Starting CloudKeychainProxy");
432    char *wait4debugger = getenv("WAIT4DEBUGGER");
433
434    if (wait4debugger && !strcasecmp("YES", wait4debugger))
435    {
436        syslog(LOG_ERR, "Waiting for debugger");
437        kill(getpid(), SIGTSTP);
438    }
439
440    if (argc > 1) {
441        diagnostics(argc, argv);
442        return 0;
443    }
444
445    // DISPATCH_TARGET_QUEUE_DEFAULT
446	xpc_connection_t listener = xpc_connection_create_mach_service(xpcServiceName, NULL, XPC_CONNECTION_MACH_SERVICE_LISTENER);
447	xpc_connection_set_event_handler(listener, ^(xpc_object_t object){ cloudkeychainproxy_event_handler(object); });
448
449    [UbiqitousKVSProxy sharedKVSProxy];
450
451    // It looks to me like there is insufficient locking to allow a request to come in on the XPC connection while doing the initial all items.
452    // Therefore I'm leaving the XPC connection suspended until that has time to process.
453	xpc_connection_resume(listener);
454
455    @autoreleasepool
456    {
457        secdebug(PROXYXPCSCOPE, "Starting mainRunLoop");
458        NSRunLoop *runLoop = [NSRunLoop mainRunLoop];
459        [runLoop run];
460    }
461
462    secdebug(PROXYXPCSCOPE, "Exiting CloudKeychainProxy");
463
464    return EXIT_FAILURE;
465}
466