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