]> git.saurik.com Git - apple/security.git/blob - KVSKeychainSyncingProxy/CKDKVSProxy.m
Security-58286.51.6.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 [self handlePendingEnsurePeerRegistrationRequests:true];
622
623 bool hadShadowPeerIDs = ![self->_shadowPendingSyncPeerIDs isEmpty] || ![self->_shadowPendingSyncBackupPeerIDs isEmpty];
624
625 // Update SyncWithPeers stuff.
626 if (handledSyncs) {
627 [self->_pendingSyncPeerIDs minusSet: handledSyncs];
628 [self->_pendingSyncBackupPeerIDs minusSet: handledSyncs];
629
630 if (![handledSyncs isEmpty]) {
631 secnotice("sync-ids", "handled syncIDs: %@", [handledSyncs logIDs]);
632 secnotice("sync-ids", "remaining peerIDs: %@", [self->_pendingSyncPeerIDs logIDs]);
633 secnotice("sync-ids", "remaining backupIDs: %@", [self->_pendingSyncBackupPeerIDs logIDs]);
634
635 if (hadShadowPeerIDs) {
636 secnotice("sync-ids", "signaled peerIDs: %@", [self->_shadowPendingSyncPeerIDs logIDs]);
637 secnotice("sync-ids", "signaled backupIDs: %@", [self->_shadowPendingSyncBackupPeerIDs logIDs]);
638 }
639 }
640
641 self->_shadowPendingSyncPeerIDs = nil;
642 self->_shadowPendingSyncBackupPeerIDs = nil;
643 }
644
645
646 // Update pendingKeys and handle them
647 [self->_pendingKeys removeObject: [NSNull null]]; // Don't let NULL hang around
648
649 [self->_pendingKeys minusSet: handledKeys];
650 bool hadShadowPendingKeys = [self->_shadowPendingKeys count];
651 // Move away shadownPendingKeys first, because pendKeysAndGetPendingForCurrentLockState
652 // will look at them. See rdar://problem/20733166.
653 NSSet *oldShadowPendingKeys = self->_shadowPendingKeys;
654 self->_shadowPendingKeys = nil;
655
656 NSSet *filteredKeys = [self pendKeysAndGetPendingForCurrentLockState:oldShadowPendingKeys];
657
658 secnoticeq("keytrace", "%@ account handled: %@ pending: %@", self,
659 [[handledKeys allObjects] componentsJoinedByString: @" "],
660 [[filteredKeys allObjects] componentsJoinedByString: @" "]);
661
662 // Write state to disk
663 [self persistState];
664
665 // Handle shadow pended stuff
666
667 // We only kick off another sync if we got new stuff during handling
668 if (hadShadowPeerIDs && ![self.lockMonitor locked]) {
669 secnotice("event", "%@ syncWithPeersPending: %d inCallout: %d isLocked: %d", self, [self hasPendingSyncIDs], self->_inCallout, [self.lockMonitor locked]);
670 if ([self hasPendingSyncIDs] && !self->_inCallout && ![self.lockMonitor locked]){
671 [self doSyncWithPendingPeers];
672 }
673 }
674
675 /* We don't want to call processKeyChangedEvent if we failed to
676 handle pending keys and the device didn't unlock nor receive
677 any kvs changes while we were in our callout.
678 Doing so will lead to securityd and CloudKeychainProxy
679 talking to each other forever in a tight loop if securityd
680 repeatedly returns an error processing the same message.
681 Instead we leave any old pending keys until the next event. */
682 if (hadShadowPendingKeys || (![self.lockMonitor locked] && wasLocked)){
683 [self processKeyChangedEvent:[self copyValues:filteredKeys]];
684 if(self->_shadowFlushBlock != NULL)
685 secerror("Flush block is not null and sending new keys");
686 }
687
688 if(self->_shadowFlushBlock != NULL){
689 dispatch_async(self->_calloutQueue, self->_shadowFlushBlock);
690 self->_shadowFlushBlock = NULL;
691 }
692
693 if (failure) {
694 [self.lockMonitor recheck];
695 }
696
697 xpc_transaction_end();
698 });
699 });
700 }
701
702 - (void) sendKeysCallout: (NSSet *(^)(NSSet* pending, NSError** error)) handleKeys {
703 [self calloutWith: ^(NSSet *pending, NSSet* pendingSyncIDs, NSSet* pendingBackupSyncIDs, bool ensurePeerRegistration, dispatch_queue_t queue, void(^done)(NSSet *handledKeys, NSSet *handledSyncs, bool handledEnsurePeerRegistration, NSError* error)) {
704 NSError* error = NULL;
705
706 secnotice("CloudKeychainProxy", "send keys: %@", pending);
707 NSSet * handled = handleKeys(pending, &error);
708
709 dispatch_async(queue, ^{
710 if (!handled) {
711 secerror("%@ ensurePeerRegistration failed: %@", self, error);
712 }
713
714 done(handled, nil, NO, error);
715 });
716 }];
717 }
718
719 - (void)handlePendingEnsurePeerRegistrationRequests:(bool)onlyIfUnlocked
720 {
721 // doEnsurePeerRegistration's callback will be run on _calloutQueue, so we should check the 'are we running yet' flags on that queue
722 dispatch_async(_calloutQueue, ^{
723 if(self.ensurePeerRegistration && (!onlyIfUnlocked || ![self.lockMonitor locked])) {
724 if(self.ensurePeerRegistrationEnqueuedButNotStarted) {
725 secnotice("EnsurePeerRegistration", "%@ ensurePeerRegistration block already enqueued, not starting a new one", self);
726 return;
727 }
728
729 [self doEnsurePeerRegistration];
730 }
731 });
732 }
733
734 - (void) doEnsurePeerRegistration
735 {
736 NSObject<CKDAccount>* accountDelegate = [self account];
737 self.ensurePeerRegistrationEnqueuedButNotStarted = true;
738 [self calloutWith:^(NSSet *pending, NSSet* pendingSyncIDs, NSSet* pendingBackupSyncIDs, bool ensurePeerRegistration, dispatch_queue_t queue, void(^done)(NSSet *handledKeys, NSSet *handledSyncs, bool handledEnsurePeerRegistration, NSError* error)) {
739 NSError* error = nil;
740 self.ensurePeerRegistrationEnqueuedButNotStarted = false;
741 bool handledEnsurePeerRegistration = [accountDelegate ensurePeerRegistration:&error];
742 secnotice("EnsurePeerRegistration", "%@ ensurePeerRegistration called, %@ (%@)", self, handledEnsurePeerRegistration ? @"success" : @"failure", error);
743 if (!handledEnsurePeerRegistration) {
744 [self.lockMonitor recheck];
745 handledEnsurePeerRegistration = ![self.lockMonitor locked]; // If we're unlocked we handled it, if we're locked we didn't.
746 // This means we get to fail once per unlock and then cut that spinning out.
747 }
748 dispatch_async(queue, ^{
749 done(nil, nil, handledEnsurePeerRegistration, error);
750 });
751 }];
752 }
753
754 - (void) doSyncWithPendingPeers
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 secnotice("syncwith", "%@ syncwith peers: %@", self, [[pendingSyncIDs allObjects] componentsJoinedByString:@" "]);
760 secnotice("syncwith", "%@ syncwith backups: %@", self, [[pendingBackupSyncIDs allObjects] componentsJoinedByString:@" "]);
761 NSSet<NSString*>* handled = [accountDelegate syncWithPeers:pendingSyncIDs backups:pendingBackupSyncIDs error:&error];
762 secnotice("syncwith", "%@ syncwith handled: %@", self, [[handled allObjects] componentsJoinedByString:@" "]);
763 dispatch_async(queue, ^{
764 if (!handled) {
765 // We might be confused about lock state
766 [self.lockMonitor recheck];
767 }
768
769 done(nil, handled, false, error);
770 });
771 }];
772 }
773
774 - (void) doSyncWithAllPeers
775 {
776 NSObject<CKDAccount>* accountDelegate = [self account];
777 [self calloutWith:^(NSSet *pending, NSSet* pendingSyncIDs, NSSet* pendingBackupSyncIDs, bool ensurePeerRegistration, dispatch_queue_t queue, void(^done)(NSSet *handledKeys, NSSet *handledSyncs, bool handledEnsurePeerRegistration, NSError*error)) {
778 NSError* error = NULL;
779 bool handled = [accountDelegate syncWithAllPeers:&error];
780 if (!handled) {
781 secerror("Failed to syncWithAllPeers: %@", error);
782 }
783 dispatch_async(queue, ^{
784 done(nil, nil, false, error);
785 });
786 }];
787 }
788
789 - (bool)hasPendingNonShadowSyncIDs {
790 return ![_pendingSyncPeerIDs isEmpty] || ![_pendingSyncBackupPeerIDs isEmpty];
791 }
792
793 - (bool)hasPendingShadowSyncIDs {
794 return (_shadowPendingSyncPeerIDs && ![_shadowPendingSyncPeerIDs isEmpty]) ||
795 (_shadowPendingSyncBackupPeerIDs && ![_shadowPendingSyncBackupPeerIDs isEmpty]);
796 }
797
798 - (bool)hasPendingSyncIDs
799 {
800 bool pendingIDs = [self hasPendingNonShadowSyncIDs];
801
802 if (_inCallout) {
803 pendingIDs |= [self hasPendingShadowSyncIDs];
804 }
805
806 return pendingIDs;
807 }
808
809 - (void)requestSyncWithPeerIDs: (NSArray<NSString*>*) peerIDs backupPeerIDs: (NSArray<NSString*>*) backupPeerIDs
810 {
811 if ([peerIDs count] == 0 && [backupPeerIDs count] == 0)
812 return; // Nothing to do;
813
814 NSSet<NSString*>* peerIDsSet = [NSSet setWithArray: peerIDs];
815 NSSet<NSString*>* backupPeerIDsSet = [NSSet setWithArray: backupPeerIDs];
816
817 [_pendingSyncPeerIDs unionSet: peerIDsSet];
818 [_pendingSyncBackupPeerIDs unionSet: backupPeerIDsSet];
819
820 if (_inCallout) {
821 [_shadowPendingSyncPeerIDs unionSet: peerIDsSet];
822 [_shadowPendingSyncBackupPeerIDs unionSet: backupPeerIDsSet];
823 }
824
825 [self persistState];
826
827 [self handlePendingEnsurePeerRegistrationRequests:true];
828
829 if ([self hasPendingSyncIDs] && !_inCallout && ![self.lockMonitor locked]){
830 [self doSyncWithPendingPeers];
831 }
832 }
833
834 - (BOOL)hasSyncPendingFor: (NSString*) peerID {
835 return [_pendingSyncPeerIDs containsObject: peerID] ||
836 (_shadowPendingSyncPeerIDs && [_shadowPendingSyncPeerIDs containsObject: peerID]);
837 }
838
839 - (BOOL)hasPendingKey: (NSString*) keyName {
840 return [self.pendingKeys containsObject: keyName]
841 || (_shadowPendingKeys && [self.shadowPendingKeys containsObject: keyName]);
842 }
843
844 - (void)requestEnsurePeerRegistration
845 {
846 #if !defined(NDEBUG)
847 NSString *desc = [self description];
848 #endif
849
850 if (_inCallout) {
851 _shadowEnsurePeerRegistration = YES;
852 } else {
853 _ensurePeerRegistration = YES;
854 [self handlePendingEnsurePeerRegistrationRequests:true];
855 [self persistState];
856 }
857
858 secdebug("event", "%@ %@", desc, self);
859 }
860
861 - (void)_queue_locked
862 {
863 dispatch_assert_queue(_ckdkvsproxy_queue);
864
865 secnotice("event", "%@ Locked", self);
866 }
867
868 - (void)_queue_unlocked
869 {
870 dispatch_assert_queue(_ckdkvsproxy_queue);
871
872 secnotice("event", "%@ Unlocked", self);
873 [self handlePendingEnsurePeerRegistrationRequests:false];
874
875 // First send changed keys to securityd so it can proccess updates
876 [self processPendingKeysForCurrentLockState];
877
878 // Then, tickle securityd to perform a sync if needed.
879 if ([self hasPendingSyncIDs]) {
880 [self doSyncWithPendingPeers];
881 }
882 }
883
884 - (void) _queue_kvsStoreChange {
885 dispatch_assert_queue(_ckdkvsproxy_queue);
886
887 os_activity_initiate("kvsStoreChange", OS_ACTIVITY_FLAG_DEFAULT, ^{
888 if (!self->_seenKVSStoreChange) {
889 secnotice("event", "%@ received darwin notification before first NSNotification", self);
890 // TODO This might not be needed if we always get the NSNotification
891 // deleived even if we were launched due to a kvsStoreChange
892 // Send all keys for current lock state to securityd so it can proccess them
893 [self pendKeysAndGetNewlyPended: [self copyAllKeyInterests]];
894 [self processPendingKeysForCurrentLockState];
895 } else {
896 secdebug("event", "%@ ignored, waiting for NSNotification", self);
897 }
898 });
899 }
900
901 #pragma mark -
902 #pragma mark XPCNotificationListener
903
904 - (void)handleNotification:(const char *) name
905 {
906 // sync because we cannot ensure the lifetime of name
907 dispatch_sync(_ckdkvsproxy_queue, ^{
908 [self _queue_handleNotification:name];
909 });
910 }
911
912 #pragma mark -
913 #pragma mark Calls from -[CKDKVSStore kvsStoreChanged:]
914
915 - (void)storeKeysChanged: (NSSet<NSString*>*) changedKeys initial: (bool) initial
916 {
917 // sync, caller must wait to ensure correct state
918 dispatch_sync(_ckdkvsproxy_queue, ^{
919 [self _queue_storeKeysChanged:changedKeys initial:initial];
920 });
921 }
922
923 - (void)storeAccountChanged
924 {
925 // sync, caller must wait to ensure correct state
926 dispatch_sync(_ckdkvsproxy_queue, ^{
927 [self _queue_storeAccountChanged];
928 });
929 }
930
931 #pragma mark -
932 #pragma mark CKDLockListener
933
934 - (void) locked
935 {
936 // sync, otherwise tests fail
937 dispatch_sync(_ckdkvsproxy_queue, ^{
938 [self _queue_locked];
939 });
940 }
941
942 - (void) unlocked
943 {
944 // sync, otherwise tests fail
945 dispatch_sync(_ckdkvsproxy_queue, ^{
946 [self _queue_unlocked];
947 });
948 }
949
950 //
951 // MARK: ----- Key Filtering -----
952 //
953
954 - (NSSet*) keysForCurrentLockState
955 {
956 secdebug("filtering", "%@ Filtering: unlockedSinceBoot: %d\n unlocked: %d\n, keysOfInterest: <%@>", self, (int) [self.lockMonitor unlockedSinceBoot], (int) ![self.lockMonitor locked], [self.persistentData compactDescription]);
957
958 NSMutableSet *currentStateKeys = [NSMutableSet setWithSet: _alwaysKeys];
959 if ([self.lockMonitor unlockedSinceBoot])
960 [currentStateKeys unionSet: _firstUnlockKeys];
961
962 if (![self.lockMonitor locked])
963 [currentStateKeys unionSet: _unlockedKeys];
964
965 return currentStateKeys;
966 }
967
968
969 - (NSMutableSet*) pendKeysAndGetNewlyPended: (NSSet*) keysToPend
970 {
971 NSMutableSet *filteredKeysToPend = [self copyAllKeyInterests];
972 [filteredKeysToPend intersectSet: keysToPend];
973
974 NSMutableSet *newlyPendedKeys = [filteredKeysToPend mutableCopy];
975 [newlyPendedKeys minusSet: _pendingKeys];
976 if (_shadowPendingKeys) {
977 [newlyPendedKeys minusSet: _shadowPendingKeys];
978 }
979
980 if (_shadowPendingKeys) {
981 [_shadowPendingKeys unionSet:filteredKeysToPend];
982 }
983 else{
984 [_pendingKeys unionSet:filteredKeysToPend];
985 }
986
987 return newlyPendedKeys;
988 }
989
990 - (void) intersectWithCurrentLockState: (NSMutableSet*) set
991 {
992 [set intersectSet: [self keysForCurrentLockState]];
993 }
994
995 - (NSMutableSet*) pendingKeysForCurrentLockState
996 {
997 NSMutableSet * result = [_pendingKeys mutableCopy];
998 [self intersectWithCurrentLockState:result];
999 return result;
1000 }
1001
1002 - (NSMutableSet*) pendKeysAndGetPendingForCurrentLockState: (NSSet*) startingSet
1003 {
1004 [self pendKeysAndGetNewlyPended: startingSet];
1005
1006 return [self pendingKeysForCurrentLockState];
1007 }
1008
1009 - (NSMutableDictionary *)copyValues:(NSSet*)keysOfInterest
1010 {
1011 // Grab values from store.
1012 NSObject<CKDStore> *store = [self store];
1013 NSMutableDictionary *changedValues = [NSMutableDictionary dictionaryWithCapacity:0];
1014 [keysOfInterest enumerateObjectsUsingBlock:^(id obj, BOOL *stop)
1015 {
1016 NSString* key = (NSString*) obj;
1017 id objval = [store objectForKey:key];
1018 if (!objval) objval = [NSNull null];
1019
1020 [changedValues setObject:objval forKey:key];
1021 secdebug(XPROXYSCOPE, "%@ storeChanged updated value for %@", self, key);
1022 }];
1023 return changedValues;
1024 }
1025
1026 /*
1027 During RegisterKeys, separate keys-of-interest into three disjoint sets:
1028 - keys that we always want to be notified about; this means we can get the
1029 value at any time
1030 - keys that require the device to have been unlocked at least once
1031 - keys that require the device to be unlocked now
1032
1033 Typically, the sets of keys will be:
1034
1035 - Dk: alwaysKeys
1036 - Ck: firstUnlock
1037 - Ak: unlocked
1038
1039 The caller is responsible for making sure that the keys in e.g. alwaysKeys are
1040 values that can be handled at any time (that is, not when unlocked)
1041
1042 Each time we get a notification from ubiquity that keys have changed, we need to
1043 see if anything of interest changed. If we don't care, then done.
1044
1045 For each key-of-interest that changed, we either notify the client that things
1046 changed, or add it to a pendingNotifications list. If the notification to the
1047 client fails, also add it to the pendingNotifications list. This pending list
1048 should be written to persistent storage and consulted any time we either get an
1049 item changed notification, or get a stream event signalling a change in lock state.
1050
1051 We can notify the client either through XPC if a connection is set up, or call a
1052 routine in securityd to launch it.
1053
1054 */
1055
1056 - (void)processKeyChangedEvent:(NSDictionary *)changedValues
1057 {
1058 NSMutableDictionary* filtered = [NSMutableDictionary dictionary];
1059
1060 secnotice("processKeyChangedEvent", "changedValues:%@", changedValues);
1061 NSMutableArray* nullKeys = [NSMutableArray array];
1062 // Remove nulls because we don't want them in securityd.
1063 [changedValues enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
1064 if (obj == [NSNull null]){
1065 [nullKeys addObject:key];
1066 }else{
1067 filtered[key] = obj;
1068 }
1069 }];
1070 if ([nullKeys count])
1071 [_pendingKeys minusSet: [NSSet setWithArray: nullKeys]];
1072
1073 if([filtered count] != 0 ) {
1074 [self sendKeysCallout:^NSSet *(NSSet *pending, NSError** error) {
1075 secnotice("processing keys", "pending:%@", pending);
1076 NSError *updateError = nil;
1077 return [[self account] keysChanged: filtered error: &updateError];
1078 }];
1079 } else {
1080 secnoticeq("keytrace", "%@ null: %@ pending: %@", self,
1081 [nullKeys componentsJoinedByString: @" "],
1082 [[_pendingKeys allObjects] componentsJoinedByString: @" "]);
1083 }
1084 }
1085
1086 - (void) processPendingKeysForCurrentLockState
1087 {
1088 [self processKeyChangedEvent: [self copyValues: [self pendingKeysForCurrentLockState]]];
1089 }
1090
1091 @end
1092
1093