2 * Copyright (c) 2012 Apple Computer, 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@
28 // Created by John Hurley on 7/19/12.
29 // Copyright (c) 2012 John Hurley. All rights reserved.
33 This XPC service is essentially just a proxy to iCloud KVS, which exists since
34 the main security code cannot link against Foundation.
36 See sendTSARequestWithXPC in tsaSupport.c for how to call the service
38 send message to app with xpc_connection_send_message
40 For now, build this with:
42 ~rc/bin/buildit . --rootsDirectory=/var/tmp -noverify -offline -target CloudKeychainProxy
44 and install or upgrade with:
46 darwinup install /var/tmp/sec.roots/sec~dst
47 darwinup upgrade /var/tmp/sec.roots/sec~dst
49 You must use darwinup during development to update system caches
52 //------------------------------------------------------------------------------------------------
54 #include <AssertMacros.h>
56 #import <Foundation/Foundation.h>
57 #import <Security/Security.h>
58 #import <utilities/SecCFRelease.h>
60 #import <xpc/private.h>
61 #import <CoreFoundation/CFXPCBridge.h>
64 #import <CommonCrypto/CommonDigest.h>
65 #include <utilities/SecXPCError.h>
67 #import "SOSCloudKeychainConstants.h"
68 #import "CKDKVSProxy.h"
70 void finalize_connection(void *not_used);
71 void handle_connection_event(const xpc_connection_t peer);
72 static void cloudkeychainproxy_peer_dictionary_handler(const xpc_connection_t peer, xpc_object_t event);
74 static bool operation_put_dictionary(xpc_object_t event);
75 static bool operation_get_v2(xpc_object_t event);
77 int ckdproxymain(int argc, const char *argv[]);
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(XPROXYSCOPE, "%s%s\n", prefix, desc);
90 secdebug(XPROXYSCOPE, "%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(XPROXYSCOPE, "Reply version: %lld\n", version);
108 require_action(version == kCKDXPCVersion, xit, result = false);
111 secdebug(XPROXYSCOPE, "Handling %s operation", operation);
113 if (operation && !strcmp(operation, kOperationPUTDictionary))
114 operation_put_dictionary(event);
116 if (operation && !strcmp(operation, kOperationGETv2))
117 operation_get_v2(event);
119 if (operation && !strcmp(operation, kOperationClearStore))
121 [[UbiqitousKVSProxy sharedKVSProxy] clearStore];
122 xpc_object_t replyMessage = xpc_dictionary_create_reply(event);
123 if (replyMessage) // Caller wanted an ACK, so give one
125 xpc_dictionary_set_string(replyMessage, kMessageKeyValue, "ACK");
126 xpc_connection_send_message(peer, replyMessage);
129 else if (operation && !strcmp(operation, kOperationRemoveObjectForKey))
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
136 xpc_dictionary_set_string(replyMessage, kMessageKeyValue, "ACK");
137 xpc_connection_send_message(peer, replyMessage);
140 else if (operation && !strcmp(operation, kOperationSynchronize))
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
146 xpc_dictionary_set_string(replyMessage, kMessageKeyValue, "ACK");
147 xpc_connection_send_message(peer, replyMessage);
150 else if (operation && !strcmp(operation, kOperationSynchronizeAndWait))
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
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);
165 xpc_object_t xerrobj = SecCreateXPCObjectWithCFError((CFErrorRef)CFBridgingRetain(err));
166 xpc_dictionary_set_value(replyMessage, kMessageKeyError, xerrobj);
167 CFReleaseSafe((__bridge CFTypeRef)(err));
169 xpc_connection_send_message(peer, replyMessage);
172 CFReleaseSafe((__bridge CFTypeRef)(keysToGet));
174 else if (operation && !strcmp(operation, kOperationRegisterKeysAndGet))
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);
184 NSArray *KTRallkeys = (__bridge NSArray *)(_CFXPCCreateCFObjectFromXPCObject(xKTRallkeys));
185 NSArray *KTRrequiresFirstUnlock = (__bridge NSArray *)(_CFXPCCreateCFObjectFromXPCObject(xKTRrequiresFirstUnlock));
186 NSArray *KTRrequiresUnlock = (__bridge NSArray *)(_CFXPCCreateCFObjectFromXPCObject(xKTRrequiresUnlock));
188 id object = [[UbiqitousKVSProxy sharedKVSProxy] registerKeysAndGet:getNewKeysOnly always:KTRallkeys reqFirstUnlock:KTRrequiresFirstUnlock reqUnlocked:KTRrequiresUnlock];
190 CFReleaseSafe((__bridge CFTypeRef)(KTRallkeys));
191 CFReleaseSafe((__bridge CFTypeRef)(KTRrequiresFirstUnlock));
192 CFReleaseSafe((__bridge CFTypeRef)(KTRrequiresUnlock));
194 secdebug("keytrace", "Result from [[UbiqitousKVSProxy sharedKVSProxy] registerKeysAndGet:]: %@", object);
196 xpc_object_t xobject = object ? _CFXPCCreateXPCObjectFromCFObject((__bridge CFTypeRef)(object)) : xpc_null_create();
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");
203 else if (operation && !strcmp(operation, kOperationUILocalNotification))
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));
219 else if (operation && !strcmp(operation, kOperationSetParams))
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
227 xpc_dictionary_set_string(replyMessage, kMessageKeyValue, "ACK");
228 xpc_connection_send_message(peer, replyMessage);
230 secdebug(XPROXYSCOPE, "setParams reply sent");
231 CFReleaseSafe((__bridge CFTypeRef)(paramsDict));
233 else if (operation && !strcmp(operation, kOperationRequestSyncWithAllPeers))
235 [[UbiqitousKVSProxy sharedKVSProxy] requestSyncWithAllPeers];
236 xpc_object_t replyMessage = xpc_dictionary_create_reply(event);
237 if (replyMessage) // Caller wanted an ACK, so give one
239 xpc_dictionary_set_string(replyMessage, kMessageKeyValue, "ACK");
240 xpc_connection_send_message(peer, replyMessage);
242 secdebug(XPROXYSCOPE, "RequestSyncWithAllPeers reply sent");
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);
253 describeXPCObject("handle_operation fail: ", event);
256 void finalize_connection(void *not_used)
258 secdebug(XPROXYSCOPE, "finalize_connection");
259 [[UbiqitousKVSProxy sharedKVSProxy] requestSynchronization:YES];
260 xpc_transaction_end();
263 static bool operation_put_dictionary(xpc_object_t event)
265 // PUT a set of objects into the KVS store. Return false if error
267 describeXPCObject("operation_put_dictionary event: ", event);
268 xpc_object_t xvalue = xpc_dictionary_get_value(event, kMessageKeyValue);
272 CFTypeRef cfvalue = _CFXPCCreateCFObjectFromXPCObject(xvalue);
273 if (cfvalue && (CFGetTypeID(cfvalue)==CFDictionaryGetTypeID()))
275 [[UbiqitousKVSProxy sharedKVSProxy] setObjectsFromDictionary:(__bridge NSDictionary *)cfvalue];
276 CFReleaseSafe(cfvalue);
280 describeXPCObject("operation_put_dictionary unable to convert to CF: ", xvalue);
281 CFReleaseSafe(cfvalue);
286 static bool operation_get_v2(xpc_object_t event)
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);
293 xpc_object_t replyMessage = xpc_dictionary_create_reply(event);
296 secdebug(XPROXYSCOPE, "can't create replyMessage");
297 assert(true); //must have a reply handler
300 xpc_object_t returnedValues = xpc_dictionary_create(NULL, NULL, 0);
303 secdebug(XPROXYSCOPE, "can't create returnedValues");
304 assert(true); // must have a spot for the returned values
308 xpc_object_t xvalue = xpc_dictionary_get_value(event, kMessageKeyValue);
311 secdebug(XPROXYSCOPE, "missing \"value\" key");
315 xpc_object_t xkeystoget = xpc_dictionary_get_value(xvalue, kMessageKeyKeysToGet);
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
322 secdebug(XPROXYSCOPE, "can't convert keystoget or is not an array");
323 CFReleaseSafe(keystoget);
327 [(__bridge NSArray *)keystoget enumerateObjectsUsingBlock: ^ (id obj, NSUInteger idx, BOOL *stop)
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);
337 else // get all values from kvs
339 secdebug(XPROXYSCOPE, "get all values from kvs");
340 NSDictionary *all = [[UbiqitousKVSProxy sharedKVSProxy] getAll];
341 [all enumerateKeysAndObjectsUsingBlock: ^ (id key, id obj, BOOL *stop)
343 xpc_object_t xobject = obj ? _CFXPCCreateXPCObjectFromCFObject((__bridge CFTypeRef)obj) : xpc_null_create();
344 xpc_dictionary_set_value(returnedValues, [(NSString *)key UTF8String], xobject);
348 xpc_dictionary_set_uint64(replyMessage, kMessageKeyVersion, kCKDXPCVersion);
349 xpc_dictionary_set_value(replyMessage, kMessageKeyValue, returnedValues);
350 xpc_connection_send_message(peer, replyMessage);
355 static void initializeProxyObjectWithConnection(const xpc_connection_t connection)
357 [[UbiqitousKVSProxy sharedKVSProxy] setItemsChangedBlock:^(CFDictionaryRef values)
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
369 static void cloudkeychainproxy_peer_event_handler(xpc_connection_t peer, xpc_object_t event)
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
380 } else if (event == XPC_ERROR_TERMINATION_IMMINENT) {
381 // Handle per-connection termination cleanup.
384 assert(type == XPC_TYPE_DICTIONARY);
385 // Handle the message.
386 // describeXPCObject("dictionary:", event);
387 cloudkeychainproxy_peer_dictionary_handler(peer, event);
391 static void cloudkeychainproxy_event_handler(xpc_connection_t peer)
393 // By defaults, new connections will target the default dispatch
396 if (xpc_get_type(peer) != XPC_TYPE_CONNECTION)
398 secdebug(XPROXYSCOPE, "expected XPC_TYPE_CONNECTION");
401 initializeProxyObjectWithConnection(peer);
402 xpc_connection_set_event_handler(peer, ^(xpc_object_t event)
404 cloudkeychainproxy_peer_event_handler(peer, event);
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);
413 static void diagnostics(int argc, const char *argv[])
417 NSDictionary *all = [[UbiqitousKVSProxy sharedKVSProxy] getAll];
418 NSLog(@"All: %@",all);
422 int ckdproxymain(int argc, const char *argv[])
424 secdebug(XPROXYSCOPE, "Starting CloudKeychainProxy");
425 char *wait4debugger = getenv("WAIT4DEBUGGER");
426 if (wait4debugger && !strcasecmp("YES", wait4debugger))
428 syslog(LOG_ERR, "Waiting for debugger");
429 kill(getpid(), SIGSTOP);
433 diagnostics(argc, argv);
437 #if !TARGET_IPHONE_SIMULATOR
438 xpc_track_activity(); // sets us up for idle timeout handling
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); });
445 [UbiqitousKVSProxy sharedKVSProxy];
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);
453 secdebug(XPROXYSCOPE, "Starting mainRunLoop");
454 NSRunLoop *runLoop = [NSRunLoop mainRunLoop];
458 secdebug(XPROXYSCOPE, "Exiting CloudKeychainProxy");