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