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"
73 #import "SOSCloudKeychainConstants.h"
75 #include <utilities/simulatecrash_assert.h>
78 #define PROXYXPCSCOPE "xpcproxy"
80 @interface CloudKeychainProxy : NSObject
83 @property (nonatomic, retain) UbiqitousKVSProxy *proxyID;
84 @property (nonatomic, retain) xpc_connection_t listener;
85 @property (nonatomic, retain) dispatch_source_t sigterm_source;
86 @property (nonatomic, retain) NSURL *registrationFileName;
88 + (CloudKeychainProxy *) sharedObject;
89 - (void) cloudkeychainproxy_peer_dictionary_handler: (const xpc_connection_t) peer forEvent: (xpc_object_t) event;
93 static void cloudkeychainproxy_event_handler(xpc_connection_t peer)
95 if (xpc_get_type(peer) != XPC_TYPE_CONNECTION) {
96 secinfo(PROXYXPCSCOPE, "expected XPC_TYPE_CONNECTION");
100 xpc_object_t ent = xpc_connection_copy_entitlement_value(peer, "com.apple.CloudKeychainProxy.client");
101 if (ent == NULL || xpc_get_type(ent) != XPC_TYPE_BOOL || xpc_bool_get_value(ent) != true) {
102 secnotice(PROXYXPCSCOPE, "cloudkeychainproxy_event_handler: rejected client %d", xpc_connection_get_pid(peer));
103 xpc_connection_cancel(peer);
107 xpc_connection_set_target_queue(peer, [[CloudKeychainProxy sharedObject].proxyID ckdkvsproxy_queue]);
108 xpc_connection_set_event_handler(peer, ^(xpc_object_t event)
110 // We could handle other peer events (e.g.) disconnects,
111 // but we don't keep per-client state so there is no need.
112 if (xpc_get_type(event) == XPC_TYPE_DICTIONARY) {
113 [[CloudKeychainProxy sharedObject] cloudkeychainproxy_peer_dictionary_handler: peer forEvent: event];
117 // This will tell the connection to begin listening for events. If you
118 // have some other initialization that must be done asynchronously, then
119 // you can defer this call until after that initialization is done.
120 xpc_connection_resume(peer);
123 static void finalize_connection(void *not_used) {
124 secinfo(PROXYXPCSCOPE, "finalize_connection");
125 [[CloudKeychainProxy sharedObject].proxyID synchronizeStore];
126 xpc_transaction_end();
129 @implementation CloudKeychainProxy
131 static CFStringRef kRegistrationFileName = CFSTR("com.apple.security.cloudkeychainproxy3.keysToRegister.plist");
133 + (CloudKeychainProxy *) sharedObject {
134 static CloudKeychainProxy *sharedCKP = nil;
135 static dispatch_once_t onceToken;
136 dispatch_once(&onceToken, ^{
137 sharedCKP = [CloudKeychainProxy new];
143 -(id _Nullable) init {
144 if ((self = [super init])) {
145 _registrationFileName = (NSURL *)CFBridgingRelease(SecCopyURLForFileInPreferencesDirectory(kRegistrationFileName));
146 _proxyID = [UbiqitousKVSProxy withAccount: [CKDSecuritydAccount securitydAccount]
147 store: [CKDKVSStore kvsInterface]
148 lockMonitor: [CKDAKSLockMonitor monitor]
149 persistence: _registrationFileName];
151 _listener = xpc_connection_create_mach_service(kCKPServiceName, NULL, XPC_CONNECTION_MACH_SERVICE_LISTENER);
152 xpc_connection_set_finalizer_f(_listener, finalize_connection);
153 xpc_connection_set_event_handler(_listener, ^(xpc_object_t object){ cloudkeychainproxy_event_handler(object); });
155 // 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.
156 // Therefore I'm leaving the XPC connection suspended until that has time to process.
157 xpc_connection_resume(_listener);
159 (void)signal(SIGTERM, SIG_IGN);
160 _sigterm_source = dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL, SIGTERM, 0, _proxyID.ckdkvsproxy_queue);
161 dispatch_source_set_event_handler(_sigterm_source, ^{
162 secnotice(PROXYXPCSCOPE, "exiting due to SIGTERM");
163 xpc_transaction_exit_clean();
165 dispatch_activate(_sigterm_source);
171 - (void) describeXPCObject: (char *) prefix withObject: (xpc_object_t) object {
173 char *desc = xpc_copy_description(object);
174 secinfo(PROXYXPCSCOPE, "%s%s", prefix, desc);
177 secinfo(PROXYXPCSCOPE, "%s<NULL>", prefix);
181 - (NSObject *) CreateNSObjectForCFXPCObjectFromKey: (xpc_object_t) xdict withKey: (const char * _Nonnull) key {
182 xpc_object_t xObj = xpc_dictionary_get_value(xdict, key);
186 return (__bridge_transfer NSObject *)(_CFXPCCreateCFObjectFromXPCObject(xObj));
189 - (NSArray<NSString*> *) CreateArrayOfStringsForCFXPCObjectFromKey: (xpc_object_t) xdict withKey: (const char * _Nonnull) key {
190 NSObject * possibleArray = [self CreateNSObjectForCFXPCObjectFromKey: xdict withKey: key];
192 if (![possibleArray isNSArray__]) {
196 __block bool onlyStrings = true;
197 [(NSArray*) possibleArray enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
198 if (![obj isNSString__]) {
204 return onlyStrings ? (NSArray<NSString*>*) possibleArray : nil;
207 - (void) sendAckResponse: (const xpc_connection_t) peer forEvent: (xpc_object_t) event {
208 xpc_object_t replyMessage = xpc_dictionary_create_reply(event);
209 if (replyMessage) { // Caller wanted an ACK, so give one
210 xpc_dictionary_set_string(replyMessage, kMessageKeyValue, "ACK");
211 xpc_connection_send_message(peer, replyMessage);
215 - (void) cloudkeychainproxy_peer_dictionary_handler: (const xpc_connection_t) peer forEvent: (xpc_object_t) event {
221 require_action_string(xpc_get_type(event) == XPC_TYPE_DICTIONARY, xit, err = -51, "expected XPC_TYPE_DICTIONARY");
223 const char *operation = xpc_dictionary_get_string(event, kMessageKeyOperation);
224 require_action(operation, xit, result = false);
226 // Check protocol version
227 uint64_t version = xpc_dictionary_get_uint64(event, kMessageKeyVersion);
228 secinfo(PROXYXPCSCOPE, "Reply version: %lld", version);
229 require_action(version == kCKDXPCVersion, xit, result = false);
232 secinfo(PROXYXPCSCOPE, "Handling %s operation", operation);
235 if (!strcmp(operation, kOperationPUTDictionary))
237 [self operation_put_dictionary: event];
238 [self sendAckResponse: peer forEvent: event];
240 else if (!strcmp(operation, kOperationGETv2))
242 [self operation_get_v2: peer forEvent: event];
243 // operationg_get_v2 sends the response
245 else if (!strcmp(operation, kOperationClearStore))
247 [_proxyID clearStore];
248 [self sendAckResponse: peer forEvent: event];
250 else if (!strcmp(operation, kOperationSynchronize))
252 [_proxyID synchronizeStore];
253 [self sendAckResponse: peer forEvent: event];
255 else if (!strcmp(operation, kOperationSynchronizeAndWait))
257 xpc_object_t replyMessage = xpc_dictionary_create_reply(event);
258 secnotice(XPROXYSCOPE, "%s XPC request: %s", kWAIT2MINID, kOperationSynchronizeAndWait);
260 [_proxyID waitForSynchronization:^(__unused NSDictionary *values, NSError *error)
262 secnotice(PROXYXPCSCOPE, "%s Result from [Proxy waitForSynchronization:]: %@", kWAIT2MINID, error);
264 if (replyMessage) // Caller wanted an ACK, so give one
268 xpc_object_t xerrobj = SecCreateXPCObjectWithCFError((__bridge CFErrorRef)(error));
269 xpc_dictionary_set_value(replyMessage, kMessageKeyError, xerrobj);
271 xpc_dictionary_set_string(replyMessage, kMessageKeyValue, "ACK");
273 xpc_connection_send_message(peer, replyMessage);
277 else if (!strcmp(operation, kOperationRegisterKeys))
279 xpc_object_t xkeysToRegisterDict = xpc_dictionary_get_value(event, kMessageKeyValue);
281 xpc_object_t xKTRallkeys = xpc_dictionary_get_value(xkeysToRegisterDict, kMessageAllKeys);
283 NSString* accountUUID = (NSString*) [self CreateNSObjectForCFXPCObjectFromKey:event withKey: kMessageKeyAccountUUID];
285 if (![accountUUID isKindOfClass:[NSString class]]) {
289 NSDictionary *KTRallkeys = (__bridge_transfer NSDictionary *)(_CFXPCCreateCFObjectFromXPCObject(xKTRallkeys));
291 [_proxyID registerKeys: KTRallkeys forAccount: accountUUID];
292 [self sendAckResponse: peer forEvent: event];
294 secinfo(PROXYXPCSCOPE, "RegisterKeys message sent");
296 else if (!strcmp(operation, kOperationRemoveKeys))
298 xpc_object_t xkeysToRemoveDict = xpc_dictionary_get_value(event, kMessageKeyValue);
300 NSString* accountUUID = (NSString*) [self CreateNSObjectForCFXPCObjectFromKey: event withKey: kMessageKeyAccountUUID];
302 if (![accountUUID isKindOfClass:[NSString class]]) {
306 NSArray *KTRallkeys = (__bridge_transfer NSArray *)(_CFXPCCreateCFObjectFromXPCObject(xkeysToRemoveDict));
308 [_proxyID removeKeys:KTRallkeys forAccount:accountUUID];
309 [self sendAckResponse: peer forEvent: event];
311 secinfo(PROXYXPCSCOPE, "RemoveKeys message sent");
313 else if (!strcmp(operation, kOperationRequestSyncWithPeers))
316 NSArray<NSString*> * peerIDs = [self CreateArrayOfStringsForCFXPCObjectFromKey: event withKey: kMessageKeyPeerIDList];
317 NSArray<NSString*> * backupPeerIDs = [self CreateArrayOfStringsForCFXPCObjectFromKey: event withKey: kMesssgeKeyBackupPeerIDList];
319 require_action(peerIDs && backupPeerIDs, xit, (secnotice(XPROXYSCOPE, "Bad call to sync with peers"), result = false));
321 [_proxyID requestSyncWithPeerIDs: peerIDs backupPeerIDs: backupPeerIDs];
322 [self sendAckResponse: peer forEvent: event];
324 secinfo(PROXYXPCSCOPE, "RequestSyncWithAllPeers reply sent");
326 else if (!strcmp(operation, kOperationHasPendingSyncWithPeer)) {
327 NSString *peerID = (NSString*) [self CreateNSObjectForCFXPCObjectFromKey: event withKey: kMessageKeyPeerID];
329 BOOL hasPending = [_proxyID hasSyncPendingFor: peerID];
331 xpc_object_t replyMessage = xpc_dictionary_create_reply(event);
334 xpc_dictionary_set_bool(replyMessage, kMessageKeyValue, hasPending);
335 xpc_connection_send_message(peer, replyMessage);
336 secinfo(PROXYXPCSCOPE, "HasPendingSyncWithPeer reply sent");
339 else if (!strcmp(operation, kOperationHasPendingKey)) {
340 NSString *peerID = (NSString*) [self CreateNSObjectForCFXPCObjectFromKey: event withKey: kMessageKeyPeerID];
342 BOOL hasPending = [_proxyID hasPendingKey: peerID];
344 xpc_object_t replyMessage = xpc_dictionary_create_reply(event);
347 xpc_dictionary_set_bool(replyMessage, kMessageKeyValue, hasPending);
348 xpc_connection_send_message(peer, replyMessage);
349 secinfo(PROXYXPCSCOPE, "HasIncomingMessageFromPeer reply sent");
352 else if (!strcmp(operation, kOperationRequestEnsurePeerRegistration))
354 [_proxyID requestEnsurePeerRegistration];
355 [self sendAckResponse: peer forEvent: event];
356 secinfo(PROXYXPCSCOPE, "RequestEnsurePeerRegistration reply sent");
358 else if (!strcmp(operation, kOperationFlush))
360 [_proxyID doAfterFlush:^{
361 [self sendAckResponse: peer forEvent: event];
362 secinfo(PROXYXPCSCOPE, "flush reply sent");
365 else if (!strcmp(operation, kOperationPerfCounters)) {
366 [_proxyID perfCounters:^(NSDictionary *counters){
367 xpc_object_t replyMessage = xpc_dictionary_create_reply(event);
368 xpc_object_t object = _CFXPCCreateXPCObjectFromCFObject((__bridge CFTypeRef)counters);
369 xpc_dictionary_set_value(replyMessage, kMessageKeyValue, object);
370 xpc_connection_send_message(peer, replyMessage);
375 char *description = xpc_copy_description(event);
376 secinfo(PROXYXPCSCOPE, "Unknown op=%s request from pid %d: %s", operation, xpc_connection_get_pid(peer), description);
382 [self describeXPCObject: "handle_operation fail: " withObject: event];
387 - (bool) operation_put_dictionary: (xpc_object_t) event {
388 // PUT a set of objects into the KVS store. Return false if error
389 xpc_object_t xvalue = xpc_dictionary_get_value(event, kMessageKeyValue);
394 NSObject* object = (__bridge_transfer NSObject*) _CFXPCCreateCFObjectFromXPCObject(xvalue);
395 if (![object isKindOfClass:[NSDictionary<NSString*, NSObject*> class]]) {
396 [self describeXPCObject: "operation_put_dictionary unable to convert to CF: " withObject: xvalue];
400 [_proxyID setObjectsFromDictionary: (NSDictionary<NSString*, NSObject*> *)object];
405 - (bool) operation_get_v2: (xpc_connection_t) peer forEvent: (xpc_object_t) event {
406 // GET a set of objects from the KVS store. Return false if error
407 xpc_object_t replyMessage = xpc_dictionary_create_reply(event);
410 secinfo(PROXYXPCSCOPE, "can't create replyMessage");
411 assert(false); //must have a reply handler
414 xpc_object_t returnedValues = xpc_dictionary_create(NULL, NULL, 0);
417 secinfo(PROXYXPCSCOPE, "can't create returnedValues");
418 assert(false); // must have a spot for the returned values
422 xpc_object_t xvalue = xpc_dictionary_get_value(event, kMessageKeyValue);
425 secinfo(PROXYXPCSCOPE, "missing \"value\" key");
429 xpc_object_t xkeystoget = xpc_dictionary_get_value(xvalue, kMessageKeyKeysToGet);
432 secinfo(PROXYXPCSCOPE, "got xkeystoget");
433 CFTypeRef keystoget = _CFXPCCreateCFObjectFromXPCObject(xkeystoget);
434 if (!keystoget || (CFGetTypeID(keystoget)!=CFArrayGetTypeID())) // not "getAll", this is an error of some kind
436 secinfo(PROXYXPCSCOPE, "can't convert keystoget or is not an array");
437 CFReleaseSafe(keystoget);
441 [(__bridge NSArray *)keystoget enumerateObjectsUsingBlock: ^ (id obj, NSUInteger idx, BOOL *stop)
443 NSString *key = (NSString *)obj;
444 id object = [_proxyID objectForKey:key];
445 secinfo(PROXYXPCSCOPE, "get: key: %@, object: %@", key, object);
446 xpc_object_t xobject = object ? _CFXPCCreateXPCObjectFromCFObject((__bridge CFTypeRef)object) : xpc_null_create();
447 xpc_dictionary_set_value(returnedValues, [key UTF8String], xobject);
450 else // get all values from kvs
452 secinfo(PROXYXPCSCOPE, "get all values from kvs");
453 NSDictionary *all = [_proxyID copyAsDictionary];
454 [all enumerateKeysAndObjectsUsingBlock: ^ (id key, id obj, BOOL *stop)
456 xpc_object_t xobject = obj ? _CFXPCCreateXPCObjectFromCFObject((__bridge CFTypeRef)obj) : xpc_null_create();
457 xpc_dictionary_set_value(returnedValues, [(NSString *)key UTF8String], xobject);
461 xpc_dictionary_set_uint64(replyMessage, kMessageKeyVersion, kCKDXPCVersion);
462 xpc_dictionary_set_value(replyMessage, kMessageKeyValue, returnedValues);
463 xpc_connection_send_message(peer, replyMessage);
471 static void diagnostics(int argc, const char *argv[]) {
473 NSDictionary *all = [[CloudKeychainProxy sharedObject].proxyID copyAsDictionary];
474 NSLog(@"All: %@",all);
480 int main(int argc, const char *argv[]) {
481 secinfo(PROXYXPCSCOPE, "Starting CloudKeychainProxy");
482 char *wait4debugger = getenv("WAIT4DEBUGGER");
484 if (wait4debugger && !strcasecmp("YES", wait4debugger)) {
485 syslog(LOG_ERR, "Waiting for debugger");
486 kill(getpid(), SIGTSTP);
490 diagnostics(argc, argv);
494 CloudKeychainProxy *ckp = nil;
496 ckp = [CloudKeychainProxy sharedObject];
499 if (ckp) { // nothing bad happened when initializing
500 secinfo(PROXYXPCSCOPE, "Starting mainRunLoop");
501 NSRunLoop *runLoop = [NSRunLoop mainRunLoop];
504 secinfo(PROXYXPCSCOPE, "Exiting CloudKeychainProxy");