]> git.saurik.com Git - apple/security.git/blob - KVSKeychainSyncingProxy/CKDKVSProxy.m
Security-59306.41.2.tar.gz
[apple/security.git] / KVSKeychainSyncingProxy / CKDKVSProxy.m
1 /*
2 * Copyright (c) 2012-2014,2016 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/Foundation.h>
30
31 #import <utilities/debugging.h>
32 #import <os/activity.h>
33
34 #import "CKDKVSProxy.h"
35 #import "CKDKVSStore.h"
36 #import "CKDAKSLockMonitor.h"
37 #import "CKDSecuritydAccount.h"
38 #import "NSURL+SOSPlistStore.h"
39
40 #include "keychain/SecureObjectSync/SOSARCDefines.h"
41 #include "keychain/SecureObjectSync/SOSKVSKeys.h"
42 #include <utilities/SecCFWrappers.h>
43 #include <utilities/SecPLWrappers.h>
44
45 #include "SOSCloudKeychainConstants.h"
46
47 #include <utilities/SecAKSWrappers.h>
48 #include <utilities/SecADWrapper.h>
49 #include <utilities/SecNSAdditions.h>
50 #import "XPCNotificationDispatcher.h"
51
52
53 @interface NSSet (CKDLogging)
54 - (NSString*) logKeys;
55 - (NSString*) logIDs;
56 @end
57
58 @implementation NSSet (CKDLogging)
59 - (NSString*) logKeys {
60 return [self sortedElementsJoinedByString:@" "];
61 }
62
63 - (NSString*) logIDs {
64 return [self sortedElementsTruncated:8 JoinedByString:@" "];
65 }
66 @end
67
68
69 /*
70 The total space available in your app’s iCloud key-value storage is 1 MB.
71 The maximum number of keys you can specify is 1024, and the size limit for
72 each value associated with a key is 1 MB. So, for example, if you store a
73 single large value of 1 MB for a single key, that consumes your total
74 available storage. If you store 1 KB of data for each key, you can use
75 1,000 key-value pairs.
76 */
77
78 static NSString *kKeyKeyParameterKeys = @"KeyParameterKeys";
79 static NSString *kKeyCircleKeys = @"CircleKeys";
80 static NSString *kKeyMessageKeys = @"MessageKeys";
81
82 static NSString *kKeyAlwaysKeys = @"AlwaysKeys";
83 static NSString *kKeyFirstUnlockKeys = @"FirstUnlockKeys";
84 static NSString *kKeyUnlockedKeys = @"UnlockedKeys";
85 static NSString *kKeyPendingKeys = @"PendingKeys";
86 static NSString *kKeyUnsentChangedKeys = @"unsentChangedKeys";
87 static NSString *kKeyUnlockNotificationRequested = @"unlockNotificationRequested";
88 static NSString *kKeySyncWithPeersPending = @"SyncWithPeersPending";
89
90 static NSString *kKeyPendingSyncPeerIDs = @"SyncPeerIDs";
91 static NSString *kKeyPendingSyncBackupPeerIDs = @"SyncBackupPeerIDs";
92
93 static NSString *kKeyEnsurePeerRegistration = @"EnsurePeerRegistration";
94 static NSString *kKeyDSID = @"DSID";
95 static NSString *kKeyAccountUUID = @"KeyAccountUUID";
96
97 static NSString *kMonitorPenaltyBoxKey = @"Penalty";
98 static NSString *kMonitorMessageKey = @"Message";
99 static NSString *kMonitorConsecutiveWrites = @"ConsecutiveWrites";
100 static NSString *kMonitorLastWriteTimestamp = @"LastWriteTimestamp";
101 static NSString *kMonitorMessageQueue = @"MessageQueue";
102
103 static NSString *kMonitorTimeTable = @"TimeTable";
104 static NSString *kMonitorFirstMinute = @"AFirstMinute";
105 static NSString *kMonitorSecondMinute = @"BSecondMinute";
106 static NSString *kMonitorThirdMinute = @"CThirdMinute";
107 static NSString *kMonitorFourthMinute = @"DFourthMinute";
108 static NSString *kMonitorFifthMinute = @"EFifthMinute";
109 static NSString *kMonitorWroteInTimeSlice = @"TimeSlice";
110
111 #define kSecServerKeychainChangedNotification "com.apple.security.keychainchanged"
112
113 @interface UbiqitousKVSProxy ()
114 @property (nonatomic) NSDictionary* persistentData;
115 - (void) doSyncWithAllPeers;
116 - (void) persistState;
117 @end
118
119 @implementation UbiqitousKVSProxy
120
121 + (instancetype)withAccount:(NSObject<CKDAccount>*) account
122 store:(NSObject<CKDStore>*) store
123 lockMonitor:(NSObject<CKDLockMonitor>*) lockMonitor
124 persistence:(NSURL*) localPersistence
125 {
126 return [[self alloc] initWithAccount:account
127 store:store
128 lockMonitor:lockMonitor
129 persistence:localPersistence];
130 }
131
132 - (instancetype)initWithAccount:(NSObject<CKDAccount>*) account
133 store:(NSObject<CKDStore>*) store
134 lockMonitor:(NSObject<CKDLockMonitor>*) lockMonitor
135 persistence:(NSURL*) localPersistence
136 {
137 if (self = [super init])
138 {
139 secnotice("event", "%@ start", self);
140
141 #if !TARGET_OS_IPHONE || TARGET_OS_SIMULATOR
142 // rdar://problem/26247270
143 if (geteuid() == 0) {
144 secerror("Cannot run CloudKeychainProxy as root");
145 return NULL;
146 }
147 #endif
148 _ensurePeerRegistration = NO;
149
150 _pendingSyncPeerIDs = [NSMutableSet set];
151 _pendingSyncBackupPeerIDs = [NSMutableSet set];
152 _shadowPendingSyncPeerIDs = nil;
153 _shadowPendingSyncBackupPeerIDs = nil;
154
155 _persistenceURL = localPersistence;
156
157 _account = account;
158 _store = store;
159 _lockMonitor = lockMonitor;
160
161
162 _calloutQueue = dispatch_queue_create("CKDCallout", DISPATCH_QUEUE_SERIAL);
163 _ckdkvsproxy_queue = dispatch_queue_create("CKDKVSProxy", DISPATCH_QUEUE_SERIAL);
164
165 _freshnessCompletions = [NSMutableArray<FreshnessResponseBlock> array];
166
167 [[XPCNotificationDispatcher dispatcher] addListener: self];
168
169 [self setPersistentData: [self.persistenceURL readPlist]];
170
171 _dsid = @"";
172 _accountUUID = @"";
173
174 [[self store] connectToProxy: self];
175 [[self lockMonitor] connectTo:self];
176
177 secdebug(XPROXYSCOPE, "%@ done", self);
178 }
179 return self;
180 }
181
182 - (NSString *)description
183 {
184 return [NSString stringWithFormat:@"<%s%s%s%s%s%s%s%s%s%s>",
185 [[self lockMonitor] locked] ? "L" : "U",
186 [[self lockMonitor] unlockedSinceBoot] ? "B" : "-",
187 _seenKVSStoreChange ? "K" : "-",
188 [self hasPendingNonShadowSyncIDs] ? "s" : "-",
189 _ensurePeerRegistration ? "e" : "-",
190 [_pendingKeys count] ? "p" : "-",
191 _inCallout ? "C" : "-",
192 [self hasPendingShadowSyncIDs] ? "S" : "-",
193 _shadowEnsurePeerRegistration ? "E" : "-",
194 [_shadowPendingKeys count] ? "P" : "-"];
195 }
196
197 //
198 // MARK: XPC Function commands
199 //
200 - (void) clearStore {
201 [self.store removeAllObjects];
202 }
203
204 - (void)synchronizeStore {
205 [self.store pushWrites];
206 }
207
208 - (id) objectForKey: (NSString*) key {
209 return [self.store objectForKey: key];
210 }
211 - (NSDictionary<NSString *, id>*) copyAsDictionary {
212 return [self.store copyAsDictionary];
213 }
214
215 - (void)_queue_processAllItems
216 {
217 dispatch_assert_queue(_ckdkvsproxy_queue);
218
219 NSDictionary *allItems = [self.store copyAsDictionary];
220 if (allItems)
221 {
222 secnotice("event", "%@ sending: %@", self, [[allItems allKeys] componentsJoinedByString: @" "]);
223 [self processKeyChangedEvent:allItems];
224 }
225 else
226 secdebug(XPROXYSCOPE, "%@ No items in KVS", self);
227 }
228
229 - (void)dealloc
230 {
231 secdebug(XPROXYSCOPE, "%@", self);
232 [[NSNotificationCenter defaultCenter] removeObserver:self
233 name:NSUbiquitousKeyValueStoreDidChangeExternallyNotification
234 object:nil];
235
236 [[NSNotificationCenter defaultCenter] removeObserver:self
237 name:NSUbiquityIdentityDidChangeNotification
238 object:nil];
239 }
240
241 // MARK: Persistence
242
243 - (NSDictionary*) persistentData
244 {
245 return @{ kKeyAlwaysKeys:[_alwaysKeys allObjects],
246 kKeyFirstUnlockKeys:[_firstUnlockKeys allObjects],
247 kKeyUnlockedKeys:[_unlockedKeys allObjects],
248 kKeyPendingKeys:[_pendingKeys allObjects],
249 kKeyPendingSyncPeerIDs:[_pendingSyncPeerIDs allObjects],
250 kKeyPendingSyncBackupPeerIDs:[_pendingSyncBackupPeerIDs allObjects],
251 kKeyEnsurePeerRegistration:[NSNumber numberWithBool:_ensurePeerRegistration],
252 kKeyDSID:_dsid,
253 kKeyAccountUUID:_accountUUID
254 };
255 }
256
257 - (void) setPersistentData: (NSDictionary*) interests
258 {
259 _alwaysKeys = [NSMutableSet setWithArray: interests[kKeyAlwaysKeys]];
260 [_alwaysKeys addObject:(__bridge NSString*) kSOSKVSKeyParametersKey]; // Make sure KeyParms are always of interest
261 _firstUnlockKeys = [NSMutableSet setWithArray: interests[kKeyFirstUnlockKeys]];
262 _unlockedKeys = [NSMutableSet setWithArray: interests[kKeyUnlockedKeys]];
263
264 _pendingKeys = [NSMutableSet setWithArray: interests[kKeyPendingKeys]];
265
266 _pendingSyncPeerIDs = [NSMutableSet setWithArray: interests[kKeyPendingSyncPeerIDs]];
267 _pendingSyncBackupPeerIDs = [NSMutableSet setWithArray: interests[kKeyPendingSyncBackupPeerIDs]];
268
269 _ensurePeerRegistration = [interests[kKeyEnsurePeerRegistration] boolValue];
270
271 _dsid = interests[kKeyDSID];
272 _accountUUID = interests[kKeyAccountUUID];
273 }
274
275 - (void)persistState
276 {
277 NSDictionary* dataToSave = self.persistentData;
278
279 secdebug("persistence", "Writing registeredKeys: %@", [dataToSave compactDescription]);
280 if (![self.persistenceURL writePlist:dataToSave]) {
281 secerror("Failed to write persistence data to %@", self.persistenceURL);
282 }
283 }
284
285 - (void)perfCounters:(void(^)(NSDictionary *counters))callback
286 {
287 /* Collect and merge perf counters from other layers here too */
288 [self.store perfCounters:callback];
289 }
290
291
292 // MARK: Object setting
293
294
295 - (void)setStoreObjectsFromDictionary:(NSDictionary *)values
296 {
297 if (values == nil) {
298 secdebug(XPROXYSCOPE, "%@ NULL? values: %@", self, values);
299 return;
300 }
301
302 NSMutableDictionary<NSString*, NSObject*> *mutableValues = [values mutableCopy];
303 NSString* newDSID = asNSString([mutableValues extractObjectForKey:(__bridge NSString*) kSOSKVSOfficialDSIDKey]);
304 if (newDSID) {
305 _dsid = newDSID;
306 }
307
308 NSString* requiredDSID = asNSString([mutableValues extractObjectForKey:(__bridge NSString*) kSOSKVSRequiredKey]);
309 if (requiredDSID) {
310 if (_dsid == nil || [_dsid isEqualToString: @""]) {
311 secdebug("dsid", "CloudKeychainProxy setting dsid to :%@ from securityd", requiredDSID);
312 _dsid = requiredDSID;
313 } else if (![_dsid isEqual: requiredDSID]) {
314 secerror("Account DSIDs do not match, cloud keychain proxy: %@, securityd: %@", _dsid, requiredDSID);
315 secerror("Not going to write these: %@ into KVS!", values);
316 return;
317 } else {
318 secnoticeq("dsid", "DSIDs match, writing");
319 }
320 }
321
322 secnoticeq("keytrace", "%@ sending: %@", self, [[mutableValues allKeys] componentsJoinedByString: @" "]);
323 [mutableValues enumerateKeysAndObjectsUsingBlock: ^(id key, id obj, BOOL *stop)
324 {
325 if (obj == NULL || obj == [NSNull null]) {
326 [self.store removeObjectForKey:key];
327 } else {
328 if ([key hasPrefix:@"ak|"]) { // TODO: somewhat of a hack
329 id oldObj = [self.store objectForKey:key];
330 if ([oldObj isEqual: obj]) {
331 // Fix KVS repeated message undelivery by sending a NULL first (deafness)
332 secnoticeq("keytrace", "forcing resend of key write: %@", key);
333 [self.store removeObjectForKey:key];
334 }
335 }
336 [[self store] addOneToOutGoing];
337 [self.store setObject:obj forKey:key];
338 }
339 }];
340
341 [self.store pushWrites];
342 }
343
344 - (void)setObjectsFromDictionary:(NSDictionary<NSString*, NSObject*> *)values
345 {
346 [self setStoreObjectsFromDictionary:values];
347 }
348
349 - (void)waitForSynchronization:(void (^)(NSDictionary<NSString*, NSObject*> *results, NSError *err))handler
350 {
351 secnoticeq("fresh", "%s Requesting WFS", kWAIT2MINID);
352
353 [_freshnessCompletions addObject: ^(bool success, NSError *error){
354 secnoticeq("fresh", "%s WFS Done", kWAIT2MINID);
355 handler(nil, error);
356 }];
357
358 if ([self.freshnessCompletions count] == 1) {
359 // We can't talk to synchronize on the _ckdkvsproxy_queue or we deadlock,
360 // bounce to a global concurrent queue
361 dispatch_after(_nextFreshnessTime, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
362 NSError *error = nil;
363 bool success = [self.store pullUpdates:&error];
364
365 dispatch_async(self->_ckdkvsproxy_queue, ^{
366 [self waitForSyncDone: success error: error];
367 });
368 });
369 }
370 }
371
372 - (void) waitForSyncDone: (bool) success error: (NSError*) error{
373 if (success) {
374 const uint64_t delayBeforeCallingAgainInSeconds = 5ull * NSEC_PER_SEC;
375 _nextFreshnessTime = dispatch_time(DISPATCH_TIME_NOW, delayBeforeCallingAgainInSeconds);
376 }
377
378 secnoticeq("fresh", "%s Completing WFS", kWAIT2MINID);
379 [_freshnessCompletions enumerateObjectsUsingBlock:^(FreshnessResponseBlock _Nonnull block,
380 NSUInteger idx,
381 BOOL * _Nonnull stop) {
382 block(success, error);
383 }];
384 [_freshnessCompletions removeAllObjects];
385
386 }
387
388 //
389 // MARK: ----- KVS key lists -----
390 //
391
392 - (NSMutableSet *)copyAllKeyInterests
393 {
394 NSMutableSet *allKeys = [NSMutableSet setWithSet: _alwaysKeys];
395 [allKeys unionSet: _firstUnlockKeys];
396 [allKeys unionSet: _unlockedKeys];
397 return allKeys;
398 }
399
400 -(void)registerAtTimeKeys:(NSDictionary*)keyparms
401 {
402 if (keyparms == nil)
403 return;
404
405 NSArray *alwaysArray = [keyparms valueForKey: kKeyAlwaysKeys];
406 NSArray *firstUnlockedKeysArray = [keyparms valueForKey: kKeyFirstUnlockKeys];
407 NSArray *whenUnlockedKeysArray = [keyparms valueForKey: kKeyUnlockedKeys];
408
409 if(alwaysArray)
410 [_alwaysKeys unionSet: [NSMutableSet setWithArray: alwaysArray]];
411 if(firstUnlockedKeysArray)
412 [_firstUnlockKeys unionSet: [NSMutableSet setWithArray: firstUnlockedKeysArray]];
413 if(whenUnlockedKeysArray)
414 [_unlockedKeys unionSet: [NSMutableSet setWithArray: whenUnlockedKeysArray]];
415 }
416
417 - (void)removeKeys: (NSArray*)keys forAccount: (NSString*) accountUUID
418 {
419 secdebug(XPROXYSCOPE, "removeKeys: keys: %@", keys);
420
421 // We only reset when we know the ID and they send the ID and it changes.
422 bool newAccount = accountUUID != nil && self.accountUUID != nil && ![accountUUID isEqualToString: self.accountUUID];
423
424 if(newAccount){
425 secnotice(XPROXYSCOPE, "not removing keys, account UUID is for a new account");
426 return;
427 }
428 [keys enumerateObjectsUsingBlock:^(NSString* _Nonnull key, NSUInteger idx, BOOL * _Nonnull stop) {
429 secnotice(XPROXYSCOPE, "removing from KVS store: %@", key);
430 [self.store removeObjectForKey:key];
431 }];
432 }
433
434 - (void)registerKeys: (NSDictionary*)keys forAccount: (NSString*) accountUUID
435 {
436 secdebug(XPROXYSCOPE, "registerKeys: keys: %@", keys);
437
438 // We only reset when we know the ID and they send the ID and it changes.
439 bool newAccount = accountUUID != nil && self.accountUUID != nil && ![accountUUID isEqualToString: self.accountUUID];
440
441 if (accountUUID) {
442 self.accountUUID = accountUUID;
443 }
444
445 // If we're a new account we don't exclude the old keys
446 NSMutableSet *allOldKeys = newAccount ? [NSMutableSet set] : [self copyAllKeyInterests];
447
448
449 NSDictionary *keyparms = [keys valueForKey: [NSString stringWithUTF8String: kMessageKeyParameter]];
450 NSDictionary *circles = [keys valueForKey: [NSString stringWithUTF8String: kMessageCircle]];
451 NSDictionary *messages = [keys valueForKey: [NSString stringWithUTF8String: kMessageMessage]];
452
453 _alwaysKeys = [NSMutableSet set];
454 _firstUnlockKeys = [NSMutableSet set];
455 _unlockedKeys = [NSMutableSet set];
456
457 [self registerAtTimeKeys: keyparms];
458 [self registerAtTimeKeys: circles];
459 [self registerAtTimeKeys: messages];
460
461 NSMutableSet *allNewKeys = [self copyAllKeyInterests];
462
463 // Make sure keys we no longer care about are not pending
464 [_pendingKeys intersectSet:allNewKeys];
465 if (_shadowPendingKeys) {
466 [_shadowPendingKeys intersectSet:allNewKeys];
467 }
468
469 // All new keys only is new keys (remove old keys)
470 [allNewKeys minusSet:allOldKeys];
471
472 // Mark new keys pending, they're new!
473 NSMutableSet *newKeysForCurrentLockState = [self pendKeysAndGetNewlyPended:allNewKeys];
474
475 [self persistState]; // Before we might call out, save our state so we recover if we crash
476
477 [self intersectWithCurrentLockState: newKeysForCurrentLockState];
478 // TODO: Don't processPendingKeysForCurrentLockState if none of the new keys have values.
479 if ([newKeysForCurrentLockState count] != 0) {
480 [self processPendingKeysForCurrentLockState];
481 }
482 }
483
484 // MARK: ----- Event Handling -----
485
486 - (void)_queue_handleNotification:(const char *) name
487 {
488 dispatch_assert_queue(_ckdkvsproxy_queue);
489
490 if (strcmp(name, kNotifyTokenForceUpdate)==0) {
491 // DEBUG -- Possibly remove in future
492 [self _queue_processAllItems];
493 } else if (strcmp(name, kCloudKeychainStorechangeChangeNotification)==0) {
494 // DEBUG -- Possibly remove in future
495 [self _queue_kvsStoreChange];
496 }
497 }
498
499 - (void)_queue_storeKeysChanged: (NSSet<NSString*>*) changedKeys initial: (bool) initial
500 {
501 dispatch_assert_queue(_ckdkvsproxy_queue);
502
503 // Mark that our store is talking to us, so we don't have to make up for missing anything previous.
504 _seenKVSStoreChange = YES;
505
506 // Unmark them as pending as they have just changed and we'll process them.
507 [_pendingKeys minusSet:changedKeys];
508
509 // Only send values that we're currently interested in.
510 NSSet *keysOfInterestThatChanged = [self pendKeysAndGetPendingForCurrentLockState:changedKeys];
511 NSMutableDictionary *changedValues = [self copyValues:keysOfInterestThatChanged];
512 if (initial)
513 changedValues[(__bridge NSString*)kSOSKVSInitialSyncKey] = @"true";
514
515 secnotice("event", "%@ keysChangedInCloud: %@ keysOfInterest: %@ initial: %@",
516 self,
517 [[changedKeys allObjects] componentsJoinedByString: @" "],
518 [[changedValues allKeys] componentsJoinedByString: @" "],
519 initial ? @"YES" : @"NO");
520
521 if ([changedValues count])
522 [self processKeyChangedEvent:changedValues];
523 }
524
525 - (void)_queue_storeAccountChanged
526 {
527 dispatch_assert_queue(_ckdkvsproxy_queue);
528
529 secnotice("event", "%@", self);
530
531 NSDictionary *changedValues = nil;
532 if(_dsid)
533 changedValues = @{ (__bridge NSString*)kSOSKVSAccountChangedKey: _dsid };
534 else
535 changedValues = @{ (__bridge NSString*)kSOSKVSAccountChangedKey: @"true" };
536
537 [self processKeyChangedEvent:changedValues];
538 }
539
540 - (void) doAfterFlush: (dispatch_block_t) block
541 {
542 //Flush any pending communication to Securityd.
543 if(!_inCallout)
544 dispatch_async(_calloutQueue, block);
545 else
546 _shadowFlushBlock = block;
547 }
548
549 - (void) calloutWith: (void(^)(NSSet *pending, NSSet* pendingSyncIDs, NSSet* pendingBackupSyncIDs, bool ensurePeerRegistration, dispatch_queue_t queue, void(^done)(NSSet *handledKeys, NSSet *handledSyncs, bool handledEnsurePeerRegistration, NSError* error))) callout
550 {
551 // In CKDKVSProxy's serial queue
552
553 // dispatch_get_global_queue - well-known global concurrent queue
554 // dispatch_get_main_queue - default queue that is bound to the main thread
555 xpc_transaction_begin();
556 dispatch_async(_calloutQueue, ^{
557 __block NSSet *myPending;
558 __block NSSet *mySyncPeerIDs;
559 __block NSSet *mySyncBackupPeerIDs;
560 __block bool myEnsurePeerRegistration;
561 __block bool wasLocked;
562 dispatch_sync(self->_ckdkvsproxy_queue, ^{
563 myPending = [self->_pendingKeys copy];
564 mySyncPeerIDs = [self->_pendingSyncPeerIDs copy];
565 mySyncBackupPeerIDs = [self->_pendingSyncBackupPeerIDs copy];
566
567 myEnsurePeerRegistration = self->_ensurePeerRegistration;
568 wasLocked = [self.lockMonitor locked];
569
570 self->_inCallout = YES;
571
572 self->_shadowPendingKeys = [NSMutableSet set];
573 self->_shadowPendingSyncPeerIDs = [NSMutableSet set];
574 self->_shadowPendingSyncBackupPeerIDs = [NSMutableSet set];
575 });
576
577 callout(myPending, mySyncPeerIDs, mySyncBackupPeerIDs, myEnsurePeerRegistration, self->_ckdkvsproxy_queue, ^(NSSet *handledKeys, NSSet *handledSyncs, bool handledEnsurePeerRegistration, NSError* failure) {
578 secdebug("event", "%@ %s%s before callout handled: %s%s", self,
579 ![mySyncPeerIDs isEmpty] || ![mySyncBackupPeerIDs isEmpty] ? "S" : "s",
580 myEnsurePeerRegistration ? "E" : "e",
581 ![handledKeys isEmpty] ? "S" : "s",
582 handledEnsurePeerRegistration ? "E" : "e");
583
584 // In CKDKVSProxy's serial queue
585 self->_inCallout = NO;
586
587 // Update ensurePeerRegistration
588 self->_ensurePeerRegistration = ((self->_ensurePeerRegistration && !handledEnsurePeerRegistration) || self->_shadowEnsurePeerRegistration);
589
590 self->_shadowEnsurePeerRegistration = NO;
591
592 [self handlePendingEnsurePeerRegistrationRequests:true];
593
594 bool hadShadowPeerIDs = ![self->_shadowPendingSyncPeerIDs isEmpty] || ![self->_shadowPendingSyncBackupPeerIDs isEmpty];
595
596 // Update SyncWithPeers stuff.
597 if (handledSyncs) {
598 [self->_pendingSyncPeerIDs minusSet: handledSyncs];
599 [self->_pendingSyncBackupPeerIDs minusSet: handledSyncs];
600
601 if (![handledSyncs isEmpty]) {
602 secnotice("sync-ids", "handled syncIDs: %@", [handledSyncs logIDs]);
603 secnotice("sync-ids", "remaining peerIDs: %@", [self->_pendingSyncPeerIDs logIDs]);
604 secnotice("sync-ids", "remaining backupIDs: %@", [self->_pendingSyncBackupPeerIDs logIDs]);
605
606 if (hadShadowPeerIDs) {
607 secnotice("sync-ids", "signaled peerIDs: %@", [self->_shadowPendingSyncPeerIDs logIDs]);
608 secnotice("sync-ids", "signaled backupIDs: %@", [self->_shadowPendingSyncBackupPeerIDs logIDs]);
609 }
610 }
611
612 self->_shadowPendingSyncPeerIDs = nil;
613 self->_shadowPendingSyncBackupPeerIDs = nil;
614 }
615
616
617 // Update pendingKeys and handle them
618 [self->_pendingKeys removeObject: [NSNull null]]; // Don't let NULL hang around
619
620 [self->_pendingKeys minusSet: handledKeys];
621 bool hadShadowPendingKeys = [self->_shadowPendingKeys count];
622 // Move away shadownPendingKeys first, because pendKeysAndGetPendingForCurrentLockState
623 // will look at them. See rdar://problem/20733166.
624 NSSet *oldShadowPendingKeys = self->_shadowPendingKeys;
625 self->_shadowPendingKeys = nil;
626
627 NSSet *filteredKeys = [self pendKeysAndGetPendingForCurrentLockState:oldShadowPendingKeys];
628
629 secnoticeq("keytrace", "%@ account handled: %@ pending: %@", self,
630 [[handledKeys allObjects] componentsJoinedByString: @" "],
631 [[filteredKeys allObjects] componentsJoinedByString: @" "]);
632
633 // Write state to disk
634 [self persistState];
635
636 // Handle shadow pended stuff
637
638 // We only kick off another sync if we got new stuff during handling
639 if (hadShadowPeerIDs && ![self.lockMonitor locked]) {
640 secnotice("event", "%@ syncWithPeersPending: %d inCallout: %d isLocked: %d", self, [self hasPendingSyncIDs], self->_inCallout, [self.lockMonitor locked]);
641 if ([self hasPendingSyncIDs] && !self->_inCallout && ![self.lockMonitor locked]){
642 [self doSyncWithPendingPeers];
643 }
644 }
645
646 /* We don't want to call processKeyChangedEvent if we failed to
647 handle pending keys and the device didn't unlock nor receive
648 any kvs changes while we were in our callout.
649 Doing so will lead to securityd and CloudKeychainProxy
650 talking to each other forever in a tight loop if securityd
651 repeatedly returns an error processing the same message.
652 Instead we leave any old pending keys until the next event. */
653 if (hadShadowPendingKeys || (![self.lockMonitor locked] && wasLocked)){
654 [self processKeyChangedEvent:[self copyValues:filteredKeys]];
655 if(self->_shadowFlushBlock != NULL)
656 secerror("Flush block is not null and sending new keys");
657 }
658
659 if(self->_shadowFlushBlock != NULL){
660 dispatch_async(self->_calloutQueue, self->_shadowFlushBlock);
661 self->_shadowFlushBlock = NULL;
662 }
663
664 if (failure) {
665 [self.lockMonitor recheck];
666 }
667
668 xpc_transaction_end();
669 });
670 });
671 }
672
673 - (void) sendKeysCallout: (NSSet *(^)(NSSet* pending, NSError** error)) handleKeys {
674 [self calloutWith: ^(NSSet *pending, NSSet* pendingSyncIDs, NSSet* pendingBackupSyncIDs, bool ensurePeerRegistration, dispatch_queue_t queue, void(^done)(NSSet *handledKeys, NSSet *handledSyncs, bool handledEnsurePeerRegistration, NSError* error)) {
675 NSError* error = NULL;
676
677 secnotice("CloudKeychainProxy", "send keys: %@", pending);
678 NSSet * handled = handleKeys(pending, &error);
679
680 dispatch_async(queue, ^{
681 if (!handled) {
682 secerror("%@ ensurePeerRegistration failed: %@", self, error);
683 }
684
685 done(handled, nil, NO, error);
686 });
687 }];
688 }
689
690 - (void)handlePendingEnsurePeerRegistrationRequests:(bool)onlyIfUnlocked
691 {
692 // doEnsurePeerRegistration's callback will be run on _calloutQueue, so we should check the 'are we running yet' flags on that queue
693 dispatch_async(_calloutQueue, ^{
694 if(self.ensurePeerRegistration && (!onlyIfUnlocked || ![self.lockMonitor locked])) {
695 if(self.ensurePeerRegistrationEnqueuedButNotStarted) {
696 secnotice("EnsurePeerRegistration", "%@ ensurePeerRegistration block already enqueued, not starting a new one", self);
697 return;
698 }
699
700 [self doEnsurePeerRegistration];
701 }
702 });
703 }
704
705 - (void) doEnsurePeerRegistration
706 {
707 NSObject<CKDAccount>* accountDelegate = [self account];
708 self.ensurePeerRegistrationEnqueuedButNotStarted = true;
709 [self calloutWith:^(NSSet *pending, NSSet* pendingSyncIDs, NSSet* pendingBackupSyncIDs, bool ensurePeerRegistration, dispatch_queue_t queue, void(^done)(NSSet *handledKeys, NSSet *handledSyncs, bool handledEnsurePeerRegistration, NSError* error)) {
710 NSError* error = nil;
711 self.ensurePeerRegistrationEnqueuedButNotStarted = false;
712 bool handledEnsurePeerRegistration = [accountDelegate ensurePeerRegistration:&error];
713 secnotice("EnsurePeerRegistration", "%@ ensurePeerRegistration called, %@ (%@)", self, handledEnsurePeerRegistration ? @"success" : @"failure", error);
714 if (!handledEnsurePeerRegistration) {
715 [self.lockMonitor recheck];
716 handledEnsurePeerRegistration = ![self.lockMonitor locked]; // If we're unlocked we handled it, if we're locked we didn't.
717 // This means we get to fail once per unlock and then cut that spinning out.
718 }
719 dispatch_async(queue, ^{
720 done(nil, nil, handledEnsurePeerRegistration, error);
721 });
722 }];
723 }
724
725 - (void) doSyncWithPendingPeers
726 {
727 NSObject<CKDAccount>* accountDelegate = [self account];
728 [self calloutWith:^(NSSet *pending, NSSet* pendingSyncIDs, NSSet* pendingBackupSyncIDs, bool ensurePeerRegistration, dispatch_queue_t queue, void(^done)(NSSet *handledKeys, NSSet *handledSyncs, bool handledEnsurePeerRegistration, NSError* error)) {
729 NSError* error = NULL;
730 secnotice("syncwith", "%@ syncwith peers: %@", self, [[pendingSyncIDs allObjects] componentsJoinedByString:@" "]);
731 secnotice("syncwith", "%@ syncwith backups: %@", self, [[pendingBackupSyncIDs allObjects] componentsJoinedByString:@" "]);
732 NSSet<NSString*>* handled = [accountDelegate syncWithPeers:pendingSyncIDs backups:pendingBackupSyncIDs error:&error];
733 secnotice("syncwith", "%@ syncwith handled: %@", self, [[handled allObjects] componentsJoinedByString:@" "]);
734 dispatch_async(queue, ^{
735 if (!handled) {
736 // We might be confused about lock state
737 [self.lockMonitor recheck];
738 }
739
740 done(nil, handled, false, error);
741 });
742 }];
743 }
744
745 - (void) doSyncWithAllPeers
746 {
747 NSObject<CKDAccount>* accountDelegate = [self account];
748 [self calloutWith:^(NSSet *pending, NSSet* pendingSyncIDs, NSSet* pendingBackupSyncIDs, bool ensurePeerRegistration, dispatch_queue_t queue, void(^done)(NSSet *handledKeys, NSSet *handledSyncs, bool handledEnsurePeerRegistration, NSError*error)) {
749 NSError* error = NULL;
750 bool handled = [accountDelegate syncWithAllPeers:&error];
751 if (!handled) {
752 secerror("Failed to syncWithAllPeers: %@", error);
753 }
754 dispatch_async(queue, ^{
755 done(nil, nil, false, error);
756 });
757 }];
758 }
759
760 - (bool)hasPendingNonShadowSyncIDs {
761 return ![_pendingSyncPeerIDs isEmpty] || ![_pendingSyncBackupPeerIDs isEmpty];
762 }
763
764 - (bool)hasPendingShadowSyncIDs {
765 return (_shadowPendingSyncPeerIDs && ![_shadowPendingSyncPeerIDs isEmpty]) ||
766 (_shadowPendingSyncBackupPeerIDs && ![_shadowPendingSyncBackupPeerIDs isEmpty]);
767 }
768
769 - (bool)hasPendingSyncIDs
770 {
771 bool pendingIDs = [self hasPendingNonShadowSyncIDs];
772
773 if (_inCallout) {
774 pendingIDs |= [self hasPendingShadowSyncIDs];
775 }
776
777 return pendingIDs;
778 }
779
780 - (void)requestSyncWithPeerIDs: (NSArray<NSString*>*) peerIDs backupPeerIDs: (NSArray<NSString*>*) backupPeerIDs
781 {
782 if ([peerIDs count] == 0 && [backupPeerIDs count] == 0)
783 return; // Nothing to do;
784
785 NSSet<NSString*>* peerIDsSet = [NSSet setWithArray: peerIDs];
786 NSSet<NSString*>* backupPeerIDsSet = [NSSet setWithArray: backupPeerIDs];
787
788 [_pendingSyncPeerIDs unionSet: peerIDsSet];
789 [_pendingSyncBackupPeerIDs unionSet: backupPeerIDsSet];
790
791 if (_inCallout) {
792 [_shadowPendingSyncPeerIDs unionSet: peerIDsSet];
793 [_shadowPendingSyncBackupPeerIDs unionSet: backupPeerIDsSet];
794 }
795
796 [self persistState];
797
798 [self handlePendingEnsurePeerRegistrationRequests:true];
799
800 if ([self hasPendingSyncIDs] && !_inCallout && ![self.lockMonitor locked]){
801 [self doSyncWithPendingPeers];
802 }
803 }
804
805 - (BOOL)hasSyncPendingFor: (NSString*) peerID {
806 return [_pendingSyncPeerIDs containsObject: peerID] ||
807 (_shadowPendingSyncPeerIDs && [_shadowPendingSyncPeerIDs containsObject: peerID]);
808 }
809
810 - (BOOL)hasPendingKey: (NSString*) keyName {
811 return [self.pendingKeys containsObject: keyName]
812 || (_shadowPendingKeys && [self.shadowPendingKeys containsObject: keyName]);
813 }
814
815 - (void)requestEnsurePeerRegistration
816 {
817 #if !defined(NDEBUG)
818 NSString *desc = [self description];
819 #endif
820
821 if (_inCallout) {
822 _shadowEnsurePeerRegistration = YES;
823 } else {
824 _ensurePeerRegistration = YES;
825 [self handlePendingEnsurePeerRegistrationRequests:true];
826 [self persistState];
827 }
828
829 secdebug("event", "%@ %@", desc, self);
830 }
831
832 - (void)_queue_locked
833 {
834 dispatch_assert_queue(_ckdkvsproxy_queue);
835
836 secnotice("event", "%@ Locked", self);
837 }
838
839 - (void)_queue_unlocked
840 {
841 dispatch_assert_queue(_ckdkvsproxy_queue);
842
843 secnotice("event", "%@ Unlocked", self);
844 [self handlePendingEnsurePeerRegistrationRequests:false];
845
846 // First send changed keys to securityd so it can proccess updates
847 [self processPendingKeysForCurrentLockState];
848
849 // Then, tickle securityd to perform a sync if needed.
850 if ([self hasPendingSyncIDs]) {
851 [self doSyncWithPendingPeers];
852 }
853 }
854
855 - (void) _queue_kvsStoreChange {
856 dispatch_assert_queue(_ckdkvsproxy_queue);
857
858 os_activity_initiate("kvsStoreChange", OS_ACTIVITY_FLAG_DEFAULT, ^{
859 if (!self->_seenKVSStoreChange) {
860 secnotice("event", "%@ received darwin notification before first NSNotification", self);
861 // TODO This might not be needed if we always get the NSNotification
862 // deleived even if we were launched due to a kvsStoreChange
863 // Send all keys for current lock state to securityd so it can proccess them
864 [self pendKeysAndGetNewlyPended: [self copyAllKeyInterests]];
865 [self processPendingKeysForCurrentLockState];
866 } else {
867 secdebug("event", "%@ ignored, waiting for NSNotification", self);
868 }
869 });
870 }
871
872 #pragma mark -
873 #pragma mark XPCNotificationListener
874
875 - (void)handleNotification:(const char *) name
876 {
877 // sync because we cannot ensure the lifetime of name
878 dispatch_sync(_ckdkvsproxy_queue, ^{
879 [self _queue_handleNotification:name];
880 });
881 }
882
883 #pragma mark -
884 #pragma mark Calls from -[CKDKVSStore kvsStoreChanged:]
885
886 - (void)storeKeysChanged: (NSSet<NSString*>*) changedKeys initial: (bool) initial
887 {
888 // sync, caller must wait to ensure correct state
889 dispatch_sync(_ckdkvsproxy_queue, ^{
890 [self _queue_storeKeysChanged:changedKeys initial:initial];
891 });
892 }
893
894 - (void)storeAccountChanged
895 {
896 // sync, caller must wait to ensure correct state
897 dispatch_sync(_ckdkvsproxy_queue, ^{
898 [self _queue_storeAccountChanged];
899 });
900 }
901
902 #pragma mark -
903 #pragma mark CKDLockListener
904
905 - (void) locked
906 {
907 // sync, otherwise tests fail
908 dispatch_sync(_ckdkvsproxy_queue, ^{
909 [self _queue_locked];
910 });
911 }
912
913 - (void) unlocked
914 {
915 // sync, otherwise tests fail
916 dispatch_sync(_ckdkvsproxy_queue, ^{
917 [self _queue_unlocked];
918 });
919 }
920
921 //
922 // MARK: ----- Key Filtering -----
923 //
924
925 - (NSSet*) keysForCurrentLockState
926 {
927 secdebug("filtering", "%@ Filtering: unlockedSinceBoot: %d\n unlocked: %d\n, keysOfInterest: <%@>", self, (int) [self.lockMonitor unlockedSinceBoot], (int) ![self.lockMonitor locked], [self.persistentData compactDescription]);
928
929 NSMutableSet *currentStateKeys = [NSMutableSet setWithSet: _alwaysKeys];
930 if ([self.lockMonitor unlockedSinceBoot])
931 [currentStateKeys unionSet: _firstUnlockKeys];
932
933 if (![self.lockMonitor locked])
934 [currentStateKeys unionSet: _unlockedKeys];
935
936 return currentStateKeys;
937 }
938
939
940 - (NSMutableSet*) pendKeysAndGetNewlyPended: (NSSet*) keysToPend
941 {
942 NSMutableSet *filteredKeysToPend = [self copyAllKeyInterests];
943 [filteredKeysToPend intersectSet: keysToPend];
944
945 NSMutableSet *newlyPendedKeys = [filteredKeysToPend mutableCopy];
946 [newlyPendedKeys minusSet: _pendingKeys];
947 if (_shadowPendingKeys) {
948 [newlyPendedKeys minusSet: _shadowPendingKeys];
949 }
950
951 if (_shadowPendingKeys) {
952 [_shadowPendingKeys unionSet:filteredKeysToPend];
953 }
954 else{
955 [_pendingKeys unionSet:filteredKeysToPend];
956 }
957
958 return newlyPendedKeys;
959 }
960
961 - (void) intersectWithCurrentLockState: (NSMutableSet*) set
962 {
963 [set intersectSet: [self keysForCurrentLockState]];
964 }
965
966 - (NSMutableSet*) pendingKeysForCurrentLockState
967 {
968 NSMutableSet * result = [_pendingKeys mutableCopy];
969 [self intersectWithCurrentLockState:result];
970 return result;
971 }
972
973 - (NSMutableSet*) pendKeysAndGetPendingForCurrentLockState: (NSSet*) startingSet
974 {
975 [self pendKeysAndGetNewlyPended: startingSet];
976
977 return [self pendingKeysForCurrentLockState];
978 }
979
980 - (NSMutableDictionary *)copyValues:(NSSet*)keysOfInterest
981 {
982 // Grab values from store.
983 NSObject<CKDStore> *store = [self store];
984 NSMutableDictionary *changedValues = [NSMutableDictionary dictionaryWithCapacity:0];
985 [keysOfInterest enumerateObjectsUsingBlock:^(id obj, BOOL *stop)
986 {
987 NSString* key = (NSString*) obj;
988 id objval = [store objectForKey:key];
989 if (!objval) objval = [NSNull null];
990
991 [changedValues setObject:objval forKey:key];
992 secdebug(XPROXYSCOPE, "%@ storeChanged updated value for %@", self, key);
993 }];
994 return changedValues;
995 }
996
997 /*
998 During RegisterKeys, separate keys-of-interest into three disjoint sets:
999 - keys that we always want to be notified about; this means we can get the
1000 value at any time
1001 - keys that require the device to have been unlocked at least once
1002 - keys that require the device to be unlocked now
1003
1004 Typically, the sets of keys will be:
1005
1006 - Dk: alwaysKeys
1007 - Ck: firstUnlock
1008 - Ak: unlocked
1009
1010 The caller is responsible for making sure that the keys in e.g. alwaysKeys are
1011 values that can be handled at any time (that is, not when unlocked)
1012
1013 Each time we get a notification from ubiquity that keys have changed, we need to
1014 see if anything of interest changed. If we don't care, then done.
1015
1016 For each key-of-interest that changed, we either notify the client that things
1017 changed, or add it to a pendingNotifications list. If the notification to the
1018 client fails, also add it to the pendingNotifications list. This pending list
1019 should be written to persistent storage and consulted any time we either get an
1020 item changed notification, or get a stream event signalling a change in lock state.
1021
1022 We can notify the client either through XPC if a connection is set up, or call a
1023 routine in securityd to launch it.
1024
1025 */
1026
1027 - (void)processKeyChangedEvent:(NSDictionary *)changedValues
1028 {
1029 NSMutableDictionary* filtered = [NSMutableDictionary dictionary];
1030
1031 secnotice("processKeyChangedEvent", "changedValues:%@", changedValues);
1032 NSMutableArray* nullKeys = [NSMutableArray array];
1033 // Remove nulls because we don't want them in securityd.
1034 [changedValues enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
1035 if (obj == [NSNull null]){
1036 [nullKeys addObject:key];
1037 }else{
1038 filtered[key] = obj;
1039 }
1040 }];
1041 if ([nullKeys count])
1042 [_pendingKeys minusSet: [NSSet setWithArray: nullKeys]];
1043
1044 if([filtered count] != 0 ) {
1045 [self sendKeysCallout:^NSSet *(NSSet *pending, NSError** error) {
1046 secnotice("processing keys", "pending:%@", pending);
1047 NSError *updateError = nil;
1048 return [[self account] keysChanged: filtered error: &updateError];
1049 }];
1050 } else {
1051 secnoticeq("keytrace", "%@ null: %@ pending: %@", self,
1052 [nullKeys componentsJoinedByString: @" "],
1053 [[_pendingKeys allObjects] componentsJoinedByString: @" "]);
1054 }
1055 }
1056
1057 - (void) processPendingKeysForCurrentLockState
1058 {
1059 [self processKeyChangedEvent: [self copyValues: [self pendingKeysForCurrentLockState]]];
1060 }
1061
1062 @end
1063
1064