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>
53 #include <TargetConditionals.h>
55 #import <Foundation/Foundation.h>
56 #import <Security/Security.h>
57 #import <utilities/SecCFRelease.h>
59 #import <xpc/private.h>
60 #import <CoreFoundation/CFXPCBridge.h>
63 #import <CommonCrypto/CommonDigest.h>
64 #include <utilities/SecXPCError.h>
65 #include <utilities/SecCFError.h>
67 #include <utilities/SecFileLocations.h>
69 #import "CKDKVSProxy.h"
70 #import "CKDSecuritydAccount.h"
71 #import "CKDKVSStore.h"
72 #import "CKDAKSLockMonitor.h"
75 void finalize_connection(void *not_used);
76 void handle_connection_event(const xpc_connection_t peer);
77 static void cloudkeychainproxy_peer_dictionary_handler(const xpc_connection_t peer, xpc_object_t event);
79 static bool operation_put_dictionary(xpc_object_t event);
80 static bool operation_get_v2(xpc_connection_t peer, xpc_object_t event);
82 int ckdproxymain(int argc, const char *argv[]);
84 #define PROXYXPCSCOPE "xpcproxy"
86 static void describeXPCObject(char *prefix, xpc_object_t object)
89 // This is useful for debugging.
92 char *desc = xpc_copy_description(object);
93 secdebug(PROXYXPCSCOPE, "%s%s\n", prefix, desc);
97 secdebug(PROXYXPCSCOPE, "%s<NULL>\n", prefix);
102 static NSObject *CreateNSObjectForCFXPCObjectFromKey(xpc_object_t xdict, const char * _Nonnull key)
104 xpc_object_t xObj = xpc_dictionary_get_value(xdict, key);
110 return (__bridge_transfer NSObject *)(_CFXPCCreateCFObjectFromXPCObject(xObj));
113 static NSArray<NSString*> *CreateArrayOfStringsForCFXPCObjectFromKey(xpc_object_t xdict, const char * _Nonnull key) {
114 NSObject * possibleArray = CreateNSObjectForCFXPCObjectFromKey(xdict, key);
116 if (![possibleArray isNSArray__])
119 __block bool onlyStrings = true;
120 [(NSArray*) possibleArray enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
121 if (![obj isNSString__]) {
127 return onlyStrings ? (NSArray<NSString*>*) possibleArray : nil;
130 static CFStringRef kRegistrationFileName = CFSTR("com.apple.security.cloudkeychainproxy3.keysToRegister.plist");
132 static UbiqitousKVSProxy *SharedProxy(void) {
133 static UbiqitousKVSProxy *sProxy = NULL;
134 static dispatch_once_t onceToken;
135 dispatch_once(&onceToken, ^{
136 sProxy = [UbiqitousKVSProxy withAccount: [CKDSecuritydAccount securitydAccount]
137 store: [CKDKVSStore kvsInterface]
138 lockMonitor: [CKDAKSLockMonitor monitor]
139 persistence: (NSURL *)CFBridgingRelease(SecCopyURLForFileInPreferencesDirectory(kRegistrationFileName))];
145 static void sendAckResponse(const xpc_connection_t peer, xpc_object_t event) {
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);
154 static void cloudkeychainproxy_peer_dictionary_handler(const xpc_connection_t peer, xpc_object_t event)
159 require_action_string(xpc_get_type(event) == XPC_TYPE_DICTIONARY, xit, err = -51, "expected XPC_TYPE_DICTIONARY");
161 const char *operation = xpc_dictionary_get_string(event, kMessageKeyOperation);
162 require_action(operation, xit, result = false);
164 // Check protocol version
165 uint64_t version = xpc_dictionary_get_uint64(event, kMessageKeyVersion);
166 secdebug(PROXYXPCSCOPE, "Reply version: %lld\n", version);
167 require_action(version == kCKDXPCVersion, xit, result = false);
170 secdebug(PROXYXPCSCOPE, "Handling %s operation", operation);
173 if (!strcmp(operation, kOperationPUTDictionary))
175 operation_put_dictionary(event);
176 sendAckResponse(peer, event);
178 else if (!strcmp(operation, kOperationGETv2))
180 operation_get_v2(peer, event);
181 // operationg_get_v2 sends the response
183 else if (!strcmp(operation, kOperationClearStore))
185 [SharedProxy() clearStore];
186 sendAckResponse(peer, event);
188 else if (!strcmp(operation, kOperationSynchronize))
190 [SharedProxy() synchronizeStore];
191 sendAckResponse(peer, event);
193 else if (!strcmp(operation, kOperationSynchronizeAndWait))
195 xpc_object_t replyMessage = xpc_dictionary_create_reply(event);
196 secnotice(XPROXYSCOPE, "%s XPC request: %s", kWAIT2MINID, kOperationSynchronizeAndWait);
198 [SharedProxy() waitForSynchronization:^(__unused NSDictionary *values, NSError *error)
200 secnotice(PROXYXPCSCOPE, "%s Result from [Proxy waitForSynchronization:]: %@", kWAIT2MINID, error);
202 if (replyMessage) // Caller wanted an ACK, so give one
206 xpc_object_t xerrobj = SecCreateXPCObjectWithCFError((__bridge CFErrorRef)(error));
207 xpc_dictionary_set_value(replyMessage, kMessageKeyError, xerrobj);
209 xpc_dictionary_set_string(replyMessage, kMessageKeyValue, "ACK");
211 xpc_connection_send_message(peer, replyMessage);
215 else if (!strcmp(operation, kOperationRegisterKeys))
217 xpc_object_t xkeysToRegisterDict = xpc_dictionary_get_value(event, kMessageKeyValue);
219 xpc_object_t xKTRallkeys = xpc_dictionary_get_value(xkeysToRegisterDict, kMessageAllKeys);
221 NSString* accountUUID = (NSString*) CreateNSObjectForCFXPCObjectFromKey(event, kMessageKeyAccountUUID);
223 if (![accountUUID isKindOfClass:[NSString class]]) {
227 NSDictionary *KTRallkeys = (__bridge_transfer NSDictionary *)(_CFXPCCreateCFObjectFromXPCObject(xKTRallkeys));
229 [SharedProxy() registerKeys: KTRallkeys forAccount: accountUUID];
230 sendAckResponse(peer, event);
232 secdebug(PROXYXPCSCOPE, "RegisterKeys message sent");
234 else if (!strcmp(operation, kOperationRemoveKeys))
236 xpc_object_t xkeysToRemoveDict = xpc_dictionary_get_value(event, kMessageKeyValue);
238 NSString* accountUUID = (NSString*) CreateNSObjectForCFXPCObjectFromKey(event, kMessageKeyAccountUUID);
240 if (![accountUUID isKindOfClass:[NSString class]]) {
244 NSArray *KTRallkeys = (__bridge_transfer NSArray *)(_CFXPCCreateCFObjectFromXPCObject(xkeysToRemoveDict));
246 [SharedProxy() removeKeys:KTRallkeys forAccount:accountUUID];
247 sendAckResponse(peer, event);
249 secdebug(PROXYXPCSCOPE, "RemoveKeys message sent");
251 else if (!strcmp(operation, kOperationRequestSyncWithPeers))
254 NSArray<NSString*> * peerIDs = CreateArrayOfStringsForCFXPCObjectFromKey(event, kMessageKeyPeerIDList);
255 NSArray<NSString*> * backupPeerIDs = CreateArrayOfStringsForCFXPCObjectFromKey(event, kMesssgeKeyBackupPeerIDList);
257 require_action(peerIDs && backupPeerIDs, xit, (secnotice(XPROXYSCOPE, "Bad call to sync with peers"), result = false));
259 [SharedProxy() requestSyncWithPeerIDs: peerIDs backupPeerIDs: backupPeerIDs];
260 sendAckResponse(peer, event);
262 secdebug(PROXYXPCSCOPE, "RequestSyncWithAllPeers reply sent");
264 else if (!strcmp(operation, kOperationHasPendingSyncWithPeer)) {
265 NSString *peerID = (NSString*) CreateNSObjectForCFXPCObjectFromKey(event, kMessageKeyPeerID);
267 BOOL hasPending = [SharedProxy() hasSyncPendingFor: peerID];
269 xpc_object_t replyMessage = xpc_dictionary_create_reply(event);
272 xpc_dictionary_set_bool(replyMessage, kMessageKeyValue, hasPending);
273 xpc_connection_send_message(peer, replyMessage);
274 secdebug(PROXYXPCSCOPE, "HasPendingSyncWithPeer reply sent");
277 else if (!strcmp(operation, kOperationHasPendingKey)) {
278 NSString *peerID = (NSString*) CreateNSObjectForCFXPCObjectFromKey(event, kMessageKeyPeerID);
280 BOOL hasPending = [SharedProxy() hasPendingKey: peerID];
282 xpc_object_t replyMessage = xpc_dictionary_create_reply(event);
285 xpc_dictionary_set_bool(replyMessage, kMessageKeyValue, hasPending);
286 xpc_connection_send_message(peer, replyMessage);
287 secdebug(PROXYXPCSCOPE, "HasIncomingMessageFromPeer reply sent");
290 else if (!strcmp(operation, kOperationRequestEnsurePeerRegistration))
292 [SharedProxy() requestEnsurePeerRegistration];
293 sendAckResponse(peer, event);
294 secdebug(PROXYXPCSCOPE, "RequestEnsurePeerRegistration reply sent");
296 else if (!strcmp(operation, kOperationFlush))
298 [SharedProxy() doAfterFlush:^{
299 sendAckResponse(peer, event);
300 secdebug(PROXYXPCSCOPE, "flush reply sent");
303 else if (!strcmp(operation, kOperationPerfCounters)) {
304 [SharedProxy() perfCounters:^(NSDictionary *counters){
305 xpc_object_t replyMessage = xpc_dictionary_create_reply(event);
306 xpc_object_t object = _CFXPCCreateXPCObjectFromCFObject((__bridge CFTypeRef)counters);
307 xpc_dictionary_set_value(replyMessage, kMessageKeyValue, object);
308 xpc_connection_send_message(peer, replyMessage);
313 char *description = xpc_copy_description(event);
314 secdebug(PROXYXPCSCOPE, "Unknown op=%s request from pid %d: %s", operation, xpc_connection_get_pid(peer), description);
320 describeXPCObject("handle_operation fail: ", event);
323 void finalize_connection(void *not_used)
325 secdebug(PROXYXPCSCOPE, "finalize_connection");
326 [SharedProxy() synchronizeStore];
327 xpc_transaction_end();
330 static bool operation_put_dictionary(xpc_object_t event)
332 // PUT a set of objects into the KVS store. Return false if error
333 describeXPCObject("operation_put_dictionary event: ", event);
334 xpc_object_t xvalue = xpc_dictionary_get_value(event, kMessageKeyValue);
339 NSObject* object = (__bridge_transfer NSObject*) _CFXPCCreateCFObjectFromXPCObject(xvalue);
340 if (![object isKindOfClass:[NSDictionary<NSString*, NSObject*> class]]) {
341 describeXPCObject("operation_put_dictionary unable to convert to CF: ", xvalue);
345 [SharedProxy() setObjectsFromDictionary: (NSDictionary<NSString*, NSObject*> *)object];
350 static bool operation_get_v2(xpc_connection_t peer, xpc_object_t event)
352 // GET a set of objects from the KVS store. Return false if error
353 describeXPCObject("operation_get_v2 event: ", event);
355 xpc_object_t replyMessage = xpc_dictionary_create_reply(event);
358 secdebug(PROXYXPCSCOPE, "can't create replyMessage");
359 assert(false); //must have a reply handler
362 xpc_object_t returnedValues = xpc_dictionary_create(NULL, NULL, 0);
365 secdebug(PROXYXPCSCOPE, "can't create returnedValues");
366 assert(false); // must have a spot for the returned values
370 xpc_object_t xvalue = xpc_dictionary_get_value(event, kMessageKeyValue);
373 secdebug(PROXYXPCSCOPE, "missing \"value\" key");
377 xpc_object_t xkeystoget = xpc_dictionary_get_value(xvalue, kMessageKeyKeysToGet);
380 secdebug(PROXYXPCSCOPE, "got xkeystoget");
381 CFTypeRef keystoget = _CFXPCCreateCFObjectFromXPCObject(xkeystoget);
382 if (!keystoget || (CFGetTypeID(keystoget)!=CFArrayGetTypeID())) // not "getAll", this is an error of some kind
384 secdebug(PROXYXPCSCOPE, "can't convert keystoget or is not an array");
385 CFReleaseSafe(keystoget);
389 [(__bridge NSArray *)keystoget enumerateObjectsUsingBlock: ^ (id obj, NSUInteger idx, BOOL *stop)
391 NSString *key = (NSString *)obj;
392 id object = [SharedProxy() objectForKey:key];
393 secdebug(PROXYXPCSCOPE, "get: key: %@, object: %@", key, object);
394 xpc_object_t xobject = object ? _CFXPCCreateXPCObjectFromCFObject((__bridge CFTypeRef)object) : xpc_null_create();
395 xpc_dictionary_set_value(returnedValues, [key UTF8String], xobject);
396 describeXPCObject("operation_get_v2: value from kvs: ", xobject);
399 else // get all values from kvs
401 secdebug(PROXYXPCSCOPE, "get all values from kvs");
402 NSDictionary *all = [SharedProxy() copyAsDictionary];
403 [all enumerateKeysAndObjectsUsingBlock: ^ (id key, id obj, BOOL *stop)
405 xpc_object_t xobject = obj ? _CFXPCCreateXPCObjectFromCFObject((__bridge CFTypeRef)obj) : xpc_null_create();
406 xpc_dictionary_set_value(returnedValues, [(NSString *)key UTF8String], xobject);
410 xpc_dictionary_set_uint64(replyMessage, kMessageKeyVersion, kCKDXPCVersion);
411 xpc_dictionary_set_value(replyMessage, kMessageKeyValue, returnedValues);
412 xpc_connection_send_message(peer, replyMessage);
417 static void cloudkeychainproxy_event_handler(xpc_connection_t peer)
419 if (xpc_get_type(peer) != XPC_TYPE_CONNECTION)
421 secdebug(PROXYXPCSCOPE, "expected XPC_TYPE_CONNECTION");
425 xpc_connection_set_target_queue(peer, [SharedProxy() ckdkvsproxy_queue]);
426 xpc_connection_set_event_handler(peer, ^(xpc_object_t event)
428 describeXPCObject("peer: ", peer); // Only describes under debug
430 // We could handle other peer events (e.g.) disconnects,
431 // but we don't keep per-client state so there is no need.
432 if (xpc_get_type(event) == XPC_TYPE_DICTIONARY) {
433 cloudkeychainproxy_peer_dictionary_handler(peer, event);
437 // This will tell the connection to begin listening for events. If you
438 // have some other initialization that must be done asynchronously, then
439 // you can defer this call until after that initialization is done.
440 xpc_connection_resume(peer);
443 static void diagnostics(int argc, const char *argv[])
447 NSDictionary *all = [SharedProxy() copyAsDictionary];
448 NSLog(@"All: %@",all);
452 int ckdproxymain(int argc, const char *argv[])
454 secdebug(PROXYXPCSCOPE, "Starting CloudKeychainProxy");
455 char *wait4debugger = getenv("WAIT4DEBUGGER");
457 if (wait4debugger && !strcasecmp("YES", wait4debugger))
459 syslog(LOG_ERR, "Waiting for debugger");
460 kill(getpid(), SIGTSTP);
464 diagnostics(argc, argv);
468 UbiqitousKVSProxy* proxyID = SharedProxy();
470 if (proxyID) { // nothing bad happened when initializing
471 xpc_connection_t listener = xpc_connection_create_mach_service(xpcServiceName, NULL, XPC_CONNECTION_MACH_SERVICE_LISTENER);
472 xpc_connection_set_event_handler(listener, ^(xpc_object_t object){ cloudkeychainproxy_event_handler(object); });
474 // 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.
475 // Therefore I'm leaving the XPC connection suspended until that has time to process.
476 xpc_connection_resume(listener);
480 secdebug(PROXYXPCSCOPE, "Starting mainRunLoop");
481 NSRunLoop *runLoop = [NSRunLoop mainRunLoop];
486 secdebug(PROXYXPCSCOPE, "Exiting CloudKeychainProxy");
491 int main(int argc, const char *argv[])
493 return ckdproxymain(argc, argv);