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