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"
43 #include <Security/SecureObjectSync/SOSARCDefines.h>
46 #include <Security/SecureObjectSync/SOSAccount.h>
47 #include <Security/SecureObjectSync/SOSCloudCircleInternal.h>
48 #include <Security/SecureObjectSync/SOSKVSKeys.h>
50 #include "SOSCloudKeychainConstants.h"
52 #include <utilities/SecAKSWrappers.h>
53 #include <utilities/SecCFRelease.h>
56 The total space available in your app’s iCloud key-value storage is 1 MB.
57 The maximum number of keys you can specify is 1024, and the size limit for
58 each value associated with a key is 1 MB. So, for example, if you store a
59 single large value of 1 MB for a single key, that consumes your total
60 available storage. If you store 1 KB of data for each key, you can use
61 1,000 key-value pairs.
64 static const char *kStreamName = "com.apple.notifyd.matching";
66 static NSString *kKeyKeyParameterKeys = @"KeyParameterKeys";
67 static NSString *kKeyCircleKeys = @"CircleKeys";
68 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";
78 static NSString *kKeyDSID = @"DSID";
79 static NSString *kMonitorState = @"MonitorState";
81 static NSString *kMonitorPenaltyBoxKey = @"Penalty";
82 static NSString *kMonitorMessageKey = @"Message";
83 static NSString *kMonitorConsecutiveWrites = @"ConsecutiveWrites";
84 static NSString *kMonitorLastWriteTimestamp = @"LastWriteTimestamp";
85 static NSString *kMonitorMessageQueue = @"MessageQueue";
86 static NSString *kMonitorPenaltyTimer = @"PenaltyTimer";
88 static NSString *kMonitorTimeTable = @"TimeTable";
89 static NSString *kMonitorFirstMinute = @"AFirstMinute";
90 static NSString *kMonitorSecondMinute = @"BSecondMinute";
91 static NSString *kMonitorThirdMinute = @"CThirdMinute";
92 static NSString *kMonitorFourthMinute = @"DFourthMinute";
93 static NSString *kMonitorFifthMinute = @"EFifthMinute";
94 static NSString *kMonitorWroteInTimeSlice = @"TimeSlice";
96 #define kSecServerKeychainChangedNotification "com.apple.security.keychainchanged"
98 static int max_penalty_timeout = 32;
99 static int seconds_per_minute = 5;
102 kCallbackMethodSecurityd = 0,
103 kCallbackMethodXPC = 1,
106 static const int64_t kMinSyncDelay = (NSEC_PER_MSEC * 500); // 500ms minimum delay before a syncWithAllPeers call.
107 static const int64_t kMaxSyncDelay = (NSEC_PER_SEC * 5); // 5s maximun delay for a given request
108 static const int64_t kMinSyncInterval = (NSEC_PER_SEC * 15); // 15s minimum time between successive syncWithAllPeers calls.
109 static const int64_t kSyncTimerLeeway = (NSEC_PER_MSEC * 250); // 250ms leeway for sync events.
112 #define UPDATE_RESUBMIT 4
114 @interface NSUbiquitousKeyValueStore (NSUbiquitousKeyValueStore_PrivateZ)
115 - (void) _synchronizeWithCompletionHandler:(void (^)(NSError *error))completionHandler;
119 @implementation UbiqitousKVSProxy
124 [SOSPersistentState setRegisteredKeys:[self exportKeyInterests]];
127 + (UbiqitousKVSProxy *) sharedKVSProxy
129 static UbiqitousKVSProxy *sharedKVSProxy;
130 if (!sharedKVSProxy) {
131 static dispatch_once_t onceToken;
132 dispatch_once(&onceToken, ^{
133 sharedKVSProxy = [[self alloc] init];
136 return sharedKVSProxy;
141 if (self = [super init])
143 secnotice("event", "%@ start", self);
145 _calloutQueue = dispatch_queue_create("CKDCallout", DISPATCH_QUEUE_SERIAL);
146 _freshParamsQueue = dispatch_queue_create("CKDFresh", DISPATCH_QUEUE_SERIAL);
147 _ckdkvsproxy_queue = dispatch_get_main_queue();
149 _syncTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue());
150 dispatch_source_set_timer(_syncTimer, DISPATCH_TIME_FOREVER, DISPATCH_TIME_FOREVER, kSyncTimerLeeway);
151 dispatch_source_set_event_handler(_syncTimer, ^{
154 dispatch_resume(_syncTimer);
156 _monitor = [NSMutableDictionary dictionary];
158 [[NSNotificationCenter defaultCenter]
160 selector: @selector (iCloudAccountAvailabilityChanged:)
161 name: NSUbiquityIdentityDidChangeNotification
164 [[NSNotificationCenter defaultCenter] addObserver:self
165 selector:@selector(cloudChanged:)
166 name:NSUbiquitousKeyValueStoreDidChangeExternallyNotification
168 int notificationToken;
169 notify_register_dispatch(kSecServerKeychainChangedNotification, ¬ificationToken, dispatch_get_main_queue(),
170 ^ (int token __unused)
172 secinfo("backoff", "keychain changed, wiping backoff monitor state");
173 _monitor = [NSMutableDictionary dictionary];
176 [self importKeyInterests: [SOSPersistentState registeredKeys]];
178 // Register for lock state changes
179 xpc_set_event_stream_handler(kStreamName, dispatch_get_main_queue(),
180 ^(xpc_object_t notification){
181 [self streamEvent:notification];
185 [self updateUnlockedSinceBoot];
186 [self updateIsLocked];
188 [self keybagDidUnlock];
190 secdebug(XPROXYSCOPE, "%@ done", self);
195 - (NSString *)description
197 return [NSString stringWithFormat:@"<%s%s%s%s%s%s%s%s%s%s%s>",
198 _isLocked ? "L" : "U",
199 _unlockedSinceBoot ? "B" : "-",
200 _seenKVSStoreChange ? "K" : "-",
201 _syncTimerScheduled ? "T" : "-",
202 _syncWithPeersPending ? "s" : "-",
203 _ensurePeerRegistration ? "e" : "-",
204 [_pendingKeys count] ? "p" : "-",
205 _inCallout ? "C" : "-",
206 _shadowSyncWithPeersPending ? "S" : "-",
207 _shadowEnsurePeerRegistration ? "E" : "-",
208 [_shadowPendingKeys count] ? "P" : "-"];
211 - (void)processAllItems
213 NSDictionary *allItems = [self getAll];
216 secnotice("event", "%@ sending: %@", self, [[allItems allKeys] componentsJoinedByString: @" "]);
217 [self processKeyChangedEvent:allItems];
220 secdebug(XPROXYSCOPE, "%@ No items in KVS", self);
223 - (void)setItemsChangedBlock:(CloudItemsChangedBlock)itemsChangedBlock
225 self->itemsChangedCallback = itemsChangedBlock;
230 secdebug(XPROXYSCOPE, "%@", self);
231 [[NSNotificationCenter defaultCenter] removeObserver:self
232 name:NSUbiquitousKeyValueStoreDidChangeExternallyNotification object:nil];
234 [[NSNotificationCenter defaultCenter] removeObserver:self
235 name:NSUbiquityIdentityDidChangeNotification object:nil];
238 // MARK: ----- Client Interface -----
240 - (void)setObject:(id)obj forKey:(id)key
242 NSUbiquitousKeyValueStore *store = [self cloudStore];
245 id value = [store objectForKey:key];
247 secdebug("kvsdebug", "%@ key %@ changed: %@ to: %@", self, key, value, obj);
249 secdebug("kvsdebug", "%@ key %@ initialized to: %@", self, key, obj);
250 [store setObject:obj forKey:key];
251 [self requestSynchronization:NO];
253 secerror("Can't get kvs store, key: %@ not set to: %@", key, obj);
257 -(dispatch_source_t)setNewTimer:(int)timeout key:(NSString*)key
259 __block dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, _ckdkvsproxy_queue);
260 dispatch_source_set_timer(timer, dispatch_time(DISPATCH_TIME_NOW, timeout * NSEC_PER_SEC * seconds_per_minute), DISPATCH_TIME_FOREVER, kSyncTimerLeeway);
261 dispatch_source_set_event_handler(timer, ^{
262 [self penaltyTimerFired:key];
264 dispatch_resume(timer);
268 -(void) increasePenalty:(NSNumber*)currentPenalty key:(NSString*)key keyEntry:(NSMutableDictionary**)keyEntry
270 secinfo("backoff", "increasing penalty!");
272 if([currentPenalty intValue] == max_penalty_timeout){
273 newPenalty = max_penalty_timeout;
275 else if ([currentPenalty intValue] == 0)
278 newPenalty = [currentPenalty intValue]*2;
280 secinfo("backoff", "key %@, waiting %d minutes long to send next messages", key, newPenalty);
282 NSNumber* penalty_timeout = [[NSNumber alloc]initWithInt:newPenalty];
283 dispatch_source_t existingTimer = [*keyEntry valueForKey:kMonitorPenaltyTimer];
285 if(existingTimer != nil){
286 [*keyEntry removeObjectForKey:kMonitorPenaltyTimer];
287 dispatch_suspend(existingTimer);
288 dispatch_source_set_timer(existingTimer,dispatch_time(DISPATCH_TIME_NOW, newPenalty * NSEC_PER_SEC * seconds_per_minute), DISPATCH_TIME_FOREVER, kSyncTimerLeeway);
289 dispatch_resume(existingTimer);
290 [*keyEntry setObject:existingTimer forKey:kMonitorPenaltyTimer];
293 dispatch_source_t timer = [self setNewTimer:newPenalty key:key];
294 [*keyEntry setObject:timer forKey:kMonitorPenaltyTimer];
297 [*keyEntry setObject:penalty_timeout forKey:kMonitorPenaltyBoxKey];
298 [_monitor setObject:*keyEntry forKey:key];
301 -(void) decreasePenalty:(NSNumber*)currentPenalty key:(NSString*)key keyEntry:(NSMutableDictionary**)keyEntry
304 secinfo("backoff","decreasing penalty!");
305 if([currentPenalty intValue] == 0 || [currentPenalty intValue] == 1)
308 newPenalty = [currentPenalty intValue]/2;
310 secinfo("backoff","key %@, waiting %d minutes long to send next messages", key, newPenalty);
312 NSNumber* penalty_timeout = [[NSNumber alloc]initWithInt:newPenalty];
314 dispatch_source_t existingTimer = [*keyEntry valueForKey:kMonitorPenaltyTimer];
315 if(existingTimer != nil){
316 [*keyEntry removeObjectForKey:kMonitorPenaltyTimer];
317 dispatch_suspend(existingTimer);
319 dispatch_source_set_timer(existingTimer,dispatch_time(DISPATCH_TIME_NOW, newPenalty * NSEC_PER_SEC * seconds_per_minute), DISPATCH_TIME_FOREVER, kSyncTimerLeeway);
320 dispatch_resume(existingTimer);
321 [*keyEntry setObject:existingTimer forKey:kMonitorPenaltyTimer];
324 dispatch_resume(existingTimer);
325 dispatch_source_cancel(existingTimer);
330 dispatch_source_t timer = [self setNewTimer:newPenalty key:key];
331 [*keyEntry setObject:timer forKey:kMonitorPenaltyTimer];
335 [*keyEntry setObject:penalty_timeout forKey:kMonitorPenaltyBoxKey];
336 [_monitor setObject:*keyEntry forKey:key];
340 - (void)penaltyTimerFired:(NSString*)key
342 secinfo("backoff","key: %@, !!!!!!!!!!!!!!!!penalty timeout is up!!!!!!!!!!!!", key);
343 NSMutableDictionary *keyEntry = [_monitor objectForKey:key];
344 NSMutableDictionary *queuedMessages = [keyEntry objectForKey:kMonitorMessageQueue];
345 secinfo("backoff","key: %@, queuedMessages: %@", key, queuedMessages);
346 if(queuedMessages && [queuedMessages count] != 0){
347 secinfo("backoff","key: %@, message queue not empty, writing to KVS!", key);
348 [self setObjectsFromDictionary:queuedMessages];
349 [keyEntry setObject:[NSMutableDictionary dictionary] forKey:kMonitorMessageQueue];
351 //decrease timeout since we successfully wrote messages out
352 NSNumber *penalty_timeout = [keyEntry valueForKey:kMonitorPenaltyBoxKey];
353 [self decreasePenalty:penalty_timeout key:key keyEntry:&keyEntry];
355 //recompute the timetable and number of consecutive writes to KVS
356 NSMutableDictionary *timetable = [keyEntry valueForKey:kMonitorTimeTable];
357 NSNumber *consecutiveWrites = [keyEntry valueForKey:kMonitorConsecutiveWrites];
358 [self recordTimestampForAppropriateInterval:&timetable key:key consecutiveWrites:&consecutiveWrites];
360 [keyEntry setObject:consecutiveWrites forKey:kMonitorConsecutiveWrites];
361 [keyEntry setObject:timetable forKey:kMonitorTimeTable];
362 [_monitor setObject:keyEntry forKey:key];
365 -(NSMutableDictionary*)initializeTimeTable:(NSString*)key
367 NSDate *currentTime = [NSDate date];
368 NSMutableDictionary *firstMinute = [NSMutableDictionary dictionaryWithObjectsAndKeys:[currentTime dateByAddingTimeInterval: seconds_per_minute], kMonitorFirstMinute, @"YES", kMonitorWroteInTimeSlice, nil];
369 NSMutableDictionary *secondMinute = [NSMutableDictionary dictionaryWithObjectsAndKeys:[currentTime dateByAddingTimeInterval: seconds_per_minute * 2],kMonitorSecondMinute, @"NO", kMonitorWroteInTimeSlice, nil];
370 NSMutableDictionary *thirdMinute = [NSMutableDictionary dictionaryWithObjectsAndKeys:[currentTime dateByAddingTimeInterval: seconds_per_minute * 3], kMonitorThirdMinute, @"NO",kMonitorWroteInTimeSlice, nil];
371 NSMutableDictionary *fourthMinute = [NSMutableDictionary dictionaryWithObjectsAndKeys:[currentTime dateByAddingTimeInterval: seconds_per_minute * 4],kMonitorFourthMinute, @"NO", kMonitorWroteInTimeSlice, nil];
372 NSMutableDictionary *fifthMinute = [NSMutableDictionary dictionaryWithObjectsAndKeys:[currentTime dateByAddingTimeInterval: seconds_per_minute * 5], kMonitorFifthMinute, @"NO", kMonitorWroteInTimeSlice, nil];
374 NSMutableDictionary *timeTable = [NSMutableDictionary dictionaryWithObjectsAndKeys: firstMinute, kMonitorFirstMinute,
375 secondMinute, kMonitorSecondMinute,
376 thirdMinute, kMonitorThirdMinute,
377 fourthMinute, kMonitorFourthMinute,
378 fifthMinute, kMonitorFifthMinute, nil];
382 - (void)initializeKeyEntry:(NSString*)key
384 NSMutableDictionary *timeTable = [self initializeTimeTable:key];
385 NSDate *currentTime = [NSDate date];
387 NSMutableDictionary *keyEntry = [NSMutableDictionary dictionaryWithObjectsAndKeys: key, kMonitorMessageKey, @0, kMonitorConsecutiveWrites, currentTime, kMonitorLastWriteTimestamp, @0, kMonitorPenaltyBoxKey, timeTable, kMonitorTimeTable,[NSMutableDictionary dictionary], kMonitorMessageQueue, nil];
389 [_monitor setObject:keyEntry forKey:key];
393 - (void)recordTimestampForAppropriateInterval:(NSMutableDictionary**)timeTable key:(NSString*)key consecutiveWrites:(NSNumber**)consecutiveWrites
395 NSDate *currentTime = [NSDate date];
396 __block int cWrites = [*consecutiveWrites intValue];
397 __block BOOL foundTimeSlot = NO;
398 __block NSMutableDictionary *previousTable = nil;
399 NSArray *sorted = [[*timeTable allKeys] sortedArrayUsingSelector:@selector(compare:)];
400 [sorted enumerateObjectsUsingBlock:^(id sortedKey, NSUInteger idx, BOOL *stop)
402 if(foundTimeSlot == YES)
404 [*timeTable enumerateKeysAndObjectsUsingBlock: ^(id key, id obj, BOOL *stop)
406 if(foundTimeSlot == YES)
408 if([sortedKey isEqualToString:key]){
409 NSMutableDictionary *minutesTable = (NSMutableDictionary*)obj;
410 NSString *minuteKey = (NSString*)key;
411 NSDate *date = [minutesTable valueForKey:minuteKey];
412 if([date compare:currentTime] == NSOrderedDescending){
414 NSString* written = [minutesTable valueForKey:kMonitorWroteInTimeSlice];
415 if([written isEqualToString:@"NO"]){
416 [minutesTable setObject:@"YES" forKey:kMonitorWroteInTimeSlice];
417 if(previousTable != nil){
418 written = [previousTable valueForKey:kMonitorWroteInTimeSlice];
419 if([written isEqualToString:@"YES"]){
422 else if ([written isEqualToString:@"NO"]){
429 previousTable = minutesTable;
434 if(foundTimeSlot == NO){
435 //reset the time table
436 secinfo("backoff","didn't find a time slot, resetting the table");
437 NSMutableDictionary *lastTable = [*timeTable valueForKey:kMonitorFifthMinute];
438 NSDate *lastDate = [lastTable valueForKey:kMonitorFifthMinute];
440 if((double)[currentTime timeIntervalSinceDate: lastDate] >= seconds_per_minute){
441 *consecutiveWrites = [[NSNumber alloc]initWithInt:0];
444 NSString* written = [lastTable valueForKey:kMonitorWroteInTimeSlice];
445 if([written isEqualToString:@"YES"]){
447 *consecutiveWrites = [[NSNumber alloc]initWithInt:cWrites];
450 *consecutiveWrites = [[NSNumber alloc]initWithInt:0];
454 *timeTable = [self initializeTimeTable:key];
457 *consecutiveWrites = [[NSNumber alloc]initWithInt:cWrites];
459 - (void)recordWriteToKVS:(NSDictionary *)values
461 if([_monitor count] == 0){
462 [values enumerateKeysAndObjectsUsingBlock: ^(id key, id obj, BOOL *stop)
464 [self initializeKeyEntry: key];
468 [values enumerateKeysAndObjectsUsingBlock: ^(id key, id obj, BOOL *stop)
470 NSMutableDictionary *keyEntry = [_monitor objectForKey:key];
472 [self initializeKeyEntry: key];
475 NSNumber *penalty_timeout = [keyEntry objectForKey:kMonitorPenaltyBoxKey];
476 NSDate *lastWriteTimestamp = [keyEntry objectForKey:kMonitorLastWriteTimestamp];
477 NSMutableDictionary *timeTable = [keyEntry objectForKey: kMonitorTimeTable];
478 NSNumber *existingWrites = [keyEntry objectForKey: kMonitorConsecutiveWrites];
479 NSDate *currentTime = [NSDate date];
481 [self recordTimestampForAppropriateInterval:&timeTable key:key consecutiveWrites:&existingWrites];
483 int consecutiveWrites = [existingWrites intValue];
484 secinfo("backoff","consecutive writes: %d", consecutiveWrites);
485 [keyEntry setObject:existingWrites forKey:kMonitorConsecutiveWrites];
486 [keyEntry setObject:timeTable forKey:kMonitorTimeTable];
487 [keyEntry setObject:currentTime forKey:kMonitorLastWriteTimestamp];
488 [_monitor setObject:keyEntry forKey:key];
490 if([penalty_timeout intValue] != 0 || ((double)[currentTime timeIntervalSinceDate: lastWriteTimestamp] <= 60 && consecutiveWrites >= 5)){
491 if([penalty_timeout intValue] != 0)
492 secinfo("backoff","still in timeout, shouldn't write anything to KVS in this time period");
494 secinfo("backoff","monitor: keys have been written for 5 or more minutes, time to bump penalty timers");
495 [self increasePenalty:penalty_timeout key:key keyEntry:&keyEntry];
497 //keep writing freely but record it
498 else if((double)[currentTime timeIntervalSinceDate: lastWriteTimestamp] <= 60 && consecutiveWrites < 5){
499 secinfo("backoff","monitor: still writing freely");
506 - (NSDictionary*)recordHaltedValuesAndReturnValuesToSafelyWrite:(NSDictionary *)values
508 NSMutableDictionary *SafeMessages = [NSMutableDictionary dictionary];
509 [values enumerateKeysAndObjectsUsingBlock: ^(id key, id obj, BOOL *stop)
511 NSMutableDictionary *keyEntry = [_monitor objectForKey:key];
512 NSNumber *penalty = [keyEntry objectForKey:kMonitorPenaltyBoxKey];
513 if([penalty intValue] != 0){
514 NSMutableDictionary* existingQueue = [keyEntry valueForKey:kMonitorMessageQueue];
516 [existingQueue setObject:obj forKey:key];
518 [keyEntry setObject:existingQueue forKey:kMonitorMessageQueue];
519 [_monitor setObject:keyEntry forKey:key];
522 [SafeMessages setObject:obj forKey:key];
528 - (void)setObjectsFromDictionary:(NSDictionary *)values
530 NSUbiquitousKeyValueStore *store = [self cloudStore];
533 secnoticeq("dsid", "Ensure DSIDs match");
534 NSMutableDictionary *mutableValues = [NSMutableDictionary dictionaryWithCapacity:0];
536 secinfo("backoff","!!writing these keys to KVS!!: %@", values);
537 [values enumerateKeysAndObjectsUsingBlock: ^(id key, id obj, BOOL *stop)
539 if (obj == NULL || obj == [NSNull null])
540 [store removeObjectForKey:key];
542 else if([key isEqualToString: @"^OfficialDSID"]){
544 secnotice("dsid", "setting dsid to %@", obj);
547 else if([key isEqual: @"^Required"]){
548 if( [_dsid isEqualToString: @""]){
549 secdebug("dsid", "CloudKeychainProxy setting dsid to :%@ from securityd", obj);
553 else if(![_dsid isEqual: obj]){
554 secerror("Account DSIDs do not match, cloud keychain proxy: %@, securityd: %@", _dsid, obj);
555 secerror("Not going to write these: %@ into KVS!", values);
560 [ mutableValues setObject:obj forKey:key ];
564 secnoticeq("keytrace", "%@ sending: %@", self, [[mutableValues allKeys] componentsJoinedByString: @" "]);
565 [mutableValues enumerateKeysAndObjectsUsingBlock: ^(id key, id obj, BOOL *stop)
567 if (obj == NULL || obj == [NSNull null])
568 [store removeObjectForKey:key];
571 id oldObj = [store objectForKey:key];
572 if ([oldObj isEqual: obj]) {
573 if ([key hasPrefix:@"ak|"]) { // TODO: somewhat of a hack
574 // Fix KVS repeated message undelivery by sending a NULL first (deafness)
575 secnoticeq("keytrace", "forcing resend of peer-peer message: %@", key);
576 [store removeObjectForKey:key];
578 // Resending circle messages is bad
579 secnoticeq("keytrace", "repeated message: %@ (not peer-peer); not propogated", key);
582 [store setObject:obj forKey:key];
586 [self requestSynchronization:NO];
589 secdebug(XPROXYSCOPE, "%@ NULL? store: %@, values: %@", self, store, values);
592 - (void)requestSynchronization:(bool)force
596 secdebug(XPROXYSCOPE, "%@ synchronize (forced)", self);
597 [[self cloudStore] synchronize];
601 secdebug(XPROXYSCOPE, "%@ synchronize (soon)", self);
602 [[self cloudStore] synchronize];
606 - (NSUbiquitousKeyValueStore *)cloudStore
608 NSUbiquitousKeyValueStore *iCloudStore = [NSUbiquitousKeyValueStore defaultStore];
610 secerror("%s %@ NO NSUbiquitousKeyValueStore defaultStore", kWAIT2MINID, self);
616 Only call out to syncdefaultsd once every 5 seconds, since parameters can't change that
617 fast and callers expect synchonicity.
619 Since we don't actually get the values for the keys, just store off a timestamp.
622 // try to synchronize asap, and invoke the handler on completion to take incoming changes.
624 static bool isResubmitError(NSError* error) {
625 return error && (CFErrorGetCode((__bridge CFErrorRef) error) == UPDATE_RESUBMIT); // Why don't we check the domain?!
628 - (BOOL) AttemptSynchronization:(NSError **)failure
631 __block NSError *tempFailure = NULL;
632 int triesRemaining = 10;
634 NSUbiquitousKeyValueStore * store = [self cloudStore];
636 dispatch_semaphore_t freshSemaphore = dispatch_semaphore_create(0);
640 secnoticeq("fresh", "%s CALLING OUT TO syncdefaultsd SWCH: %@", kWAIT2MINID, self);
642 [store synchronizeWithCompletionHandler:^(NSError *error) {
645 secerrorq("%s RETURNING FROM syncdefaultsd SWCH: %@: %@", kWAIT2MINID, self, error);
647 secnoticeq("fresh", "%s RETURNING FROM syncdefaultsd SWCH: %@", kWAIT2MINID, self);
648 [store synchronize]; // Per olivier in <rdar://problem/13412631>, sync before getting values
649 secnoticeq("fresh", "%s RETURNING FROM syncdefaultsd SYNC: %@", kWAIT2MINID, self);
651 dispatch_semaphore_signal(freshSemaphore);
653 dispatch_semaphore_wait(freshSemaphore, DISPATCH_TIME_FOREVER);
654 } while (triesRemaining > 0 && isResubmitError(tempFailure));
656 if (isResubmitError(tempFailure)) {
657 secerrorq("%s Number of retry attempts to request freshness exceeded", kWAIT2MINID);
660 if (failure && (*failure == NULL)) {
661 *failure = tempFailure;
664 return tempFailure == nil;
667 static void wait_until(dispatch_time_t when) {
668 static dispatch_once_t once;
669 static dispatch_semaphore_t never_fires = nil;
670 dispatch_once(&once, ^{
671 never_fires = dispatch_semaphore_create(0);
674 dispatch_semaphore_wait(never_fires, when); // Will always timeout.
677 - (void)waitForSynchronization:(NSArray *)keys handler:(void (^)(NSDictionary *values, NSError *err))handler
681 NSError *err = [NSError errorWithDomain:(NSString *)NSPOSIXErrorDomain code:(NSInteger)ENOPROTOOPT userInfo:nil];
682 secerrorq("%s RETURNING TO securityd: %@ param error; calling handler", kWAIT2MINID, self);
687 secnoticeq("fresh", "%s Requesting freshness", kWAIT2MINID);
689 dispatch_async(_freshParamsQueue, ^{
690 // Hold off (keeping the queue occupied) until we hit the next time we can fresh.
691 wait_until(_nextFreshnessTime);
693 NSError *error = nil;
694 BOOL success = [self AttemptSynchronization:&error];
696 // Advance the next time can can call freshness.
697 const uint64_t delayBeforeCallingAgainInSeconds = 5ull * NSEC_PER_SEC;
698 _nextFreshnessTime = dispatch_time(DISPATCH_TIME_NOW, delayBeforeCallingAgainInSeconds);
700 dispatch_async(dispatch_get_main_queue(), ^{
701 NSDictionary * freshValues = success ? [self copyValues:[NSSet setWithArray:keys]] : @{};
702 handler(freshValues, error);
707 - (void)removeObjectForKey:(NSString *)keyToRemove
709 [[self cloudStore] removeObjectForKey:keyToRemove];
714 secdebug(XPROXYSCOPE, "%@ clearStore", self);
715 NSDictionary *dict = [[self cloudStore] dictionaryRepresentation];
716 NSArray *allKeys = [dict allKeys];
717 NSMutableArray* nullKeys = [NSMutableArray array];
719 _alwaysKeys = [NSMutableSet setWithArray:nullKeys];
720 _firstUnlockKeys = [NSMutableSet setWithArray:nullKeys];
721 _unlockedKeys = [NSMutableSet setWithArray:nullKeys];
722 _keyParameterKeys = @{};
726 [allKeys enumerateObjectsUsingBlock:^(id key, NSUInteger idx, BOOL *stop)
728 secdebug(XPROXYSCOPE, "%@ Clearing value for key %@", self, key);
729 [[self cloudStore] removeObjectForKey:(NSString *)key];
732 [self requestSynchronization:YES];
737 // MARK: ----- KVS key lists -----
742 return [[self cloudStore] objectForKey:key];
745 - (NSDictionary *)getAll
747 return [[self cloudStore] dictionaryRepresentation];
750 - (NSDictionary*) exportKeyInterests
753 return @{ kKeyAlwaysKeys:[_alwaysKeys allObjects],
754 kKeyFirstUnlockKeys:[_firstUnlockKeys allObjects],
755 kKeyUnlockedKeys:[_unlockedKeys allObjects],
756 kMonitorState:_monitor,
757 kKeyPendingKeys:[_pendingKeys allObjects],
758 kKeySyncWithPeersPending:[NSNumber numberWithBool:_syncWithPeersPending],
759 kKeyEnsurePeerRegistration:[NSNumber numberWithBool:_ensurePeerRegistration],
764 - (void) importKeyInterests: (NSDictionary*) interests
766 _alwaysKeys = [NSMutableSet setWithArray: interests[kKeyAlwaysKeys]];
767 _firstUnlockKeys = [NSMutableSet setWithArray: interests[kKeyFirstUnlockKeys]];
768 _unlockedKeys = [NSMutableSet setWithArray: interests[kKeyUnlockedKeys]];
770 _pendingKeys = [NSMutableSet setWithArray: interests[kKeyPendingKeys]];
771 _syncWithPeersPending = [interests[kKeySyncWithPeersPending] boolValue];
772 _ensurePeerRegistration = [interests[kKeyEnsurePeerRegistration] boolValue];
773 _dsid = interests[kKeyDSID];
774 _monitor = interests[kMonitorState];
776 _monitor = [NSMutableDictionary dictionary];
779 - (NSMutableSet *)copyAllKeys
781 NSMutableSet *allKeys = [NSMutableSet setWithSet: _alwaysKeys];
782 [allKeys unionSet: _firstUnlockKeys];
783 [allKeys unionSet: _unlockedKeys];
787 -(void)registerAtTimeKeys:(NSDictionary*)keyparms
789 NSArray *alwaysArray = [keyparms valueForKey: kKeyAlwaysKeys];
790 NSArray *firstUnlockedKeysArray = [keyparms valueForKey: kKeyFirstUnlockKeys];
791 NSArray *whenUnlockedKeysArray = [keyparms valueForKey: kKeyUnlockedKeys];
794 [_alwaysKeys unionSet: [NSMutableSet setWithArray: alwaysArray]];
795 if(firstUnlockedKeysArray)
796 [_firstUnlockKeys unionSet: [NSMutableSet setWithArray: firstUnlockedKeysArray]];
797 if(whenUnlockedKeysArray)
798 [_unlockedKeys unionSet: [NSMutableSet setWithArray: whenUnlockedKeysArray]];
802 - (void)registerKeys: (NSDictionary*)keys
804 secdebug(XPROXYSCOPE, "registerKeys: keys: %@", keys);
806 NSMutableSet *allOldKeys = [self copyAllKeys];
808 NSDictionary *keyparms = [keys valueForKey: [NSString stringWithUTF8String: kMessageKeyParameter]];
809 NSDictionary *circles = [keys valueForKey: [NSString stringWithUTF8String: kMessageCircle]];
810 NSDictionary *messages = [keys valueForKey: [NSString stringWithUTF8String: kMessageMessage]];
812 _alwaysKeys = [NSMutableSet set];
813 _firstUnlockKeys = [NSMutableSet set];
814 _unlockedKeys = [NSMutableSet set];
816 _keyParameterKeys = keyparms;
817 _circleKeys = circles;
818 _messageKeys = messages;
820 [self registerAtTimeKeys: _keyParameterKeys];
821 [self registerAtTimeKeys: _circleKeys];
822 [self registerAtTimeKeys: _messageKeys];
824 NSMutableSet *allNewKeys = [self copyAllKeys];
826 // Make sure keys we no longer care about are not pending
827 [_pendingKeys intersectSet:allNewKeys];
828 if (_shadowPendingKeys) {
829 [_shadowPendingKeys intersectSet:allNewKeys];
832 // All new keys only is new keys (remove old keys)
833 [allNewKeys minusSet:allOldKeys];
835 // Mark new keys pending, they're new!
836 NSMutableSet *newKeysForCurrentLockState = [self pendKeysAndGetNewlyPended:allNewKeys];
838 [self persistState]; // Before we might call out, save our state so we recover if we crash
840 [self intersectWithCurrentLockState: newKeysForCurrentLockState];
841 // TODO: Don't processPendingKeysForCurrentLockState if none of the new keys have values.
842 if ([newKeysForCurrentLockState count] != 0) {
843 [self processPendingKeysForCurrentLockState];
847 - (void)saveToUbiquitousStore
849 [self requestSynchronization:NO];
852 // MARK: ----- Event Handling -----
854 - (void)streamEvent:(xpc_object_t)notification
856 #if (!TARGET_IPHONE_SIMULATOR)
857 const char *notificationName = xpc_dictionary_get_string(notification, "Notification");
858 if (!notificationName) {
859 } else if (strcmp(notificationName, kUserKeybagStateChangeNotification)==0) {
860 return [self keybagStateChange];
861 } else if (strcmp(notificationName, kCloudKeychainStorechangeChangeNotification)==0) {
862 return [self kvsStoreChange];
863 } else if (strcmp(notificationName, kNotifyTokenForceUpdate)==0) {
864 // DEBUG -- Possibly remove in future
865 return [self processAllItems];
867 const char *eventName = xpc_dictionary_get_string(notification, "XPCEventName");
868 char *desc = xpc_copy_description(notification);
869 secnotice("event", "%@ event: %s name: %s desc: %s", self, eventName, notificationName, desc);
875 - (void)iCloudAccountAvailabilityChanged:(NSNotification*)notification
878 Continuing this example, you’d then implement an iCloudAccountAvailabilityChanged: method that would:
880 Call the ubiquityIdentityToken method and store its return value.
881 Compare the new value to the previous value, to find out if the user logged out of their account or
882 logged in to a different account. If the previously-used account is now unavailable, save the current
883 state locally as needed, empty your iCloud-related data caches, and refresh all iCloud-related user interface elements.
885 id previCloudToken = currentiCloudToken;
886 currentiCloudToken = [[NSFileManager defaultManager] ubiquityIdentityToken];
887 if (previCloudToken != currentiCloudToken)
888 secnotice("event", "%@ iCloud account changed!", self);
890 secnotice("event", "%@ %@", self, notification);
893 - (void)cloudChanged:(NSNotification*)notification
896 Posted when the value of one or more keys in the local key-value store
897 changed due to incoming data pushed from iCloud. This notification is
898 sent only upon a change received from iCloud; it is not sent when your
901 The user info dictionary can contain the reason for the notification as
902 well as a list of which values changed, as follows:
904 The value of the NSUbiquitousKeyValueStoreChangeReasonKey key, when
905 present, indicates why the key-value store changed. Its value is one of
906 the constants in "Change Reason Values."
908 The value of the NSUbiquitousKeyValueStoreChangedKeysKey, when present,
909 is an array of strings, each the name of a key whose value changed. The
910 notification object is the NSUbiquitousKeyValueStore object whose contents
913 NSUbiquitousKeyValueStoreInitialSyncChange is only posted if there is any
914 local value that has been overwritten by a distant value. If there is no
915 conflict between the local and the distant values when doing the initial
916 sync (e.g. if the cloud has no data stored or the client has not stored
917 any data yet), you'll never see that notification.
919 NSUbiquitousKeyValueStoreInitialSyncChange implies an initial round trip
920 with server but initial round trip with server does not imply
921 NSUbiquitousKeyValueStoreInitialSyncChange.
923 os_activity_initiate("cloudChanged", OS_ACTIVITY_FLAG_DEFAULT, ^{
924 secdebug(XPROXYSCOPE, "%@ cloudChanged notification: %@", self, notification);
926 NSDictionary *userInfo = [notification userInfo];
927 NSNumber *reason = [userInfo objectForKey:NSUbiquitousKeyValueStoreChangeReasonKey];
928 if (reason) switch ([reason integerValue]) {
929 case NSUbiquitousKeyValueStoreInitialSyncChange:
930 case NSUbiquitousKeyValueStoreServerChange:
932 _seenKVSStoreChange = YES;
933 NSSet *keysChangedInCloud = [NSSet setWithArray:[userInfo objectForKey:NSUbiquitousKeyValueStoreChangedKeysKey]];
935 /* We are saying that we want to try processing a key no matter what,
936 * *if* it has changed in the cloud. */
937 [_pendingKeys minusSet:keysChangedInCloud];
939 NSSet *keysOfInterestThatChanged = [self pendKeysAndGetPendingForCurrentLockState:keysChangedInCloud];
940 NSMutableDictionary *changedValues = [self copyValues:keysOfInterestThatChanged];
941 if ([reason integerValue] == NSUbiquitousKeyValueStoreInitialSyncChange)
942 changedValues[(__bridge NSString*)kSOSKVSInitialSyncKey] = @"true";
944 secnotice("event", "%@ keysChangedInCloud: %@ keysOfInterest: %@", self, [[keysChangedInCloud allObjects] componentsJoinedByString: @" "], [[changedValues allKeys] componentsJoinedByString: @" "]);
945 if ([changedValues count])
946 [self processKeyChangedEvent:changedValues];
949 case NSUbiquitousKeyValueStoreQuotaViolationChange:
950 seccritical("%@ event received NSUbiquitousKeyValueStoreQuotaViolationChange", self);
952 case NSUbiquitousKeyValueStoreAccountChange:
953 // The primary account changed. We do not get this for password changes on the same account
954 secnotice("event", "%@ NSUbiquitousKeyValueStoreAccountChange", self);
955 NSDictionary *changedValues = nil;
957 changedValues = @{ (__bridge NSString*)kSOSKVSAccountChangedKey: _dsid };
959 changedValues = @{ (__bridge NSString*)kSOSKVSAccountChangedKey: @"true" };
961 [self processKeyChangedEvent:changedValues];
967 - (void) doAfterFlush: (dispatch_block_t) block
969 // Flush any pending communication to Securityd.
971 dispatch_async(_calloutQueue, block);
974 - (void) calloutWith: (void(^)(NSSet *pending, bool syncWithPeersPending, bool ensurePeerRegistration, dispatch_queue_t queue, void(^done)(NSSet *handledKeys, bool handledSyncWithPeers, bool handledEnsurePeerRegistration))) callout
976 // In CKDKVSProxy's serial queue
980 // dispatch_get_global_queue - well-known global concurrent queue
981 // dispatch_get_main_queue - default queue that is bound to the main thread
982 xpc_transaction_begin();
983 dispatch_async(_calloutQueue, ^{
984 __block NSSet *myPending;
985 __block bool mySyncWithPeersPending;
986 __block bool myEnsurePeerRegistration;
987 __block bool wasLocked;
988 dispatch_sync(_ckdkvsproxy_queue, ^{
989 myPending = [_pendingKeys copy];
990 mySyncWithPeersPending = _syncWithPeersPending;
991 myEnsurePeerRegistration = _ensurePeerRegistration;
992 wasLocked = _isLocked;
996 secnotice("deaf", ">>>>>>>>>>> _oldInCallout is NO and we're heading in to the callout!");
998 _shadowPendingKeys = [NSMutableSet set];
999 _shadowSyncWithPeersPending = NO;
1002 callout(myPending, mySyncWithPeersPending, myEnsurePeerRegistration, _ckdkvsproxy_queue, ^(NSSet *handledKeys, bool handledSyncWithPeers, bool handledEnsurePeerRegistration) {
1003 secdebug("event", "%@ %s%s before callout handled: %s%s", self, mySyncWithPeersPending ? "S" : "s", myEnsurePeerRegistration ? "E" : "e", handledSyncWithPeers ? "S" : "s", handledEnsurePeerRegistration ? "E" : "e");
1005 // In CKDKVSProxy's serial queue
1009 // Update ensurePeerRegistration
1010 _ensurePeerRegistration = ((myEnsurePeerRegistration && !handledEnsurePeerRegistration) || _shadowEnsurePeerRegistration);
1012 _shadowEnsurePeerRegistration = NO;
1014 if(_ensurePeerRegistration && !_isLocked)
1015 [self doEnsurePeerRegistration];
1017 // Update SyncWithPeers stuff.
1018 _syncWithPeersPending = ((mySyncWithPeersPending && (!handledSyncWithPeers)) || _shadowSyncWithPeersPending);
1020 _shadowSyncWithPeersPending = NO;
1021 if (handledSyncWithPeers)
1022 _lastSyncTime = dispatch_time(DISPATCH_TIME_NOW, 0);
1024 // Update pendingKeys and handle them
1025 [_pendingKeys minusSet: handledKeys];
1026 bool hadShadowPendingKeys = [_shadowPendingKeys count];
1027 // Move away shadownPendingKeys first, because pendKeysAndGetPendingForCurrentLockState
1028 // will look at them. See rdar://problem/20733166.
1029 NSSet *oldShadowPendingKeys = _shadowPendingKeys;
1030 _shadowPendingKeys = nil;
1032 NSSet *filteredKeys = [self pendKeysAndGetPendingForCurrentLockState:oldShadowPendingKeys];
1034 // Write state to disk
1035 [self persistState];
1037 // Handle shadow pended stuff
1038 if (_syncWithPeersPending && !_isLocked)
1039 [self scheduleSyncRequestTimer];
1040 /* We don't want to call processKeyChangedEvent if we failed to
1041 handle pending keys and the device didn't unlock nor receive
1042 any kvs changes while we were in our callout.
1043 Doing so will lead to securityd and CloudKeychainProxy
1044 talking to each other forever in a tight loop if securityd
1045 repeatedly returns an error processing the same message.
1046 Instead we leave any old pending keys until the next event. */
1047 if (hadShadowPendingKeys || (!_isLocked && wasLocked))
1048 [self processKeyChangedEvent:[self copyValues:filteredKeys]];
1050 xpc_transaction_end();
1055 - (void) sendKeysCallout: (NSSet *(^)(NSSet* pending, NSError** error)) handleKeys {
1056 [self calloutWith: ^(NSSet *pending, bool syncWithPeersPending, bool ensurePeerRegistration, dispatch_queue_t queue, void(^done)(NSSet *, bool, bool)) {
1057 NSError* error = NULL;
1059 NSSet * handled = handleKeys(pending, &error);
1061 dispatch_async(queue, ^{
1063 secerror("%@ ensurePeerRegistration failed: %@", self, error);
1066 done(handled, NO, NO);
1071 - (void) doEnsurePeerRegistration
1073 [self calloutWith:^(NSSet *pending, bool syncWithPeersPending, bool ensurePeerRegistration, dispatch_queue_t queue, void(^done)(NSSet *, bool, bool)) {
1074 CFErrorRef error = NULL;
1075 bool handledEnsurePeerRegistration = SOSCCProcessEnsurePeerRegistration(&error);
1076 secerror("%@ ensurePeerRegistration called, %@ (%@)", self, handledEnsurePeerRegistration ? @"success" : @"failure", error);
1077 dispatch_async(queue, ^{
1078 if (!handledEnsurePeerRegistration) {
1079 secerror("%@ ensurePeerRegistration failed: %@", self, error);
1082 done(nil, NO, handledEnsurePeerRegistration);
1083 CFReleaseSafe(error);
1088 - (void) doSyncWithAllPeers
1090 [self calloutWith:^(NSSet *pending, bool syncWithPeersPending, bool ensurePeerRegistration, dispatch_queue_t queue, void(^done)(NSSet *, bool, bool)) {
1091 CFErrorRef error = NULL;
1093 SyncWithAllPeersReason reason = SOSCCProcessSyncWithAllPeers(&error);
1094 dispatch_async(queue, ^{
1095 bool handledSyncWithPeers = NO;
1096 if (reason == kSyncWithAllPeersSuccess) {
1097 handledSyncWithPeers = YES;
1098 secnotice("event", "%@ syncWithAllPeers succeeded", self);
1099 } else if (reason == kSyncWithAllPeersLocked) {
1100 secnotice("event", "%@ syncWithAllPeers attempted while locked - waiting for unlock", self);
1101 handledSyncWithPeers = NO;
1102 [self updateIsLocked];
1103 } else if (reason == kSyncWithAllPeersOtherFail) {
1104 // Pretend we handled syncWithPeers, by pushing out the _lastSyncTime
1105 // This will cause us to wait for kMinSyncInterval seconds before
1106 // retrying, so we don't spam securityd if sync is failing
1107 secerror("%@ syncWithAllPeers %@, rescheduling timer", self, error);
1108 _lastSyncTime = dispatch_time(DISPATCH_TIME_NOW, 0);
1110 secerror("%@ syncWithAllPeers %@, unknown reason: %d", self, error, reason);
1113 done(nil, handledSyncWithPeers, false);
1114 CFReleaseSafe(error);
1121 secnotice("event", "%@ syncWithPeersPending: %d inCallout: %d isLocked: %d", self, _syncWithPeersPending, _inCallout, _isLocked);
1122 _syncTimerScheduled = NO;
1123 if(_ensurePeerRegistration){
1124 [self doEnsurePeerRegistration];
1126 if (_syncWithPeersPending && !_inCallout && !_isLocked){
1127 [self doSyncWithAllPeers];
1131 - (dispatch_time_t) nextSyncTime
1133 dispatch_time_t nextSync = dispatch_time(DISPATCH_TIME_NOW, kMinSyncDelay);
1135 // Don't sync again unless we waited at least kMinSyncInterval
1136 if (_lastSyncTime) {
1137 dispatch_time_t soonest = dispatch_time(_lastSyncTime, kMinSyncInterval);
1138 if (nextSync < soonest || _deadline < soonest) {
1139 secdebug("timer", "%@ backing off", self);
1144 // Don't delay more than kMaxSyncDelay after the first request.
1145 if (nextSync > _deadline) {
1146 secdebug("timer", "%@ hit deadline", self);
1150 // Bump the timer by kMinSyncDelay
1151 if (_syncTimerScheduled)
1152 secdebug("timer", "%@ bumped timer", self);
1154 secdebug("timer", "%@ scheduled timer", self);
1159 - (void)scheduleSyncRequestTimer
1161 dispatch_source_set_timer(_syncTimer, [self nextSyncTime], DISPATCH_TIME_FOREVER, kSyncTimerLeeway);
1162 _syncTimerScheduled = YES;
1165 - (void)requestSyncWithAllPeers // secd calling SOSCCSyncWithAllPeers invokes this
1167 #if !defined(NDEBUG)
1168 NSString *desc = [self description];
1171 if (!_syncWithPeersPending || (_inCallout && !_shadowSyncWithPeersPending))
1172 _deadline = dispatch_time(DISPATCH_TIME_NOW, kMaxSyncDelay);
1174 if (!_syncWithPeersPending) {
1175 _syncWithPeersPending = YES;
1176 [self persistState];
1180 _shadowSyncWithPeersPending = YES;
1181 else if (!_isLocked)
1182 [self scheduleSyncRequestTimer];
1184 secdebug("event", "%@ %@", desc, self);
1187 - (void)requestEnsurePeerRegistration // secd calling SOSCCSyncWithAllPeers invokes this
1189 #if !defined(NDEBUG)
1190 NSString *desc = [self description];
1194 _shadowEnsurePeerRegistration = YES;
1196 _ensurePeerRegistration = YES;
1198 [self doEnsurePeerRegistration];
1200 [self persistState];
1203 secdebug("event", "%@ %@", desc, self);
1207 - (BOOL) updateUnlockedSinceBoot
1209 CFErrorRef aksError = NULL;
1210 if (!SecAKSGetHasBeenUnlocked(&_unlockedSinceBoot, &aksError)) {
1211 secerror("%@ Got error from SecAKSGetHasBeenUnlocked: %@", self, aksError);
1212 CFReleaseSafe(aksError);
1218 - (BOOL) updateIsLocked
1220 CFErrorRef aksError = NULL;
1221 if (!SecAKSGetIsLocked(&_isLocked, &aksError)) {
1222 secerror("%@ Got error querying lock state: %@", self, aksError);
1223 CFReleaseSafe(aksError);
1227 _unlockedSinceBoot = YES;
1231 - (void) keybagStateChange
1233 os_activity_initiate("keybagStateChanged", OS_ACTIVITY_FLAG_DEFAULT, ^{
1234 BOOL wasLocked = _isLocked;
1235 if ([self updateIsLocked]) {
1236 if (wasLocked == _isLocked)
1237 secdebug("event", "%@ still %s ignoring", self, _isLocked ? "locked" : "unlocked");
1239 [self keybagDidLock];
1241 [self keybagDidUnlock];
1246 - (void) keybagDidLock
1248 secnotice("event", "%@", self);
1251 - (void) keybagDidUnlock
1253 secnotice("event", "%@", self);
1254 if (_ensurePeerRegistration) {
1255 [self doEnsurePeerRegistration];
1258 // First send changed keys to securityd so it can proccess updates
1259 [self processPendingKeysForCurrentLockState];
1261 // Then, tickle securityd to perform a sync if needed.
1262 if (_syncWithPeersPending && !_syncTimerScheduled) {
1263 [self doSyncWithAllPeers];
1267 - (void) kvsStoreChange {
1268 os_activity_initiate("kvsStoreChange", OS_ACTIVITY_FLAG_DEFAULT, ^{
1269 if (!_seenKVSStoreChange) {
1270 _seenKVSStoreChange = YES; // Only do this once
1271 secnotice("event", "%@ received darwin notification before first NSNotification", self);
1272 // TODO This might not be needed if we always get the NSNotification
1273 // deleived even if we were launched due to a kvsStoreChange
1274 // Send all keys for current lock state to securityd so it can proccess them
1275 [self pendKeysAndGetNewlyPended: [self copyAllKeys]];
1276 [self processPendingKeysForCurrentLockState];
1278 secdebug("event", "%@ ignored, waiting for NSNotification", self);
1284 // MARK: ----- Key Filtering -----
1287 - (NSSet*) keysForCurrentLockState
1289 secdebug("filtering", "%@ Filtering: unlockedSinceBoot: %d\n unlocked: %d\n, keysOfInterest: <%@>", self, (int) _unlockedSinceBoot, (int) !_isLocked, [SOSPersistentState dictionaryDescription: [self exportKeyInterests]]);
1291 NSMutableSet *currentStateKeys = [NSMutableSet setWithSet: _alwaysKeys];
1292 if (_unlockedSinceBoot)
1293 [currentStateKeys unionSet: _firstUnlockKeys];
1296 [currentStateKeys unionSet: _unlockedKeys];
1298 return currentStateKeys;
1301 - (NSMutableSet*) pendKeysAndGetNewlyPended: (NSSet*) keysToPend
1303 NSMutableSet *newlyPendedKeys = [keysToPend mutableCopy];
1304 [newlyPendedKeys minusSet: _pendingKeys];
1305 if (_shadowPendingKeys) {
1306 [newlyPendedKeys minusSet: _shadowPendingKeys];
1309 [_pendingKeys unionSet:keysToPend];
1310 if (_shadowPendingKeys) {
1311 [_shadowPendingKeys unionSet:keysToPend];
1314 return newlyPendedKeys;
1317 - (void) intersectWithCurrentLockState: (NSMutableSet*) set
1319 [set intersectSet: [self keysForCurrentLockState]];
1322 - (NSMutableSet*) pendingKeysForCurrentLockState
1324 NSMutableSet * result = [_pendingKeys mutableCopy];
1325 [self intersectWithCurrentLockState:result];
1329 - (NSMutableSet*) pendKeysAndGetPendingForCurrentLockState: (NSSet*) startingSet
1331 [self pendKeysAndGetNewlyPended: startingSet];
1333 return [self pendingKeysForCurrentLockState];
1336 - (NSMutableDictionary *)copyValues:(NSSet*)keysOfInterest
1338 // Grab values from KVS.
1339 NSUbiquitousKeyValueStore *store = [self cloudStore];
1340 NSMutableDictionary *changedValues = [NSMutableDictionary dictionaryWithCapacity:0];
1341 [keysOfInterest enumerateObjectsUsingBlock:^(id obj, BOOL *stop)
1343 NSString* key = (NSString*) obj;
1344 id objval = [store objectForKey:key];
1345 if (!objval) objval = [NSNull null];
1347 [changedValues setObject:objval forKey:key];
1348 secdebug(XPROXYSCOPE, "%@ storeChanged updated value for %@", self, key);
1350 return changedValues;
1354 During RegisterKeys, separate keys-of-interest into three disjoint sets:
1355 - keys that we always want to be notified about; this means we can get the
1357 - keys that require the device to have been unlocked at least once
1358 - keys that require the device to be unlocked now
1360 Typically, the sets of keys will be:
1366 The caller is responsible for making sure that the keys in e.g. alwaysKeys are
1367 values that can be handled at any time (that is, not when unlocked)
1369 Each time we get a notification from ubiquity that keys have changed, we need to
1370 see if anything of interest changed. If we don't care, then done.
1372 For each key-of-interest that changed, we either notify the client that things
1373 changed, or add it to a pendingNotifications list. If the notification to the
1374 client fails, also add it to the pendingNotifications list. This pending list
1375 should be written to persistent storage and consulted any time we either get an
1376 item changed notification, or get a stream event signalling a change in lock state.
1378 We can notify the client either through XPC if a connection is set up, or call a
1379 routine in securityd to launch it.
1383 - (void)processKeyChangedEvent:(NSDictionary *)changedValues
1385 NSMutableDictionary* filtered = [NSMutableDictionary dictionary];
1387 NSMutableArray* nullKeys = [NSMutableArray array];
1388 // Remove nulls because we don't want them in securityd.
1389 [changedValues enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
1390 if (obj == [NSNull null])
1391 [nullKeys addObject:key];
1393 filtered[key] = obj;
1396 if ([nullKeys count])
1397 [_pendingKeys minusSet: [NSSet setWithArray: nullKeys]];
1399 if([filtered count] != 0 ){
1400 [self sendKeysCallout:^NSSet *(NSSet *pending, NSError** error) {
1401 CFErrorRef cf_error = NULL;
1402 NSArray* handledMessage = (__bridge_transfer NSArray*) _SecKeychainSyncUpdateMessage((__bridge CFDictionaryRef)filtered, &cf_error);
1403 NSError *updateError = (__bridge_transfer NSError*)cf_error;
1405 *error = updateError;
1407 secnoticeq("keytrace", "%@ misc handled: %@ null: %@ pending: %@", self,
1408 [handledMessage componentsJoinedByString: @" "],
1409 [nullKeys componentsJoinedByString: @" "],
1410 [[_pendingKeys allObjects] componentsJoinedByString: @" "]);
1412 return handledMessage ? [NSSet setWithArray: handledMessage] : nil;
1415 secnoticeq("keytrace", "%@ null: %@ pending: %@", self,
1416 [nullKeys componentsJoinedByString: @" "],
1417 [[_pendingKeys allObjects] componentsJoinedByString: @" "]);
1421 - (void) processPendingKeysForCurrentLockState
1423 [self processKeyChangedEvent: [self copyValues: [self pendingKeysForCurrentLockState]]];