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