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.
328 - (BOOL) AttemptSynchronization:(NSError **)failure
331 __block bool tryAgain = false;
332 __block NSError *tempFailure = NULL;
334 NSUbiquitousKeyValueStore * store = [self cloudStore];
336 dispatch_semaphore_t freshSemaphore = dispatch_semaphore_create(0);
337 secnoticeq("fresh", "%s CALLING OUT TO syncdefaultsd SWCH: %@", kWAIT2MINID, self);
338 [store synchronizeWithCompletionHandler:^(NSError *error) {
341 secerrorq("%s RETURNING FROM syncdefaultsd SWCH: %@: %@", kWAIT2MINID, self, error);
342 if(CFErrorGetCode((__bridge CFErrorRef) error) == UPDATE_RESUBMIT){
346 secnoticeq("fresh", "%s RETURNING FROM syncdefaultsd SWCH: %@", kWAIT2MINID, self);
348 [store synchronize]; // Per olivier in <rdar://problem/13412631>, sync before getting values
349 secnoticeq("fresh", "%s RETURNING FROM syncdefaultsd SYNC: %@", kWAIT2MINID, self);
351 dispatch_semaphore_signal(freshSemaphore);
353 dispatch_semaphore_wait(freshSemaphore, DISPATCH_TIME_FOREVER);
356 *failure = tempFailure;
361 - (void)waitForSynchronization:(NSArray *)keys handler:(void (^)(NSDictionary *values, NSError *err))handler
365 NSError *err = [NSError errorWithDomain:(NSString *)NSPOSIXErrorDomain code:(NSInteger)ENOPROTOOPT userInfo:nil];
366 secerrorq("%s RETURNING TO securityd: %@ param error; calling handler", kWAIT2MINID, self);
371 secnoticeq("fresh", "%s Requesting freshness", kWAIT2MINID);
372 dispatch_async(_freshParamsQueue, ^{
374 __block dispatch_time_t next_time = _nextFreshnessTime;
375 __block NSError *failure = nil;
377 dispatch_time_t now = dispatch_time(DISPATCH_TIME_NOW, 0);
378 if (now > next_time) {
379 bool attemptSynchronizationAgain = false;
380 int numberOfAttempts = 0;
382 attemptSynchronizationAgain = [ self AttemptSynchronization:&failure ];
385 } while (attemptSynchronizationAgain && numberOfAttempts <= 10);
386 if(numberOfAttempts > 10)
388 secerrorq("%s Number of attempts to request freshness exceeded", kWAIT2MINID);
390 const uint64_t delayBeforeCallingAgainInSeconds = 5ull * NSEC_PER_SEC;
391 next_time = dispatch_time(DISPATCH_TIME_NOW, delayBeforeCallingAgainInSeconds);
394 dispatch_async(dispatch_get_main_queue(), ^{
395 _nextFreshnessTime = next_time;
398 handler(@{}, failure);
400 handler([self copyValues:[NSSet setWithArray:keys]], NULL);
405 - (void)removeObjectForKey:(NSString *)keyToRemove
407 [[self cloudStore] removeObjectForKey:keyToRemove];
412 secdebug(XPROXYSCOPE, "%@ clearStore", self);
413 NSDictionary *dict = [[self cloudStore] dictionaryRepresentation];
414 NSArray *allKeys = [dict allKeys];
415 NSMutableArray* nullKeys = [NSMutableArray array];
417 _alwaysKeys = [NSMutableSet setWithArray:nullKeys];
418 _firstUnlockKeys = [NSMutableSet setWithArray:nullKeys];
419 _unlockedKeys = [NSMutableSet setWithArray:nullKeys];
420 _keyParameterKeys = @{};
424 [allKeys enumerateObjectsUsingBlock:^(id key, NSUInteger idx, BOOL *stop)
426 secdebug(XPROXYSCOPE, "%@ Clearing value for key %@", self, key);
427 [[self cloudStore] removeObjectForKey:(NSString *)key];
430 [self requestSynchronization:YES];
435 // MARK: ----- KVS key lists -----
440 return [[self cloudStore] objectForKey:key];
443 - (NSDictionary *)getAll
445 return [[self cloudStore] dictionaryRepresentation];
448 - (NSDictionary*) exportKeyInterests
450 return @{ kKeyAlwaysKeys:[_alwaysKeys allObjects],
451 kKeyFirstUnlockKeys:[_firstUnlockKeys allObjects],
452 kKeyUnlockedKeys:[_unlockedKeys allObjects],
455 kKeyKeyParameterKeys: _keyParameterKeys,
456 kKeyMessageKeys : _messageKeys,
457 kKeyCircleKeys : _circleKeys,
459 kKeyPendingKeys:[_pendingKeys allObjects],
460 kKeySyncWithPeersPending:[NSNumber numberWithBool:_syncWithPeersPending],
461 kKeyEnsurePeerRegistration:[NSNumber numberWithBool:_ensurePeerRegistration],
466 - (void) importKeyInterests: (NSDictionary*) interests
468 _alwaysKeys = [NSMutableSet setWithArray: interests[kKeyAlwaysKeys]];
469 _firstUnlockKeys = [NSMutableSet setWithArray: interests[kKeyFirstUnlockKeys]];
470 _unlockedKeys = [NSMutableSet setWithArray: interests[kKeyUnlockedKeys]];
472 _pendingKeys = [NSMutableSet setWithArray: interests[kKeyPendingKeys]];
473 _syncWithPeersPending = [interests[kKeySyncWithPeersPending] boolValue];
474 _ensurePeerRegistration = [interests[kKeyEnsurePeerRegistration] boolValue];
475 _dsid = interests[kKeyDSID];
478 - (NSMutableSet *)copyAllKeys
480 NSMutableSet *allKeys = [NSMutableSet setWithSet: _alwaysKeys];
481 [allKeys unionSet: _firstUnlockKeys];
482 [allKeys unionSet: _unlockedKeys];
486 -(void)registerAtTimeKeys:(NSDictionary*)keyparms
488 NSArray *alwaysArray = [keyparms valueForKey: kKeyAlwaysKeys];
489 NSArray *firstUnlockedKeysArray = [keyparms valueForKey: kKeyFirstUnlockKeys];
490 NSArray *whenUnlockedKeysArray = [keyparms valueForKey: kKeyUnlockedKeys];
493 [_alwaysKeys unionSet: [NSMutableSet setWithArray: alwaysArray]];
494 if(firstUnlockedKeysArray)
495 [_firstUnlockKeys unionSet: [NSMutableSet setWithArray: firstUnlockedKeysArray]];
496 if(whenUnlockedKeysArray)
497 [_unlockedKeys unionSet: [NSMutableSet setWithArray: whenUnlockedKeysArray]];
501 - (void)registerKeys: (NSDictionary*)keys
503 secdebug(XPROXYSCOPE, "registerKeys: keys: %@", keys);
505 NSMutableSet *allOldKeys = [self copyAllKeys];
507 NSDictionary *keyparms = [keys valueForKey: [NSString stringWithUTF8String: kMessageKeyParameter]];
508 NSDictionary *circles = [keys valueForKey: [NSString stringWithUTF8String: kMessageCircle]];
509 NSDictionary *messages = [keys valueForKey: [NSString stringWithUTF8String: kMessageMessage]];
511 _alwaysKeys = [NSMutableSet set];
512 _firstUnlockKeys = [NSMutableSet set];
513 _unlockedKeys = [NSMutableSet set];
515 _keyParameterKeys = keyparms;
516 _circleKeys = circles;
517 _messageKeys = messages;
519 [self registerAtTimeKeys: _keyParameterKeys];
520 [self registerAtTimeKeys: _circleKeys];
521 [self registerAtTimeKeys: _messageKeys];
523 NSMutableSet *allNewKeys = [self copyAllKeys];
525 // Make sure keys we no longer care about are not pending
526 [_pendingKeys intersectSet:allNewKeys];
527 if (_shadowPendingKeys) {
528 [_shadowPendingKeys intersectSet:allNewKeys];
531 // All new keys only is new keys (remove old keys)
532 [allNewKeys minusSet:allOldKeys];
534 // Mark new keys pending, they're new!
535 NSMutableSet *newKeysForCurrentLockState = [self pendKeysAndGetNewlyPended:allNewKeys];
537 [self persistState]; // Before we might call out, save our state so we recover if we crash
539 [self intersectWithCurrentLockState: newKeysForCurrentLockState];
540 // TODO: Don't processPendingKeysForCurrentLockState if none of the new keys have values.
541 if ([newKeysForCurrentLockState count] != 0) {
542 [self processPendingKeysForCurrentLockState];
546 - (NSDictionary *)localNotification:(NSDictionary *)localNotificationDict outFlags:(int64_t *)outFlags
548 __block bool done = false;
549 __block int64_t returnedFlags = 0;
550 __block NSDictionary *responses = NULL;
552 CKDUserInteraction *cui = [CKDUserInteraction sharedInstance];
553 [cui requestShowNotification:localNotificationDict completion:^ bool (CFDictionaryRef userResponses, int64_t flags)
555 responses = [NSDictionary dictionaryWithDictionary:(__bridge NSDictionary *)userResponses];
556 returnedFlags = flags;
557 secdebug(XPROXYSCOPE, "%@ requestShowNotification: dict: %@, flags: %#llx", self, responses, returnedFlags);
562 // TODO: replace with e.g. dispatch calls to wait, or semaphore
567 secdebug(XPROXYSCOPE, "%@ outFlags: %#llx", self, returnedFlags);
568 *outFlags = returnedFlags;
573 - (void)saveToUbiquitousStore
575 [self requestSynchronization:NO];
578 // MARK: ----- Event Handling -----
580 - (void)streamEvent:(xpc_object_t)notification
582 #if (!TARGET_IPHONE_SIMULATOR)
583 const char *notificationName = xpc_dictionary_get_string(notification, "Notification");
584 if (!notificationName) {
585 } else if (strcmp(notificationName, kUserKeybagStateChangeNotification)==0) {
586 return [self keybagStateChange];
587 } else if (strcmp(notificationName, kCloudKeychainStorechangeChangeNotification)==0) {
588 return [self kvsStoreChange];
589 } else if (strcmp(notificationName, kNotifyTokenForceUpdate)==0) {
590 // DEBUG -- Possibly remove in future
591 return [self processAllItems];
593 const char *eventName = xpc_dictionary_get_string(notification, "XPCEventName");
594 char *desc = xpc_copy_description(notification);
595 secnotice("event", "%@ event: %s name: %s desc: %s", self, eventName, notificationName, desc);
601 - (void)iCloudAccountAvailabilityChanged:(NSNotification*)notification
604 Continuing this example, you’d then implement an iCloudAccountAvailabilityChanged: method that would:
606 Call the ubiquityIdentityToken method and store its return value.
607 Compare the new value to the previous value, to find out if the user logged out of their account or
608 logged in to a different account. If the previously-used account is now unavailable, save the current
609 state locally as needed, empty your iCloud-related data caches, and refresh all iCloud-related user interface elements.
611 id previCloudToken = currentiCloudToken;
612 currentiCloudToken = [[NSFileManager defaultManager] ubiquityIdentityToken];
613 if (previCloudToken != currentiCloudToken)
614 secnotice("event", "%@ iCloud account changed!", self);
616 secnotice("event", "%@ %@", self, notification);
619 - (void)cloudChanged:(NSNotification*)notification
622 Posted when the value of one or more keys in the local key-value store
623 changed due to incoming data pushed from iCloud. This notification is
624 sent only upon a change received from iCloud; it is not sent when your
627 The user info dictionary can contain the reason for the notification as
628 well as a list of which values changed, as follows:
630 The value of the NSUbiquitousKeyValueStoreChangeReasonKey key, when
631 present, indicates why the key-value store changed. Its value is one of
632 the constants in "Change Reason Values."
634 The value of the NSUbiquitousKeyValueStoreChangedKeysKey, when present,
635 is an array of strings, each the name of a key whose value changed. The
636 notification object is the NSUbiquitousKeyValueStore object whose contents
639 NSUbiquitousKeyValueStoreInitialSyncChange is only posted if there is any
640 local value that has been overwritten by a distant value. If there is no
641 conflict between the local and the distant values when doing the initial
642 sync (e.g. if the cloud has no data stored or the client has not stored
643 any data yet), you'll never see that notification.
645 NSUbiquitousKeyValueStoreInitialSyncChange implies an initial round trip
646 with server but initial round trip with server does not imply
647 NSUbiquitousKeyValueStoreInitialSyncChange.
649 os_activity_initiate("cloudChanged", OS_ACTIVITY_FLAG_DEFAULT, ^{
650 secdebug(XPROXYSCOPE, "%@ cloudChanged notification: %@", self, notification);
652 NSDictionary *userInfo = [notification userInfo];
653 NSNumber *reason = [userInfo objectForKey:NSUbiquitousKeyValueStoreChangeReasonKey];
654 if (reason) switch ([reason integerValue]) {
655 case NSUbiquitousKeyValueStoreInitialSyncChange:
656 case NSUbiquitousKeyValueStoreServerChange:
658 _seenKVSStoreChange = YES;
659 NSSet *keysChangedInCloud = [NSSet setWithArray:[userInfo objectForKey:NSUbiquitousKeyValueStoreChangedKeysKey]];
661 /* We are saying that we want to try processing a key no matter what,
662 * *if* it has changed in the cloud. */
663 [_pendingKeys minusSet:keysChangedInCloud];
665 NSSet *keysOfInterestThatChanged = [self pendKeysAndGetPendingForCurrentLockState:keysChangedInCloud];
666 NSMutableDictionary *changedValues = [self copyValues:keysOfInterestThatChanged];
667 if ([reason integerValue] == NSUbiquitousKeyValueStoreInitialSyncChange)
668 changedValues[(__bridge NSString*)kSOSKVSInitialSyncKey] = @"true";
670 secnotice("event", "%@ keysChangedInCloud: %@ keysOfInterest: %@", self, [[keysChangedInCloud allObjects] componentsJoinedByString: @" "], [[changedValues allKeys] componentsJoinedByString: @" "]);
671 if ([changedValues count])
672 [self processKeyChangedEvent:changedValues];
675 case NSUbiquitousKeyValueStoreQuotaViolationChange:
676 seccritical("%@ event received NSUbiquitousKeyValueStoreQuotaViolationChange", self);
678 case NSUbiquitousKeyValueStoreAccountChange:
679 // The primary account changed. We do not get this for password changes on the same account
680 secnotice("event", "%@ NSUbiquitousKeyValueStoreAccountChange", self);
681 NSDictionary *changedValues = nil;
683 changedValues = @{ (__bridge NSString*)kSOSKVSAccountChangedKey: _dsid };
685 changedValues = @{ (__bridge NSString*)kSOSKVSAccountChangedKey: @"true" };
687 [self processKeyChangedEvent:changedValues];
693 - (void) doAfterFlush: (dispatch_block_t) block
695 // Flush any pending communication to Securityd.
697 dispatch_async(_calloutQueue, block);
700 - (void) calloutWith: (void(^)(NSSet *pending, bool syncWithPeersPending, bool ensurePeerRegistration, dispatch_queue_t queue, void(^done)(NSSet *handledKeys, bool handledSyncWithPeers, bool handledEnsurePeerRegistration))) callout
702 // In CKDKVSProxy's serial queue
703 dispatch_queue_t ckdkvsproxy_queue = dispatch_get_main_queue();
707 // dispatch_get_global_queue - well-known global concurrent queue
708 // dispatch_get_main_queue - default queue that is bound to the main thread
709 xpc_transaction_begin();
710 dispatch_async(_calloutQueue, ^{
711 __block NSSet *myPending;
712 __block bool mySyncWithPeersPending;
713 __block bool myEnsurePeerRegistration;
714 __block bool wasLocked;
715 dispatch_sync(ckdkvsproxy_queue, ^{
716 myPending = [_pendingKeys copy];
717 mySyncWithPeersPending = _syncWithPeersPending;
718 myEnsurePeerRegistration = _ensurePeerRegistration;
719 wasLocked = _isLocked;
723 secnotice("deaf", ">>>>>>>>>>> _oldInCallout is NO and we're heading in to the callout!");
725 _shadowPendingKeys = [NSMutableSet set];
726 _shadowSyncWithPeersPending = NO;
729 callout(myPending, mySyncWithPeersPending, myEnsurePeerRegistration, ckdkvsproxy_queue, ^(NSSet *handledKeys, bool handledSyncWithPeers, bool handledEnsurePeerRegistration) {
730 secdebug("event", "%@ %s%s before callout handled: %s%s", self, mySyncWithPeersPending ? "S" : "s", myEnsurePeerRegistration ? "E" : "e", handledSyncWithPeers ? "S" : "s", handledEnsurePeerRegistration ? "E" : "e");
732 // In CKDKVSProxy's serial queue
736 // Update ensurePeerRegistration
737 _ensurePeerRegistration = ((myEnsurePeerRegistration && !handledEnsurePeerRegistration) || _shadowEnsurePeerRegistration);
739 _shadowEnsurePeerRegistration = NO;
741 if(_ensurePeerRegistration && !_isLocked)
742 [self doEnsurePeerRegistration];
744 // Update SyncWithPeers stuff.
745 _syncWithPeersPending = ((mySyncWithPeersPending && (!handledSyncWithPeers)) || _shadowSyncWithPeersPending);
747 _shadowSyncWithPeersPending = NO;
748 if (handledSyncWithPeers)
749 _lastSyncTime = dispatch_time(DISPATCH_TIME_NOW, 0);
751 // Update pendingKeys and handle them
752 [_pendingKeys minusSet: handledKeys];
753 bool hadShadowPendingKeys = [_shadowPendingKeys count];
754 // Move away shadownPendingKeys first, because pendKeysAndGetPendingForCurrentLockState
755 // will look at them. See rdar://problem/20733166.
756 NSSet *oldShadowPendingKeys = _shadowPendingKeys;
757 _shadowPendingKeys = nil;
759 NSSet *filteredKeys = [self pendKeysAndGetPendingForCurrentLockState:oldShadowPendingKeys];
761 // Write state to disk
764 // Handle shadow pended stuff
765 if (_syncWithPeersPending && !_isLocked)
766 [self scheduleSyncRequestTimer];
767 /* We don't want to call processKeyChangedEvent if we failed to
768 handle pending keys and the device didn't unlock nor receive
769 any kvs changes while we were in our callout.
770 Doing so will lead to securityd and CloudKeychainProxy
771 talking to each other forever in a tight loop if securityd
772 repeatedly returns an error processing the same message.
773 Instead we leave any old pending keys until the next event. */
774 if (hadShadowPendingKeys || (!_isLocked && wasLocked))
775 [self processKeyChangedEvent:[self copyValues:filteredKeys]];
777 xpc_transaction_end();
782 - (void) sendKeysCallout: (NSSet *(^)(NSSet* pending, NSError** error)) handleKeys {
783 [self calloutWith: ^(NSSet *pending, bool syncWithPeersPending, bool ensurePeerRegistration, dispatch_queue_t queue, void(^done)(NSSet *, bool, bool)) {
784 NSError* error = NULL;
786 NSSet * handled = handleKeys(pending, &error);
788 dispatch_async(queue, ^{
790 secerror("%@ ensurePeerRegistration failed: %@", self, error);
793 done(handled, NO, NO);
798 - (void) doEnsurePeerRegistration
800 [self calloutWith:^(NSSet *pending, bool syncWithPeersPending, bool ensurePeerRegistration, dispatch_queue_t queue, void(^done)(NSSet *, bool, bool)) {
801 CFErrorRef error = NULL;
802 bool handledEnsurePeerRegistration = SOSCCProcessEnsurePeerRegistration(&error);
803 secerror("%@ ensurePeerRegistration called, %@ (%@)", self, handledEnsurePeerRegistration ? @"success" : @"failure", error);
804 dispatch_async(queue, ^{
805 if (!handledEnsurePeerRegistration) {
806 secerror("%@ ensurePeerRegistration failed: %@", self, error);
809 done(nil, NO, handledEnsurePeerRegistration);
810 CFReleaseSafe(error);
815 - (void) doSyncWithAllPeers
817 [self calloutWith:^(NSSet *pending, bool syncWithPeersPending, bool ensurePeerRegistration, dispatch_queue_t queue, void(^done)(NSSet *, bool, bool)) {
818 CFErrorRef error = NULL;
820 SyncWithAllPeersReason reason = SOSCCProcessSyncWithAllPeers(&error);
821 dispatch_async(queue, ^{
822 bool handledSyncWithPeers = NO;
823 if (reason == kSyncWithAllPeersSuccess) {
824 handledSyncWithPeers = YES;
825 secnotice("event", "%@ syncWithAllPeers succeeded", self);
826 } else if (reason == kSyncWithAllPeersLocked) {
827 secnotice("event", "%@ syncWithAllPeers attempted while locked - waiting for unlock", self);
828 handledSyncWithPeers = NO;
829 [self updateIsLocked];
830 } else if (reason == kSyncWithAllPeersOtherFail) {
831 // Pretend we handled syncWithPeers, by pushing out the _lastSyncTime
832 // This will cause us to wait for kMinSyncInterval seconds before
833 // retrying, so we don't spam securityd if sync is failing
834 secerror("%@ syncWithAllPeers %@, rescheduling timer", self, error);
835 _lastSyncTime = dispatch_time(DISPATCH_TIME_NOW, 0);
837 secerror("%@ syncWithAllPeers %@, unknown reason: %d", self, error, reason);
840 done(nil, handledSyncWithPeers, false);
841 CFReleaseSafe(error);
848 secnotice("event", "%@ syncWithPeersPending: %d inCallout: %d isLocked: %d", self, _syncWithPeersPending, _inCallout, _isLocked);
849 _syncTimerScheduled = NO;
850 if(_ensurePeerRegistration){
851 [self doEnsurePeerRegistration];
853 if (_syncWithPeersPending && !_inCallout && !_isLocked){
854 [self doSyncWithAllPeers];
858 - (dispatch_time_t) nextSyncTime
860 dispatch_time_t nextSync = dispatch_time(DISPATCH_TIME_NOW, kMinSyncDelay);
862 // Don't sync again unless we waited at least kMinSyncInterval
864 dispatch_time_t soonest = dispatch_time(_lastSyncTime, kMinSyncInterval);
865 if (nextSync < soonest || _deadline < soonest) {
866 secdebug("timer", "%@ backing off", self);
871 // Don't delay more than kMaxSyncDelay after the first request.
872 if (nextSync > _deadline) {
873 secdebug("timer", "%@ hit deadline", self);
877 // Bump the timer by kMinSyncDelay
878 if (_syncTimerScheduled)
879 secdebug("timer", "%@ bumped timer", self);
881 secdebug("timer", "%@ scheduled timer", self);
886 - (void)scheduleSyncRequestTimer
888 dispatch_source_set_timer(_syncTimer, [self nextSyncTime], DISPATCH_TIME_FOREVER, kSyncTimerLeeway);
889 _syncTimerScheduled = YES;
892 - (void)requestSyncWithAllPeers // secd calling SOSCCSyncWithAllPeers invokes this
895 NSString *desc = [self description];
898 if (!_syncWithPeersPending || (_inCallout && !_shadowSyncWithPeersPending))
899 _deadline = dispatch_time(DISPATCH_TIME_NOW, kMaxSyncDelay);
901 if (!_syncWithPeersPending) {
902 _syncWithPeersPending = YES;
907 _shadowSyncWithPeersPending = YES;
909 [self scheduleSyncRequestTimer];
911 secdebug("event", "%@ %@", desc, self);
914 - (void)requestEnsurePeerRegistration // secd calling SOSCCSyncWithAllPeers invokes this
917 NSString *desc = [self description];
921 _shadowEnsurePeerRegistration = YES;
923 _ensurePeerRegistration = YES;
925 [self doEnsurePeerRegistration];
930 secdebug("event", "%@ %@", desc, self);
934 - (BOOL) updateUnlockedSinceBoot
936 CFErrorRef aksError = NULL;
937 if (!SecAKSGetHasBeenUnlocked(&_unlockedSinceBoot, &aksError)) {
938 secerror("%@ Got error from SecAKSGetHasBeenUnlocked: %@", self, aksError);
939 CFReleaseSafe(aksError);
945 - (BOOL) updateIsLocked
947 CFErrorRef aksError = NULL;
948 if (!SecAKSGetIsLocked(&_isLocked, &aksError)) {
949 secerror("%@ Got error querying lock state: %@", self, aksError);
950 CFReleaseSafe(aksError);
954 _unlockedSinceBoot = YES;
958 - (void) keybagStateChange
960 os_activity_initiate("keybagStateChanged", OS_ACTIVITY_FLAG_DEFAULT, ^{
961 BOOL wasLocked = _isLocked;
962 if ([self updateIsLocked]) {
963 if (wasLocked == _isLocked)
964 secdebug("event", "%@ still %s ignoring", self, _isLocked ? "locked" : "unlocked");
966 [self keybagDidLock];
968 [self keybagDidUnlock];
973 - (void) keybagDidLock
975 secnotice("event", "%@", self);
978 - (void) keybagDidUnlock
980 secnotice("event", "%@", self);
981 if (_ensurePeerRegistration) {
982 [self doEnsurePeerRegistration];
985 // First send changed keys to securityd so it can proccess updates
986 [self processPendingKeysForCurrentLockState];
988 // Then, tickle securityd to perform a sync if needed.
989 if (_syncWithPeersPending && !_syncTimerScheduled) {
990 [self doSyncWithAllPeers];
994 - (void) kvsStoreChange {
995 os_activity_initiate("kvsStoreChange", OS_ACTIVITY_FLAG_DEFAULT, ^{
996 if (!_seenKVSStoreChange) {
997 _seenKVSStoreChange = YES; // Only do this once
998 secnotice("event", "%@ received darwin notification before first NSNotification", self);
999 // TODO This might not be needed if we always get the NSNotification
1000 // deleived even if we were launched due to a kvsStoreChange
1001 // Send all keys for current lock state to securityd so it can proccess them
1002 [self pendKeysAndGetNewlyPended: [self copyAllKeys]];
1003 [self processPendingKeysForCurrentLockState];
1005 secdebug("event", "%@ ignored, waiting for NSNotification", self);
1011 // MARK: ----- Key Filtering -----
1014 - (NSSet*) keysForCurrentLockState
1016 secdebug("filtering", "%@ Filtering: unlockedSinceBoot: %d\n unlocked: %d\n, keysOfInterest: <%@>", self, (int) _unlockedSinceBoot, (int) !_isLocked, [SOSPersistentState dictionaryDescription: [self exportKeyInterests]]);
1018 NSMutableSet *currentStateKeys = [NSMutableSet setWithSet: _alwaysKeys];
1019 if (_unlockedSinceBoot)
1020 [currentStateKeys unionSet: _firstUnlockKeys];
1023 [currentStateKeys unionSet: _unlockedKeys];
1025 return currentStateKeys;
1028 - (NSMutableSet*) pendKeysAndGetNewlyPended: (NSSet*) keysToPend
1030 NSMutableSet *newlyPendedKeys = [keysToPend mutableCopy];
1031 [newlyPendedKeys minusSet: _pendingKeys];
1032 if (_shadowPendingKeys) {
1033 [newlyPendedKeys minusSet: _shadowPendingKeys];
1036 [_pendingKeys unionSet:keysToPend];
1037 if (_shadowPendingKeys) {
1038 [_shadowPendingKeys unionSet:keysToPend];
1041 return newlyPendedKeys;
1044 - (void) intersectWithCurrentLockState: (NSMutableSet*) set
1046 [set intersectSet: [self keysForCurrentLockState]];
1049 - (NSMutableSet*) pendingKeysForCurrentLockState
1051 NSMutableSet * result = [_pendingKeys mutableCopy];
1052 [self intersectWithCurrentLockState:result];
1056 - (NSMutableSet*) pendKeysAndGetPendingForCurrentLockState: (NSSet*) startingSet
1058 [self pendKeysAndGetNewlyPended: startingSet];
1060 return [self pendingKeysForCurrentLockState];
1063 - (NSMutableDictionary *)copyValues:(NSSet*)keysOfInterest
1065 // Grab values from KVS.
1066 NSUbiquitousKeyValueStore *store = [self cloudStore];
1067 NSMutableDictionary *changedValues = [NSMutableDictionary dictionaryWithCapacity:0];
1068 [keysOfInterest enumerateObjectsUsingBlock:^(id obj, BOOL *stop)
1070 NSString* key = (NSString*) obj;
1071 id objval = [store objectForKey:key];
1072 if (!objval) objval = [NSNull null];
1074 [changedValues setObject:objval forKey:key];
1075 secdebug(XPROXYSCOPE, "%@ storeChanged updated value for %@", self, key);
1077 return changedValues;
1081 During RegisterKeys, separate keys-of-interest into three disjoint sets:
1082 - keys that we always want to be notified about; this means we can get the
1084 - keys that require the device to have been unlocked at least once
1085 - keys that require the device to be unlocked now
1087 Typically, the sets of keys will be:
1093 The caller is responsible for making sure that the keys in e.g. alwaysKeys are
1094 values that can be handled at any time (that is, not when unlocked)
1096 Each time we get a notification from ubiquity that keys have changed, we need to
1097 see if anything of interest changed. If we don't care, then done.
1099 For each key-of-interest that changed, we either notify the client that things
1100 changed, or add it to a pendingNotifications list. If the notification to the
1101 client fails, also add it to the pendingNotifications list. This pending list
1102 should be written to persistent storage and consulted any time we either get an
1103 item changed notification, or get a stream event signalling a change in lock state.
1105 We can notify the client either through XPC if a connection is set up, or call a
1106 routine in securityd to launch it.
1110 - (void)processKeyChangedEvent:(NSDictionary *)changedValues
1112 NSMutableDictionary* filtered = [NSMutableDictionary dictionary];
1114 NSMutableArray* nullKeys = [NSMutableArray array];
1115 // Remove nulls because we don't want them in securityd.
1116 [changedValues enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
1117 if (obj == [NSNull null])
1118 [nullKeys addObject:key];
1120 filtered[key] = obj;
1123 if ([nullKeys count])
1124 [_pendingKeys minusSet: [NSSet setWithArray: nullKeys]];
1126 if([filtered count] != 0 ){
1127 [self sendKeysCallout:^NSSet *(NSSet *pending, NSError** error) {
1128 CFErrorRef cf_error = NULL;
1129 NSArray* handledMessage = (__bridge_transfer NSArray*) _SecKeychainSyncUpdateMessage((__bridge CFDictionaryRef)filtered, &cf_error);
1130 NSError *updateError = (__bridge_transfer NSError*)cf_error;
1132 *error = updateError;
1134 secnoticeq("keytrace", "%@ misc handled: %@ null: %@ pending: %@", self,
1135 [handledMessage componentsJoinedByString: @" "],
1136 [nullKeys componentsJoinedByString: @" "],
1137 [[_pendingKeys allObjects] componentsJoinedByString: @" "]);
1139 return handledMessage ? [NSSet setWithArray: handledMessage] : nil;
1142 secnoticeq("keytrace", "%@ null: %@ pending: %@", self,
1143 [nullKeys componentsJoinedByString: @" "],
1144 [[_pendingKeys allObjects] componentsJoinedByString: @" "]);
1148 - (void) processPendingKeysForCurrentLockState
1150 [self processKeyChangedEvent: [self copyValues: [self pendingKeysForCurrentLockState]]];