]> git.saurik.com Git - apple/security.git/blob - Security/sec/SOSCircle/CloudKeychainProxy/CKDKVSProxy.m
Security-57031.1.35.tar.gz
[apple/security.git] / Security / 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
39 #import "CKDKVSProxy.h"
40 #import "CKDPersistentState.h"
41 #import "CKDUserInteraction.h"
42
43 #import "SOSARCDefines.h"
44
45 #include <SecureObjectSync/SOSAccount.h>
46 #include <SecureObjectSync/SOSCloudCircleInternal.h>
47 #include <SecureObjectSync/SOSKVSKeys.h>
48
49 #include "SOSCloudKeychainConstants.h"
50
51 #include <utilities/SecAKSWrappers.h>
52 #include <utilities/SecCFRelease.h>
53
54 /*
55 The total space available in your app’s iCloud key-value storage is 1 MB.
56 The maximum number of keys you can specify is 1024, and the size limit for
57 each value associated with a key is 1 MB. So, for example, if you store a
58 single large value of 1 MB for a single key, that consumes your total
59 available storage. If you store 1 KB of data for each key, you can use
60 1,000 key-value pairs.
61 */
62
63 static const char *kStreamName = "com.apple.notifyd.matching";
64
65 static NSString *kKeyKeyParameterKeys = @"KeyParameterKeys";
66 static NSString *kKeyCircleKeys = @"CircleKeys";
67 static NSString *kKeyMessageKeys = @"MessageKeys";
68
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
79 enum
80 {
81 kCallbackMethodSecurityd = 0,
82 kCallbackMethodXPC = 1,
83 };
84
85 static const int64_t kMinSyncDelay = (NSEC_PER_MSEC * 500); // 500ms minimum delay before a syncWithAllPeers call.
86 static const int64_t kMaxSyncDelay = (NSEC_PER_SEC * 5); // 5s maximun delay for a given request
87 static const int64_t kMinSyncInterval = (NSEC_PER_SEC * 15); // 15s minimum time between successive syncWithAllPeers calls.
88 static const int64_t kSyncTimerLeeway = (NSEC_PER_MSEC * 250); // 250ms leeway for sync events.
89
90 @interface NSUbiquitousKeyValueStore (NSUbiquitousKeyValueStore_PrivateZ)
91 - (void) _synchronizeWithCompletionHandler:(void (^)(NSError *error))completionHandler;
92
93 /*
94 // SPI For Security
95 - (void) synchronizeWithCompletionHandler:(void (^)(NSError *error))completionHandler;
96 */
97
98 @end
99
100 @implementation UbiqitousKVSProxy
101
102
103 - (void)persistState
104 {
105 [SOSPersistentState setRegisteredKeys:[self exportKeyInterests]];
106 }
107
108 + (UbiqitousKVSProxy *) sharedKVSProxy
109 {
110 static UbiqitousKVSProxy *sharedKVSProxy;
111 if (!sharedKVSProxy) {
112 static dispatch_once_t onceToken;
113 dispatch_once(&onceToken, ^{
114 sharedKVSProxy = [[self alloc] init];
115 });
116 }
117 return sharedKVSProxy;
118 }
119
120 - (id)init
121 {
122 if (self = [super init])
123 {
124 secnotice("event", "%@ start", self);
125
126 _calloutQueue = dispatch_queue_create("CKDCallout", DISPATCH_QUEUE_SERIAL);
127 _freshParamsQueue = dispatch_queue_create("CKDFresh", DISPATCH_QUEUE_SERIAL);
128 _syncTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue());
129 dispatch_source_set_timer(_syncTimer, DISPATCH_TIME_FOREVER, DISPATCH_TIME_FOREVER, kSyncTimerLeeway);
130 dispatch_source_set_event_handler(_syncTimer, ^{
131 [self timerFired];
132 });
133 dispatch_resume(_syncTimer);
134
135 [[NSNotificationCenter defaultCenter]
136 addObserver: self
137 selector: @selector (iCloudAccountAvailabilityChanged:)
138 name: NSUbiquityIdentityDidChangeNotification
139 object: nil];
140
141 [[NSNotificationCenter defaultCenter] addObserver:self
142 selector:@selector(cloudChanged:)
143 name:NSUbiquitousKeyValueStoreDidChangeExternallyNotification
144 object:nil];
145
146 [self importKeyInterests: [SOSPersistentState registeredKeys]];
147
148 // Register for lock state changes
149 xpc_set_event_stream_handler(kStreamName, dispatch_get_main_queue(),
150 ^(xpc_object_t notification){
151 [self streamEvent:notification];
152 });
153
154 [self updateUnlockedSinceBoot];
155 [self updateIsLocked];
156 if (!_isLocked)
157 [self keybagDidUnlock];
158
159 secdebug(XPROXYSCOPE, "%@ done", self);
160 }
161 return self;
162 }
163
164 - (NSString *)description
165 {
166 return [NSString stringWithFormat:@"<%s%s%s%s%s%s%s%s%s%s%s>",
167 _isLocked ? "L" : "U",
168 _unlockedSinceBoot ? "B" : "-",
169 _seenKVSStoreChange ? "K" : "-",
170 _syncTimerScheduled ? "T" : "-",
171 _syncWithPeersPending ? "s" : "-",
172 _ensurePeerRegistration ? "e" : "-",
173 [_pendingKeys count] ? "p" : "-",
174 _inCallout ? "C" : "-",
175 _shadowSyncWithPeersPending ? "S" : "-",
176 _shadowEnsurePeerRegistration ? "E" : "-",
177 [_shadowPendingKeys count] ? "P" : "-"];
178 }
179
180 - (void)processAllItems
181 {
182 NSDictionary *allItems = [self getAll];
183 if (allItems)
184 {
185 secnotice("event", "%@ sending: %@", self, [[allItems allKeys] componentsJoinedByString: @" "]);
186 [self processKeyChangedEvent:allItems];
187 }
188 else
189 secdebug(XPROXYSCOPE, "%@ No items in KVS", self);
190 }
191
192 - (void)setItemsChangedBlock:(CloudItemsChangedBlock)itemsChangedBlock
193 {
194 self->itemsChangedCallback = itemsChangedBlock;
195 }
196
197 - (void)dealloc
198 {
199 secdebug(XPROXYSCOPE, "%@", self);
200 [[NSNotificationCenter defaultCenter] removeObserver:self
201 name:NSUbiquitousKeyValueStoreDidChangeExternallyNotification object:nil];
202
203 [[NSNotificationCenter defaultCenter] removeObserver:self
204 name:NSUbiquityIdentityDidChangeNotification object:nil];
205 }
206
207 // MARK: ----- Client Interface -----
208
209 - (void)setObject:(id)obj forKey:(id)key
210 {
211 secdebug("keytrace", "%@ key: %@, obj: %@", self, key, obj);
212 NSUbiquitousKeyValueStore *store = [self cloudStore];
213 if (store)
214 {
215 id value = [store objectForKey:key];
216 if (value)
217 secdebug("keytrace", "%@ Current value (before set) for key %@ is: %@", self, key, value);
218 else
219 secdebug("keytrace", "%@ No current value for key %@", self, key);
220 }
221 else
222 secdebug("keytrace", "Can't get store");
223
224 [[self cloudStore] setObject:obj forKey:key];
225 [self requestSynchronization:NO];
226 }
227
228 - (void)setObjectsFromDictionary:(NSDictionary *)values
229 {
230 secdebug(XPROXYSCOPE, "%@ start: %lu values to put", self, (unsigned long)[values count]);
231
232 NSUbiquitousKeyValueStore *store = [self cloudStore];
233 if (store && values)
234 {
235 [values enumerateKeysAndObjectsUsingBlock: ^(id key, id obj, BOOL *stop)
236 {
237 if (obj == NULL || obj == [NSNull null])
238 [store removeObjectForKey:key];
239 else
240 [store setObject:obj forKey:key];
241 }];
242
243 [self requestSynchronization:NO];
244 }
245 else
246 secdebug(XPROXYSCOPE, "%@ NULL? store: %@, values: %@", self, store, values);
247 }
248
249 - (void)requestSynchronization:(bool)force
250 {
251 if (force)
252 {
253 secdebug(XPROXYSCOPE, "%@ synchronize (forced)", self);
254 [[self cloudStore] synchronize];
255 }
256 else
257 {
258 secdebug(XPROXYSCOPE, "%@ synchronize (soon)", self);
259 [[self cloudStore] synchronize];
260 }
261 }
262
263 - (NSUbiquitousKeyValueStore *)cloudStore
264 {
265 NSUbiquitousKeyValueStore *iCloudStore = [NSUbiquitousKeyValueStore defaultStore];
266 if (!iCloudStore) {
267 secerror("%s %@ NO NSUbiquitousKeyValueStore defaultStore", kWAIT2MINID, self);
268 }
269 return iCloudStore;
270 }
271
272 /*
273 Only call out to syncdefaultsd once every 5 seconds, since parameters can't change that
274 fast and callers expect synchonicity.
275
276 Since we don't actually get the values for the keys, just store off a timestamp.
277 */
278
279 // try to synchronize asap, and invoke the handler on completion to take incoming changes.
280
281
282 - (void)waitForSynchronization:(NSArray *)keys handler:(void (^)(NSDictionary *values, NSError *err))handler
283 {
284 if (!keys)
285 {
286 NSError *err = [NSError errorWithDomain:(NSString *)NSPOSIXErrorDomain code:(NSInteger)ENOPROTOOPT userInfo:nil];
287 secerrorq("%s RETURNING TO securityd: %@ param error; calling handler", kWAIT2MINID, self);
288 handler(@{}, err);
289 return;
290 }
291
292 NSUbiquitousKeyValueStore * store = [self cloudStore];
293
294 secnoticeq("fresh", "%s Requesting freshness", kWAIT2MINID);
295 dispatch_async(_freshParamsQueue, ^{
296 // _freshParamsQueue
297 __block NSError *failure = NULL;
298 __block dispatch_time_t next_time = _nextFreshnessTime;
299 dispatch_time_t now = dispatch_time(DISPATCH_TIME_NOW, 0);
300 if (now > next_time) {
301 dispatch_semaphore_t freshSemaphore = dispatch_semaphore_create(0);
302 secnoticeq("fresh", "%s CALLING OUT TO syncdefaultsd SWCH: %@", kWAIT2MINID, self);
303 [store synchronizeWithCompletionHandler:^(NSError *error) {
304 if (error) {
305 failure = error;
306 secerrorq("%s RETURNING FROM syncdefaultsd SWCH: %@: %@", kWAIT2MINID, self, error);
307 } else {
308 secnoticeq("fresh", "%s RETURNING FROM syncdefaultsd SWCH: %@", kWAIT2MINID, self);
309
310 [store synchronize]; // Per olivier in <rdar://problem/13412631>, sync before getting values
311 secnoticeq("fresh", "%s RETURNING FROM syncdefaultsd SYNC: %@", kWAIT2MINID, self);
312
313 // This hysteresis balances between failing to notice parameters from remote devices and
314 // pestering synceddefaultsd with clients many repeated requests to do freshness.
315 const uint64_t delayBeforeCallingAgainInSeconds = 5ull * NSEC_PER_SEC;
316 next_time = dispatch_time(DISPATCH_TIME_NOW, delayBeforeCallingAgainInSeconds);
317 }
318 dispatch_semaphore_signal(freshSemaphore);
319 }];
320 dispatch_semaphore_wait(freshSemaphore, DISPATCH_TIME_FOREVER);
321 }
322
323 dispatch_async(dispatch_get_main_queue(), ^{
324 _nextFreshnessTime = next_time;
325
326 if (failure)
327 handler(@{}, failure);
328 else
329 handler([self copyValues:[NSSet setWithArray:keys]], NULL);
330 });
331
332 });
333 }
334
335 - (void)removeObjectForKey:(NSString *)keyToRemove
336 {
337 [[self cloudStore] removeObjectForKey:keyToRemove];
338 }
339
340 - (void)clearStore
341 {
342 secdebug(XPROXYSCOPE, "%@ clearStore", self);
343 NSDictionary *dict = [[self cloudStore] dictionaryRepresentation];
344 NSArray *allKeys = [dict allKeys];
345 NSMutableArray* nullKeys = [NSMutableArray array];
346
347 _alwaysKeys = [NSMutableSet setWithArray:nullKeys];
348 _firstUnlockKeys = [NSMutableSet setWithArray:nullKeys];
349 _unlockedKeys = [NSMutableSet setWithArray:nullKeys];
350 _keyParameterKeys = @{};
351 _circleKeys = @{};
352 _messageKeys = @{};
353
354 [allKeys enumerateObjectsUsingBlock:^(id key, NSUInteger idx, BOOL *stop)
355 {
356 secdebug(XPROXYSCOPE, "%@ Clearing value for key %@", self, key);
357 [[self cloudStore] removeObjectForKey:(NSString *)key];
358 }];
359
360 [self requestSynchronization:YES];
361 }
362
363
364 //
365 // MARK: ----- KVS key lists -----
366 //
367
368 - (id)get:(id)key
369 {
370 return [[self cloudStore] objectForKey:key];
371 }
372
373 - (NSDictionary *)getAll
374 {
375 return [[self cloudStore] dictionaryRepresentation];
376 }
377
378 - (NSDictionary*) exportKeyInterests
379 {
380 return @{ kKeyAlwaysKeys:[_alwaysKeys allObjects],
381 kKeyFirstUnlockKeys:[_firstUnlockKeys allObjects],
382 kKeyUnlockedKeys:[_unlockedKeys allObjects],
383
384 #if 0
385 kKeyKeyParameterKeys: _keyParameterKeys,
386 kKeyMessageKeys : _messageKeys,
387 kKeyCircleKeys : _circleKeys,
388 #endif
389 kKeyPendingKeys:[_pendingKeys allObjects],
390 kKeySyncWithPeersPending:[NSNumber numberWithBool:_syncWithPeersPending],
391 kKeyEnsurePeerRegistration:[NSNumber numberWithBool:_ensurePeerRegistration]
392 };
393 }
394
395 - (void) importKeyInterests: (NSDictionary*) interests
396 {
397 _alwaysKeys = [NSMutableSet setWithArray: interests[kKeyAlwaysKeys]];
398 _firstUnlockKeys = [NSMutableSet setWithArray: interests[kKeyFirstUnlockKeys]];
399 _unlockedKeys = [NSMutableSet setWithArray: interests[kKeyUnlockedKeys]];
400
401 _pendingKeys = [NSMutableSet setWithArray: interests[kKeyPendingKeys]];
402 _syncWithPeersPending = [interests[kKeySyncWithPeersPending] boolValue];
403 _ensurePeerRegistration = [interests[kKeyEnsurePeerRegistration] boolValue];
404 }
405
406 - (NSMutableSet *)copyAllKeys
407 {
408 NSMutableSet *allKeys = [NSMutableSet setWithSet: _alwaysKeys];
409 [allKeys unionSet: _firstUnlockKeys];
410 [allKeys unionSet: _unlockedKeys];
411 return allKeys;
412 }
413
414 -(void)registerAtTimeKeys:(NSDictionary*)keyparms
415 {
416 NSArray *alwaysArray = [keyparms valueForKey: kKeyAlwaysKeys];
417 NSArray *firstUnlockedKeysArray = [keyparms valueForKey: kKeyFirstUnlockKeys];
418 NSArray *whenUnlockedKeysArray = [keyparms valueForKey: kKeyUnlockedKeys];
419
420 if(alwaysArray)
421 [_alwaysKeys unionSet: [NSMutableSet setWithArray: alwaysArray]];
422 if(firstUnlockedKeysArray)
423 [_firstUnlockKeys unionSet: [NSMutableSet setWithArray: firstUnlockedKeysArray]];
424 if(whenUnlockedKeysArray)
425 [_unlockedKeys unionSet: [NSMutableSet setWithArray: whenUnlockedKeysArray]];
426 }
427
428
429 - (void)registerKeys: (NSDictionary*)keys
430 {
431 secdebug(XPROXYSCOPE, "registerKeys: keys: %@", keys);
432
433 NSMutableSet *allOldKeys = [self copyAllKeys];
434
435 NSDictionary *keyparms = [keys valueForKey: [NSString stringWithUTF8String: kMessageKeyParameter]];
436 NSDictionary *circles = [keys valueForKey: [NSString stringWithUTF8String: kMessageCircle]];
437 NSDictionary *messages = [keys valueForKey: [NSString stringWithUTF8String: kMessageMessage]];
438
439 _alwaysKeys = [NSMutableSet set];
440 _firstUnlockKeys = [NSMutableSet set];
441 _unlockedKeys = [NSMutableSet set];
442
443 _keyParameterKeys = keyparms;
444 _circleKeys = circles;
445 _messageKeys = messages;
446
447 [self registerAtTimeKeys: _keyParameterKeys];
448 [self registerAtTimeKeys: _circleKeys];
449 [self registerAtTimeKeys: _messageKeys];
450
451 NSMutableSet *allNewKeys = [self copyAllKeys];
452
453 // Make sure keys we no longer care about are not pending
454 [_pendingKeys intersectSet:allNewKeys];
455 if (_shadowPendingKeys) {
456 [_shadowPendingKeys intersectSet:allNewKeys];
457 }
458
459 // All new keys only is new keys (remove old keys)
460 [allNewKeys minusSet:allOldKeys];
461
462 // Mark new keys pending, they're new!
463 NSMutableSet *newKeysForCurrentLockState = [self pendKeysAndGetNewlyPended:allNewKeys];
464
465 [self persistState]; // Before we might call out, save our state so we recover if we crash
466
467 [self intersectWithCurrentLockState: newKeysForCurrentLockState];
468 // TODO: Don't processPendingKeysForCurrentLockState if none of the new keys have values.
469 if ([newKeysForCurrentLockState count] != 0) {
470 [self processPendingKeysForCurrentLockState];
471 }
472 }
473
474 - (NSDictionary *)localNotification:(NSDictionary *)localNotificationDict outFlags:(int64_t *)outFlags
475 {
476 __block bool done = false;
477 __block int64_t returnedFlags = 0;
478 __block NSDictionary *responses = NULL;
479 typedef bool (^CKDUserInteractionBlock) (CFDictionaryRef responses);
480
481 CKDUserInteraction *cui = [CKDUserInteraction sharedInstance];
482 [cui requestShowNotification:localNotificationDict completion:^ bool (CFDictionaryRef userResponses, int64_t flags)
483 {
484 responses = [NSDictionary dictionaryWithDictionary:(__bridge NSDictionary *)userResponses];
485 returnedFlags = flags;
486 secdebug(XPROXYSCOPE, "%@ requestShowNotification: dict: %@, flags: %#llx", self, responses, returnedFlags);
487 done = true;
488 return true;
489 }];
490
491 // TODO: replace with e.g. dispatch calls to wait, or semaphore
492 while (!done)
493 sleep(1);
494 if (outFlags)
495 {
496 secdebug(XPROXYSCOPE, "%@ outFlags: %#llx", self, returnedFlags);
497 *outFlags = returnedFlags;
498 }
499 return responses;
500 }
501
502 - (void)saveToUbiquitousStore
503 {
504 [self requestSynchronization:NO];
505 }
506
507 // MARK: ----- Event Handling -----
508
509 - (void)streamEvent:(xpc_object_t)notification
510 {
511 #if (!TARGET_IPHONE_SIMULATOR)
512 const char *notificationName = xpc_dictionary_get_string(notification, "Notification");
513 if (!notificationName) {
514 } else if (strcmp(notificationName, kUserKeybagStateChangeNotification)==0) {
515 return [self keybagStateChange];
516 } else if (strcmp(notificationName, kCloudKeychainStorechangeChangeNotification)==0) {
517 return [self kvsStoreChange];
518 } else if (strcmp(notificationName, kNotifyTokenForceUpdate)==0) {
519 // DEBUG -- Possibly remove in future
520 return [self processAllItems];
521 }
522 const char *eventName = xpc_dictionary_get_string(notification, "XPCEventName");
523 char *desc = xpc_copy_description(notification);
524 secerror("%@ event: %s name: %s desc: %s", self, eventName, notificationName, desc);
525 if (desc)
526 free((void *)desc);
527 #endif
528 }
529
530 - (void)iCloudAccountAvailabilityChanged:(NSNotification*)notification
531 {
532 /*
533 Continuing this example, you’d then implement an iCloudAccountAvailabilityChanged: method that would:
534
535 Call the ubiquityIdentityToken method and store its return value.
536 Compare the new value to the previous value, to find out if the user logged out of their account or
537 logged in to a different account. If the previously-used account is now unavailable, save the current
538 state locally as needed, empty your iCloud-related data caches, and refresh all iCloud-related user interface elements.
539 */
540 id previCloudToken = currentiCloudToken;
541 currentiCloudToken = [[NSFileManager defaultManager] ubiquityIdentityToken];
542 if (previCloudToken != currentiCloudToken)
543 secnotice("event", "%@ iCloud account changed!", self);
544 else
545 secnotice("event", "%@ %@", self, notification);
546 }
547
548 - (void)cloudChanged:(NSNotification*)notification
549 {
550 /*
551 Posted when the value of one or more keys in the local key-value store
552 changed due to incoming data pushed from iCloud. This notification is
553 sent only upon a change received from iCloud; it is not sent when your
554 app sets a value.
555
556 The user info dictionary can contain the reason for the notification as
557 well as a list of which values changed, as follows:
558
559 The value of the NSUbiquitousKeyValueStoreChangeReasonKey key, when
560 present, indicates why the key-value store changed. Its value is one of
561 the constants in "Change Reason Values."
562
563 The value of the NSUbiquitousKeyValueStoreChangedKeysKey, when present,
564 is an array of strings, each the name of a key whose value changed. The
565 notification object is the NSUbiquitousKeyValueStore object whose contents
566 changed.
567
568 NSUbiquitousKeyValueStoreInitialSyncChange is only posted if there is any
569 local value that has been overwritten by a distant value. If there is no
570 conflict between the local and the distant values when doing the initial
571 sync (e.g. if the cloud has no data stored or the client has not stored
572 any data yet), you'll never see that notification.
573
574 NSUbiquitousKeyValueStoreInitialSyncChange implies an initial round trip
575 with server but initial round trip with server does not imply
576 NSUbiquitousKeyValueStoreInitialSyncChange.
577 */
578
579 secdebug(XPROXYSCOPE, "%@ cloudChanged notification: %@", self, notification);
580
581 NSDictionary *userInfo = [notification userInfo];
582 NSNumber *reason = [userInfo objectForKey:NSUbiquitousKeyValueStoreChangeReasonKey];
583 if (reason) switch ([reason integerValue]) {
584 case NSUbiquitousKeyValueStoreInitialSyncChange:
585 case NSUbiquitousKeyValueStoreServerChange:
586 {
587 _seenKVSStoreChange = YES;
588 NSSet *keysChangedInCloud = [NSSet setWithArray:[userInfo objectForKey:NSUbiquitousKeyValueStoreChangedKeysKey]];
589 NSSet *keysOfInterestThatChanged = [self pendKeysAndGetPendingForCurrentLockState:keysChangedInCloud];
590 NSMutableDictionary *changedValues = [self copyValues:keysOfInterestThatChanged];
591 if ([reason integerValue] == NSUbiquitousKeyValueStoreInitialSyncChange)
592 changedValues[(__bridge NSString*)kSOSKVSInitialSyncKey] = @"true";
593
594 secnotice("event", "%@ keysChangedInCloud: %@ keysOfInterest: %@", self, [[keysChangedInCloud allObjects] componentsJoinedByString: @" "], [[changedValues allKeys] componentsJoinedByString: @" "]);
595 if ([changedValues count])
596 [self processKeyChangedEvent:changedValues];
597 break;
598 }
599 case NSUbiquitousKeyValueStoreQuotaViolationChange:
600 seccritical("%@ event received NSUbiquitousKeyValueStoreQuotaViolationChange", self);
601 break;
602 case NSUbiquitousKeyValueStoreAccountChange:
603 // The primary account changed. We do not get this for password changes on the same account
604 secnotice("event", "%@ NSUbiquitousKeyValueStoreAccountChange", self);
605 NSDictionary *changedValues = @{ (__bridge NSString*)kSOSKVSAccountChangedKey: @"true" };
606 [self processKeyChangedEvent:changedValues];
607 break;
608 }
609 }
610
611 - (void) doAfterFlush: (dispatch_block_t) block
612 {
613 // Flush any pending communication to Securityd.
614
615 dispatch_async(_calloutQueue, block);
616 }
617
618 - (void) calloutWith: (void(^)(NSSet *pending, bool syncWithPeersPending, bool ensurePeerRegistration, dispatch_queue_t queue, void(^done)(NSSet *handledKeys, bool handledSyncWithPeers, bool handledEnsurePeerRegistration))) callout
619 {
620 // In CKDKVSProxy's serial queue
621 dispatch_queue_t ckdkvsproxy_queue = dispatch_get_main_queue();
622
623 _oldInCallout = YES;
624
625 // dispatch_get_global_queue - well-known global concurrent queue
626 // dispatch_get_main_queue - default queue that is bound to the main thread
627 xpc_transaction_begin();
628 dispatch_async(_calloutQueue, ^{
629 __block NSSet *myPending;
630 __block bool mySyncWithPeersPending;
631 __block bool myEnsurePeerRegistration;
632 __block bool wasLocked;
633 dispatch_sync(ckdkvsproxy_queue, ^{
634 myPending = [_pendingKeys copy];
635 mySyncWithPeersPending = _syncWithPeersPending;
636 myEnsurePeerRegistration = _ensurePeerRegistration;
637 wasLocked = _isLocked;
638
639 _inCallout = YES;
640 if (!_oldInCallout)
641 secnotice("deaf", ">>>>>>>>>>> WTFBBQ _oldInCallout is NO and we're heading in to the callout!");
642
643 _shadowPendingKeys = [NSMutableSet set];
644 _shadowSyncWithPeersPending = NO;
645 });
646
647 callout(myPending, mySyncWithPeersPending, myEnsurePeerRegistration, ckdkvsproxy_queue, ^(NSSet *handledKeys, bool handledSyncWithPeers, bool handledEnsurePeerRegistration) {
648 secdebug("event", "%@ %s%s before callout handled: %s%s", self, mySyncWithPeersPending ? "S" : "s", myEnsurePeerRegistration ? "E" : "e", handledSyncWithPeers ? "S" : "s", handledEnsurePeerRegistration ? "E" : "e");
649
650 // In CKDKVSProxy's serial queue
651 _inCallout = NO;
652 _oldInCallout = NO;
653
654 // Update ensurePeerRegistration
655 _ensurePeerRegistration = ((myEnsurePeerRegistration && !handledEnsurePeerRegistration) || _shadowEnsurePeerRegistration);
656
657 _shadowEnsurePeerRegistration = NO;
658
659 if(_ensurePeerRegistration && !_isLocked)
660 [self doEnsurePeerRegistration];
661
662 // Update SyncWithPeers stuff.
663 _syncWithPeersPending = ((mySyncWithPeersPending && (!handledSyncWithPeers)) || _shadowSyncWithPeersPending);
664
665 _shadowSyncWithPeersPending = NO;
666 if (handledSyncWithPeers)
667 _lastSyncTime = dispatch_time(DISPATCH_TIME_NOW, 0);
668
669 // Update pendingKeys and handle them
670 [_pendingKeys minusSet: handledKeys];
671 bool hadShadowPendingKeys = [_shadowPendingKeys count];
672 NSSet *filteredKeys = [self pendKeysAndGetPendingForCurrentLockState:_shadowPendingKeys];
673 _shadowPendingKeys = nil;
674
675 // Write state to disk
676 [self persistState];
677
678 // Handle shadow pended stuff
679 if (_syncWithPeersPending && !_isLocked)
680 [self scheduleSyncRequestTimer];
681 /* We don't want to call processKeyChangedEvent if we failed to
682 handle pending keys and the device didn't unlock nor receive
683 any kvs changes while we were in our callout.
684 Doing so will lead to securityd and CloudKeychainProxy
685 talking to each other forever in a tight loop if securityd
686 repeatedly returns an error processing the same message.
687 Instead we leave any old pending keys until the next event. */
688 if (hadShadowPendingKeys || (!_isLocked && wasLocked))
689 [self processKeyChangedEvent:[self copyValues:filteredKeys]];
690 xpc_transaction_end();
691 });
692 });
693 }
694
695 - (void) sendKeysCallout: (NSSet *(^)(NSSet* pending, NSError** error)) handleKeys {
696 [self calloutWith: ^(NSSet *pending, bool syncWithPeersPending, bool ensurePeerRegistration, dispatch_queue_t queue, void(^done)(NSSet *, bool, bool)) {
697 NSError* error = NULL;
698
699 NSSet * handled = handleKeys(pending, &error);
700
701 dispatch_async(queue, ^{
702 if (!handled) {
703 secerror("%@ ensurePeerRegistration failed: %@", self, error);
704 }
705
706 done(handled, NO, NO);
707 });
708 }];
709 }
710
711 - (void) doEnsurePeerRegistration
712 {
713 [self calloutWith:^(NSSet *pending, bool syncWithPeersPending, bool ensurePeerRegistration, dispatch_queue_t queue, void(^done)(NSSet *, bool, bool)) {
714 CFErrorRef error = NULL;
715 bool handledEnsurePeerRegistration = SOSCCProcessEnsurePeerRegistration(&error);
716 secerror("%@ ensurePeerRegistration called, %@ (%@)", self, handledEnsurePeerRegistration ? @"success" : @"failure", error);
717 dispatch_async(queue, ^{
718 if (!handledEnsurePeerRegistration) {
719 secerror("%@ ensurePeerRegistration failed: %@", self, error);
720 }
721
722 done(nil, NO, handledEnsurePeerRegistration);
723 CFReleaseSafe(error);
724 });
725 }];
726 }
727
728 - (void) doSyncWithAllPeers
729 {
730 [self calloutWith:^(NSSet *pending, bool syncWithPeersPending, bool ensurePeerRegistration, dispatch_queue_t queue, void(^done)(NSSet *, bool, bool)) {
731 CFErrorRef error = NULL;
732 SyncWithAllPeersReason reason = SOSCCProcessSyncWithAllPeers(&error);
733 dispatch_async(queue, ^{
734 bool handledSyncWithPeers = NO;
735 if (reason == kSyncWithAllPeersSuccess) {
736 handledSyncWithPeers = YES;
737 secnotice("event", "%@ syncWithAllPeers succeeded", self);
738 } else if (reason == kSyncWithAllPeersLocked) {
739 secnotice("event", "%@ syncWithAllPeers attempted while locked - waiting for unlock", self);
740 handledSyncWithPeers = NO;
741 [self updateIsLocked];
742 } else if (reason == kSyncWithAllPeersOtherFail) {
743 // Pretend we handled syncWithPeers, by pushing out the _lastSyncTime
744 // This will cause us to wait for kMinSyncInterval seconds before
745 // retrying, so we don't spam securityd if sync is failing
746 secerror("%@ syncWithAllPeers %@, rescheduling timer", self, error);
747 _lastSyncTime = dispatch_time(DISPATCH_TIME_NOW, 0);
748 } else {
749 secerror("%@ syncWithAllPeers %@, unknown reason: %d", self, error, reason);
750 }
751
752 done(nil, handledSyncWithPeers, false);
753 CFReleaseSafe(error);
754 });
755 }];
756 }
757
758 - (void)timerFired
759 {
760 secnotice("event", "%@ syncWithPeersPending: %d inCallout: %d isLocked: %d", self, _syncWithPeersPending, _inCallout, _isLocked);
761 _syncTimerScheduled = NO;
762 if (_syncWithPeersPending && !_inCallout && !_isLocked)
763 [self doSyncWithAllPeers];
764 }
765
766 - (dispatch_time_t) nextSyncTime
767 {
768 dispatch_time_t nextSync = dispatch_time(DISPATCH_TIME_NOW, kMinSyncDelay);
769
770 // Don't sync again unless we waited at least kMinSyncInterval
771 if (_lastSyncTime) {
772 dispatch_time_t soonest = dispatch_time(_lastSyncTime, kMinSyncInterval);
773 if (nextSync < soonest || _deadline < soonest) {
774 secdebug("timer", "%@ backing off", self);
775 return soonest;
776 }
777 }
778
779 // Don't delay more than kMaxSyncDelay after the first request.
780 if (nextSync > _deadline) {
781 secdebug("timer", "%@ hit deadline", self);
782 return _deadline;
783 }
784
785 // Bump the timer by kMinSyncDelay
786 if (_syncTimerScheduled)
787 secdebug("timer", "%@ bumped timer", self);
788 else
789 secdebug("timer", "%@ scheduled timer", self);
790
791 return nextSync;
792 }
793
794 - (void)scheduleSyncRequestTimer
795 {
796 dispatch_source_set_timer(_syncTimer, [self nextSyncTime], DISPATCH_TIME_FOREVER, kSyncTimerLeeway);
797 _syncTimerScheduled = YES;
798 }
799
800 - (void)requestSyncWithAllPeers // secd calling SOSCCSyncWithAllPeers invokes this
801 {
802 #if !defined(NDEBUG)
803 NSString *desc = [self description];
804 #endif
805
806 if (!_syncWithPeersPending || (_inCallout && !_shadowSyncWithPeersPending))
807 _deadline = dispatch_time(DISPATCH_TIME_NOW, kMaxSyncDelay);
808
809 if (!_syncWithPeersPending) {
810 _syncWithPeersPending = YES;
811 [self persistState];
812 }
813
814 if (_inCallout)
815 _shadowSyncWithPeersPending = YES;
816 else if (!_isLocked)
817 [self scheduleSyncRequestTimer];
818
819 secdebug("event", "%@ %@", desc, self);
820 }
821
822 - (void)requestEnsurePeerRegistration // secd calling SOSCCSyncWithAllPeers invokes this
823 {
824 #if !defined(NDEBUG)
825 NSString *desc = [self description];
826 #endif
827
828 if (_inCallout) {
829 _shadowEnsurePeerRegistration = YES;
830 } else {
831 _ensurePeerRegistration = YES;
832 if (!_isLocked)
833 [self doEnsurePeerRegistration];
834 [self persistState];
835 }
836
837 secdebug("event", "%@ %@", desc, self);
838 }
839
840
841 - (BOOL) updateUnlockedSinceBoot
842 {
843 CFErrorRef aksError = NULL;
844 if (!SecAKSGetHasBeenUnlocked(&_unlockedSinceBoot, &aksError)) {
845 secerror("%@ Got error from SecAKSGetHasBeenUnlocked: %@", self, aksError);
846 CFReleaseSafe(aksError);
847 return NO;
848 }
849 return YES;
850 }
851
852 - (BOOL) updateIsLocked
853 {
854 CFErrorRef aksError = NULL;
855 if (!SecAKSGetIsLocked(&_isLocked, &aksError)) {
856 secerror("%@ Got error querying lock state: %@", self, aksError);
857 CFReleaseSafe(aksError);
858 return NO;
859 }
860 if (!_isLocked)
861 _unlockedSinceBoot = YES;
862 return YES;
863 }
864
865 - (void) keybagStateChange
866 {
867 BOOL wasLocked = _isLocked;
868 if ([self updateIsLocked]) {
869 if (wasLocked == _isLocked)
870 secdebug("event", "%@ still %s ignoring", self, _isLocked ? "locked" : "unlocked");
871 else if (_isLocked)
872 [self keybagDidLock];
873 else
874 [self keybagDidUnlock];
875 }
876 }
877
878 - (void) keybagDidLock
879 {
880 secnotice("event", "%@", self);
881 }
882
883 - (void) keybagDidUnlock
884 {
885 secnotice("event", "%@", self);
886 if (_ensurePeerRegistration) {
887 [self doEnsurePeerRegistration];
888 }
889
890 // First send changed keys to securityd so it can proccess updates
891 [self processPendingKeysForCurrentLockState];
892
893 // Then, tickle securityd to perform a sync if needed.
894 if (_syncWithPeersPending && !_syncTimerScheduled) {
895 [self doSyncWithAllPeers];
896 }
897 }
898
899 - (void) kvsStoreChange {
900 if (!_seenKVSStoreChange) {
901 _seenKVSStoreChange = YES; // Only do this once
902 secnotice("event", "%@ received darwin notification before first NSNotification", self);
903 // TODO This might not be needed if we always get the NSNotification
904 // deleived even if we were launched due to a kvsStoreChange
905 // Send all keys for current lock state to securityd so it can proccess them
906 [self pendKeysAndGetNewlyPended: [self copyAllKeys]];
907 [self processPendingKeysForCurrentLockState];
908 } else {
909 secdebug("event", "%@ ignored, waiting for NSNotification", self);
910 }
911 }
912
913 //
914 // MARK: ----- Key Filtering -----
915 //
916
917 - (NSSet*) keysForCurrentLockState
918 {
919 secdebug("keytrace", "%@ Filtering: unlockedSinceBoot: %d\n unlocked: %d\n, _keysOfInterest: %@", self, (int) _unlockedSinceBoot, (int) !_isLocked, [self exportKeyInterests]);
920
921 NSMutableSet *currentStateKeys = [NSMutableSet setWithSet: _alwaysKeys];
922 if (_unlockedSinceBoot)
923 [currentStateKeys unionSet: _firstUnlockKeys];
924
925 if (!_isLocked)
926 [currentStateKeys unionSet: _unlockedKeys];
927
928 return currentStateKeys;
929 }
930
931 - (NSMutableSet*) pendKeysAndGetNewlyPended: (NSSet*) keysToPend
932 {
933 NSMutableSet *newlyPendedKeys = [keysToPend mutableCopy];
934 [newlyPendedKeys minusSet: _pendingKeys];
935 if (_shadowPendingKeys) {
936 [newlyPendedKeys minusSet: _shadowPendingKeys];
937 }
938
939 [_pendingKeys unionSet:keysToPend];
940 if (_shadowPendingKeys) {
941 [_shadowPendingKeys unionSet:keysToPend];
942 }
943
944 return newlyPendedKeys;
945 }
946
947 - (void) intersectWithCurrentLockState: (NSMutableSet*) set
948 {
949 [set intersectSet: [self keysForCurrentLockState]];
950 }
951
952 - (NSMutableSet*) pendingKeysForCurrentLockState
953 {
954 NSMutableSet * result = [_pendingKeys mutableCopy];
955 [self intersectWithCurrentLockState:result];
956 return result;
957 }
958
959 - (NSMutableSet*) pendKeysAndGetPendingForCurrentLockState: (NSSet*) startingSet
960 {
961 [self pendKeysAndGetNewlyPended: startingSet];
962
963 return [self pendingKeysForCurrentLockState];
964 }
965
966 - (NSMutableDictionary *)copyValues:(NSSet*)keysOfInterest
967 {
968 // Grab values from KVS.
969 NSUbiquitousKeyValueStore *store = [self cloudStore];
970 NSMutableDictionary *changedValues = [NSMutableDictionary dictionaryWithCapacity:0];
971 [keysOfInterest enumerateObjectsUsingBlock:^(id obj, BOOL *stop)
972 {
973 NSString* key = (NSString*) obj;
974 id objval = [store objectForKey:key];
975 if (!objval) objval = [NSNull null];
976
977 [changedValues setObject:objval forKey:key];
978 secdebug(XPROXYSCOPE, "%@ storeChanged updated value for %@", self, key);
979 }];
980 return changedValues;
981 }
982
983 /*
984 During RegisterKeys, separate keys-of-interest into three disjoint sets:
985 - keys that we always want to be notified about; this means we can get the
986 value at any time
987 - keys that require the device to have been unlocked at least once
988 - keys that require the device to be unlocked now
989
990 Typically, the sets of keys will be:
991
992 - Dk: alwaysKeys
993 - Ck: firstUnlock
994 - Ak: unlocked
995
996 The caller is responsible for making sure that the keys in e.g. alwaysKeys are
997 values that can be handled at any time (that is, not when unlocked)
998
999 Each time we get a notification from ubiquity that keys have changed, we need to
1000 see if anything of interest changed. If we don't care, then done.
1001
1002 For each key-of-interest that changed, we either notify the client that things
1003 changed, or add it to a pendingNotifications list. If the notification to the
1004 client fails, also add it to the pendingNotifications list. This pending list
1005 should be written to persistent storage and consulted any time we either get an
1006 item changed notification, or get a stream event signalling a change in lock state.
1007
1008 We can notify the client either through XPC if a connection is set up, or call a
1009 routine in securityd to launch it.
1010
1011 */
1012
1013 - (void)processKeyChangedEvent:(NSDictionary *)changedValues
1014 {
1015 NSMutableDictionary* filtered = [NSMutableDictionary dictionary];
1016
1017 NSMutableArray* nullKeys = [NSMutableArray array];
1018 // Remove nulls because we don't want them in securityd.
1019 [changedValues enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
1020 if (obj == [NSNull null])
1021 [nullKeys addObject:key];
1022 else{
1023 filtered[key] = obj;
1024 }
1025 }];
1026 if ([nullKeys count])
1027 [_pendingKeys minusSet: [NSSet setWithArray: nullKeys]];
1028
1029 if([filtered count] != 0 ){
1030 [self sendKeysCallout:^NSSet *(NSSet *pending, NSError** error) {
1031 CFErrorRef cf_error = NULL;
1032 NSArray* handledMessage = (__bridge_transfer NSArray*) _SecKeychainSyncUpdateMessage((__bridge CFDictionaryRef)filtered, &cf_error);
1033 NSError *updateError = (__bridge_transfer NSError*)cf_error;
1034 if (error)
1035 *error = updateError;
1036
1037 secnotice("keytrace", "%@ misc handled: %@ null: %@ pending: %@", self,
1038 [handledMessage componentsJoinedByString: @" "],
1039 [nullKeys componentsJoinedByString: @" "],
1040 [[_pendingKeys allObjects] componentsJoinedByString: @" "]);
1041
1042 return handledMessage ? [NSSet setWithArray: handledMessage] : nil;
1043 }];
1044 } else {
1045 secnotice("keytrace", "null: %@ pending: %@",
1046 [nullKeys componentsJoinedByString: @" "],
1047 [[_pendingKeys allObjects] componentsJoinedByString: @" "]);
1048 }
1049 }
1050
1051 - (void) processPendingKeysForCurrentLockState
1052 {
1053 [self processKeyChangedEvent: [self copyValues: [self pendingKeysForCurrentLockState]]];
1054 }
1055
1056 @end
1057
1058