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";
81 static NSString *kMonitorState = @"MonitorState";
83 static NSString *kMonitorPenaltyBoxKey = @"Penalty";
84 static NSString *kMonitorMessageKey = @"Message";
85 static NSString *kMonitorConsecutiveWrites = @"ConsecutiveWrites";
86 static NSString *kMonitorLastWriteTimestamp = @"LastWriteTimestamp";
87 static NSString *kMonitorMessageQueue = @"MessageQueue";
88 static NSString *kMonitorPenaltyTimer = @"PenaltyTimer";
90 static NSString *kMonitorTimeTable = @"TimeTable";
91 static NSString *kMonitorFirstMinute = @"AFirstMinute";
92 static NSString *kMonitorSecondMinute = @"BSecondMinute";
93 static NSString *kMonitorThirdMinute = @"CThirdMinute";
94 static NSString *kMonitorFourthMinute = @"DFourthMinute";
95 static NSString *kMonitorFifthMinute = @"EFifthMinute";
96 static NSString *kMonitorWroteInTimeSlice = @"TimeSlice";
98 #define kSecServerKeychainChangedNotification "com.apple.security.keychainchanged"
100 static int max_penalty_timeout = 32;
101 static int seconds_per_minute = 60;
105 kCallbackMethodSecurityd = 0,
106 kCallbackMethodXPC = 1,
109 static const int64_t kMinSyncDelay = (NSEC_PER_MSEC * 500); // 500ms minimum delay before a syncWithAllPeers call.
110 static const int64_t kMaxSyncDelay = (NSEC_PER_SEC * 5); // 5s maximun delay for a given request
111 static const int64_t kMinSyncInterval = (NSEC_PER_SEC * 15); // 15s minimum time between successive syncWithAllPeers calls.
112 static const int64_t kSyncTimerLeeway = (NSEC_PER_MSEC * 250); // 250ms leeway for sync events.
115 #define UPDATE_RESUBMIT 4
117 @interface NSUbiquitousKeyValueStore (NSUbiquitousKeyValueStore_PrivateZ)
118 - (void) _synchronizeWithCompletionHandler:(void (^)(NSError *error))completionHandler;
122 - (void) synchronizeWithCompletionHandler:(void (^)(NSError *error))completionHandler;
127 @implementation UbiqitousKVSProxy
132 [SOSPersistentState setRegisteredKeys:[self exportKeyInterests]];
135 + (UbiqitousKVSProxy *) sharedKVSProxy
137 static UbiqitousKVSProxy *sharedKVSProxy;
138 if (!sharedKVSProxy) {
139 static dispatch_once_t onceToken;
140 dispatch_once(&onceToken, ^{
141 sharedKVSProxy = [[self alloc] init];
144 return sharedKVSProxy;
149 if (self = [super init])
151 secnotice("event", "%@ start", self);
153 _calloutQueue = dispatch_queue_create("CKDCallout", DISPATCH_QUEUE_SERIAL);
154 _freshParamsQueue = dispatch_queue_create("CKDFresh", DISPATCH_QUEUE_SERIAL);
155 _ckdkvsproxy_queue = dispatch_get_main_queue();
157 _syncTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue());
158 dispatch_source_set_timer(_syncTimer, DISPATCH_TIME_FOREVER, DISPATCH_TIME_FOREVER, kSyncTimerLeeway);
159 dispatch_source_set_event_handler(_syncTimer, ^{
162 dispatch_resume(_syncTimer);
164 _monitor = [NSMutableDictionary dictionary];
166 [[NSNotificationCenter defaultCenter]
168 selector: @selector (iCloudAccountAvailabilityChanged:)
169 name: NSUbiquityIdentityDidChangeNotification
172 [[NSNotificationCenter defaultCenter] addObserver:self
173 selector:@selector(cloudChanged:)
174 name:NSUbiquitousKeyValueStoreDidChangeExternallyNotification
176 int notificationToken;
177 notify_register_dispatch(kSecServerKeychainChangedNotification, ¬ificationToken, _ckdkvsproxy_queue,
178 ^ (int token __unused)
180 secinfo("backoff", "keychain changed, wiping backoff monitor state");
181 _monitor = [NSMutableDictionary dictionary];
183 [self importKeyInterests: [SOSPersistentState registeredKeys]];
185 // Register for lock state changes
186 xpc_set_event_stream_handler(kStreamName, dispatch_get_main_queue(),
187 ^(xpc_object_t notification){
188 [self streamEvent:notification];
192 [self updateUnlockedSinceBoot];
193 [self updateIsLocked];
195 [self keybagDidUnlock];
197 secdebug(XPROXYSCOPE, "%@ done", self);
202 - (NSString *)description
204 return [NSString stringWithFormat:@"<%s%s%s%s%s%s%s%s%s%s%s>",
205 _isLocked ? "L" : "U",
206 _unlockedSinceBoot ? "B" : "-",
207 _seenKVSStoreChange ? "K" : "-",
208 _syncTimerScheduled ? "T" : "-",
209 _syncWithPeersPending ? "s" : "-",
210 _ensurePeerRegistration ? "e" : "-",
211 [_pendingKeys count] ? "p" : "-",
212 _inCallout ? "C" : "-",
213 _shadowSyncWithPeersPending ? "S" : "-",
214 _shadowEnsurePeerRegistration ? "E" : "-",
215 [_shadowPendingKeys count] ? "P" : "-"];
218 - (void)processAllItems
220 NSDictionary *allItems = [self getAll];
223 secnotice("event", "%@ sending: %@", self, [[allItems allKeys] componentsJoinedByString: @" "]);
224 [self processKeyChangedEvent:allItems];
227 secdebug(XPROXYSCOPE, "%@ No items in KVS", self);
230 - (void)setItemsChangedBlock:(CloudItemsChangedBlock)itemsChangedBlock
232 self->itemsChangedCallback = itemsChangedBlock;
237 secdebug(XPROXYSCOPE, "%@", self);
238 [[NSNotificationCenter defaultCenter] removeObserver:self
239 name:NSUbiquitousKeyValueStoreDidChangeExternallyNotification object:nil];
241 [[NSNotificationCenter defaultCenter] removeObserver:self
242 name:NSUbiquityIdentityDidChangeNotification object:nil];
245 // MARK: ----- Client Interface -----
247 - (void)setObject:(id)obj forKey:(id)key
249 NSUbiquitousKeyValueStore *store = [self cloudStore];
252 id value = [store objectForKey:key];
254 secdebug("kvsdebug", "%@ key %@ changed: %@ to: %@", self, key, value, obj);
256 secdebug("kvsdebug", "%@ key %@ initialized to: %@", self, key, obj);
257 [store setObject:obj forKey:key];
258 [self requestSynchronization:NO];
260 secerror("Can't get kvs store, key: %@ not set to: %@", key, obj);
264 -(dispatch_source_t)setNewTimer:(int)timeout key:(NSString*)key
266 __block dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, _ckdkvsproxy_queue);
267 dispatch_source_set_timer(timer, dispatch_time(DISPATCH_TIME_NOW, timeout * NSEC_PER_SEC * seconds_per_minute), DISPATCH_TIME_FOREVER, kSyncTimerLeeway);
268 dispatch_source_set_event_handler(timer, ^{
269 [self penaltyTimerFired:key];
271 dispatch_resume(timer);
275 -(void) increasePenalty:(NSNumber*)currentPenalty key:(NSString*)key keyEntry:(NSMutableDictionary**)keyEntry
277 secnotice("backoff", "increasing penalty!");
279 if([currentPenalty intValue] == max_penalty_timeout){
280 newPenalty = max_penalty_timeout;
282 else if ([currentPenalty intValue] == 0)
285 newPenalty = [currentPenalty intValue]*2;
287 secnotice("backoff", "key %@, waiting %d minutes long to send next messages", key, newPenalty);
289 NSNumber* penalty_timeout = [[NSNumber alloc]initWithInt:newPenalty];
290 dispatch_source_t existingTimer = [*keyEntry valueForKey:kMonitorPenaltyTimer];
292 if(existingTimer != nil){
293 [*keyEntry removeObjectForKey:kMonitorPenaltyTimer];
294 dispatch_suspend(existingTimer);
295 dispatch_source_set_timer(existingTimer,dispatch_time(DISPATCH_TIME_NOW, newPenalty * NSEC_PER_SEC * seconds_per_minute), DISPATCH_TIME_FOREVER, kSyncTimerLeeway);
296 dispatch_resume(existingTimer);
297 [*keyEntry setObject:existingTimer forKey:kMonitorPenaltyTimer];
300 dispatch_source_t timer = [self setNewTimer:newPenalty key:key];
301 [*keyEntry setObject:timer forKey:kMonitorPenaltyTimer];
304 [*keyEntry setObject:penalty_timeout forKey:kMonitorPenaltyBoxKey];
305 [_monitor setObject:*keyEntry forKey:key];
308 -(void) decreasePenalty:(NSNumber*)currentPenalty key:(NSString*)key keyEntry:(NSMutableDictionary**)keyEntry
311 secnotice("backoff","decreasing penalty!");
312 if([currentPenalty intValue] == 0 || [currentPenalty intValue] == 1)
315 newPenalty = [currentPenalty intValue]/2;
317 secnotice("backoff","key %@, waiting %d minutes long to send next messages", key, newPenalty);
319 NSNumber* penalty_timeout = [[NSNumber alloc]initWithInt:newPenalty];
321 dispatch_source_t existingTimer = [*keyEntry valueForKey:kMonitorPenaltyTimer];
322 if(existingTimer != nil){
323 [*keyEntry removeObjectForKey:kMonitorPenaltyTimer];
324 dispatch_suspend(existingTimer);
326 dispatch_source_set_timer(existingTimer,dispatch_time(DISPATCH_TIME_NOW, newPenalty * NSEC_PER_SEC * seconds_per_minute), DISPATCH_TIME_FOREVER, kSyncTimerLeeway);
327 dispatch_resume(existingTimer);
328 [*keyEntry setObject:existingTimer forKey:kMonitorPenaltyTimer];
331 dispatch_resume(existingTimer);
332 dispatch_source_cancel(existingTimer);
337 dispatch_source_t timer = [self setNewTimer:newPenalty key:key];
338 [*keyEntry setObject:timer forKey:kMonitorPenaltyTimer];
342 [*keyEntry setObject:penalty_timeout forKey:kMonitorPenaltyBoxKey];
343 [_monitor setObject:*keyEntry forKey:key];
347 - (void)penaltyTimerFired:(NSString*)key
349 secnotice("backoff", "key: %@, !!!!!!!!!!!!!!!!penalty timeout is up!!!!!!!!!!!!", key);
350 NSMutableDictionary *keyEntry = [_monitor objectForKey:key];
351 NSMutableDictionary *queuedMessages = [keyEntry objectForKey:kMonitorMessageQueue];
352 secnotice("backoff","key: %@, queuedMessages: %@", key, queuedMessages);
353 if(queuedMessages && [queuedMessages count] != 0){
354 secnotice("backoff","key: %@, message queue not empty, writing to KVS!", key);
355 [self setObjectsFromDictionary:queuedMessages];
356 [keyEntry setObject:[NSMutableDictionary dictionary] forKey:kMonitorMessageQueue];
358 //decrease timeout since we successfully wrote messages out
359 NSNumber *penalty_timeout = [keyEntry valueForKey:kMonitorPenaltyBoxKey];
360 [self decreasePenalty:penalty_timeout key:key keyEntry:&keyEntry];
362 //recompute the timetable and number of consecutive writes to KVS
363 NSMutableDictionary *timetable = [keyEntry valueForKey:kMonitorTimeTable];
364 NSNumber *consecutiveWrites = [keyEntry valueForKey:kMonitorConsecutiveWrites];
365 [self recordTimestampForAppropriateInterval:&timetable key:key consecutiveWrites:&consecutiveWrites];
367 [keyEntry setObject:consecutiveWrites forKey:kMonitorConsecutiveWrites];
368 [keyEntry setObject:timetable forKey:kMonitorTimeTable];
369 [_monitor setObject:keyEntry forKey:key];
372 -(NSMutableDictionary*)initializeTimeTable:(NSString*)key
374 NSDate *currentTime = [NSDate date];
375 NSMutableDictionary *firstMinute = [NSMutableDictionary dictionaryWithObjectsAndKeys:[currentTime dateByAddingTimeInterval: seconds_per_minute], kMonitorFirstMinute, @"YES", kMonitorWroteInTimeSlice, nil];
376 NSMutableDictionary *secondMinute = [NSMutableDictionary dictionaryWithObjectsAndKeys:[currentTime dateByAddingTimeInterval: seconds_per_minute * 2],kMonitorSecondMinute, @"NO", kMonitorWroteInTimeSlice, nil];
377 NSMutableDictionary *thirdMinute = [NSMutableDictionary dictionaryWithObjectsAndKeys:[currentTime dateByAddingTimeInterval: seconds_per_minute * 3], kMonitorThirdMinute, @"NO",kMonitorWroteInTimeSlice, nil];
378 NSMutableDictionary *fourthMinute = [NSMutableDictionary dictionaryWithObjectsAndKeys:[currentTime dateByAddingTimeInterval: seconds_per_minute * 4],kMonitorFourthMinute, @"NO", kMonitorWroteInTimeSlice, nil];
379 NSMutableDictionary *fifthMinute = [NSMutableDictionary dictionaryWithObjectsAndKeys:[currentTime dateByAddingTimeInterval: seconds_per_minute * 5], kMonitorFifthMinute, @"NO", kMonitorWroteInTimeSlice, nil];
381 NSMutableDictionary *timeTable = [NSMutableDictionary dictionaryWithObjectsAndKeys: firstMinute, kMonitorFirstMinute,
382 secondMinute, kMonitorSecondMinute,
383 thirdMinute, kMonitorThirdMinute,
384 fourthMinute, kMonitorFourthMinute,
385 fifthMinute, kMonitorFifthMinute, nil];
389 - (void)initializeKeyEntry:(NSString*)key
391 NSMutableDictionary *timeTable = [self initializeTimeTable:key];
392 NSDate *currentTime = [NSDate date];
394 NSMutableDictionary *keyEntry = [NSMutableDictionary dictionaryWithObjectsAndKeys: key, kMonitorMessageKey, @0, kMonitorConsecutiveWrites, currentTime, kMonitorLastWriteTimestamp, @0, kMonitorPenaltyBoxKey, timeTable, kMonitorTimeTable,[NSMutableDictionary dictionary], kMonitorMessageQueue, nil];
396 [_monitor setObject:keyEntry forKey:key];
400 - (void)recordTimestampForAppropriateInterval:(NSMutableDictionary**)timeTable key:(NSString*)key consecutiveWrites:(NSNumber**)consecutiveWrites
402 NSDate *currentTime = [NSDate date];
403 __block int cWrites = [*consecutiveWrites intValue];
404 __block BOOL foundTimeSlot = NO;
405 __block NSMutableDictionary *previousTable = nil;
406 NSArray *sorted = [[*timeTable allKeys] sortedArrayUsingSelector:@selector(compare:)];
407 [sorted enumerateObjectsUsingBlock:^(id sortedKey, NSUInteger idx, BOOL *stop)
409 if(foundTimeSlot == YES)
411 [*timeTable enumerateKeysAndObjectsUsingBlock: ^(id minute, id obj, BOOL *stop2)
413 if(foundTimeSlot == YES)
415 if([sortedKey isEqualToString:minute]){
416 NSMutableDictionary *minutesTable = (NSMutableDictionary*)obj;
417 NSString *minuteKey = (NSString*)minute;
418 NSDate *date = [minutesTable valueForKey:minuteKey];
419 if([date compare:currentTime] == NSOrderedDescending){
421 NSString* written = [minutesTable valueForKey:kMonitorWroteInTimeSlice];
422 if([written isEqualToString:@"NO"]){
423 [minutesTable setObject:@"YES" forKey:kMonitorWroteInTimeSlice];
424 if(previousTable != nil){
425 written = [previousTable valueForKey:kMonitorWroteInTimeSlice];
426 if([written isEqualToString:@"YES"]){
429 else if ([written isEqualToString:@"NO"]){
436 previousTable = minutesTable;
441 if(foundTimeSlot == NO){
442 //reset the time table
443 secnotice("backoff","didn't find a time slot, resetting the table");
444 NSMutableDictionary *lastTable = [*timeTable valueForKey:kMonitorFifthMinute];
445 NSDate *lastDate = [lastTable valueForKey:kMonitorFifthMinute];
447 if((double)[currentTime timeIntervalSinceDate: lastDate] >= seconds_per_minute){
448 *consecutiveWrites = [[NSNumber alloc]initWithInt:0];
451 NSString* written = [lastTable valueForKey:kMonitorWroteInTimeSlice];
452 if([written isEqualToString:@"YES"]){
454 *consecutiveWrites = [[NSNumber alloc]initWithInt:cWrites];
457 *consecutiveWrites = [[NSNumber alloc]initWithInt:0];
461 *timeTable = [self initializeTimeTable:key];
464 *consecutiveWrites = [[NSNumber alloc]initWithInt:cWrites];
466 - (void)recordWriteToKVS:(NSDictionary *)values
468 if([_monitor count] == 0){
469 [values enumerateKeysAndObjectsUsingBlock: ^(id key, id obj, BOOL *stop)
471 [self initializeKeyEntry: key];
475 [values enumerateKeysAndObjectsUsingBlock: ^(id key, id obj, BOOL *stop)
477 NSMutableDictionary *keyEntry = [_monitor objectForKey:key];
479 [self initializeKeyEntry: key];
482 NSNumber *penalty_timeout = [keyEntry objectForKey:kMonitorPenaltyBoxKey];
483 NSDate *lastWriteTimestamp = [keyEntry objectForKey:kMonitorLastWriteTimestamp];
484 NSMutableDictionary *timeTable = [keyEntry objectForKey: kMonitorTimeTable];
485 NSNumber *existingWrites = [keyEntry objectForKey: kMonitorConsecutiveWrites];
486 NSDate *currentTime = [NSDate date];
488 [self recordTimestampForAppropriateInterval:&timeTable key:key consecutiveWrites:&existingWrites];
490 int consecutiveWrites = [existingWrites intValue];
491 secnotice("backoff","consecutive writes: %d", consecutiveWrites);
492 [keyEntry setObject:existingWrites forKey:kMonitorConsecutiveWrites];
493 [keyEntry setObject:timeTable forKey:kMonitorTimeTable];
494 [keyEntry setObject:currentTime forKey:kMonitorLastWriteTimestamp];
495 [_monitor setObject:keyEntry forKey:key];
497 if([penalty_timeout intValue] != 0 || ((double)[currentTime timeIntervalSinceDate: lastWriteTimestamp] <= 60 && consecutiveWrites >= 5)){
498 if([penalty_timeout intValue] != 0)
499 secnotice("backoff","still in timeout, shouldn't write anything to KVS in this time period");
501 secnotice("backoff","monitor: keys have been written for 5 or more minutes, time to bump penalty timers");
502 [self increasePenalty:penalty_timeout key:key keyEntry:&keyEntry];
504 //keep writing freely but record it
505 else if((double)[currentTime timeIntervalSinceDate: lastWriteTimestamp] <= 60 && consecutiveWrites < 5){
506 secnotice("backoff","monitor: still writing freely");
513 - (NSDictionary*)recordHaltedValuesAndReturnValuesToSafelyWrite:(NSDictionary *)values
515 NSMutableDictionary *SafeMessages = [NSMutableDictionary dictionary];
516 [values enumerateKeysAndObjectsUsingBlock: ^(id key, id obj, BOOL *stop)
518 NSMutableDictionary *keyEntry = [_monitor objectForKey:key];
519 NSNumber *penalty = [keyEntry objectForKey:kMonitorPenaltyBoxKey];
520 if([penalty intValue] != 0){
521 NSMutableDictionary* existingQueue = [keyEntry valueForKey:kMonitorMessageQueue];
523 [existingQueue setObject:obj forKey:key];
525 [keyEntry setObject:existingQueue forKey:kMonitorMessageQueue];
526 [_monitor setObject:keyEntry forKey:key];
529 [SafeMessages setObject:obj forKey:key];
535 - (void)setObjectsFromDictionary:(NSDictionary *)values
537 NSUbiquitousKeyValueStore *store = [self cloudStore];
540 secnoticeq("dsid", "Ensure DSIDs match");
541 NSMutableDictionary *mutableValues = [NSMutableDictionary dictionaryWithCapacity:0];
543 secnotice("backoff","!!writing these keys to KVS!!: %@", values);
544 [values enumerateKeysAndObjectsUsingBlock: ^(id key, id obj, BOOL *stop)
546 if (obj == NULL || obj == [NSNull null])
547 [store removeObjectForKey:key];
549 else if([key isEqualToString: @"^OfficialDSID"]){
551 secnotice("dsid", "setting dsid to %@", obj);
554 else if([key isEqual: @"^Required"]){
555 if( [_dsid isEqualToString: @""]){
556 secdebug("dsid", "CloudKeychainProxy setting dsid to :%@ from securityd", obj);
560 else if(![_dsid isEqual: obj]){
561 secerror("Account DSIDs do not match, cloud keychain proxy: %@, securityd: %@", _dsid, obj);
562 secerror("Not going to write these: %@ into KVS!", values);
567 [ mutableValues setValue:obj forKey:key ];
571 secnoticeq("keytrace", "%@ sending: %@", self, [[mutableValues allKeys] componentsJoinedByString: @" "]);
572 [mutableValues enumerateKeysAndObjectsUsingBlock: ^(id key, id obj, BOOL *stop)
574 if (obj == NULL || obj == [NSNull null])
575 [store removeObjectForKey:key];
578 id oldObj = [store objectForKey:key];
579 if ([oldObj isEqual: obj]) {
580 if ([key hasPrefix:@"ak|"]) { // TODO: somewhat of a hack
581 // Fix KVS repeated message undelivery by sending a NULL first (deafness)
582 secnoticeq("keytrace", "forcing resend of peer-peer message: %@", key);
583 [store removeObjectForKey:key];
585 // Resending circle messages is bad
586 secnoticeq("keytrace", "repeated message: %@ (not peer-peer); not propogated", key);
589 [store setObject:obj forKey:key];
593 [self requestSynchronization:NO];
596 secdebug(XPROXYSCOPE, "%@ NULL? store: %@, values: %@", self, store, values);
599 - (void)requestSynchronization:(bool)force
603 secdebug(XPROXYSCOPE, "%@ synchronize (forced)", self);
604 [[self cloudStore] synchronize];
608 secdebug(XPROXYSCOPE, "%@ synchronize (soon)", self);
609 [[self cloudStore] synchronize];
613 - (NSUbiquitousKeyValueStore *)cloudStore
615 NSUbiquitousKeyValueStore *iCloudStore = [NSUbiquitousKeyValueStore defaultStore];
617 secerror("%s %@ NO NSUbiquitousKeyValueStore defaultStore", kWAIT2MINID, self);
623 Only call out to syncdefaultsd once every 5 seconds, since parameters can't change that
624 fast and callers expect synchonicity.
626 Since we don't actually get the values for the keys, just store off a timestamp.
629 // try to synchronize asap, and invoke the handler on completion to take incoming changes.
631 static bool isResubmitError(NSError* error) {
632 return error && (CFErrorGetCode((__bridge CFErrorRef) error) == UPDATE_RESUBMIT); // Why don't we check the domain?!
635 - (BOOL) AttemptSynchronization:(NSError **)failure
638 __block NSError *tempFailure = NULL;
639 int triesRemaining = 10;
641 NSUbiquitousKeyValueStore * store = [self cloudStore];
643 dispatch_semaphore_t freshSemaphore = dispatch_semaphore_create(0);
647 secnoticeq("fresh", "%s CALLING OUT TO syncdefaultsd SWCH: %@", kWAIT2MINID, self);
649 [store synchronizeWithCompletionHandler:^(NSError *error) {
652 secerrorq("%s RETURNING FROM syncdefaultsd SWCH: %@: %@", kWAIT2MINID, self, error);
654 secnoticeq("fresh", "%s RETURNING FROM syncdefaultsd SWCH: %@", kWAIT2MINID, self);
655 [store synchronize]; // Per olivier in <rdar://problem/13412631>, sync before getting values
656 secnoticeq("fresh", "%s RETURNING FROM syncdefaultsd SYNC: %@", kWAIT2MINID, self);
658 dispatch_semaphore_signal(freshSemaphore);
660 dispatch_semaphore_wait(freshSemaphore, DISPATCH_TIME_FOREVER);
661 } while (triesRemaining > 0 && isResubmitError(tempFailure));
663 if (isResubmitError(tempFailure)) {
664 secerrorq("%s Number of retry attempts to request freshness exceeded", kWAIT2MINID);
667 if (failure && (*failure == NULL)) {
668 *failure = tempFailure;
671 return tempFailure == nil;
674 static void wait_until(dispatch_time_t when) {
675 static dispatch_once_t once;
676 static dispatch_semaphore_t never_fires = nil;
677 dispatch_once(&once, ^{
678 never_fires = dispatch_semaphore_create(0);
681 dispatch_semaphore_wait(never_fires, when); // Will always timeout.
684 - (void)waitForSynchronization:(NSArray *)keys handler:(void (^)(NSDictionary *values, NSError *err))handler
688 NSError *err = [NSError errorWithDomain:(NSString *)NSPOSIXErrorDomain code:(NSInteger)ENOPROTOOPT userInfo:nil];
689 secerrorq("%s RETURNING TO securityd: %@ param error; calling handler", kWAIT2MINID, self);
694 secnoticeq("fresh", "%s Requesting freshness", kWAIT2MINID);
696 dispatch_async(_freshParamsQueue, ^{
697 // Hold off (keeping the queue occupied) until we hit the next time we can fresh.
698 wait_until(_nextFreshnessTime);
700 NSError *error = nil;
701 BOOL success = [self AttemptSynchronization:&error];
703 // Advance the next time can can call freshness.
704 const uint64_t delayBeforeCallingAgainInSeconds = 5ull * NSEC_PER_SEC;
705 _nextFreshnessTime = dispatch_time(DISPATCH_TIME_NOW, delayBeforeCallingAgainInSeconds);
707 dispatch_async(dispatch_get_main_queue(), ^{
708 NSDictionary * freshValues = success ? [self copyValues:[NSSet setWithArray:keys]] : @{};
709 handler(freshValues, error);
714 - (void)removeObjectForKey:(NSString *)keyToRemove
716 [[self cloudStore] removeObjectForKey:keyToRemove];
721 secdebug(XPROXYSCOPE, "%@ clearStore", self);
722 NSDictionary *dict = [[self cloudStore] dictionaryRepresentation];
723 NSArray *allKeys = [dict allKeys];
724 NSMutableArray* nullKeys = [NSMutableArray array];
726 _alwaysKeys = [NSMutableSet setWithArray:nullKeys];
727 _firstUnlockKeys = [NSMutableSet setWithArray:nullKeys];
728 _unlockedKeys = [NSMutableSet setWithArray:nullKeys];
729 _keyParameterKeys = @{};
733 [allKeys enumerateObjectsUsingBlock:^(id key, NSUInteger idx, BOOL *stop)
735 secdebug(XPROXYSCOPE, "%@ Clearing value for key %@", self, key);
736 [[self cloudStore] removeObjectForKey:(NSString *)key];
739 [self requestSynchronization:YES];
744 // MARK: ----- KVS key lists -----
749 return [[self cloudStore] objectForKey:key];
752 - (NSDictionary *)getAll
754 return [[self cloudStore] dictionaryRepresentation];
757 - (NSDictionary*) exportKeyInterests
759 return @{ kKeyAlwaysKeys:[_alwaysKeys allObjects],
760 kKeyFirstUnlockKeys:[_firstUnlockKeys allObjects],
761 kKeyUnlockedKeys:[_unlockedKeys allObjects],
762 kMonitorState:_monitor,
763 kKeyPendingKeys:[_pendingKeys allObjects],
764 kKeySyncWithPeersPending:[NSNumber numberWithBool:_syncWithPeersPending],
765 kKeyEnsurePeerRegistration:[NSNumber numberWithBool:_ensurePeerRegistration],
770 - (void) importKeyInterests: (NSDictionary*) interests
772 _alwaysKeys = [NSMutableSet setWithArray: interests[kKeyAlwaysKeys]];
773 _firstUnlockKeys = [NSMutableSet setWithArray: interests[kKeyFirstUnlockKeys]];
774 _unlockedKeys = [NSMutableSet setWithArray: interests[kKeyUnlockedKeys]];
776 _pendingKeys = [NSMutableSet setWithArray: interests[kKeyPendingKeys]];
777 _syncWithPeersPending = [interests[kKeySyncWithPeersPending] boolValue];
778 _ensurePeerRegistration = [interests[kKeyEnsurePeerRegistration] boolValue];
779 _dsid = interests[kKeyDSID];
780 _monitor = interests[kMonitorState];
782 _monitor = [NSMutableDictionary dictionary];
785 - (NSMutableSet *)copyAllKeys
787 NSMutableSet *allKeys = [NSMutableSet setWithSet: _alwaysKeys];
788 [allKeys unionSet: _firstUnlockKeys];
789 [allKeys unionSet: _unlockedKeys];
793 -(void)registerAtTimeKeys:(NSDictionary*)keyparms
795 NSArray *alwaysArray = [keyparms valueForKey: kKeyAlwaysKeys];
796 NSArray *firstUnlockedKeysArray = [keyparms valueForKey: kKeyFirstUnlockKeys];
797 NSArray *whenUnlockedKeysArray = [keyparms valueForKey: kKeyUnlockedKeys];
800 [_alwaysKeys unionSet: [NSMutableSet setWithArray: alwaysArray]];
801 if(firstUnlockedKeysArray)
802 [_firstUnlockKeys unionSet: [NSMutableSet setWithArray: firstUnlockedKeysArray]];
803 if(whenUnlockedKeysArray)
804 [_unlockedKeys unionSet: [NSMutableSet setWithArray: whenUnlockedKeysArray]];
808 - (void)registerKeys: (NSDictionary*)keys
810 secdebug(XPROXYSCOPE, "registerKeys: keys: %@", keys);
812 NSMutableSet *allOldKeys = [self copyAllKeys];
814 NSDictionary *keyparms = [keys valueForKey: [NSString stringWithUTF8String: kMessageKeyParameter]];
815 NSDictionary *circles = [keys valueForKey: [NSString stringWithUTF8String: kMessageCircle]];
816 NSDictionary *messages = [keys valueForKey: [NSString stringWithUTF8String: kMessageMessage]];
818 _alwaysKeys = [NSMutableSet set];
819 _firstUnlockKeys = [NSMutableSet set];
820 _unlockedKeys = [NSMutableSet set];
822 _keyParameterKeys = keyparms;
823 _circleKeys = circles;
824 _messageKeys = messages;
826 [self registerAtTimeKeys: _keyParameterKeys];
827 [self registerAtTimeKeys: _circleKeys];
828 [self registerAtTimeKeys: _messageKeys];
830 NSMutableSet *allNewKeys = [self copyAllKeys];
832 // Make sure keys we no longer care about are not pending
833 [_pendingKeys intersectSet:allNewKeys];
834 if (_shadowPendingKeys) {
835 [_shadowPendingKeys intersectSet:allNewKeys];
838 // All new keys only is new keys (remove old keys)
839 [allNewKeys minusSet:allOldKeys];
841 // Mark new keys pending, they're new!
842 NSMutableSet *newKeysForCurrentLockState = [self pendKeysAndGetNewlyPended:allNewKeys];
844 [self persistState]; // Before we might call out, save our state so we recover if we crash
846 [self intersectWithCurrentLockState: newKeysForCurrentLockState];
847 // TODO: Don't processPendingKeysForCurrentLockState if none of the new keys have values.
848 if ([newKeysForCurrentLockState count] != 0) {
849 [self processPendingKeysForCurrentLockState];
853 - (NSDictionary *)localNotification:(NSDictionary *)localNotificationDict outFlags:(int64_t *)outFlags
855 __block bool done = false;
856 __block int64_t returnedFlags = 0;
857 __block NSDictionary *responses = NULL;
859 CKDUserInteraction *cui = [CKDUserInteraction sharedInstance];
860 [cui requestShowNotification:localNotificationDict completion:^ bool (CFDictionaryRef userResponses, int64_t flags)
862 responses = [NSDictionary dictionaryWithDictionary:(__bridge NSDictionary *)userResponses];
863 returnedFlags = flags;
864 secdebug(XPROXYSCOPE, "%@ requestShowNotification: dict: %@, flags: %#llx", self, responses, returnedFlags);
869 // TODO: replace with e.g. dispatch calls to wait, or semaphore
874 secdebug(XPROXYSCOPE, "%@ outFlags: %#llx", self, returnedFlags);
875 *outFlags = returnedFlags;
880 - (void)saveToUbiquitousStore
882 [self requestSynchronization:NO];
885 // MARK: ----- Event Handling -----
887 - (void)streamEvent:(xpc_object_t)notification
889 #if (!TARGET_IPHONE_SIMULATOR)
890 const char *notificationName = xpc_dictionary_get_string(notification, "Notification");
891 if (!notificationName) {
892 } else if (strcmp(notificationName, kUserKeybagStateChangeNotification)==0) {
893 return [self keybagStateChange];
894 } else if (strcmp(notificationName, kCloudKeychainStorechangeChangeNotification)==0) {
895 return [self kvsStoreChange];
896 } else if (strcmp(notificationName, kNotifyTokenForceUpdate)==0) {
897 // DEBUG -- Possibly remove in future
898 return [self processAllItems];
900 const char *eventName = xpc_dictionary_get_string(notification, "XPCEventName");
901 char *desc = xpc_copy_description(notification);
902 secnotice("event", "%@ event: %s name: %s desc: %s", self, eventName, notificationName, desc);
908 - (void)iCloudAccountAvailabilityChanged:(NSNotification*)notification
911 Continuing this example, you’d then implement an iCloudAccountAvailabilityChanged: method that would:
913 Call the ubiquityIdentityToken method and store its return value.
914 Compare the new value to the previous value, to find out if the user logged out of their account or
915 logged in to a different account. If the previously-used account is now unavailable, save the current
916 state locally as needed, empty your iCloud-related data caches, and refresh all iCloud-related user interface elements.
918 id previCloudToken = currentiCloudToken;
919 currentiCloudToken = [[NSFileManager defaultManager] ubiquityIdentityToken];
920 if (previCloudToken != currentiCloudToken)
921 secnotice("event", "%@ iCloud account changed!", self);
923 secnotice("event", "%@ %@", self, notification);
926 - (void)cloudChanged:(NSNotification*)notification
929 Posted when the value of one or more keys in the local key-value store
930 changed due to incoming data pushed from iCloud. This notification is
931 sent only upon a change received from iCloud; it is not sent when your
934 The user info dictionary can contain the reason for the notification as
935 well as a list of which values changed, as follows:
937 The value of the NSUbiquitousKeyValueStoreChangeReasonKey key, when
938 present, indicates why the key-value store changed. Its value is one of
939 the constants in "Change Reason Values."
941 The value of the NSUbiquitousKeyValueStoreChangedKeysKey, when present,
942 is an array of strings, each the name of a key whose value changed. The
943 notification object is the NSUbiquitousKeyValueStore object whose contents
946 NSUbiquitousKeyValueStoreInitialSyncChange is only posted if there is any
947 local value that has been overwritten by a distant value. If there is no
948 conflict between the local and the distant values when doing the initial
949 sync (e.g. if the cloud has no data stored or the client has not stored
950 any data yet), you'll never see that notification.
952 NSUbiquitousKeyValueStoreInitialSyncChange implies an initial round trip
953 with server but initial round trip with server does not imply
954 NSUbiquitousKeyValueStoreInitialSyncChange.
956 os_activity_initiate("cloudChanged", OS_ACTIVITY_FLAG_DEFAULT, ^{
957 secdebug(XPROXYSCOPE, "%@ cloudChanged notification: %@", self, notification);
959 NSDictionary *userInfo = [notification userInfo];
960 NSNumber *reason = [userInfo objectForKey:NSUbiquitousKeyValueStoreChangeReasonKey];
961 if (reason) switch ([reason integerValue]) {
962 case NSUbiquitousKeyValueStoreInitialSyncChange:
963 case NSUbiquitousKeyValueStoreServerChange:
965 _seenKVSStoreChange = YES;
966 NSSet *keysChangedInCloud = [NSSet setWithArray:[userInfo objectForKey:NSUbiquitousKeyValueStoreChangedKeysKey]];
968 /* We are saying that we want to try processing a key no matter what,
969 * *if* it has changed in the cloud. */
970 [_pendingKeys minusSet:keysChangedInCloud];
972 NSSet *keysOfInterestThatChanged = [self pendKeysAndGetPendingForCurrentLockState:keysChangedInCloud];
973 NSMutableDictionary *changedValues = [self copyValues:keysOfInterestThatChanged];
974 if ([reason integerValue] == NSUbiquitousKeyValueStoreInitialSyncChange)
975 changedValues[(__bridge NSString*)kSOSKVSInitialSyncKey] = @"true";
977 secnotice("event", "%@ keysChangedInCloud: %@ keysOfInterest: %@", self, [[keysChangedInCloud allObjects] componentsJoinedByString: @" "], [[changedValues allKeys] componentsJoinedByString: @" "]);
978 if ([changedValues count])
979 [self processKeyChangedEvent:changedValues];
982 case NSUbiquitousKeyValueStoreQuotaViolationChange:
983 seccritical("%@ event received NSUbiquitousKeyValueStoreQuotaViolationChange", self);
985 case NSUbiquitousKeyValueStoreAccountChange:
986 // The primary account changed. We do not get this for password changes on the same account
987 secnotice("event", "%@ NSUbiquitousKeyValueStoreAccountChange", self);
988 NSDictionary *changedValues = nil;
990 changedValues = @{ (__bridge NSString*)kSOSKVSAccountChangedKey: _dsid };
992 changedValues = @{ (__bridge NSString*)kSOSKVSAccountChangedKey: @"true" };
994 [self processKeyChangedEvent:changedValues];
1000 - (void) doAfterFlush: (dispatch_block_t) block
1002 // Flush any pending communication to Securityd.
1004 dispatch_async(_calloutQueue, block);
1007 - (void) calloutWith: (void(^)(NSSet *pending, bool syncWithPeersPending, bool ensurePeerRegistration, dispatch_queue_t queue, void(^done)(NSSet *handledKeys, bool handledSyncWithPeers, bool handledEnsurePeerRegistration))) callout
1009 // In CKDKVSProxy's serial queue
1010 dispatch_queue_t ckdkvsproxy_queue = dispatch_get_main_queue();
1012 _oldInCallout = YES;
1014 // dispatch_get_global_queue - well-known global concurrent queue
1015 // dispatch_get_main_queue - default queue that is bound to the main thread
1016 xpc_transaction_begin();
1017 dispatch_async(_calloutQueue, ^{
1018 __block NSSet *myPending;
1019 __block bool mySyncWithPeersPending;
1020 __block bool myEnsurePeerRegistration;
1021 __block bool wasLocked;
1022 dispatch_sync(ckdkvsproxy_queue, ^{
1023 myPending = [_pendingKeys copy];
1024 mySyncWithPeersPending = _syncWithPeersPending;
1025 myEnsurePeerRegistration = _ensurePeerRegistration;
1026 wasLocked = _isLocked;
1030 secnotice("deaf", ">>>>>>>>>>> _oldInCallout is NO and we're heading in to the callout!");
1032 _shadowPendingKeys = [NSMutableSet set];
1033 _shadowSyncWithPeersPending = NO;
1036 callout(myPending, mySyncWithPeersPending, myEnsurePeerRegistration, ckdkvsproxy_queue, ^(NSSet *handledKeys, bool handledSyncWithPeers, bool handledEnsurePeerRegistration) {
1037 secdebug("event", "%@ %s%s before callout handled: %s%s", self, mySyncWithPeersPending ? "S" : "s", myEnsurePeerRegistration ? "E" : "e", handledSyncWithPeers ? "S" : "s", handledEnsurePeerRegistration ? "E" : "e");
1039 // In CKDKVSProxy's serial queue
1043 // Update ensurePeerRegistration
1044 _ensurePeerRegistration = ((myEnsurePeerRegistration && !handledEnsurePeerRegistration) || _shadowEnsurePeerRegistration);
1046 _shadowEnsurePeerRegistration = NO;
1048 if(_ensurePeerRegistration && !_isLocked)
1049 [self doEnsurePeerRegistration];
1051 // Update SyncWithPeers stuff.
1052 _syncWithPeersPending = ((mySyncWithPeersPending && (!handledSyncWithPeers)) || _shadowSyncWithPeersPending);
1054 _shadowSyncWithPeersPending = NO;
1055 if (handledSyncWithPeers)
1056 _lastSyncTime = dispatch_time(DISPATCH_TIME_NOW, 0);
1058 // Update pendingKeys and handle them
1059 [_pendingKeys minusSet: handledKeys];
1060 bool hadShadowPendingKeys = [_shadowPendingKeys count];
1061 // Move away shadownPendingKeys first, because pendKeysAndGetPendingForCurrentLockState
1062 // will look at them. See rdar://problem/20733166.
1063 NSSet *oldShadowPendingKeys = _shadowPendingKeys;
1064 _shadowPendingKeys = nil;
1066 NSSet *filteredKeys = [self pendKeysAndGetPendingForCurrentLockState:oldShadowPendingKeys];
1068 // Write state to disk
1069 [self persistState];
1071 // Handle shadow pended stuff
1072 if (_syncWithPeersPending && !_isLocked)
1073 [self scheduleSyncRequestTimer];
1074 /* We don't want to call processKeyChangedEvent if we failed to
1075 handle pending keys and the device didn't unlock nor receive
1076 any kvs changes while we were in our callout.
1077 Doing so will lead to securityd and CloudKeychainProxy
1078 talking to each other forever in a tight loop if securityd
1079 repeatedly returns an error processing the same message.
1080 Instead we leave any old pending keys until the next event. */
1081 if (hadShadowPendingKeys || (!_isLocked && wasLocked))
1082 [self processKeyChangedEvent:[self copyValues:filteredKeys]];
1084 xpc_transaction_end();
1089 - (void) sendKeysCallout: (NSSet *(^)(NSSet* pending, NSError** error)) handleKeys {
1090 [self calloutWith: ^(NSSet *pending, bool syncWithPeersPending, bool ensurePeerRegistration, dispatch_queue_t queue, void(^done)(NSSet *, bool, bool)) {
1091 NSError* error = NULL;
1093 NSSet * handled = handleKeys(pending, &error);
1095 dispatch_async(queue, ^{
1097 secerror("%@ ensurePeerRegistration failed: %@", self, error);
1100 done(handled, NO, NO);
1105 - (void) doEnsurePeerRegistration
1107 [self calloutWith:^(NSSet *pending, bool syncWithPeersPending, bool ensurePeerRegistration, dispatch_queue_t queue, void(^done)(NSSet *, bool, bool)) {
1108 CFErrorRef error = NULL;
1109 bool handledEnsurePeerRegistration = SOSCCProcessEnsurePeerRegistration(&error);
1110 secerror("%@ ensurePeerRegistration called, %@ (%@)", self, handledEnsurePeerRegistration ? @"success" : @"failure", error);
1111 dispatch_async(queue, ^{
1112 if (!handledEnsurePeerRegistration) {
1113 secerror("%@ ensurePeerRegistration failed: %@", self, error);
1116 done(nil, NO, handledEnsurePeerRegistration);
1117 CFReleaseSafe(error);
1122 - (void) doSyncWithAllPeers
1124 [self calloutWith:^(NSSet *pending, bool syncWithPeersPending, bool ensurePeerRegistration, dispatch_queue_t queue, void(^done)(NSSet *, bool, bool)) {
1125 CFErrorRef error = NULL;
1127 SyncWithAllPeersReason reason = SOSCCProcessSyncWithAllPeers(&error);
1128 dispatch_async(queue, ^{
1129 bool handledSyncWithPeers = NO;
1130 if (reason == kSyncWithAllPeersSuccess) {
1131 handledSyncWithPeers = YES;
1132 secnotice("event", "%@ syncWithAllPeers succeeded", self);
1133 } else if (reason == kSyncWithAllPeersLocked) {
1134 secnotice("event", "%@ syncWithAllPeers attempted while locked - waiting for unlock", self);
1135 handledSyncWithPeers = NO;
1136 [self updateIsLocked];
1137 } else if (reason == kSyncWithAllPeersOtherFail) {
1138 // Pretend we handled syncWithPeers, by pushing out the _lastSyncTime
1139 // This will cause us to wait for kMinSyncInterval seconds before
1140 // retrying, so we don't spam securityd if sync is failing
1141 secerror("%@ syncWithAllPeers %@, rescheduling timer", self, error);
1142 _lastSyncTime = dispatch_time(DISPATCH_TIME_NOW, 0);
1144 secerror("%@ syncWithAllPeers %@, unknown reason: %d", self, error, reason);
1147 done(nil, handledSyncWithPeers, false);
1148 CFReleaseSafe(error);
1155 secnotice("event", "%@ syncWithPeersPending: %d inCallout: %d isLocked: %d", self, _syncWithPeersPending, _inCallout, _isLocked);
1156 _syncTimerScheduled = NO;
1157 if(_ensurePeerRegistration){
1158 [self doEnsurePeerRegistration];
1160 if (_syncWithPeersPending && !_inCallout && !_isLocked){
1161 [self doSyncWithAllPeers];
1165 - (dispatch_time_t) nextSyncTime
1167 dispatch_time_t nextSync = dispatch_time(DISPATCH_TIME_NOW, kMinSyncDelay);
1169 // Don't sync again unless we waited at least kMinSyncInterval
1170 if (_lastSyncTime) {
1171 dispatch_time_t soonest = dispatch_time(_lastSyncTime, kMinSyncInterval);
1172 if (nextSync < soonest || _deadline < soonest) {
1173 secdebug("timer", "%@ backing off", self);
1178 // Don't delay more than kMaxSyncDelay after the first request.
1179 if (nextSync > _deadline) {
1180 secdebug("timer", "%@ hit deadline", self);
1184 // Bump the timer by kMinSyncDelay
1185 if (_syncTimerScheduled)
1186 secdebug("timer", "%@ bumped timer", self);
1188 secdebug("timer", "%@ scheduled timer", self);
1193 - (void)scheduleSyncRequestTimer
1195 dispatch_source_set_timer(_syncTimer, [self nextSyncTime], DISPATCH_TIME_FOREVER, kSyncTimerLeeway);
1196 _syncTimerScheduled = YES;
1199 - (void)requestSyncWithAllPeers // secd calling SOSCCSyncWithAllPeers invokes this
1201 #if !defined(NDEBUG)
1202 NSString *desc = [self description];
1205 if (!_syncWithPeersPending || (_inCallout && !_shadowSyncWithPeersPending))
1206 _deadline = dispatch_time(DISPATCH_TIME_NOW, kMaxSyncDelay);
1208 if (!_syncWithPeersPending) {
1209 _syncWithPeersPending = YES;
1210 [self persistState];
1214 _shadowSyncWithPeersPending = YES;
1215 else if (!_isLocked)
1216 [self scheduleSyncRequestTimer];
1218 secdebug("event", "%@ %@", desc, self);
1221 - (void)requestEnsurePeerRegistration // secd calling SOSCCSyncWithAllPeers invokes this
1223 #if !defined(NDEBUG)
1224 NSString *desc = [self description];
1228 _shadowEnsurePeerRegistration = YES;
1230 _ensurePeerRegistration = YES;
1232 [self doEnsurePeerRegistration];
1234 [self persistState];
1237 secdebug("event", "%@ %@", desc, self);
1241 - (BOOL) updateUnlockedSinceBoot
1243 CFErrorRef aksError = NULL;
1244 if (!SecAKSGetHasBeenUnlocked(&_unlockedSinceBoot, &aksError)) {
1245 secerror("%@ Got error from SecAKSGetHasBeenUnlocked: %@", self, aksError);
1246 CFReleaseSafe(aksError);
1252 - (BOOL) updateIsLocked
1254 CFErrorRef aksError = NULL;
1255 if (!SecAKSGetIsLocked(&_isLocked, &aksError)) {
1256 secerror("%@ Got error querying lock state: %@", self, aksError);
1257 CFReleaseSafe(aksError);
1261 _unlockedSinceBoot = YES;
1265 - (void) keybagStateChange
1267 os_activity_initiate("keybagStateChanged", OS_ACTIVITY_FLAG_DEFAULT, ^{
1268 BOOL wasLocked = _isLocked;
1269 if ([self updateIsLocked]) {
1270 if (wasLocked == _isLocked)
1271 secdebug("event", "%@ still %s ignoring", self, _isLocked ? "locked" : "unlocked");
1273 [self keybagDidLock];
1275 [self keybagDidUnlock];
1280 - (void) keybagDidLock
1282 secnotice("event", "%@", self);
1285 - (void) keybagDidUnlock
1287 secnotice("event", "%@", self);
1288 if (_ensurePeerRegistration) {
1289 [self doEnsurePeerRegistration];
1292 // First send changed keys to securityd so it can proccess updates
1293 [self processPendingKeysForCurrentLockState];
1295 // Then, tickle securityd to perform a sync if needed.
1296 if (_syncWithPeersPending && !_syncTimerScheduled) {
1297 [self doSyncWithAllPeers];
1301 - (void) kvsStoreChange {
1302 os_activity_initiate("kvsStoreChange", OS_ACTIVITY_FLAG_DEFAULT, ^{
1303 if (!_seenKVSStoreChange) {
1304 _seenKVSStoreChange = YES; // Only do this once
1305 secnotice("event", "%@ received darwin notification before first NSNotification", self);
1306 // TODO This might not be needed if we always get the NSNotification
1307 // deleived even if we were launched due to a kvsStoreChange
1308 // Send all keys for current lock state to securityd so it can proccess them
1309 [self pendKeysAndGetNewlyPended: [self copyAllKeys]];
1310 [self processPendingKeysForCurrentLockState];
1312 secdebug("event", "%@ ignored, waiting for NSNotification", self);
1318 // MARK: ----- Key Filtering -----
1321 - (NSSet*) keysForCurrentLockState
1323 secdebug("filtering", "%@ Filtering: unlockedSinceBoot: %d\n unlocked: %d\n, keysOfInterest: <%@>", self, (int) _unlockedSinceBoot, (int) !_isLocked, [SOSPersistentState dictionaryDescription: [self exportKeyInterests]]);
1325 NSMutableSet *currentStateKeys = [NSMutableSet setWithSet: _alwaysKeys];
1326 if (_unlockedSinceBoot)
1327 [currentStateKeys unionSet: _firstUnlockKeys];
1330 [currentStateKeys unionSet: _unlockedKeys];
1332 return currentStateKeys;
1335 - (NSMutableSet*) pendKeysAndGetNewlyPended: (NSSet*) keysToPend
1337 NSMutableSet *newlyPendedKeys = [keysToPend mutableCopy];
1338 [newlyPendedKeys minusSet: _pendingKeys];
1339 if (_shadowPendingKeys) {
1340 [newlyPendedKeys minusSet: _shadowPendingKeys];
1343 [_pendingKeys unionSet:keysToPend];
1344 if (_shadowPendingKeys) {
1345 [_shadowPendingKeys unionSet:keysToPend];
1348 return newlyPendedKeys;
1351 - (void) intersectWithCurrentLockState: (NSMutableSet*) set
1353 [set intersectSet: [self keysForCurrentLockState]];
1356 - (NSMutableSet*) pendingKeysForCurrentLockState
1358 NSMutableSet * result = [_pendingKeys mutableCopy];
1359 [self intersectWithCurrentLockState:result];
1363 - (NSMutableSet*) pendKeysAndGetPendingForCurrentLockState: (NSSet*) startingSet
1365 [self pendKeysAndGetNewlyPended: startingSet];
1367 return [self pendingKeysForCurrentLockState];
1370 - (NSMutableDictionary *)copyValues:(NSSet*)keysOfInterest
1372 // Grab values from KVS.
1373 NSUbiquitousKeyValueStore *store = [self cloudStore];
1374 NSMutableDictionary *changedValues = [NSMutableDictionary dictionaryWithCapacity:0];
1375 [keysOfInterest enumerateObjectsUsingBlock:^(id obj, BOOL *stop)
1377 NSString* key = (NSString*) obj;
1378 id objval = [store objectForKey:key];
1379 if (!objval) objval = [NSNull null];
1381 [changedValues setObject:objval forKey:key];
1382 secdebug(XPROXYSCOPE, "%@ storeChanged updated value for %@", self, key);
1384 return changedValues;
1388 During RegisterKeys, separate keys-of-interest into three disjoint sets:
1389 - keys that we always want to be notified about; this means we can get the
1391 - keys that require the device to have been unlocked at least once
1392 - keys that require the device to be unlocked now
1394 Typically, the sets of keys will be:
1400 The caller is responsible for making sure that the keys in e.g. alwaysKeys are
1401 values that can be handled at any time (that is, not when unlocked)
1403 Each time we get a notification from ubiquity that keys have changed, we need to
1404 see if anything of interest changed. If we don't care, then done.
1406 For each key-of-interest that changed, we either notify the client that things
1407 changed, or add it to a pendingNotifications list. If the notification to the
1408 client fails, also add it to the pendingNotifications list. This pending list
1409 should be written to persistent storage and consulted any time we either get an
1410 item changed notification, or get a stream event signalling a change in lock state.
1412 We can notify the client either through XPC if a connection is set up, or call a
1413 routine in securityd to launch it.
1417 - (void)processKeyChangedEvent:(NSDictionary *)changedValues
1419 NSMutableDictionary* filtered = [NSMutableDictionary dictionary];
1421 NSMutableArray* nullKeys = [NSMutableArray array];
1422 // Remove nulls because we don't want them in securityd.
1423 [changedValues enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
1424 if (obj == [NSNull null])
1425 [nullKeys addObject:key];
1427 filtered[key] = obj;
1430 if ([nullKeys count])
1431 [_pendingKeys minusSet: [NSSet setWithArray: nullKeys]];
1433 if([filtered count] != 0 ){
1434 [self sendKeysCallout:^NSSet *(NSSet *pending, NSError** error) {
1435 CFErrorRef cf_error = NULL;
1436 NSArray* handledMessage = (__bridge_transfer NSArray*) _SecKeychainSyncUpdateMessage((__bridge CFDictionaryRef)filtered, &cf_error);
1437 NSError *updateError = (__bridge_transfer NSError*)cf_error;
1439 *error = updateError;
1441 secnoticeq("keytrace", "%@ misc handled: %@ null: %@ pending: %@", self,
1442 [handledMessage componentsJoinedByString: @" "],
1443 [nullKeys componentsJoinedByString: @" "],
1444 [[_pendingKeys allObjects] componentsJoinedByString: @" "]);
1446 return handledMessage ? [NSSet setWithArray: handledMessage] : nil;
1449 secnoticeq("keytrace", "%@ null: %@ pending: %@", self,
1450 [nullKeys componentsJoinedByString: @" "],
1451 [[_pendingKeys allObjects] componentsJoinedByString: @" "]);
1455 - (void) processPendingKeysForCurrentLockState
1457 [self processKeyChangedEvent: [self copyValues: [self pendingKeysForCurrentLockState]]];