]> git.saurik.com Git - apple/security.git/blob - OSX/sec/SOSCircle/CloudKeychainProxy/CKDKVSProxy.m
Security-57740.31.2.tar.gz
[apple/security.git] / OSX / sec / SOSCircle / CloudKeychainProxy / CKDKVSProxy.m
1 /*
2 * Copyright (c) 2012-2014 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/NSUbiquitousKeyValueStore.h>
30 #import <Foundation/NSUbiquitousKeyValueStore_Private.h>
31 #import <Foundation/NSArray.h>
32 #import <Foundation/Foundation.h>
33
34 #import <Security/SecBasePriv.h>
35 #import <Security/SecItemPriv.h>
36 #import <utilities/debugging.h>
37 #import <notify.h>
38 #import <os/activity.h>
39
40 #import "CKDKVSProxy.h"
41 #import "CKDPersistentState.h"
42
43 #include <Security/SecureObjectSync/SOSARCDefines.h>
44 #import <IDS/IDS.h>
45
46 #include <Security/SecureObjectSync/SOSAccount.h>
47 #include <Security/SecureObjectSync/SOSCloudCircleInternal.h>
48 #include <Security/SecureObjectSync/SOSKVSKeys.h>
49
50 #include "SOSCloudKeychainConstants.h"
51
52 #include <utilities/SecAKSWrappers.h>
53 #include <utilities/SecCFRelease.h>
54
55 /*
56 The total space available in your app’s iCloud key-value storage is 1 MB.
57 The maximum number of keys you can specify is 1024, and the size limit for
58 each value associated with a key is 1 MB. So, for example, if you store a
59 single large value of 1 MB for a single key, that consumes your total
60 available storage. If you store 1 KB of data for each key, you can use
61 1,000 key-value pairs.
62 */
63
64 static const char *kStreamName = "com.apple.notifyd.matching";
65
66 static NSString *kKeyKeyParameterKeys = @"KeyParameterKeys";
67 static NSString *kKeyCircleKeys = @"CircleKeys";
68 static NSString *kKeyMessageKeys = @"MessageKeys";
69
70 static NSString *kKeyAlwaysKeys = @"AlwaysKeys";
71 static NSString *kKeyFirstUnlockKeys = @"FirstUnlockKeys";
72 static NSString *kKeyUnlockedKeys = @"UnlockedKeys";
73 static NSString *kKeyPendingKeys = @"PendingKeys";
74 static NSString *kKeyUnsentChangedKeys = @"unsentChangedKeys";
75 static NSString *kKeyUnlockNotificationRequested = @"unlockNotificationRequested";
76 static NSString *kKeySyncWithPeersPending = @"SyncWithPeersPending";
77 static NSString *kKeyEnsurePeerRegistration = @"EnsurePeerRegistration";
78 static NSString *kKeyDSID = @"DSID";
79 static NSString *kMonitorState = @"MonitorState";
80
81 static NSString *kMonitorPenaltyBoxKey = @"Penalty";
82 static NSString *kMonitorMessageKey = @"Message";
83 static NSString *kMonitorConsecutiveWrites = @"ConsecutiveWrites";
84 static NSString *kMonitorLastWriteTimestamp = @"LastWriteTimestamp";
85 static NSString *kMonitorMessageQueue = @"MessageQueue";
86 static NSString *kMonitorPenaltyTimer = @"PenaltyTimer";
87
88 static NSString *kMonitorTimeTable = @"TimeTable";
89 static NSString *kMonitorFirstMinute = @"AFirstMinute";
90 static NSString *kMonitorSecondMinute = @"BSecondMinute";
91 static NSString *kMonitorThirdMinute = @"CThirdMinute";
92 static NSString *kMonitorFourthMinute = @"DFourthMinute";
93 static NSString *kMonitorFifthMinute = @"EFifthMinute";
94 static NSString *kMonitorWroteInTimeSlice = @"TimeSlice";
95
96 #define kSecServerKeychainChangedNotification "com.apple.security.keychainchanged"
97
98 static int max_penalty_timeout = 32;
99 static int seconds_per_minute = 5;
100 enum
101 {
102 kCallbackMethodSecurityd = 0,
103 kCallbackMethodXPC = 1,
104 };
105
106 static const int64_t kMinSyncDelay = (NSEC_PER_MSEC * 500); // 500ms minimum delay before a syncWithAllPeers call.
107 static const int64_t kMaxSyncDelay = (NSEC_PER_SEC * 5); // 5s maximun delay for a given request
108 static const int64_t kMinSyncInterval = (NSEC_PER_SEC * 15); // 15s minimum time between successive syncWithAllPeers calls.
109 static const int64_t kSyncTimerLeeway = (NSEC_PER_MSEC * 250); // 250ms leeway for sync events.
110
111 //KVS error codes
112 #define UPDATE_RESUBMIT 4
113
114 @interface NSUbiquitousKeyValueStore (NSUbiquitousKeyValueStore_PrivateZ)
115 - (void) _synchronizeWithCompletionHandler:(void (^)(NSError *error))completionHandler;
116
117 @end
118
119 @implementation UbiqitousKVSProxy
120
121
122 - (void)persistState
123 {
124 [SOSPersistentState setRegisteredKeys:[self exportKeyInterests]];
125 }
126
127 + (UbiqitousKVSProxy *) sharedKVSProxy
128 {
129 static UbiqitousKVSProxy *sharedKVSProxy;
130 if (!sharedKVSProxy) {
131 static dispatch_once_t onceToken;
132 dispatch_once(&onceToken, ^{
133 sharedKVSProxy = [[self alloc] init];
134 });
135 }
136 return sharedKVSProxy;
137 }
138
139 - (id)init
140 {
141 if (self = [super init])
142 {
143 secnotice("event", "%@ start", self);
144
145 _calloutQueue = dispatch_queue_create("CKDCallout", DISPATCH_QUEUE_SERIAL);
146 _freshParamsQueue = dispatch_queue_create("CKDFresh", DISPATCH_QUEUE_SERIAL);
147 _ckdkvsproxy_queue = dispatch_get_main_queue();
148
149 _syncTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue());
150 dispatch_source_set_timer(_syncTimer, DISPATCH_TIME_FOREVER, DISPATCH_TIME_FOREVER, kSyncTimerLeeway);
151 dispatch_source_set_event_handler(_syncTimer, ^{
152 [self timerFired];
153 });
154 dispatch_resume(_syncTimer);
155
156 _monitor = [NSMutableDictionary dictionary];
157
158 [[NSNotificationCenter defaultCenter]
159 addObserver: self
160 selector: @selector (iCloudAccountAvailabilityChanged:)
161 name: NSUbiquityIdentityDidChangeNotification
162 object: nil];
163
164 [[NSNotificationCenter defaultCenter] addObserver:self
165 selector:@selector(cloudChanged:)
166 name:NSUbiquitousKeyValueStoreDidChangeExternallyNotification
167 object:nil];
168 int notificationToken;
169 notify_register_dispatch(kSecServerKeychainChangedNotification, &notificationToken, dispatch_get_main_queue(),
170 ^ (int token __unused)
171 {
172 secinfo("backoff", "keychain changed, wiping backoff monitor state");
173 _monitor = [NSMutableDictionary dictionary];
174 });
175
176 [self importKeyInterests: [SOSPersistentState registeredKeys]];
177
178 // Register for lock state changes
179 xpc_set_event_stream_handler(kStreamName, dispatch_get_main_queue(),
180 ^(xpc_object_t notification){
181 [self streamEvent:notification];
182 });
183 _dsid = @"";;
184
185 [self updateUnlockedSinceBoot];
186 [self updateIsLocked];
187 if (!_isLocked)
188 [self keybagDidUnlock];
189
190 secdebug(XPROXYSCOPE, "%@ done", self);
191 }
192 return self;
193 }
194
195 - (NSString *)description
196 {
197 return [NSString stringWithFormat:@"<%s%s%s%s%s%s%s%s%s%s%s>",
198 _isLocked ? "L" : "U",
199 _unlockedSinceBoot ? "B" : "-",
200 _seenKVSStoreChange ? "K" : "-",
201 _syncTimerScheduled ? "T" : "-",
202 _syncWithPeersPending ? "s" : "-",
203 _ensurePeerRegistration ? "e" : "-",
204 [_pendingKeys count] ? "p" : "-",
205 _inCallout ? "C" : "-",
206 _shadowSyncWithPeersPending ? "S" : "-",
207 _shadowEnsurePeerRegistration ? "E" : "-",
208 [_shadowPendingKeys count] ? "P" : "-"];
209 }
210
211 - (void)processAllItems
212 {
213 NSDictionary *allItems = [self getAll];
214 if (allItems)
215 {
216 secnotice("event", "%@ sending: %@", self, [[allItems allKeys] componentsJoinedByString: @" "]);
217 [self processKeyChangedEvent:allItems];
218 }
219 else
220 secdebug(XPROXYSCOPE, "%@ No items in KVS", self);
221 }
222
223 - (void)setItemsChangedBlock:(CloudItemsChangedBlock)itemsChangedBlock
224 {
225 self->itemsChangedCallback = itemsChangedBlock;
226 }
227
228 - (void)dealloc
229 {
230 secdebug(XPROXYSCOPE, "%@", self);
231 [[NSNotificationCenter defaultCenter] removeObserver:self
232 name:NSUbiquitousKeyValueStoreDidChangeExternallyNotification object:nil];
233
234 [[NSNotificationCenter defaultCenter] removeObserver:self
235 name:NSUbiquityIdentityDidChangeNotification object:nil];
236 }
237
238 // MARK: ----- Client Interface -----
239
240 - (void)setObject:(id)obj forKey:(id)key
241 {
242 NSUbiquitousKeyValueStore *store = [self cloudStore];
243 if (store)
244 {
245 id value = [store objectForKey:key];
246 if (value)
247 secdebug("kvsdebug", "%@ key %@ changed: %@ to: %@", self, key, value, obj);
248 else
249 secdebug("kvsdebug", "%@ key %@ initialized to: %@", self, key, obj);
250 [store setObject:obj forKey:key];
251 [self requestSynchronization:NO];
252 } else {
253 secerror("Can't get kvs store, key: %@ not set to: %@", key, obj);
254 }
255 }
256
257 -(dispatch_source_t)setNewTimer:(int)timeout key:(NSString*)key
258 {
259 __block dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, _ckdkvsproxy_queue);
260 dispatch_source_set_timer(timer, dispatch_time(DISPATCH_TIME_NOW, timeout * NSEC_PER_SEC * seconds_per_minute), DISPATCH_TIME_FOREVER, kSyncTimerLeeway);
261 dispatch_source_set_event_handler(timer, ^{
262 [self penaltyTimerFired:key];
263 });
264 dispatch_resume(timer);
265 return timer;
266 }
267
268 -(void) increasePenalty:(NSNumber*)currentPenalty key:(NSString*)key keyEntry:(NSMutableDictionary**)keyEntry
269 {
270 secinfo("backoff", "increasing penalty!");
271 int newPenalty = 0;
272 if([currentPenalty intValue] == max_penalty_timeout){
273 newPenalty = max_penalty_timeout;
274 }
275 else if ([currentPenalty intValue] == 0)
276 newPenalty = 1;
277 else
278 newPenalty = [currentPenalty intValue]*2;
279
280 secinfo("backoff", "key %@, waiting %d minutes long to send next messages", key, newPenalty);
281
282 NSNumber* penalty_timeout = [[NSNumber alloc]initWithInt:newPenalty];
283 dispatch_source_t existingTimer = [*keyEntry valueForKey:kMonitorPenaltyTimer];
284
285 if(existingTimer != nil){
286 [*keyEntry removeObjectForKey:kMonitorPenaltyTimer];
287 dispatch_suspend(existingTimer);
288 dispatch_source_set_timer(existingTimer,dispatch_time(DISPATCH_TIME_NOW, newPenalty * NSEC_PER_SEC * seconds_per_minute), DISPATCH_TIME_FOREVER, kSyncTimerLeeway);
289 dispatch_resume(existingTimer);
290 [*keyEntry setObject:existingTimer forKey:kMonitorPenaltyTimer];
291 }
292 else{
293 dispatch_source_t timer = [self setNewTimer:newPenalty key:key];
294 [*keyEntry setObject:timer forKey:kMonitorPenaltyTimer];
295 }
296
297 [*keyEntry setObject:penalty_timeout forKey:kMonitorPenaltyBoxKey];
298 [_monitor setObject:*keyEntry forKey:key];
299 }
300
301 -(void) decreasePenalty:(NSNumber*)currentPenalty key:(NSString*)key keyEntry:(NSMutableDictionary**)keyEntry
302 {
303 int newPenalty = 0;
304 secinfo("backoff","decreasing penalty!");
305 if([currentPenalty intValue] == 0 || [currentPenalty intValue] == 1)
306 newPenalty = 0;
307 else
308 newPenalty = [currentPenalty intValue]/2;
309
310 secinfo("backoff","key %@, waiting %d minutes long to send next messages", key, newPenalty);
311
312 NSNumber* penalty_timeout = [[NSNumber alloc]initWithInt:newPenalty];
313
314 dispatch_source_t existingTimer = [*keyEntry valueForKey:kMonitorPenaltyTimer];
315 if(existingTimer != nil){
316 [*keyEntry removeObjectForKey:kMonitorPenaltyTimer];
317 dispatch_suspend(existingTimer);
318 if(newPenalty != 0){
319 dispatch_source_set_timer(existingTimer,dispatch_time(DISPATCH_TIME_NOW, newPenalty * NSEC_PER_SEC * seconds_per_minute), DISPATCH_TIME_FOREVER, kSyncTimerLeeway);
320 dispatch_resume(existingTimer);
321 [*keyEntry setObject:existingTimer forKey:kMonitorPenaltyTimer];
322 }
323 else{
324 dispatch_resume(existingTimer);
325 dispatch_source_cancel(existingTimer);
326 }
327 }
328 else{
329 if(newPenalty != 0){
330 dispatch_source_t timer = [self setNewTimer:newPenalty key:key];
331 [*keyEntry setObject:timer forKey:kMonitorPenaltyTimer];
332 }
333 }
334
335 [*keyEntry setObject:penalty_timeout forKey:kMonitorPenaltyBoxKey];
336 [_monitor setObject:*keyEntry forKey:key];
337
338 }
339
340 - (void)penaltyTimerFired:(NSString*)key
341 {
342 secinfo("backoff","key: %@, !!!!!!!!!!!!!!!!penalty timeout is up!!!!!!!!!!!!", key);
343 NSMutableDictionary *keyEntry = [_monitor objectForKey:key];
344 NSMutableDictionary *queuedMessages = [keyEntry objectForKey:kMonitorMessageQueue];
345 secinfo("backoff","key: %@, queuedMessages: %@", key, queuedMessages);
346 if(queuedMessages && [queuedMessages count] != 0){
347 secinfo("backoff","key: %@, message queue not empty, writing to KVS!", key);
348 [self setObjectsFromDictionary:queuedMessages];
349 [keyEntry setObject:[NSMutableDictionary dictionary] forKey:kMonitorMessageQueue];
350 }
351 //decrease timeout since we successfully wrote messages out
352 NSNumber *penalty_timeout = [keyEntry valueForKey:kMonitorPenaltyBoxKey];
353 [self decreasePenalty:penalty_timeout key:key keyEntry:&keyEntry];
354
355 //recompute the timetable and number of consecutive writes to KVS
356 NSMutableDictionary *timetable = [keyEntry valueForKey:kMonitorTimeTable];
357 NSNumber *consecutiveWrites = [keyEntry valueForKey:kMonitorConsecutiveWrites];
358 [self recordTimestampForAppropriateInterval:&timetable key:key consecutiveWrites:&consecutiveWrites];
359
360 [keyEntry setObject:consecutiveWrites forKey:kMonitorConsecutiveWrites];
361 [keyEntry setObject:timetable forKey:kMonitorTimeTable];
362 [_monitor setObject:keyEntry forKey:key];
363 }
364
365 -(NSMutableDictionary*)initializeTimeTable:(NSString*)key
366 {
367 NSDate *currentTime = [NSDate date];
368 NSMutableDictionary *firstMinute = [NSMutableDictionary dictionaryWithObjectsAndKeys:[currentTime dateByAddingTimeInterval: seconds_per_minute], kMonitorFirstMinute, @"YES", kMonitorWroteInTimeSlice, nil];
369 NSMutableDictionary *secondMinute = [NSMutableDictionary dictionaryWithObjectsAndKeys:[currentTime dateByAddingTimeInterval: seconds_per_minute * 2],kMonitorSecondMinute, @"NO", kMonitorWroteInTimeSlice, nil];
370 NSMutableDictionary *thirdMinute = [NSMutableDictionary dictionaryWithObjectsAndKeys:[currentTime dateByAddingTimeInterval: seconds_per_minute * 3], kMonitorThirdMinute, @"NO",kMonitorWroteInTimeSlice, nil];
371 NSMutableDictionary *fourthMinute = [NSMutableDictionary dictionaryWithObjectsAndKeys:[currentTime dateByAddingTimeInterval: seconds_per_minute * 4],kMonitorFourthMinute, @"NO", kMonitorWroteInTimeSlice, nil];
372 NSMutableDictionary *fifthMinute = [NSMutableDictionary dictionaryWithObjectsAndKeys:[currentTime dateByAddingTimeInterval: seconds_per_minute * 5], kMonitorFifthMinute, @"NO", kMonitorWroteInTimeSlice, nil];
373
374 NSMutableDictionary *timeTable = [NSMutableDictionary dictionaryWithObjectsAndKeys: firstMinute, kMonitorFirstMinute,
375 secondMinute, kMonitorSecondMinute,
376 thirdMinute, kMonitorThirdMinute,
377 fourthMinute, kMonitorFourthMinute,
378 fifthMinute, kMonitorFifthMinute, nil];
379 return timeTable;
380 }
381
382 - (void)initializeKeyEntry:(NSString*)key
383 {
384 NSMutableDictionary *timeTable = [self initializeTimeTable:key];
385 NSDate *currentTime = [NSDate date];
386
387 NSMutableDictionary *keyEntry = [NSMutableDictionary dictionaryWithObjectsAndKeys: key, kMonitorMessageKey, @0, kMonitorConsecutiveWrites, currentTime, kMonitorLastWriteTimestamp, @0, kMonitorPenaltyBoxKey, timeTable, kMonitorTimeTable,[NSMutableDictionary dictionary], kMonitorMessageQueue, nil];
388
389 [_monitor setObject:keyEntry forKey:key];
390
391 }
392
393 - (void)recordTimestampForAppropriateInterval:(NSMutableDictionary**)timeTable key:(NSString*)key consecutiveWrites:(NSNumber**)consecutiveWrites
394 {
395 NSDate *currentTime = [NSDate date];
396 __block int cWrites = [*consecutiveWrites intValue];
397 __block BOOL foundTimeSlot = NO;
398 __block NSMutableDictionary *previousTable = nil;
399 NSArray *sorted = [[*timeTable allKeys] sortedArrayUsingSelector:@selector(compare:)];
400 [sorted enumerateObjectsUsingBlock:^(id sortedKey, NSUInteger idx, BOOL *stop)
401 {
402 if(foundTimeSlot == YES)
403 return;
404 [*timeTable enumerateKeysAndObjectsUsingBlock: ^(id key, id obj, BOOL *stop)
405 {
406 if(foundTimeSlot == YES)
407 return;
408 if([sortedKey isEqualToString:key]){
409 NSMutableDictionary *minutesTable = (NSMutableDictionary*)obj;
410 NSString *minuteKey = (NSString*)key;
411 NSDate *date = [minutesTable valueForKey:minuteKey];
412 if([date compare:currentTime] == NSOrderedDescending){
413 foundTimeSlot = YES;
414 NSString* written = [minutesTable valueForKey:kMonitorWroteInTimeSlice];
415 if([written isEqualToString:@"NO"]){
416 [minutesTable setObject:@"YES" forKey:kMonitorWroteInTimeSlice];
417 if(previousTable != nil){
418 written = [previousTable valueForKey:kMonitorWroteInTimeSlice];
419 if([written isEqualToString:@"YES"]){
420 cWrites++;
421 }
422 else if ([written isEqualToString:@"NO"]){
423 cWrites = 0;
424 }
425 }
426 }
427 return;
428 }
429 previousTable = minutesTable;
430 }
431 }];
432 }];
433
434 if(foundTimeSlot == NO){
435 //reset the time table
436 secinfo("backoff","didn't find a time slot, resetting the table");
437 NSMutableDictionary *lastTable = [*timeTable valueForKey:kMonitorFifthMinute];
438 NSDate *lastDate = [lastTable valueForKey:kMonitorFifthMinute];
439
440 if((double)[currentTime timeIntervalSinceDate: lastDate] >= seconds_per_minute){
441 *consecutiveWrites = [[NSNumber alloc]initWithInt:0];
442 }
443 else{
444 NSString* written = [lastTable valueForKey:kMonitorWroteInTimeSlice];
445 if([written isEqualToString:@"YES"]){
446 cWrites++;
447 *consecutiveWrites = [[NSNumber alloc]initWithInt:cWrites];
448 }
449 else{
450 *consecutiveWrites = [[NSNumber alloc]initWithInt:0];
451 }
452 }
453
454 *timeTable = [self initializeTimeTable:key];
455 return;
456 }
457 *consecutiveWrites = [[NSNumber alloc]initWithInt:cWrites];
458 }
459 - (void)recordWriteToKVS:(NSDictionary *)values
460 {
461 if([_monitor count] == 0){
462 [values enumerateKeysAndObjectsUsingBlock: ^(id key, id obj, BOOL *stop)
463 {
464 [self initializeKeyEntry: key];
465 }];
466 }
467 else{
468 [values enumerateKeysAndObjectsUsingBlock: ^(id key, id obj, BOOL *stop)
469 {
470 NSMutableDictionary *keyEntry = [_monitor objectForKey:key];
471 if(keyEntry == nil){
472 [self initializeKeyEntry: key];
473 }
474 else{
475 NSNumber *penalty_timeout = [keyEntry objectForKey:kMonitorPenaltyBoxKey];
476 NSDate *lastWriteTimestamp = [keyEntry objectForKey:kMonitorLastWriteTimestamp];
477 NSMutableDictionary *timeTable = [keyEntry objectForKey: kMonitorTimeTable];
478 NSNumber *existingWrites = [keyEntry objectForKey: kMonitorConsecutiveWrites];
479 NSDate *currentTime = [NSDate date];
480
481 [self recordTimestampForAppropriateInterval:&timeTable key:key consecutiveWrites:&existingWrites];
482
483 int consecutiveWrites = [existingWrites intValue];
484 secinfo("backoff","consecutive writes: %d", consecutiveWrites);
485 [keyEntry setObject:existingWrites forKey:kMonitorConsecutiveWrites];
486 [keyEntry setObject:timeTable forKey:kMonitorTimeTable];
487 [keyEntry setObject:currentTime forKey:kMonitorLastWriteTimestamp];
488 [_monitor setObject:keyEntry forKey:key];
489
490 if([penalty_timeout intValue] != 0 || ((double)[currentTime timeIntervalSinceDate: lastWriteTimestamp] <= 60 && consecutiveWrites >= 5)){
491 if([penalty_timeout intValue] != 0)
492 secinfo("backoff","still in timeout, shouldn't write anything to KVS in this time period");
493 else
494 secinfo("backoff","monitor: keys have been written for 5 or more minutes, time to bump penalty timers");
495 [self increasePenalty:penalty_timeout key:key keyEntry:&keyEntry];
496 }
497 //keep writing freely but record it
498 else if((double)[currentTime timeIntervalSinceDate: lastWriteTimestamp] <= 60 && consecutiveWrites < 5){
499 secinfo("backoff","monitor: still writing freely");
500 }
501 }
502 }];
503 }
504 }
505
506 - (NSDictionary*)recordHaltedValuesAndReturnValuesToSafelyWrite:(NSDictionary *)values
507 {
508 NSMutableDictionary *SafeMessages = [NSMutableDictionary dictionary];
509 [values enumerateKeysAndObjectsUsingBlock: ^(id key, id obj, BOOL *stop)
510 {
511 NSMutableDictionary *keyEntry = [_monitor objectForKey:key];
512 NSNumber *penalty = [keyEntry objectForKey:kMonitorPenaltyBoxKey];
513 if([penalty intValue] != 0){
514 NSMutableDictionary* existingQueue = [keyEntry valueForKey:kMonitorMessageQueue];
515
516 [existingQueue setObject:obj forKey:key];
517
518 [keyEntry setObject:existingQueue forKey:kMonitorMessageQueue];
519 [_monitor setObject:keyEntry forKey:key];
520 }
521 else{
522 [SafeMessages setObject:obj forKey:key];
523 }
524 }];
525 return SafeMessages;
526 }
527
528 - (void)setObjectsFromDictionary:(NSDictionary *)values
529 {
530 NSUbiquitousKeyValueStore *store = [self cloudStore];
531 if (store && values)
532 {
533 secnoticeq("dsid", "Ensure DSIDs match");
534 NSMutableDictionary *mutableValues = [NSMutableDictionary dictionaryWithCapacity:0];
535
536 secinfo("backoff","!!writing these keys to KVS!!: %@", values);
537 [values enumerateKeysAndObjectsUsingBlock: ^(id key, id obj, BOOL *stop)
538 {
539 if (obj == NULL || obj == [NSNull null])
540 [store removeObjectForKey:key];
541
542 else if([key isEqualToString: @"^OfficialDSID"]){
543 _dsid = obj;
544 secnotice("dsid", "setting dsid to %@", obj);
545 }
546
547 else if([key isEqual: @"^Required"]){
548 if( [_dsid isEqualToString: @""]){
549 secdebug("dsid", "CloudKeychainProxy setting dsid to :%@ from securityd", obj);
550 _dsid = obj;
551 }
552
553 else if(![_dsid isEqual: obj]){
554 secerror("Account DSIDs do not match, cloud keychain proxy: %@, securityd: %@", _dsid, obj);
555 secerror("Not going to write these: %@ into KVS!", values);
556 return;
557 }
558 }
559 else
560 [ mutableValues setObject:obj forKey:key ];
561
562 }];
563
564 secnoticeq("keytrace", "%@ sending: %@", self, [[mutableValues allKeys] componentsJoinedByString: @" "]);
565 [mutableValues enumerateKeysAndObjectsUsingBlock: ^(id key, id obj, BOOL *stop)
566 {
567 if (obj == NULL || obj == [NSNull null])
568 [store removeObjectForKey:key];
569
570 else {
571 id oldObj = [store objectForKey:key];
572 if ([oldObj isEqual: obj]) {
573 if ([key hasPrefix:@"ak|"]) { // TODO: somewhat of a hack
574 // Fix KVS repeated message undelivery by sending a NULL first (deafness)
575 secnoticeq("keytrace", "forcing resend of peer-peer message: %@", key);
576 [store removeObjectForKey:key];
577 } else {
578 // Resending circle messages is bad
579 secnoticeq("keytrace", "repeated message: %@ (not peer-peer); not propogated", key);
580 }
581 }
582 [store setObject:obj forKey:key];
583 }
584 }];
585
586 [self requestSynchronization:NO];
587 }
588 else
589 secdebug(XPROXYSCOPE, "%@ NULL? store: %@, values: %@", self, store, values);
590 }
591
592 - (void)requestSynchronization:(bool)force
593 {
594 if (force)
595 {
596 secdebug(XPROXYSCOPE, "%@ synchronize (forced)", self);
597 [[self cloudStore] synchronize];
598 }
599 else
600 {
601 secdebug(XPROXYSCOPE, "%@ synchronize (soon)", self);
602 [[self cloudStore] synchronize];
603 }
604 }
605
606 - (NSUbiquitousKeyValueStore *)cloudStore
607 {
608 NSUbiquitousKeyValueStore *iCloudStore = [NSUbiquitousKeyValueStore defaultStore];
609 if (!iCloudStore) {
610 secerror("%s %@ NO NSUbiquitousKeyValueStore defaultStore", kWAIT2MINID, self);
611 }
612 return iCloudStore;
613 }
614
615 /*
616 Only call out to syncdefaultsd once every 5 seconds, since parameters can't change that
617 fast and callers expect synchonicity.
618
619 Since we don't actually get the values for the keys, just store off a timestamp.
620 */
621
622 // try to synchronize asap, and invoke the handler on completion to take incoming changes.
623
624 static bool isResubmitError(NSError* error) {
625 return error && (CFErrorGetCode((__bridge CFErrorRef) error) == UPDATE_RESUBMIT); // Why don't we check the domain?!
626 }
627
628 - (BOOL) AttemptSynchronization:(NSError **)failure
629 {
630
631 __block NSError *tempFailure = NULL;
632 int triesRemaining = 10;
633
634 NSUbiquitousKeyValueStore * store = [self cloudStore];
635
636 dispatch_semaphore_t freshSemaphore = dispatch_semaphore_create(0);
637
638 do {
639 --triesRemaining;
640 secnoticeq("fresh", "%s CALLING OUT TO syncdefaultsd SWCH: %@", kWAIT2MINID, self);
641
642 [store synchronizeWithCompletionHandler:^(NSError *error) {
643 if (error) {
644 tempFailure = error;
645 secerrorq("%s RETURNING FROM syncdefaultsd SWCH: %@: %@", kWAIT2MINID, self, error);
646 } else {
647 secnoticeq("fresh", "%s RETURNING FROM syncdefaultsd SWCH: %@", kWAIT2MINID, self);
648 [store synchronize]; // Per olivier in <rdar://problem/13412631>, sync before getting values
649 secnoticeq("fresh", "%s RETURNING FROM syncdefaultsd SYNC: %@", kWAIT2MINID, self);
650 }
651 dispatch_semaphore_signal(freshSemaphore);
652 }];
653 dispatch_semaphore_wait(freshSemaphore, DISPATCH_TIME_FOREVER);
654 } while (triesRemaining > 0 && isResubmitError(tempFailure));
655
656 if (isResubmitError(tempFailure)) {
657 secerrorq("%s Number of retry attempts to request freshness exceeded", kWAIT2MINID);
658 }
659
660 if (failure && (*failure == NULL)) {
661 *failure = tempFailure;
662 }
663
664 return tempFailure == nil;
665 }
666
667 static void wait_until(dispatch_time_t when) {
668 static dispatch_once_t once;
669 static dispatch_semaphore_t never_fires = nil;
670 dispatch_once(&once, ^{
671 never_fires = dispatch_semaphore_create(0);
672 });
673
674 dispatch_semaphore_wait(never_fires, when); // Will always timeout.
675 }
676
677 - (void)waitForSynchronization:(NSArray *)keys handler:(void (^)(NSDictionary *values, NSError *err))handler
678 {
679 if (!keys)
680 {
681 NSError *err = [NSError errorWithDomain:(NSString *)NSPOSIXErrorDomain code:(NSInteger)ENOPROTOOPT userInfo:nil];
682 secerrorq("%s RETURNING TO securityd: %@ param error; calling handler", kWAIT2MINID, self);
683 handler(@{}, err);
684 return;
685 }
686
687 secnoticeq("fresh", "%s Requesting freshness", kWAIT2MINID);
688
689 dispatch_async(_freshParamsQueue, ^{
690 // Hold off (keeping the queue occupied) until we hit the next time we can fresh.
691 wait_until(_nextFreshnessTime);
692
693 NSError *error = nil;
694 BOOL success = [self AttemptSynchronization:&error];
695
696 // Advance the next time can can call freshness.
697 const uint64_t delayBeforeCallingAgainInSeconds = 5ull * NSEC_PER_SEC;
698 _nextFreshnessTime = dispatch_time(DISPATCH_TIME_NOW, delayBeforeCallingAgainInSeconds);
699
700 dispatch_async(dispatch_get_main_queue(), ^{
701 NSDictionary * freshValues = success ? [self copyValues:[NSSet setWithArray:keys]] : @{};
702 handler(freshValues, error);
703 });
704 });
705 }
706
707 - (void)removeObjectForKey:(NSString *)keyToRemove
708 {
709 [[self cloudStore] removeObjectForKey:keyToRemove];
710 }
711
712 - (void)clearStore
713 {
714 secdebug(XPROXYSCOPE, "%@ clearStore", self);
715 NSDictionary *dict = [[self cloudStore] dictionaryRepresentation];
716 NSArray *allKeys = [dict allKeys];
717 NSMutableArray* nullKeys = [NSMutableArray array];
718
719 _alwaysKeys = [NSMutableSet setWithArray:nullKeys];
720 _firstUnlockKeys = [NSMutableSet setWithArray:nullKeys];
721 _unlockedKeys = [NSMutableSet setWithArray:nullKeys];
722 _keyParameterKeys = @{};
723 _circleKeys = @{};
724 _messageKeys = @{};
725
726 [allKeys enumerateObjectsUsingBlock:^(id key, NSUInteger idx, BOOL *stop)
727 {
728 secdebug(XPROXYSCOPE, "%@ Clearing value for key %@", self, key);
729 [[self cloudStore] removeObjectForKey:(NSString *)key];
730 }];
731
732 [self requestSynchronization:YES];
733 }
734
735
736 //
737 // MARK: ----- KVS key lists -----
738 //
739
740 - (id)get:(id)key
741 {
742 return [[self cloudStore] objectForKey:key];
743 }
744
745 - (NSDictionary *)getAll
746 {
747 return [[self cloudStore] dictionaryRepresentation];
748 }
749
750 - (NSDictionary*) exportKeyInterests
751 {
752
753 return @{ kKeyAlwaysKeys:[_alwaysKeys allObjects],
754 kKeyFirstUnlockKeys:[_firstUnlockKeys allObjects],
755 kKeyUnlockedKeys:[_unlockedKeys allObjects],
756 kMonitorState:_monitor,
757 kKeyPendingKeys:[_pendingKeys allObjects],
758 kKeySyncWithPeersPending:[NSNumber numberWithBool:_syncWithPeersPending],
759 kKeyEnsurePeerRegistration:[NSNumber numberWithBool:_ensurePeerRegistration],
760 kKeyDSID:_dsid
761 };
762 }
763
764 - (void) importKeyInterests: (NSDictionary*) interests
765 {
766 _alwaysKeys = [NSMutableSet setWithArray: interests[kKeyAlwaysKeys]];
767 _firstUnlockKeys = [NSMutableSet setWithArray: interests[kKeyFirstUnlockKeys]];
768 _unlockedKeys = [NSMutableSet setWithArray: interests[kKeyUnlockedKeys]];
769
770 _pendingKeys = [NSMutableSet setWithArray: interests[kKeyPendingKeys]];
771 _syncWithPeersPending = [interests[kKeySyncWithPeersPending] boolValue];
772 _ensurePeerRegistration = [interests[kKeyEnsurePeerRegistration] boolValue];
773 _dsid = interests[kKeyDSID];
774 _monitor = interests[kMonitorState];
775 if(_monitor == nil)
776 _monitor = [NSMutableDictionary dictionary];
777 }
778
779 - (NSMutableSet *)copyAllKeys
780 {
781 NSMutableSet *allKeys = [NSMutableSet setWithSet: _alwaysKeys];
782 [allKeys unionSet: _firstUnlockKeys];
783 [allKeys unionSet: _unlockedKeys];
784 return allKeys;
785 }
786
787 -(void)registerAtTimeKeys:(NSDictionary*)keyparms
788 {
789 NSArray *alwaysArray = [keyparms valueForKey: kKeyAlwaysKeys];
790 NSArray *firstUnlockedKeysArray = [keyparms valueForKey: kKeyFirstUnlockKeys];
791 NSArray *whenUnlockedKeysArray = [keyparms valueForKey: kKeyUnlockedKeys];
792
793 if(alwaysArray)
794 [_alwaysKeys unionSet: [NSMutableSet setWithArray: alwaysArray]];
795 if(firstUnlockedKeysArray)
796 [_firstUnlockKeys unionSet: [NSMutableSet setWithArray: firstUnlockedKeysArray]];
797 if(whenUnlockedKeysArray)
798 [_unlockedKeys unionSet: [NSMutableSet setWithArray: whenUnlockedKeysArray]];
799 }
800
801
802 - (void)registerKeys: (NSDictionary*)keys
803 {
804 secdebug(XPROXYSCOPE, "registerKeys: keys: %@", keys);
805
806 NSMutableSet *allOldKeys = [self copyAllKeys];
807
808 NSDictionary *keyparms = [keys valueForKey: [NSString stringWithUTF8String: kMessageKeyParameter]];
809 NSDictionary *circles = [keys valueForKey: [NSString stringWithUTF8String: kMessageCircle]];
810 NSDictionary *messages = [keys valueForKey: [NSString stringWithUTF8String: kMessageMessage]];
811
812 _alwaysKeys = [NSMutableSet set];
813 _firstUnlockKeys = [NSMutableSet set];
814 _unlockedKeys = [NSMutableSet set];
815
816 _keyParameterKeys = keyparms;
817 _circleKeys = circles;
818 _messageKeys = messages;
819
820 [self registerAtTimeKeys: _keyParameterKeys];
821 [self registerAtTimeKeys: _circleKeys];
822 [self registerAtTimeKeys: _messageKeys];
823
824 NSMutableSet *allNewKeys = [self copyAllKeys];
825
826 // Make sure keys we no longer care about are not pending
827 [_pendingKeys intersectSet:allNewKeys];
828 if (_shadowPendingKeys) {
829 [_shadowPendingKeys intersectSet:allNewKeys];
830 }
831
832 // All new keys only is new keys (remove old keys)
833 [allNewKeys minusSet:allOldKeys];
834
835 // Mark new keys pending, they're new!
836 NSMutableSet *newKeysForCurrentLockState = [self pendKeysAndGetNewlyPended:allNewKeys];
837
838 [self persistState]; // Before we might call out, save our state so we recover if we crash
839
840 [self intersectWithCurrentLockState: newKeysForCurrentLockState];
841 // TODO: Don't processPendingKeysForCurrentLockState if none of the new keys have values.
842 if ([newKeysForCurrentLockState count] != 0) {
843 [self processPendingKeysForCurrentLockState];
844 }
845 }
846
847 - (void)saveToUbiquitousStore
848 {
849 [self requestSynchronization:NO];
850 }
851
852 // MARK: ----- Event Handling -----
853
854 - (void)streamEvent:(xpc_object_t)notification
855 {
856 #if (!TARGET_IPHONE_SIMULATOR)
857 const char *notificationName = xpc_dictionary_get_string(notification, "Notification");
858 if (!notificationName) {
859 } else if (strcmp(notificationName, kUserKeybagStateChangeNotification)==0) {
860 return [self keybagStateChange];
861 } else if (strcmp(notificationName, kCloudKeychainStorechangeChangeNotification)==0) {
862 return [self kvsStoreChange];
863 } else if (strcmp(notificationName, kNotifyTokenForceUpdate)==0) {
864 // DEBUG -- Possibly remove in future
865 return [self processAllItems];
866 }
867 const char *eventName = xpc_dictionary_get_string(notification, "XPCEventName");
868 char *desc = xpc_copy_description(notification);
869 secnotice("event", "%@ event: %s name: %s desc: %s", self, eventName, notificationName, desc);
870 if (desc)
871 free((void *)desc);
872 #endif
873 }
874
875 - (void)iCloudAccountAvailabilityChanged:(NSNotification*)notification
876 {
877 /*
878 Continuing this example, you’d then implement an iCloudAccountAvailabilityChanged: method that would:
879
880 Call the ubiquityIdentityToken method and store its return value.
881 Compare the new value to the previous value, to find out if the user logged out of their account or
882 logged in to a different account. If the previously-used account is now unavailable, save the current
883 state locally as needed, empty your iCloud-related data caches, and refresh all iCloud-related user interface elements.
884 */
885 id previCloudToken = currentiCloudToken;
886 currentiCloudToken = [[NSFileManager defaultManager] ubiquityIdentityToken];
887 if (previCloudToken != currentiCloudToken)
888 secnotice("event", "%@ iCloud account changed!", self);
889 else
890 secnotice("event", "%@ %@", self, notification);
891 }
892
893 - (void)cloudChanged:(NSNotification*)notification
894 {
895 /*
896 Posted when the value of one or more keys in the local key-value store
897 changed due to incoming data pushed from iCloud. This notification is
898 sent only upon a change received from iCloud; it is not sent when your
899 app sets a value.
900
901 The user info dictionary can contain the reason for the notification as
902 well as a list of which values changed, as follows:
903
904 The value of the NSUbiquitousKeyValueStoreChangeReasonKey key, when
905 present, indicates why the key-value store changed. Its value is one of
906 the constants in "Change Reason Values."
907
908 The value of the NSUbiquitousKeyValueStoreChangedKeysKey, when present,
909 is an array of strings, each the name of a key whose value changed. The
910 notification object is the NSUbiquitousKeyValueStore object whose contents
911 changed.
912
913 NSUbiquitousKeyValueStoreInitialSyncChange is only posted if there is any
914 local value that has been overwritten by a distant value. If there is no
915 conflict between the local and the distant values when doing the initial
916 sync (e.g. if the cloud has no data stored or the client has not stored
917 any data yet), you'll never see that notification.
918
919 NSUbiquitousKeyValueStoreInitialSyncChange implies an initial round trip
920 with server but initial round trip with server does not imply
921 NSUbiquitousKeyValueStoreInitialSyncChange.
922 */
923 os_activity_initiate("cloudChanged", OS_ACTIVITY_FLAG_DEFAULT, ^{
924 secdebug(XPROXYSCOPE, "%@ cloudChanged notification: %@", self, notification);
925
926 NSDictionary *userInfo = [notification userInfo];
927 NSNumber *reason = [userInfo objectForKey:NSUbiquitousKeyValueStoreChangeReasonKey];
928 if (reason) switch ([reason integerValue]) {
929 case NSUbiquitousKeyValueStoreInitialSyncChange:
930 case NSUbiquitousKeyValueStoreServerChange:
931 {
932 _seenKVSStoreChange = YES;
933 NSSet *keysChangedInCloud = [NSSet setWithArray:[userInfo objectForKey:NSUbiquitousKeyValueStoreChangedKeysKey]];
934
935 /* We are saying that we want to try processing a key no matter what,
936 * *if* it has changed in the cloud. */
937 [_pendingKeys minusSet:keysChangedInCloud];
938
939 NSSet *keysOfInterestThatChanged = [self pendKeysAndGetPendingForCurrentLockState:keysChangedInCloud];
940 NSMutableDictionary *changedValues = [self copyValues:keysOfInterestThatChanged];
941 if ([reason integerValue] == NSUbiquitousKeyValueStoreInitialSyncChange)
942 changedValues[(__bridge NSString*)kSOSKVSInitialSyncKey] = @"true";
943
944 secnotice("event", "%@ keysChangedInCloud: %@ keysOfInterest: %@", self, [[keysChangedInCloud allObjects] componentsJoinedByString: @" "], [[changedValues allKeys] componentsJoinedByString: @" "]);
945 if ([changedValues count])
946 [self processKeyChangedEvent:changedValues];
947 break;
948 }
949 case NSUbiquitousKeyValueStoreQuotaViolationChange:
950 seccritical("%@ event received NSUbiquitousKeyValueStoreQuotaViolationChange", self);
951 break;
952 case NSUbiquitousKeyValueStoreAccountChange:
953 // The primary account changed. We do not get this for password changes on the same account
954 secnotice("event", "%@ NSUbiquitousKeyValueStoreAccountChange", self);
955 NSDictionary *changedValues = nil;
956 if(_dsid)
957 changedValues = @{ (__bridge NSString*)kSOSKVSAccountChangedKey: _dsid };
958 else
959 changedValues = @{ (__bridge NSString*)kSOSKVSAccountChangedKey: @"true" };
960
961 [self processKeyChangedEvent:changedValues];
962 break;
963 }
964 });
965 }
966
967 - (void) doAfterFlush: (dispatch_block_t) block
968 {
969 // Flush any pending communication to Securityd.
970
971 dispatch_async(_calloutQueue, block);
972 }
973
974 - (void) calloutWith: (void(^)(NSSet *pending, bool syncWithPeersPending, bool ensurePeerRegistration, dispatch_queue_t queue, void(^done)(NSSet *handledKeys, bool handledSyncWithPeers, bool handledEnsurePeerRegistration))) callout
975 {
976 // In CKDKVSProxy's serial queue
977
978 _oldInCallout = YES;
979
980 // dispatch_get_global_queue - well-known global concurrent queue
981 // dispatch_get_main_queue - default queue that is bound to the main thread
982 xpc_transaction_begin();
983 dispatch_async(_calloutQueue, ^{
984 __block NSSet *myPending;
985 __block bool mySyncWithPeersPending;
986 __block bool myEnsurePeerRegistration;
987 __block bool wasLocked;
988 dispatch_sync(_ckdkvsproxy_queue, ^{
989 myPending = [_pendingKeys copy];
990 mySyncWithPeersPending = _syncWithPeersPending;
991 myEnsurePeerRegistration = _ensurePeerRegistration;
992 wasLocked = _isLocked;
993
994 _inCallout = YES;
995 if (!_oldInCallout)
996 secnotice("deaf", ">>>>>>>>>>> _oldInCallout is NO and we're heading in to the callout!");
997
998 _shadowPendingKeys = [NSMutableSet set];
999 _shadowSyncWithPeersPending = NO;
1000 });
1001
1002 callout(myPending, mySyncWithPeersPending, myEnsurePeerRegistration, _ckdkvsproxy_queue, ^(NSSet *handledKeys, bool handledSyncWithPeers, bool handledEnsurePeerRegistration) {
1003 secdebug("event", "%@ %s%s before callout handled: %s%s", self, mySyncWithPeersPending ? "S" : "s", myEnsurePeerRegistration ? "E" : "e", handledSyncWithPeers ? "S" : "s", handledEnsurePeerRegistration ? "E" : "e");
1004
1005 // In CKDKVSProxy's serial queue
1006 _inCallout = NO;
1007 _oldInCallout = NO;
1008
1009 // Update ensurePeerRegistration
1010 _ensurePeerRegistration = ((myEnsurePeerRegistration && !handledEnsurePeerRegistration) || _shadowEnsurePeerRegistration);
1011
1012 _shadowEnsurePeerRegistration = NO;
1013
1014 if(_ensurePeerRegistration && !_isLocked)
1015 [self doEnsurePeerRegistration];
1016
1017 // Update SyncWithPeers stuff.
1018 _syncWithPeersPending = ((mySyncWithPeersPending && (!handledSyncWithPeers)) || _shadowSyncWithPeersPending);
1019
1020 _shadowSyncWithPeersPending = NO;
1021 if (handledSyncWithPeers)
1022 _lastSyncTime = dispatch_time(DISPATCH_TIME_NOW, 0);
1023
1024 // Update pendingKeys and handle them
1025 [_pendingKeys minusSet: handledKeys];
1026 bool hadShadowPendingKeys = [_shadowPendingKeys count];
1027 // Move away shadownPendingKeys first, because pendKeysAndGetPendingForCurrentLockState
1028 // will look at them. See rdar://problem/20733166.
1029 NSSet *oldShadowPendingKeys = _shadowPendingKeys;
1030 _shadowPendingKeys = nil;
1031
1032 NSSet *filteredKeys = [self pendKeysAndGetPendingForCurrentLockState:oldShadowPendingKeys];
1033
1034 // Write state to disk
1035 [self persistState];
1036
1037 // Handle shadow pended stuff
1038 if (_syncWithPeersPending && !_isLocked)
1039 [self scheduleSyncRequestTimer];
1040 /* We don't want to call processKeyChangedEvent if we failed to
1041 handle pending keys and the device didn't unlock nor receive
1042 any kvs changes while we were in our callout.
1043 Doing so will lead to securityd and CloudKeychainProxy
1044 talking to each other forever in a tight loop if securityd
1045 repeatedly returns an error processing the same message.
1046 Instead we leave any old pending keys until the next event. */
1047 if (hadShadowPendingKeys || (!_isLocked && wasLocked))
1048 [self processKeyChangedEvent:[self copyValues:filteredKeys]];
1049
1050 xpc_transaction_end();
1051 });
1052 });
1053 }
1054
1055 - (void) sendKeysCallout: (NSSet *(^)(NSSet* pending, NSError** error)) handleKeys {
1056 [self calloutWith: ^(NSSet *pending, bool syncWithPeersPending, bool ensurePeerRegistration, dispatch_queue_t queue, void(^done)(NSSet *, bool, bool)) {
1057 NSError* error = NULL;
1058
1059 NSSet * handled = handleKeys(pending, &error);
1060
1061 dispatch_async(queue, ^{
1062 if (!handled) {
1063 secerror("%@ ensurePeerRegistration failed: %@", self, error);
1064 }
1065
1066 done(handled, NO, NO);
1067 });
1068 }];
1069 }
1070
1071 - (void) doEnsurePeerRegistration
1072 {
1073 [self calloutWith:^(NSSet *pending, bool syncWithPeersPending, bool ensurePeerRegistration, dispatch_queue_t queue, void(^done)(NSSet *, bool, bool)) {
1074 CFErrorRef error = NULL;
1075 bool handledEnsurePeerRegistration = SOSCCProcessEnsurePeerRegistration(&error);
1076 secerror("%@ ensurePeerRegistration called, %@ (%@)", self, handledEnsurePeerRegistration ? @"success" : @"failure", error);
1077 dispatch_async(queue, ^{
1078 if (!handledEnsurePeerRegistration) {
1079 secerror("%@ ensurePeerRegistration failed: %@", self, error);
1080 }
1081
1082 done(nil, NO, handledEnsurePeerRegistration);
1083 CFReleaseSafe(error);
1084 });
1085 }];
1086 }
1087
1088 - (void) doSyncWithAllPeers
1089 {
1090 [self calloutWith:^(NSSet *pending, bool syncWithPeersPending, bool ensurePeerRegistration, dispatch_queue_t queue, void(^done)(NSSet *, bool, bool)) {
1091 CFErrorRef error = NULL;
1092
1093 SyncWithAllPeersReason reason = SOSCCProcessSyncWithAllPeers(&error);
1094 dispatch_async(queue, ^{
1095 bool handledSyncWithPeers = NO;
1096 if (reason == kSyncWithAllPeersSuccess) {
1097 handledSyncWithPeers = YES;
1098 secnotice("event", "%@ syncWithAllPeers succeeded", self);
1099 } else if (reason == kSyncWithAllPeersLocked) {
1100 secnotice("event", "%@ syncWithAllPeers attempted while locked - waiting for unlock", self);
1101 handledSyncWithPeers = NO;
1102 [self updateIsLocked];
1103 } else if (reason == kSyncWithAllPeersOtherFail) {
1104 // Pretend we handled syncWithPeers, by pushing out the _lastSyncTime
1105 // This will cause us to wait for kMinSyncInterval seconds before
1106 // retrying, so we don't spam securityd if sync is failing
1107 secerror("%@ syncWithAllPeers %@, rescheduling timer", self, error);
1108 _lastSyncTime = dispatch_time(DISPATCH_TIME_NOW, 0);
1109 } else {
1110 secerror("%@ syncWithAllPeers %@, unknown reason: %d", self, error, reason);
1111 }
1112
1113 done(nil, handledSyncWithPeers, false);
1114 CFReleaseSafe(error);
1115 });
1116 }];
1117 }
1118
1119 - (void)timerFired
1120 {
1121 secnotice("event", "%@ syncWithPeersPending: %d inCallout: %d isLocked: %d", self, _syncWithPeersPending, _inCallout, _isLocked);
1122 _syncTimerScheduled = NO;
1123 if(_ensurePeerRegistration){
1124 [self doEnsurePeerRegistration];
1125 }
1126 if (_syncWithPeersPending && !_inCallout && !_isLocked){
1127 [self doSyncWithAllPeers];
1128 }
1129 }
1130
1131 - (dispatch_time_t) nextSyncTime
1132 {
1133 dispatch_time_t nextSync = dispatch_time(DISPATCH_TIME_NOW, kMinSyncDelay);
1134
1135 // Don't sync again unless we waited at least kMinSyncInterval
1136 if (_lastSyncTime) {
1137 dispatch_time_t soonest = dispatch_time(_lastSyncTime, kMinSyncInterval);
1138 if (nextSync < soonest || _deadline < soonest) {
1139 secdebug("timer", "%@ backing off", self);
1140 return soonest;
1141 }
1142 }
1143
1144 // Don't delay more than kMaxSyncDelay after the first request.
1145 if (nextSync > _deadline) {
1146 secdebug("timer", "%@ hit deadline", self);
1147 return _deadline;
1148 }
1149
1150 // Bump the timer by kMinSyncDelay
1151 if (_syncTimerScheduled)
1152 secdebug("timer", "%@ bumped timer", self);
1153 else
1154 secdebug("timer", "%@ scheduled timer", self);
1155
1156 return nextSync;
1157 }
1158
1159 - (void)scheduleSyncRequestTimer
1160 {
1161 dispatch_source_set_timer(_syncTimer, [self nextSyncTime], DISPATCH_TIME_FOREVER, kSyncTimerLeeway);
1162 _syncTimerScheduled = YES;
1163 }
1164
1165 - (void)requestSyncWithAllPeers // secd calling SOSCCSyncWithAllPeers invokes this
1166 {
1167 #if !defined(NDEBUG)
1168 NSString *desc = [self description];
1169 #endif
1170
1171 if (!_syncWithPeersPending || (_inCallout && !_shadowSyncWithPeersPending))
1172 _deadline = dispatch_time(DISPATCH_TIME_NOW, kMaxSyncDelay);
1173
1174 if (!_syncWithPeersPending) {
1175 _syncWithPeersPending = YES;
1176 [self persistState];
1177 }
1178
1179 if (_inCallout)
1180 _shadowSyncWithPeersPending = YES;
1181 else if (!_isLocked)
1182 [self scheduleSyncRequestTimer];
1183
1184 secdebug("event", "%@ %@", desc, self);
1185 }
1186
1187 - (void)requestEnsurePeerRegistration // secd calling SOSCCSyncWithAllPeers invokes this
1188 {
1189 #if !defined(NDEBUG)
1190 NSString *desc = [self description];
1191 #endif
1192
1193 if (_inCallout) {
1194 _shadowEnsurePeerRegistration = YES;
1195 } else {
1196 _ensurePeerRegistration = YES;
1197 if (!_isLocked){
1198 [self doEnsurePeerRegistration];
1199 }
1200 [self persistState];
1201 }
1202
1203 secdebug("event", "%@ %@", desc, self);
1204 }
1205
1206
1207 - (BOOL) updateUnlockedSinceBoot
1208 {
1209 CFErrorRef aksError = NULL;
1210 if (!SecAKSGetHasBeenUnlocked(&_unlockedSinceBoot, &aksError)) {
1211 secerror("%@ Got error from SecAKSGetHasBeenUnlocked: %@", self, aksError);
1212 CFReleaseSafe(aksError);
1213 return NO;
1214 }
1215 return YES;
1216 }
1217
1218 - (BOOL) updateIsLocked
1219 {
1220 CFErrorRef aksError = NULL;
1221 if (!SecAKSGetIsLocked(&_isLocked, &aksError)) {
1222 secerror("%@ Got error querying lock state: %@", self, aksError);
1223 CFReleaseSafe(aksError);
1224 return NO;
1225 }
1226 if (!_isLocked)
1227 _unlockedSinceBoot = YES;
1228 return YES;
1229 }
1230
1231 - (void) keybagStateChange
1232 {
1233 os_activity_initiate("keybagStateChanged", OS_ACTIVITY_FLAG_DEFAULT, ^{
1234 BOOL wasLocked = _isLocked;
1235 if ([self updateIsLocked]) {
1236 if (wasLocked == _isLocked)
1237 secdebug("event", "%@ still %s ignoring", self, _isLocked ? "locked" : "unlocked");
1238 else if (_isLocked)
1239 [self keybagDidLock];
1240 else
1241 [self keybagDidUnlock];
1242 }
1243 });
1244 }
1245
1246 - (void) keybagDidLock
1247 {
1248 secnotice("event", "%@", self);
1249 }
1250
1251 - (void) keybagDidUnlock
1252 {
1253 secnotice("event", "%@", self);
1254 if (_ensurePeerRegistration) {
1255 [self doEnsurePeerRegistration];
1256 }
1257
1258 // First send changed keys to securityd so it can proccess updates
1259 [self processPendingKeysForCurrentLockState];
1260
1261 // Then, tickle securityd to perform a sync if needed.
1262 if (_syncWithPeersPending && !_syncTimerScheduled) {
1263 [self doSyncWithAllPeers];
1264 }
1265 }
1266
1267 - (void) kvsStoreChange {
1268 os_activity_initiate("kvsStoreChange", OS_ACTIVITY_FLAG_DEFAULT, ^{
1269 if (!_seenKVSStoreChange) {
1270 _seenKVSStoreChange = YES; // Only do this once
1271 secnotice("event", "%@ received darwin notification before first NSNotification", self);
1272 // TODO This might not be needed if we always get the NSNotification
1273 // deleived even if we were launched due to a kvsStoreChange
1274 // Send all keys for current lock state to securityd so it can proccess them
1275 [self pendKeysAndGetNewlyPended: [self copyAllKeys]];
1276 [self processPendingKeysForCurrentLockState];
1277 } else {
1278 secdebug("event", "%@ ignored, waiting for NSNotification", self);
1279 }
1280 });
1281 }
1282
1283 //
1284 // MARK: ----- Key Filtering -----
1285 //
1286
1287 - (NSSet*) keysForCurrentLockState
1288 {
1289 secdebug("filtering", "%@ Filtering: unlockedSinceBoot: %d\n unlocked: %d\n, keysOfInterest: <%@>", self, (int) _unlockedSinceBoot, (int) !_isLocked, [SOSPersistentState dictionaryDescription: [self exportKeyInterests]]);
1290
1291 NSMutableSet *currentStateKeys = [NSMutableSet setWithSet: _alwaysKeys];
1292 if (_unlockedSinceBoot)
1293 [currentStateKeys unionSet: _firstUnlockKeys];
1294
1295 if (!_isLocked)
1296 [currentStateKeys unionSet: _unlockedKeys];
1297
1298 return currentStateKeys;
1299 }
1300
1301 - (NSMutableSet*) pendKeysAndGetNewlyPended: (NSSet*) keysToPend
1302 {
1303 NSMutableSet *newlyPendedKeys = [keysToPend mutableCopy];
1304 [newlyPendedKeys minusSet: _pendingKeys];
1305 if (_shadowPendingKeys) {
1306 [newlyPendedKeys minusSet: _shadowPendingKeys];
1307 }
1308
1309 [_pendingKeys unionSet:keysToPend];
1310 if (_shadowPendingKeys) {
1311 [_shadowPendingKeys unionSet:keysToPend];
1312 }
1313
1314 return newlyPendedKeys;
1315 }
1316
1317 - (void) intersectWithCurrentLockState: (NSMutableSet*) set
1318 {
1319 [set intersectSet: [self keysForCurrentLockState]];
1320 }
1321
1322 - (NSMutableSet*) pendingKeysForCurrentLockState
1323 {
1324 NSMutableSet * result = [_pendingKeys mutableCopy];
1325 [self intersectWithCurrentLockState:result];
1326 return result;
1327 }
1328
1329 - (NSMutableSet*) pendKeysAndGetPendingForCurrentLockState: (NSSet*) startingSet
1330 {
1331 [self pendKeysAndGetNewlyPended: startingSet];
1332
1333 return [self pendingKeysForCurrentLockState];
1334 }
1335
1336 - (NSMutableDictionary *)copyValues:(NSSet*)keysOfInterest
1337 {
1338 // Grab values from KVS.
1339 NSUbiquitousKeyValueStore *store = [self cloudStore];
1340 NSMutableDictionary *changedValues = [NSMutableDictionary dictionaryWithCapacity:0];
1341 [keysOfInterest enumerateObjectsUsingBlock:^(id obj, BOOL *stop)
1342 {
1343 NSString* key = (NSString*) obj;
1344 id objval = [store objectForKey:key];
1345 if (!objval) objval = [NSNull null];
1346
1347 [changedValues setObject:objval forKey:key];
1348 secdebug(XPROXYSCOPE, "%@ storeChanged updated value for %@", self, key);
1349 }];
1350 return changedValues;
1351 }
1352
1353 /*
1354 During RegisterKeys, separate keys-of-interest into three disjoint sets:
1355 - keys that we always want to be notified about; this means we can get the
1356 value at any time
1357 - keys that require the device to have been unlocked at least once
1358 - keys that require the device to be unlocked now
1359
1360 Typically, the sets of keys will be:
1361
1362 - Dk: alwaysKeys
1363 - Ck: firstUnlock
1364 - Ak: unlocked
1365
1366 The caller is responsible for making sure that the keys in e.g. alwaysKeys are
1367 values that can be handled at any time (that is, not when unlocked)
1368
1369 Each time we get a notification from ubiquity that keys have changed, we need to
1370 see if anything of interest changed. If we don't care, then done.
1371
1372 For each key-of-interest that changed, we either notify the client that things
1373 changed, or add it to a pendingNotifications list. If the notification to the
1374 client fails, also add it to the pendingNotifications list. This pending list
1375 should be written to persistent storage and consulted any time we either get an
1376 item changed notification, or get a stream event signalling a change in lock state.
1377
1378 We can notify the client either through XPC if a connection is set up, or call a
1379 routine in securityd to launch it.
1380
1381 */
1382
1383 - (void)processKeyChangedEvent:(NSDictionary *)changedValues
1384 {
1385 NSMutableDictionary* filtered = [NSMutableDictionary dictionary];
1386
1387 NSMutableArray* nullKeys = [NSMutableArray array];
1388 // Remove nulls because we don't want them in securityd.
1389 [changedValues enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
1390 if (obj == [NSNull null])
1391 [nullKeys addObject:key];
1392 else{
1393 filtered[key] = obj;
1394 }
1395 }];
1396 if ([nullKeys count])
1397 [_pendingKeys minusSet: [NSSet setWithArray: nullKeys]];
1398
1399 if([filtered count] != 0 ){
1400 [self sendKeysCallout:^NSSet *(NSSet *pending, NSError** error) {
1401 CFErrorRef cf_error = NULL;
1402 NSArray* handledMessage = (__bridge_transfer NSArray*) _SecKeychainSyncUpdateMessage((__bridge CFDictionaryRef)filtered, &cf_error);
1403 NSError *updateError = (__bridge_transfer NSError*)cf_error;
1404 if (error)
1405 *error = updateError;
1406
1407 secnoticeq("keytrace", "%@ misc handled: %@ null: %@ pending: %@", self,
1408 [handledMessage componentsJoinedByString: @" "],
1409 [nullKeys componentsJoinedByString: @" "],
1410 [[_pendingKeys allObjects] componentsJoinedByString: @" "]);
1411
1412 return handledMessage ? [NSSet setWithArray: handledMessage] : nil;
1413 }];
1414 } else {
1415 secnoticeq("keytrace", "%@ null: %@ pending: %@", self,
1416 [nullKeys componentsJoinedByString: @" "],
1417 [[_pendingKeys allObjects] componentsJoinedByString: @" "]);
1418 }
1419 }
1420
1421 - (void) processPendingKeysForCurrentLockState
1422 {
1423 [self processKeyChangedEvent: [self copyValues: [self pendingKeysForCurrentLockState]]];
1424 }
1425
1426 @end
1427
1428