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]];
589 NSSet *keysOfInterestThatChanged = [self pendKeysAndGetPendingForCurrentLockState:keysChangedInCloud];
590 NSMutableDictionary *changedValues = [self copyValues:keysOfInterestThatChanged];
591 if ([reason integerValue] == NSUbiquitousKeyValueStoreInitialSyncChange)
592 changedValues[(__bridge NSString*)kSOSKVSInitialSyncKey] = @"true";
594 secnotice("event", "%@ keysChangedInCloud: %@ keysOfInterest: %@", self, [[keysChangedInCloud allObjects] componentsJoinedByString: @" "], [[changedValues allKeys] componentsJoinedByString: @" "]);
595 if ([changedValues count])
596 [self processKeyChangedEvent:changedValues];
599 case NSUbiquitousKeyValueStoreQuotaViolationChange:
600 seccritical("%@ event received NSUbiquitousKeyValueStoreQuotaViolationChange", self);
602 case NSUbiquitousKeyValueStoreAccountChange:
603 // The primary account changed. We do not get this for password changes on the same account
604 secnotice("event", "%@ NSUbiquitousKeyValueStoreAccountChange", self);
605 NSDictionary *changedValues = @{ (__bridge NSString*)kSOSKVSAccountChangedKey: @"true" };
606 [self processKeyChangedEvent:changedValues];
611 - (void) doAfterFlush: (dispatch_block_t) block
613 // Flush any pending communication to Securityd.
615 dispatch_async(_calloutQueue, block);
618 - (void) calloutWith: (void(^)(NSSet *pending, bool syncWithPeersPending, bool ensurePeerRegistration, dispatch_queue_t queue, void(^done)(NSSet *handledKeys, bool handledSyncWithPeers, bool handledEnsurePeerRegistration))) callout
620 // In CKDKVSProxy's serial queue
621 dispatch_queue_t ckdkvsproxy_queue = dispatch_get_main_queue();
625 // dispatch_get_global_queue - well-known global concurrent queue
626 // dispatch_get_main_queue - default queue that is bound to the main thread
627 xpc_transaction_begin();
628 dispatch_async(_calloutQueue, ^{
629 __block NSSet *myPending;
630 __block bool mySyncWithPeersPending;
631 __block bool myEnsurePeerRegistration;
632 __block bool wasLocked;
633 dispatch_sync(ckdkvsproxy_queue, ^{
634 myPending = [_pendingKeys copy];
635 mySyncWithPeersPending = _syncWithPeersPending;
636 myEnsurePeerRegistration = _ensurePeerRegistration;
637 wasLocked = _isLocked;
641 secnotice("deaf", ">>>>>>>>>>> _oldInCallout is NO and we're heading in to the callout!");
643 _shadowPendingKeys = [NSMutableSet set];
644 _shadowSyncWithPeersPending = NO;
647 callout(myPending, mySyncWithPeersPending, myEnsurePeerRegistration, ckdkvsproxy_queue, ^(NSSet *handledKeys, bool handledSyncWithPeers, bool handledEnsurePeerRegistration) {
648 secdebug("event", "%@ %s%s before callout handled: %s%s", self, mySyncWithPeersPending ? "S" : "s", myEnsurePeerRegistration ? "E" : "e", handledSyncWithPeers ? "S" : "s", handledEnsurePeerRegistration ? "E" : "e");
650 // In CKDKVSProxy's serial queue
654 // Update ensurePeerRegistration
655 _ensurePeerRegistration = ((myEnsurePeerRegistration && !handledEnsurePeerRegistration) || _shadowEnsurePeerRegistration);
657 _shadowEnsurePeerRegistration = NO;
659 if(_ensurePeerRegistration && !_isLocked)
660 [self doEnsurePeerRegistration];
662 // Update SyncWithPeers stuff.
663 _syncWithPeersPending = ((mySyncWithPeersPending && (!handledSyncWithPeers)) || _shadowSyncWithPeersPending);
665 _shadowSyncWithPeersPending = NO;
666 if (handledSyncWithPeers)
667 _lastSyncTime = dispatch_time(DISPATCH_TIME_NOW, 0);
669 // Update pendingKeys and handle them
670 [_pendingKeys minusSet: handledKeys];
671 bool hadShadowPendingKeys = [_shadowPendingKeys count];
672 NSSet *filteredKeys = [self pendKeysAndGetPendingForCurrentLockState:_shadowPendingKeys];
673 _shadowPendingKeys = nil;
675 // Write state to disk
678 // Handle shadow pended stuff
679 if (_syncWithPeersPending && !_isLocked)
680 [self scheduleSyncRequestTimer];
681 /* We don't want to call processKeyChangedEvent if we failed to
682 handle pending keys and the device didn't unlock nor receive
683 any kvs changes while we were in our callout.
684 Doing so will lead to securityd and CloudKeychainProxy
685 talking to each other forever in a tight loop if securityd
686 repeatedly returns an error processing the same message.
687 Instead we leave any old pending keys until the next event. */
688 if (hadShadowPendingKeys || (!_isLocked && wasLocked))
689 [self processKeyChangedEvent:[self copyValues:filteredKeys]];
690 xpc_transaction_end();
695 - (void) sendKeysCallout: (NSSet *(^)(NSSet* pending, NSError** error)) handleKeys {
696 [self calloutWith: ^(NSSet *pending, bool syncWithPeersPending, bool ensurePeerRegistration, dispatch_queue_t queue, void(^done)(NSSet *, bool, bool)) {
697 NSError* error = NULL;
699 NSSet * handled = handleKeys(pending, &error);
701 dispatch_async(queue, ^{
703 secerror("%@ ensurePeerRegistration failed: %@", self, error);
706 done(handled, NO, NO);
711 - (void) doEnsurePeerRegistration
713 [self calloutWith:^(NSSet *pending, bool syncWithPeersPending, bool ensurePeerRegistration, dispatch_queue_t queue, void(^done)(NSSet *, bool, bool)) {
714 CFErrorRef error = NULL;
715 bool handledEnsurePeerRegistration = SOSCCProcessEnsurePeerRegistration(&error);
716 secerror("%@ ensurePeerRegistration called, %@ (%@)", self, handledEnsurePeerRegistration ? @"success" : @"failure", error);
717 dispatch_async(queue, ^{
718 if (!handledEnsurePeerRegistration) {
719 secerror("%@ ensurePeerRegistration failed: %@", self, error);
722 done(nil, NO, handledEnsurePeerRegistration);
723 CFReleaseSafe(error);
728 - (void) doSyncWithAllPeers
730 [self calloutWith:^(NSSet *pending, bool syncWithPeersPending, bool ensurePeerRegistration, dispatch_queue_t queue, void(^done)(NSSet *, bool, bool)) {
731 CFErrorRef error = NULL;
732 SyncWithAllPeersReason reason = SOSCCProcessSyncWithAllPeers(&error);
733 dispatch_async(queue, ^{
734 bool handledSyncWithPeers = NO;
735 if (reason == kSyncWithAllPeersSuccess) {
736 handledSyncWithPeers = YES;
737 secnotice("event", "%@ syncWithAllPeers succeeded", self);
738 } else if (reason == kSyncWithAllPeersLocked) {
739 secnotice("event", "%@ syncWithAllPeers attempted while locked - waiting for unlock", self);
740 handledSyncWithPeers = NO;
741 [self updateIsLocked];
742 } else if (reason == kSyncWithAllPeersOtherFail) {
743 // Pretend we handled syncWithPeers, by pushing out the _lastSyncTime
744 // This will cause us to wait for kMinSyncInterval seconds before
745 // retrying, so we don't spam securityd if sync is failing
746 secerror("%@ syncWithAllPeers %@, rescheduling timer", self, error);
747 _lastSyncTime = dispatch_time(DISPATCH_TIME_NOW, 0);
749 secerror("%@ syncWithAllPeers %@, unknown reason: %d", self, error, reason);
752 done(nil, handledSyncWithPeers, false);
753 CFReleaseSafe(error);
760 secnotice("event", "%@ syncWithPeersPending: %d inCallout: %d isLocked: %d", self, _syncWithPeersPending, _inCallout, _isLocked);
761 _syncTimerScheduled = NO;
762 if (_syncWithPeersPending && !_inCallout && !_isLocked)
763 [self doSyncWithAllPeers];
766 - (dispatch_time_t) nextSyncTime
768 dispatch_time_t nextSync = dispatch_time(DISPATCH_TIME_NOW, kMinSyncDelay);
770 // Don't sync again unless we waited at least kMinSyncInterval
772 dispatch_time_t soonest = dispatch_time(_lastSyncTime, kMinSyncInterval);
773 if (nextSync < soonest || _deadline < soonest) {
774 secdebug("timer", "%@ backing off", self);
779 // Don't delay more than kMaxSyncDelay after the first request.
780 if (nextSync > _deadline) {
781 secdebug("timer", "%@ hit deadline", self);
785 // Bump the timer by kMinSyncDelay
786 if (_syncTimerScheduled)
787 secdebug("timer", "%@ bumped timer", self);
789 secdebug("timer", "%@ scheduled timer", self);
794 - (void)scheduleSyncRequestTimer
796 dispatch_source_set_timer(_syncTimer, [self nextSyncTime], DISPATCH_TIME_FOREVER, kSyncTimerLeeway);
797 _syncTimerScheduled = YES;
800 - (void)requestSyncWithAllPeers // secd calling SOSCCSyncWithAllPeers invokes this
803 NSString *desc = [self description];
806 if (!_syncWithPeersPending || (_inCallout && !_shadowSyncWithPeersPending))
807 _deadline = dispatch_time(DISPATCH_TIME_NOW, kMaxSyncDelay);
809 if (!_syncWithPeersPending) {
810 _syncWithPeersPending = YES;
815 _shadowSyncWithPeersPending = YES;
817 [self scheduleSyncRequestTimer];
819 secdebug("event", "%@ %@", desc, self);
822 - (void)requestEnsurePeerRegistration // secd calling SOSCCSyncWithAllPeers invokes this
825 NSString *desc = [self description];
829 _shadowEnsurePeerRegistration = YES;
831 _ensurePeerRegistration = YES;
833 [self doEnsurePeerRegistration];
837 secdebug("event", "%@ %@", desc, self);
841 - (BOOL) updateUnlockedSinceBoot
843 CFErrorRef aksError = NULL;
844 if (!SecAKSGetHasBeenUnlocked(&_unlockedSinceBoot, &aksError)) {
845 secerror("%@ Got error from SecAKSGetHasBeenUnlocked: %@", self, aksError);
846 CFReleaseSafe(aksError);
852 - (BOOL) updateIsLocked
854 CFErrorRef aksError = NULL;
855 if (!SecAKSGetIsLocked(&_isLocked, &aksError)) {
856 secerror("%@ Got error querying lock state: %@", self, aksError);
857 CFReleaseSafe(aksError);
861 _unlockedSinceBoot = YES;
865 - (void) keybagStateChange
867 BOOL wasLocked = _isLocked;
868 if ([self updateIsLocked]) {
869 if (wasLocked == _isLocked)
870 secdebug("event", "%@ still %s ignoring", self, _isLocked ? "locked" : "unlocked");
872 [self keybagDidLock];
874 [self keybagDidUnlock];
878 - (void) keybagDidLock
880 secnotice("event", "%@", self);
883 - (void) keybagDidUnlock
885 secnotice("event", "%@", self);
886 if (_ensurePeerRegistration) {
887 [self doEnsurePeerRegistration];
890 // First send changed keys to securityd so it can proccess updates
891 [self processPendingKeysForCurrentLockState];
893 // Then, tickle securityd to perform a sync if needed.
894 if (_syncWithPeersPending && !_syncTimerScheduled) {
895 [self doSyncWithAllPeers];
899 - (void) kvsStoreChange {
900 if (!_seenKVSStoreChange) {
901 _seenKVSStoreChange = YES; // Only do this once
902 secnotice("event", "%@ received darwin notification before first NSNotification", self);
903 // TODO This might not be needed if we always get the NSNotification
904 // deleived even if we were launched due to a kvsStoreChange
905 // Send all keys for current lock state to securityd so it can proccess them
906 [self pendKeysAndGetNewlyPended: [self copyAllKeys]];
907 [self processPendingKeysForCurrentLockState];
909 secdebug("event", "%@ ignored, waiting for NSNotification", self);
914 // MARK: ----- Key Filtering -----
917 - (NSSet*) keysForCurrentLockState
919 secdebug("keytrace", "%@ Filtering: unlockedSinceBoot: %d\n unlocked: %d\n, _keysOfInterest: %@", self, (int) _unlockedSinceBoot, (int) !_isLocked, [self exportKeyInterests]);
921 NSMutableSet *currentStateKeys = [NSMutableSet setWithSet: _alwaysKeys];
922 if (_unlockedSinceBoot)
923 [currentStateKeys unionSet: _firstUnlockKeys];
926 [currentStateKeys unionSet: _unlockedKeys];
928 return currentStateKeys;
931 - (NSMutableSet*) pendKeysAndGetNewlyPended: (NSSet*) keysToPend
933 NSMutableSet *newlyPendedKeys = [keysToPend mutableCopy];
934 [newlyPendedKeys minusSet: _pendingKeys];
935 if (_shadowPendingKeys) {
936 [newlyPendedKeys minusSet: _shadowPendingKeys];
939 [_pendingKeys unionSet:keysToPend];
940 if (_shadowPendingKeys) {
941 [_shadowPendingKeys unionSet:keysToPend];
944 return newlyPendedKeys;
947 - (void) intersectWithCurrentLockState: (NSMutableSet*) set
949 [set intersectSet: [self keysForCurrentLockState]];
952 - (NSMutableSet*) pendingKeysForCurrentLockState
954 NSMutableSet * result = [_pendingKeys mutableCopy];
955 [self intersectWithCurrentLockState:result];
959 - (NSMutableSet*) pendKeysAndGetPendingForCurrentLockState: (NSSet*) startingSet
961 [self pendKeysAndGetNewlyPended: startingSet];
963 return [self pendingKeysForCurrentLockState];
966 - (NSMutableDictionary *)copyValues:(NSSet*)keysOfInterest
968 // Grab values from KVS.
969 NSUbiquitousKeyValueStore *store = [self cloudStore];
970 NSMutableDictionary *changedValues = [NSMutableDictionary dictionaryWithCapacity:0];
971 [keysOfInterest enumerateObjectsUsingBlock:^(id obj, BOOL *stop)
973 NSString* key = (NSString*) obj;
974 id objval = [store objectForKey:key];
975 if (!objval) objval = [NSNull null];
977 [changedValues setObject:objval forKey:key];
978 secdebug(XPROXYSCOPE, "%@ storeChanged updated value for %@", self, key);
980 return changedValues;
984 During RegisterKeys, separate keys-of-interest into three disjoint sets:
985 - keys that we always want to be notified about; this means we can get the
987 - keys that require the device to have been unlocked at least once
988 - keys that require the device to be unlocked now
990 Typically, the sets of keys will be:
996 The caller is responsible for making sure that the keys in e.g. alwaysKeys are
997 values that can be handled at any time (that is, not when unlocked)
999 Each time we get a notification from ubiquity that keys have changed, we need to
1000 see if anything of interest changed. If we don't care, then done.
1002 For each key-of-interest that changed, we either notify the client that things
1003 changed, or add it to a pendingNotifications list. If the notification to the
1004 client fails, also add it to the pendingNotifications list. This pending list
1005 should be written to persistent storage and consulted any time we either get an
1006 item changed notification, or get a stream event signalling a change in lock state.
1008 We can notify the client either through XPC if a connection is set up, or call a
1009 routine in securityd to launch it.
1013 - (void)processKeyChangedEvent:(NSDictionary *)changedValues
1015 NSMutableDictionary* filtered = [NSMutableDictionary dictionary];
1017 NSMutableArray* nullKeys = [NSMutableArray array];
1018 // Remove nulls because we don't want them in securityd.
1019 [changedValues enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
1020 if (obj == [NSNull null])
1021 [nullKeys addObject:key];
1023 filtered[key] = obj;
1026 if ([nullKeys count])
1027 [_pendingKeys minusSet: [NSSet setWithArray: nullKeys]];
1029 if([filtered count] != 0 ){
1030 [self sendKeysCallout:^NSSet *(NSSet *pending, NSError** error) {
1031 CFErrorRef cf_error = NULL;
1032 NSArray* handledMessage = (__bridge_transfer NSArray*) _SecKeychainSyncUpdateMessage((__bridge CFDictionaryRef)filtered, &cf_error);
1033 NSError *updateError = (__bridge_transfer NSError*)cf_error;
1035 *error = updateError;
1037 secnotice("keytrace", "%@ misc handled: %@ null: %@ pending: %@", self,
1038 [handledMessage componentsJoinedByString: @" "],
1039 [nullKeys componentsJoinedByString: @" "],
1040 [[_pendingKeys allObjects] componentsJoinedByString: @" "]);
1042 return handledMessage ? [NSSet setWithArray: handledMessage] : nil;
1045 secnotice("keytrace", "null: %@ pending: %@",
1046 [nullKeys componentsJoinedByString: @" "],
1047 [[_pendingKeys allObjects] componentsJoinedByString: @" "]);
1051 - (void) processPendingKeysForCurrentLockState
1053 [self processKeyChangedEvent: [self copyValues: [self pendingKeysForCurrentLockState]]];