]> git.saurik.com Git - apple/security.git/blob - KVSKeychainSyncingProxy/cloudkeychainproxy.m
Security-59306.101.1.tar.gz
[apple/security.git] / KVSKeychainSyncingProxy / cloudkeychainproxy.m
1 /*
2 * Copyright (c) 2012-2014 Apple 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 //
29
30 /*
31 This XPC service is essentially just a proxy to iCloud KVS, which exists since
32 the main security code cannot link against Foundation.
33
34 See sendTSARequestWithXPC in tsaSupport.c for how to call the service
35
36 send message to app with xpc_connection_send_message
37
38 For now, build this with:
39
40 ~rc/bin/buildit . --rootsDirectory=/var/tmp -noverify -offline -target CloudKeychainProxy
41
42 and install or upgrade with:
43
44 darwinup install /var/tmp/sec.roots/sec~dst
45 darwinup upgrade /var/tmp/sec.roots/sec~dst
46
47 You must use darwinup during development to update system caches
48 */
49
50 //------------------------------------------------------------------------------------------------
51
52 #include <AssertMacros.h>
53 #include <TargetConditionals.h>
54
55 #import <Foundation/Foundation.h>
56 #import <Security/Security.h>
57 #import <utilities/SecCFRelease.h>
58 #import <xpc/xpc.h>
59 #import <xpc/private.h>
60 #import <CoreFoundation/CFXPCBridge.h>
61 #import <sysexits.h>
62 #import <syslog.h>
63 #import <CommonCrypto/CommonDigest.h>
64 #include <utilities/SecXPCError.h>
65 #include <utilities/SecCFError.h>
66
67 #include <utilities/SecFileLocations.h>
68
69 #import "CKDKVSProxy.h"
70 #import "CKDSecuritydAccount.h"
71 #import "CKDKVSStore.h"
72 #import "CKDAKSLockMonitor.h"
73
74
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);
78
79 static bool operation_put_dictionary(xpc_object_t event);
80 static bool operation_get_v2(xpc_connection_t peer, xpc_object_t event);
81
82 int ckdproxymain(int argc, const char *argv[]);
83
84 #define PROXYXPCSCOPE "xpcproxy"
85
86 static void describeXPCObject(char *prefix, xpc_object_t object)
87 {
88 //#ifndef NDEBUG
89 // This is useful for debugging.
90 if (object)
91 {
92 char *desc = xpc_copy_description(object);
93 secdebug(PROXYXPCSCOPE, "%s%s\n", prefix, desc);
94 free(desc);
95 }
96 else
97 secdebug(PROXYXPCSCOPE, "%s<NULL>\n", prefix);
98
99 //#endif
100 }
101
102 static NSObject *CreateNSObjectForCFXPCObjectFromKey(xpc_object_t xdict, const char * _Nonnull key)
103 {
104 xpc_object_t xObj = xpc_dictionary_get_value(xdict, key);
105
106 if (!xObj) {
107 return nil;
108 }
109
110 return (__bridge_transfer NSObject *)(_CFXPCCreateCFObjectFromXPCObject(xObj));
111 }
112
113 static NSArray<NSString*> *CreateArrayOfStringsForCFXPCObjectFromKey(xpc_object_t xdict, const char * _Nonnull key) {
114 NSObject * possibleArray = CreateNSObjectForCFXPCObjectFromKey(xdict, key);
115
116 if (![possibleArray isNSArray__])
117 return nil;
118
119 __block bool onlyStrings = true;
120 [(NSArray*) possibleArray enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
121 if (![obj isNSString__]) {
122 *stop = true;
123 onlyStrings = false;
124 }
125 }];
126
127 return onlyStrings ? (NSArray<NSString*>*) possibleArray : nil;
128 }
129
130 static CFStringRef kRegistrationFileName = CFSTR("com.apple.security.cloudkeychainproxy3.keysToRegister.plist");
131
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))];
140 });
141
142 return sProxy;
143 }
144
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
148 {
149 xpc_dictionary_set_string(replyMessage, kMessageKeyValue, "ACK");
150 xpc_connection_send_message(peer, replyMessage);
151 }
152 }
153
154 static void cloudkeychainproxy_peer_dictionary_handler(const xpc_connection_t peer, xpc_object_t event)
155 {
156 bool result = false;
157 int err = 0;
158
159 require_action_string(xpc_get_type(event) == XPC_TYPE_DICTIONARY, xit, err = -51, "expected XPC_TYPE_DICTIONARY");
160
161 const char *operation = xpc_dictionary_get_string(event, kMessageKeyOperation);
162 require_action(operation, xit, result = false);
163
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);
168
169 // Operations
170 secdebug(PROXYXPCSCOPE, "Handling %s operation", operation);
171
172
173 if (!strcmp(operation, kOperationPUTDictionary))
174 {
175 operation_put_dictionary(event);
176 sendAckResponse(peer, event);
177 }
178 else if (!strcmp(operation, kOperationGETv2))
179 {
180 operation_get_v2(peer, event);
181 // operationg_get_v2 sends the response
182 }
183 else if (!strcmp(operation, kOperationClearStore))
184 {
185 [SharedProxy() clearStore];
186 sendAckResponse(peer, event);
187 }
188 else if (!strcmp(operation, kOperationSynchronize))
189 {
190 [SharedProxy() synchronizeStore];
191 sendAckResponse(peer, event);
192 }
193 else if (!strcmp(operation, kOperationSynchronizeAndWait))
194 {
195 xpc_object_t replyMessage = xpc_dictionary_create_reply(event);
196 secnotice(XPROXYSCOPE, "%s XPC request: %s", kWAIT2MINID, kOperationSynchronizeAndWait);
197
198 [SharedProxy() waitForSynchronization:^(__unused NSDictionary *values, NSError *error)
199 {
200 secnotice(PROXYXPCSCOPE, "%s Result from [Proxy waitForSynchronization:]: %@", kWAIT2MINID, error);
201
202 if (replyMessage) // Caller wanted an ACK, so give one
203 {
204 if (error)
205 {
206 xpc_object_t xerrobj = SecCreateXPCObjectWithCFError((__bridge CFErrorRef)(error));
207 xpc_dictionary_set_value(replyMessage, kMessageKeyError, xerrobj);
208 } else {
209 xpc_dictionary_set_string(replyMessage, kMessageKeyValue, "ACK");
210 }
211 xpc_connection_send_message(peer, replyMessage);
212 }
213 }];
214 }
215 else if (!strcmp(operation, kOperationRegisterKeys))
216 {
217 xpc_object_t xkeysToRegisterDict = xpc_dictionary_get_value(event, kMessageKeyValue);
218
219 xpc_object_t xKTRallkeys = xpc_dictionary_get_value(xkeysToRegisterDict, kMessageAllKeys);
220
221 NSString* accountUUID = (NSString*) CreateNSObjectForCFXPCObjectFromKey(event, kMessageKeyAccountUUID);
222
223 if (![accountUUID isKindOfClass:[NSString class]]) {
224 accountUUID = nil;
225 }
226
227 NSDictionary *KTRallkeys = (__bridge_transfer NSDictionary *)(_CFXPCCreateCFObjectFromXPCObject(xKTRallkeys));
228
229 [SharedProxy() registerKeys: KTRallkeys forAccount: accountUUID];
230 sendAckResponse(peer, event);
231
232 secdebug(PROXYXPCSCOPE, "RegisterKeys message sent");
233 }
234 else if (!strcmp(operation, kOperationRemoveKeys))
235 {
236 xpc_object_t xkeysToRemoveDict = xpc_dictionary_get_value(event, kMessageKeyValue);
237
238 NSString* accountUUID = (NSString*) CreateNSObjectForCFXPCObjectFromKey(event, kMessageKeyAccountUUID);
239
240 if (![accountUUID isKindOfClass:[NSString class]]) {
241 accountUUID = nil;
242 }
243
244 NSArray *KTRallkeys = (__bridge_transfer NSArray *)(_CFXPCCreateCFObjectFromXPCObject(xkeysToRemoveDict));
245
246 [SharedProxy() removeKeys:KTRallkeys forAccount:accountUUID];
247 sendAckResponse(peer, event);
248
249 secdebug(PROXYXPCSCOPE, "RemoveKeys message sent");
250 }
251 else if (!strcmp(operation, kOperationRequestSyncWithPeers))
252 {
253
254 NSArray<NSString*> * peerIDs = CreateArrayOfStringsForCFXPCObjectFromKey(event, kMessageKeyPeerIDList);
255 NSArray<NSString*> * backupPeerIDs = CreateArrayOfStringsForCFXPCObjectFromKey(event, kMesssgeKeyBackupPeerIDList);
256
257 require_action(peerIDs && backupPeerIDs, xit, (secnotice(XPROXYSCOPE, "Bad call to sync with peers"), result = false));
258
259 [SharedProxy() requestSyncWithPeerIDs: peerIDs backupPeerIDs: backupPeerIDs];
260 sendAckResponse(peer, event);
261
262 secdebug(PROXYXPCSCOPE, "RequestSyncWithAllPeers reply sent");
263 }
264 else if (!strcmp(operation, kOperationHasPendingSyncWithPeer)) {
265 NSString *peerID = (NSString*) CreateNSObjectForCFXPCObjectFromKey(event, kMessageKeyPeerID);
266
267 BOOL hasPending = [SharedProxy() hasSyncPendingFor: peerID];
268
269 xpc_object_t replyMessage = xpc_dictionary_create_reply(event);
270 if (replyMessage)
271 {
272 xpc_dictionary_set_bool(replyMessage, kMessageKeyValue, hasPending);
273 xpc_connection_send_message(peer, replyMessage);
274 secdebug(PROXYXPCSCOPE, "HasPendingSyncWithPeer reply sent");
275 }
276 }
277 else if (!strcmp(operation, kOperationHasPendingKey)) {
278 NSString *peerID = (NSString*) CreateNSObjectForCFXPCObjectFromKey(event, kMessageKeyPeerID);
279
280 BOOL hasPending = [SharedProxy() hasPendingKey: peerID];
281
282 xpc_object_t replyMessage = xpc_dictionary_create_reply(event);
283 if (replyMessage)
284 {
285 xpc_dictionary_set_bool(replyMessage, kMessageKeyValue, hasPending);
286 xpc_connection_send_message(peer, replyMessage);
287 secdebug(PROXYXPCSCOPE, "HasIncomingMessageFromPeer reply sent");
288 }
289 }
290 else if (!strcmp(operation, kOperationRequestEnsurePeerRegistration))
291 {
292 [SharedProxy() requestEnsurePeerRegistration];
293 sendAckResponse(peer, event);
294 secdebug(PROXYXPCSCOPE, "RequestEnsurePeerRegistration reply sent");
295 }
296 else if (!strcmp(operation, kOperationFlush))
297 {
298 [SharedProxy() doAfterFlush:^{
299 sendAckResponse(peer, event);
300 secdebug(PROXYXPCSCOPE, "flush reply sent");
301 }];
302 }
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);
309 }];
310 }
311 else
312 {
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);
315 free(description);
316 }
317 result = true;
318 xit:
319 if (!result)
320 describeXPCObject("handle_operation fail: ", event);
321 }
322
323 void finalize_connection(void *not_used)
324 {
325 secdebug(PROXYXPCSCOPE, "finalize_connection");
326 [SharedProxy() synchronizeStore];
327 xpc_transaction_end();
328 }
329
330 static bool operation_put_dictionary(xpc_object_t event)
331 {
332 // PUT a set of objects into the KVS store. Return false if error
333 xpc_object_t xvalue = xpc_dictionary_get_value(event, kMessageKeyValue);
334 if (!xvalue) {
335 return false;
336 }
337
338 NSObject* object = (__bridge_transfer NSObject*) _CFXPCCreateCFObjectFromXPCObject(xvalue);
339 if (![object isKindOfClass:[NSDictionary<NSString*, NSObject*> class]]) {
340 describeXPCObject("operation_put_dictionary unable to convert to CF: ", xvalue);
341 return false;
342 }
343
344 [SharedProxy() setObjectsFromDictionary: (NSDictionary<NSString*, NSObject*> *)object];
345
346 return true;
347 }
348
349 static bool operation_get_v2(xpc_connection_t peer, xpc_object_t event)
350 {
351 // GET a set of objects from the KVS store. Return false if error
352 xpc_object_t replyMessage = xpc_dictionary_create_reply(event);
353 if (!replyMessage)
354 {
355 secdebug(PROXYXPCSCOPE, "can't create replyMessage");
356 assert(false); //must have a reply handler
357 return false;
358 }
359 xpc_object_t returnedValues = xpc_dictionary_create(NULL, NULL, 0);
360 if (!returnedValues)
361 {
362 secdebug(PROXYXPCSCOPE, "can't create returnedValues");
363 assert(false); // must have a spot for the returned values
364 return false;
365 }
366
367 xpc_object_t xvalue = xpc_dictionary_get_value(event, kMessageKeyValue);
368 if (!xvalue)
369 {
370 secdebug(PROXYXPCSCOPE, "missing \"value\" key");
371 return false;
372 }
373
374 xpc_object_t xkeystoget = xpc_dictionary_get_value(xvalue, kMessageKeyKeysToGet);
375 if (xkeystoget)
376 {
377 secdebug(PROXYXPCSCOPE, "got xkeystoget");
378 CFTypeRef keystoget = _CFXPCCreateCFObjectFromXPCObject(xkeystoget);
379 if (!keystoget || (CFGetTypeID(keystoget)!=CFArrayGetTypeID())) // not "getAll", this is an error of some kind
380 {
381 secdebug(PROXYXPCSCOPE, "can't convert keystoget or is not an array");
382 CFReleaseSafe(keystoget);
383 return false;
384 }
385
386 [(__bridge NSArray *)keystoget enumerateObjectsUsingBlock: ^ (id obj, NSUInteger idx, BOOL *stop)
387 {
388 NSString *key = (NSString *)obj;
389 id object = [SharedProxy() objectForKey:key];
390 secdebug(PROXYXPCSCOPE, "get: key: %@, object: %@", key, object);
391 xpc_object_t xobject = object ? _CFXPCCreateXPCObjectFromCFObject((__bridge CFTypeRef)object) : xpc_null_create();
392 xpc_dictionary_set_value(returnedValues, [key UTF8String], xobject);
393 }];
394 }
395 else // get all values from kvs
396 {
397 secdebug(PROXYXPCSCOPE, "get all values from kvs");
398 NSDictionary *all = [SharedProxy() copyAsDictionary];
399 [all enumerateKeysAndObjectsUsingBlock: ^ (id key, id obj, BOOL *stop)
400 {
401 xpc_object_t xobject = obj ? _CFXPCCreateXPCObjectFromCFObject((__bridge CFTypeRef)obj) : xpc_null_create();
402 xpc_dictionary_set_value(returnedValues, [(NSString *)key UTF8String], xobject);
403 }];
404 }
405
406 xpc_dictionary_set_uint64(replyMessage, kMessageKeyVersion, kCKDXPCVersion);
407 xpc_dictionary_set_value(replyMessage, kMessageKeyValue, returnedValues);
408 xpc_connection_send_message(peer, replyMessage);
409
410 return true;
411 }
412
413 static void cloudkeychainproxy_event_handler(xpc_connection_t peer)
414 {
415 if (xpc_get_type(peer) != XPC_TYPE_CONNECTION)
416 {
417 secdebug(PROXYXPCSCOPE, "expected XPC_TYPE_CONNECTION");
418 return;
419 }
420
421 xpc_object_t ent = xpc_connection_copy_entitlement_value(peer, "com.apple.CloudKeychainProxy.client");
422 if (ent == NULL || xpc_get_type(ent) != XPC_TYPE_BOOL || xpc_bool_get_value(ent) != true) {
423 secnotice(PROXYXPCSCOPE, "cloudkeychainproxy_event_handler: rejected client %d", xpc_connection_get_pid(peer));
424 xpc_connection_cancel(peer);
425 return;
426 }
427
428 xpc_connection_set_target_queue(peer, [SharedProxy() ckdkvsproxy_queue]);
429 xpc_connection_set_event_handler(peer, ^(xpc_object_t event)
430 {
431 // We could handle other peer events (e.g.) disconnects,
432 // but we don't keep per-client state so there is no need.
433 if (xpc_get_type(event) == XPC_TYPE_DICTIONARY) {
434 cloudkeychainproxy_peer_dictionary_handler(peer, event);
435 }
436 });
437
438 // This will tell the connection to begin listening for events. If you
439 // have some other initialization that must be done asynchronously, then
440 // you can defer this call until after that initialization is done.
441 xpc_connection_resume(peer);
442 }
443
444 static void diagnostics(int argc, const char *argv[])
445 {
446 @autoreleasepool
447 {
448 NSDictionary *all = [SharedProxy() copyAsDictionary];
449 NSLog(@"All: %@",all);
450 }
451 }
452
453 int ckdproxymain(int argc, const char *argv[])
454 {
455 secdebug(PROXYXPCSCOPE, "Starting CloudKeychainProxy");
456 char *wait4debugger = getenv("WAIT4DEBUGGER");
457
458 if (wait4debugger && !strcasecmp("YES", wait4debugger))
459 {
460 syslog(LOG_ERR, "Waiting for debugger");
461 kill(getpid(), SIGTSTP);
462 }
463
464 if (argc > 1) {
465 diagnostics(argc, argv);
466 return 0;
467 }
468
469 UbiqitousKVSProxy* proxyID = SharedProxy();
470
471 if (proxyID) { // nothing bad happened when initializing
472 xpc_connection_t listener = xpc_connection_create_mach_service(kCKPServiceName, NULL, XPC_CONNECTION_MACH_SERVICE_LISTENER);
473 xpc_connection_set_event_handler(listener, ^(xpc_object_t object){ cloudkeychainproxy_event_handler(object); });
474
475 // 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.
476 // Therefore I'm leaving the XPC connection suspended until that has time to process.
477 xpc_connection_resume(listener);
478
479 @autoreleasepool
480 {
481 secdebug(PROXYXPCSCOPE, "Starting mainRunLoop");
482 NSRunLoop *runLoop = [NSRunLoop mainRunLoop];
483 [runLoop run];
484 }
485 }
486
487 secdebug(PROXYXPCSCOPE, "Exiting CloudKeychainProxy");
488
489 return EXIT_FAILURE;
490 }
491
492 int main(int argc, const char *argv[])
493 {
494 return ckdproxymain(argc, argv);
495 }