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>
38 #import <os/activity.h>
40 #import "CKDKVSProxy.h"
41 #import "CKDPersistentState.h"
42 #import "CKDUserInteraction.h"
44 #include <Security/SecureObjectSync/SOSARCDefines.h>
47 #include <Security/SecureObjectSync/SOSAccount.h>
48 #include <Security/SecureObjectSync/SOSCloudCircleInternal.h>
49 #include <Security/SecureObjectSync/SOSKVSKeys.h>
51 #include "SOSCloudKeychainConstants.h"
53 #include <utilities/SecAKSWrappers.h>
54 #include <utilities/SecCFRelease.h>
57 The total space available in your app’s iCloud key-value storage is 1 MB.
58 The maximum number of keys you can specify is 1024, and the size limit for
59 each value associated with a key is 1 MB. So, for example, if you store a
60 single large value of 1 MB for a single key, that consumes your total
61 available storage. If you store 1 KB of data for each key, you can use
62 1,000 key-value pairs.
65 static const char *kStreamName = "com.apple.notifyd.matching";
67 static NSString *kKeyKeyParameterKeys = @"KeyParameterKeys";
68 static NSString *kKeyCircleKeys = @"CircleKeys";
69 static NSString *kKeyMessageKeys = @"MessageKeys";
71 static NSString *kKeyAlwaysKeys = @"AlwaysKeys";
72 static NSString *kKeyFirstUnlockKeys = @"FirstUnlockKeys";
73 static NSString *kKeyUnlockedKeys = @"UnlockedKeys";
74 static NSString *kKeyPendingKeys = @"PendingKeys";
75 static NSString *kKeyUnsentChangedKeys = @"unsentChangedKeys";
76 static NSString *kKeyUnlockNotificationRequested = @"unlockNotificationRequested";
77 static NSString *kKeySyncWithPeersPending = @"SyncWithPeersPending";
78 static NSString *kKeyEnsurePeerRegistration = @"EnsurePeerRegistration";
79 static NSString *kKeyDSID = @"DSID";
83 kCallbackMethodSecurityd = 0,
84 kCallbackMethodXPC = 1,
87 static const int64_t kMinSyncDelay = (NSEC_PER_MSEC * 500); // 500ms minimum delay before a syncWithAllPeers call.
88 static const int64_t kMaxSyncDelay = (NSEC_PER_SEC * 5); // 5s maximun delay for a given request
89 static const int64_t kMinSyncInterval = (NSEC_PER_SEC * 15); // 15s minimum time between successive syncWithAllPeers calls.
90 static const int64_t kSyncTimerLeeway = (NSEC_PER_MSEC * 250); // 250ms leeway for sync events.
93 #define UPDATE_RESUBMIT 4
95 @interface NSUbiquitousKeyValueStore (NSUbiquitousKeyValueStore_PrivateZ)
96 - (void) _synchronizeWithCompletionHandler:(void (^)(NSError *error))completionHandler;
100 - (void) synchronizeWithCompletionHandler:(void (^)(NSError *error))completionHandler;
105 @implementation UbiqitousKVSProxy
110 [SOSPersistentState setRegisteredKeys:[self exportKeyInterests]];
113 + (UbiqitousKVSProxy *) sharedKVSProxy
115 static UbiqitousKVSProxy *sharedKVSProxy;
116 if (!sharedKVSProxy) {
117 static dispatch_once_t onceToken;
118 dispatch_once(&onceToken, ^{
119 sharedKVSProxy = [[self alloc] init];
122 return sharedKVSProxy;
127 if (self = [super init])
129 secnotice("event", "%@ start", self);
131 _calloutQueue = dispatch_queue_create("CKDCallout", DISPATCH_QUEUE_SERIAL);
132 _freshParamsQueue = dispatch_queue_create("CKDFresh", DISPATCH_QUEUE_SERIAL);
133 _syncTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue());
134 dispatch_source_set_timer(_syncTimer, DISPATCH_TIME_FOREVER, DISPATCH_TIME_FOREVER, kSyncTimerLeeway);
135 dispatch_source_set_event_handler(_syncTimer, ^{
138 dispatch_resume(_syncTimer);
140 [[NSNotificationCenter defaultCenter]
142 selector: @selector (iCloudAccountAvailabilityChanged:)
143 name: NSUbiquityIdentityDidChangeNotification
146 [[NSNotificationCenter defaultCenter] addObserver:self
147 selector:@selector(cloudChanged:)
148 name:NSUbiquitousKeyValueStoreDidChangeExternallyNotification
151 [self importKeyInterests: [SOSPersistentState registeredKeys]];
153 // Register for lock state changes
154 xpc_set_event_stream_handler(kStreamName, dispatch_get_main_queue(),
155 ^(xpc_object_t notification){
156 [self streamEvent:notification];
160 [self updateUnlockedSinceBoot];
161 [self updateIsLocked];
163 [self keybagDidUnlock];
165 secdebug(XPROXYSCOPE, "%@ done", self);
170 - (NSString *)description
172 return [NSString stringWithFormat:@"<%s%s%s%s%s%s%s%s%s%s%s>",
173 _isLocked ? "L" : "U",
174 _unlockedSinceBoot ? "B" : "-",
175 _seenKVSStoreChange ? "K" : "-",
176 _syncTimerScheduled ? "T" : "-",
177 _syncWithPeersPending ? "s" : "-",
178 _ensurePeerRegistration ? "e" : "-",
179 [_pendingKeys count] ? "p" : "-",
180 _inCallout ? "C" : "-",
181 _shadowSyncWithPeersPending ? "S" : "-",
182 _shadowEnsurePeerRegistration ? "E" : "-",
183 [_shadowPendingKeys count] ? "P" : "-"];
186 - (void)processAllItems
188 NSDictionary *allItems = [self getAll];
191 secnotice("event", "%@ sending: %@", self, [[allItems allKeys] componentsJoinedByString: @" "]);
192 [self processKeyChangedEvent:allItems];
195 secdebug(XPROXYSCOPE, "%@ No items in KVS", self);
198 - (void)setItemsChangedBlock:(CloudItemsChangedBlock)itemsChangedBlock
200 self->itemsChangedCallback = itemsChangedBlock;
205 secdebug(XPROXYSCOPE, "%@", self);
206 [[NSNotificationCenter defaultCenter] removeObserver:self
207 name:NSUbiquitousKeyValueStoreDidChangeExternallyNotification object:nil];
209 [[NSNotificationCenter defaultCenter] removeObserver:self
210 name:NSUbiquityIdentityDidChangeNotification object:nil];
213 // MARK: ----- Client Interface -----
215 - (void)setObject:(id)obj forKey:(id)key
217 NSUbiquitousKeyValueStore *store = [self cloudStore];
220 id value = [store objectForKey:key];
222 secdebug("kvsdebug", "%@ key %@ changed: %@ to: %@", self, key, value, obj);
224 secdebug("kvsdebug", "%@ key %@ initialized to: %@", self, key, obj);
225 [store setObject:obj forKey:key];
226 [self requestSynchronization:NO];
228 secerror("Can't get kvs store, key: %@ not set to: %@", key, obj);
232 - (void)setObjectsFromDictionary:(NSDictionary *)values
234 NSUbiquitousKeyValueStore *store = [self cloudStore];
237 secnoticeq("dsid", "Ensure DSIDs match");
238 NSMutableDictionary *mutableValues = [NSMutableDictionary dictionaryWithCapacity:0];
240 [values enumerateKeysAndObjectsUsingBlock: ^(id key, id obj, BOOL *stop)
242 if (obj == NULL || obj == [NSNull null])
243 [store removeObjectForKey:key];
245 else if([key isEqualToString: @"^OfficialDSID"]){
247 secnotice("dsid", "setting dsid to %@", obj);
250 else if([key isEqual: @"^Required"]){
251 if( [_dsid isEqualToString: @""]){
252 secdebug("dsid", "CloudKeychainProxy setting dsid to :%@ from securityd", obj);
256 else if(![_dsid isEqual: obj]){
257 secerror("Account DSIDs do not match, cloud keychain proxy: %@, securityd: %@", _dsid, obj);
258 secerror("Not going to write these: %@ into KVS!", values);
263 [ mutableValues setValue:obj forKey:key ];
267 secnoticeq("keytrace", "%@ sending: %@", self, [[mutableValues allKeys] componentsJoinedByString: @" "]);
268 [mutableValues enumerateKeysAndObjectsUsingBlock: ^(id key, id obj, BOOL *stop)
270 if (obj == NULL || obj == [NSNull null])
271 [store removeObjectForKey:key];
274 id oldObj = [store objectForKey:key];
275 if ([oldObj isEqual: obj]) {
276 if ([key hasPrefix:@"ak|"]) { // TODO: somewhat of a hack
277 // Fix KVS repeated message undelivery by sending a NULL first (deafness)
278 secnoticeq("keytrace", "forcing resend of peer-peer message: %@", key);
279 [store removeObjectForKey:key];
281 // Resending circle messages is bad
282 secnoticeq("keytrace", "repeated message: %@ (not peer-peer); not propogated", key);
285 [store setObject:obj forKey:key];
289 [self requestSynchronization:NO];
292 secdebug(XPROXYSCOPE, "%@ NULL? store: %@, values: %@", self, store, values);
295 - (void)requestSynchronization:(bool)force
299 secdebug(XPROXYSCOPE, "%@ synchronize (forced)", self);
300 [[self cloudStore] synchronize];
304 secdebug(XPROXYSCOPE, "%@ synchronize (soon)", self);
305 [[self cloudStore] synchronize];
309 - (NSUbiquitousKeyValueStore *)cloudStore
311 NSUbiquitousKeyValueStore *iCloudStore = [NSUbiquitousKeyValueStore defaultStore];
313 secerror("%s %@ NO NSUbiquitousKeyValueStore defaultStore", kWAIT2MINID, self);
319 Only call out to syncdefaultsd once every 5 seconds, since parameters can't change that
320 fast and callers expect synchonicity.
322 Since we don't actually get the values for the keys, just store off a timestamp.
325 // try to synchronize asap, and invoke the handler on completion to take incoming changes.
327 static bool isResubmitError(NSError* error) {
328 return error && (CFErrorGetCode((__bridge CFErrorRef) error) == UPDATE_RESUBMIT); // Why don't we check the domain?!
331 - (BOOL) AttemptSynchronization:(NSError **)failure
334 __block NSError *tempFailure = NULL;
335 int triesRemaining = 10;
337 NSUbiquitousKeyValueStore * store = [self cloudStore];
339 dispatch_semaphore_t freshSemaphore = dispatch_semaphore_create(0);
343 secnoticeq("fresh", "%s CALLING OUT TO syncdefaultsd SWCH: %@", kWAIT2MINID, self);
345 [store synchronizeWithCompletionHandler:^(NSError *error) {
348 secerrorq("%s RETURNING FROM syncdefaultsd SWCH: %@: %@", kWAIT2MINID, self, error);
350 secnoticeq("fresh", "%s RETURNING FROM syncdefaultsd SWCH: %@", kWAIT2MINID, self);
351 [store synchronize]; // Per olivier in <rdar://problem/13412631>, sync before getting values
352 secnoticeq("fresh", "%s RETURNING FROM syncdefaultsd SYNC: %@", kWAIT2MINID, self);
354 dispatch_semaphore_signal(freshSemaphore);
356 dispatch_semaphore_wait(freshSemaphore, DISPATCH_TIME_FOREVER);
357 } while (triesRemaining > 0 && isResubmitError(tempFailure));
359 if (isResubmitError(tempFailure)) {
360 secerrorq("%s Number of retry attempts to request freshness exceeded", kWAIT2MINID);
363 if (failure && (*failure == NULL)) {
364 *failure = tempFailure;
367 return tempFailure == nil;
370 static void wait_until(dispatch_time_t when) {
371 static dispatch_once_t once;
372 static dispatch_semaphore_t never_fires = nil;
373 dispatch_once(&once, ^{
374 never_fires = dispatch_semaphore_create(0);
377 dispatch_semaphore_wait(never_fires, when); // Will always timeout.
380 - (void)waitForSynchronization:(NSArray *)keys handler:(void (^)(NSDictionary *values, NSError *err))handler
384 NSError *err = [NSError errorWithDomain:(NSString *)NSPOSIXErrorDomain code:(NSInteger)ENOPROTOOPT userInfo:nil];
385 secerrorq("%s RETURNING TO securityd: %@ param error; calling handler", kWAIT2MINID, self);
390 secnoticeq("fresh", "%s Requesting freshness", kWAIT2MINID);
392 dispatch_async(_freshParamsQueue, ^{
393 // Hold off (keeping the queue occupied) until we hit the next time we can fresh.
394 wait_until(_nextFreshnessTime);
396 NSError *error = nil;
397 BOOL success = [self AttemptSynchronization:&error];
399 // Advance the next time can can call freshness.
400 const uint64_t delayBeforeCallingAgainInSeconds = 5ull * NSEC_PER_SEC;
401 _nextFreshnessTime = dispatch_time(DISPATCH_TIME_NOW, delayBeforeCallingAgainInSeconds);
403 dispatch_async(dispatch_get_main_queue(), ^{
404 NSDictionary * freshValues = success ? [self copyValues:[NSSet setWithArray:keys]] : @{};
405 handler(freshValues, error);
410 - (void)removeObjectForKey:(NSString *)keyToRemove
412 [[self cloudStore] removeObjectForKey:keyToRemove];
417 secdebug(XPROXYSCOPE, "%@ clearStore", self);
418 NSDictionary *dict = [[self cloudStore] dictionaryRepresentation];
419 NSArray *allKeys = [dict allKeys];
420 NSMutableArray* nullKeys = [NSMutableArray array];
422 _alwaysKeys = [NSMutableSet setWithArray:nullKeys];
423 _firstUnlockKeys = [NSMutableSet setWithArray:nullKeys];
424 _unlockedKeys = [NSMutableSet setWithArray:nullKeys];
425 _keyParameterKeys = @{};
429 [allKeys enumerateObjectsUsingBlock:^(id key, NSUInteger idx, BOOL *stop)
431 secdebug(XPROXYSCOPE, "%@ Clearing value for key %@", self, key);
432 [[self cloudStore] removeObjectForKey:(NSString *)key];
435 [self requestSynchronization:YES];
440 // MARK: ----- KVS key lists -----
445 return [[self cloudStore] objectForKey:key];
448 - (NSDictionary *)getAll
450 return [[self cloudStore] dictionaryRepresentation];
453 - (NSDictionary*) exportKeyInterests
455 return @{ kKeyAlwaysKeys:[_alwaysKeys allObjects],
456 kKeyFirstUnlockKeys:[_firstUnlockKeys allObjects],
457 kKeyUnlockedKeys:[_unlockedKeys allObjects],
460 kKeyKeyParameterKeys: _keyParameterKeys,
461 kKeyMessageKeys : _messageKeys,
462 kKeyCircleKeys : _circleKeys,
464 kKeyPendingKeys:[_pendingKeys allObjects],
465 kKeySyncWithPeersPending:[NSNumber numberWithBool:_syncWithPeersPending],
466 kKeyEnsurePeerRegistration:[NSNumber numberWithBool:_ensurePeerRegistration],
471 - (void) importKeyInterests: (NSDictionary*) interests
473 _alwaysKeys = [NSMutableSet setWithArray: interests[kKeyAlwaysKeys]];
474 _firstUnlockKeys = [NSMutableSet setWithArray: interests[kKeyFirstUnlockKeys]];
475 _unlockedKeys = [NSMutableSet setWithArray: interests[kKeyUnlockedKeys]];
477 _pendingKeys = [NSMutableSet setWithArray: interests[kKeyPendingKeys]];
478 _syncWithPeersPending = [interests[kKeySyncWithPeersPending] boolValue];
479 _ensurePeerRegistration = [interests[kKeyEnsurePeerRegistration] boolValue];
480 _dsid = interests[kKeyDSID];
483 - (NSMutableSet *)copyAllKeys
485 NSMutableSet *allKeys = [NSMutableSet setWithSet: _alwaysKeys];
486 [allKeys unionSet: _firstUnlockKeys];
487 [allKeys unionSet: _unlockedKeys];
491 -(void)registerAtTimeKeys:(NSDictionary*)keyparms
493 NSArray *alwaysArray = [keyparms valueForKey: kKeyAlwaysKeys];
494 NSArray *firstUnlockedKeysArray = [keyparms valueForKey: kKeyFirstUnlockKeys];
495 NSArray *whenUnlockedKeysArray = [keyparms valueForKey: kKeyUnlockedKeys];
498 [_alwaysKeys unionSet: [NSMutableSet setWithArray: alwaysArray]];
499 if(firstUnlockedKeysArray)
500 [_firstUnlockKeys unionSet: [NSMutableSet setWithArray: firstUnlockedKeysArray]];
501 if(whenUnlockedKeysArray)
502 [_unlockedKeys unionSet: [NSMutableSet setWithArray: whenUnlockedKeysArray]];
506 - (void)registerKeys: (NSDictionary*)keys
508 secdebug(XPROXYSCOPE, "registerKeys: keys: %@", keys);
510 NSMutableSet *allOldKeys = [self copyAllKeys];
512 NSDictionary *keyparms = [keys valueForKey: [NSString stringWithUTF8String: kMessageKeyParameter]];
513 NSDictionary *circles = [keys valueForKey: [NSString stringWithUTF8String: kMessageCircle]];
514 NSDictionary *messages = [keys valueForKey: [NSString stringWithUTF8String: kMessageMessage]];
516 _alwaysKeys = [NSMutableSet set];
517 _firstUnlockKeys = [NSMutableSet set];
518 _unlockedKeys = [NSMutableSet set];
520 _keyParameterKeys = keyparms;
521 _circleKeys = circles;
522 _messageKeys = messages;
524 [self registerAtTimeKeys: _keyParameterKeys];
525 [self registerAtTimeKeys: _circleKeys];
526 [self registerAtTimeKeys: _messageKeys];
528 NSMutableSet *allNewKeys = [self copyAllKeys];
530 // Make sure keys we no longer care about are not pending
531 [_pendingKeys intersectSet:allNewKeys];
532 if (_shadowPendingKeys) {
533 [_shadowPendingKeys intersectSet:allNewKeys];
536 // All new keys only is new keys (remove old keys)
537 [allNewKeys minusSet:allOldKeys];
539 // Mark new keys pending, they're new!
540 NSMutableSet *newKeysForCurrentLockState = [self pendKeysAndGetNewlyPended:allNewKeys];
542 [self persistState]; // Before we might call out, save our state so we recover if we crash
544 [self intersectWithCurrentLockState: newKeysForCurrentLockState];
545 // TODO: Don't processPendingKeysForCurrentLockState if none of the new keys have values.
546 if ([newKeysForCurrentLockState count] != 0) {
547 [self processPendingKeysForCurrentLockState];
551 - (NSDictionary *)localNotification:(NSDictionary *)localNotificationDict outFlags:(int64_t *)outFlags
553 __block bool done = false;
554 __block int64_t returnedFlags = 0;
555 __block NSDictionary *responses = NULL;
557 CKDUserInteraction *cui = [CKDUserInteraction sharedInstance];
558 [cui requestShowNotification:localNotificationDict completion:^ bool (CFDictionaryRef userResponses, int64_t flags)
560 responses = [NSDictionary dictionaryWithDictionary:(__bridge NSDictionary *)userResponses];
561 returnedFlags = flags;
562 secdebug(XPROXYSCOPE, "%@ requestShowNotification: dict: %@, flags: %#llx", self, responses, returnedFlags);
567 // TODO: replace with e.g. dispatch calls to wait, or semaphore
572 secdebug(XPROXYSCOPE, "%@ outFlags: %#llx", self, returnedFlags);
573 *outFlags = returnedFlags;
578 - (void)saveToUbiquitousStore
580 [self requestSynchronization:NO];
583 // MARK: ----- Event Handling -----
585 - (void)streamEvent:(xpc_object_t)notification
587 #if (!TARGET_IPHONE_SIMULATOR)
588 const char *notificationName = xpc_dictionary_get_string(notification, "Notification");
589 if (!notificationName) {
590 } else if (strcmp(notificationName, kUserKeybagStateChangeNotification)==0) {
591 return [self keybagStateChange];
592 } else if (strcmp(notificationName, kCloudKeychainStorechangeChangeNotification)==0) {
593 return [self kvsStoreChange];
594 } else if (strcmp(notificationName, kNotifyTokenForceUpdate)==0) {
595 // DEBUG -- Possibly remove in future
596 return [self processAllItems];
598 const char *eventName = xpc_dictionary_get_string(notification, "XPCEventName");
599 char *desc = xpc_copy_description(notification);
600 secnotice("event", "%@ event: %s name: %s desc: %s", self, eventName, notificationName, desc);
606 - (void)iCloudAccountAvailabilityChanged:(NSNotification*)notification
609 Continuing this example, you’d then implement an iCloudAccountAvailabilityChanged: method that would:
611 Call the ubiquityIdentityToken method and store its return value.
612 Compare the new value to the previous value, to find out if the user logged out of their account or
613 logged in to a different account. If the previously-used account is now unavailable, save the current
614 state locally as needed, empty your iCloud-related data caches, and refresh all iCloud-related user interface elements.
616 id previCloudToken = currentiCloudToken;
617 currentiCloudToken = [[NSFileManager defaultManager] ubiquityIdentityToken];
618 if (previCloudToken != currentiCloudToken)
619 secnotice("event", "%@ iCloud account changed!", self);
621 secnotice("event", "%@ %@", self, notification);
624 - (void)cloudChanged:(NSNotification*)notification
627 Posted when the value of one or more keys in the local key-value store
628 changed due to incoming data pushed from iCloud. This notification is
629 sent only upon a change received from iCloud; it is not sent when your
632 The user info dictionary can contain the reason for the notification as
633 well as a list of which values changed, as follows:
635 The value of the NSUbiquitousKeyValueStoreChangeReasonKey key, when
636 present, indicates why the key-value store changed. Its value is one of
637 the constants in "Change Reason Values."
639 The value of the NSUbiquitousKeyValueStoreChangedKeysKey, when present,
640 is an array of strings, each the name of a key whose value changed. The
641 notification object is the NSUbiquitousKeyValueStore object whose contents
644 NSUbiquitousKeyValueStoreInitialSyncChange is only posted if there is any
645 local value that has been overwritten by a distant value. If there is no
646 conflict between the local and the distant values when doing the initial
647 sync (e.g. if the cloud has no data stored or the client has not stored
648 any data yet), you'll never see that notification.
650 NSUbiquitousKeyValueStoreInitialSyncChange implies an initial round trip
651 with server but initial round trip with server does not imply
652 NSUbiquitousKeyValueStoreInitialSyncChange.
654 os_activity_initiate("cloudChanged", OS_ACTIVITY_FLAG_DEFAULT, ^{
655 secdebug(XPROXYSCOPE, "%@ cloudChanged notification: %@", self, notification);
657 NSDictionary *userInfo = [notification userInfo];
658 NSNumber *reason = [userInfo objectForKey:NSUbiquitousKeyValueStoreChangeReasonKey];
659 if (reason) switch ([reason integerValue]) {
660 case NSUbiquitousKeyValueStoreInitialSyncChange:
661 case NSUbiquitousKeyValueStoreServerChange:
663 _seenKVSStoreChange = YES;
664 NSSet *keysChangedInCloud = [NSSet setWithArray:[userInfo objectForKey:NSUbiquitousKeyValueStoreChangedKeysKey]];
666 /* We are saying that we want to try processing a key no matter what,
667 * *if* it has changed in the cloud. */
668 [_pendingKeys minusSet:keysChangedInCloud];
670 NSSet *keysOfInterestThatChanged = [self pendKeysAndGetPendingForCurrentLockState:keysChangedInCloud];
671 NSMutableDictionary *changedValues = [self copyValues:keysOfInterestThatChanged];
672 if ([reason integerValue] == NSUbiquitousKeyValueStoreInitialSyncChange)
673 changedValues[(__bridge NSString*)kSOSKVSInitialSyncKey] = @"true";
675 secnotice("event", "%@ keysChangedInCloud: %@ keysOfInterest: %@", self, [[keysChangedInCloud allObjects] componentsJoinedByString: @" "], [[changedValues allKeys] componentsJoinedByString: @" "]);
676 if ([changedValues count])
677 [self processKeyChangedEvent:changedValues];
680 case NSUbiquitousKeyValueStoreQuotaViolationChange:
681 seccritical("%@ event received NSUbiquitousKeyValueStoreQuotaViolationChange", self);
683 case NSUbiquitousKeyValueStoreAccountChange:
684 // The primary account changed. We do not get this for password changes on the same account
685 secnotice("event", "%@ NSUbiquitousKeyValueStoreAccountChange", self);
686 NSDictionary *changedValues = nil;
688 changedValues = @{ (__bridge NSString*)kSOSKVSAccountChangedKey: _dsid };
690 changedValues = @{ (__bridge NSString*)kSOSKVSAccountChangedKey: @"true" };
692 [self processKeyChangedEvent:changedValues];
698 - (void) doAfterFlush: (dispatch_block_t) block
700 // Flush any pending communication to Securityd.
702 dispatch_async(_calloutQueue, block);
705 - (void) calloutWith: (void(^)(NSSet *pending, bool syncWithPeersPending, bool ensurePeerRegistration, dispatch_queue_t queue, void(^done)(NSSet *handledKeys, bool handledSyncWithPeers, bool handledEnsurePeerRegistration))) callout
707 // In CKDKVSProxy's serial queue
708 dispatch_queue_t ckdkvsproxy_queue = dispatch_get_main_queue();
712 // dispatch_get_global_queue - well-known global concurrent queue
713 // dispatch_get_main_queue - default queue that is bound to the main thread
714 xpc_transaction_begin();
715 dispatch_async(_calloutQueue, ^{
716 __block NSSet *myPending;
717 __block bool mySyncWithPeersPending;
718 __block bool myEnsurePeerRegistration;
719 __block bool wasLocked;
720 dispatch_sync(ckdkvsproxy_queue, ^{
721 myPending = [_pendingKeys copy];
722 mySyncWithPeersPending = _syncWithPeersPending;
723 myEnsurePeerRegistration = _ensurePeerRegistration;
724 wasLocked = _isLocked;
728 secnotice("deaf", ">>>>>>>>>>> _oldInCallout is NO and we're heading in to the callout!");
730 _shadowPendingKeys = [NSMutableSet set];
731 _shadowSyncWithPeersPending = NO;
734 callout(myPending, mySyncWithPeersPending, myEnsurePeerRegistration, ckdkvsproxy_queue, ^(NSSet *handledKeys, bool handledSyncWithPeers, bool handledEnsurePeerRegistration) {
735 secdebug("event", "%@ %s%s before callout handled: %s%s", self, mySyncWithPeersPending ? "S" : "s", myEnsurePeerRegistration ? "E" : "e", handledSyncWithPeers ? "S" : "s", handledEnsurePeerRegistration ? "E" : "e");
737 // In CKDKVSProxy's serial queue
741 // Update ensurePeerRegistration
742 _ensurePeerRegistration = ((myEnsurePeerRegistration && !handledEnsurePeerRegistration) || _shadowEnsurePeerRegistration);
744 _shadowEnsurePeerRegistration = NO;
746 if(_ensurePeerRegistration && !_isLocked)
747 [self doEnsurePeerRegistration];
749 // Update SyncWithPeers stuff.
750 _syncWithPeersPending = ((mySyncWithPeersPending && (!handledSyncWithPeers)) || _shadowSyncWithPeersPending);
752 _shadowSyncWithPeersPending = NO;
753 if (handledSyncWithPeers)
754 _lastSyncTime = dispatch_time(DISPATCH_TIME_NOW, 0);
756 // Update pendingKeys and handle them
757 [_pendingKeys minusSet: handledKeys];
758 bool hadShadowPendingKeys = [_shadowPendingKeys count];
759 // Move away shadownPendingKeys first, because pendKeysAndGetPendingForCurrentLockState
760 // will look at them. See rdar://problem/20733166.
761 NSSet *oldShadowPendingKeys = _shadowPendingKeys;
762 _shadowPendingKeys = nil;
764 NSSet *filteredKeys = [self pendKeysAndGetPendingForCurrentLockState:oldShadowPendingKeys];
766 // Write state to disk
769 // Handle shadow pended stuff
770 if (_syncWithPeersPending && !_isLocked)
771 [self scheduleSyncRequestTimer];
772 /* We don't want to call processKeyChangedEvent if we failed to
773 handle pending keys and the device didn't unlock nor receive
774 any kvs changes while we were in our callout.
775 Doing so will lead to securityd and CloudKeychainProxy
776 talking to each other forever in a tight loop if securityd
777 repeatedly returns an error processing the same message.
778 Instead we leave any old pending keys until the next event. */
779 if (hadShadowPendingKeys || (!_isLocked && wasLocked))
780 [self processKeyChangedEvent:[self copyValues:filteredKeys]];
782 xpc_transaction_end();
787 - (void) sendKeysCallout: (NSSet *(^)(NSSet* pending, NSError** error)) handleKeys {
788 [self calloutWith: ^(NSSet *pending, bool syncWithPeersPending, bool ensurePeerRegistration, dispatch_queue_t queue, void(^done)(NSSet *, bool, bool)) {
789 NSError* error = NULL;
791 NSSet * handled = handleKeys(pending, &error);
793 dispatch_async(queue, ^{
795 secerror("%@ ensurePeerRegistration failed: %@", self, error);
798 done(handled, NO, NO);
803 - (void) doEnsurePeerRegistration
805 [self calloutWith:^(NSSet *pending, bool syncWithPeersPending, bool ensurePeerRegistration, dispatch_queue_t queue, void(^done)(NSSet *, bool, bool)) {
806 CFErrorRef error = NULL;
807 bool handledEnsurePeerRegistration = SOSCCProcessEnsurePeerRegistration(&error);
808 secerror("%@ ensurePeerRegistration called, %@ (%@)", self, handledEnsurePeerRegistration ? @"success" : @"failure", error);
809 dispatch_async(queue, ^{
810 if (!handledEnsurePeerRegistration) {
811 secerror("%@ ensurePeerRegistration failed: %@", self, error);
814 done(nil, NO, handledEnsurePeerRegistration);
815 CFReleaseSafe(error);
820 - (void) doSyncWithAllPeers
822 [self calloutWith:^(NSSet *pending, bool syncWithPeersPending, bool ensurePeerRegistration, dispatch_queue_t queue, void(^done)(NSSet *, bool, bool)) {
823 CFErrorRef error = NULL;
825 SyncWithAllPeersReason reason = SOSCCProcessSyncWithAllPeers(&error);
826 dispatch_async(queue, ^{
827 bool handledSyncWithPeers = NO;
828 if (reason == kSyncWithAllPeersSuccess) {
829 handledSyncWithPeers = YES;
830 secnotice("event", "%@ syncWithAllPeers succeeded", self);
831 } else if (reason == kSyncWithAllPeersLocked) {
832 secnotice("event", "%@ syncWithAllPeers attempted while locked - waiting for unlock", self);
833 handledSyncWithPeers = NO;
834 [self updateIsLocked];
835 } else if (reason == kSyncWithAllPeersOtherFail) {
836 // Pretend we handled syncWithPeers, by pushing out the _lastSyncTime
837 // This will cause us to wait for kMinSyncInterval seconds before
838 // retrying, so we don't spam securityd if sync is failing
839 secerror("%@ syncWithAllPeers %@, rescheduling timer", self, error);
840 _lastSyncTime = dispatch_time(DISPATCH_TIME_NOW, 0);
842 secerror("%@ syncWithAllPeers %@, unknown reason: %d", self, error, reason);
845 done(nil, handledSyncWithPeers, false);
846 CFReleaseSafe(error);
853 secnotice("event", "%@ syncWithPeersPending: %d inCallout: %d isLocked: %d", self, _syncWithPeersPending, _inCallout, _isLocked);
854 _syncTimerScheduled = NO;
855 if(_ensurePeerRegistration){
856 [self doEnsurePeerRegistration];
858 if (_syncWithPeersPending && !_inCallout && !_isLocked){
859 [self doSyncWithAllPeers];
863 - (dispatch_time_t) nextSyncTime
865 dispatch_time_t nextSync = dispatch_time(DISPATCH_TIME_NOW, kMinSyncDelay);
867 // Don't sync again unless we waited at least kMinSyncInterval
869 dispatch_time_t soonest = dispatch_time(_lastSyncTime, kMinSyncInterval);
870 if (nextSync < soonest || _deadline < soonest) {
871 secdebug("timer", "%@ backing off", self);
876 // Don't delay more than kMaxSyncDelay after the first request.
877 if (nextSync > _deadline) {
878 secdebug("timer", "%@ hit deadline", self);
882 // Bump the timer by kMinSyncDelay
883 if (_syncTimerScheduled)
884 secdebug("timer", "%@ bumped timer", self);
886 secdebug("timer", "%@ scheduled timer", self);
891 - (void)scheduleSyncRequestTimer
893 dispatch_source_set_timer(_syncTimer, [self nextSyncTime], DISPATCH_TIME_FOREVER, kSyncTimerLeeway);
894 _syncTimerScheduled = YES;
897 - (void)requestSyncWithAllPeers // secd calling SOSCCSyncWithAllPeers invokes this
900 NSString *desc = [self description];
903 if (!_syncWithPeersPending || (_inCallout && !_shadowSyncWithPeersPending))
904 _deadline = dispatch_time(DISPATCH_TIME_NOW, kMaxSyncDelay);
906 if (!_syncWithPeersPending) {
907 _syncWithPeersPending = YES;
912 _shadowSyncWithPeersPending = YES;
914 [self scheduleSyncRequestTimer];
916 secdebug("event", "%@ %@", desc, self);
919 - (void)requestEnsurePeerRegistration // secd calling SOSCCSyncWithAllPeers invokes this
922 NSString *desc = [self description];
926 _shadowEnsurePeerRegistration = YES;
928 _ensurePeerRegistration = YES;
930 [self doEnsurePeerRegistration];
935 secdebug("event", "%@ %@", desc, self);
939 - (BOOL) updateUnlockedSinceBoot
941 CFErrorRef aksError = NULL;
942 if (!SecAKSGetHasBeenUnlocked(&_unlockedSinceBoot, &aksError)) {
943 secerror("%@ Got error from SecAKSGetHasBeenUnlocked: %@", self, aksError);
944 CFReleaseSafe(aksError);
950 - (BOOL) updateIsLocked
952 CFErrorRef aksError = NULL;
953 if (!SecAKSGetIsLocked(&_isLocked, &aksError)) {
954 secerror("%@ Got error querying lock state: %@", self, aksError);
955 CFReleaseSafe(aksError);
959 _unlockedSinceBoot = YES;
963 - (void) keybagStateChange
965 os_activity_initiate("keybagStateChanged", OS_ACTIVITY_FLAG_DEFAULT, ^{
966 BOOL wasLocked = _isLocked;
967 if ([self updateIsLocked]) {
968 if (wasLocked == _isLocked)
969 secdebug("event", "%@ still %s ignoring", self, _isLocked ? "locked" : "unlocked");
971 [self keybagDidLock];
973 [self keybagDidUnlock];
978 - (void) keybagDidLock
980 secnotice("event", "%@", self);
983 - (void) keybagDidUnlock
985 secnotice("event", "%@", self);
986 if (_ensurePeerRegistration) {
987 [self doEnsurePeerRegistration];
990 // First send changed keys to securityd so it can proccess updates
991 [self processPendingKeysForCurrentLockState];
993 // Then, tickle securityd to perform a sync if needed.
994 if (_syncWithPeersPending && !_syncTimerScheduled) {
995 [self doSyncWithAllPeers];
999 - (void) kvsStoreChange {
1000 os_activity_initiate("kvsStoreChange", OS_ACTIVITY_FLAG_DEFAULT, ^{
1001 if (!_seenKVSStoreChange) {
1002 _seenKVSStoreChange = YES; // Only do this once
1003 secnotice("event", "%@ received darwin notification before first NSNotification", self);
1004 // TODO This might not be needed if we always get the NSNotification
1005 // deleived even if we were launched due to a kvsStoreChange
1006 // Send all keys for current lock state to securityd so it can proccess them
1007 [self pendKeysAndGetNewlyPended: [self copyAllKeys]];
1008 [self processPendingKeysForCurrentLockState];
1010 secdebug("event", "%@ ignored, waiting for NSNotification", self);
1016 // MARK: ----- Key Filtering -----
1019 - (NSSet*) keysForCurrentLockState
1021 secdebug("filtering", "%@ Filtering: unlockedSinceBoot: %d\n unlocked: %d\n, keysOfInterest: <%@>", self, (int) _unlockedSinceBoot, (int) !_isLocked, [SOSPersistentState dictionaryDescription: [self exportKeyInterests]]);
1023 NSMutableSet *currentStateKeys = [NSMutableSet setWithSet: _alwaysKeys];
1024 if (_unlockedSinceBoot)
1025 [currentStateKeys unionSet: _firstUnlockKeys];
1028 [currentStateKeys unionSet: _unlockedKeys];
1030 return currentStateKeys;
1033 - (NSMutableSet*) pendKeysAndGetNewlyPended: (NSSet*) keysToPend
1035 NSMutableSet *newlyPendedKeys = [keysToPend mutableCopy];
1036 [newlyPendedKeys minusSet: _pendingKeys];
1037 if (_shadowPendingKeys) {
1038 [newlyPendedKeys minusSet: _shadowPendingKeys];
1041 [_pendingKeys unionSet:keysToPend];
1042 if (_shadowPendingKeys) {
1043 [_shadowPendingKeys unionSet:keysToPend];
1046 return newlyPendedKeys;
1049 - (void) intersectWithCurrentLockState: (NSMutableSet*) set
1051 [set intersectSet: [self keysForCurrentLockState]];
1054 - (NSMutableSet*) pendingKeysForCurrentLockState
1056 NSMutableSet * result = [_pendingKeys mutableCopy];
1057 [self intersectWithCurrentLockState:result];
1061 - (NSMutableSet*) pendKeysAndGetPendingForCurrentLockState: (NSSet*) startingSet
1063 [self pendKeysAndGetNewlyPended: startingSet];
1065 return [self pendingKeysForCurrentLockState];
1068 - (NSMutableDictionary *)copyValues:(NSSet*)keysOfInterest
1070 // Grab values from KVS.
1071 NSUbiquitousKeyValueStore *store = [self cloudStore];
1072 NSMutableDictionary *changedValues = [NSMutableDictionary dictionaryWithCapacity:0];
1073 [keysOfInterest enumerateObjectsUsingBlock:^(id obj, BOOL *stop)
1075 NSString* key = (NSString*) obj;
1076 id objval = [store objectForKey:key];
1077 if (!objval) objval = [NSNull null];
1079 [changedValues setObject:objval forKey:key];
1080 secdebug(XPROXYSCOPE, "%@ storeChanged updated value for %@", self, key);
1082 return changedValues;
1086 During RegisterKeys, separate keys-of-interest into three disjoint sets:
1087 - keys that we always want to be notified about; this means we can get the
1089 - keys that require the device to have been unlocked at least once
1090 - keys that require the device to be unlocked now
1092 Typically, the sets of keys will be:
1098 The caller is responsible for making sure that the keys in e.g. alwaysKeys are
1099 values that can be handled at any time (that is, not when unlocked)
1101 Each time we get a notification from ubiquity that keys have changed, we need to
1102 see if anything of interest changed. If we don't care, then done.
1104 For each key-of-interest that changed, we either notify the client that things
1105 changed, or add it to a pendingNotifications list. If the notification to the
1106 client fails, also add it to the pendingNotifications list. This pending list
1107 should be written to persistent storage and consulted any time we either get an
1108 item changed notification, or get a stream event signalling a change in lock state.
1110 We can notify the client either through XPC if a connection is set up, or call a
1111 routine in securityd to launch it.
1115 - (void)processKeyChangedEvent:(NSDictionary *)changedValues
1117 NSMutableDictionary* filtered = [NSMutableDictionary dictionary];
1119 NSMutableArray* nullKeys = [NSMutableArray array];
1120 // Remove nulls because we don't want them in securityd.
1121 [changedValues enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
1122 if (obj == [NSNull null])
1123 [nullKeys addObject:key];
1125 filtered[key] = obj;
1128 if ([nullKeys count])
1129 [_pendingKeys minusSet: [NSSet setWithArray: nullKeys]];
1131 if([filtered count] != 0 ){
1132 [self sendKeysCallout:^NSSet *(NSSet *pending, NSError** error) {
1133 CFErrorRef cf_error = NULL;
1134 NSArray* handledMessage = (__bridge_transfer NSArray*) _SecKeychainSyncUpdateMessage((__bridge CFDictionaryRef)filtered, &cf_error);
1135 NSError *updateError = (__bridge_transfer NSError*)cf_error;
1137 *error = updateError;
1139 secnoticeq("keytrace", "%@ misc handled: %@ null: %@ pending: %@", self,
1140 [handledMessage componentsJoinedByString: @" "],
1141 [nullKeys componentsJoinedByString: @" "],
1142 [[_pendingKeys allObjects] componentsJoinedByString: @" "]);
1144 return handledMessage ? [NSSet setWithArray: handledMessage] : nil;
1147 secnoticeq("keytrace", "%@ null: %@ pending: %@", self,
1148 [nullKeys componentsJoinedByString: @" "],
1149 [[_pendingKeys allObjects] componentsJoinedByString: @" "]);
1153 - (void) processPendingKeysForCurrentLockState
1155 [self processKeyChangedEvent: [self copyValues: [self pendingKeysForCurrentLockState]]];