2 * Copyright (c) 2012-2014,2016 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/Foundation.h>
31 #import <utilities/debugging.h>
32 #import <os/activity.h>
34 #import "CKDKVSProxy.h"
35 #import "CKDPersistentState.h"
36 #import "CKDKVSStore.h"
37 #import "CKDSecuritydAccount.h"
39 #include <Security/SecureObjectSync/SOSARCDefines.h>
40 #include <Security/SecureObjectSync/SOSKVSKeys.h>
42 #include "SOSCloudKeychainConstants.h"
44 #include <utilities/SecAKSWrappers.h>
47 The total space available in your app’s iCloud key-value storage is 1 MB.
48 The maximum number of keys you can specify is 1024, and the size limit for
49 each value associated with a key is 1 MB. So, for example, if you store a
50 single large value of 1 MB for a single key, that consumes your total
51 available storage. If you store 1 KB of data for each key, you can use
52 1,000 key-value pairs.
55 static const char *kStreamName = "com.apple.notifyd.matching";
57 static NSString *kKeyKeyParameterKeys = @"KeyParameterKeys";
58 static NSString *kKeyCircleKeys = @"CircleKeys";
59 static NSString *kKeyMessageKeys = @"MessageKeys";
61 static NSString *kKeyAlwaysKeys = @"AlwaysKeys";
62 static NSString *kKeyFirstUnlockKeys = @"FirstUnlockKeys";
63 static NSString *kKeyUnlockedKeys = @"UnlockedKeys";
64 static NSString *kKeyPendingKeys = @"PendingKeys";
65 static NSString *kKeyUnsentChangedKeys = @"unsentChangedKeys";
66 static NSString *kKeyUnlockNotificationRequested = @"unlockNotificationRequested";
67 static NSString *kKeySyncWithPeersPending = @"SyncWithPeersPending";
68 static NSString *kKeyEnsurePeerRegistration = @"EnsurePeerRegistration";
69 static NSString *kKeyDSID = @"DSID";
70 static NSString *kMonitorState = @"MonitorState";
72 static NSString *kMonitorPenaltyBoxKey = @"Penalty";
73 static NSString *kMonitorMessageKey = @"Message";
74 static NSString *kMonitorConsecutiveWrites = @"ConsecutiveWrites";
75 static NSString *kMonitorLastWriteTimestamp = @"LastWriteTimestamp";
76 static NSString *kMonitorMessageQueue = @"MessageQueue";
77 static NSString *kMonitorPenaltyTimer = @"PenaltyTimer";
78 static NSString *kMonitorDidWriteDuringPenalty = @"DidWriteDuringPenalty";
80 static NSString *kMonitorTimeTable = @"TimeTable";
81 static NSString *kMonitorFirstMinute = @"AFirstMinute";
82 static NSString *kMonitorSecondMinute = @"BSecondMinute";
83 static NSString *kMonitorThirdMinute = @"CThirdMinute";
84 static NSString *kMonitorFourthMinute = @"DFourthMinute";
85 static NSString *kMonitorFifthMinute = @"EFifthMinute";
86 static NSString *kMonitorWroteInTimeSlice = @"TimeSlice";
88 #define kSecServerKeychainChangedNotification "com.apple.security.keychainchanged"
90 static int max_penalty_timeout = 32;
91 static int seconds_per_minute = 60;
93 static const int64_t kMinSyncDelay = (NSEC_PER_MSEC * 500); // 500ms minimum delay before a syncWithAllPeers call.
94 static const int64_t kMaxSyncDelay = (NSEC_PER_SEC * 5); // 5s maximun delay for a given request
95 static const int64_t kMinSyncInterval = (NSEC_PER_SEC * 15); // 15s minimum time between successive syncWithAllPeers calls.
96 static const int64_t kSyncTimerLeeway = (NSEC_PER_MSEC * 250); // 250ms leeway for sync events.
98 static NSString* asNSString(NSObject* object) {
99 return [object isKindOfClass:[NSString class]] ? (NSString*) object : nil;
102 @interface NSMutableDictionary (FindAndRemove)
103 -(NSObject*)extractObjectForKey:(NSString*)key;
106 @implementation NSMutableDictionary (FindAndRemove)
107 -(NSObject*)extractObjectForKey:(NSString*)key {
108 NSObject* result = [self objectForKey:key];
109 [self removeObjectForKey: key];
114 @implementation UbiqitousKVSProxy
118 [SOSPersistentState setRegisteredKeys:[self exportKeyInterests]];
121 + (UbiqitousKVSProxy *) sharedKVSProxy
123 static UbiqitousKVSProxy *sharedKVSProxy;
124 if (!sharedKVSProxy) {
125 static dispatch_once_t onceToken;
126 dispatch_once(&onceToken, ^{
127 sharedKVSProxy = [[self alloc] initWithAccount: [CKDSecuritydAccount securitydAccount]
128 store: [CKDKVSStore kvsInterface]];
131 return sharedKVSProxy;
134 - (id)initWithAccount:(NSObject<CKDAccount>*) account
135 store:(NSObject<CKDStore>*) store
137 if (self = [super init])
139 secnotice("event", "%@ start", self);
141 #if !(TARGET_OS_EMBEDDED)
142 // rdar://problem/26247270
143 if (geteuid() == 0) {
144 secerror("Cannot run CloudKeychainProxy as root");
148 _unlockedSinceBoot = NO;
149 _isLocked = YES; // until we know for sure
150 _ensurePeerRegistration = NO;
151 _syncWithPeersPending = NO;
157 _calloutQueue = dispatch_queue_create("CKDCallout", DISPATCH_QUEUE_SERIAL);
158 _ckdkvsproxy_queue = dispatch_get_main_queue();
160 _freshnessCompletions = [NSMutableArray<FreshnessResponseBlock> array];
162 _syncTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, _ckdkvsproxy_queue);
163 dispatch_source_set_timer(_syncTimer, DISPATCH_TIME_FOREVER, DISPATCH_TIME_FOREVER, kSyncTimerLeeway);
164 dispatch_source_set_event_handler(_syncTimer, ^{
167 dispatch_resume(_syncTimer);
169 _monitor = [NSMutableDictionary dictionary];
171 int notificationToken;
172 notify_register_dispatch(kSecServerKeychainChangedNotification, ¬ificationToken, _ckdkvsproxy_queue,
173 ^ (int token __unused)
175 secinfo("backoff", "keychain changed, wiping backoff monitor state");
176 _monitor = [NSMutableDictionary dictionary];
179 [self importKeyInterests: [SOSPersistentState registeredKeys]];
181 // Register for lock state changes
182 xpc_set_event_stream_handler(kStreamName, _ckdkvsproxy_queue,
183 ^(xpc_object_t notification){
184 [self streamEvent:notification];
188 [self updateUnlockedSinceBoot];
189 [self updateIsLocked];
191 [self keybagDidUnlock];
193 [[self store] connectToProxy: self];
195 secdebug(XPROXYSCOPE, "%@ done", self);
200 - (NSString *)description
202 return [NSString stringWithFormat:@"<%s%s%s%s%s%s%s%s%s%s%s>",
203 _isLocked ? "L" : "U",
204 _unlockedSinceBoot ? "B" : "-",
205 _seenKVSStoreChange ? "K" : "-",
206 _syncTimerScheduled ? "T" : "-",
207 _syncWithPeersPending ? "s" : "-",
208 _ensurePeerRegistration ? "e" : "-",
209 [_pendingKeys count] ? "p" : "-",
210 _inCallout ? "C" : "-",
211 _shadowSyncWithPeersPending ? "S" : "-",
212 _shadowEnsurePeerRegistration ? "E" : "-",
213 [_shadowPendingKeys count] ? "P" : "-"];
217 // MARK: XPC Function commands
219 - (void) clearStore {
220 [self.store removeAllObjects];
223 - (void)synchronizeStore {
224 [self.store pushWrites];
227 - (id) objectForKey: (NSString*) key {
228 return [self.store objectForKey: key];
230 - (NSDictionary<NSString *, id>*) copyAsDictionary {
231 return [self.store copyAsDictionary];
237 - (void)processAllItems
239 NSDictionary *allItems = [self.store copyAsDictionary];
242 secnotice("event", "%@ sending: %@", self, [[allItems allKeys] componentsJoinedByString: @" "]);
243 [self processKeyChangedEvent:allItems];
246 secdebug(XPROXYSCOPE, "%@ No items in KVS", self);
251 secdebug(XPROXYSCOPE, "%@", self);
252 [[NSNotificationCenter defaultCenter] removeObserver:self
253 name:NSUbiquitousKeyValueStoreDidChangeExternallyNotification
256 [[NSNotificationCenter defaultCenter] removeObserver:self
257 name:NSUbiquityIdentityDidChangeNotification
261 // MARK: Penalty measurement and handling
262 -(dispatch_source_t)setNewTimer:(int)timeout key:(NSString*)key
264 __block dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, _ckdkvsproxy_queue);
265 dispatch_source_set_timer(timer, dispatch_time(DISPATCH_TIME_NOW, timeout * NSEC_PER_SEC * seconds_per_minute), DISPATCH_TIME_FOREVER, kSyncTimerLeeway);
266 dispatch_source_set_event_handler(timer, ^{
267 [self penaltyTimerFired:key];
269 dispatch_resume(timer);
273 -(void) increasePenalty:(NSNumber*)currentPenalty key:(NSString*)key keyEntry:(NSMutableDictionary**)keyEntry
275 secnotice("backoff", "increasing penalty!");
277 if([currentPenalty intValue] == max_penalty_timeout){
278 newPenalty = max_penalty_timeout;
280 else if ([currentPenalty intValue] == 0)
283 newPenalty = [currentPenalty intValue]*2;
285 secnotice("backoff", "key %@, waiting %d minutes long to send next messages", key, newPenalty);
287 NSNumber* penalty_timeout = [[NSNumber alloc]initWithInt:newPenalty];
288 dispatch_source_t existingTimer = [*keyEntry valueForKey:kMonitorPenaltyTimer];
290 if(existingTimer != nil){
291 [*keyEntry removeObjectForKey:kMonitorPenaltyTimer];
292 dispatch_suspend(existingTimer);
293 dispatch_source_set_timer(existingTimer,dispatch_time(DISPATCH_TIME_NOW, newPenalty * NSEC_PER_SEC * seconds_per_minute), DISPATCH_TIME_FOREVER, kSyncTimerLeeway);
294 dispatch_resume(existingTimer);
295 [*keyEntry setObject:existingTimer forKey:kMonitorPenaltyTimer];
298 dispatch_source_t timer = [self setNewTimer:newPenalty key:key];
299 [*keyEntry setObject:timer forKey:kMonitorPenaltyTimer];
302 [*keyEntry setObject:penalty_timeout forKey:kMonitorPenaltyBoxKey];
303 [_monitor setObject:*keyEntry forKey:key];
306 -(void) decreasePenalty:(NSNumber*)currentPenalty key:(NSString*)key keyEntry:(NSMutableDictionary**)keyEntry
309 secnotice("backoff","decreasing penalty!");
310 if([currentPenalty intValue] == 0 || [currentPenalty intValue] == 1)
313 newPenalty = [currentPenalty intValue]/2;
315 secnotice("backoff","key %@, waiting %d minutes long to send next messages", key, newPenalty);
317 NSNumber* penalty_timeout = [[NSNumber alloc]initWithInt:newPenalty];
319 dispatch_source_t existingTimer = [*keyEntry valueForKey:kMonitorPenaltyTimer];
320 if(existingTimer != nil){
321 [*keyEntry removeObjectForKey:kMonitorPenaltyTimer];
322 dispatch_suspend(existingTimer);
324 dispatch_source_set_timer(existingTimer,dispatch_time(DISPATCH_TIME_NOW, newPenalty * NSEC_PER_SEC * seconds_per_minute), DISPATCH_TIME_FOREVER, kSyncTimerLeeway);
325 dispatch_resume(existingTimer);
326 [*keyEntry setObject:existingTimer forKey:kMonitorPenaltyTimer];
329 dispatch_resume(existingTimer);
330 dispatch_source_cancel(existingTimer);
335 dispatch_source_t timer = [self setNewTimer:newPenalty key:key];
336 [*keyEntry setObject:timer forKey:kMonitorPenaltyTimer];
340 [*keyEntry setObject:penalty_timeout forKey:kMonitorPenaltyBoxKey];
341 [_monitor setObject:*keyEntry forKey:key];
345 - (void)penaltyTimerFired:(NSString*)key
347 secnotice("backoff", "key: %@, !!!!!!!!!!!!!!!!penalty timeout is up!!!!!!!!!!!!", key);
349 NSMutableDictionary *keyEntry = [_monitor objectForKey:key];
350 NSMutableDictionary *queuedMessages = [keyEntry objectForKey:kMonitorMessageQueue];
351 secnotice("backoff","key: %@, queuedMessages: %@", key, queuedMessages);
352 if(queuedMessages && [queuedMessages count] != 0){
353 secnotice("backoff","key: %@, message queue not empty, writing to KVS!", key);
354 [self setObjectsFromDictionary:queuedMessages];
355 [keyEntry setObject:[NSMutableDictionary dictionary] forKey:kMonitorMessageQueue];
358 NSNumber *penalty_timeout = [keyEntry valueForKey:kMonitorPenaltyBoxKey];
359 secnotice("backoff", "key: %@, current penalty timeout: %@", key, penalty_timeout);
361 NSString* didWriteDuringTimeout = [keyEntry objectForKey:kMonitorDidWriteDuringPenalty];
362 if( didWriteDuringTimeout && [didWriteDuringTimeout isEqualToString:@"YES"] )
364 //increase timeout since we wrote during out penalty timeout
365 [self increasePenalty:penalty_timeout key:key keyEntry:&keyEntry];
368 //decrease timeout since we successfully wrote messages out
369 [self decreasePenalty:penalty_timeout key:key keyEntry:&keyEntry];
372 //resetting the check
373 [keyEntry setObject: @"NO" forKey:kMonitorDidWriteDuringPenalty];
375 //recompute the timetable and number of consecutive writes to KVS
376 NSMutableDictionary *timetable = [keyEntry valueForKey:kMonitorTimeTable];
377 NSNumber *consecutiveWrites = [keyEntry valueForKey:kMonitorConsecutiveWrites];
378 [self recordTimestampForAppropriateInterval:&timetable key:key consecutiveWrites:&consecutiveWrites];
380 [keyEntry setObject:consecutiveWrites forKey:kMonitorConsecutiveWrites];
381 [keyEntry setObject:timetable forKey:kMonitorTimeTable];
382 [_monitor setObject:keyEntry forKey:key];
385 -(NSMutableDictionary*)initializeTimeTable:(NSString*)key
387 NSDate *currentTime = [NSDate date];
388 NSMutableDictionary *firstMinute = [NSMutableDictionary dictionaryWithObjectsAndKeys:[currentTime dateByAddingTimeInterval: seconds_per_minute], kMonitorFirstMinute, @"YES", kMonitorWroteInTimeSlice, nil];
389 NSMutableDictionary *secondMinute = [NSMutableDictionary dictionaryWithObjectsAndKeys:[currentTime dateByAddingTimeInterval: seconds_per_minute * 2],kMonitorSecondMinute, @"NO", kMonitorWroteInTimeSlice, nil];
390 NSMutableDictionary *thirdMinute = [NSMutableDictionary dictionaryWithObjectsAndKeys:[currentTime dateByAddingTimeInterval: seconds_per_minute * 3], kMonitorThirdMinute, @"NO",kMonitorWroteInTimeSlice, nil];
391 NSMutableDictionary *fourthMinute = [NSMutableDictionary dictionaryWithObjectsAndKeys:[currentTime dateByAddingTimeInterval: seconds_per_minute * 4],kMonitorFourthMinute, @"NO", kMonitorWroteInTimeSlice, nil];
392 NSMutableDictionary *fifthMinute = [NSMutableDictionary dictionaryWithObjectsAndKeys:[currentTime dateByAddingTimeInterval: seconds_per_minute * 5], kMonitorFifthMinute, @"NO", kMonitorWroteInTimeSlice, nil];
394 NSMutableDictionary *timeTable = [NSMutableDictionary dictionaryWithObjectsAndKeys: firstMinute, kMonitorFirstMinute,
395 secondMinute, kMonitorSecondMinute,
396 thirdMinute, kMonitorThirdMinute,
397 fourthMinute, kMonitorFourthMinute,
398 fifthMinute, kMonitorFifthMinute, nil];
402 - (void)initializeKeyEntry:(NSString*)key
404 NSMutableDictionary *timeTable = [self initializeTimeTable:key];
405 NSDate *currentTime = [NSDate date];
407 NSMutableDictionary *keyEntry = [NSMutableDictionary dictionaryWithObjectsAndKeys: key, kMonitorMessageKey, @0, kMonitorConsecutiveWrites, currentTime, kMonitorLastWriteTimestamp, @0, kMonitorPenaltyBoxKey, timeTable, kMonitorTimeTable,[NSMutableDictionary dictionary], kMonitorMessageQueue, nil];
409 [_monitor setObject:keyEntry forKey:key];
413 - (void)recordTimestampForAppropriateInterval:(NSMutableDictionary**)timeTable key:(NSString*)key consecutiveWrites:(NSNumber**)consecutiveWrites
415 NSDate *currentTime = [NSDate date];
416 __block int cWrites = [*consecutiveWrites intValue];
417 __block BOOL foundTimeSlot = NO;
418 __block NSMutableDictionary *previousTable = nil;
419 NSArray *sorted = [[*timeTable allKeys] sortedArrayUsingSelector:@selector(compare:)];
420 [sorted enumerateObjectsUsingBlock:^(id sortedKey, NSUInteger idx, BOOL *stop)
422 if(foundTimeSlot == YES)
424 [*timeTable enumerateKeysAndObjectsUsingBlock: ^(id minute, id obj, BOOL *stop2)
426 if(foundTimeSlot == YES)
428 if([sortedKey isEqualToString:minute]){
429 NSMutableDictionary *minutesTable = (NSMutableDictionary*)obj;
430 NSString *minuteKey = (NSString*)minute;
431 NSDate *date = [minutesTable valueForKey:minuteKey];
432 if([date compare:currentTime] == NSOrderedDescending){
434 NSString* written = [minutesTable valueForKey:kMonitorWroteInTimeSlice];
435 if([written isEqualToString:@"NO"]){
436 [minutesTable setObject:@"YES" forKey:kMonitorWroteInTimeSlice];
437 if(previousTable != nil){
438 written = [previousTable valueForKey:kMonitorWroteInTimeSlice];
439 if([written isEqualToString:@"YES"]){
442 else if ([written isEqualToString:@"NO"]){
449 previousTable = minutesTable;
454 if(foundTimeSlot == NO){
455 //reset the time table
456 secnotice("backoff","didn't find a time slot, resetting the table");
457 NSMutableDictionary *lastTable = [*timeTable valueForKey:kMonitorFifthMinute];
458 NSDate *lastDate = [lastTable valueForKey:kMonitorFifthMinute];
460 if((double)[currentTime timeIntervalSinceDate: lastDate] >= seconds_per_minute){
461 *consecutiveWrites = [[NSNumber alloc]initWithInt:0];
464 NSString* written = [lastTable valueForKey:kMonitorWroteInTimeSlice];
465 if([written isEqualToString:@"YES"]){
467 *consecutiveWrites = [[NSNumber alloc]initWithInt:cWrites];
470 *consecutiveWrites = [[NSNumber alloc]initWithInt:0];
474 *timeTable = [self initializeTimeTable:key];
477 *consecutiveWrites = [[NSNumber alloc]initWithInt:cWrites];
479 - (void)recordWriteToKVS:(NSDictionary *)values
481 if([_monitor count] == 0){
482 [values enumerateKeysAndObjectsUsingBlock: ^(id key, id obj, BOOL *stop)
484 [self initializeKeyEntry: key];
488 [values enumerateKeysAndObjectsUsingBlock: ^(id key, id obj, BOOL *stop)
490 NSMutableDictionary *keyEntry = [_monitor objectForKey:key];
492 [self initializeKeyEntry: key];
495 NSNumber *penalty_timeout = [keyEntry objectForKey:kMonitorPenaltyBoxKey];
496 NSDate *lastWriteTimestamp = [keyEntry objectForKey:kMonitorLastWriteTimestamp];
497 NSMutableDictionary *timeTable = [keyEntry objectForKey: kMonitorTimeTable];
498 NSNumber *existingWrites = [keyEntry objectForKey: kMonitorConsecutiveWrites];
499 NSDate *currentTime = [NSDate date];
501 [self recordTimestampForAppropriateInterval:&timeTable key:key consecutiveWrites:&existingWrites];
503 int consecutiveWrites = [existingWrites intValue];
504 secnotice("backoff","consecutive writes: %d", consecutiveWrites);
505 [keyEntry setObject:existingWrites forKey:kMonitorConsecutiveWrites];
506 [keyEntry setObject:timeTable forKey:kMonitorTimeTable];
507 [keyEntry setObject:currentTime forKey:kMonitorLastWriteTimestamp];
508 [_monitor setObject:keyEntry forKey:key];
510 if([penalty_timeout intValue] != 0 || ((double)[currentTime timeIntervalSinceDate: lastWriteTimestamp] <= 60 && consecutiveWrites >= 5)){
511 if([penalty_timeout intValue] != 0 && consecutiveWrites == 5){
512 secnotice("backoff","written for 5 consecutive minutes, time to start throttling");
513 [self increasePenalty:penalty_timeout key:key keyEntry:&keyEntry];
516 secnotice("backoff","monitor: keys have been written for 5 or more minutes, recording we wrote during timeout");
518 //record we wrote during a timeout
519 [keyEntry setObject: @"YES" forKey:kMonitorDidWriteDuringPenalty];
521 //keep writing freely but record it
522 else if((double)[currentTime timeIntervalSinceDate: lastWriteTimestamp] <= 60 && consecutiveWrites < 5){
523 secnotice("backoff","monitor: still writing freely");
530 - (NSDictionary*)recordHaltedValuesAndReturnValuesToSafelyWrite:(NSDictionary *)values
532 NSMutableDictionary *SafeMessages = [NSMutableDictionary dictionary];
533 [values enumerateKeysAndObjectsUsingBlock: ^(id key, id obj, BOOL *stop)
535 NSMutableDictionary *keyEntry = [_monitor objectForKey:key];
536 NSNumber *penalty = [keyEntry objectForKey:kMonitorPenaltyBoxKey];
537 if([penalty intValue] != 0){
538 NSMutableDictionary* existingQueue = [keyEntry valueForKey:kMonitorMessageQueue];
540 [existingQueue setObject:obj forKey:key];
542 [keyEntry setObject:existingQueue forKey:kMonitorMessageQueue];
543 [_monitor setObject:keyEntry forKey:key];
546 [SafeMessages setObject:obj forKey:key];
552 // MARK: Object setting
555 - (void)setStoreObjectsFromDictionary:(NSDictionary *)values
558 secdebug(XPROXYSCOPE, "%@ NULL? values: %@", self, values);
562 NSMutableDictionary<NSString*, NSObject*> *mutableValues = [values mutableCopy];
563 NSString* newDSID = asNSString([mutableValues extractObjectForKey:(__bridge NSString*) kSOSKVSOfficialDSIDKey]);
568 NSString* requiredDSID = asNSString([mutableValues extractObjectForKey:(__bridge NSString*) kSOSKVSRequiredKey]);
570 if (_dsid == nil || [_dsid isEqualToString: @""]) {
571 secdebug("dsid", "CloudKeychainProxy setting dsid to :%@ from securityd", requiredDSID);
572 _dsid = requiredDSID;
573 } else if (![_dsid isEqual: requiredDSID]) {
574 secerror("Account DSIDs do not match, cloud keychain proxy: %@, securityd: %@", _dsid, requiredDSID);
575 secerror("Not going to write these: %@ into KVS!", values);
578 secnoticeq("dsid", "DSIDs match, writing");
582 secnoticeq("keytrace", "%@ sending: %@", self, [[mutableValues allKeys] componentsJoinedByString: @" "]);
583 [mutableValues enumerateKeysAndObjectsUsingBlock: ^(id key, id obj, BOOL *stop)
585 if (obj == NULL || obj == [NSNull null]) {
586 [self.store removeObjectForKey:key];
588 if ([key hasPrefix:@"ak|"]) { // TODO: somewhat of a hack
589 id oldObj = [self.store objectForKey:key];
590 if ([oldObj isEqual: obj]) {
591 // Fix KVS repeated message undelivery by sending a NULL first (deafness)
592 secnoticeq("keytrace", "forcing resend of key write: %@", key);
593 [self.store removeObjectForKey:key];
596 [self.store setObject:obj forKey:key];
600 [self.store pushWrites];
603 - (void)setObjectsFromDictionary:(NSDictionary<NSString*, NSObject*> *)values
605 [[UbiqitousKVSProxy sharedKVSProxy] recordWriteToKVS: values];
606 NSDictionary *safeValues = [[UbiqitousKVSProxy sharedKVSProxy] recordHaltedValuesAndReturnValuesToSafelyWrite: values];
607 if([safeValues count] !=0){
608 [[UbiqitousKVSProxy sharedKVSProxy] setStoreObjectsFromDictionary:safeValues];
612 - (void)waitForSynchronization:(void (^)(NSDictionary<NSString*, NSObject*> *results, NSError *err))handler
614 secnoticeq("fresh", "%s Requesting WFS", kWAIT2MINID);
616 [_freshnessCompletions addObject: ^(bool success, NSError *error){
617 secnoticeq("fresh", "%s WFS Done", kWAIT2MINID);
621 if ([self.freshnessCompletions count] == 1) {
622 // We can't talk to synchronize on the _ckdkvsproxy_queue or we deadlock,
623 // bounce to a global concurrent queue
624 dispatch_after(_nextFreshnessTime, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
625 NSError *error = nil;
626 bool success = [self.store pullUpdates:&error];
628 dispatch_async(_ckdkvsproxy_queue, ^{
629 [self waitForSyncDone: success error: error];
635 - (void) waitForSyncDone: (bool) success error: (NSError*) error{
637 const uint64_t delayBeforeCallingAgainInSeconds = 5ull * NSEC_PER_SEC;
638 _nextFreshnessTime = dispatch_time(DISPATCH_TIME_NOW, delayBeforeCallingAgainInSeconds);
641 secnoticeq("fresh", "%s Completing WFS", kWAIT2MINID);
642 [_freshnessCompletions enumerateObjectsUsingBlock:^(FreshnessResponseBlock _Nonnull block,
644 BOOL * _Nonnull stop) {
645 block(success, error);
647 [_freshnessCompletions removeAllObjects];
652 // MARK: ----- KVS key lists -----
656 - (NSDictionary*) exportKeyInterests
658 return @{ kKeyAlwaysKeys:[_alwaysKeys allObjects],
659 kKeyFirstUnlockKeys:[_firstUnlockKeys allObjects],
660 kKeyUnlockedKeys:[_unlockedKeys allObjects],
661 kMonitorState:_monitor,
662 kKeyPendingKeys:[_pendingKeys allObjects],
663 kKeySyncWithPeersPending:[NSNumber numberWithBool:_syncWithPeersPending],
664 kKeyEnsurePeerRegistration:[NSNumber numberWithBool:_ensurePeerRegistration],
669 - (void) importKeyInterests: (NSDictionary*) interests
671 _alwaysKeys = [NSMutableSet setWithArray: interests[kKeyAlwaysKeys]];
672 _firstUnlockKeys = [NSMutableSet setWithArray: interests[kKeyFirstUnlockKeys]];
673 _unlockedKeys = [NSMutableSet setWithArray: interests[kKeyUnlockedKeys]];
675 _pendingKeys = [NSMutableSet setWithArray: interests[kKeyPendingKeys]];
676 _syncWithPeersPending = [interests[kKeySyncWithPeersPending] boolValue];
677 _ensurePeerRegistration = [interests[kKeyEnsurePeerRegistration] boolValue];
678 _dsid = interests[kKeyDSID];
679 _monitor = interests[kMonitorState];
681 _monitor = [NSMutableDictionary dictionary];
685 - (NSMutableSet *)copyAllKeyInterests
687 NSMutableSet *allKeys = [NSMutableSet setWithSet: _alwaysKeys];
688 [allKeys unionSet: _firstUnlockKeys];
689 [allKeys unionSet: _unlockedKeys];
693 -(void)registerAtTimeKeys:(NSDictionary*)keyparms
698 NSArray *alwaysArray = [keyparms valueForKey: kKeyAlwaysKeys];
699 NSArray *firstUnlockedKeysArray = [keyparms valueForKey: kKeyFirstUnlockKeys];
700 NSArray *whenUnlockedKeysArray = [keyparms valueForKey: kKeyUnlockedKeys];
703 [_alwaysKeys unionSet: [NSMutableSet setWithArray: alwaysArray]];
704 if(firstUnlockedKeysArray)
705 [_firstUnlockKeys unionSet: [NSMutableSet setWithArray: firstUnlockedKeysArray]];
706 if(whenUnlockedKeysArray)
707 [_unlockedKeys unionSet: [NSMutableSet setWithArray: whenUnlockedKeysArray]];
711 - (void)registerKeys: (NSDictionary*)keys
713 secdebug(XPROXYSCOPE, "registerKeys: keys: %@", keys);
715 NSMutableSet *allOldKeys = [self copyAllKeyInterests];
717 NSDictionary *keyparms = [keys valueForKey: [NSString stringWithUTF8String: kMessageKeyParameter]];
718 NSDictionary *circles = [keys valueForKey: [NSString stringWithUTF8String: kMessageCircle]];
719 NSDictionary *messages = [keys valueForKey: [NSString stringWithUTF8String: kMessageMessage]];
721 _alwaysKeys = [NSMutableSet set];
722 _firstUnlockKeys = [NSMutableSet set];
723 _unlockedKeys = [NSMutableSet set];
725 [self registerAtTimeKeys: keyparms];
726 [self registerAtTimeKeys: circles];
727 [self registerAtTimeKeys: messages];
729 NSMutableSet *allNewKeys = [self copyAllKeyInterests];
731 // Make sure keys we no longer care about are not pending
732 [_pendingKeys intersectSet:allNewKeys];
733 if (_shadowPendingKeys) {
734 [_shadowPendingKeys intersectSet:allNewKeys];
737 // All new keys only is new keys (remove old keys)
738 [allNewKeys minusSet:allOldKeys];
740 // Mark new keys pending, they're new!
741 NSMutableSet *newKeysForCurrentLockState = [self pendKeysAndGetNewlyPended:allNewKeys];
743 [self persistState]; // Before we might call out, save our state so we recover if we crash
745 [self intersectWithCurrentLockState: newKeysForCurrentLockState];
746 // TODO: Don't processPendingKeysForCurrentLockState if none of the new keys have values.
747 if ([newKeysForCurrentLockState count] != 0) {
748 [self processPendingKeysForCurrentLockState];
752 // MARK: ----- Event Handling -----
754 - (void)streamEvent:(xpc_object_t)notification
756 #if (!TARGET_IPHONE_SIMULATOR)
757 const char *notificationName = xpc_dictionary_get_string(notification, "Notification");
758 if (!notificationName) {
759 } else if (strcmp(notificationName, kUserKeybagStateChangeNotification)==0) {
760 return [self keybagStateChange];
761 } else if (strcmp(notificationName, kCloudKeychainStorechangeChangeNotification)==0) {
762 return [self kvsStoreChange];
763 } else if (strcmp(notificationName, kNotifyTokenForceUpdate)==0) {
764 // DEBUG -- Possibly remove in future
765 return [self processAllItems];
767 const char *eventName = xpc_dictionary_get_string(notification, "XPCEventName");
768 char *desc = xpc_copy_description(notification);
769 secnotice("event", "%@ event: %s name: %s desc: %s", self, eventName, notificationName, desc);
775 - (void)storeKeysChanged: (NSSet<NSString*>*) changedKeys initial: (bool) initial
777 // Mark that our store is talking to us, so we don't have to make up for missing anything previous.
778 _seenKVSStoreChange = YES;
780 // Unmark them as pending as they have just changed and we'll process them.
781 [_pendingKeys minusSet:changedKeys];
783 // Only send values that we're currently interested in.
784 NSSet *keysOfInterestThatChanged = [self pendKeysAndGetPendingForCurrentLockState:changedKeys];
785 NSMutableDictionary *changedValues = [self copyValues:keysOfInterestThatChanged];
787 changedValues[(__bridge NSString*)kSOSKVSInitialSyncKey] = @"true";
789 secnotice("event", "%@ keysChangedInCloud: %@ keysOfInterest: %@ initial: %@",
791 [[changedKeys allObjects] componentsJoinedByString: @" "],
792 [[changedValues allKeys] componentsJoinedByString: @" "],
793 initial ? @"YES" : @"NO");
795 if ([changedValues count])
796 [self processKeyChangedEvent:changedValues];
799 - (void)storeAccountChanged
801 secnotice("event", "%@", self);
803 NSDictionary *changedValues = nil;
805 changedValues = @{ (__bridge NSString*)kSOSKVSAccountChangedKey: _dsid };
807 changedValues = @{ (__bridge NSString*)kSOSKVSAccountChangedKey: @"true" };
809 [self processKeyChangedEvent:changedValues];
812 - (void) doAfterFlush: (dispatch_block_t) block
814 //Flush any pending communication to Securityd.
816 dispatch_async(_calloutQueue, block);
818 _shadowFlushBlock = block;
821 - (void) calloutWith: (void(^)(NSSet *pending, bool syncWithPeersPending, bool ensurePeerRegistration, dispatch_queue_t queue, void(^done)(NSSet *handledKeys, bool handledSyncWithPeers, bool handledEnsurePeerRegistration))) callout
823 // In CKDKVSProxy's serial queue
825 // dispatch_get_global_queue - well-known global concurrent queue
826 // dispatch_get_main_queue - default queue that is bound to the main thread
827 xpc_transaction_begin();
828 dispatch_async(_calloutQueue, ^{
829 __block NSSet *myPending;
830 __block bool mySyncWithPeersPending;
831 __block bool myEnsurePeerRegistration;
832 __block bool wasLocked;
833 dispatch_sync(_ckdkvsproxy_queue, ^{
834 myPending = [_pendingKeys copy];
835 mySyncWithPeersPending = _syncWithPeersPending;
836 myEnsurePeerRegistration = _ensurePeerRegistration;
837 wasLocked = _isLocked;
841 _shadowPendingKeys = [NSMutableSet set];
842 _shadowSyncWithPeersPending = NO;
845 callout(myPending, mySyncWithPeersPending, myEnsurePeerRegistration, _ckdkvsproxy_queue, ^(NSSet *handledKeys, bool handledSyncWithPeers, bool handledEnsurePeerRegistration) {
846 secdebug("event", "%@ %s%s before callout handled: %s%s", self, mySyncWithPeersPending ? "S" : "s", myEnsurePeerRegistration ? "E" : "e", handledSyncWithPeers ? "S" : "s", handledEnsurePeerRegistration ? "E" : "e");
848 // In CKDKVSProxy's serial queue
851 // Update ensurePeerRegistration
852 _ensurePeerRegistration = ((myEnsurePeerRegistration && !handledEnsurePeerRegistration) || _shadowEnsurePeerRegistration);
854 _shadowEnsurePeerRegistration = NO;
856 if(_ensurePeerRegistration && !_isLocked)
857 [self doEnsurePeerRegistration];
859 // Update SyncWithPeers stuff.
860 _syncWithPeersPending = ((mySyncWithPeersPending && (!handledSyncWithPeers)) || _shadowSyncWithPeersPending);
862 _shadowSyncWithPeersPending = NO;
863 if (handledSyncWithPeers)
864 _lastSyncTime = dispatch_time(DISPATCH_TIME_NOW, 0);
866 // Update pendingKeys and handle them
867 [_pendingKeys removeObject: [NSNull null]]; // Don't let NULL hang around
869 [_pendingKeys minusSet: handledKeys];
870 bool hadShadowPendingKeys = [_shadowPendingKeys count];
871 // Move away shadownPendingKeys first, because pendKeysAndGetPendingForCurrentLockState
872 // will look at them. See rdar://problem/20733166.
873 NSSet *oldShadowPendingKeys = _shadowPendingKeys;
874 _shadowPendingKeys = nil;
876 NSSet *filteredKeys = [self pendKeysAndGetPendingForCurrentLockState:oldShadowPendingKeys];
878 secnoticeq("keytrace", "%@ account handled: %@ pending: %@", self,
879 [[handledKeys allObjects] componentsJoinedByString: @" "],
880 [[filteredKeys allObjects] componentsJoinedByString: @" "]);
882 // Write state to disk
885 // Handle shadow pended stuff
886 if (_syncWithPeersPending && !_isLocked)
887 [self scheduleSyncRequestTimer];
888 /* We don't want to call processKeyChangedEvent if we failed to
889 handle pending keys and the device didn't unlock nor receive
890 any kvs changes while we were in our callout.
891 Doing so will lead to securityd and CloudKeychainProxy
892 talking to each other forever in a tight loop if securityd
893 repeatedly returns an error processing the same message.
894 Instead we leave any old pending keys until the next event. */
896 if (hadShadowPendingKeys || (!_isLocked && wasLocked)){
897 [self processKeyChangedEvent:[self copyValues:filteredKeys]];
898 if(_shadowFlushBlock != NULL)
899 secerror("Flush block is not null and sending new keys");
901 if(_shadowFlushBlock != NULL){
902 dispatch_async(_calloutQueue, _shadowFlushBlock);
903 _shadowFlushBlock = NULL;
906 xpc_transaction_end();
911 - (void) sendKeysCallout: (NSSet *(^)(NSSet* pending, NSError** error)) handleKeys {
912 [self calloutWith: ^(NSSet *pending, bool syncWithPeersPending, bool ensurePeerRegistration, dispatch_queue_t queue, void(^done)(NSSet *, bool, bool)) {
913 NSError* error = NULL;
915 secnotice("CloudKeychainProxy", "send keys: %@", pending);
916 NSSet * handled = handleKeys(pending, &error);
918 dispatch_async(queue, ^{
920 secerror("%@ ensurePeerRegistration failed: %@", self, error);
923 done(handled, NO, NO);
928 - (void) doEnsurePeerRegistration
930 NSObject<CKDAccount>* accountDelegate = [self account];
931 [self calloutWith:^(NSSet *pending, bool syncWithPeersPending, bool ensurePeerRegistration, dispatch_queue_t queue, void(^done)(NSSet *, bool, bool)) {
932 NSError* error = nil;
933 bool handledEnsurePeerRegistration = [accountDelegate ensurePeerRegistration:&error];
934 secnotice("EnsurePeerRegistration", "%@ ensurePeerRegistration called, %@ (%@)", self, handledEnsurePeerRegistration ? @"success" : @"failure", error);
935 dispatch_async(queue, ^{
936 done(nil, NO, handledEnsurePeerRegistration);
941 - (void) doSyncWithAllPeers
943 NSObject<CKDAccount>* accountDelegate = [self account];
944 [self calloutWith:^(NSSet *pending, bool syncWithPeersPending, bool ensurePeerRegistration, dispatch_queue_t queue, void(^done)(NSSet *, bool, bool)) {
945 NSError* error = NULL;
946 SyncWithAllPeersReason reason = [accountDelegate syncWithAllPeers: &error];
947 dispatch_async(queue, ^{
948 bool handledSyncWithPeers = NO;
949 if (reason == kSyncWithAllPeersSuccess) {
950 handledSyncWithPeers = YES;
951 secnotice("event", "%@ syncWithAllPeers succeeded", self);
952 } else if (reason == kSyncWithAllPeersLocked) {
953 secnotice("event", "%@ syncWithAllPeers attempted while locked - waiting for unlock", self);
954 handledSyncWithPeers = NO;
955 [self updateIsLocked];
956 } else if (reason == kSyncWithAllPeersOtherFail) {
957 // Pretend we handled syncWithPeers, by pushing out the _lastSyncTime
958 // This will cause us to wait for kMinSyncInterval seconds before
959 // retrying, so we don't spam securityd if sync is failing
960 secerror("%@ syncWithAllPeers %@, rescheduling timer", self, error);
961 _lastSyncTime = dispatch_time(DISPATCH_TIME_NOW, 0);
963 secerror("%@ syncWithAllPeers %@, unknown reason: %d", self, error, reason);
966 done(nil, handledSyncWithPeers, false);
973 secnotice("event", "%@ syncWithPeersPending: %d inCallout: %d isLocked: %d", self, _syncWithPeersPending, _inCallout, _isLocked);
974 _syncTimerScheduled = NO;
975 if(_ensurePeerRegistration){
976 [self doEnsurePeerRegistration];
978 if (_syncWithPeersPending && !_inCallout && !_isLocked){
979 [self doSyncWithAllPeers];
983 - (dispatch_time_t) nextSyncTime
985 dispatch_time_t nextSync = dispatch_time(DISPATCH_TIME_NOW, kMinSyncDelay);
987 // Don't sync again unless we waited at least kMinSyncInterval
989 dispatch_time_t soonest = dispatch_time(_lastSyncTime, kMinSyncInterval);
990 if (nextSync < soonest || _deadline < soonest) {
991 secdebug("timer", "%@ backing off", self);
996 // Don't delay more than kMaxSyncDelay after the first request.
997 if (nextSync > _deadline) {
998 secdebug("timer", "%@ hit deadline", self);
1002 // Bump the timer by kMinSyncDelay
1003 if (_syncTimerScheduled)
1004 secdebug("timer", "%@ bumped timer", self);
1006 secdebug("timer", "%@ scheduled timer", self);
1011 - (void)scheduleSyncRequestTimer
1013 dispatch_source_set_timer(_syncTimer, [self nextSyncTime], DISPATCH_TIME_FOREVER, kSyncTimerLeeway);
1014 _syncTimerScheduled = YES;
1017 - (void)requestSyncWithAllPeers // secd calling SOSCCSyncWithAllPeers invokes this
1019 #if !defined(NDEBUG)
1020 NSString *desc = [self description];
1023 if (!_syncWithPeersPending || (_inCallout && !_shadowSyncWithPeersPending))
1024 _deadline = dispatch_time(DISPATCH_TIME_NOW, kMaxSyncDelay);
1026 if (!_syncWithPeersPending) {
1027 _syncWithPeersPending = YES;
1028 [self persistState];
1032 _shadowSyncWithPeersPending = YES;
1033 else if (!_isLocked)
1034 [self scheduleSyncRequestTimer];
1036 secdebug("event", "%@ %@", desc, self);
1039 - (void)requestEnsurePeerRegistration // secd calling SOSCCSyncWithAllPeers invokes this
1041 #if !defined(NDEBUG)
1042 NSString *desc = [self description];
1046 _shadowEnsurePeerRegistration = YES;
1048 _ensurePeerRegistration = YES;
1050 [self doEnsurePeerRegistration];
1052 [self persistState];
1055 secdebug("event", "%@ %@", desc, self);
1059 - (BOOL) updateUnlockedSinceBoot
1061 CFErrorRef aksError = NULL;
1062 if (!SecAKSGetHasBeenUnlocked(&_unlockedSinceBoot, &aksError)) {
1063 secerror("%@ Got error from SecAKSGetHasBeenUnlocked: %@", self, aksError);
1064 CFReleaseSafe(aksError);
1070 - (BOOL) updateIsLocked
1072 CFErrorRef aksError = NULL;
1073 if (!SecAKSGetIsLocked(&_isLocked, &aksError)) {
1075 secerror("%@ Got error querying lock state: %@", self, aksError);
1076 CFReleaseSafe(aksError);
1080 _unlockedSinceBoot = YES;
1084 - (void) keybagStateChange
1086 os_activity_initiate("keybagStateChanged", OS_ACTIVITY_FLAG_DEFAULT, ^{
1087 BOOL wasLocked = _isLocked;
1088 if ([self updateIsLocked]) {
1089 if (wasLocked == _isLocked)
1090 secdebug("event", "%@ still %s ignoring", self, _isLocked ? "locked" : "unlocked");
1092 [self keybagDidLock];
1094 [self keybagDidUnlock];
1099 - (void) keybagDidLock
1101 secnotice("event", "%@", self);
1104 - (void) keybagDidUnlock
1106 secnotice("event", "%@", self);
1107 if (_ensurePeerRegistration) {
1108 [self doEnsurePeerRegistration];
1111 // First send changed keys to securityd so it can proccess updates
1112 [self processPendingKeysForCurrentLockState];
1114 // Then, tickle securityd to perform a sync if needed.
1115 if (_syncWithPeersPending && !_syncTimerScheduled) {
1116 [self doSyncWithAllPeers];
1120 - (void) kvsStoreChange {
1121 os_activity_initiate("kvsStoreChange", OS_ACTIVITY_FLAG_DEFAULT, ^{
1122 if (!_seenKVSStoreChange) {
1123 secnotice("event", "%@ received darwin notification before first NSNotification", self);
1124 // TODO This might not be needed if we always get the NSNotification
1125 // deleived even if we were launched due to a kvsStoreChange
1126 // Send all keys for current lock state to securityd so it can proccess them
1127 [self pendKeysAndGetNewlyPended: [self copyAllKeyInterests]];
1128 [self processPendingKeysForCurrentLockState];
1130 secdebug("event", "%@ ignored, waiting for NSNotification", self);
1136 // MARK: ----- Key Filtering -----
1139 - (NSSet*) keysForCurrentLockState
1141 secdebug("filtering", "%@ Filtering: unlockedSinceBoot: %d\n unlocked: %d\n, keysOfInterest: <%@>", self, (int) _unlockedSinceBoot, (int) !_isLocked, [SOSPersistentState dictionaryDescription: [self exportKeyInterests]]);
1143 NSMutableSet *currentStateKeys = [NSMutableSet setWithSet: _alwaysKeys];
1144 if (_unlockedSinceBoot)
1145 [currentStateKeys unionSet: _firstUnlockKeys];
1148 [currentStateKeys unionSet: _unlockedKeys];
1150 return currentStateKeys;
1154 - (NSMutableSet*) pendKeysAndGetNewlyPended: (NSSet*) keysToPend
1156 NSMutableSet *filteredKeysToPend = [self copyAllKeyInterests];
1157 [filteredKeysToPend intersectSet: keysToPend];
1159 NSMutableSet *newlyPendedKeys = [filteredKeysToPend mutableCopy];
1160 [newlyPendedKeys minusSet: _pendingKeys];
1161 if (_shadowPendingKeys) {
1162 [newlyPendedKeys minusSet: _shadowPendingKeys];
1165 if (_shadowPendingKeys) {
1166 [_shadowPendingKeys unionSet:filteredKeysToPend];
1169 [_pendingKeys unionSet:filteredKeysToPend];
1172 return newlyPendedKeys;
1175 - (void) intersectWithCurrentLockState: (NSMutableSet*) set
1177 [set intersectSet: [self keysForCurrentLockState]];
1180 - (NSMutableSet*) pendingKeysForCurrentLockState
1182 NSMutableSet * result = [_pendingKeys mutableCopy];
1183 [self intersectWithCurrentLockState:result];
1187 - (NSMutableSet*) pendKeysAndGetPendingForCurrentLockState: (NSSet*) startingSet
1189 [self pendKeysAndGetNewlyPended: startingSet];
1191 return [self pendingKeysForCurrentLockState];
1194 - (NSMutableDictionary *)copyValues:(NSSet*)keysOfInterest
1196 // Grab values from store.
1197 NSObject<CKDStore> *store = [self store];
1198 NSMutableDictionary *changedValues = [NSMutableDictionary dictionaryWithCapacity:0];
1199 [keysOfInterest enumerateObjectsUsingBlock:^(id obj, BOOL *stop)
1201 NSString* key = (NSString*) obj;
1202 id objval = [store objectForKey:key];
1203 if (!objval) objval = [NSNull null];
1205 [changedValues setObject:objval forKey:key];
1206 secdebug(XPROXYSCOPE, "%@ storeChanged updated value for %@", self, key);
1208 return changedValues;
1212 During RegisterKeys, separate keys-of-interest into three disjoint sets:
1213 - keys that we always want to be notified about; this means we can get the
1215 - keys that require the device to have been unlocked at least once
1216 - keys that require the device to be unlocked now
1218 Typically, the sets of keys will be:
1224 The caller is responsible for making sure that the keys in e.g. alwaysKeys are
1225 values that can be handled at any time (that is, not when unlocked)
1227 Each time we get a notification from ubiquity that keys have changed, we need to
1228 see if anything of interest changed. If we don't care, then done.
1230 For each key-of-interest that changed, we either notify the client that things
1231 changed, or add it to a pendingNotifications list. If the notification to the
1232 client fails, also add it to the pendingNotifications list. This pending list
1233 should be written to persistent storage and consulted any time we either get an
1234 item changed notification, or get a stream event signalling a change in lock state.
1236 We can notify the client either through XPC if a connection is set up, or call a
1237 routine in securityd to launch it.
1241 - (void)processKeyChangedEvent:(NSDictionary *)changedValues
1243 NSMutableDictionary* filtered = [NSMutableDictionary dictionary];
1245 secnotice("processKeyChangedEvent", "changedValues:%@", changedValues);
1246 NSMutableArray* nullKeys = [NSMutableArray array];
1247 // Remove nulls because we don't want them in securityd.
1248 [changedValues enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
1249 if (obj == [NSNull null]){
1250 [nullKeys addObject:key];
1252 filtered[key] = obj;
1255 if ([nullKeys count])
1256 [_pendingKeys minusSet: [NSSet setWithArray: nullKeys]];
1258 if([filtered count] != 0 ) {
1259 [self sendKeysCallout:^NSSet *(NSSet *pending, NSError** error) {
1260 secnotice("processing keys", "pending:%@", pending);
1261 NSError *updateError = nil;
1262 return [[self account] keysChanged: filtered error: &updateError];
1265 secnoticeq("keytrace", "%@ null: %@ pending: %@", self,
1266 [nullKeys componentsJoinedByString: @" "],
1267 [[_pendingKeys allObjects] componentsJoinedByString: @" "]);
1271 - (void) processPendingKeysForCurrentLockState
1273 [self processKeyChangedEvent: [self copyValues: [self pendingKeysForCurrentLockState]]];