]> git.saurik.com Git - apple/security.git/blob - OSX/sec/SOSCircle/CloudKeychainProxy/CKDKVSProxy.m
Security-57336.10.29.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 static bool isResubmitError(NSError* error) {
328 return error && (CFErrorGetCode((__bridge CFErrorRef) error) == UPDATE_RESUBMIT); // Why don't we check the domain?!
329 }
330
331 - (BOOL) AttemptSynchronization:(NSError **)failure
332 {
333
334 __block NSError *tempFailure = NULL;
335 int triesRemaining = 10;
336
337 NSUbiquitousKeyValueStore * store = [self cloudStore];
338
339 dispatch_semaphore_t freshSemaphore = dispatch_semaphore_create(0);
340
341 do {
342 --triesRemaining;
343 secnoticeq("fresh", "%s CALLING OUT TO syncdefaultsd SWCH: %@", kWAIT2MINID, self);
344
345 [store synchronizeWithCompletionHandler:^(NSError *error) {
346 if (error) {
347 tempFailure = error;
348 secerrorq("%s RETURNING FROM syncdefaultsd SWCH: %@: %@", kWAIT2MINID, self, error);
349 } else {
350 secnoticeq("fresh", "%s RETURNING FROM syncdefaultsd SWCH: %@", kWAIT2MINID, self);
351 [store synchronize]; // Per olivier in <rdar://problem/13412631>, sync before getting values
352 secnoticeq("fresh", "%s RETURNING FROM syncdefaultsd SYNC: %@", kWAIT2MINID, self);
353 }
354 dispatch_semaphore_signal(freshSemaphore);
355 }];
356 dispatch_semaphore_wait(freshSemaphore, DISPATCH_TIME_FOREVER);
357 } while (triesRemaining > 0 && isResubmitError(tempFailure));
358
359 if (isResubmitError(tempFailure)) {
360 secerrorq("%s Number of retry attempts to request freshness exceeded", kWAIT2MINID);
361 }
362
363 if (failure && (*failure == NULL)) {
364 *failure = tempFailure;
365 }
366
367 return tempFailure == nil;
368 }
369
370 static void wait_until(dispatch_time_t when) {
371 static dispatch_once_t once;
372 static dispatch_semaphore_t never_fires = nil;
373 dispatch_once(&once, ^{
374 never_fires = dispatch_semaphore_create(0);
375 });
376
377 dispatch_semaphore_wait(never_fires, when); // Will always timeout.
378 }
379
380 - (void)waitForSynchronization:(NSArray *)keys handler:(void (^)(NSDictionary *values, NSError *err))handler
381 {
382 if (!keys)
383 {
384 NSError *err = [NSError errorWithDomain:(NSString *)NSPOSIXErrorDomain code:(NSInteger)ENOPROTOOPT userInfo:nil];
385 secerrorq("%s RETURNING TO securityd: %@ param error; calling handler", kWAIT2MINID, self);
386 handler(@{}, err);
387 return;
388 }
389
390 secnoticeq("fresh", "%s Requesting freshness", kWAIT2MINID);
391
392 dispatch_async(_freshParamsQueue, ^{
393 // Hold off (keeping the queue occupied) until we hit the next time we can fresh.
394 wait_until(_nextFreshnessTime);
395
396 NSError *error = nil;
397 BOOL success = [self AttemptSynchronization:&error];
398
399 // Advance the next time can can call freshness.
400 const uint64_t delayBeforeCallingAgainInSeconds = 5ull * NSEC_PER_SEC;
401 _nextFreshnessTime = dispatch_time(DISPATCH_TIME_NOW, delayBeforeCallingAgainInSeconds);
402
403 dispatch_async(dispatch_get_main_queue(), ^{
404 NSDictionary * freshValues = success ? [self copyValues:[NSSet setWithArray:keys]] : @{};
405 handler(freshValues, error);
406 });
407 });
408 }
409
410 - (void)removeObjectForKey:(NSString *)keyToRemove
411 {
412 [[self cloudStore] removeObjectForKey:keyToRemove];
413 }
414
415 - (void)clearStore
416 {
417 secdebug(XPROXYSCOPE, "%@ clearStore", self);
418 NSDictionary *dict = [[self cloudStore] dictionaryRepresentation];
419 NSArray *allKeys = [dict allKeys];
420 NSMutableArray* nullKeys = [NSMutableArray array];
421
422 _alwaysKeys = [NSMutableSet setWithArray:nullKeys];
423 _firstUnlockKeys = [NSMutableSet setWithArray:nullKeys];
424 _unlockedKeys = [NSMutableSet setWithArray:nullKeys];
425 _keyParameterKeys = @{};
426 _circleKeys = @{};
427 _messageKeys = @{};
428
429 [allKeys enumerateObjectsUsingBlock:^(id key, NSUInteger idx, BOOL *stop)
430 {
431 secdebug(XPROXYSCOPE, "%@ Clearing value for key %@", self, key);
432 [[self cloudStore] removeObjectForKey:(NSString *)key];
433 }];
434
435 [self requestSynchronization:YES];
436 }
437
438
439 //
440 // MARK: ----- KVS key lists -----
441 //
442
443 - (id)get:(id)key
444 {
445 return [[self cloudStore] objectForKey:key];
446 }
447
448 - (NSDictionary *)getAll
449 {
450 return [[self cloudStore] dictionaryRepresentation];
451 }
452
453 - (NSDictionary*) exportKeyInterests
454 {
455 return @{ kKeyAlwaysKeys:[_alwaysKeys allObjects],
456 kKeyFirstUnlockKeys:[_firstUnlockKeys allObjects],
457 kKeyUnlockedKeys:[_unlockedKeys allObjects],
458
459 #if 0
460 kKeyKeyParameterKeys: _keyParameterKeys,
461 kKeyMessageKeys : _messageKeys,
462 kKeyCircleKeys : _circleKeys,
463 #endif
464 kKeyPendingKeys:[_pendingKeys allObjects],
465 kKeySyncWithPeersPending:[NSNumber numberWithBool:_syncWithPeersPending],
466 kKeyEnsurePeerRegistration:[NSNumber numberWithBool:_ensurePeerRegistration],
467 kKeyDSID:_dsid
468 };
469 }
470
471 - (void) importKeyInterests: (NSDictionary*) interests
472 {
473 _alwaysKeys = [NSMutableSet setWithArray: interests[kKeyAlwaysKeys]];
474 _firstUnlockKeys = [NSMutableSet setWithArray: interests[kKeyFirstUnlockKeys]];
475 _unlockedKeys = [NSMutableSet setWithArray: interests[kKeyUnlockedKeys]];
476
477 _pendingKeys = [NSMutableSet setWithArray: interests[kKeyPendingKeys]];
478 _syncWithPeersPending = [interests[kKeySyncWithPeersPending] boolValue];
479 _ensurePeerRegistration = [interests[kKeyEnsurePeerRegistration] boolValue];
480 _dsid = interests[kKeyDSID];
481 }
482
483 - (NSMutableSet *)copyAllKeys
484 {
485 NSMutableSet *allKeys = [NSMutableSet setWithSet: _alwaysKeys];
486 [allKeys unionSet: _firstUnlockKeys];
487 [allKeys unionSet: _unlockedKeys];
488 return allKeys;
489 }
490
491 -(void)registerAtTimeKeys:(NSDictionary*)keyparms
492 {
493 NSArray *alwaysArray = [keyparms valueForKey: kKeyAlwaysKeys];
494 NSArray *firstUnlockedKeysArray = [keyparms valueForKey: kKeyFirstUnlockKeys];
495 NSArray *whenUnlockedKeysArray = [keyparms valueForKey: kKeyUnlockedKeys];
496
497 if(alwaysArray)
498 [_alwaysKeys unionSet: [NSMutableSet setWithArray: alwaysArray]];
499 if(firstUnlockedKeysArray)
500 [_firstUnlockKeys unionSet: [NSMutableSet setWithArray: firstUnlockedKeysArray]];
501 if(whenUnlockedKeysArray)
502 [_unlockedKeys unionSet: [NSMutableSet setWithArray: whenUnlockedKeysArray]];
503 }
504
505
506 - (void)registerKeys: (NSDictionary*)keys
507 {
508 secdebug(XPROXYSCOPE, "registerKeys: keys: %@", keys);
509
510 NSMutableSet *allOldKeys = [self copyAllKeys];
511
512 NSDictionary *keyparms = [keys valueForKey: [NSString stringWithUTF8String: kMessageKeyParameter]];
513 NSDictionary *circles = [keys valueForKey: [NSString stringWithUTF8String: kMessageCircle]];
514 NSDictionary *messages = [keys valueForKey: [NSString stringWithUTF8String: kMessageMessage]];
515
516 _alwaysKeys = [NSMutableSet set];
517 _firstUnlockKeys = [NSMutableSet set];
518 _unlockedKeys = [NSMutableSet set];
519
520 _keyParameterKeys = keyparms;
521 _circleKeys = circles;
522 _messageKeys = messages;
523
524 [self registerAtTimeKeys: _keyParameterKeys];
525 [self registerAtTimeKeys: _circleKeys];
526 [self registerAtTimeKeys: _messageKeys];
527
528 NSMutableSet *allNewKeys = [self copyAllKeys];
529
530 // Make sure keys we no longer care about are not pending
531 [_pendingKeys intersectSet:allNewKeys];
532 if (_shadowPendingKeys) {
533 [_shadowPendingKeys intersectSet:allNewKeys];
534 }
535
536 // All new keys only is new keys (remove old keys)
537 [allNewKeys minusSet:allOldKeys];
538
539 // Mark new keys pending, they're new!
540 NSMutableSet *newKeysForCurrentLockState = [self pendKeysAndGetNewlyPended:allNewKeys];
541
542 [self persistState]; // Before we might call out, save our state so we recover if we crash
543
544 [self intersectWithCurrentLockState: newKeysForCurrentLockState];
545 // TODO: Don't processPendingKeysForCurrentLockState if none of the new keys have values.
546 if ([newKeysForCurrentLockState count] != 0) {
547 [self processPendingKeysForCurrentLockState];
548 }
549 }
550
551 - (NSDictionary *)localNotification:(NSDictionary *)localNotificationDict outFlags:(int64_t *)outFlags
552 {
553 __block bool done = false;
554 __block int64_t returnedFlags = 0;
555 __block NSDictionary *responses = NULL;
556
557 CKDUserInteraction *cui = [CKDUserInteraction sharedInstance];
558 [cui requestShowNotification:localNotificationDict completion:^ bool (CFDictionaryRef userResponses, int64_t flags)
559 {
560 responses = [NSDictionary dictionaryWithDictionary:(__bridge NSDictionary *)userResponses];
561 returnedFlags = flags;
562 secdebug(XPROXYSCOPE, "%@ requestShowNotification: dict: %@, flags: %#llx", self, responses, returnedFlags);
563 done = true;
564 return true;
565 }];
566
567 // TODO: replace with e.g. dispatch calls to wait, or semaphore
568 while (!done)
569 sleep(1);
570 if (outFlags)
571 {
572 secdebug(XPROXYSCOPE, "%@ outFlags: %#llx", self, returnedFlags);
573 *outFlags = returnedFlags;
574 }
575 return responses;
576 }
577
578 - (void)saveToUbiquitousStore
579 {
580 [self requestSynchronization:NO];
581 }
582
583 // MARK: ----- Event Handling -----
584
585 - (void)streamEvent:(xpc_object_t)notification
586 {
587 #if (!TARGET_IPHONE_SIMULATOR)
588 const char *notificationName = xpc_dictionary_get_string(notification, "Notification");
589 if (!notificationName) {
590 } else if (strcmp(notificationName, kUserKeybagStateChangeNotification)==0) {
591 return [self keybagStateChange];
592 } else if (strcmp(notificationName, kCloudKeychainStorechangeChangeNotification)==0) {
593 return [self kvsStoreChange];
594 } else if (strcmp(notificationName, kNotifyTokenForceUpdate)==0) {
595 // DEBUG -- Possibly remove in future
596 return [self processAllItems];
597 }
598 const char *eventName = xpc_dictionary_get_string(notification, "XPCEventName");
599 char *desc = xpc_copy_description(notification);
600 secnotice("event", "%@ event: %s name: %s desc: %s", self, eventName, notificationName, desc);
601 if (desc)
602 free((void *)desc);
603 #endif
604 }
605
606 - (void)iCloudAccountAvailabilityChanged:(NSNotification*)notification
607 {
608 /*
609 Continuing this example, you’d then implement an iCloudAccountAvailabilityChanged: method that would:
610
611 Call the ubiquityIdentityToken method and store its return value.
612 Compare the new value to the previous value, to find out if the user logged out of their account or
613 logged in to a different account. If the previously-used account is now unavailable, save the current
614 state locally as needed, empty your iCloud-related data caches, and refresh all iCloud-related user interface elements.
615 */
616 id previCloudToken = currentiCloudToken;
617 currentiCloudToken = [[NSFileManager defaultManager] ubiquityIdentityToken];
618 if (previCloudToken != currentiCloudToken)
619 secnotice("event", "%@ iCloud account changed!", self);
620 else
621 secnotice("event", "%@ %@", self, notification);
622 }
623
624 - (void)cloudChanged:(NSNotification*)notification
625 {
626 /*
627 Posted when the value of one or more keys in the local key-value store
628 changed due to incoming data pushed from iCloud. This notification is
629 sent only upon a change received from iCloud; it is not sent when your
630 app sets a value.
631
632 The user info dictionary can contain the reason for the notification as
633 well as a list of which values changed, as follows:
634
635 The value of the NSUbiquitousKeyValueStoreChangeReasonKey key, when
636 present, indicates why the key-value store changed. Its value is one of
637 the constants in "Change Reason Values."
638
639 The value of the NSUbiquitousKeyValueStoreChangedKeysKey, when present,
640 is an array of strings, each the name of a key whose value changed. The
641 notification object is the NSUbiquitousKeyValueStore object whose contents
642 changed.
643
644 NSUbiquitousKeyValueStoreInitialSyncChange is only posted if there is any
645 local value that has been overwritten by a distant value. If there is no
646 conflict between the local and the distant values when doing the initial
647 sync (e.g. if the cloud has no data stored or the client has not stored
648 any data yet), you'll never see that notification.
649
650 NSUbiquitousKeyValueStoreInitialSyncChange implies an initial round trip
651 with server but initial round trip with server does not imply
652 NSUbiquitousKeyValueStoreInitialSyncChange.
653 */
654 os_activity_initiate("cloudChanged", OS_ACTIVITY_FLAG_DEFAULT, ^{
655 secdebug(XPROXYSCOPE, "%@ cloudChanged notification: %@", self, notification);
656
657 NSDictionary *userInfo = [notification userInfo];
658 NSNumber *reason = [userInfo objectForKey:NSUbiquitousKeyValueStoreChangeReasonKey];
659 if (reason) switch ([reason integerValue]) {
660 case NSUbiquitousKeyValueStoreInitialSyncChange:
661 case NSUbiquitousKeyValueStoreServerChange:
662 {
663 _seenKVSStoreChange = YES;
664 NSSet *keysChangedInCloud = [NSSet setWithArray:[userInfo objectForKey:NSUbiquitousKeyValueStoreChangedKeysKey]];
665
666 /* We are saying that we want to try processing a key no matter what,
667 * *if* it has changed in the cloud. */
668 [_pendingKeys minusSet:keysChangedInCloud];
669
670 NSSet *keysOfInterestThatChanged = [self pendKeysAndGetPendingForCurrentLockState:keysChangedInCloud];
671 NSMutableDictionary *changedValues = [self copyValues:keysOfInterestThatChanged];
672 if ([reason integerValue] == NSUbiquitousKeyValueStoreInitialSyncChange)
673 changedValues[(__bridge NSString*)kSOSKVSInitialSyncKey] = @"true";
674
675 secnotice("event", "%@ keysChangedInCloud: %@ keysOfInterest: %@", self, [[keysChangedInCloud allObjects] componentsJoinedByString: @" "], [[changedValues allKeys] componentsJoinedByString: @" "]);
676 if ([changedValues count])
677 [self processKeyChangedEvent:changedValues];
678 break;
679 }
680 case NSUbiquitousKeyValueStoreQuotaViolationChange:
681 seccritical("%@ event received NSUbiquitousKeyValueStoreQuotaViolationChange", self);
682 break;
683 case NSUbiquitousKeyValueStoreAccountChange:
684 // The primary account changed. We do not get this for password changes on the same account
685 secnotice("event", "%@ NSUbiquitousKeyValueStoreAccountChange", self);
686 NSDictionary *changedValues = nil;
687 if(_dsid)
688 changedValues = @{ (__bridge NSString*)kSOSKVSAccountChangedKey: _dsid };
689 else
690 changedValues = @{ (__bridge NSString*)kSOSKVSAccountChangedKey: @"true" };
691
692 [self processKeyChangedEvent:changedValues];
693 break;
694 }
695 });
696 }
697
698 - (void) doAfterFlush: (dispatch_block_t) block
699 {
700 // Flush any pending communication to Securityd.
701
702 dispatch_async(_calloutQueue, block);
703 }
704
705 - (void) calloutWith: (void(^)(NSSet *pending, bool syncWithPeersPending, bool ensurePeerRegistration, dispatch_queue_t queue, void(^done)(NSSet *handledKeys, bool handledSyncWithPeers, bool handledEnsurePeerRegistration))) callout
706 {
707 // In CKDKVSProxy's serial queue
708 dispatch_queue_t ckdkvsproxy_queue = dispatch_get_main_queue();
709
710 _oldInCallout = YES;
711
712 // dispatch_get_global_queue - well-known global concurrent queue
713 // dispatch_get_main_queue - default queue that is bound to the main thread
714 xpc_transaction_begin();
715 dispatch_async(_calloutQueue, ^{
716 __block NSSet *myPending;
717 __block bool mySyncWithPeersPending;
718 __block bool myEnsurePeerRegistration;
719 __block bool wasLocked;
720 dispatch_sync(ckdkvsproxy_queue, ^{
721 myPending = [_pendingKeys copy];
722 mySyncWithPeersPending = _syncWithPeersPending;
723 myEnsurePeerRegistration = _ensurePeerRegistration;
724 wasLocked = _isLocked;
725
726 _inCallout = YES;
727 if (!_oldInCallout)
728 secnotice("deaf", ">>>>>>>>>>> _oldInCallout is NO and we're heading in to the callout!");
729
730 _shadowPendingKeys = [NSMutableSet set];
731 _shadowSyncWithPeersPending = NO;
732 });
733
734 callout(myPending, mySyncWithPeersPending, myEnsurePeerRegistration, ckdkvsproxy_queue, ^(NSSet *handledKeys, bool handledSyncWithPeers, bool handledEnsurePeerRegistration) {
735 secdebug("event", "%@ %s%s before callout handled: %s%s", self, mySyncWithPeersPending ? "S" : "s", myEnsurePeerRegistration ? "E" : "e", handledSyncWithPeers ? "S" : "s", handledEnsurePeerRegistration ? "E" : "e");
736
737 // In CKDKVSProxy's serial queue
738 _inCallout = NO;
739 _oldInCallout = NO;
740
741 // Update ensurePeerRegistration
742 _ensurePeerRegistration = ((myEnsurePeerRegistration && !handledEnsurePeerRegistration) || _shadowEnsurePeerRegistration);
743
744 _shadowEnsurePeerRegistration = NO;
745
746 if(_ensurePeerRegistration && !_isLocked)
747 [self doEnsurePeerRegistration];
748
749 // Update SyncWithPeers stuff.
750 _syncWithPeersPending = ((mySyncWithPeersPending && (!handledSyncWithPeers)) || _shadowSyncWithPeersPending);
751
752 _shadowSyncWithPeersPending = NO;
753 if (handledSyncWithPeers)
754 _lastSyncTime = dispatch_time(DISPATCH_TIME_NOW, 0);
755
756 // Update pendingKeys and handle them
757 [_pendingKeys minusSet: handledKeys];
758 bool hadShadowPendingKeys = [_shadowPendingKeys count];
759 // Move away shadownPendingKeys first, because pendKeysAndGetPendingForCurrentLockState
760 // will look at them. See rdar://problem/20733166.
761 NSSet *oldShadowPendingKeys = _shadowPendingKeys;
762 _shadowPendingKeys = nil;
763
764 NSSet *filteredKeys = [self pendKeysAndGetPendingForCurrentLockState:oldShadowPendingKeys];
765
766 // Write state to disk
767 [self persistState];
768
769 // Handle shadow pended stuff
770 if (_syncWithPeersPending && !_isLocked)
771 [self scheduleSyncRequestTimer];
772 /* We don't want to call processKeyChangedEvent if we failed to
773 handle pending keys and the device didn't unlock nor receive
774 any kvs changes while we were in our callout.
775 Doing so will lead to securityd and CloudKeychainProxy
776 talking to each other forever in a tight loop if securityd
777 repeatedly returns an error processing the same message.
778 Instead we leave any old pending keys until the next event. */
779 if (hadShadowPendingKeys || (!_isLocked && wasLocked))
780 [self processKeyChangedEvent:[self copyValues:filteredKeys]];
781
782 xpc_transaction_end();
783 });
784 });
785 }
786
787 - (void) sendKeysCallout: (NSSet *(^)(NSSet* pending, NSError** error)) handleKeys {
788 [self calloutWith: ^(NSSet *pending, bool syncWithPeersPending, bool ensurePeerRegistration, dispatch_queue_t queue, void(^done)(NSSet *, bool, bool)) {
789 NSError* error = NULL;
790
791 NSSet * handled = handleKeys(pending, &error);
792
793 dispatch_async(queue, ^{
794 if (!handled) {
795 secerror("%@ ensurePeerRegistration failed: %@", self, error);
796 }
797
798 done(handled, NO, NO);
799 });
800 }];
801 }
802
803 - (void) doEnsurePeerRegistration
804 {
805 [self calloutWith:^(NSSet *pending, bool syncWithPeersPending, bool ensurePeerRegistration, dispatch_queue_t queue, void(^done)(NSSet *, bool, bool)) {
806 CFErrorRef error = NULL;
807 bool handledEnsurePeerRegistration = SOSCCProcessEnsurePeerRegistration(&error);
808 secerror("%@ ensurePeerRegistration called, %@ (%@)", self, handledEnsurePeerRegistration ? @"success" : @"failure", error);
809 dispatch_async(queue, ^{
810 if (!handledEnsurePeerRegistration) {
811 secerror("%@ ensurePeerRegistration failed: %@", self, error);
812 }
813
814 done(nil, NO, handledEnsurePeerRegistration);
815 CFReleaseSafe(error);
816 });
817 }];
818 }
819
820 - (void) doSyncWithAllPeers
821 {
822 [self calloutWith:^(NSSet *pending, bool syncWithPeersPending, bool ensurePeerRegistration, dispatch_queue_t queue, void(^done)(NSSet *, bool, bool)) {
823 CFErrorRef error = NULL;
824
825 SyncWithAllPeersReason reason = SOSCCProcessSyncWithAllPeers(&error);
826 dispatch_async(queue, ^{
827 bool handledSyncWithPeers = NO;
828 if (reason == kSyncWithAllPeersSuccess) {
829 handledSyncWithPeers = YES;
830 secnotice("event", "%@ syncWithAllPeers succeeded", self);
831 } else if (reason == kSyncWithAllPeersLocked) {
832 secnotice("event", "%@ syncWithAllPeers attempted while locked - waiting for unlock", self);
833 handledSyncWithPeers = NO;
834 [self updateIsLocked];
835 } else if (reason == kSyncWithAllPeersOtherFail) {
836 // Pretend we handled syncWithPeers, by pushing out the _lastSyncTime
837 // This will cause us to wait for kMinSyncInterval seconds before
838 // retrying, so we don't spam securityd if sync is failing
839 secerror("%@ syncWithAllPeers %@, rescheduling timer", self, error);
840 _lastSyncTime = dispatch_time(DISPATCH_TIME_NOW, 0);
841 } else {
842 secerror("%@ syncWithAllPeers %@, unknown reason: %d", self, error, reason);
843 }
844
845 done(nil, handledSyncWithPeers, false);
846 CFReleaseSafe(error);
847 });
848 }];
849 }
850
851 - (void)timerFired
852 {
853 secnotice("event", "%@ syncWithPeersPending: %d inCallout: %d isLocked: %d", self, _syncWithPeersPending, _inCallout, _isLocked);
854 _syncTimerScheduled = NO;
855 if(_ensurePeerRegistration){
856 [self doEnsurePeerRegistration];
857 }
858 if (_syncWithPeersPending && !_inCallout && !_isLocked){
859 [self doSyncWithAllPeers];
860 }
861 }
862
863 - (dispatch_time_t) nextSyncTime
864 {
865 dispatch_time_t nextSync = dispatch_time(DISPATCH_TIME_NOW, kMinSyncDelay);
866
867 // Don't sync again unless we waited at least kMinSyncInterval
868 if (_lastSyncTime) {
869 dispatch_time_t soonest = dispatch_time(_lastSyncTime, kMinSyncInterval);
870 if (nextSync < soonest || _deadline < soonest) {
871 secdebug("timer", "%@ backing off", self);
872 return soonest;
873 }
874 }
875
876 // Don't delay more than kMaxSyncDelay after the first request.
877 if (nextSync > _deadline) {
878 secdebug("timer", "%@ hit deadline", self);
879 return _deadline;
880 }
881
882 // Bump the timer by kMinSyncDelay
883 if (_syncTimerScheduled)
884 secdebug("timer", "%@ bumped timer", self);
885 else
886 secdebug("timer", "%@ scheduled timer", self);
887
888 return nextSync;
889 }
890
891 - (void)scheduleSyncRequestTimer
892 {
893 dispatch_source_set_timer(_syncTimer, [self nextSyncTime], DISPATCH_TIME_FOREVER, kSyncTimerLeeway);
894 _syncTimerScheduled = YES;
895 }
896
897 - (void)requestSyncWithAllPeers // secd calling SOSCCSyncWithAllPeers invokes this
898 {
899 #if !defined(NDEBUG)
900 NSString *desc = [self description];
901 #endif
902
903 if (!_syncWithPeersPending || (_inCallout && !_shadowSyncWithPeersPending))
904 _deadline = dispatch_time(DISPATCH_TIME_NOW, kMaxSyncDelay);
905
906 if (!_syncWithPeersPending) {
907 _syncWithPeersPending = YES;
908 [self persistState];
909 }
910
911 if (_inCallout)
912 _shadowSyncWithPeersPending = YES;
913 else if (!_isLocked)
914 [self scheduleSyncRequestTimer];
915
916 secdebug("event", "%@ %@", desc, self);
917 }
918
919 - (void)requestEnsurePeerRegistration // secd calling SOSCCSyncWithAllPeers invokes this
920 {
921 #if !defined(NDEBUG)
922 NSString *desc = [self description];
923 #endif
924
925 if (_inCallout) {
926 _shadowEnsurePeerRegistration = YES;
927 } else {
928 _ensurePeerRegistration = YES;
929 if (!_isLocked){
930 [self doEnsurePeerRegistration];
931 }
932 [self persistState];
933 }
934
935 secdebug("event", "%@ %@", desc, self);
936 }
937
938
939 - (BOOL) updateUnlockedSinceBoot
940 {
941 CFErrorRef aksError = NULL;
942 if (!SecAKSGetHasBeenUnlocked(&_unlockedSinceBoot, &aksError)) {
943 secerror("%@ Got error from SecAKSGetHasBeenUnlocked: %@", self, aksError);
944 CFReleaseSafe(aksError);
945 return NO;
946 }
947 return YES;
948 }
949
950 - (BOOL) updateIsLocked
951 {
952 CFErrorRef aksError = NULL;
953 if (!SecAKSGetIsLocked(&_isLocked, &aksError)) {
954 secerror("%@ Got error querying lock state: %@", self, aksError);
955 CFReleaseSafe(aksError);
956 return NO;
957 }
958 if (!_isLocked)
959 _unlockedSinceBoot = YES;
960 return YES;
961 }
962
963 - (void) keybagStateChange
964 {
965 os_activity_initiate("keybagStateChanged", OS_ACTIVITY_FLAG_DEFAULT, ^{
966 BOOL wasLocked = _isLocked;
967 if ([self updateIsLocked]) {
968 if (wasLocked == _isLocked)
969 secdebug("event", "%@ still %s ignoring", self, _isLocked ? "locked" : "unlocked");
970 else if (_isLocked)
971 [self keybagDidLock];
972 else
973 [self keybagDidUnlock];
974 }
975 });
976 }
977
978 - (void) keybagDidLock
979 {
980 secnotice("event", "%@", self);
981 }
982
983 - (void) keybagDidUnlock
984 {
985 secnotice("event", "%@", self);
986 if (_ensurePeerRegistration) {
987 [self doEnsurePeerRegistration];
988 }
989
990 // First send changed keys to securityd so it can proccess updates
991 [self processPendingKeysForCurrentLockState];
992
993 // Then, tickle securityd to perform a sync if needed.
994 if (_syncWithPeersPending && !_syncTimerScheduled) {
995 [self doSyncWithAllPeers];
996 }
997 }
998
999 - (void) kvsStoreChange {
1000 os_activity_initiate("kvsStoreChange", OS_ACTIVITY_FLAG_DEFAULT, ^{
1001 if (!_seenKVSStoreChange) {
1002 _seenKVSStoreChange = YES; // Only do this once
1003 secnotice("event", "%@ received darwin notification before first NSNotification", self);
1004 // TODO This might not be needed if we always get the NSNotification
1005 // deleived even if we were launched due to a kvsStoreChange
1006 // Send all keys for current lock state to securityd so it can proccess them
1007 [self pendKeysAndGetNewlyPended: [self copyAllKeys]];
1008 [self processPendingKeysForCurrentLockState];
1009 } else {
1010 secdebug("event", "%@ ignored, waiting for NSNotification", self);
1011 }
1012 });
1013 }
1014
1015 //
1016 // MARK: ----- Key Filtering -----
1017 //
1018
1019 - (NSSet*) keysForCurrentLockState
1020 {
1021 secdebug("filtering", "%@ Filtering: unlockedSinceBoot: %d\n unlocked: %d\n, keysOfInterest: <%@>", self, (int) _unlockedSinceBoot, (int) !_isLocked, [SOSPersistentState dictionaryDescription: [self exportKeyInterests]]);
1022
1023 NSMutableSet *currentStateKeys = [NSMutableSet setWithSet: _alwaysKeys];
1024 if (_unlockedSinceBoot)
1025 [currentStateKeys unionSet: _firstUnlockKeys];
1026
1027 if (!_isLocked)
1028 [currentStateKeys unionSet: _unlockedKeys];
1029
1030 return currentStateKeys;
1031 }
1032
1033 - (NSMutableSet*) pendKeysAndGetNewlyPended: (NSSet*) keysToPend
1034 {
1035 NSMutableSet *newlyPendedKeys = [keysToPend mutableCopy];
1036 [newlyPendedKeys minusSet: _pendingKeys];
1037 if (_shadowPendingKeys) {
1038 [newlyPendedKeys minusSet: _shadowPendingKeys];
1039 }
1040
1041 [_pendingKeys unionSet:keysToPend];
1042 if (_shadowPendingKeys) {
1043 [_shadowPendingKeys unionSet:keysToPend];
1044 }
1045
1046 return newlyPendedKeys;
1047 }
1048
1049 - (void) intersectWithCurrentLockState: (NSMutableSet*) set
1050 {
1051 [set intersectSet: [self keysForCurrentLockState]];
1052 }
1053
1054 - (NSMutableSet*) pendingKeysForCurrentLockState
1055 {
1056 NSMutableSet * result = [_pendingKeys mutableCopy];
1057 [self intersectWithCurrentLockState:result];
1058 return result;
1059 }
1060
1061 - (NSMutableSet*) pendKeysAndGetPendingForCurrentLockState: (NSSet*) startingSet
1062 {
1063 [self pendKeysAndGetNewlyPended: startingSet];
1064
1065 return [self pendingKeysForCurrentLockState];
1066 }
1067
1068 - (NSMutableDictionary *)copyValues:(NSSet*)keysOfInterest
1069 {
1070 // Grab values from KVS.
1071 NSUbiquitousKeyValueStore *store = [self cloudStore];
1072 NSMutableDictionary *changedValues = [NSMutableDictionary dictionaryWithCapacity:0];
1073 [keysOfInterest enumerateObjectsUsingBlock:^(id obj, BOOL *stop)
1074 {
1075 NSString* key = (NSString*) obj;
1076 id objval = [store objectForKey:key];
1077 if (!objval) objval = [NSNull null];
1078
1079 [changedValues setObject:objval forKey:key];
1080 secdebug(XPROXYSCOPE, "%@ storeChanged updated value for %@", self, key);
1081 }];
1082 return changedValues;
1083 }
1084
1085 /*
1086 During RegisterKeys, separate keys-of-interest into three disjoint sets:
1087 - keys that we always want to be notified about; this means we can get the
1088 value at any time
1089 - keys that require the device to have been unlocked at least once
1090 - keys that require the device to be unlocked now
1091
1092 Typically, the sets of keys will be:
1093
1094 - Dk: alwaysKeys
1095 - Ck: firstUnlock
1096 - Ak: unlocked
1097
1098 The caller is responsible for making sure that the keys in e.g. alwaysKeys are
1099 values that can be handled at any time (that is, not when unlocked)
1100
1101 Each time we get a notification from ubiquity that keys have changed, we need to
1102 see if anything of interest changed. If we don't care, then done.
1103
1104 For each key-of-interest that changed, we either notify the client that things
1105 changed, or add it to a pendingNotifications list. If the notification to the
1106 client fails, also add it to the pendingNotifications list. This pending list
1107 should be written to persistent storage and consulted any time we either get an
1108 item changed notification, or get a stream event signalling a change in lock state.
1109
1110 We can notify the client either through XPC if a connection is set up, or call a
1111 routine in securityd to launch it.
1112
1113 */
1114
1115 - (void)processKeyChangedEvent:(NSDictionary *)changedValues
1116 {
1117 NSMutableDictionary* filtered = [NSMutableDictionary dictionary];
1118
1119 NSMutableArray* nullKeys = [NSMutableArray array];
1120 // Remove nulls because we don't want them in securityd.
1121 [changedValues enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
1122 if (obj == [NSNull null])
1123 [nullKeys addObject:key];
1124 else{
1125 filtered[key] = obj;
1126 }
1127 }];
1128 if ([nullKeys count])
1129 [_pendingKeys minusSet: [NSSet setWithArray: nullKeys]];
1130
1131 if([filtered count] != 0 ){
1132 [self sendKeysCallout:^NSSet *(NSSet *pending, NSError** error) {
1133 CFErrorRef cf_error = NULL;
1134 NSArray* handledMessage = (__bridge_transfer NSArray*) _SecKeychainSyncUpdateMessage((__bridge CFDictionaryRef)filtered, &cf_error);
1135 NSError *updateError = (__bridge_transfer NSError*)cf_error;
1136 if (error)
1137 *error = updateError;
1138
1139 secnoticeq("keytrace", "%@ misc handled: %@ null: %@ pending: %@", self,
1140 [handledMessage componentsJoinedByString: @" "],
1141 [nullKeys componentsJoinedByString: @" "],
1142 [[_pendingKeys allObjects] componentsJoinedByString: @" "]);
1143
1144 return handledMessage ? [NSSet setWithArray: handledMessage] : nil;
1145 }];
1146 } else {
1147 secnoticeq("keytrace", "%@ null: %@ pending: %@", self,
1148 [nullKeys componentsJoinedByString: @" "],
1149 [[_pendingKeys allObjects] componentsJoinedByString: @" "]);
1150 }
1151 }
1152
1153 - (void) processPendingKeysForCurrentLockState
1154 {
1155 [self processKeyChangedEvent: [self copyValues: [self pendingKeysForCurrentLockState]]];
1156 }
1157
1158 @end
1159
1160