2 * Copyright (c) 2012-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 This XPC service is essentially just a proxy to iCloud KVS, which exists since
32 the main security code cannot link against Foundation.
34 See sendTSARequestWithXPC in tsaSupport.c for how to call the service
36 send message to app with xpc_connection_send_message
38 For now, build this with:
40 ~rc/bin/buildit . --rootsDirectory=/var/tmp -noverify -offline -target CloudKeychainProxy
42 and install or upgrade with:
44 darwinup install /var/tmp/sec.roots/sec~dst
45 darwinup upgrade /var/tmp/sec.roots/sec~dst
47 You must use darwinup during development to update system caches
50 //------------------------------------------------------------------------------------------------
52 #include <AssertMacros.h>
54 #import <Foundation/Foundation.h>
55 #import <Security/Security.h>
56 #import <utilities/SecCFRelease.h>
58 #import <xpc/private.h>
59 #import <CoreFoundation/CFXPCBridge.h>
62 #import <CommonCrypto/CommonDigest.h>
63 #include <utilities/SecXPCError.h>
64 #include <TargetConditionals.h>
66 #import "CKDKVSProxy.h"
68 void finalize_connection(void *not_used);
69 void handle_connection_event(const xpc_connection_t peer);
70 static void cloudkeychainproxy_peer_dictionary_handler(const xpc_connection_t peer, xpc_object_t event);
72 static bool operation_put_dictionary(xpc_object_t event);
73 static bool operation_get_v2(xpc_object_t event);
75 int ckdproxymain(int argc, const char *argv[]);
77 #define PROXYXPCSCOPE "xpcproxy"
79 static void describeXPCObject(char *prefix, xpc_object_t object)
82 // This is useful for debugging.
85 char *desc = xpc_copy_description(object);
86 secdebug(PROXYXPCSCOPE, "%s%s\n", prefix, desc);
90 secdebug(PROXYXPCSCOPE, "%s<NULL>\n", prefix);
95 static void cloudkeychainproxy_peer_dictionary_handler(const xpc_connection_t peer, xpc_object_t event)
100 require_action_string(xpc_get_type(event) == XPC_TYPE_DICTIONARY, xit, err = -51, "expected XPC_TYPE_DICTIONARY");
102 const char *operation = xpc_dictionary_get_string(event, kMessageKeyOperation);
103 require_action(operation, xit, result = false);
105 // Check protocol version
106 uint64_t version = xpc_dictionary_get_uint64(event, kMessageKeyVersion);
107 secdebug(PROXYXPCSCOPE, "Reply version: %lld\n", version);
108 require_action(version == kCKDXPCVersion, xit, result = false);
111 secdebug(PROXYXPCSCOPE, "Handling %s operation", operation);
114 if (operation && !strcmp(operation, kOperationPUTDictionary))
116 operation_put_dictionary(event);
118 else if (operation && !strcmp(operation, kOperationGETv2))
120 operation_get_v2(event);
122 else if (operation && !strcmp(operation, kOperationClearStore))
124 [[UbiqitousKVSProxy sharedKVSProxy] clearStore];
125 xpc_object_t replyMessage = xpc_dictionary_create_reply(event);
126 if (replyMessage) // Caller wanted an ACK, so give one
128 xpc_dictionary_set_string(replyMessage, kMessageKeyValue, "ACK");
129 xpc_connection_send_message(peer, replyMessage);
132 else if (operation && !strcmp(operation, kOperationRemoveObjectForKey))
134 const char *keyToRemove = xpc_dictionary_get_string(event, kMessageKeyKey);
135 [[UbiqitousKVSProxy sharedKVSProxy] removeObjectForKey:[NSString stringWithCString:keyToRemove encoding:NSUTF8StringEncoding]];
136 xpc_object_t replyMessage = xpc_dictionary_create_reply(event);
137 if (replyMessage) // Caller wanted an ACK, so give one
139 xpc_dictionary_set_string(replyMessage, kMessageKeyValue, "ACK");
140 xpc_connection_send_message(peer, replyMessage);
143 else if (operation && !strcmp(operation, kOperationSynchronize))
145 [[UbiqitousKVSProxy sharedKVSProxy] requestSynchronization:YES];
146 xpc_object_t replyMessage = xpc_dictionary_create_reply(event);
147 if (replyMessage) // Caller wanted an ACK, so give one
149 xpc_dictionary_set_string(replyMessage, kMessageKeyValue, "ACK");
150 xpc_connection_send_message(peer, replyMessage);
153 else if (operation && !strcmp(operation, kOperationSynchronizeAndWait))
155 xpc_object_t xkeysToGetDict = xpc_dictionary_get_value(event, kMessageKeyValue);
156 xpc_object_t replyMessage = xpc_dictionary_create_reply(event);
157 xpc_object_t xkeys = xpc_dictionary_get_value(xkeysToGetDict, kMessageKeyKeysToGet);
158 NSArray *keysToGet = (__bridge_transfer NSArray *)(_CFXPCCreateCFObjectFromXPCObject(xkeys));
159 secnotice(XPROXYSCOPE, "%s XPC request: %s", kWAIT2MINID, kOperationSynchronizeAndWait);
161 [[UbiqitousKVSProxy sharedKVSProxy] waitForSynchronization:keysToGet
162 handler:^(NSDictionary *values, NSError *err) {
163 if (replyMessage) // Caller wanted an ACK, so give one
165 secnotice(PROXYXPCSCOPE, "%s Result from [[UbiqitousKVSProxy sharedKVSProxy] waitForSynchronization:]: %@", kWAIT2MINID, err);
166 xpc_object_t xobject = values ? _CFXPCCreateXPCObjectFromCFObject((__bridge CFTypeRef)(values)) : xpc_null_create();
167 xpc_dictionary_set_value(replyMessage, kMessageKeyValue, xobject);
170 xpc_object_t xerrobj = SecCreateXPCObjectWithCFError((__bridge CFErrorRef)(err));
171 xpc_dictionary_set_value(replyMessage, kMessageKeyError, xerrobj);
173 xpc_connection_send_message(peer, replyMessage);
176 secerror("%s XPC request: %s - No replyMessage: %@", kWAIT2MINID, kOperationSynchronizeAndWait, err);
180 else if (operation && !strcmp(operation, kOperationRegisterKeys))
182 xpc_object_t xkeysToRegisterDict = xpc_dictionary_get_value(event, kMessageKeyValue);
183 // describeXPCObject("xkeysToRegister: ", xkeysToRegisterDict);
185 // KTR = keysToRegister
186 xpc_object_t xKTRallkeys = xpc_dictionary_get_value(xkeysToRegisterDict, kMessageAllKeys);
188 NSDictionary *KTRallkeys = (__bridge_transfer NSDictionary *)(_CFXPCCreateCFObjectFromXPCObject(xKTRallkeys));
190 [[UbiqitousKVSProxy sharedKVSProxy] registerKeys: KTRallkeys];
192 xpc_object_t replyMessage = xpc_dictionary_create_reply(event);
193 xpc_dictionary_set_string(replyMessage, kMessageKeyValue, "ACK");
194 xpc_connection_send_message(peer, replyMessage);
196 secdebug(PROXYXPCSCOPE, "RegisterKeys message sent");
198 else if (operation && !strcmp(operation, kOperationUILocalNotification))
200 xpc_object_t xLocalNotificationDict = xpc_dictionary_get_value(event, kMessageKeyValue);
201 // describeXPCObject("xLocalNotificationDict: ", xLocalNotificationDict);
202 NSDictionary *localNotificationDict = (__bridge_transfer NSDictionary *)(_CFXPCCreateCFObjectFromXPCObject(xLocalNotificationDict));
203 int64_t outFlags = 0;
204 id object = [[UbiqitousKVSProxy sharedKVSProxy] localNotification:localNotificationDict outFlags:&outFlags];
205 secdebug(PROXYXPCSCOPE, "Result from [[UbiqitousKVSProxy sharedKVSProxy] localNotification:]: %@", object);
206 xpc_object_t xobject = object ? _CFXPCCreateXPCObjectFromCFObject((__bridge CFTypeRef)(object)) : xpc_null_create();
207 xpc_object_t replyMessage = xpc_dictionary_create_reply(event);
208 xpc_dictionary_set_int64(xobject, kMessageKeyNotificationFlags, outFlags);
209 xpc_dictionary_set_value(replyMessage, kMessageKeyValue, xobject);
210 xpc_connection_send_message(peer, replyMessage);
211 secdebug(PROXYXPCSCOPE, "localNotification reply sent");
213 else if (operation && !strcmp(operation, kOperationRequestSyncWithAllPeers))
215 [[UbiqitousKVSProxy sharedKVSProxy] requestSyncWithAllPeers];
216 xpc_object_t replyMessage = xpc_dictionary_create_reply(event);
217 if (replyMessage) // Caller wanted an ACK, so give one
219 xpc_dictionary_set_string(replyMessage, kMessageKeyValue, "ACK");
220 xpc_connection_send_message(peer, replyMessage);
222 secdebug(PROXYXPCSCOPE, "RequestSyncWithAllPeers reply sent");
224 else if (operation && !strcmp(operation, kOperationRequestEnsurePeerRegistration))
226 [[UbiqitousKVSProxy sharedKVSProxy] requestEnsurePeerRegistration];
227 xpc_object_t replyMessage = xpc_dictionary_create_reply(event);
228 if (replyMessage) // Caller wanted an ACK, so give one
230 xpc_dictionary_set_string(replyMessage, kMessageKeyValue, "ACK");
231 xpc_connection_send_message(peer, replyMessage);
233 secdebug(PROXYXPCSCOPE, "RequestEnsurePeerRegistration reply sent");
235 else if (operation && !strcmp(operation, kOperationFlush))
237 [[UbiqitousKVSProxy sharedKVSProxy] doAfterFlush:^{
238 xpc_object_t replyMessage = xpc_dictionary_create_reply(event);
239 if (replyMessage) // Caller wanted an ACK, so give one
241 xpc_dictionary_set_string(replyMessage, kMessageKeyValue, "ACK");
242 xpc_connection_send_message(peer, replyMessage);
244 secdebug(PROXYXPCSCOPE, "flush reply sent");
249 char *description = xpc_copy_description(event);
250 secdebug(PROXYXPCSCOPE, "Unknown op=%s request from pid %d: %s", operation, xpc_connection_get_pid(peer), description);
256 describeXPCObject("handle_operation fail: ", event);
259 void finalize_connection(void *not_used)
261 secdebug(PROXYXPCSCOPE, "finalize_connection");
262 [[UbiqitousKVSProxy sharedKVSProxy] requestSynchronization:YES];
263 xpc_transaction_end();
266 static bool operation_put_dictionary(xpc_object_t event)
268 // PUT a set of objects into the KVS store. Return false if error
270 describeXPCObject("operation_put_dictionary event: ", event);
271 xpc_object_t xvalue = xpc_dictionary_get_value(event, kMessageKeyValue);
275 CFTypeRef cfvalue = _CFXPCCreateCFObjectFromXPCObject(xvalue);
276 if (cfvalue && (CFGetTypeID(cfvalue)==CFDictionaryGetTypeID()))
278 [[UbiqitousKVSProxy sharedKVSProxy] recordWriteToKVS:(__bridge NSDictionary *)cfvalue];
279 NSDictionary *safeValues = [[UbiqitousKVSProxy sharedKVSProxy] recordHaltedValuesAndReturnValuesToSafelyWrite:(__bridge NSDictionary *)cfvalue];
280 if([safeValues count] !=0){
281 [[UbiqitousKVSProxy sharedKVSProxy] setObjectsFromDictionary:safeValues];
283 CFReleaseSafe(cfvalue);
287 describeXPCObject("operation_put_dictionary unable to convert to CF: ", xvalue);
288 CFReleaseSafe(cfvalue);
293 static bool operation_get_v2(xpc_object_t event)
295 // GET a set of objects from the KVS store. Return false if error
296 describeXPCObject("operation_get_v2 event: ", event);
297 xpc_connection_t peer = xpc_dictionary_get_remote_connection(event);
298 describeXPCObject("operation_get_v2: peer: ", peer);
300 xpc_object_t replyMessage = xpc_dictionary_create_reply(event);
303 secdebug(PROXYXPCSCOPE, "can't create replyMessage");
304 assert(true); //must have a reply handler
307 xpc_object_t returnedValues = xpc_dictionary_create(NULL, NULL, 0);
310 secdebug(PROXYXPCSCOPE, "can't create returnedValues");
311 assert(true); // must have a spot for the returned values
315 xpc_object_t xvalue = xpc_dictionary_get_value(event, kMessageKeyValue);
318 secdebug(PROXYXPCSCOPE, "missing \"value\" key");
322 xpc_object_t xkeystoget = xpc_dictionary_get_value(xvalue, kMessageKeyKeysToGet);
325 secdebug(PROXYXPCSCOPE, "got xkeystoget");
326 CFTypeRef keystoget = _CFXPCCreateCFObjectFromXPCObject(xkeystoget);
327 if (!keystoget || (CFGetTypeID(keystoget)!=CFArrayGetTypeID())) // not "getAll", this is an error of some kind
329 secdebug(PROXYXPCSCOPE, "can't convert keystoget or is not an array");
330 CFReleaseSafe(keystoget);
334 [(__bridge NSArray *)keystoget enumerateObjectsUsingBlock: ^ (id obj, NSUInteger idx, BOOL *stop)
336 NSString *key = (NSString *)obj;
337 id object = [[UbiqitousKVSProxy sharedKVSProxy] get:key];
338 secdebug(PROXYXPCSCOPE, "[UbiqitousKVSProxy sharedKVSProxy] get: key: %@, object: %@", key, object);
339 xpc_object_t xobject = object ? _CFXPCCreateXPCObjectFromCFObject((__bridge CFTypeRef)object) : xpc_null_create();
340 xpc_dictionary_set_value(returnedValues, [key UTF8String], xobject);
341 describeXPCObject("operation_get_v2: value from kvs: ", xobject);
344 else // get all values from kvs
346 secdebug(PROXYXPCSCOPE, "get all values from kvs");
347 NSDictionary *all = [[UbiqitousKVSProxy sharedKVSProxy] getAll];
348 [all enumerateKeysAndObjectsUsingBlock: ^ (id key, id obj, BOOL *stop)
350 xpc_object_t xobject = obj ? _CFXPCCreateXPCObjectFromCFObject((__bridge CFTypeRef)obj) : xpc_null_create();
351 xpc_dictionary_set_value(returnedValues, [(NSString *)key UTF8String], xobject);
355 xpc_dictionary_set_uint64(replyMessage, kMessageKeyVersion, kCKDXPCVersion);
356 xpc_dictionary_set_value(replyMessage, kMessageKeyValue, returnedValues);
357 xpc_connection_send_message(peer, replyMessage);
362 static void initializeProxyObjectWithConnection(const xpc_connection_t connection)
364 [[UbiqitousKVSProxy sharedKVSProxy] setItemsChangedBlock:^CFArrayRef(CFDictionaryRef values)
366 secdebug(PROXYXPCSCOPE, "UbiqitousKVSProxy called back");
367 xpc_object_t xobj = _CFXPCCreateXPCObjectFromCFObject(values);
368 xpc_object_t message = xpc_dictionary_create(NULL, NULL, 0);
369 xpc_dictionary_set_uint64(message, kMessageKeyVersion, kCKDXPCVersion);
370 xpc_dictionary_set_string(message, kMessageKeyOperation, kMessageOperationItemChanged);
371 xpc_dictionary_set_value(message, kMessageKeyValue, xobj?xobj:xpc_null_create());
372 xpc_connection_send_message(connection, message); // Send message; don't wait for a reply
377 static void cloudkeychainproxy_peer_event_handler(xpc_connection_t peer, xpc_object_t event)
379 describeXPCObject("peer: ", peer);
380 xpc_type_t type = xpc_get_type(event);
381 if (type == XPC_TYPE_ERROR) {
382 if (event == XPC_ERROR_CONNECTION_INVALID) {
383 // The client process on the other end of the connection has either
384 // crashed or cancelled the connection. After receiving this error,
385 // the connection is in an invalid state, and you do not need to
386 // call xpc_connection_cancel(). Just tear down any associated state
388 } else if (event == XPC_ERROR_TERMINATION_IMMINENT) {
389 // Handle per-connection termination cleanup.
392 assert(type == XPC_TYPE_DICTIONARY);
393 // Handle the message.
394 // describeXPCObject("dictionary:", event);
395 dispatch_async(dispatch_get_main_queue(), ^{
396 cloudkeychainproxy_peer_dictionary_handler(peer, event);
401 static void cloudkeychainproxy_event_handler(xpc_connection_t peer)
403 // By defaults, new connections will target the default dispatch
406 if (xpc_get_type(peer) != XPC_TYPE_CONNECTION)
408 secdebug(PROXYXPCSCOPE, "expected XPC_TYPE_CONNECTION");
411 initializeProxyObjectWithConnection(peer);
412 xpc_connection_set_event_handler(peer, ^(xpc_object_t event)
414 cloudkeychainproxy_peer_event_handler(peer, event);
417 // This will tell the connection to begin listening for events. If you
418 // have some other initialization that must be done asynchronously, then
419 // you can defer this call until after that initialization is done.
420 xpc_connection_resume(peer);
423 static void diagnostics(int argc, const char *argv[])
427 NSDictionary *all = [[UbiqitousKVSProxy sharedKVSProxy] getAll];
428 NSLog(@"All: %@",all);
432 int ckdproxymain(int argc, const char *argv[])
434 secdebug(PROXYXPCSCOPE, "Starting CloudKeychainProxy");
435 char *wait4debugger = getenv("WAIT4DEBUGGER");
437 if (wait4debugger && !strcasecmp("YES", wait4debugger))
439 syslog(LOG_ERR, "Waiting for debugger");
440 kill(getpid(), SIGTSTP);
444 diagnostics(argc, argv);
448 // DISPATCH_TARGET_QUEUE_DEFAULT
449 xpc_connection_t listener = xpc_connection_create_mach_service(xpcServiceName, NULL, XPC_CONNECTION_MACH_SERVICE_LISTENER);
450 xpc_connection_set_event_handler(listener, ^(xpc_object_t object){ cloudkeychainproxy_event_handler(object); });
452 [UbiqitousKVSProxy sharedKVSProxy];
454 // 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.
455 // Therefore I'm leaving the XPC connection suspended until that has time to process.
456 xpc_connection_resume(listener);
460 secdebug(PROXYXPCSCOPE, "Starting mainRunLoop");
461 NSRunLoop *runLoop = [NSRunLoop mainRunLoop];
465 secdebug(PROXYXPCSCOPE, "Exiting CloudKeychainProxy");