2 * Copyright (c) 2012-2014,2016 Apple Inc. All Rights Reserved.
4 * @APPLE_LICENSE_HEADER_START@
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
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.
21 * @APPLE_LICENSE_HEADER_END@
29 #import <Foundation/Foundation.h>
31 #import <utilities/debugging.h>
32 #import <os/activity.h>
34 #import "CKDKVSProxy.h"
35 #import "CKDKVSStore.h"
36 #import "CKDAKSLockMonitor.h"
37 #import "CKDSecuritydAccount.h"
38 #import "NSURL+SOSPlistStore.h"
40 #include <Security/SecureObjectSync/SOSARCDefines.h>
41 #include <utilities/SecCFWrappers.h>
42 #include <utilities/SecPLWrappers.h>
44 #include "SOSCloudKeychainConstants.h"
46 #include <utilities/SecAKSWrappers.h>
47 #include <utilities/SecADWrapper.h>
48 #include <utilities/SecNSAdditions.h>
49 #import "XPCNotificationDispatcher.h"
52 CFStringRef const CKDAggdIncreaseThrottlingKey = CFSTR("com.apple.cloudkeychainproxy.backoff.increase");
53 CFStringRef const CKDAggdDecreaseThrottlingKey = CFSTR("com.apple.cloudkeychainproxy.backoff.decrease");
55 @interface NSSet (CKDLogging)
56 - (NSString*) logKeys;
60 @implementation NSSet (CKDLogging)
61 - (NSString*) logKeys {
62 return [self sortedElementsJoinedByString:@" "];
65 - (NSString*) logIDs {
66 return [self sortedElementsTruncated:8 JoinedByString:@" "];
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.
80 static NSString *kKeyKeyParameterKeys = @"KeyParameterKeys";
81 static NSString *kKeyCircleKeys = @"CircleKeys";
82 static NSString *kKeyMessageKeys = @"MessageKeys";
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";
92 static NSString *kKeyPendingSyncPeerIDs = @"SyncPeerIDs";
93 static NSString *kKeyPendingSyncBackupPeerIDs = @"SyncBackupPeerIDs";
95 static NSString *kKeyEnsurePeerRegistration = @"EnsurePeerRegistration";
96 static NSString *kKeyDSID = @"DSID";
97 static NSString *kMonitorState = @"MonitorState";
98 static NSString *kKeyAccountUUID = @"MonitorState";
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";
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");
121 #define kSecServerKeychainChangedNotification "com.apple.security.keychainchanged"
123 @interface UbiqitousKVSProxy ()
124 @property (nonatomic) NSDictionary* persistentData;
125 - (void) doSyncWithAllPeers;
126 - (void) persistState;
129 @implementation UbiqitousKVSProxy
131 + (instancetype)withAccount:(NSObject<CKDAccount>*) account
132 store:(NSObject<CKDStore>*) store
133 lockMonitor:(NSObject<CKDLockMonitor>*) lockMonitor
134 persistence:(NSURL*) localPersistence
136 return [[self alloc] initWithAccount:account
138 lockMonitor:lockMonitor
139 persistence:localPersistence];
142 - (instancetype)initWithAccount:(NSObject<CKDAccount>*) account
143 store:(NSObject<CKDStore>*) store
144 lockMonitor:(NSObject<CKDLockMonitor>*) lockMonitor
145 persistence:(NSURL*) localPersistence
147 if (self = [super init])
149 secnotice("event", "%@ start", self);
151 #if !(TARGET_OS_EMBEDDED)
152 // rdar://problem/26247270
153 if (geteuid() == 0) {
154 secerror("Cannot run CloudKeychainProxy as root");
158 _ensurePeerRegistration = NO;
160 _pendingSyncPeerIDs = [NSMutableSet set];
161 _pendingSyncBackupPeerIDs = [NSMutableSet set];
162 _shadowPendingSyncPeerIDs = nil;
163 _shadowPendingSyncBackupPeerIDs = nil;
165 _persistenceURL = localPersistence;
169 _lockMonitor = lockMonitor;
172 _calloutQueue = dispatch_queue_create("CKDCallout", DISPATCH_QUEUE_SERIAL);
173 _ckdkvsproxy_queue = dispatch_queue_create("CKDKVSProxy", DISPATCH_QUEUE_SERIAL);
175 _freshnessCompletions = [NSMutableArray<FreshnessResponseBlock> array];
177 _monitor = [NSMutableDictionary dictionary];
179 [[XPCNotificationDispatcher dispatcher] addListener: self];
181 int notificationToken;
182 notify_register_dispatch(kSecServerKeychainChangedNotification, ¬ificationToken, _ckdkvsproxy_queue,
183 ^ (int token __unused)
185 secinfo("backoff", "keychain changed, wiping backoff monitor state");
186 self->_monitor = [NSMutableDictionary dictionary];
189 [self setPersistentData: [self.persistenceURL readPlist]];
194 [[self store] connectToProxy: self];
195 [[self lockMonitor] connectTo:self];
197 secdebug(XPROXYSCOPE, "%@ done", self);
202 - (NSString *)description
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" : "-"];
218 // MARK: XPC Function commands
220 - (void) clearStore {
221 [self.store removeAllObjects];
224 - (void)synchronizeStore {
225 [self.store pushWrites];
228 - (id) objectForKey: (NSString*) key {
229 return [self.store objectForKey: key];
231 - (NSDictionary<NSString *, id>*) copyAsDictionary {
232 return [self.store copyAsDictionary];
235 - (void)_queue_processAllItems
237 dispatch_assert_queue(_ckdkvsproxy_queue);
239 NSDictionary *allItems = [self.store copyAsDictionary];
242 secnotice("event", "%@ sending: %@", self, [[allItems allKeys] componentsJoinedByString: @" "]);
243 [self processKeyChangedEvent:allItems];
246 secdebug(XPROXYSCOPE, "%@ No items in KVS", self);
251 secdebug(XPROXYSCOPE, "%@", self);
252 [[NSNotificationCenter defaultCenter] removeObserver:self
253 name:NSUbiquitousKeyValueStoreDidChangeExternallyNotification
256 [[NSNotificationCenter defaultCenter] removeObserver:self
257 name:NSUbiquityIdentityDidChangeNotification
263 - (NSDictionary*) persistentData
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],
274 kKeyAccountUUID:_accountUUID
278 - (void) setPersistentData: (NSDictionary*) interests
280 _alwaysKeys = [NSMutableSet setWithArray: interests[kKeyAlwaysKeys]];
281 _firstUnlockKeys = [NSMutableSet setWithArray: interests[kKeyFirstUnlockKeys]];
282 _unlockedKeys = [NSMutableSet setWithArray: interests[kKeyUnlockedKeys]];
284 _pendingKeys = [NSMutableSet setWithArray: interests[kKeyPendingKeys]];
286 _pendingSyncPeerIDs = [NSMutableSet setWithArray: interests[kKeyPendingSyncPeerIDs]];
287 _pendingSyncBackupPeerIDs = [NSMutableSet setWithArray: interests[kKeyPendingSyncBackupPeerIDs]];
289 _monitor = interests[kMonitorState];
291 _monitor = [NSMutableDictionary dictionary];
293 _ensurePeerRegistration = [interests[kKeyEnsurePeerRegistration] boolValue];
295 _dsid = interests[kKeyDSID];
296 _accountUUID = interests[kKeyAccountUUID];
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];
306 NSDictionary* dataToSave = self.persistentData;
308 secdebug("persistence", "Writing registeredKeys: %@", [dataToSave compactDescription]);
309 if (![self.persistenceURL writePlist:dataToSave]) {
310 secerror("Failed to write persistence data to %@", self.persistenceURL);
314 - (void)perfCounters:(void(^)(NSDictionary *counters))callback
316 /* Collect and merge perf counters from other layers here too */
317 [self.store perfCounters:callback];
321 // MARK: Object setting
324 - (void)setStoreObjectsFromDictionary:(NSDictionary *)values
327 secdebug(XPROXYSCOPE, "%@ NULL? values: %@", self, values);
331 NSMutableDictionary<NSString*, NSObject*> *mutableValues = [values mutableCopy];
332 NSString* newDSID = asNSString([mutableValues extractObjectForKey:(__bridge NSString*) kSOSKVSOfficialDSIDKey]);
337 NSString* requiredDSID = asNSString([mutableValues extractObjectForKey:(__bridge NSString*) kSOSKVSRequiredKey]);
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);
347 secnoticeq("dsid", "DSIDs match, writing");
351 secnoticeq("keytrace", "%@ sending: %@", self, [[mutableValues allKeys] componentsJoinedByString: @" "]);
352 [mutableValues enumerateKeysAndObjectsUsingBlock: ^(id key, id obj, BOOL *stop)
354 if (obj == NULL || obj == [NSNull null]) {
355 [self.store removeObjectForKey:key];
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];
365 [[self store] addOneToOutGoing];
366 [self.store setObject:obj forKey:key];
370 [self.store pushWrites];
373 - (void)setObjectsFromDictionary:(NSDictionary<NSString*, NSObject*> *)values
375 [self setStoreObjectsFromDictionary:values];
378 - (void)waitForSynchronization:(void (^)(NSDictionary<NSString*, NSObject*> *results, NSError *err))handler
380 secnoticeq("fresh", "%s Requesting WFS", kWAIT2MINID);
382 [_freshnessCompletions addObject: ^(bool success, NSError *error){
383 secnoticeq("fresh", "%s WFS Done", kWAIT2MINID);
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];
394 dispatch_async(self->_ckdkvsproxy_queue, ^{
395 [self waitForSyncDone: success error: error];
401 - (void) waitForSyncDone: (bool) success error: (NSError*) error{
403 const uint64_t delayBeforeCallingAgainInSeconds = 5ull * NSEC_PER_SEC;
404 _nextFreshnessTime = dispatch_time(DISPATCH_TIME_NOW, delayBeforeCallingAgainInSeconds);
407 secnoticeq("fresh", "%s Completing WFS", kWAIT2MINID);
408 [_freshnessCompletions enumerateObjectsUsingBlock:^(FreshnessResponseBlock _Nonnull block,
410 BOOL * _Nonnull stop) {
411 block(success, error);
413 [_freshnessCompletions removeAllObjects];
418 // MARK: ----- KVS key lists -----
421 - (NSMutableSet *)copyAllKeyInterests
423 NSMutableSet *allKeys = [NSMutableSet setWithSet: _alwaysKeys];
424 [allKeys unionSet: _firstUnlockKeys];
425 [allKeys unionSet: _unlockedKeys];
429 -(void)registerAtTimeKeys:(NSDictionary*)keyparms
434 NSArray *alwaysArray = [keyparms valueForKey: kKeyAlwaysKeys];
435 NSArray *firstUnlockedKeysArray = [keyparms valueForKey: kKeyFirstUnlockKeys];
436 NSArray *whenUnlockedKeysArray = [keyparms valueForKey: kKeyUnlockedKeys];
439 [_alwaysKeys unionSet: [NSMutableSet setWithArray: alwaysArray]];
440 if(firstUnlockedKeysArray)
441 [_firstUnlockKeys unionSet: [NSMutableSet setWithArray: firstUnlockedKeysArray]];
442 if(whenUnlockedKeysArray)
443 [_unlockedKeys unionSet: [NSMutableSet setWithArray: whenUnlockedKeysArray]];
446 - (void)removeKeys: (NSArray*)keys forAccount: (NSString*) accountUUID
448 secdebug(XPROXYSCOPE, "removeKeys: keys: %@", keys);
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];
454 secnotice(XPROXYSCOPE, "not removing keys, account UUID is for a new account");
457 [keys enumerateObjectsUsingBlock:^(NSString* _Nonnull key, NSUInteger idx, BOOL * _Nonnull stop) {
458 secnotice(XPROXYSCOPE, "removing from KVS store: %@", key);
459 [self.store removeObjectForKey:key];
463 - (void)registerKeys: (NSDictionary*)keys forAccount: (NSString*) accountUUID
465 secdebug(XPROXYSCOPE, "registerKeys: keys: %@", keys);
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];
471 self.accountUUID = accountUUID;
474 // If we're a new account we don't exclude the old keys
475 NSMutableSet *allOldKeys = newAccount ? [NSMutableSet set] : [self copyAllKeyInterests];
478 NSDictionary *keyparms = [keys valueForKey: [NSString stringWithUTF8String: kMessageKeyParameter]];
479 NSDictionary *circles = [keys valueForKey: [NSString stringWithUTF8String: kMessageCircle]];
480 NSDictionary *messages = [keys valueForKey: [NSString stringWithUTF8String: kMessageMessage]];
482 _alwaysKeys = [NSMutableSet set];
483 _firstUnlockKeys = [NSMutableSet set];
484 _unlockedKeys = [NSMutableSet set];
486 [self registerAtTimeKeys: keyparms];
487 [self registerAtTimeKeys: circles];
488 [self registerAtTimeKeys: messages];
490 NSMutableSet *allNewKeys = [self copyAllKeyInterests];
492 // Make sure keys we no longer care about are not pending
493 [_pendingKeys intersectSet:allNewKeys];
494 if (_shadowPendingKeys) {
495 [_shadowPendingKeys intersectSet:allNewKeys];
498 // All new keys only is new keys (remove old keys)
499 [allNewKeys minusSet:allOldKeys];
501 // Mark new keys pending, they're new!
502 NSMutableSet *newKeysForCurrentLockState = [self pendKeysAndGetNewlyPended:allNewKeys];
504 [self persistState]; // Before we might call out, save our state so we recover if we crash
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];
513 // MARK: ----- Event Handling -----
515 - (void)_queue_handleNotification:(const char *) name
517 dispatch_assert_queue(_ckdkvsproxy_queue);
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];
528 - (void)_queue_storeKeysChanged: (NSSet<NSString*>*) changedKeys initial: (bool) initial
530 dispatch_assert_queue(_ckdkvsproxy_queue);
532 // Mark that our store is talking to us, so we don't have to make up for missing anything previous.
533 _seenKVSStoreChange = YES;
535 // Unmark them as pending as they have just changed and we'll process them.
536 [_pendingKeys minusSet:changedKeys];
538 // Only send values that we're currently interested in.
539 NSSet *keysOfInterestThatChanged = [self pendKeysAndGetPendingForCurrentLockState:changedKeys];
540 NSMutableDictionary *changedValues = [self copyValues:keysOfInterestThatChanged];
542 changedValues[(__bridge NSString*)kSOSKVSInitialSyncKey] = @"true";
544 secnotice("event", "%@ keysChangedInCloud: %@ keysOfInterest: %@ initial: %@",
546 [[changedKeys allObjects] componentsJoinedByString: @" "],
547 [[changedValues allKeys] componentsJoinedByString: @" "],
548 initial ? @"YES" : @"NO");
550 if ([changedValues count])
551 [self processKeyChangedEvent:changedValues];
554 - (void)_queue_storeAccountChanged
556 dispatch_assert_queue(_ckdkvsproxy_queue);
558 secnotice("event", "%@", self);
560 NSDictionary *changedValues = nil;
562 changedValues = @{ (__bridge NSString*)kSOSKVSAccountChangedKey: _dsid };
564 changedValues = @{ (__bridge NSString*)kSOSKVSAccountChangedKey: @"true" };
566 [self processKeyChangedEvent:changedValues];
569 - (void) doAfterFlush: (dispatch_block_t) block
571 //Flush any pending communication to Securityd.
573 dispatch_async(_calloutQueue, block);
575 _shadowFlushBlock = block;
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
580 // In CKDKVSProxy's serial queue
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];
596 myEnsurePeerRegistration = self->_ensurePeerRegistration;
597 wasLocked = [self.lockMonitor locked];
599 self->_inCallout = YES;
601 self->_shadowPendingKeys = [NSMutableSet set];
602 self->_shadowPendingSyncPeerIDs = [NSMutableSet set];
603 self->_shadowPendingSyncBackupPeerIDs = [NSMutableSet set];
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");
613 // In CKDKVSProxy's serial queue
614 self->_inCallout = NO;
616 // Update ensurePeerRegistration
617 self->_ensurePeerRegistration = ((self->_ensurePeerRegistration && !handledEnsurePeerRegistration) || self->_shadowEnsurePeerRegistration);
619 self->_shadowEnsurePeerRegistration = NO;
621 if(self->_ensurePeerRegistration && ![self.lockMonitor locked])
622 [self doEnsurePeerRegistration];
624 bool hadShadowPeerIDs = ![self->_shadowPendingSyncPeerIDs isEmpty] || ![self->_shadowPendingSyncBackupPeerIDs isEmpty];
626 // Update SyncWithPeers stuff.
628 [self->_pendingSyncPeerIDs minusSet: handledSyncs];
629 [self->_pendingSyncBackupPeerIDs minusSet: handledSyncs];
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]);
636 if (hadShadowPeerIDs) {
637 secnotice("sync-ids", "signaled peerIDs: %@", [self->_shadowPendingSyncPeerIDs logIDs]);
638 secnotice("sync-ids", "signaled backupIDs: %@", [self->_shadowPendingSyncBackupPeerIDs logIDs]);
642 self->_shadowPendingSyncPeerIDs = nil;
643 self->_shadowPendingSyncBackupPeerIDs = nil;
647 // Update pendingKeys and handle them
648 [self->_pendingKeys removeObject: [NSNull null]]; // Don't let NULL hang around
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;
657 NSSet *filteredKeys = [self pendKeysAndGetPendingForCurrentLockState:oldShadowPendingKeys];
659 secnoticeq("keytrace", "%@ account handled: %@ pending: %@", self,
660 [[handledKeys allObjects] componentsJoinedByString: @" "],
661 [[filteredKeys allObjects] componentsJoinedByString: @" "]);
663 // Write state to disk
666 // Handle shadow pended stuff
668 // We only kick off another sync if we got new stuff during handling
669 if (hadShadowPeerIDs && ![self.lockMonitor locked])
670 [self newPeersToSyncWith];
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");
685 if(self->_shadowFlushBlock != NULL){
686 dispatch_async(self->_calloutQueue, self->_shadowFlushBlock);
687 self->_shadowFlushBlock = NULL;
691 [self.lockMonitor recheck];
694 xpc_transaction_end();
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;
703 secnotice("CloudKeychainProxy", "send keys: %@", pending);
704 NSSet * handled = handleKeys(pending, &error);
706 dispatch_async(queue, ^{
708 secerror("%@ ensurePeerRegistration failed: %@", self, error);
711 done(handled, nil, NO, error);
716 - (void) doEnsurePeerRegistration
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.
728 dispatch_async(queue, ^{
729 done(nil, nil, handledEnsurePeerRegistration, error);
734 - (void) doSyncWithPendingPeers
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, ^{
745 // We might be confused about lock state
746 [self.lockMonitor recheck];
749 done(nil, handled, false, error);
754 - (void) doSyncWithAllPeers
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];
761 secerror("Failed to syncWithAllPeers: %@", error);
763 dispatch_async(queue, ^{
764 done(nil, nil, false, error);
769 - (void)newPeersToSyncWith
771 secnotice("event", "%@ syncWithPeersPending: %d inCallout: %d isLocked: %d", self, [self hasPendingSyncIDs], _inCallout, [self.lockMonitor locked]);
772 if(_ensurePeerRegistration){
773 [self doEnsurePeerRegistration];
775 if ([self hasPendingSyncIDs] && !_inCallout && ![self.lockMonitor locked]){
776 [self doSyncWithPendingPeers];
780 - (bool)hasPendingNonShadowSyncIDs {
781 return ![_pendingSyncPeerIDs isEmpty] || ![_pendingSyncBackupPeerIDs isEmpty];
784 - (bool)hasPendingShadowSyncIDs {
785 return (_shadowPendingSyncPeerIDs && ![_shadowPendingSyncPeerIDs isEmpty]) ||
786 (_shadowPendingSyncBackupPeerIDs && ![_shadowPendingSyncBackupPeerIDs isEmpty]);
789 - (bool)hasPendingSyncIDs
791 bool pendingIDs = [self hasPendingNonShadowSyncIDs];
794 pendingIDs |= [self hasPendingShadowSyncIDs];
800 - (void)requestSyncWithPeerIDs: (NSArray<NSString*>*) peerIDs backupPeerIDs: (NSArray<NSString*>*) backupPeerIDs
802 if ([peerIDs count] == 0 && [backupPeerIDs count] == 0)
803 return; // Nothing to do;
805 NSSet<NSString*>* peerIDsSet = [NSSet setWithArray: peerIDs];
806 NSSet<NSString*>* backupPeerIDsSet = [NSSet setWithArray: backupPeerIDs];
808 [_pendingSyncPeerIDs unionSet: peerIDsSet];
809 [_pendingSyncBackupPeerIDs unionSet: backupPeerIDsSet];
812 [_shadowPendingSyncPeerIDs unionSet: peerIDsSet];
813 [_shadowPendingSyncBackupPeerIDs unionSet: backupPeerIDsSet];
818 if(_ensurePeerRegistration){
819 [self doEnsurePeerRegistration];
821 if ([self hasPendingSyncIDs] && !_inCallout && ![self.lockMonitor locked]){
822 [self doSyncWithPendingPeers];
826 - (BOOL)hasSyncPendingFor: (NSString*) peerID {
827 return [_pendingSyncPeerIDs containsObject: peerID] ||
828 (_shadowPendingSyncPeerIDs && [_shadowPendingSyncPeerIDs containsObject: peerID]);
831 - (BOOL)hasPendingKey: (NSString*) keyName {
832 return [self.pendingKeys containsObject: keyName]
833 || (_shadowPendingKeys && [self.shadowPendingKeys containsObject: keyName]);
836 - (void)requestEnsurePeerRegistration
839 NSString *desc = [self description];
843 _shadowEnsurePeerRegistration = YES;
845 _ensurePeerRegistration = YES;
846 if (![self.lockMonitor locked]){
847 [self doEnsurePeerRegistration];
852 secdebug("event", "%@ %@", desc, self);
855 - (void)_queue_locked
857 dispatch_assert_queue(_ckdkvsproxy_queue);
859 secnotice("event", "%@ Locked", self);
862 - (void)_queue_unlocked
864 dispatch_assert_queue(_ckdkvsproxy_queue);
866 secnotice("event", "%@ Unlocked", self);
867 if (_ensurePeerRegistration) {
868 [self doEnsurePeerRegistration];
871 // First send changed keys to securityd so it can proccess updates
872 [self processPendingKeysForCurrentLockState];
874 // Then, tickle securityd to perform a sync if needed.
875 if ([self hasPendingSyncIDs]) {
876 [self doSyncWithPendingPeers];
880 - (void) _queue_kvsStoreChange {
881 dispatch_assert_queue(_ckdkvsproxy_queue);
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];
892 secdebug("event", "%@ ignored, waiting for NSNotification", self);
898 #pragma mark XPCNotificationListener
900 - (void)handleNotification:(const char *) name
902 // sync because we cannot ensure the lifetime of name
903 dispatch_sync(_ckdkvsproxy_queue, ^{
904 [self _queue_handleNotification:name];
909 #pragma mark Calls from -[CKDKVSStore kvsStoreChanged:]
911 - (void)storeKeysChanged: (NSSet<NSString*>*) changedKeys initial: (bool) initial
913 // sync, caller must wait to ensure correct state
914 dispatch_sync(_ckdkvsproxy_queue, ^{
915 [self _queue_storeKeysChanged:changedKeys initial:initial];
919 - (void)storeAccountChanged
921 // sync, caller must wait to ensure correct state
922 dispatch_sync(_ckdkvsproxy_queue, ^{
923 [self _queue_storeAccountChanged];
928 #pragma mark CKDLockListener
932 // sync, otherwise tests fail
933 dispatch_sync(_ckdkvsproxy_queue, ^{
934 [self _queue_locked];
940 // sync, otherwise tests fail
941 dispatch_sync(_ckdkvsproxy_queue, ^{
942 [self _queue_unlocked];
947 // MARK: ----- Key Filtering -----
950 - (NSSet*) keysForCurrentLockState
952 secdebug("filtering", "%@ Filtering: unlockedSinceBoot: %d\n unlocked: %d\n, keysOfInterest: <%@>", self, (int) [self.lockMonitor unlockedSinceBoot], (int) ![self.lockMonitor locked], [self.persistentData compactDescription]);
954 NSMutableSet *currentStateKeys = [NSMutableSet setWithSet: _alwaysKeys];
955 if ([self.lockMonitor unlockedSinceBoot])
956 [currentStateKeys unionSet: _firstUnlockKeys];
958 if (![self.lockMonitor locked])
959 [currentStateKeys unionSet: _unlockedKeys];
961 return currentStateKeys;
965 - (NSMutableSet*) pendKeysAndGetNewlyPended: (NSSet*) keysToPend
967 NSMutableSet *filteredKeysToPend = [self copyAllKeyInterests];
968 [filteredKeysToPend intersectSet: keysToPend];
970 NSMutableSet *newlyPendedKeys = [filteredKeysToPend mutableCopy];
971 [newlyPendedKeys minusSet: _pendingKeys];
972 if (_shadowPendingKeys) {
973 [newlyPendedKeys minusSet: _shadowPendingKeys];
976 if (_shadowPendingKeys) {
977 [_shadowPendingKeys unionSet:filteredKeysToPend];
980 [_pendingKeys unionSet:filteredKeysToPend];
983 return newlyPendedKeys;
986 - (void) intersectWithCurrentLockState: (NSMutableSet*) set
988 [set intersectSet: [self keysForCurrentLockState]];
991 - (NSMutableSet*) pendingKeysForCurrentLockState
993 NSMutableSet * result = [_pendingKeys mutableCopy];
994 [self intersectWithCurrentLockState:result];
998 - (NSMutableSet*) pendKeysAndGetPendingForCurrentLockState: (NSSet*) startingSet
1000 [self pendKeysAndGetNewlyPended: startingSet];
1002 return [self pendingKeysForCurrentLockState];
1005 - (NSMutableDictionary *)copyValues:(NSSet*)keysOfInterest
1007 // Grab values from store.
1008 NSObject<CKDStore> *store = [self store];
1009 NSMutableDictionary *changedValues = [NSMutableDictionary dictionaryWithCapacity:0];
1010 [keysOfInterest enumerateObjectsUsingBlock:^(id obj, BOOL *stop)
1012 NSString* key = (NSString*) obj;
1013 id objval = [store objectForKey:key];
1014 if (!objval) objval = [NSNull null];
1016 [changedValues setObject:objval forKey:key];
1017 secdebug(XPROXYSCOPE, "%@ storeChanged updated value for %@", self, key);
1019 return changedValues;
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
1026 - keys that require the device to have been unlocked at least once
1027 - keys that require the device to be unlocked now
1029 Typically, the sets of keys will be:
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)
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.
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.
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.
1052 - (void)processKeyChangedEvent:(NSDictionary *)changedValues
1054 NSMutableDictionary* filtered = [NSMutableDictionary dictionary];
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];
1063 filtered[key] = obj;
1066 if ([nullKeys count])
1067 [_pendingKeys minusSet: [NSSet setWithArray: nullKeys]];
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];
1076 secnoticeq("keytrace", "%@ null: %@ pending: %@", self,
1077 [nullKeys componentsJoinedByString: @" "],
1078 [[_pendingKeys allObjects] componentsJoinedByString: @" "]);
1082 - (void) processPendingKeysForCurrentLockState
1084 [self processKeyChangedEvent: [self copyValues: [self pendingKeysForCurrentLockState]]];