]> git.saurik.com Git - apple/security.git/blob - KVSKeychainSyncingProxy/CKDKVSProxy.m
Security-58286.270.3.0.1.tar.gz
[apple/security.git] / KVSKeychainSyncingProxy / CKDKVSProxy.m
1 /*
2 * Copyright (c) 2012-2014,2016 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 // CKDKVSProxy.m
26 // ckd-xpc
27 //
28
29 #import <Foundation/Foundation.h>
30
31 #import <utilities/debugging.h>
32 #import <os/activity.h>
33
34 #import "CKDKVSProxy.h"
35 #import "CKDKVSStore.h"
36 #import "CKDAKSLockMonitor.h"
37 #import "CKDSecuritydAccount.h"
38 #import "NSURL+SOSPlistStore.h"
39
40 #include <Security/SecureObjectSync/SOSARCDefines.h>
41 #include <Security/SecureObjectSync/SOSKVSKeys.h>
42 #include <utilities/SecCFWrappers.h>
43 #include <utilities/SecPLWrappers.h>
44
45 #include "SOSCloudKeychainConstants.h"
46
47 #include <utilities/SecAKSWrappers.h>
48 #include <utilities/SecADWrapper.h>
49 #include <utilities/SecNSAdditions.h>
50 #import "XPCNotificationDispatcher.h"
51
52
53 @interface NSSet (CKDLogging)
54 - (NSString*) logKeys;
55 - (NSString*) logIDs;
56 @end
57
58 @implementation NSSet (CKDLogging)
59 - (NSString*) logKeys {
60 return [self sortedElementsJoinedByString:@" "];
61 }
62
63 - (NSString*) logIDs {
64 return [self sortedElementsTruncated:8 JoinedByString:@" "];
65 }
66 @end
67
68
69 /*
70 The total space available in your app’s iCloud key-value storage is 1 MB.
71 The maximum number of keys you can specify is 1024, and the size limit for
72 each value associated with a key is 1 MB. So, for example, if you store a
73 single large value of 1 MB for a single key, that consumes your total
74 available storage. If you store 1 KB of data for each key, you can use
75 1,000 key-value pairs.
76 */
77
78 static NSString *kKeyKeyParameterKeys = @"KeyParameterKeys";
79 static NSString *kKeyCircleKeys = @"CircleKeys";
80 static NSString *kKeyMessageKeys = @"MessageKeys";
81
82 static NSString *kKeyAlwaysKeys = @"AlwaysKeys";
83 static NSString *kKeyFirstUnlockKeys = @"FirstUnlockKeys";
84 static NSString *kKeyUnlockedKeys = @"UnlockedKeys";
85 static NSString *kKeyPendingKeys = @"PendingKeys";
86 static NSString *kKeyUnsentChangedKeys = @"unsentChangedKeys";
87 static NSString *kKeyUnlockNotificationRequested = @"unlockNotificationRequested";
88 static NSString *kKeySyncWithPeersPending = @"SyncWithPeersPending";
89
90 static NSString *kKeyPendingSyncPeerIDs = @"SyncPeerIDs";
91 static NSString *kKeyPendingSyncBackupPeerIDs = @"SyncBackupPeerIDs";
92
93 static NSString *kKeyEnsurePeerRegistration = @"EnsurePeerRegistration";
94 static NSString *kKeyDSID = @"DSID";
95 static NSString *kMonitorState = @"MonitorState";
96 static NSString *kKeyAccountUUID = @"MonitorState";
97
98 static NSString *kMonitorPenaltyBoxKey = @"Penalty";
99 static NSString *kMonitorMessageKey = @"Message";
100 static NSString *kMonitorConsecutiveWrites = @"ConsecutiveWrites";
101 static NSString *kMonitorLastWriteTimestamp = @"LastWriteTimestamp";
102 static NSString *kMonitorMessageQueue = @"MessageQueue";
103 static NSString *kMonitorPenaltyTimer = @"PenaltyTimer";
104 static NSString *kMonitorDidWriteDuringPenalty = @"DidWriteDuringPenalty";
105
106 static NSString *kMonitorTimeTable = @"TimeTable";
107 static NSString *kMonitorFirstMinute = @"AFirstMinute";
108 static NSString *kMonitorSecondMinute = @"BSecondMinute";
109 static NSString *kMonitorThirdMinute = @"CThirdMinute";
110 static NSString *kMonitorFourthMinute = @"DFourthMinute";
111 static NSString *kMonitorFifthMinute = @"EFifthMinute";
112 static NSString *kMonitorWroteInTimeSlice = @"TimeSlice";
113
114 #define kSecServerKeychainChangedNotification "com.apple.security.keychainchanged"
115
116 @interface UbiqitousKVSProxy ()
117 @property (nonatomic) NSDictionary* persistentData;
118 - (void) doSyncWithAllPeers;
119 - (void) persistState;
120 @end
121
122 @implementation UbiqitousKVSProxy
123
124 + (instancetype)withAccount:(NSObject<CKDAccount>*) account
125 store:(NSObject<CKDStore>*) store
126 lockMonitor:(NSObject<CKDLockMonitor>*) lockMonitor
127 persistence:(NSURL*) localPersistence
128 {
129 return [[self alloc] initWithAccount:account
130 store:store
131 lockMonitor:lockMonitor
132 persistence:localPersistence];
133 }
134
135 - (instancetype)initWithAccount:(NSObject<CKDAccount>*) account
136 store:(NSObject<CKDStore>*) store
137 lockMonitor:(NSObject<CKDLockMonitor>*) lockMonitor
138 persistence:(NSURL*) localPersistence
139 {
140 if (self = [super init])
141 {
142 secnotice("event", "%@ start", self);
143
144 #if !(TARGET_OS_EMBEDDED)
145 // rdar://problem/26247270
146 if (geteuid() == 0) {
147 secerror("Cannot run CloudKeychainProxy as root");
148 return NULL;
149 }
150 #endif
151 _ensurePeerRegistration = NO;
152
153 _pendingSyncPeerIDs = [NSMutableSet set];
154 _pendingSyncBackupPeerIDs = [NSMutableSet set];
155 _shadowPendingSyncPeerIDs = nil;
156 _shadowPendingSyncBackupPeerIDs = nil;
157
158 _persistenceURL = localPersistence;
159
160 _account = account;
161 _store = store;
162 _lockMonitor = lockMonitor;
163
164
165 _calloutQueue = dispatch_queue_create("CKDCallout", DISPATCH_QUEUE_SERIAL);
166 _ckdkvsproxy_queue = dispatch_queue_create("CKDKVSProxy", DISPATCH_QUEUE_SERIAL);
167
168 _freshnessCompletions = [NSMutableArray<FreshnessResponseBlock> array];
169
170 _monitor = [NSMutableDictionary dictionary];
171
172 [[XPCNotificationDispatcher dispatcher] addListener: self];
173
174 int notificationToken;
175 notify_register_dispatch(kSecServerKeychainChangedNotification, &notificationToken, _ckdkvsproxy_queue,
176 ^ (int token __unused)
177 {
178 secinfo("backoff", "keychain changed, wiping backoff monitor state");
179 self->_monitor = [NSMutableDictionary dictionary];
180 });
181
182 [self setPersistentData: [self.persistenceURL readPlist]];
183
184 _dsid = @"";
185 _accountUUID = @"";
186
187 [[self store] connectToProxy: self];
188 [[self lockMonitor] connectTo:self];
189
190 secdebug(XPROXYSCOPE, "%@ done", self);
191 }
192 return self;
193 }
194
195 - (NSString *)description
196 {
197 return [NSString stringWithFormat:@"<%s%s%s%s%s%s%s%s%s%s>",
198 [[self lockMonitor] locked] ? "L" : "U",
199 [[self lockMonitor] unlockedSinceBoot] ? "B" : "-",
200 _seenKVSStoreChange ? "K" : "-",
201 [self hasPendingNonShadowSyncIDs] ? "s" : "-",
202 _ensurePeerRegistration ? "e" : "-",
203 [_pendingKeys count] ? "p" : "-",
204 _inCallout ? "C" : "-",
205 [self hasPendingShadowSyncIDs] ? "S" : "-",
206 _shadowEnsurePeerRegistration ? "E" : "-",
207 [_shadowPendingKeys count] ? "P" : "-"];
208 }
209
210 //
211 // MARK: XPC Function commands
212 //
213 - (void) clearStore {
214 [self.store removeAllObjects];
215 }
216
217 - (void)synchronizeStore {
218 [self.store pushWrites];
219 }
220
221 - (id) objectForKey: (NSString*) key {
222 return [self.store objectForKey: key];
223 }
224 - (NSDictionary<NSString *, id>*) copyAsDictionary {
225 return [self.store copyAsDictionary];
226 }
227
228 - (void)_queue_processAllItems
229 {
230 dispatch_assert_queue(_ckdkvsproxy_queue);
231
232 NSDictionary *allItems = [self.store copyAsDictionary];
233 if (allItems)
234 {
235 secnotice("event", "%@ sending: %@", self, [[allItems allKeys] componentsJoinedByString: @" "]);
236 [self processKeyChangedEvent:allItems];
237 }
238 else
239 secdebug(XPROXYSCOPE, "%@ No items in KVS", self);
240 }
241
242 - (void)dealloc
243 {
244 secdebug(XPROXYSCOPE, "%@", self);
245 [[NSNotificationCenter defaultCenter] removeObserver:self
246 name:NSUbiquitousKeyValueStoreDidChangeExternallyNotification
247 object:nil];
248
249 [[NSNotificationCenter defaultCenter] removeObserver:self
250 name:NSUbiquityIdentityDidChangeNotification
251 object:nil];
252 }
253
254 // MARK: Persistence
255
256 - (NSDictionary*) persistentData
257 {
258 return @{ kKeyAlwaysKeys:[_alwaysKeys allObjects],
259 kKeyFirstUnlockKeys:[_firstUnlockKeys allObjects],
260 kKeyUnlockedKeys:[_unlockedKeys allObjects],
261 kKeyPendingKeys:[_pendingKeys allObjects],
262 kKeyPendingSyncPeerIDs:[_pendingSyncPeerIDs allObjects],
263 kKeyPendingSyncBackupPeerIDs:[_pendingSyncBackupPeerIDs allObjects],
264 kMonitorState:_monitor,
265 kKeyEnsurePeerRegistration:[NSNumber numberWithBool:_ensurePeerRegistration],
266 kKeyDSID:_dsid,
267 kKeyAccountUUID:_accountUUID
268 };
269 }
270
271 - (void) setPersistentData: (NSDictionary*) interests
272 {
273 _alwaysKeys = [NSMutableSet setWithArray: interests[kKeyAlwaysKeys]];
274 _firstUnlockKeys = [NSMutableSet setWithArray: interests[kKeyFirstUnlockKeys]];
275 _unlockedKeys = [NSMutableSet setWithArray: interests[kKeyUnlockedKeys]];
276
277 _pendingKeys = [NSMutableSet setWithArray: interests[kKeyPendingKeys]];
278
279 _pendingSyncPeerIDs = [NSMutableSet setWithArray: interests[kKeyPendingSyncPeerIDs]];
280 _pendingSyncBackupPeerIDs = [NSMutableSet setWithArray: interests[kKeyPendingSyncBackupPeerIDs]];
281
282 _monitor = interests[kMonitorState];
283 if(_monitor == nil)
284 _monitor = [NSMutableDictionary dictionary];
285
286 _ensurePeerRegistration = [interests[kKeyEnsurePeerRegistration] boolValue];
287
288 _dsid = interests[kKeyDSID];
289 _accountUUID = interests[kKeyAccountUUID];
290
291 // If we had a sync pending, we kick it off and migrate to sync with these peers
292 if ([interests[kKeySyncWithPeersPending] boolValue]) {
293 [self doSyncWithAllPeers];
294 }
295 }
296
297 - (void)persistState
298 {
299 NSDictionary* dataToSave = self.persistentData;
300
301 secdebug("persistence", "Writing registeredKeys: %@", [dataToSave compactDescription]);
302 if (![self.persistenceURL writePlist:dataToSave]) {
303 secerror("Failed to write persistence data to %@", self.persistenceURL);
304 }
305 }
306
307 - (void)perfCounters:(void(^)(NSDictionary *counters))callback
308 {
309 /* Collect and merge perf counters from other layers here too */
310 [self.store perfCounters:callback];
311 }
312
313
314 // MARK: Object setting
315
316
317 - (void)setStoreObjectsFromDictionary:(NSDictionary *)values
318 {
319 if (values == nil) {
320 secdebug(XPROXYSCOPE, "%@ NULL? values: %@", self, values);
321 return;
322 }
323
324 NSMutableDictionary<NSString*, NSObject*> *mutableValues = [values mutableCopy];
325 NSString* newDSID = asNSString([mutableValues extractObjectForKey:(__bridge NSString*) kSOSKVSOfficialDSIDKey]);
326 if (newDSID) {
327 _dsid = newDSID;
328 }
329
330 NSString* requiredDSID = asNSString([mutableValues extractObjectForKey:(__bridge NSString*) kSOSKVSRequiredKey]);
331 if (requiredDSID) {
332 if (_dsid == nil || [_dsid isEqualToString: @""]) {
333 secdebug("dsid", "CloudKeychainProxy setting dsid to :%@ from securityd", requiredDSID);
334 _dsid = requiredDSID;
335 } else if (![_dsid isEqual: requiredDSID]) {
336 secerror("Account DSIDs do not match, cloud keychain proxy: %@, securityd: %@", _dsid, requiredDSID);
337 secerror("Not going to write these: %@ into KVS!", values);
338 return;
339 } else {
340 secnoticeq("dsid", "DSIDs match, writing");
341 }
342 }
343
344 secnoticeq("keytrace", "%@ sending: %@", self, [[mutableValues allKeys] componentsJoinedByString: @" "]);
345 [mutableValues enumerateKeysAndObjectsUsingBlock: ^(id key, id obj, BOOL *stop)
346 {
347 if (obj == NULL || obj == [NSNull null]) {
348 [self.store removeObjectForKey:key];
349 } else {
350 if ([key hasPrefix:@"ak|"]) { // TODO: somewhat of a hack
351 id oldObj = [self.store objectForKey:key];
352 if ([oldObj isEqual: obj]) {
353 // Fix KVS repeated message undelivery by sending a NULL first (deafness)
354 secnoticeq("keytrace", "forcing resend of key write: %@", key);
355 [self.store removeObjectForKey:key];
356 }
357 }
358 [[self store] addOneToOutGoing];
359 [self.store setObject:obj forKey:key];
360 }
361 }];
362
363 [self.store pushWrites];
364 }
365
366 - (void)setObjectsFromDictionary:(NSDictionary<NSString*, NSObject*> *)values
367 {
368 [self setStoreObjectsFromDictionary:values];
369 }
370
371 - (void)waitForSynchronization:(void (^)(NSDictionary<NSString*, NSObject*> *results, NSError *err))handler
372 {
373 secnoticeq("fresh", "%s Requesting WFS", kWAIT2MINID);
374
375 [_freshnessCompletions addObject: ^(bool success, NSError *error){
376 secnoticeq("fresh", "%s WFS Done", kWAIT2MINID);
377 handler(nil, error);
378 }];
379
380 if ([self.freshnessCompletions count] == 1) {
381 // We can't talk to synchronize on the _ckdkvsproxy_queue or we deadlock,
382 // bounce to a global concurrent queue
383 dispatch_after(_nextFreshnessTime, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
384 NSError *error = nil;
385 bool success = [self.store pullUpdates:&error];
386
387 dispatch_async(self->_ckdkvsproxy_queue, ^{
388 [self waitForSyncDone: success error: error];
389 });
390 });
391 }
392 }
393
394 - (void) waitForSyncDone: (bool) success error: (NSError*) error{
395 if (success) {
396 const uint64_t delayBeforeCallingAgainInSeconds = 5ull * NSEC_PER_SEC;
397 _nextFreshnessTime = dispatch_time(DISPATCH_TIME_NOW, delayBeforeCallingAgainInSeconds);
398 }
399
400 secnoticeq("fresh", "%s Completing WFS", kWAIT2MINID);
401 [_freshnessCompletions enumerateObjectsUsingBlock:^(FreshnessResponseBlock _Nonnull block,
402 NSUInteger idx,
403 BOOL * _Nonnull stop) {
404 block(success, error);
405 }];
406 [_freshnessCompletions removeAllObjects];
407
408 }
409
410 //
411 // MARK: ----- KVS key lists -----
412 //
413
414 - (NSMutableSet *)copyAllKeyInterests
415 {
416 NSMutableSet *allKeys = [NSMutableSet setWithSet: _alwaysKeys];
417 [allKeys unionSet: _firstUnlockKeys];
418 [allKeys unionSet: _unlockedKeys];
419 return allKeys;
420 }
421
422 -(void)registerAtTimeKeys:(NSDictionary*)keyparms
423 {
424 if (keyparms == nil)
425 return;
426
427 NSArray *alwaysArray = [keyparms valueForKey: kKeyAlwaysKeys];
428 NSArray *firstUnlockedKeysArray = [keyparms valueForKey: kKeyFirstUnlockKeys];
429 NSArray *whenUnlockedKeysArray = [keyparms valueForKey: kKeyUnlockedKeys];
430
431 if(alwaysArray)
432 [_alwaysKeys unionSet: [NSMutableSet setWithArray: alwaysArray]];
433 if(firstUnlockedKeysArray)
434 [_firstUnlockKeys unionSet: [NSMutableSet setWithArray: firstUnlockedKeysArray]];
435 if(whenUnlockedKeysArray)
436 [_unlockedKeys unionSet: [NSMutableSet setWithArray: whenUnlockedKeysArray]];
437 }
438
439 - (void)removeKeys: (NSArray*)keys forAccount: (NSString*) accountUUID
440 {
441 secdebug(XPROXYSCOPE, "removeKeys: keys: %@", keys);
442
443 // We only reset when we know the ID and they send the ID and it changes.
444 bool newAccount = accountUUID != nil && self.accountUUID != nil && ![accountUUID isEqualToString: self.accountUUID];
445
446 if(newAccount){
447 secnotice(XPROXYSCOPE, "not removing keys, account UUID is for a new account");
448 return;
449 }
450 [keys enumerateObjectsUsingBlock:^(NSString* _Nonnull key, NSUInteger idx, BOOL * _Nonnull stop) {
451 secnotice(XPROXYSCOPE, "removing from KVS store: %@", key);
452 [self.store removeObjectForKey:key];
453 }];
454 }
455
456 - (void)registerKeys: (NSDictionary*)keys forAccount: (NSString*) accountUUID
457 {
458 secdebug(XPROXYSCOPE, "registerKeys: keys: %@", keys);
459
460 // We only reset when we know the ID and they send the ID and it changes.
461 bool newAccount = accountUUID != nil && self.accountUUID != nil && ![accountUUID isEqualToString: self.accountUUID];
462
463 if (accountUUID) {
464 self.accountUUID = accountUUID;
465 }
466
467 // If we're a new account we don't exclude the old keys
468 NSMutableSet *allOldKeys = newAccount ? [NSMutableSet set] : [self copyAllKeyInterests];
469
470
471 NSDictionary *keyparms = [keys valueForKey: [NSString stringWithUTF8String: kMessageKeyParameter]];
472 NSDictionary *circles = [keys valueForKey: [NSString stringWithUTF8String: kMessageCircle]];
473 NSDictionary *messages = [keys valueForKey: [NSString stringWithUTF8String: kMessageMessage]];
474
475 _alwaysKeys = [NSMutableSet set];
476 _firstUnlockKeys = [NSMutableSet set];
477 _unlockedKeys = [NSMutableSet set];
478
479 [self registerAtTimeKeys: keyparms];
480 [self registerAtTimeKeys: circles];
481 [self registerAtTimeKeys: messages];
482
483 NSMutableSet *allNewKeys = [self copyAllKeyInterests];
484
485 // Make sure keys we no longer care about are not pending
486 [_pendingKeys intersectSet:allNewKeys];
487 if (_shadowPendingKeys) {
488 [_shadowPendingKeys intersectSet:allNewKeys];
489 }
490
491 // All new keys only is new keys (remove old keys)
492 [allNewKeys minusSet:allOldKeys];
493
494 // Mark new keys pending, they're new!
495 NSMutableSet *newKeysForCurrentLockState = [self pendKeysAndGetNewlyPended:allNewKeys];
496
497 [self persistState]; // Before we might call out, save our state so we recover if we crash
498
499 [self intersectWithCurrentLockState: newKeysForCurrentLockState];
500 // TODO: Don't processPendingKeysForCurrentLockState if none of the new keys have values.
501 if ([newKeysForCurrentLockState count] != 0) {
502 [self processPendingKeysForCurrentLockState];
503 }
504 }
505
506 // MARK: ----- Event Handling -----
507
508 - (void)_queue_handleNotification:(const char *) name
509 {
510 dispatch_assert_queue(_ckdkvsproxy_queue);
511
512 if (strcmp(name, kNotifyTokenForceUpdate)==0) {
513 // DEBUG -- Possibly remove in future
514 [self _queue_processAllItems];
515 } else if (strcmp(name, kCloudKeychainStorechangeChangeNotification)==0) {
516 // DEBUG -- Possibly remove in future
517 [self _queue_kvsStoreChange];
518 }
519 }
520
521 - (void)_queue_storeKeysChanged: (NSSet<NSString*>*) changedKeys initial: (bool) initial
522 {
523 dispatch_assert_queue(_ckdkvsproxy_queue);
524
525 // Mark that our store is talking to us, so we don't have to make up for missing anything previous.
526 _seenKVSStoreChange = YES;
527
528 // Unmark them as pending as they have just changed and we'll process them.
529 [_pendingKeys minusSet:changedKeys];
530
531 // Only send values that we're currently interested in.
532 NSSet *keysOfInterestThatChanged = [self pendKeysAndGetPendingForCurrentLockState:changedKeys];
533 NSMutableDictionary *changedValues = [self copyValues:keysOfInterestThatChanged];
534 if (initial)
535 changedValues[(__bridge NSString*)kSOSKVSInitialSyncKey] = @"true";
536
537 secnotice("event", "%@ keysChangedInCloud: %@ keysOfInterest: %@ initial: %@",
538 self,
539 [[changedKeys allObjects] componentsJoinedByString: @" "],
540 [[changedValues allKeys] componentsJoinedByString: @" "],
541 initial ? @"YES" : @"NO");
542
543 if ([changedValues count])
544 [self processKeyChangedEvent:changedValues];
545 }
546
547 - (void)_queue_storeAccountChanged
548 {
549 dispatch_assert_queue(_ckdkvsproxy_queue);
550
551 secnotice("event", "%@", self);
552
553 NSDictionary *changedValues = nil;
554 if(_dsid)
555 changedValues = @{ (__bridge NSString*)kSOSKVSAccountChangedKey: _dsid };
556 else
557 changedValues = @{ (__bridge NSString*)kSOSKVSAccountChangedKey: @"true" };
558
559 [self processKeyChangedEvent:changedValues];
560 }
561
562 - (void) doAfterFlush: (dispatch_block_t) block
563 {
564 //Flush any pending communication to Securityd.
565 if(!_inCallout)
566 dispatch_async(_calloutQueue, block);
567 else
568 _shadowFlushBlock = block;
569 }
570
571 - (void) calloutWith: (void(^)(NSSet *pending, NSSet* pendingSyncIDs, NSSet* pendingBackupSyncIDs, bool ensurePeerRegistration, dispatch_queue_t queue, void(^done)(NSSet *handledKeys, NSSet *handledSyncs, bool handledEnsurePeerRegistration, NSError* error))) callout
572 {
573 // In CKDKVSProxy's serial queue
574
575 // dispatch_get_global_queue - well-known global concurrent queue
576 // dispatch_get_main_queue - default queue that is bound to the main thread
577 xpc_transaction_begin();
578 dispatch_async(_calloutQueue, ^{
579 __block NSSet *myPending;
580 __block NSSet *mySyncPeerIDs;
581 __block NSSet *mySyncBackupPeerIDs;
582 __block bool myEnsurePeerRegistration;
583 __block bool wasLocked;
584 dispatch_sync(self->_ckdkvsproxy_queue, ^{
585 myPending = [self->_pendingKeys copy];
586 mySyncPeerIDs = [self->_pendingSyncPeerIDs copy];
587 mySyncBackupPeerIDs = [self->_pendingSyncBackupPeerIDs copy];
588
589 myEnsurePeerRegistration = self->_ensurePeerRegistration;
590 wasLocked = [self.lockMonitor locked];
591
592 self->_inCallout = YES;
593
594 self->_shadowPendingKeys = [NSMutableSet set];
595 self->_shadowPendingSyncPeerIDs = [NSMutableSet set];
596 self->_shadowPendingSyncBackupPeerIDs = [NSMutableSet set];
597 });
598
599 callout(myPending, mySyncPeerIDs, mySyncBackupPeerIDs, myEnsurePeerRegistration, self->_ckdkvsproxy_queue, ^(NSSet *handledKeys, NSSet *handledSyncs, bool handledEnsurePeerRegistration, NSError* failure) {
600 secdebug("event", "%@ %s%s before callout handled: %s%s", self,
601 ![mySyncPeerIDs isEmpty] || ![mySyncBackupPeerIDs isEmpty] ? "S" : "s",
602 myEnsurePeerRegistration ? "E" : "e",
603 ![handledKeys isEmpty] ? "S" : "s",
604 handledEnsurePeerRegistration ? "E" : "e");
605
606 // In CKDKVSProxy's serial queue
607 self->_inCallout = NO;
608
609 // Update ensurePeerRegistration
610 self->_ensurePeerRegistration = ((self->_ensurePeerRegistration && !handledEnsurePeerRegistration) || self->_shadowEnsurePeerRegistration);
611
612 self->_shadowEnsurePeerRegistration = NO;
613
614 [self handlePendingEnsurePeerRegistrationRequests:true];
615
616 bool hadShadowPeerIDs = ![self->_shadowPendingSyncPeerIDs isEmpty] || ![self->_shadowPendingSyncBackupPeerIDs isEmpty];
617
618 // Update SyncWithPeers stuff.
619 if (handledSyncs) {
620 [self->_pendingSyncPeerIDs minusSet: handledSyncs];
621 [self->_pendingSyncBackupPeerIDs minusSet: handledSyncs];
622
623 if (![handledSyncs isEmpty]) {
624 secnotice("sync-ids", "handled syncIDs: %@", [handledSyncs logIDs]);
625 secnotice("sync-ids", "remaining peerIDs: %@", [self->_pendingSyncPeerIDs logIDs]);
626 secnotice("sync-ids", "remaining backupIDs: %@", [self->_pendingSyncBackupPeerIDs logIDs]);
627
628 if (hadShadowPeerIDs) {
629 secnotice("sync-ids", "signaled peerIDs: %@", [self->_shadowPendingSyncPeerIDs logIDs]);
630 secnotice("sync-ids", "signaled backupIDs: %@", [self->_shadowPendingSyncBackupPeerIDs logIDs]);
631 }
632 }
633
634 self->_shadowPendingSyncPeerIDs = nil;
635 self->_shadowPendingSyncBackupPeerIDs = nil;
636 }
637
638
639 // Update pendingKeys and handle them
640 [self->_pendingKeys removeObject: [NSNull null]]; // Don't let NULL hang around
641
642 [self->_pendingKeys minusSet: handledKeys];
643 bool hadShadowPendingKeys = [self->_shadowPendingKeys count];
644 // Move away shadownPendingKeys first, because pendKeysAndGetPendingForCurrentLockState
645 // will look at them. See rdar://problem/20733166.
646 NSSet *oldShadowPendingKeys = self->_shadowPendingKeys;
647 self->_shadowPendingKeys = nil;
648
649 NSSet *filteredKeys = [self pendKeysAndGetPendingForCurrentLockState:oldShadowPendingKeys];
650
651 secnoticeq("keytrace", "%@ account handled: %@ pending: %@", self,
652 [[handledKeys allObjects] componentsJoinedByString: @" "],
653 [[filteredKeys allObjects] componentsJoinedByString: @" "]);
654
655 // Write state to disk
656 [self persistState];
657
658 // Handle shadow pended stuff
659
660 // We only kick off another sync if we got new stuff during handling
661 if (hadShadowPeerIDs && ![self.lockMonitor locked]) {
662 secnotice("event", "%@ syncWithPeersPending: %d inCallout: %d isLocked: %d", self, [self hasPendingSyncIDs], self->_inCallout, [self.lockMonitor locked]);
663 if ([self hasPendingSyncIDs] && !self->_inCallout && ![self.lockMonitor locked]){
664 [self doSyncWithPendingPeers];
665 }
666 }
667
668 /* We don't want to call processKeyChangedEvent if we failed to
669 handle pending keys and the device didn't unlock nor receive
670 any kvs changes while we were in our callout.
671 Doing so will lead to securityd and CloudKeychainProxy
672 talking to each other forever in a tight loop if securityd
673 repeatedly returns an error processing the same message.
674 Instead we leave any old pending keys until the next event. */
675 if (hadShadowPendingKeys || (![self.lockMonitor locked] && wasLocked)){
676 [self processKeyChangedEvent:[self copyValues:filteredKeys]];
677 if(self->_shadowFlushBlock != NULL)
678 secerror("Flush block is not null and sending new keys");
679 }
680
681 if(self->_shadowFlushBlock != NULL){
682 dispatch_async(self->_calloutQueue, self->_shadowFlushBlock);
683 self->_shadowFlushBlock = NULL;
684 }
685
686 if (failure) {
687 [self.lockMonitor recheck];
688 }
689
690 xpc_transaction_end();
691 });
692 });
693 }
694
695 - (void) sendKeysCallout: (NSSet *(^)(NSSet* pending, NSError** error)) handleKeys {
696 [self calloutWith: ^(NSSet *pending, NSSet* pendingSyncIDs, NSSet* pendingBackupSyncIDs, bool ensurePeerRegistration, dispatch_queue_t queue, void(^done)(NSSet *handledKeys, NSSet *handledSyncs, bool handledEnsurePeerRegistration, NSError* error)) {
697 NSError* error = NULL;
698
699 secnotice("CloudKeychainProxy", "send keys: %@", pending);
700 NSSet * handled = handleKeys(pending, &error);
701
702 dispatch_async(queue, ^{
703 if (!handled) {
704 secerror("%@ ensurePeerRegistration failed: %@", self, error);
705 }
706
707 done(handled, nil, NO, error);
708 });
709 }];
710 }
711
712 - (void)handlePendingEnsurePeerRegistrationRequests:(bool)onlyIfUnlocked
713 {
714 // doEnsurePeerRegistration's callback will be run on _calloutQueue, so we should check the 'are we running yet' flags on that queue
715 dispatch_async(_calloutQueue, ^{
716 if(self.ensurePeerRegistration && (!onlyIfUnlocked || ![self.lockMonitor locked])) {
717 if(self.ensurePeerRegistrationEnqueuedButNotStarted) {
718 secnotice("EnsurePeerRegistration", "%@ ensurePeerRegistration block already enqueued, not starting a new one", self);
719 return;
720 }
721
722 [self doEnsurePeerRegistration];
723 }
724 });
725 }
726
727 - (void) doEnsurePeerRegistration
728 {
729 NSObject<CKDAccount>* accountDelegate = [self account];
730 self.ensurePeerRegistrationEnqueuedButNotStarted = true;
731 [self calloutWith:^(NSSet *pending, NSSet* pendingSyncIDs, NSSet* pendingBackupSyncIDs, bool ensurePeerRegistration, dispatch_queue_t queue, void(^done)(NSSet *handledKeys, NSSet *handledSyncs, bool handledEnsurePeerRegistration, NSError* error)) {
732 NSError* error = nil;
733 self.ensurePeerRegistrationEnqueuedButNotStarted = false;
734 bool handledEnsurePeerRegistration = [accountDelegate ensurePeerRegistration:&error];
735 secnotice("EnsurePeerRegistration", "%@ ensurePeerRegistration called, %@ (%@)", self, handledEnsurePeerRegistration ? @"success" : @"failure", error);
736 if (!handledEnsurePeerRegistration) {
737 [self.lockMonitor recheck];
738 handledEnsurePeerRegistration = ![self.lockMonitor locked]; // If we're unlocked we handled it, if we're locked we didn't.
739 // This means we get to fail once per unlock and then cut that spinning out.
740 }
741 dispatch_async(queue, ^{
742 done(nil, nil, handledEnsurePeerRegistration, error);
743 });
744 }];
745 }
746
747 - (void) doSyncWithPendingPeers
748 {
749 NSObject<CKDAccount>* accountDelegate = [self account];
750 [self calloutWith:^(NSSet *pending, NSSet* pendingSyncIDs, NSSet* pendingBackupSyncIDs, bool ensurePeerRegistration, dispatch_queue_t queue, void(^done)(NSSet *handledKeys, NSSet *handledSyncs, bool handledEnsurePeerRegistration, NSError* error)) {
751 NSError* error = NULL;
752 secnotice("syncwith", "%@ syncwith peers: %@", self, [[pendingSyncIDs allObjects] componentsJoinedByString:@" "]);
753 secnotice("syncwith", "%@ syncwith backups: %@", self, [[pendingBackupSyncIDs allObjects] componentsJoinedByString:@" "]);
754 NSSet<NSString*>* handled = [accountDelegate syncWithPeers:pendingSyncIDs backups:pendingBackupSyncIDs error:&error];
755 secnotice("syncwith", "%@ syncwith handled: %@", self, [[handled allObjects] componentsJoinedByString:@" "]);
756 dispatch_async(queue, ^{
757 if (!handled) {
758 // We might be confused about lock state
759 [self.lockMonitor recheck];
760 }
761
762 done(nil, handled, false, error);
763 });
764 }];
765 }
766
767 - (void) doSyncWithAllPeers
768 {
769 NSObject<CKDAccount>* accountDelegate = [self account];
770 [self calloutWith:^(NSSet *pending, NSSet* pendingSyncIDs, NSSet* pendingBackupSyncIDs, bool ensurePeerRegistration, dispatch_queue_t queue, void(^done)(NSSet *handledKeys, NSSet *handledSyncs, bool handledEnsurePeerRegistration, NSError*error)) {
771 NSError* error = NULL;
772 bool handled = [accountDelegate syncWithAllPeers:&error];
773 if (!handled) {
774 secerror("Failed to syncWithAllPeers: %@", error);
775 }
776 dispatch_async(queue, ^{
777 done(nil, nil, false, error);
778 });
779 }];
780 }
781
782 - (bool)hasPendingNonShadowSyncIDs {
783 return ![_pendingSyncPeerIDs isEmpty] || ![_pendingSyncBackupPeerIDs isEmpty];
784 }
785
786 - (bool)hasPendingShadowSyncIDs {
787 return (_shadowPendingSyncPeerIDs && ![_shadowPendingSyncPeerIDs isEmpty]) ||
788 (_shadowPendingSyncBackupPeerIDs && ![_shadowPendingSyncBackupPeerIDs isEmpty]);
789 }
790
791 - (bool)hasPendingSyncIDs
792 {
793 bool pendingIDs = [self hasPendingNonShadowSyncIDs];
794
795 if (_inCallout) {
796 pendingIDs |= [self hasPendingShadowSyncIDs];
797 }
798
799 return pendingIDs;
800 }
801
802 - (void)requestSyncWithPeerIDs: (NSArray<NSString*>*) peerIDs backupPeerIDs: (NSArray<NSString*>*) backupPeerIDs
803 {
804 if ([peerIDs count] == 0 && [backupPeerIDs count] == 0)
805 return; // Nothing to do;
806
807 NSSet<NSString*>* peerIDsSet = [NSSet setWithArray: peerIDs];
808 NSSet<NSString*>* backupPeerIDsSet = [NSSet setWithArray: backupPeerIDs];
809
810 [_pendingSyncPeerIDs unionSet: peerIDsSet];
811 [_pendingSyncBackupPeerIDs unionSet: backupPeerIDsSet];
812
813 if (_inCallout) {
814 [_shadowPendingSyncPeerIDs unionSet: peerIDsSet];
815 [_shadowPendingSyncBackupPeerIDs unionSet: backupPeerIDsSet];
816 }
817
818 [self persistState];
819
820 [self handlePendingEnsurePeerRegistrationRequests:true];
821
822 if ([self hasPendingSyncIDs] && !_inCallout && ![self.lockMonitor locked]){
823 [self doSyncWithPendingPeers];
824 }
825 }
826
827 - (BOOL)hasSyncPendingFor: (NSString*) peerID {
828 return [_pendingSyncPeerIDs containsObject: peerID] ||
829 (_shadowPendingSyncPeerIDs && [_shadowPendingSyncPeerIDs containsObject: peerID]);
830 }
831
832 - (BOOL)hasPendingKey: (NSString*) keyName {
833 return [self.pendingKeys containsObject: keyName]
834 || (_shadowPendingKeys && [self.shadowPendingKeys containsObject: keyName]);
835 }
836
837 - (void)requestEnsurePeerRegistration
838 {
839 #if !defined(NDEBUG)
840 NSString *desc = [self description];
841 #endif
842
843 if (_inCallout) {
844 _shadowEnsurePeerRegistration = YES;
845 } else {
846 _ensurePeerRegistration = YES;
847 [self handlePendingEnsurePeerRegistrationRequests:true];
848 [self persistState];
849 }
850
851 secdebug("event", "%@ %@", desc, self);
852 }
853
854 - (void)_queue_locked
855 {
856 dispatch_assert_queue(_ckdkvsproxy_queue);
857
858 secnotice("event", "%@ Locked", self);
859 }
860
861 - (void)_queue_unlocked
862 {
863 dispatch_assert_queue(_ckdkvsproxy_queue);
864
865 secnotice("event", "%@ Unlocked", self);
866 [self handlePendingEnsurePeerRegistrationRequests:false];
867
868 // First send changed keys to securityd so it can proccess updates
869 [self processPendingKeysForCurrentLockState];
870
871 // Then, tickle securityd to perform a sync if needed.
872 if ([self hasPendingSyncIDs]) {
873 [self doSyncWithPendingPeers];
874 }
875 }
876
877 - (void) _queue_kvsStoreChange {
878 dispatch_assert_queue(_ckdkvsproxy_queue);
879
880 os_activity_initiate("kvsStoreChange", OS_ACTIVITY_FLAG_DEFAULT, ^{
881 if (!self->_seenKVSStoreChange) {
882 secnotice("event", "%@ received darwin notification before first NSNotification", self);
883 // TODO This might not be needed if we always get the NSNotification
884 // deleived even if we were launched due to a kvsStoreChange
885 // Send all keys for current lock state to securityd so it can proccess them
886 [self pendKeysAndGetNewlyPended: [self copyAllKeyInterests]];
887 [self processPendingKeysForCurrentLockState];
888 } else {
889 secdebug("event", "%@ ignored, waiting for NSNotification", self);
890 }
891 });
892 }
893
894 #pragma mark -
895 #pragma mark XPCNotificationListener
896
897 - (void)handleNotification:(const char *) name
898 {
899 // sync because we cannot ensure the lifetime of name
900 dispatch_sync(_ckdkvsproxy_queue, ^{
901 [self _queue_handleNotification:name];
902 });
903 }
904
905 #pragma mark -
906 #pragma mark Calls from -[CKDKVSStore kvsStoreChanged:]
907
908 - (void)storeKeysChanged: (NSSet<NSString*>*) changedKeys initial: (bool) initial
909 {
910 // sync, caller must wait to ensure correct state
911 dispatch_sync(_ckdkvsproxy_queue, ^{
912 [self _queue_storeKeysChanged:changedKeys initial:initial];
913 });
914 }
915
916 - (void)storeAccountChanged
917 {
918 // sync, caller must wait to ensure correct state
919 dispatch_sync(_ckdkvsproxy_queue, ^{
920 [self _queue_storeAccountChanged];
921 });
922 }
923
924 #pragma mark -
925 #pragma mark CKDLockListener
926
927 - (void) locked
928 {
929 // sync, otherwise tests fail
930 dispatch_sync(_ckdkvsproxy_queue, ^{
931 [self _queue_locked];
932 });
933 }
934
935 - (void) unlocked
936 {
937 // sync, otherwise tests fail
938 dispatch_sync(_ckdkvsproxy_queue, ^{
939 [self _queue_unlocked];
940 });
941 }
942
943 //
944 // MARK: ----- Key Filtering -----
945 //
946
947 - (NSSet*) keysForCurrentLockState
948 {
949 secdebug("filtering", "%@ Filtering: unlockedSinceBoot: %d\n unlocked: %d\n, keysOfInterest: <%@>", self, (int) [self.lockMonitor unlockedSinceBoot], (int) ![self.lockMonitor locked], [self.persistentData compactDescription]);
950
951 NSMutableSet *currentStateKeys = [NSMutableSet setWithSet: _alwaysKeys];
952 if ([self.lockMonitor unlockedSinceBoot])
953 [currentStateKeys unionSet: _firstUnlockKeys];
954
955 if (![self.lockMonitor locked])
956 [currentStateKeys unionSet: _unlockedKeys];
957
958 return currentStateKeys;
959 }
960
961
962 - (NSMutableSet*) pendKeysAndGetNewlyPended: (NSSet*) keysToPend
963 {
964 NSMutableSet *filteredKeysToPend = [self copyAllKeyInterests];
965 [filteredKeysToPend intersectSet: keysToPend];
966
967 NSMutableSet *newlyPendedKeys = [filteredKeysToPend mutableCopy];
968 [newlyPendedKeys minusSet: _pendingKeys];
969 if (_shadowPendingKeys) {
970 [newlyPendedKeys minusSet: _shadowPendingKeys];
971 }
972
973 if (_shadowPendingKeys) {
974 [_shadowPendingKeys unionSet:filteredKeysToPend];
975 }
976 else{
977 [_pendingKeys unionSet:filteredKeysToPend];
978 }
979
980 return newlyPendedKeys;
981 }
982
983 - (void) intersectWithCurrentLockState: (NSMutableSet*) set
984 {
985 [set intersectSet: [self keysForCurrentLockState]];
986 }
987
988 - (NSMutableSet*) pendingKeysForCurrentLockState
989 {
990 NSMutableSet * result = [_pendingKeys mutableCopy];
991 [self intersectWithCurrentLockState:result];
992 return result;
993 }
994
995 - (NSMutableSet*) pendKeysAndGetPendingForCurrentLockState: (NSSet*) startingSet
996 {
997 [self pendKeysAndGetNewlyPended: startingSet];
998
999 return [self pendingKeysForCurrentLockState];
1000 }
1001
1002 - (NSMutableDictionary *)copyValues:(NSSet*)keysOfInterest
1003 {
1004 // Grab values from store.
1005 NSObject<CKDStore> *store = [self store];
1006 NSMutableDictionary *changedValues = [NSMutableDictionary dictionaryWithCapacity:0];
1007 [keysOfInterest enumerateObjectsUsingBlock:^(id obj, BOOL *stop)
1008 {
1009 NSString* key = (NSString*) obj;
1010 id objval = [store objectForKey:key];
1011 if (!objval) objval = [NSNull null];
1012
1013 [changedValues setObject:objval forKey:key];
1014 secdebug(XPROXYSCOPE, "%@ storeChanged updated value for %@", self, key);
1015 }];
1016 return changedValues;
1017 }
1018
1019 /*
1020 During RegisterKeys, separate keys-of-interest into three disjoint sets:
1021 - keys that we always want to be notified about; this means we can get the
1022 value at any time
1023 - keys that require the device to have been unlocked at least once
1024 - keys that require the device to be unlocked now
1025
1026 Typically, the sets of keys will be:
1027
1028 - Dk: alwaysKeys
1029 - Ck: firstUnlock
1030 - Ak: unlocked
1031
1032 The caller is responsible for making sure that the keys in e.g. alwaysKeys are
1033 values that can be handled at any time (that is, not when unlocked)
1034
1035 Each time we get a notification from ubiquity that keys have changed, we need to
1036 see if anything of interest changed. If we don't care, then done.
1037
1038 For each key-of-interest that changed, we either notify the client that things
1039 changed, or add it to a pendingNotifications list. If the notification to the
1040 client fails, also add it to the pendingNotifications list. This pending list
1041 should be written to persistent storage and consulted any time we either get an
1042 item changed notification, or get a stream event signalling a change in lock state.
1043
1044 We can notify the client either through XPC if a connection is set up, or call a
1045 routine in securityd to launch it.
1046
1047 */
1048
1049 - (void)processKeyChangedEvent:(NSDictionary *)changedValues
1050 {
1051 NSMutableDictionary* filtered = [NSMutableDictionary dictionary];
1052
1053 secnotice("processKeyChangedEvent", "changedValues:%@", changedValues);
1054 NSMutableArray* nullKeys = [NSMutableArray array];
1055 // Remove nulls because we don't want them in securityd.
1056 [changedValues enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
1057 if (obj == [NSNull null]){
1058 [nullKeys addObject:key];
1059 }else{
1060 filtered[key] = obj;
1061 }
1062 }];
1063 if ([nullKeys count])
1064 [_pendingKeys minusSet: [NSSet setWithArray: nullKeys]];
1065
1066 if([filtered count] != 0 ) {
1067 [self sendKeysCallout:^NSSet *(NSSet *pending, NSError** error) {
1068 secnotice("processing keys", "pending:%@", pending);
1069 NSError *updateError = nil;
1070 return [[self account] keysChanged: filtered error: &updateError];
1071 }];
1072 } else {
1073 secnoticeq("keytrace", "%@ null: %@ pending: %@", self,
1074 [nullKeys componentsJoinedByString: @" "],
1075 [[_pendingKeys allObjects] componentsJoinedByString: @" "]);
1076 }
1077 }
1078
1079 - (void) processPendingKeysForCurrentLockState
1080 {
1081 [self processKeyChangedEvent: [self copyValues: [self pendingKeysForCurrentLockState]]];
1082 }
1083
1084 @end
1085
1086