]> git.saurik.com Git - apple/security.git/blob - KVSKeychainSyncingProxy/cloudkeychainproxy.m
Security-59754.80.3.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 #import "SOSCloudKeychainConstants.h"
74
75 #include <utilities/simulatecrash_assert.h>
76
77
78 #define PROXYXPCSCOPE "xpcproxy"
79
80 @interface CloudKeychainProxy : NSObject
81 -(id _Nullable) init;
82
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;
87
88 + (CloudKeychainProxy *) sharedObject;
89 - (void) cloudkeychainproxy_peer_dictionary_handler: (const xpc_connection_t) peer forEvent: (xpc_object_t) event;
90
91 @end
92
93 static void cloudkeychainproxy_event_handler(xpc_connection_t peer)
94 {
95 if (xpc_get_type(peer) != XPC_TYPE_CONNECTION) {
96 secinfo(PROXYXPCSCOPE, "expected XPC_TYPE_CONNECTION");
97 return;
98 }
99
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);
104 return;
105 }
106
107 xpc_connection_set_target_queue(peer, [[CloudKeychainProxy sharedObject].proxyID ckdkvsproxy_queue]);
108 xpc_connection_set_event_handler(peer, ^(xpc_object_t event)
109 {
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];
114 }
115 });
116
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);
121 }
122
123 static void finalize_connection(void *not_used) {
124 secinfo(PROXYXPCSCOPE, "finalize_connection");
125 [[CloudKeychainProxy sharedObject].proxyID synchronizeStore];
126 xpc_transaction_end();
127 }
128
129 @implementation CloudKeychainProxy
130
131 static CFStringRef kRegistrationFileName = CFSTR("com.apple.security.cloudkeychainproxy3.keysToRegister.plist");
132
133 + (CloudKeychainProxy *) sharedObject {
134 static CloudKeychainProxy *sharedCKP = nil;
135 static dispatch_once_t onceToken;
136 dispatch_once(&onceToken, ^{
137 sharedCKP = [CloudKeychainProxy new];
138 });
139
140 return sharedCKP;
141 }
142
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];
150
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); });
154
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);
158
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();
164 });
165 dispatch_activate(_sigterm_source);
166 }
167 return self;
168 }
169
170
171 - (void) describeXPCObject: (char *) prefix withObject: (xpc_object_t) object {
172 if(object) {
173 char *desc = xpc_copy_description(object);
174 secinfo(PROXYXPCSCOPE, "%s%s", prefix, desc);
175 free(desc);
176 } else {
177 secinfo(PROXYXPCSCOPE, "%s<NULL>", prefix);
178 }
179 }
180
181 - (NSObject *) CreateNSObjectForCFXPCObjectFromKey: (xpc_object_t) xdict withKey: (const char * _Nonnull) key {
182 xpc_object_t xObj = xpc_dictionary_get_value(xdict, key);
183 if (!xObj) {
184 return nil;
185 }
186 return (__bridge_transfer NSObject *)(_CFXPCCreateCFObjectFromXPCObject(xObj));
187 }
188
189 - (NSArray<NSString*> *) CreateArrayOfStringsForCFXPCObjectFromKey: (xpc_object_t) xdict withKey: (const char * _Nonnull) key {
190 NSObject * possibleArray = [self CreateNSObjectForCFXPCObjectFromKey: xdict withKey: key];
191
192 if (![possibleArray isNSArray__]) {
193 return nil;
194 }
195
196 __block bool onlyStrings = true;
197 [(NSArray*) possibleArray enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
198 if (![obj isNSString__]) {
199 *stop = true;
200 onlyStrings = false;
201 }
202 }];
203
204 return onlyStrings ? (NSArray<NSString*>*) possibleArray : nil;
205 }
206
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);
212 }
213 }
214
215 - (void) cloudkeychainproxy_peer_dictionary_handler: (const xpc_connection_t) peer forEvent: (xpc_object_t) event {
216 bool result = false;
217 int err = 0;
218
219 @autoreleasepool {
220
221 require_action_string(xpc_get_type(event) == XPC_TYPE_DICTIONARY, xit, err = -51, "expected XPC_TYPE_DICTIONARY");
222
223 const char *operation = xpc_dictionary_get_string(event, kMessageKeyOperation);
224 require_action(operation, xit, result = false);
225
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);
230
231 // Operations
232 secinfo(PROXYXPCSCOPE, "Handling %s operation", operation);
233
234
235 if (!strcmp(operation, kOperationPUTDictionary))
236 {
237 [self operation_put_dictionary: event];
238 [self sendAckResponse: peer forEvent: event];
239 }
240 else if (!strcmp(operation, kOperationGETv2))
241 {
242 [self operation_get_v2: peer forEvent: event];
243 // operationg_get_v2 sends the response
244 }
245 else if (!strcmp(operation, kOperationClearStore))
246 {
247 [_proxyID clearStore];
248 [self sendAckResponse: peer forEvent: event];
249 }
250 else if (!strcmp(operation, kOperationSynchronize))
251 {
252 [_proxyID synchronizeStore];
253 [self sendAckResponse: peer forEvent: event];
254 }
255 else if (!strcmp(operation, kOperationSynchronizeAndWait))
256 {
257 xpc_object_t replyMessage = xpc_dictionary_create_reply(event);
258 secnotice(XPROXYSCOPE, "%s XPC request: %s", kWAIT2MINID, kOperationSynchronizeAndWait);
259
260 [_proxyID waitForSynchronization:^(__unused NSDictionary *values, NSError *error)
261 {
262 secnotice(PROXYXPCSCOPE, "%s Result from [Proxy waitForSynchronization:]: %@", kWAIT2MINID, error);
263
264 if (replyMessage) // Caller wanted an ACK, so give one
265 {
266 if (error)
267 {
268 xpc_object_t xerrobj = SecCreateXPCObjectWithCFError((__bridge CFErrorRef)(error));
269 xpc_dictionary_set_value(replyMessage, kMessageKeyError, xerrobj);
270 } else {
271 xpc_dictionary_set_string(replyMessage, kMessageKeyValue, "ACK");
272 }
273 xpc_connection_send_message(peer, replyMessage);
274 }
275 }];
276 }
277 else if (!strcmp(operation, kOperationRegisterKeys))
278 {
279 xpc_object_t xkeysToRegisterDict = xpc_dictionary_get_value(event, kMessageKeyValue);
280
281 xpc_object_t xKTRallkeys = xpc_dictionary_get_value(xkeysToRegisterDict, kMessageAllKeys);
282
283 NSString* accountUUID = (NSString*) [self CreateNSObjectForCFXPCObjectFromKey:event withKey: kMessageKeyAccountUUID];
284
285 if (![accountUUID isKindOfClass:[NSString class]]) {
286 accountUUID = nil;
287 }
288
289 NSDictionary *KTRallkeys = (__bridge_transfer NSDictionary *)(_CFXPCCreateCFObjectFromXPCObject(xKTRallkeys));
290
291 [_proxyID registerKeys: KTRallkeys forAccount: accountUUID];
292 [self sendAckResponse: peer forEvent: event];
293
294 secinfo(PROXYXPCSCOPE, "RegisterKeys message sent");
295 }
296 else if (!strcmp(operation, kOperationRemoveKeys))
297 {
298 xpc_object_t xkeysToRemoveDict = xpc_dictionary_get_value(event, kMessageKeyValue);
299
300 NSString* accountUUID = (NSString*) [self CreateNSObjectForCFXPCObjectFromKey: event withKey: kMessageKeyAccountUUID];
301
302 if (![accountUUID isKindOfClass:[NSString class]]) {
303 accountUUID = nil;
304 }
305
306 NSArray *KTRallkeys = (__bridge_transfer NSArray *)(_CFXPCCreateCFObjectFromXPCObject(xkeysToRemoveDict));
307
308 [_proxyID removeKeys:KTRallkeys forAccount:accountUUID];
309 [self sendAckResponse: peer forEvent: event];
310
311 secinfo(PROXYXPCSCOPE, "RemoveKeys message sent");
312 }
313 else if (!strcmp(operation, kOperationRequestSyncWithPeers))
314 {
315
316 NSArray<NSString*> * peerIDs = [self CreateArrayOfStringsForCFXPCObjectFromKey: event withKey: kMessageKeyPeerIDList];
317 NSArray<NSString*> * backupPeerIDs = [self CreateArrayOfStringsForCFXPCObjectFromKey: event withKey: kMesssgeKeyBackupPeerIDList];
318
319 require_action(peerIDs && backupPeerIDs, xit, (secnotice(XPROXYSCOPE, "Bad call to sync with peers"), result = false));
320
321 [_proxyID requestSyncWithPeerIDs: peerIDs backupPeerIDs: backupPeerIDs];
322 [self sendAckResponse: peer forEvent: event];
323
324 secinfo(PROXYXPCSCOPE, "RequestSyncWithAllPeers reply sent");
325 }
326 else if (!strcmp(operation, kOperationHasPendingSyncWithPeer)) {
327 NSString *peerID = (NSString*) [self CreateNSObjectForCFXPCObjectFromKey: event withKey: kMessageKeyPeerID];
328
329 BOOL hasPending = [_proxyID hasSyncPendingFor: peerID];
330
331 xpc_object_t replyMessage = xpc_dictionary_create_reply(event);
332 if (replyMessage)
333 {
334 xpc_dictionary_set_bool(replyMessage, kMessageKeyValue, hasPending);
335 xpc_connection_send_message(peer, replyMessage);
336 secinfo(PROXYXPCSCOPE, "HasPendingSyncWithPeer reply sent");
337 }
338 }
339 else if (!strcmp(operation, kOperationHasPendingKey)) {
340 NSString *peerID = (NSString*) [self CreateNSObjectForCFXPCObjectFromKey: event withKey: kMessageKeyPeerID];
341
342 BOOL hasPending = [_proxyID hasPendingKey: peerID];
343
344 xpc_object_t replyMessage = xpc_dictionary_create_reply(event);
345 if (replyMessage)
346 {
347 xpc_dictionary_set_bool(replyMessage, kMessageKeyValue, hasPending);
348 xpc_connection_send_message(peer, replyMessage);
349 secinfo(PROXYXPCSCOPE, "HasIncomingMessageFromPeer reply sent");
350 }
351 }
352 else if (!strcmp(operation, kOperationRequestEnsurePeerRegistration))
353 {
354 [_proxyID requestEnsurePeerRegistration];
355 [self sendAckResponse: peer forEvent: event];
356 secinfo(PROXYXPCSCOPE, "RequestEnsurePeerRegistration reply sent");
357 }
358 else if (!strcmp(operation, kOperationFlush))
359 {
360 [_proxyID doAfterFlush:^{
361 [self sendAckResponse: peer forEvent: event];
362 secinfo(PROXYXPCSCOPE, "flush reply sent");
363 }];
364 }
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);
371 }];
372 }
373 else
374 {
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);
377 free(description);
378 }
379 result = true;
380 xit:
381 if (!result) {
382 [self describeXPCObject: "handle_operation fail: " withObject: event];
383 }
384 }
385 }
386
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);
390 if (!xvalue) {
391 return false;
392 }
393
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];
397 return false;
398 }
399
400 [_proxyID setObjectsFromDictionary: (NSDictionary<NSString*, NSObject*> *)object];
401
402 return true;
403 }
404
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);
408 if (!replyMessage)
409 {
410 secinfo(PROXYXPCSCOPE, "can't create replyMessage");
411 assert(false); //must have a reply handler
412 return false;
413 }
414 xpc_object_t returnedValues = xpc_dictionary_create(NULL, NULL, 0);
415 if (!returnedValues)
416 {
417 secinfo(PROXYXPCSCOPE, "can't create returnedValues");
418 assert(false); // must have a spot for the returned values
419 return false;
420 }
421
422 xpc_object_t xvalue = xpc_dictionary_get_value(event, kMessageKeyValue);
423 if (!xvalue)
424 {
425 secinfo(PROXYXPCSCOPE, "missing \"value\" key");
426 return false;
427 }
428
429 xpc_object_t xkeystoget = xpc_dictionary_get_value(xvalue, kMessageKeyKeysToGet);
430 if (xkeystoget)
431 {
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
435 {
436 secinfo(PROXYXPCSCOPE, "can't convert keystoget or is not an array");
437 CFReleaseSafe(keystoget);
438 return false;
439 }
440
441 [(__bridge NSArray *)keystoget enumerateObjectsUsingBlock: ^ (id obj, NSUInteger idx, BOOL *stop)
442 {
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);
448 }];
449 }
450 else // get all values from kvs
451 {
452 secinfo(PROXYXPCSCOPE, "get all values from kvs");
453 NSDictionary *all = [_proxyID copyAsDictionary];
454 [all enumerateKeysAndObjectsUsingBlock: ^ (id key, id obj, BOOL *stop)
455 {
456 xpc_object_t xobject = obj ? _CFXPCCreateXPCObjectFromCFObject((__bridge CFTypeRef)obj) : xpc_null_create();
457 xpc_dictionary_set_value(returnedValues, [(NSString *)key UTF8String], xobject);
458 }];
459 }
460
461 xpc_dictionary_set_uint64(replyMessage, kMessageKeyVersion, kCKDXPCVersion);
462 xpc_dictionary_set_value(replyMessage, kMessageKeyValue, returnedValues);
463 xpc_connection_send_message(peer, replyMessage);
464
465 return true;
466 }
467
468
469 @end
470
471 static void diagnostics(int argc, const char *argv[]) {
472 @autoreleasepool {
473 NSDictionary *all = [[CloudKeychainProxy sharedObject].proxyID copyAsDictionary];
474 NSLog(@"All: %@",all);
475 }
476 }
477
478
479
480 int main(int argc, const char *argv[]) {
481 secinfo(PROXYXPCSCOPE, "Starting CloudKeychainProxy");
482 char *wait4debugger = getenv("WAIT4DEBUGGER");
483
484 if (wait4debugger && !strcasecmp("YES", wait4debugger)) {
485 syslog(LOG_ERR, "Waiting for debugger");
486 kill(getpid(), SIGTSTP);
487 }
488
489 if (argc > 1) {
490 diagnostics(argc, argv);
491 return 0;
492 }
493
494 CloudKeychainProxy *ckp = nil;
495 @autoreleasepool {
496 ckp = [CloudKeychainProxy sharedObject];
497 }
498
499 if (ckp) { // nothing bad happened when initializing
500 secinfo(PROXYXPCSCOPE, "Starting mainRunLoop");
501 NSRunLoop *runLoop = [NSRunLoop mainRunLoop];
502 [runLoop run];
503 }
504 secinfo(PROXYXPCSCOPE, "Exiting CloudKeychainProxy");
505
506 return EXIT_FAILURE;
507 }