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