]> git.saurik.com Git - apple/security.git/blob - KVSKeychainSyncingProxy/CKDKVSProxy.m
Security-57740.20.22.tar.gz
[apple/security.git] / KVSKeychainSyncingProxy / CKDKVSProxy.m
1 /*
2 * Copyright (c) 2012-2014,2016 Apple Inc. All Rights Reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
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
11 * file.
12 *
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.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 */
23
24 //
25 // CKDKVSProxy.m
26 // ckd-xpc
27 //
28
29 #import <Foundation/Foundation.h>
30
31 #import <utilities/debugging.h>
32 #import <os/activity.h>
33
34 #import "CKDKVSProxy.h"
35 #import "CKDPersistentState.h"
36 #import "CKDKVSStore.h"
37 #import "CKDSecuritydAccount.h"
38
39 #include <Security/SecureObjectSync/SOSARCDefines.h>
40 #include <Security/SecureObjectSync/SOSKVSKeys.h>
41
42 #include "SOSCloudKeychainConstants.h"
43
44 #include <utilities/SecAKSWrappers.h>
45
46 /*
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.
53 */
54
55 static const char *kStreamName = "com.apple.notifyd.matching";
56
57 static NSString *kKeyKeyParameterKeys = @"KeyParameterKeys";
58 static NSString *kKeyCircleKeys = @"CircleKeys";
59 static NSString *kKeyMessageKeys = @"MessageKeys";
60
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";
71
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";
79
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";
87
88 #define kSecServerKeychainChangedNotification "com.apple.security.keychainchanged"
89
90 static int max_penalty_timeout = 32;
91 static int seconds_per_minute = 60;
92
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.
97
98 static NSString* asNSString(NSObject* object) {
99 return [object isKindOfClass:[NSString class]] ? (NSString*) object : nil;
100 }
101
102 @interface NSMutableDictionary (FindAndRemove)
103 -(NSObject*)extractObjectForKey:(NSString*)key;
104 @end
105
106 @implementation NSMutableDictionary (FindAndRemove)
107 -(NSObject*)extractObjectForKey:(NSString*)key {
108 NSObject* result = [self objectForKey:key];
109 [self removeObjectForKey: key];
110 return result;
111 }
112 @end
113
114 @implementation UbiqitousKVSProxy
115
116 - (void)persistState
117 {
118 [SOSPersistentState setRegisteredKeys:[self exportKeyInterests]];
119 }
120
121 + (UbiqitousKVSProxy *) sharedKVSProxy
122 {
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]];
129 });
130 }
131 return sharedKVSProxy;
132 }
133
134 - (id)initWithAccount:(NSObject<CKDAccount>*) account
135 store:(NSObject<CKDStore>*) store
136 {
137 if (self = [super init])
138 {
139 secnotice("event", "%@ start", self);
140
141 #if !(TARGET_OS_EMBEDDED)
142 // rdar://problem/26247270
143 if (geteuid() == 0) {
144 secerror("Cannot run CloudKeychainProxy as root");
145 return NULL;
146 }
147 #endif
148 _unlockedSinceBoot = NO;
149 _isLocked = YES; // until we know for sure
150 _ensurePeerRegistration = NO;
151 _syncWithPeersPending = NO;
152
153
154 _account = account;
155 _store = store;
156
157 _calloutQueue = dispatch_queue_create("CKDCallout", DISPATCH_QUEUE_SERIAL);
158 _ckdkvsproxy_queue = dispatch_get_main_queue();
159
160 _freshnessCompletions = [NSMutableArray<FreshnessResponseBlock> array];
161
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, ^{
165 [self timerFired];
166 });
167 dispatch_resume(_syncTimer);
168
169 _monitor = [NSMutableDictionary dictionary];
170
171 int notificationToken;
172 notify_register_dispatch(kSecServerKeychainChangedNotification, &notificationToken, _ckdkvsproxy_queue,
173 ^ (int token __unused)
174 {
175 secinfo("backoff", "keychain changed, wiping backoff monitor state");
176 _monitor = [NSMutableDictionary dictionary];
177 });
178
179 [self importKeyInterests: [SOSPersistentState registeredKeys]];
180
181 // Register for lock state changes
182 xpc_set_event_stream_handler(kStreamName, _ckdkvsproxy_queue,
183 ^(xpc_object_t notification){
184 [self streamEvent:notification];
185 });
186 _dsid = @"";
187
188 [self updateUnlockedSinceBoot];
189 [self updateIsLocked];
190 if (!_isLocked)
191 [self keybagDidUnlock];
192
193 [[self store] connectToProxy: self];
194
195 secdebug(XPROXYSCOPE, "%@ done", self);
196 }
197 return self;
198 }
199
200 - (NSString *)description
201 {
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" : "-"];
214 }
215
216 //
217 // MARK: XPC Function commands
218 //
219 - (void) clearStore {
220 [self.store removeAllObjects];
221 }
222
223 - (void)synchronizeStore {
224 [self.store pushWrites];
225 }
226
227 - (id) objectForKey: (NSString*) key {
228 return [self.store objectForKey: key];
229 }
230 - (NSDictionary<NSString *, id>*) copyAsDictionary {
231 return [self.store copyAsDictionary];
232 }
233
234 //
235 //
236 //
237 - (void)processAllItems
238 {
239 NSDictionary *allItems = [self.store copyAsDictionary];
240 if (allItems)
241 {
242 secnotice("event", "%@ sending: %@", self, [[allItems allKeys] componentsJoinedByString: @" "]);
243 [self processKeyChangedEvent:allItems];
244 }
245 else
246 secdebug(XPROXYSCOPE, "%@ No items in KVS", self);
247 }
248
249 - (void)dealloc
250 {
251 secdebug(XPROXYSCOPE, "%@", self);
252 [[NSNotificationCenter defaultCenter] removeObserver:self
253 name:NSUbiquitousKeyValueStoreDidChangeExternallyNotification
254 object:nil];
255
256 [[NSNotificationCenter defaultCenter] removeObserver:self
257 name:NSUbiquityIdentityDidChangeNotification
258 object:nil];
259 }
260
261 // MARK: Penalty measurement and handling
262 -(dispatch_source_t)setNewTimer:(int)timeout key:(NSString*)key
263 {
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];
268 });
269 dispatch_resume(timer);
270 return timer;
271 }
272
273 -(void) increasePenalty:(NSNumber*)currentPenalty key:(NSString*)key keyEntry:(NSMutableDictionary**)keyEntry
274 {
275 secnotice("backoff", "increasing penalty!");
276 int newPenalty = 0;
277 if([currentPenalty intValue] == max_penalty_timeout){
278 newPenalty = max_penalty_timeout;
279 }
280 else if ([currentPenalty intValue] == 0)
281 newPenalty = 1;
282 else
283 newPenalty = [currentPenalty intValue]*2;
284
285 secnotice("backoff", "key %@, waiting %d minutes long to send next messages", key, newPenalty);
286
287 NSNumber* penalty_timeout = [[NSNumber alloc]initWithInt:newPenalty];
288 dispatch_source_t existingTimer = [*keyEntry valueForKey:kMonitorPenaltyTimer];
289
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];
296 }
297 else{
298 dispatch_source_t timer = [self setNewTimer:newPenalty key:key];
299 [*keyEntry setObject:timer forKey:kMonitorPenaltyTimer];
300 }
301
302 [*keyEntry setObject:penalty_timeout forKey:kMonitorPenaltyBoxKey];
303 [_monitor setObject:*keyEntry forKey:key];
304 }
305
306 -(void) decreasePenalty:(NSNumber*)currentPenalty key:(NSString*)key keyEntry:(NSMutableDictionary**)keyEntry
307 {
308 int newPenalty = 0;
309 secnotice("backoff","decreasing penalty!");
310 if([currentPenalty intValue] == 0 || [currentPenalty intValue] == 1)
311 newPenalty = 0;
312 else
313 newPenalty = [currentPenalty intValue]/2;
314
315 secnotice("backoff","key %@, waiting %d minutes long to send next messages", key, newPenalty);
316
317 NSNumber* penalty_timeout = [[NSNumber alloc]initWithInt:newPenalty];
318
319 dispatch_source_t existingTimer = [*keyEntry valueForKey:kMonitorPenaltyTimer];
320 if(existingTimer != nil){
321 [*keyEntry removeObjectForKey:kMonitorPenaltyTimer];
322 dispatch_suspend(existingTimer);
323 if(newPenalty != 0){
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];
327 }
328 else{
329 dispatch_resume(existingTimer);
330 dispatch_source_cancel(existingTimer);
331 }
332 }
333 else{
334 if(newPenalty != 0){
335 dispatch_source_t timer = [self setNewTimer:newPenalty key:key];
336 [*keyEntry setObject:timer forKey:kMonitorPenaltyTimer];
337 }
338 }
339
340 [*keyEntry setObject:penalty_timeout forKey:kMonitorPenaltyBoxKey];
341 [_monitor setObject:*keyEntry forKey:key];
342
343 }
344
345 - (void)penaltyTimerFired:(NSString*)key
346 {
347 secnotice("backoff", "key: %@, !!!!!!!!!!!!!!!!penalty timeout is up!!!!!!!!!!!!", key);
348
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];
356 }
357
358 NSNumber *penalty_timeout = [keyEntry valueForKey:kMonitorPenaltyBoxKey];
359 secnotice("backoff", "key: %@, current penalty timeout: %@", key, penalty_timeout);
360
361 NSString* didWriteDuringTimeout = [keyEntry objectForKey:kMonitorDidWriteDuringPenalty];
362 if( didWriteDuringTimeout && [didWriteDuringTimeout isEqualToString:@"YES"] )
363 {
364 //increase timeout since we wrote during out penalty timeout
365 [self increasePenalty:penalty_timeout key:key keyEntry:&keyEntry];
366 }
367 else{
368 //decrease timeout since we successfully wrote messages out
369 [self decreasePenalty:penalty_timeout key:key keyEntry:&keyEntry];
370 }
371
372 //resetting the check
373 [keyEntry setObject: @"NO" forKey:kMonitorDidWriteDuringPenalty];
374
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];
379
380 [keyEntry setObject:consecutiveWrites forKey:kMonitorConsecutiveWrites];
381 [keyEntry setObject:timetable forKey:kMonitorTimeTable];
382 [_monitor setObject:keyEntry forKey:key];
383 }
384
385 -(NSMutableDictionary*)initializeTimeTable:(NSString*)key
386 {
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];
393
394 NSMutableDictionary *timeTable = [NSMutableDictionary dictionaryWithObjectsAndKeys: firstMinute, kMonitorFirstMinute,
395 secondMinute, kMonitorSecondMinute,
396 thirdMinute, kMonitorThirdMinute,
397 fourthMinute, kMonitorFourthMinute,
398 fifthMinute, kMonitorFifthMinute, nil];
399 return timeTable;
400 }
401
402 - (void)initializeKeyEntry:(NSString*)key
403 {
404 NSMutableDictionary *timeTable = [self initializeTimeTable:key];
405 NSDate *currentTime = [NSDate date];
406
407 NSMutableDictionary *keyEntry = [NSMutableDictionary dictionaryWithObjectsAndKeys: key, kMonitorMessageKey, @0, kMonitorConsecutiveWrites, currentTime, kMonitorLastWriteTimestamp, @0, kMonitorPenaltyBoxKey, timeTable, kMonitorTimeTable,[NSMutableDictionary dictionary], kMonitorMessageQueue, nil];
408
409 [_monitor setObject:keyEntry forKey:key];
410
411 }
412
413 - (void)recordTimestampForAppropriateInterval:(NSMutableDictionary**)timeTable key:(NSString*)key consecutiveWrites:(NSNumber**)consecutiveWrites
414 {
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)
421 {
422 if(foundTimeSlot == YES)
423 return;
424 [*timeTable enumerateKeysAndObjectsUsingBlock: ^(id minute, id obj, BOOL *stop2)
425 {
426 if(foundTimeSlot == YES)
427 return;
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){
433 foundTimeSlot = YES;
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"]){
440 cWrites++;
441 }
442 else if ([written isEqualToString:@"NO"]){
443 cWrites = 0;
444 }
445 }
446 }
447 return;
448 }
449 previousTable = minutesTable;
450 }
451 }];
452 }];
453
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];
459
460 if((double)[currentTime timeIntervalSinceDate: lastDate] >= seconds_per_minute){
461 *consecutiveWrites = [[NSNumber alloc]initWithInt:0];
462 }
463 else{
464 NSString* written = [lastTable valueForKey:kMonitorWroteInTimeSlice];
465 if([written isEqualToString:@"YES"]){
466 cWrites++;
467 *consecutiveWrites = [[NSNumber alloc]initWithInt:cWrites];
468 }
469 else{
470 *consecutiveWrites = [[NSNumber alloc]initWithInt:0];
471 }
472 }
473
474 *timeTable = [self initializeTimeTable:key];
475 return;
476 }
477 *consecutiveWrites = [[NSNumber alloc]initWithInt:cWrites];
478 }
479 - (void)recordWriteToKVS:(NSDictionary *)values
480 {
481 if([_monitor count] == 0){
482 [values enumerateKeysAndObjectsUsingBlock: ^(id key, id obj, BOOL *stop)
483 {
484 [self initializeKeyEntry: key];
485 }];
486 }
487 else{
488 [values enumerateKeysAndObjectsUsingBlock: ^(id key, id obj, BOOL *stop)
489 {
490 NSMutableDictionary *keyEntry = [_monitor objectForKey:key];
491 if(keyEntry == nil){
492 [self initializeKeyEntry: key];
493 }
494 else{
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];
500
501 [self recordTimestampForAppropriateInterval:&timeTable key:key consecutiveWrites:&existingWrites];
502
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];
509
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];
514 }
515 else
516 secnotice("backoff","monitor: keys have been written for 5 or more minutes, recording we wrote during timeout");
517
518 //record we wrote during a timeout
519 [keyEntry setObject: @"YES" forKey:kMonitorDidWriteDuringPenalty];
520 }
521 //keep writing freely but record it
522 else if((double)[currentTime timeIntervalSinceDate: lastWriteTimestamp] <= 60 && consecutiveWrites < 5){
523 secnotice("backoff","monitor: still writing freely");
524 }
525 }
526 }];
527 }
528 }
529
530 - (NSDictionary*)recordHaltedValuesAndReturnValuesToSafelyWrite:(NSDictionary *)values
531 {
532 NSMutableDictionary *SafeMessages = [NSMutableDictionary dictionary];
533 [values enumerateKeysAndObjectsUsingBlock: ^(id key, id obj, BOOL *stop)
534 {
535 NSMutableDictionary *keyEntry = [_monitor objectForKey:key];
536 NSNumber *penalty = [keyEntry objectForKey:kMonitorPenaltyBoxKey];
537 if([penalty intValue] != 0){
538 NSMutableDictionary* existingQueue = [keyEntry valueForKey:kMonitorMessageQueue];
539
540 [existingQueue setObject:obj forKey:key];
541
542 [keyEntry setObject:existingQueue forKey:kMonitorMessageQueue];
543 [_monitor setObject:keyEntry forKey:key];
544 }
545 else{
546 [SafeMessages setObject:obj forKey:key];
547 }
548 }];
549 return SafeMessages;
550 }
551
552 // MARK: Object setting
553
554
555 - (void)setStoreObjectsFromDictionary:(NSDictionary *)values
556 {
557 if (values == nil) {
558 secdebug(XPROXYSCOPE, "%@ NULL? values: %@", self, values);
559 return;
560 }
561
562 NSMutableDictionary<NSString*, NSObject*> *mutableValues = [values mutableCopy];
563 NSString* newDSID = asNSString([mutableValues extractObjectForKey:(__bridge NSString*) kSOSKVSOfficialDSIDKey]);
564 if (newDSID) {
565 _dsid = newDSID;
566 }
567
568 NSString* requiredDSID = asNSString([mutableValues extractObjectForKey:(__bridge NSString*) kSOSKVSRequiredKey]);
569 if (requiredDSID) {
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);
576 return;
577 } else {
578 secnoticeq("dsid", "DSIDs match, writing");
579 }
580 }
581
582 secnoticeq("keytrace", "%@ sending: %@", self, [[mutableValues allKeys] componentsJoinedByString: @" "]);
583 [mutableValues enumerateKeysAndObjectsUsingBlock: ^(id key, id obj, BOOL *stop)
584 {
585 if (obj == NULL || obj == [NSNull null]) {
586 [self.store removeObjectForKey:key];
587 } else {
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];
594 }
595 }
596 [self.store setObject:obj forKey:key];
597 }
598 }];
599
600 [self.store pushWrites];
601 }
602
603 - (void)setObjectsFromDictionary:(NSDictionary<NSString*, NSObject*> *)values
604 {
605 [[UbiqitousKVSProxy sharedKVSProxy] recordWriteToKVS: values];
606 NSDictionary *safeValues = [[UbiqitousKVSProxy sharedKVSProxy] recordHaltedValuesAndReturnValuesToSafelyWrite: values];
607 if([safeValues count] !=0){
608 [[UbiqitousKVSProxy sharedKVSProxy] setStoreObjectsFromDictionary:safeValues];
609 }
610 }
611
612 - (void)waitForSynchronization:(void (^)(NSDictionary<NSString*, NSObject*> *results, NSError *err))handler
613 {
614 secnoticeq("fresh", "%s Requesting WFS", kWAIT2MINID);
615
616 [_freshnessCompletions addObject: ^(bool success, NSError *error){
617 secnoticeq("fresh", "%s WFS Done", kWAIT2MINID);
618 handler(nil, error);
619 }];
620
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];
627
628 dispatch_async(_ckdkvsproxy_queue, ^{
629 [self waitForSyncDone: success error: error];
630 });
631 });
632 }
633 }
634
635 - (void) waitForSyncDone: (bool) success error: (NSError*) error{
636 if (success) {
637 const uint64_t delayBeforeCallingAgainInSeconds = 5ull * NSEC_PER_SEC;
638 _nextFreshnessTime = dispatch_time(DISPATCH_TIME_NOW, delayBeforeCallingAgainInSeconds);
639 }
640
641 secnoticeq("fresh", "%s Completing WFS", kWAIT2MINID);
642 [_freshnessCompletions enumerateObjectsUsingBlock:^(FreshnessResponseBlock _Nonnull block,
643 NSUInteger idx,
644 BOOL * _Nonnull stop) {
645 block(success, error);
646 }];
647 [_freshnessCompletions removeAllObjects];
648
649 }
650
651 //
652 // MARK: ----- KVS key lists -----
653 //
654
655
656 - (NSDictionary*) exportKeyInterests
657 {
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],
665 kKeyDSID:_dsid
666 };
667 }
668
669 - (void) importKeyInterests: (NSDictionary*) interests
670 {
671 _alwaysKeys = [NSMutableSet setWithArray: interests[kKeyAlwaysKeys]];
672 _firstUnlockKeys = [NSMutableSet setWithArray: interests[kKeyFirstUnlockKeys]];
673 _unlockedKeys = [NSMutableSet setWithArray: interests[kKeyUnlockedKeys]];
674
675 _pendingKeys = [NSMutableSet setWithArray: interests[kKeyPendingKeys]];
676 _syncWithPeersPending = [interests[kKeySyncWithPeersPending] boolValue];
677 _ensurePeerRegistration = [interests[kKeyEnsurePeerRegistration] boolValue];
678 _dsid = interests[kKeyDSID];
679 _monitor = interests[kMonitorState];
680 if(_monitor == nil)
681 _monitor = [NSMutableDictionary dictionary];
682
683 }
684
685 - (NSMutableSet *)copyAllKeyInterests
686 {
687 NSMutableSet *allKeys = [NSMutableSet setWithSet: _alwaysKeys];
688 [allKeys unionSet: _firstUnlockKeys];
689 [allKeys unionSet: _unlockedKeys];
690 return allKeys;
691 }
692
693 -(void)registerAtTimeKeys:(NSDictionary*)keyparms
694 {
695 if (keyparms == nil)
696 return;
697
698 NSArray *alwaysArray = [keyparms valueForKey: kKeyAlwaysKeys];
699 NSArray *firstUnlockedKeysArray = [keyparms valueForKey: kKeyFirstUnlockKeys];
700 NSArray *whenUnlockedKeysArray = [keyparms valueForKey: kKeyUnlockedKeys];
701
702 if(alwaysArray)
703 [_alwaysKeys unionSet: [NSMutableSet setWithArray: alwaysArray]];
704 if(firstUnlockedKeysArray)
705 [_firstUnlockKeys unionSet: [NSMutableSet setWithArray: firstUnlockedKeysArray]];
706 if(whenUnlockedKeysArray)
707 [_unlockedKeys unionSet: [NSMutableSet setWithArray: whenUnlockedKeysArray]];
708 }
709
710
711 - (void)registerKeys: (NSDictionary*)keys
712 {
713 secdebug(XPROXYSCOPE, "registerKeys: keys: %@", keys);
714
715 NSMutableSet *allOldKeys = [self copyAllKeyInterests];
716
717 NSDictionary *keyparms = [keys valueForKey: [NSString stringWithUTF8String: kMessageKeyParameter]];
718 NSDictionary *circles = [keys valueForKey: [NSString stringWithUTF8String: kMessageCircle]];
719 NSDictionary *messages = [keys valueForKey: [NSString stringWithUTF8String: kMessageMessage]];
720
721 _alwaysKeys = [NSMutableSet set];
722 _firstUnlockKeys = [NSMutableSet set];
723 _unlockedKeys = [NSMutableSet set];
724
725 [self registerAtTimeKeys: keyparms];
726 [self registerAtTimeKeys: circles];
727 [self registerAtTimeKeys: messages];
728
729 NSMutableSet *allNewKeys = [self copyAllKeyInterests];
730
731 // Make sure keys we no longer care about are not pending
732 [_pendingKeys intersectSet:allNewKeys];
733 if (_shadowPendingKeys) {
734 [_shadowPendingKeys intersectSet:allNewKeys];
735 }
736
737 // All new keys only is new keys (remove old keys)
738 [allNewKeys minusSet:allOldKeys];
739
740 // Mark new keys pending, they're new!
741 NSMutableSet *newKeysForCurrentLockState = [self pendKeysAndGetNewlyPended:allNewKeys];
742
743 [self persistState]; // Before we might call out, save our state so we recover if we crash
744
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];
749 }
750 }
751
752 // MARK: ----- Event Handling -----
753
754 - (void)streamEvent:(xpc_object_t)notification
755 {
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];
766 }
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);
770 if (desc)
771 free((void *)desc);
772 #endif
773 }
774
775 - (void)storeKeysChanged: (NSSet<NSString*>*) changedKeys initial: (bool) initial
776 {
777 // Mark that our store is talking to us, so we don't have to make up for missing anything previous.
778 _seenKVSStoreChange = YES;
779
780 // Unmark them as pending as they have just changed and we'll process them.
781 [_pendingKeys minusSet:changedKeys];
782
783 // Only send values that we're currently interested in.
784 NSSet *keysOfInterestThatChanged = [self pendKeysAndGetPendingForCurrentLockState:changedKeys];
785 NSMutableDictionary *changedValues = [self copyValues:keysOfInterestThatChanged];
786 if (initial)
787 changedValues[(__bridge NSString*)kSOSKVSInitialSyncKey] = @"true";
788
789 secnotice("event", "%@ keysChangedInCloud: %@ keysOfInterest: %@ initial: %@",
790 self,
791 [[changedKeys allObjects] componentsJoinedByString: @" "],
792 [[changedValues allKeys] componentsJoinedByString: @" "],
793 initial ? @"YES" : @"NO");
794
795 if ([changedValues count])
796 [self processKeyChangedEvent:changedValues];
797 }
798
799 - (void)storeAccountChanged
800 {
801 secnotice("event", "%@", self);
802
803 NSDictionary *changedValues = nil;
804 if(_dsid)
805 changedValues = @{ (__bridge NSString*)kSOSKVSAccountChangedKey: _dsid };
806 else
807 changedValues = @{ (__bridge NSString*)kSOSKVSAccountChangedKey: @"true" };
808
809 [self processKeyChangedEvent:changedValues];
810 }
811
812 - (void) doAfterFlush: (dispatch_block_t) block
813 {
814 //Flush any pending communication to Securityd.
815 if(!_inCallout)
816 dispatch_async(_calloutQueue, block);
817 else
818 _shadowFlushBlock = block;
819 }
820
821 - (void) calloutWith: (void(^)(NSSet *pending, bool syncWithPeersPending, bool ensurePeerRegistration, dispatch_queue_t queue, void(^done)(NSSet *handledKeys, bool handledSyncWithPeers, bool handledEnsurePeerRegistration))) callout
822 {
823 // In CKDKVSProxy's serial queue
824
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;
838
839 _inCallout = YES;
840
841 _shadowPendingKeys = [NSMutableSet set];
842 _shadowSyncWithPeersPending = NO;
843 });
844
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");
847
848 // In CKDKVSProxy's serial queue
849 _inCallout = NO;
850
851 // Update ensurePeerRegistration
852 _ensurePeerRegistration = ((myEnsurePeerRegistration && !handledEnsurePeerRegistration) || _shadowEnsurePeerRegistration);
853
854 _shadowEnsurePeerRegistration = NO;
855
856 if(_ensurePeerRegistration && !_isLocked)
857 [self doEnsurePeerRegistration];
858
859 // Update SyncWithPeers stuff.
860 _syncWithPeersPending = ((mySyncWithPeersPending && (!handledSyncWithPeers)) || _shadowSyncWithPeersPending);
861
862 _shadowSyncWithPeersPending = NO;
863 if (handledSyncWithPeers)
864 _lastSyncTime = dispatch_time(DISPATCH_TIME_NOW, 0);
865
866 // Update pendingKeys and handle them
867 [_pendingKeys removeObject: [NSNull null]]; // Don't let NULL hang around
868
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;
875
876 NSSet *filteredKeys = [self pendKeysAndGetPendingForCurrentLockState:oldShadowPendingKeys];
877
878 secnoticeq("keytrace", "%@ account handled: %@ pending: %@", self,
879 [[handledKeys allObjects] componentsJoinedByString: @" "],
880 [[filteredKeys allObjects] componentsJoinedByString: @" "]);
881
882 // Write state to disk
883 [self persistState];
884
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. */
895
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");
900 }
901 if(_shadowFlushBlock != NULL){
902 dispatch_async(_calloutQueue, _shadowFlushBlock);
903 _shadowFlushBlock = NULL;
904 }
905
906 xpc_transaction_end();
907 });
908 });
909 }
910
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;
914
915 secnotice("CloudKeychainProxy", "send keys: %@", pending);
916 NSSet * handled = handleKeys(pending, &error);
917
918 dispatch_async(queue, ^{
919 if (!handled) {
920 secerror("%@ ensurePeerRegistration failed: %@", self, error);
921 }
922
923 done(handled, NO, NO);
924 });
925 }];
926 }
927
928 - (void) doEnsurePeerRegistration
929 {
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);
937 });
938 }];
939 }
940
941 - (void) doSyncWithAllPeers
942 {
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);
962 } else {
963 secerror("%@ syncWithAllPeers %@, unknown reason: %d", self, error, reason);
964 }
965
966 done(nil, handledSyncWithPeers, false);
967 });
968 }];
969 }
970
971 - (void)timerFired
972 {
973 secnotice("event", "%@ syncWithPeersPending: %d inCallout: %d isLocked: %d", self, _syncWithPeersPending, _inCallout, _isLocked);
974 _syncTimerScheduled = NO;
975 if(_ensurePeerRegistration){
976 [self doEnsurePeerRegistration];
977 }
978 if (_syncWithPeersPending && !_inCallout && !_isLocked){
979 [self doSyncWithAllPeers];
980 }
981 }
982
983 - (dispatch_time_t) nextSyncTime
984 {
985 dispatch_time_t nextSync = dispatch_time(DISPATCH_TIME_NOW, kMinSyncDelay);
986
987 // Don't sync again unless we waited at least kMinSyncInterval
988 if (_lastSyncTime) {
989 dispatch_time_t soonest = dispatch_time(_lastSyncTime, kMinSyncInterval);
990 if (nextSync < soonest || _deadline < soonest) {
991 secdebug("timer", "%@ backing off", self);
992 return soonest;
993 }
994 }
995
996 // Don't delay more than kMaxSyncDelay after the first request.
997 if (nextSync > _deadline) {
998 secdebug("timer", "%@ hit deadline", self);
999 return _deadline;
1000 }
1001
1002 // Bump the timer by kMinSyncDelay
1003 if (_syncTimerScheduled)
1004 secdebug("timer", "%@ bumped timer", self);
1005 else
1006 secdebug("timer", "%@ scheduled timer", self);
1007
1008 return nextSync;
1009 }
1010
1011 - (void)scheduleSyncRequestTimer
1012 {
1013 dispatch_source_set_timer(_syncTimer, [self nextSyncTime], DISPATCH_TIME_FOREVER, kSyncTimerLeeway);
1014 _syncTimerScheduled = YES;
1015 }
1016
1017 - (void)requestSyncWithAllPeers // secd calling SOSCCSyncWithAllPeers invokes this
1018 {
1019 #if !defined(NDEBUG)
1020 NSString *desc = [self description];
1021 #endif
1022
1023 if (!_syncWithPeersPending || (_inCallout && !_shadowSyncWithPeersPending))
1024 _deadline = dispatch_time(DISPATCH_TIME_NOW, kMaxSyncDelay);
1025
1026 if (!_syncWithPeersPending) {
1027 _syncWithPeersPending = YES;
1028 [self persistState];
1029 }
1030
1031 if (_inCallout)
1032 _shadowSyncWithPeersPending = YES;
1033 else if (!_isLocked)
1034 [self scheduleSyncRequestTimer];
1035
1036 secdebug("event", "%@ %@", desc, self);
1037 }
1038
1039 - (void)requestEnsurePeerRegistration // secd calling SOSCCSyncWithAllPeers invokes this
1040 {
1041 #if !defined(NDEBUG)
1042 NSString *desc = [self description];
1043 #endif
1044
1045 if (_inCallout) {
1046 _shadowEnsurePeerRegistration = YES;
1047 } else {
1048 _ensurePeerRegistration = YES;
1049 if (!_isLocked){
1050 [self doEnsurePeerRegistration];
1051 }
1052 [self persistState];
1053 }
1054
1055 secdebug("event", "%@ %@", desc, self);
1056 }
1057
1058
1059 - (BOOL) updateUnlockedSinceBoot
1060 {
1061 CFErrorRef aksError = NULL;
1062 if (!SecAKSGetHasBeenUnlocked(&_unlockedSinceBoot, &aksError)) {
1063 secerror("%@ Got error from SecAKSGetHasBeenUnlocked: %@", self, aksError);
1064 CFReleaseSafe(aksError);
1065 return NO;
1066 }
1067 return YES;
1068 }
1069
1070 - (BOOL) updateIsLocked
1071 {
1072 CFErrorRef aksError = NULL;
1073 if (!SecAKSGetIsLocked(&_isLocked, &aksError)) {
1074 _isLocked = YES;
1075 secerror("%@ Got error querying lock state: %@", self, aksError);
1076 CFReleaseSafe(aksError);
1077 return NO;
1078 }
1079 if (!_isLocked)
1080 _unlockedSinceBoot = YES;
1081 return YES;
1082 }
1083
1084 - (void) keybagStateChange
1085 {
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");
1091 else if (_isLocked)
1092 [self keybagDidLock];
1093 else
1094 [self keybagDidUnlock];
1095 }
1096 });
1097 }
1098
1099 - (void) keybagDidLock
1100 {
1101 secnotice("event", "%@", self);
1102 }
1103
1104 - (void) keybagDidUnlock
1105 {
1106 secnotice("event", "%@", self);
1107 if (_ensurePeerRegistration) {
1108 [self doEnsurePeerRegistration];
1109 }
1110
1111 // First send changed keys to securityd so it can proccess updates
1112 [self processPendingKeysForCurrentLockState];
1113
1114 // Then, tickle securityd to perform a sync if needed.
1115 if (_syncWithPeersPending && !_syncTimerScheduled) {
1116 [self doSyncWithAllPeers];
1117 }
1118 }
1119
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];
1129 } else {
1130 secdebug("event", "%@ ignored, waiting for NSNotification", self);
1131 }
1132 });
1133 }
1134
1135 //
1136 // MARK: ----- Key Filtering -----
1137 //
1138
1139 - (NSSet*) keysForCurrentLockState
1140 {
1141 secdebug("filtering", "%@ Filtering: unlockedSinceBoot: %d\n unlocked: %d\n, keysOfInterest: <%@>", self, (int) _unlockedSinceBoot, (int) !_isLocked, [SOSPersistentState dictionaryDescription: [self exportKeyInterests]]);
1142
1143 NSMutableSet *currentStateKeys = [NSMutableSet setWithSet: _alwaysKeys];
1144 if (_unlockedSinceBoot)
1145 [currentStateKeys unionSet: _firstUnlockKeys];
1146
1147 if (!_isLocked)
1148 [currentStateKeys unionSet: _unlockedKeys];
1149
1150 return currentStateKeys;
1151 }
1152
1153
1154 - (NSMutableSet*) pendKeysAndGetNewlyPended: (NSSet*) keysToPend
1155 {
1156 NSMutableSet *filteredKeysToPend = [self copyAllKeyInterests];
1157 [filteredKeysToPend intersectSet: keysToPend];
1158
1159 NSMutableSet *newlyPendedKeys = [filteredKeysToPend mutableCopy];
1160 [newlyPendedKeys minusSet: _pendingKeys];
1161 if (_shadowPendingKeys) {
1162 [newlyPendedKeys minusSet: _shadowPendingKeys];
1163 }
1164
1165 if (_shadowPendingKeys) {
1166 [_shadowPendingKeys unionSet:filteredKeysToPend];
1167 }
1168 else{
1169 [_pendingKeys unionSet:filteredKeysToPend];
1170 }
1171
1172 return newlyPendedKeys;
1173 }
1174
1175 - (void) intersectWithCurrentLockState: (NSMutableSet*) set
1176 {
1177 [set intersectSet: [self keysForCurrentLockState]];
1178 }
1179
1180 - (NSMutableSet*) pendingKeysForCurrentLockState
1181 {
1182 NSMutableSet * result = [_pendingKeys mutableCopy];
1183 [self intersectWithCurrentLockState:result];
1184 return result;
1185 }
1186
1187 - (NSMutableSet*) pendKeysAndGetPendingForCurrentLockState: (NSSet*) startingSet
1188 {
1189 [self pendKeysAndGetNewlyPended: startingSet];
1190
1191 return [self pendingKeysForCurrentLockState];
1192 }
1193
1194 - (NSMutableDictionary *)copyValues:(NSSet*)keysOfInterest
1195 {
1196 // Grab values from store.
1197 NSObject<CKDStore> *store = [self store];
1198 NSMutableDictionary *changedValues = [NSMutableDictionary dictionaryWithCapacity:0];
1199 [keysOfInterest enumerateObjectsUsingBlock:^(id obj, BOOL *stop)
1200 {
1201 NSString* key = (NSString*) obj;
1202 id objval = [store objectForKey:key];
1203 if (!objval) objval = [NSNull null];
1204
1205 [changedValues setObject:objval forKey:key];
1206 secdebug(XPROXYSCOPE, "%@ storeChanged updated value for %@", self, key);
1207 }];
1208 return changedValues;
1209 }
1210
1211 /*
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
1214 value at any time
1215 - keys that require the device to have been unlocked at least once
1216 - keys that require the device to be unlocked now
1217
1218 Typically, the sets of keys will be:
1219
1220 - Dk: alwaysKeys
1221 - Ck: firstUnlock
1222 - Ak: unlocked
1223
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)
1226
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.
1229
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.
1235
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.
1238
1239 */
1240
1241 - (void)processKeyChangedEvent:(NSDictionary *)changedValues
1242 {
1243 NSMutableDictionary* filtered = [NSMutableDictionary dictionary];
1244
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];
1251 }else{
1252 filtered[key] = obj;
1253 }
1254 }];
1255 if ([nullKeys count])
1256 [_pendingKeys minusSet: [NSSet setWithArray: nullKeys]];
1257
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];
1263 }];
1264 } else {
1265 secnoticeq("keytrace", "%@ null: %@ pending: %@", self,
1266 [nullKeys componentsJoinedByString: @" "],
1267 [[_pendingKeys allObjects] componentsJoinedByString: @" "]);
1268 }
1269 }
1270
1271 - (void) processPendingKeysForCurrentLockState
1272 {
1273 [self processKeyChangedEvent: [self copyValues: [self pendingKeysForCurrentLockState]]];
1274 }
1275
1276 @end
1277
1278