]> git.saurik.com Git - apple/security.git/blob - Security/sec/SOSCircle/CloudKeychainProxy/CKDKVSProxy.m
Security-57031.40.6.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
590 /* We are saying that we want to try processing a key no matter what,
591 * *if* it has changed in the cloud. */
592 [_pendingKeys minusSet:keysChangedInCloud];
593
594 NSSet *keysOfInterestThatChanged = [self pendKeysAndGetPendingForCurrentLockState:keysChangedInCloud];
595 NSMutableDictionary *changedValues = [self copyValues:keysOfInterestThatChanged];
596 if ([reason integerValue] == NSUbiquitousKeyValueStoreInitialSyncChange)
597 changedValues[(__bridge NSString*)kSOSKVSInitialSyncKey] = @"true";
598
599 secnotice("event", "%@ keysChangedInCloud: %@ keysOfInterest: %@", self, [[keysChangedInCloud allObjects] componentsJoinedByString: @" "], [[changedValues allKeys] componentsJoinedByString: @" "]);
600 if ([changedValues count])
601 [self processKeyChangedEvent:changedValues];
602 break;
603 }
604 case NSUbiquitousKeyValueStoreQuotaViolationChange:
605 seccritical("%@ event received NSUbiquitousKeyValueStoreQuotaViolationChange", self);
606 break;
607 case NSUbiquitousKeyValueStoreAccountChange:
608 // The primary account changed. We do not get this for password changes on the same account
609 secnotice("event", "%@ NSUbiquitousKeyValueStoreAccountChange", self);
610 NSDictionary *changedValues = @{ (__bridge NSString*)kSOSKVSAccountChangedKey: @"true" };
611 [self processKeyChangedEvent:changedValues];
612 break;
613 }
614 }
615
616 - (void) doAfterFlush: (dispatch_block_t) block
617 {
618 // Flush any pending communication to Securityd.
619
620 dispatch_async(_calloutQueue, block);
621 }
622
623 - (void) calloutWith: (void(^)(NSSet *pending, bool syncWithPeersPending, bool ensurePeerRegistration, dispatch_queue_t queue, void(^done)(NSSet *handledKeys, bool handledSyncWithPeers, bool handledEnsurePeerRegistration))) callout
624 {
625 // In CKDKVSProxy's serial queue
626 dispatch_queue_t ckdkvsproxy_queue = dispatch_get_main_queue();
627
628 _oldInCallout = YES;
629
630 // dispatch_get_global_queue - well-known global concurrent queue
631 // dispatch_get_main_queue - default queue that is bound to the main thread
632 xpc_transaction_begin();
633 dispatch_async(_calloutQueue, ^{
634 __block NSSet *myPending;
635 __block bool mySyncWithPeersPending;
636 __block bool myEnsurePeerRegistration;
637 __block bool wasLocked;
638 dispatch_sync(ckdkvsproxy_queue, ^{
639 myPending = [_pendingKeys copy];
640 mySyncWithPeersPending = _syncWithPeersPending;
641 myEnsurePeerRegistration = _ensurePeerRegistration;
642 wasLocked = _isLocked;
643
644 _inCallout = YES;
645 if (!_oldInCallout)
646 secnotice("deaf", ">>>>>>>>>>> _oldInCallout is NO and we're heading in to the callout!");
647
648 _shadowPendingKeys = [NSMutableSet set];
649 _shadowSyncWithPeersPending = NO;
650 });
651
652 callout(myPending, mySyncWithPeersPending, myEnsurePeerRegistration, ckdkvsproxy_queue, ^(NSSet *handledKeys, bool handledSyncWithPeers, bool handledEnsurePeerRegistration) {
653 secdebug("event", "%@ %s%s before callout handled: %s%s", self, mySyncWithPeersPending ? "S" : "s", myEnsurePeerRegistration ? "E" : "e", handledSyncWithPeers ? "S" : "s", handledEnsurePeerRegistration ? "E" : "e");
654
655 // In CKDKVSProxy's serial queue
656 _inCallout = NO;
657 _oldInCallout = NO;
658
659 // Update ensurePeerRegistration
660 _ensurePeerRegistration = ((myEnsurePeerRegistration && !handledEnsurePeerRegistration) || _shadowEnsurePeerRegistration);
661
662 _shadowEnsurePeerRegistration = NO;
663
664 if(_ensurePeerRegistration && !_isLocked)
665 [self doEnsurePeerRegistration];
666
667 // Update SyncWithPeers stuff.
668 _syncWithPeersPending = ((mySyncWithPeersPending && (!handledSyncWithPeers)) || _shadowSyncWithPeersPending);
669
670 _shadowSyncWithPeersPending = NO;
671 if (handledSyncWithPeers)
672 _lastSyncTime = dispatch_time(DISPATCH_TIME_NOW, 0);
673
674 // Update pendingKeys and handle them
675 [_pendingKeys minusSet: handledKeys];
676 bool hadShadowPendingKeys = [_shadowPendingKeys count];
677 // Move away shadownPendingKeys first, because pendKeysAndGetPendingForCurrentLockState
678 // will look at them. See rdar://problem/20733166.
679 NSSet *oldShadowPendingKeys = _shadowPendingKeys;
680 _shadowPendingKeys = nil;
681 NSSet *filteredKeys = [self pendKeysAndGetPendingForCurrentLockState:oldShadowPendingKeys];
682
683 // Write state to disk
684 [self persistState];
685
686 // Handle shadow pended stuff
687 if (_syncWithPeersPending && !_isLocked)
688 [self scheduleSyncRequestTimer];
689 /* We don't want to call processKeyChangedEvent if we failed to
690 handle pending keys and the device didn't unlock nor receive
691 any kvs changes while we were in our callout.
692 Doing so will lead to securityd and CloudKeychainProxy
693 talking to each other forever in a tight loop if securityd
694 repeatedly returns an error processing the same message.
695 Instead we leave any old pending keys until the next event. */
696 if (hadShadowPendingKeys || (!_isLocked && wasLocked))
697 [self processKeyChangedEvent:[self copyValues:filteredKeys]];
698 xpc_transaction_end();
699 });
700 });
701 }
702
703 - (void) sendKeysCallout: (NSSet *(^)(NSSet* pending, NSError** error)) handleKeys {
704 [self calloutWith: ^(NSSet *pending, bool syncWithPeersPending, bool ensurePeerRegistration, dispatch_queue_t queue, void(^done)(NSSet *, bool, bool)) {
705 NSError* error = NULL;
706
707 NSSet * handled = handleKeys(pending, &error);
708
709 dispatch_async(queue, ^{
710 if (!handled) {
711 secerror("%@ ensurePeerRegistration failed: %@", self, error);
712 }
713
714 done(handled, NO, NO);
715 });
716 }];
717 }
718
719 - (void) doEnsurePeerRegistration
720 {
721 [self calloutWith:^(NSSet *pending, bool syncWithPeersPending, bool ensurePeerRegistration, dispatch_queue_t queue, void(^done)(NSSet *, bool, bool)) {
722 CFErrorRef error = NULL;
723 bool handledEnsurePeerRegistration = SOSCCProcessEnsurePeerRegistration(&error);
724 secerror("%@ ensurePeerRegistration called, %@ (%@)", self, handledEnsurePeerRegistration ? @"success" : @"failure", error);
725 dispatch_async(queue, ^{
726 if (!handledEnsurePeerRegistration) {
727 secerror("%@ ensurePeerRegistration failed: %@", self, error);
728 }
729
730 done(nil, NO, handledEnsurePeerRegistration);
731 CFReleaseSafe(error);
732 });
733 }];
734 }
735
736 - (void) doSyncWithAllPeers
737 {
738 [self calloutWith:^(NSSet *pending, bool syncWithPeersPending, bool ensurePeerRegistration, dispatch_queue_t queue, void(^done)(NSSet *, bool, bool)) {
739 CFErrorRef error = NULL;
740 SyncWithAllPeersReason reason = SOSCCProcessSyncWithAllPeers(&error);
741 dispatch_async(queue, ^{
742 bool handledSyncWithPeers = NO;
743 if (reason == kSyncWithAllPeersSuccess) {
744 handledSyncWithPeers = YES;
745 secnotice("event", "%@ syncWithAllPeers succeeded", self);
746 } else if (reason == kSyncWithAllPeersLocked) {
747 secnotice("event", "%@ syncWithAllPeers attempted while locked - waiting for unlock", self);
748 handledSyncWithPeers = NO;
749 [self updateIsLocked];
750 } else if (reason == kSyncWithAllPeersOtherFail) {
751 // Pretend we handled syncWithPeers, by pushing out the _lastSyncTime
752 // This will cause us to wait for kMinSyncInterval seconds before
753 // retrying, so we don't spam securityd if sync is failing
754 secerror("%@ syncWithAllPeers %@, rescheduling timer", self, error);
755 _lastSyncTime = dispatch_time(DISPATCH_TIME_NOW, 0);
756 } else {
757 secerror("%@ syncWithAllPeers %@, unknown reason: %d", self, error, reason);
758 }
759
760 done(nil, handledSyncWithPeers, false);
761 CFReleaseSafe(error);
762 });
763 }];
764 }
765
766 - (void)timerFired
767 {
768 secnotice("event", "%@ syncWithPeersPending: %d inCallout: %d isLocked: %d", self, _syncWithPeersPending, _inCallout, _isLocked);
769 _syncTimerScheduled = NO;
770 if (_syncWithPeersPending && !_inCallout && !_isLocked)
771 [self doSyncWithAllPeers];
772 }
773
774 - (dispatch_time_t) nextSyncTime
775 {
776 dispatch_time_t nextSync = dispatch_time(DISPATCH_TIME_NOW, kMinSyncDelay);
777
778 // Don't sync again unless we waited at least kMinSyncInterval
779 if (_lastSyncTime) {
780 dispatch_time_t soonest = dispatch_time(_lastSyncTime, kMinSyncInterval);
781 if (nextSync < soonest || _deadline < soonest) {
782 secdebug("timer", "%@ backing off", self);
783 return soonest;
784 }
785 }
786
787 // Don't delay more than kMaxSyncDelay after the first request.
788 if (nextSync > _deadline) {
789 secdebug("timer", "%@ hit deadline", self);
790 return _deadline;
791 }
792
793 // Bump the timer by kMinSyncDelay
794 if (_syncTimerScheduled)
795 secdebug("timer", "%@ bumped timer", self);
796 else
797 secdebug("timer", "%@ scheduled timer", self);
798
799 return nextSync;
800 }
801
802 - (void)scheduleSyncRequestTimer
803 {
804 dispatch_source_set_timer(_syncTimer, [self nextSyncTime], DISPATCH_TIME_FOREVER, kSyncTimerLeeway);
805 _syncTimerScheduled = YES;
806 }
807
808 - (void)requestSyncWithAllPeers // secd calling SOSCCSyncWithAllPeers invokes this
809 {
810 #if !defined(NDEBUG)
811 NSString *desc = [self description];
812 #endif
813
814 if (!_syncWithPeersPending || (_inCallout && !_shadowSyncWithPeersPending))
815 _deadline = dispatch_time(DISPATCH_TIME_NOW, kMaxSyncDelay);
816
817 if (!_syncWithPeersPending) {
818 _syncWithPeersPending = YES;
819 [self persistState];
820 }
821
822 if (_inCallout)
823 _shadowSyncWithPeersPending = YES;
824 else if (!_isLocked)
825 [self scheduleSyncRequestTimer];
826
827 secdebug("event", "%@ %@", desc, self);
828 }
829
830 - (void)requestEnsurePeerRegistration // secd calling SOSCCSyncWithAllPeers invokes this
831 {
832 #if !defined(NDEBUG)
833 NSString *desc = [self description];
834 #endif
835
836 if (_inCallout) {
837 _shadowEnsurePeerRegistration = YES;
838 } else {
839 _ensurePeerRegistration = YES;
840 if (!_isLocked)
841 [self doEnsurePeerRegistration];
842 [self persistState];
843 }
844
845 secdebug("event", "%@ %@", desc, self);
846 }
847
848
849 - (BOOL) updateUnlockedSinceBoot
850 {
851 CFErrorRef aksError = NULL;
852 if (!SecAKSGetHasBeenUnlocked(&_unlockedSinceBoot, &aksError)) {
853 secerror("%@ Got error from SecAKSGetHasBeenUnlocked: %@", self, aksError);
854 CFReleaseSafe(aksError);
855 return NO;
856 }
857 return YES;
858 }
859
860 - (BOOL) updateIsLocked
861 {
862 CFErrorRef aksError = NULL;
863 if (!SecAKSGetIsLocked(&_isLocked, &aksError)) {
864 secerror("%@ Got error querying lock state: %@", self, aksError);
865 CFReleaseSafe(aksError);
866 return NO;
867 }
868 if (!_isLocked)
869 _unlockedSinceBoot = YES;
870 return YES;
871 }
872
873 - (void) keybagStateChange
874 {
875 BOOL wasLocked = _isLocked;
876 if ([self updateIsLocked]) {
877 if (wasLocked == _isLocked)
878 secdebug("event", "%@ still %s ignoring", self, _isLocked ? "locked" : "unlocked");
879 else if (_isLocked)
880 [self keybagDidLock];
881 else
882 [self keybagDidUnlock];
883 }
884 }
885
886 - (void) keybagDidLock
887 {
888 secnotice("event", "%@", self);
889 }
890
891 - (void) keybagDidUnlock
892 {
893 secnotice("event", "%@", self);
894 if (_ensurePeerRegistration) {
895 [self doEnsurePeerRegistration];
896 }
897
898 // First send changed keys to securityd so it can proccess updates
899 [self processPendingKeysForCurrentLockState];
900
901 // Then, tickle securityd to perform a sync if needed.
902 if (_syncWithPeersPending && !_syncTimerScheduled) {
903 [self doSyncWithAllPeers];
904 }
905 }
906
907 - (void) kvsStoreChange {
908 if (!_seenKVSStoreChange) {
909 _seenKVSStoreChange = YES; // Only do this once
910 secnotice("event", "%@ received darwin notification before first NSNotification", self);
911 // TODO This might not be needed if we always get the NSNotification
912 // deleived even if we were launched due to a kvsStoreChange
913 // Send all keys for current lock state to securityd so it can proccess them
914 [self pendKeysAndGetNewlyPended: [self copyAllKeys]];
915 [self processPendingKeysForCurrentLockState];
916 } else {
917 secdebug("event", "%@ ignored, waiting for NSNotification", self);
918 }
919 }
920
921 //
922 // MARK: ----- Key Filtering -----
923 //
924
925 - (NSSet*) keysForCurrentLockState
926 {
927 secdebug("keytrace", "%@ Filtering: unlockedSinceBoot: %d\n unlocked: %d\n, _keysOfInterest: %@", self, (int) _unlockedSinceBoot, (int) !_isLocked, [self exportKeyInterests]);
928
929 NSMutableSet *currentStateKeys = [NSMutableSet setWithSet: _alwaysKeys];
930 if (_unlockedSinceBoot)
931 [currentStateKeys unionSet: _firstUnlockKeys];
932
933 if (!_isLocked)
934 [currentStateKeys unionSet: _unlockedKeys];
935
936 return currentStateKeys;
937 }
938
939 - (NSMutableSet*) pendKeysAndGetNewlyPended: (NSSet*) keysToPend
940 {
941 NSMutableSet *newlyPendedKeys = [keysToPend mutableCopy];
942 [newlyPendedKeys minusSet: _pendingKeys];
943 if (_shadowPendingKeys) {
944 [newlyPendedKeys minusSet: _shadowPendingKeys];
945 }
946
947 [_pendingKeys unionSet:keysToPend];
948 if (_shadowPendingKeys) {
949 [_shadowPendingKeys unionSet:keysToPend];
950 }
951
952 return newlyPendedKeys;
953 }
954
955 - (void) intersectWithCurrentLockState: (NSMutableSet*) set
956 {
957 [set intersectSet: [self keysForCurrentLockState]];
958 }
959
960 - (NSMutableSet*) pendingKeysForCurrentLockState
961 {
962 NSMutableSet * result = [_pendingKeys mutableCopy];
963 [self intersectWithCurrentLockState:result];
964 return result;
965 }
966
967 - (NSMutableSet*) pendKeysAndGetPendingForCurrentLockState: (NSSet*) startingSet
968 {
969 [self pendKeysAndGetNewlyPended: startingSet];
970
971 return [self pendingKeysForCurrentLockState];
972 }
973
974 - (NSMutableDictionary *)copyValues:(NSSet*)keysOfInterest
975 {
976 // Grab values from KVS.
977 NSUbiquitousKeyValueStore *store = [self cloudStore];
978 NSMutableDictionary *changedValues = [NSMutableDictionary dictionaryWithCapacity:0];
979 [keysOfInterest enumerateObjectsUsingBlock:^(id obj, BOOL *stop)
980 {
981 NSString* key = (NSString*) obj;
982 id objval = [store objectForKey:key];
983 if (!objval) objval = [NSNull null];
984
985 [changedValues setObject:objval forKey:key];
986 secdebug(XPROXYSCOPE, "%@ storeChanged updated value for %@", self, key);
987 }];
988 return changedValues;
989 }
990
991 /*
992 During RegisterKeys, separate keys-of-interest into three disjoint sets:
993 - keys that we always want to be notified about; this means we can get the
994 value at any time
995 - keys that require the device to have been unlocked at least once
996 - keys that require the device to be unlocked now
997
998 Typically, the sets of keys will be:
999
1000 - Dk: alwaysKeys
1001 - Ck: firstUnlock
1002 - Ak: unlocked
1003
1004 The caller is responsible for making sure that the keys in e.g. alwaysKeys are
1005 values that can be handled at any time (that is, not when unlocked)
1006
1007 Each time we get a notification from ubiquity that keys have changed, we need to
1008 see if anything of interest changed. If we don't care, then done.
1009
1010 For each key-of-interest that changed, we either notify the client that things
1011 changed, or add it to a pendingNotifications list. If the notification to the
1012 client fails, also add it to the pendingNotifications list. This pending list
1013 should be written to persistent storage and consulted any time we either get an
1014 item changed notification, or get a stream event signalling a change in lock state.
1015
1016 We can notify the client either through XPC if a connection is set up, or call a
1017 routine in securityd to launch it.
1018
1019 */
1020
1021 - (void)processKeyChangedEvent:(NSDictionary *)changedValues
1022 {
1023 NSMutableDictionary* filtered = [NSMutableDictionary dictionary];
1024
1025 NSMutableArray* nullKeys = [NSMutableArray array];
1026 // Remove nulls because we don't want them in securityd.
1027 [changedValues enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
1028 if (obj == [NSNull null])
1029 [nullKeys addObject:key];
1030 else{
1031 filtered[key] = obj;
1032 }
1033 }];
1034 if ([nullKeys count])
1035 [_pendingKeys minusSet: [NSSet setWithArray: nullKeys]];
1036
1037 if([filtered count] != 0 ){
1038 [self sendKeysCallout:^NSSet *(NSSet *pending, NSError** error) {
1039 CFErrorRef cf_error = NULL;
1040 NSArray* handledMessage = (__bridge_transfer NSArray*) _SecKeychainSyncUpdateMessage((__bridge CFDictionaryRef)filtered, &cf_error);
1041 NSError *updateError = (__bridge_transfer NSError*)cf_error;
1042 if (error)
1043 *error = updateError;
1044
1045 secnotice("keytrace", "%@ misc handled: %@ null: %@ pending: %@", self,
1046 [handledMessage componentsJoinedByString: @" "],
1047 [nullKeys componentsJoinedByString: @" "],
1048 [[_pendingKeys allObjects] componentsJoinedByString: @" "]);
1049
1050 return handledMessage ? [NSSet setWithArray: handledMessage] : nil;
1051 }];
1052 } else {
1053 secnotice("keytrace", "null: %@ pending: %@",
1054 [nullKeys componentsJoinedByString: @" "],
1055 [[_pendingKeys allObjects] componentsJoinedByString: @" "]);
1056 }
1057 }
1058
1059 - (void) processPendingKeysForCurrentLockState
1060 {
1061 [self processKeyChangedEvent: [self copyValues: [self pendingKeysForCurrentLockState]]];
1062 }
1063
1064 @end
1065
1066