2 * Copyright (c) 2012-2014 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/NSUbiquitousKeyValueStore.h>
30 #import <Foundation/NSUbiquitousKeyValueStore_Private.h>
31 #import <Foundation/NSArray.h>
32 #import <Foundation/Foundation.h>
34 #import <Security/SecBasePriv.h>
35 #import <Security/SecItemPriv.h>
36 #import <utilities/debugging.h>
39 #import "CKDKVSProxy.h"
40 #import "CKDPersistentState.h"
41 #import "CKDUserInteraction.h"
43 #import "SOSARCDefines.h"
45 #include <SecureObjectSync/SOSAccount.h>
46 #include <SecureObjectSync/SOSCloudCircleInternal.h>
47 #include <SecureObjectSync/SOSKVSKeys.h>
49 #include "SOSCloudKeychainConstants.h"
51 #include <utilities/SecAKSWrappers.h>
52 #include <utilities/SecCFRelease.h>
55 The total space available in your app’s iCloud key-value storage is 1 MB.
56 The maximum number of keys you can specify is 1024, and the size limit for
57 each value associated with a key is 1 MB. So, for example, if you store a
58 single large value of 1 MB for a single key, that consumes your total
59 available storage. If you store 1 KB of data for each key, you can use
60 1,000 key-value pairs.
63 static const char *kStreamName = "com.apple.notifyd.matching";
65 static NSString *kKeyKeyParameterKeys = @"KeyParameterKeys";
66 static NSString *kKeyCircleKeys = @"CircleKeys";
67 static NSString *kKeyMessageKeys = @"MessageKeys";
70 static NSString *kKeyAlwaysKeys = @"AlwaysKeys";
71 static NSString *kKeyFirstUnlockKeys = @"FirstUnlockKeys";
72 static NSString *kKeyUnlockedKeys = @"UnlockedKeys";
73 static NSString *kKeyPendingKeys = @"PendingKeys";
74 static NSString *kKeyUnsentChangedKeys = @"unsentChangedKeys";
75 static NSString *kKeyUnlockNotificationRequested = @"unlockNotificationRequested";
76 static NSString *kKeySyncWithPeersPending = @"SyncWithPeersPending";
77 static NSString *kKeyEnsurePeerRegistration = @"EnsurePeerRegistration";
81 kCallbackMethodSecurityd = 0,
82 kCallbackMethodXPC = 1,
85 static const int64_t kMinSyncDelay = (NSEC_PER_MSEC * 500); // 500ms minimum delay before a syncWithAllPeers call.
86 static const int64_t kMaxSyncDelay = (NSEC_PER_SEC * 5); // 5s maximun delay for a given request
87 static const int64_t kMinSyncInterval = (NSEC_PER_SEC * 15); // 15s minimum time between successive syncWithAllPeers calls.
88 static const int64_t kSyncTimerLeeway = (NSEC_PER_MSEC * 250); // 250ms leeway for sync events.
90 @interface NSUbiquitousKeyValueStore (NSUbiquitousKeyValueStore_PrivateZ)
91 - (void) _synchronizeWithCompletionHandler:(void (^)(NSError *error))completionHandler;
95 - (void) synchronizeWithCompletionHandler:(void (^)(NSError *error))completionHandler;
100 @implementation UbiqitousKVSProxy
105 [SOSPersistentState setRegisteredKeys:[self exportKeyInterests]];
108 + (UbiqitousKVSProxy *) sharedKVSProxy
110 static UbiqitousKVSProxy *sharedKVSProxy;
111 if (!sharedKVSProxy) {
112 static dispatch_once_t onceToken;
113 dispatch_once(&onceToken, ^{
114 sharedKVSProxy = [[self alloc] init];
117 return sharedKVSProxy;
122 if (self = [super init])
124 secnotice("event", "%@ start", self);
126 _calloutQueue = dispatch_queue_create("CKDCallout", DISPATCH_QUEUE_SERIAL);
127 _freshParamsQueue = dispatch_queue_create("CKDFresh", DISPATCH_QUEUE_SERIAL);
128 _syncTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue());
129 dispatch_source_set_timer(_syncTimer, DISPATCH_TIME_FOREVER, DISPATCH_TIME_FOREVER, kSyncTimerLeeway);
130 dispatch_source_set_event_handler(_syncTimer, ^{
133 dispatch_resume(_syncTimer);
135 [[NSNotificationCenter defaultCenter]
137 selector: @selector (iCloudAccountAvailabilityChanged:)
138 name: NSUbiquityIdentityDidChangeNotification
141 [[NSNotificationCenter defaultCenter] addObserver:self
142 selector:@selector(cloudChanged:)
143 name:NSUbiquitousKeyValueStoreDidChangeExternallyNotification
146 [self importKeyInterests: [SOSPersistentState registeredKeys]];
148 // Register for lock state changes
149 xpc_set_event_stream_handler(kStreamName, dispatch_get_main_queue(),
150 ^(xpc_object_t notification){
151 [self streamEvent:notification];
154 [self updateUnlockedSinceBoot];
155 [self updateIsLocked];
157 [self keybagDidUnlock];
159 secdebug(XPROXYSCOPE, "%@ done", self);
164 - (NSString *)description
166 return [NSString stringWithFormat:@"<%s%s%s%s%s%s%s%s%s%s%s>",
167 _isLocked ? "L" : "U",
168 _unlockedSinceBoot ? "B" : "-",
169 _seenKVSStoreChange ? "K" : "-",
170 _syncTimerScheduled ? "T" : "-",
171 _syncWithPeersPending ? "s" : "-",
172 _ensurePeerRegistration ? "e" : "-",
173 [_pendingKeys count] ? "p" : "-",
174 _inCallout ? "C" : "-",
175 _shadowSyncWithPeersPending ? "S" : "-",
176 _shadowEnsurePeerRegistration ? "E" : "-",
177 [_shadowPendingKeys count] ? "P" : "-"];
180 - (void)processAllItems
182 NSDictionary *allItems = [self getAll];
185 secnotice("event", "%@ sending: %@", self, [[allItems allKeys] componentsJoinedByString: @" "]);
186 [self processKeyChangedEvent:allItems];
189 secdebug(XPROXYSCOPE, "%@ No items in KVS", self);
192 - (void)setItemsChangedBlock:(CloudItemsChangedBlock)itemsChangedBlock
194 self->itemsChangedCallback = itemsChangedBlock;
199 secdebug(XPROXYSCOPE, "%@", self);
200 [[NSNotificationCenter defaultCenter] removeObserver:self
201 name:NSUbiquitousKeyValueStoreDidChangeExternallyNotification object:nil];
203 [[NSNotificationCenter defaultCenter] removeObserver:self
204 name:NSUbiquityIdentityDidChangeNotification object:nil];
207 // MARK: ----- Client Interface -----
209 - (void)setObject:(id)obj forKey:(id)key
211 secdebug("keytrace", "%@ key: %@, obj: %@", self, key, obj);
212 NSUbiquitousKeyValueStore *store = [self cloudStore];
215 id value = [store objectForKey:key];
217 secdebug("keytrace", "%@ Current value (before set) for key %@ is: %@", self, key, value);
219 secdebug("keytrace", "%@ No current value for key %@", self, key);
222 secdebug("keytrace", "Can't get store");
224 [[self cloudStore] setObject:obj forKey:key];
225 [self requestSynchronization:NO];
228 - (void)setObjectsFromDictionary:(NSDictionary *)values
230 secdebug(XPROXYSCOPE, "%@ start: %lu values to put", self, (unsigned long)[values count]);
232 NSUbiquitousKeyValueStore *store = [self cloudStore];
235 [values enumerateKeysAndObjectsUsingBlock: ^(id key, id obj, BOOL *stop)
237 if (obj == NULL || obj == [NSNull null])
238 [store removeObjectForKey:key];
240 [store setObject:obj forKey:key];
243 [self requestSynchronization:NO];
246 secdebug(XPROXYSCOPE, "%@ NULL? store: %@, values: %@", self, store, values);
249 - (void)requestSynchronization:(bool)force
253 secdebug(XPROXYSCOPE, "%@ synchronize (forced)", self);
254 [[self cloudStore] synchronize];
258 secdebug(XPROXYSCOPE, "%@ synchronize (soon)", self);
259 [[self cloudStore] synchronize];
263 - (NSUbiquitousKeyValueStore *)cloudStore
265 NSUbiquitousKeyValueStore *iCloudStore = [NSUbiquitousKeyValueStore defaultStore];
267 secerror("%s %@ NO NSUbiquitousKeyValueStore defaultStore", kWAIT2MINID, self);
273 Only call out to syncdefaultsd once every 5 seconds, since parameters can't change that
274 fast and callers expect synchonicity.
276 Since we don't actually get the values for the keys, just store off a timestamp.
279 // try to synchronize asap, and invoke the handler on completion to take incoming changes.
282 - (void)waitForSynchronization:(NSArray *)keys handler:(void (^)(NSDictionary *values, NSError *err))handler
286 NSError *err = [NSError errorWithDomain:(NSString *)NSPOSIXErrorDomain code:(NSInteger)ENOPROTOOPT userInfo:nil];
287 secerrorq("%s RETURNING TO securityd: %@ param error; calling handler", kWAIT2MINID, self);
292 NSUbiquitousKeyValueStore * store = [self cloudStore];
294 secnoticeq("fresh", "%s Requesting freshness", kWAIT2MINID);
295 dispatch_async(_freshParamsQueue, ^{
297 __block NSError *failure = NULL;
298 __block dispatch_time_t next_time = _nextFreshnessTime;
299 dispatch_time_t now = dispatch_time(DISPATCH_TIME_NOW, 0);
300 if (now > next_time) {
301 dispatch_semaphore_t freshSemaphore = dispatch_semaphore_create(0);
302 secnoticeq("fresh", "%s CALLING OUT TO syncdefaultsd SWCH: %@", kWAIT2MINID, self);
303 [store synchronizeWithCompletionHandler:^(NSError *error) {
306 secerrorq("%s RETURNING FROM syncdefaultsd SWCH: %@: %@", kWAIT2MINID, self, error);
308 secnoticeq("fresh", "%s RETURNING FROM syncdefaultsd SWCH: %@", kWAIT2MINID, self);
310 [store synchronize]; // Per olivier in <rdar://problem/13412631>, sync before getting values
311 secnoticeq("fresh", "%s RETURNING FROM syncdefaultsd SYNC: %@", kWAIT2MINID, self);
313 // This hysteresis balances between failing to notice parameters from remote devices and
314 // pestering synceddefaultsd with clients many repeated requests to do freshness.
315 const uint64_t delayBeforeCallingAgainInSeconds = 5ull * NSEC_PER_SEC;
316 next_time = dispatch_time(DISPATCH_TIME_NOW, delayBeforeCallingAgainInSeconds);
318 dispatch_semaphore_signal(freshSemaphore);
320 dispatch_semaphore_wait(freshSemaphore, DISPATCH_TIME_FOREVER);
323 dispatch_async(dispatch_get_main_queue(), ^{
324 _nextFreshnessTime = next_time;
327 handler(@{}, failure);
329 handler([self copyValues:[NSSet setWithArray:keys]], NULL);
335 - (void)removeObjectForKey:(NSString *)keyToRemove
337 [[self cloudStore] removeObjectForKey:keyToRemove];
342 secdebug(XPROXYSCOPE, "%@ clearStore", self);
343 NSDictionary *dict = [[self cloudStore] dictionaryRepresentation];
344 NSArray *allKeys = [dict allKeys];
345 NSMutableArray* nullKeys = [NSMutableArray array];
347 _alwaysKeys = [NSMutableSet setWithArray:nullKeys];
348 _firstUnlockKeys = [NSMutableSet setWithArray:nullKeys];
349 _unlockedKeys = [NSMutableSet setWithArray:nullKeys];
350 _keyParameterKeys = @{};
354 [allKeys enumerateObjectsUsingBlock:^(id key, NSUInteger idx, BOOL *stop)
356 secdebug(XPROXYSCOPE, "%@ Clearing value for key %@", self, key);
357 [[self cloudStore] removeObjectForKey:(NSString *)key];
360 [self requestSynchronization:YES];
365 // MARK: ----- KVS key lists -----
370 return [[self cloudStore] objectForKey:key];
373 - (NSDictionary *)getAll
375 return [[self cloudStore] dictionaryRepresentation];
378 - (NSDictionary*) exportKeyInterests
380 return @{ kKeyAlwaysKeys:[_alwaysKeys allObjects],
381 kKeyFirstUnlockKeys:[_firstUnlockKeys allObjects],
382 kKeyUnlockedKeys:[_unlockedKeys allObjects],
385 kKeyKeyParameterKeys: _keyParameterKeys,
386 kKeyMessageKeys : _messageKeys,
387 kKeyCircleKeys : _circleKeys,
389 kKeyPendingKeys:[_pendingKeys allObjects],
390 kKeySyncWithPeersPending:[NSNumber numberWithBool:_syncWithPeersPending],
391 kKeyEnsurePeerRegistration:[NSNumber numberWithBool:_ensurePeerRegistration]
395 - (void) importKeyInterests: (NSDictionary*) interests
397 _alwaysKeys = [NSMutableSet setWithArray: interests[kKeyAlwaysKeys]];
398 _firstUnlockKeys = [NSMutableSet setWithArray: interests[kKeyFirstUnlockKeys]];
399 _unlockedKeys = [NSMutableSet setWithArray: interests[kKeyUnlockedKeys]];
401 _pendingKeys = [NSMutableSet setWithArray: interests[kKeyPendingKeys]];
402 _syncWithPeersPending = [interests[kKeySyncWithPeersPending] boolValue];
403 _ensurePeerRegistration = [interests[kKeyEnsurePeerRegistration] boolValue];
406 - (NSMutableSet *)copyAllKeys
408 NSMutableSet *allKeys = [NSMutableSet setWithSet: _alwaysKeys];
409 [allKeys unionSet: _firstUnlockKeys];
410 [allKeys unionSet: _unlockedKeys];
414 -(void)registerAtTimeKeys:(NSDictionary*)keyparms
416 NSArray *alwaysArray = [keyparms valueForKey: kKeyAlwaysKeys];
417 NSArray *firstUnlockedKeysArray = [keyparms valueForKey: kKeyFirstUnlockKeys];
418 NSArray *whenUnlockedKeysArray = [keyparms valueForKey: kKeyUnlockedKeys];
421 [_alwaysKeys unionSet: [NSMutableSet setWithArray: alwaysArray]];
422 if(firstUnlockedKeysArray)
423 [_firstUnlockKeys unionSet: [NSMutableSet setWithArray: firstUnlockedKeysArray]];
424 if(whenUnlockedKeysArray)
425 [_unlockedKeys unionSet: [NSMutableSet setWithArray: whenUnlockedKeysArray]];
429 - (void)registerKeys: (NSDictionary*)keys
431 secdebug(XPROXYSCOPE, "registerKeys: keys: %@", keys);
433 NSMutableSet *allOldKeys = [self copyAllKeys];
435 NSDictionary *keyparms = [keys valueForKey: [NSString stringWithUTF8String: kMessageKeyParameter]];
436 NSDictionary *circles = [keys valueForKey: [NSString stringWithUTF8String: kMessageCircle]];
437 NSDictionary *messages = [keys valueForKey: [NSString stringWithUTF8String: kMessageMessage]];
439 _alwaysKeys = [NSMutableSet set];
440 _firstUnlockKeys = [NSMutableSet set];
441 _unlockedKeys = [NSMutableSet set];
443 _keyParameterKeys = keyparms;
444 _circleKeys = circles;
445 _messageKeys = messages;
447 [self registerAtTimeKeys: _keyParameterKeys];
448 [self registerAtTimeKeys: _circleKeys];
449 [self registerAtTimeKeys: _messageKeys];
451 NSMutableSet *allNewKeys = [self copyAllKeys];
453 // Make sure keys we no longer care about are not pending
454 [_pendingKeys intersectSet:allNewKeys];
455 if (_shadowPendingKeys) {
456 [_shadowPendingKeys intersectSet:allNewKeys];
459 // All new keys only is new keys (remove old keys)
460 [allNewKeys minusSet:allOldKeys];
462 // Mark new keys pending, they're new!
463 NSMutableSet *newKeysForCurrentLockState = [self pendKeysAndGetNewlyPended:allNewKeys];
465 [self persistState]; // Before we might call out, save our state so we recover if we crash
467 [self intersectWithCurrentLockState: newKeysForCurrentLockState];
468 // TODO: Don't processPendingKeysForCurrentLockState if none of the new keys have values.
469 if ([newKeysForCurrentLockState count] != 0) {
470 [self processPendingKeysForCurrentLockState];
474 - (NSDictionary *)localNotification:(NSDictionary *)localNotificationDict outFlags:(int64_t *)outFlags
476 __block bool done = false;
477 __block int64_t returnedFlags = 0;
478 __block NSDictionary *responses = NULL;
479 typedef bool (^CKDUserInteractionBlock) (CFDictionaryRef responses);
481 CKDUserInteraction *cui = [CKDUserInteraction sharedInstance];
482 [cui requestShowNotification:localNotificationDict completion:^ bool (CFDictionaryRef userResponses, int64_t flags)
484 responses = [NSDictionary dictionaryWithDictionary:(__bridge NSDictionary *)userResponses];
485 returnedFlags = flags;
486 secdebug(XPROXYSCOPE, "%@ requestShowNotification: dict: %@, flags: %#llx", self, responses, returnedFlags);
491 // TODO: replace with e.g. dispatch calls to wait, or semaphore
496 secdebug(XPROXYSCOPE, "%@ outFlags: %#llx", self, returnedFlags);
497 *outFlags = returnedFlags;
502 - (void)saveToUbiquitousStore
504 [self requestSynchronization:NO];
507 // MARK: ----- Event Handling -----
509 - (void)streamEvent:(xpc_object_t)notification
511 #if (!TARGET_IPHONE_SIMULATOR)
512 const char *notificationName = xpc_dictionary_get_string(notification, "Notification");
513 if (!notificationName) {
514 } else if (strcmp(notificationName, kUserKeybagStateChangeNotification)==0) {
515 return [self keybagStateChange];
516 } else if (strcmp(notificationName, kCloudKeychainStorechangeChangeNotification)==0) {
517 return [self kvsStoreChange];
518 } else if (strcmp(notificationName, kNotifyTokenForceUpdate)==0) {
519 // DEBUG -- Possibly remove in future
520 return [self processAllItems];
522 const char *eventName = xpc_dictionary_get_string(notification, "XPCEventName");
523 char *desc = xpc_copy_description(notification);
524 secerror("%@ event: %s name: %s desc: %s", self, eventName, notificationName, desc);
530 - (void)iCloudAccountAvailabilityChanged:(NSNotification*)notification
533 Continuing this example, you’d then implement an iCloudAccountAvailabilityChanged: method that would:
535 Call the ubiquityIdentityToken method and store its return value.
536 Compare the new value to the previous value, to find out if the user logged out of their account or
537 logged in to a different account. If the previously-used account is now unavailable, save the current
538 state locally as needed, empty your iCloud-related data caches, and refresh all iCloud-related user interface elements.
540 id previCloudToken = currentiCloudToken;
541 currentiCloudToken = [[NSFileManager defaultManager] ubiquityIdentityToken];
542 if (previCloudToken != currentiCloudToken)
543 secnotice("event", "%@ iCloud account changed!", self);
545 secnotice("event", "%@ %@", self, notification);
548 - (void)cloudChanged:(NSNotification*)notification
551 Posted when the value of one or more keys in the local key-value store
552 changed due to incoming data pushed from iCloud. This notification is
553 sent only upon a change received from iCloud; it is not sent when your
556 The user info dictionary can contain the reason for the notification as
557 well as a list of which values changed, as follows:
559 The value of the NSUbiquitousKeyValueStoreChangeReasonKey key, when
560 present, indicates why the key-value store changed. Its value is one of
561 the constants in "Change Reason Values."
563 The value of the NSUbiquitousKeyValueStoreChangedKeysKey, when present,
564 is an array of strings, each the name of a key whose value changed. The
565 notification object is the NSUbiquitousKeyValueStore object whose contents
568 NSUbiquitousKeyValueStoreInitialSyncChange is only posted if there is any
569 local value that has been overwritten by a distant value. If there is no
570 conflict between the local and the distant values when doing the initial
571 sync (e.g. if the cloud has no data stored or the client has not stored
572 any data yet), you'll never see that notification.
574 NSUbiquitousKeyValueStoreInitialSyncChange implies an initial round trip
575 with server but initial round trip with server does not imply
576 NSUbiquitousKeyValueStoreInitialSyncChange.
579 secdebug(XPROXYSCOPE, "%@ cloudChanged notification: %@", self, notification);
581 NSDictionary *userInfo = [notification userInfo];
582 NSNumber *reason = [userInfo objectForKey:NSUbiquitousKeyValueStoreChangeReasonKey];
583 if (reason) switch ([reason integerValue]) {
584 case NSUbiquitousKeyValueStoreInitialSyncChange:
585 case NSUbiquitousKeyValueStoreServerChange:
587 _seenKVSStoreChange = YES;
588 NSSet *keysChangedInCloud = [NSSet setWithArray:[userInfo objectForKey:NSUbiquitousKeyValueStoreChangedKeysKey]];
590 /* We are saying that we want to try processing a key no matter what,
591 * *if* it has changed in the cloud. */
592 [_pendingKeys minusSet:keysChangedInCloud];
594 NSSet *keysOfInterestThatChanged = [self pendKeysAndGetPendingForCurrentLockState:keysChangedInCloud];
595 NSMutableDictionary *changedValues = [self copyValues:keysOfInterestThatChanged];
596 if ([reason integerValue] == NSUbiquitousKeyValueStoreInitialSyncChange)
597 changedValues[(__bridge NSString*)kSOSKVSInitialSyncKey] = @"true";
599 secnotice("event", "%@ keysChangedInCloud: %@ keysOfInterest: %@", self, [[keysChangedInCloud allObjects] componentsJoinedByString: @" "], [[changedValues allKeys] componentsJoinedByString: @" "]);
600 if ([changedValues count])
601 [self processKeyChangedEvent:changedValues];
604 case NSUbiquitousKeyValueStoreQuotaViolationChange:
605 seccritical("%@ event received NSUbiquitousKeyValueStoreQuotaViolationChange", self);
607 case NSUbiquitousKeyValueStoreAccountChange:
608 // The primary account changed. We do not get this for password changes on the same account
609 secnotice("event", "%@ NSUbiquitousKeyValueStoreAccountChange", self);
610 NSDictionary *changedValues = @{ (__bridge NSString*)kSOSKVSAccountChangedKey: @"true" };
611 [self processKeyChangedEvent:changedValues];
616 - (void) doAfterFlush: (dispatch_block_t) block
618 // Flush any pending communication to Securityd.
620 dispatch_async(_calloutQueue, block);
623 - (void) calloutWith: (void(^)(NSSet *pending, bool syncWithPeersPending, bool ensurePeerRegistration, dispatch_queue_t queue, void(^done)(NSSet *handledKeys, bool handledSyncWithPeers, bool handledEnsurePeerRegistration))) callout
625 // In CKDKVSProxy's serial queue
626 dispatch_queue_t ckdkvsproxy_queue = dispatch_get_main_queue();
630 // dispatch_get_global_queue - well-known global concurrent queue
631 // dispatch_get_main_queue - default queue that is bound to the main thread
632 xpc_transaction_begin();
633 dispatch_async(_calloutQueue, ^{
634 __block NSSet *myPending;
635 __block bool mySyncWithPeersPending;
636 __block bool myEnsurePeerRegistration;
637 __block bool wasLocked;
638 dispatch_sync(ckdkvsproxy_queue, ^{
639 myPending = [_pendingKeys copy];
640 mySyncWithPeersPending = _syncWithPeersPending;
641 myEnsurePeerRegistration = _ensurePeerRegistration;
642 wasLocked = _isLocked;
646 secnotice("deaf", ">>>>>>>>>>> _oldInCallout is NO and we're heading in to the callout!");
648 _shadowPendingKeys = [NSMutableSet set];
649 _shadowSyncWithPeersPending = NO;
652 callout(myPending, mySyncWithPeersPending, myEnsurePeerRegistration, ckdkvsproxy_queue, ^(NSSet *handledKeys, bool handledSyncWithPeers, bool handledEnsurePeerRegistration) {
653 secdebug("event", "%@ %s%s before callout handled: %s%s", self, mySyncWithPeersPending ? "S" : "s", myEnsurePeerRegistration ? "E" : "e", handledSyncWithPeers ? "S" : "s", handledEnsurePeerRegistration ? "E" : "e");
655 // In CKDKVSProxy's serial queue
659 // Update ensurePeerRegistration
660 _ensurePeerRegistration = ((myEnsurePeerRegistration && !handledEnsurePeerRegistration) || _shadowEnsurePeerRegistration);
662 _shadowEnsurePeerRegistration = NO;
664 if(_ensurePeerRegistration && !_isLocked)
665 [self doEnsurePeerRegistration];
667 // Update SyncWithPeers stuff.
668 _syncWithPeersPending = ((mySyncWithPeersPending && (!handledSyncWithPeers)) || _shadowSyncWithPeersPending);
670 _shadowSyncWithPeersPending = NO;
671 if (handledSyncWithPeers)
672 _lastSyncTime = dispatch_time(DISPATCH_TIME_NOW, 0);
674 // Update pendingKeys and handle them
675 [_pendingKeys minusSet: handledKeys];
676 bool hadShadowPendingKeys = [_shadowPendingKeys count];
677 // Move away shadownPendingKeys first, because pendKeysAndGetPendingForCurrentLockState
678 // will look at them. See rdar://problem/20733166.
679 NSSet *oldShadowPendingKeys = _shadowPendingKeys;
680 _shadowPendingKeys = nil;
681 NSSet *filteredKeys = [self pendKeysAndGetPendingForCurrentLockState:oldShadowPendingKeys];
683 // Write state to disk
686 // Handle shadow pended stuff
687 if (_syncWithPeersPending && !_isLocked)
688 [self scheduleSyncRequestTimer];
689 /* We don't want to call processKeyChangedEvent if we failed to
690 handle pending keys and the device didn't unlock nor receive
691 any kvs changes while we were in our callout.
692 Doing so will lead to securityd and CloudKeychainProxy
693 talking to each other forever in a tight loop if securityd
694 repeatedly returns an error processing the same message.
695 Instead we leave any old pending keys until the next event. */
696 if (hadShadowPendingKeys || (!_isLocked && wasLocked))
697 [self processKeyChangedEvent:[self copyValues:filteredKeys]];
698 xpc_transaction_end();
703 - (void) sendKeysCallout: (NSSet *(^)(NSSet* pending, NSError** error)) handleKeys {
704 [self calloutWith: ^(NSSet *pending, bool syncWithPeersPending, bool ensurePeerRegistration, dispatch_queue_t queue, void(^done)(NSSet *, bool, bool)) {
705 NSError* error = NULL;
707 NSSet * handled = handleKeys(pending, &error);
709 dispatch_async(queue, ^{
711 secerror("%@ ensurePeerRegistration failed: %@", self, error);
714 done(handled, NO, NO);
719 - (void) doEnsurePeerRegistration
721 [self calloutWith:^(NSSet *pending, bool syncWithPeersPending, bool ensurePeerRegistration, dispatch_queue_t queue, void(^done)(NSSet *, bool, bool)) {
722 CFErrorRef error = NULL;
723 bool handledEnsurePeerRegistration = SOSCCProcessEnsurePeerRegistration(&error);
724 secerror("%@ ensurePeerRegistration called, %@ (%@)", self, handledEnsurePeerRegistration ? @"success" : @"failure", error);
725 dispatch_async(queue, ^{
726 if (!handledEnsurePeerRegistration) {
727 secerror("%@ ensurePeerRegistration failed: %@", self, error);
730 done(nil, NO, handledEnsurePeerRegistration);
731 CFReleaseSafe(error);
736 - (void) doSyncWithAllPeers
738 [self calloutWith:^(NSSet *pending, bool syncWithPeersPending, bool ensurePeerRegistration, dispatch_queue_t queue, void(^done)(NSSet *, bool, bool)) {
739 CFErrorRef error = NULL;
740 SyncWithAllPeersReason reason = SOSCCProcessSyncWithAllPeers(&error);
741 dispatch_async(queue, ^{
742 bool handledSyncWithPeers = NO;
743 if (reason == kSyncWithAllPeersSuccess) {
744 handledSyncWithPeers = YES;
745 secnotice("event", "%@ syncWithAllPeers succeeded", self);
746 } else if (reason == kSyncWithAllPeersLocked) {
747 secnotice("event", "%@ syncWithAllPeers attempted while locked - waiting for unlock", self);
748 handledSyncWithPeers = NO;
749 [self updateIsLocked];
750 } else if (reason == kSyncWithAllPeersOtherFail) {
751 // Pretend we handled syncWithPeers, by pushing out the _lastSyncTime
752 // This will cause us to wait for kMinSyncInterval seconds before
753 // retrying, so we don't spam securityd if sync is failing
754 secerror("%@ syncWithAllPeers %@, rescheduling timer", self, error);
755 _lastSyncTime = dispatch_time(DISPATCH_TIME_NOW, 0);
757 secerror("%@ syncWithAllPeers %@, unknown reason: %d", self, error, reason);
760 done(nil, handledSyncWithPeers, false);
761 CFReleaseSafe(error);
768 secnotice("event", "%@ syncWithPeersPending: %d inCallout: %d isLocked: %d", self, _syncWithPeersPending, _inCallout, _isLocked);
769 _syncTimerScheduled = NO;
770 if (_syncWithPeersPending && !_inCallout && !_isLocked)
771 [self doSyncWithAllPeers];
774 - (dispatch_time_t) nextSyncTime
776 dispatch_time_t nextSync = dispatch_time(DISPATCH_TIME_NOW, kMinSyncDelay);
778 // Don't sync again unless we waited at least kMinSyncInterval
780 dispatch_time_t soonest = dispatch_time(_lastSyncTime, kMinSyncInterval);
781 if (nextSync < soonest || _deadline < soonest) {
782 secdebug("timer", "%@ backing off", self);
787 // Don't delay more than kMaxSyncDelay after the first request.
788 if (nextSync > _deadline) {
789 secdebug("timer", "%@ hit deadline", self);
793 // Bump the timer by kMinSyncDelay
794 if (_syncTimerScheduled)
795 secdebug("timer", "%@ bumped timer", self);
797 secdebug("timer", "%@ scheduled timer", self);
802 - (void)scheduleSyncRequestTimer
804 dispatch_source_set_timer(_syncTimer, [self nextSyncTime], DISPATCH_TIME_FOREVER, kSyncTimerLeeway);
805 _syncTimerScheduled = YES;
808 - (void)requestSyncWithAllPeers // secd calling SOSCCSyncWithAllPeers invokes this
811 NSString *desc = [self description];
814 if (!_syncWithPeersPending || (_inCallout && !_shadowSyncWithPeersPending))
815 _deadline = dispatch_time(DISPATCH_TIME_NOW, kMaxSyncDelay);
817 if (!_syncWithPeersPending) {
818 _syncWithPeersPending = YES;
823 _shadowSyncWithPeersPending = YES;
825 [self scheduleSyncRequestTimer];
827 secdebug("event", "%@ %@", desc, self);
830 - (void)requestEnsurePeerRegistration // secd calling SOSCCSyncWithAllPeers invokes this
833 NSString *desc = [self description];
837 _shadowEnsurePeerRegistration = YES;
839 _ensurePeerRegistration = YES;
841 [self doEnsurePeerRegistration];
845 secdebug("event", "%@ %@", desc, self);
849 - (BOOL) updateUnlockedSinceBoot
851 CFErrorRef aksError = NULL;
852 if (!SecAKSGetHasBeenUnlocked(&_unlockedSinceBoot, &aksError)) {
853 secerror("%@ Got error from SecAKSGetHasBeenUnlocked: %@", self, aksError);
854 CFReleaseSafe(aksError);
860 - (BOOL) updateIsLocked
862 CFErrorRef aksError = NULL;
863 if (!SecAKSGetIsLocked(&_isLocked, &aksError)) {
864 secerror("%@ Got error querying lock state: %@", self, aksError);
865 CFReleaseSafe(aksError);
869 _unlockedSinceBoot = YES;
873 - (void) keybagStateChange
875 BOOL wasLocked = _isLocked;
876 if ([self updateIsLocked]) {
877 if (wasLocked == _isLocked)
878 secdebug("event", "%@ still %s ignoring", self, _isLocked ? "locked" : "unlocked");
880 [self keybagDidLock];
882 [self keybagDidUnlock];
886 - (void) keybagDidLock
888 secnotice("event", "%@", self);
891 - (void) keybagDidUnlock
893 secnotice("event", "%@", self);
894 if (_ensurePeerRegistration) {
895 [self doEnsurePeerRegistration];
898 // First send changed keys to securityd so it can proccess updates
899 [self processPendingKeysForCurrentLockState];
901 // Then, tickle securityd to perform a sync if needed.
902 if (_syncWithPeersPending && !_syncTimerScheduled) {
903 [self doSyncWithAllPeers];
907 - (void) kvsStoreChange {
908 if (!_seenKVSStoreChange) {
909 _seenKVSStoreChange = YES; // Only do this once
910 secnotice("event", "%@ received darwin notification before first NSNotification", self);
911 // TODO This might not be needed if we always get the NSNotification
912 // deleived even if we were launched due to a kvsStoreChange
913 // Send all keys for current lock state to securityd so it can proccess them
914 [self pendKeysAndGetNewlyPended: [self copyAllKeys]];
915 [self processPendingKeysForCurrentLockState];
917 secdebug("event", "%@ ignored, waiting for NSNotification", self);
922 // MARK: ----- Key Filtering -----
925 - (NSSet*) keysForCurrentLockState
927 secdebug("keytrace", "%@ Filtering: unlockedSinceBoot: %d\n unlocked: %d\n, _keysOfInterest: %@", self, (int) _unlockedSinceBoot, (int) !_isLocked, [self exportKeyInterests]);
929 NSMutableSet *currentStateKeys = [NSMutableSet setWithSet: _alwaysKeys];
930 if (_unlockedSinceBoot)
931 [currentStateKeys unionSet: _firstUnlockKeys];
934 [currentStateKeys unionSet: _unlockedKeys];
936 return currentStateKeys;
939 - (NSMutableSet*) pendKeysAndGetNewlyPended: (NSSet*) keysToPend
941 NSMutableSet *newlyPendedKeys = [keysToPend mutableCopy];
942 [newlyPendedKeys minusSet: _pendingKeys];
943 if (_shadowPendingKeys) {
944 [newlyPendedKeys minusSet: _shadowPendingKeys];
947 [_pendingKeys unionSet:keysToPend];
948 if (_shadowPendingKeys) {
949 [_shadowPendingKeys unionSet:keysToPend];
952 return newlyPendedKeys;
955 - (void) intersectWithCurrentLockState: (NSMutableSet*) set
957 [set intersectSet: [self keysForCurrentLockState]];
960 - (NSMutableSet*) pendingKeysForCurrentLockState
962 NSMutableSet * result = [_pendingKeys mutableCopy];
963 [self intersectWithCurrentLockState:result];
967 - (NSMutableSet*) pendKeysAndGetPendingForCurrentLockState: (NSSet*) startingSet
969 [self pendKeysAndGetNewlyPended: startingSet];
971 return [self pendingKeysForCurrentLockState];
974 - (NSMutableDictionary *)copyValues:(NSSet*)keysOfInterest
976 // Grab values from KVS.
977 NSUbiquitousKeyValueStore *store = [self cloudStore];
978 NSMutableDictionary *changedValues = [NSMutableDictionary dictionaryWithCapacity:0];
979 [keysOfInterest enumerateObjectsUsingBlock:^(id obj, BOOL *stop)
981 NSString* key = (NSString*) obj;
982 id objval = [store objectForKey:key];
983 if (!objval) objval = [NSNull null];
985 [changedValues setObject:objval forKey:key];
986 secdebug(XPROXYSCOPE, "%@ storeChanged updated value for %@", self, key);
988 return changedValues;
992 During RegisterKeys, separate keys-of-interest into three disjoint sets:
993 - keys that we always want to be notified about; this means we can get the
995 - keys that require the device to have been unlocked at least once
996 - keys that require the device to be unlocked now
998 Typically, the sets of keys will be:
1004 The caller is responsible for making sure that the keys in e.g. alwaysKeys are
1005 values that can be handled at any time (that is, not when unlocked)
1007 Each time we get a notification from ubiquity that keys have changed, we need to
1008 see if anything of interest changed. If we don't care, then done.
1010 For each key-of-interest that changed, we either notify the client that things
1011 changed, or add it to a pendingNotifications list. If the notification to the
1012 client fails, also add it to the pendingNotifications list. This pending list
1013 should be written to persistent storage and consulted any time we either get an
1014 item changed notification, or get a stream event signalling a change in lock state.
1016 We can notify the client either through XPC if a connection is set up, or call a
1017 routine in securityd to launch it.
1021 - (void)processKeyChangedEvent:(NSDictionary *)changedValues
1023 NSMutableDictionary* filtered = [NSMutableDictionary dictionary];
1025 NSMutableArray* nullKeys = [NSMutableArray array];
1026 // Remove nulls because we don't want them in securityd.
1027 [changedValues enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
1028 if (obj == [NSNull null])
1029 [nullKeys addObject:key];
1031 filtered[key] = obj;
1034 if ([nullKeys count])
1035 [_pendingKeys minusSet: [NSSet setWithArray: nullKeys]];
1037 if([filtered count] != 0 ){
1038 [self sendKeysCallout:^NSSet *(NSSet *pending, NSError** error) {
1039 CFErrorRef cf_error = NULL;
1040 NSArray* handledMessage = (__bridge_transfer NSArray*) _SecKeychainSyncUpdateMessage((__bridge CFDictionaryRef)filtered, &cf_error);
1041 NSError *updateError = (__bridge_transfer NSError*)cf_error;
1043 *error = updateError;
1045 secnotice("keytrace", "%@ misc handled: %@ null: %@ pending: %@", self,
1046 [handledMessage componentsJoinedByString: @" "],
1047 [nullKeys componentsJoinedByString: @" "],
1048 [[_pendingKeys allObjects] componentsJoinedByString: @" "]);
1050 return handledMessage ? [NSSet setWithArray: handledMessage] : nil;
1053 secnotice("keytrace", "null: %@ pending: %@",
1054 [nullKeys componentsJoinedByString: @" "],
1055 [[_pendingKeys allObjects] componentsJoinedByString: @" "]);
1059 - (void) processPendingKeysForCurrentLockState
1061 [self processKeyChangedEvent: [self copyValues: [self pendingKeysForCurrentLockState]]];