]> git.saurik.com Git - apple/security.git/blob - keychain/ckks/CKKSViewManager.m
Security-59754.41.1.tar.gz
[apple/security.git] / keychain / ckks / CKKSViewManager.m
1 /*
2 * Copyright (c) 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 #import <os/feature_private.h>
25
26 #import "keychain/ckks/CKKSAccountStateTracker.h"
27 #import "keychain/ckks/CKKSViewManager.h"
28 #import "keychain/ckks/CKKSKeychainView.h"
29 #import "keychain/ckks/CKKSSynchronizeOperation.h"
30 #import "keychain/ckks/CKKSKey.h"
31 #import "keychain/ckks/CKKSZoneStateEntry.h"
32 #import "keychain/ckks/CKKSNearFutureScheduler.h"
33 #import "keychain/ckks/CKKSNotifier.h"
34 #import "keychain/ckks/CKKSCondition.h"
35 #import "keychain/ckks/CKKSListenerCollection.h"
36 #import "keychain/ckks/CloudKitCategories.h"
37 #import "keychain/ckks/OctagonAPSReceiver.h"
38 #import "keychain/categories/NSError+UsefulConstructors.h"
39 #import "keychain/analytics/SecEventMetric.h"
40 #import "keychain/analytics/SecMetrics.h"
41
42 #import "keychain/ot/OTManager.h"
43 #import "keychain/ot/OTDefines.h"
44 #import "keychain/ot/OTConstants.h"
45 #import "keychain/ot/ObjCImprovements.h"
46
47 #import "keychain/trust/TrustedPeers/TPSyncingPolicy.h"
48
49 #import "SecEntitlements.h"
50
51 #include "keychain/securityd/SecDbItem.h"
52 #include "keychain/securityd/SecDbKeychainItem.h"
53 #include "keychain/securityd/SecItemSchema.h"
54 #include <Security/SecureObjectSync/SOSViews.h>
55
56 #import <Foundation/NSXPCConnection.h>
57 #import <Foundation/NSXPCConnection_Private.h>
58
59 #include "keychain/SecureObjectSync/SOSAccount.h"
60 #include <Security/SecItemBackup.h>
61
62 #if OCTAGON
63 #import <CloudKit/CloudKit.h>
64 #import <CloudKit/CloudKit_Private.h>
65
66 #import <SecurityFoundation/SFKey.h>
67 #import <SecurityFoundation/SFKey_Private.h>
68
69 #import "CKKSAnalytics.h"
70 #endif
71
72 #if !OCTAGON
73 @interface CKKSViewManager () <NSXPCListenerDelegate>
74 #else
75 @interface CKKSViewManager () <NSXPCListenerDelegate,
76 CKKSCloudKitAccountStateListener>
77
78 @property NSXPCListener *listener;
79
80 @property (nullable) NSSet<NSString*>* viewAllowList;
81
82 // Once you set these, all CKKSKeychainViews created will use them
83 @property CKKSCloudKitClassDependencies* cloudKitClassDependencies;
84
85 @property NSMutableDictionary<NSString*, SecBoolNSErrorCallback>* pendingSyncCallbacks;
86 @property CKKSNearFutureScheduler* savedTLKNotifier;;
87 @property NSOperationQueue* operationQueue;
88
89 @property CKKSListenerCollection<id<CKKSPeerUpdateListener>>* peerChangeListenerCollection;
90
91 @property (nonatomic) BOOL overrideCKKSViewsFromPolicy;
92 @property (nonatomic) BOOL valueCKKSViewsFromPolicy;
93 @property (nonatomic) BOOL startCKOperationAtViewCreation;
94
95 @property BOOL itemModificationsBeforePolicyLoaded;
96
97 // Make writable
98 @property (nullable) TPSyncingPolicy* policy;
99 @property CKKSCondition* policyLoaded;
100
101 #endif
102 @end
103
104 #if OCTAGON
105 @interface CKKSViewManager (lockstateTracker) <CKKSLockStateNotification>
106 @end
107 #endif
108
109 @implementation CKKSViewManager
110 #if OCTAGON
111
112 - (instancetype)initWithContainer:(CKContainer*)container
113 sosAdapter:(id<OTSOSAdapter> _Nullable)sosAdapter
114 accountStateTracker:(CKKSAccountStateTracker*)accountTracker
115 lockStateTracker:(CKKSLockStateTracker*)lockStateTracker
116 cloudKitClassDependencies:(CKKSCloudKitClassDependencies*)cloudKitClassDependencies
117 {
118 if(self = [super init]) {
119 _cloudKitClassDependencies = cloudKitClassDependencies;
120 _sosPeerAdapter = sosAdapter;
121
122 _viewAllowList = nil;
123 _container = container;
124 _accountTracker = accountTracker;
125 _lockStateTracker = lockStateTracker;
126 [_lockStateTracker addLockStateObserver:self];
127 _reachabilityTracker = [[CKKSReachabilityTracker alloc] init];
128 _itemModificationsBeforePolicyLoaded = NO;
129
130 _zoneChangeFetcher = [[CKKSZoneChangeFetcher alloc] initWithContainer:_container
131 fetchClass:cloudKitClassDependencies.fetchRecordZoneChangesOperationClass
132 reachabilityTracker:_reachabilityTracker];
133 OctagonAPSReceiver* globalAPSReceiver = [OctagonAPSReceiver receiverForNamedDelegatePort:SecCKKSAPSNamedPort
134 apsConnectionClass:cloudKitClassDependencies.apsConnectionClass];
135 [globalAPSReceiver registerCKKSReceiver:_zoneChangeFetcher];
136
137 _zoneModifier = [[CKKSZoneModifier alloc] initWithContainer:_container
138 reachabilityTracker:_reachabilityTracker
139 cloudkitDependencies:cloudKitClassDependencies];
140
141 _operationQueue = [[NSOperationQueue alloc] init];
142
143 _peerChangeListenerCollection = [[CKKSListenerCollection alloc] initWithName:@"sos-peer-set"];
144
145 _views = [[NSMutableDictionary alloc] init];
146 _pendingSyncCallbacks = [[NSMutableDictionary alloc] init];
147
148 _startCKOperationAtViewCreation = NO;
149
150 _completedSecCKKSInitialize = [[CKKSCondition alloc] init];
151
152 WEAKIFY(self);
153 _savedTLKNotifier = [[CKKSNearFutureScheduler alloc] initWithName:@"newtlks"
154 delay:5*NSEC_PER_SEC
155 keepProcessAlive:true
156 dependencyDescriptionCode:CKKSResultDescriptionNone
157 block:^{
158 STRONGIFY(self);
159 [self notifyNewTLKsInKeychain];
160 }];
161
162 _policy = nil;
163 _policyLoaded = [[CKKSCondition alloc] init];
164
165 _listener = [NSXPCListener anonymousListener];
166 _listener.delegate = self;
167 [_listener resume];
168
169 // Start listening for CK account status (for sync callbacks)
170 [_accountTracker registerForNotificationsOfCloudKitAccountStatusChange:self];
171 }
172 return self;
173 }
174
175 + (CKContainer*)makeCKContainer:(NSString*)containerName
176 usePCS:(bool)usePCS
177 {
178 CKContainer* container = [CKContainer containerWithIdentifier:containerName];
179 if(!usePCS) {
180 CKContainerOptions* containerOptions = [[CKContainerOptions alloc] init];
181 containerOptions.bypassPCSEncryption = YES;
182
183 // We don't have a great way to set these, so replace the entire container object
184 container = [[CKContainer alloc] initWithContainerID: container.containerID options:containerOptions];
185 }
186 return container;
187 }
188
189 - (BOOL)waitForTrustReady {
190 static dispatch_once_t onceToken;
191 __block BOOL success = YES;
192 dispatch_once(&onceToken, ^{
193 OTManager* manager = [OTManager manager];
194 if (![manager waitForReady:OTCKContainerName context:OTDefaultContext wait:3*NSEC_PER_SEC]) {
195 success = NO;
196 }
197 });
198 return success;
199 }
200
201 - (void)setupAnalytics
202 {
203 WEAKIFY(self);
204
205 // Tests shouldn't continue here; it leads to entitlement crashes with CloudKit if the mocks aren't enabled when this function runs
206 if(SecCKKSTestsEnabled()) {
207 return;
208 }
209
210 [[CKKSAnalytics logger] AddMultiSamplerForName:@"CKKS-healthSummary" withTimeInterval:SFAnalyticsSamplerIntervalOncePerReport block:^NSDictionary<NSString *,NSNumber *> *{
211 STRONGIFY(self);
212 if(!self) {
213 return nil;
214 }
215
216 NSError* sosCircleError = nil;
217 SOSCCStatus sosStatus = [self.sosPeerAdapter circleStatus:&sosCircleError];
218 if(sosCircleError) {
219 ckkserror_global("manager", " couldn't fetch sos status for SF report: %@", sosCircleError);
220 }
221
222 NSMutableDictionary* values = [NSMutableDictionary dictionary];
223 BOOL inCircle = (sosStatus == kSOSCCInCircle);
224 if (inCircle) {
225 [[CKKSAnalytics logger] setDateProperty:[NSDate date] forKey:CKKSAnalyticsLastInCircle];
226 }
227 values[CKKSAnalyticsInCircle] = @(inCircle);
228
229 BOOL validCredentials = self.accountTracker.currentCKAccountInfo.hasValidCredentials;
230 if (!validCredentials) {
231 values[CKKSAnalyticsValidCredentials] = @(validCredentials);
232 }
233
234 NSArray<NSString *>* keys = @[ CKKSAnalyticsLastUnlock, CKKSAnalyticsLastInCircle];
235 for (NSString * key in keys) {
236 NSDate *date = [[CKKSAnalytics logger] datePropertyForKey:key];
237 values[key] = @([CKKSAnalytics fuzzyDaysSinceDate:date]);
238 }
239 return values;
240 }];
241
242 for (NSString* viewName in [self viewList]) {
243 [[CKKSAnalytics logger] AddMultiSamplerForName:[NSString stringWithFormat:@"CKKS-%@-healthSummary", viewName] withTimeInterval:SFAnalyticsSamplerIntervalOncePerReport block:^NSDictionary<NSString *,NSNumber *> *{
244 STRONGIFY(self);
245 if(!self) {
246 return nil;
247 }
248
249 NSError* sosCircleError = nil;
250 SOSCCStatus sosStatus = [self.sosPeerAdapter circleStatus:&sosCircleError];
251 if(sosCircleError) {
252 ckkserror_global("manager", " couldn't fetch sos status for SF report: %@", sosCircleError);
253 }
254 BOOL inCircle = (sosStatus == kSOSCCInCircle);
255 NSMutableDictionary* values = [NSMutableDictionary dictionary];
256 CKKSKeychainView* view = [self findOrCreateView:viewName];
257 NSDate* dateOfLastSyncClassA = [[CKKSAnalytics logger] dateOfLastSuccessForEvent:CKKSEventProcessIncomingQueueClassA zoneName:view.zoneName];
258 NSDate* dateOfLastSyncClassC = [[CKKSAnalytics logger] dateOfLastSuccessForEvent:CKKSEventProcessIncomingQueueClassC zoneName:view.zoneName];
259 NSDate* dateOfLastKSR = [[CKKSAnalytics logger] datePropertyForKey:CKKSAnalyticsLastKeystateReady zoneName:view.zoneName];
260
261 NSInteger fuzzyDaysSinceClassASync = [CKKSAnalytics fuzzyDaysSinceDate:dateOfLastSyncClassA];
262 NSInteger fuzzyDaysSinceClassCSync = [CKKSAnalytics fuzzyDaysSinceDate:dateOfLastSyncClassC];
263 NSInteger fuzzyDaysSinceKSR = [CKKSAnalytics fuzzyDaysSinceDate:dateOfLastKSR];
264 [values setValue:@(fuzzyDaysSinceClassASync) forKey:[NSString stringWithFormat:@"%@-daysSinceClassASync", viewName]];
265 [values setValue:@(fuzzyDaysSinceClassCSync) forKey:[NSString stringWithFormat:@"%@-daysSinceClassCSync", viewName]];
266 [values setValue:@(fuzzyDaysSinceKSR) forKey:[NSString stringWithFormat:@"%@-daysSinceLastKeystateReady", viewName]];
267
268 BOOL hasTLKs = [view.stateMachine.currentState isEqualToString:SecCKKSZoneKeyStateReady] || [view.stateMachine.currentState isEqualToString:SecCKKSZoneKeyStateReadyPendingUnlock];
269 /* only synced recently if between [0...7, ie within 7 days */
270 BOOL syncedClassARecently = fuzzyDaysSinceClassASync >= 0 && fuzzyDaysSinceClassASync < 7;
271 BOOL syncedClassCRecently = fuzzyDaysSinceClassCSync >= 0 && fuzzyDaysSinceClassCSync < 7;
272 BOOL incomingQueueIsErrorFree = view.lastIncomingQueueOperation.error == nil;
273 BOOL outgoingQueueIsErrorFree = view.lastOutgoingQueueOperation.error == nil;
274
275 NSString* hasTLKsKey = [NSString stringWithFormat:@"%@-%@", viewName, CKKSAnalyticsHasTLKs];
276 NSString* syncedClassARecentlyKey = [NSString stringWithFormat:@"%@-%@", viewName, CKKSAnalyticsSyncedClassARecently];
277 NSString* syncedClassCRecentlyKey = [NSString stringWithFormat:@"%@-%@", viewName, CKKSAnalyticsSyncedClassCRecently];
278 NSString* incomingQueueIsErrorFreeKey = [NSString stringWithFormat:@"%@-%@", viewName, CKKSAnalyticsIncomingQueueIsErrorFree];
279 NSString* outgoingQueueIsErrorFreeKey = [NSString stringWithFormat:@"%@-%@", viewName, CKKSAnalyticsOutgoingQueueIsErrorFree];
280
281 values[hasTLKsKey] = @(hasTLKs);
282 values[syncedClassARecentlyKey] = @(syncedClassARecently);
283 values[syncedClassCRecentlyKey] = @(syncedClassCRecently);
284 values[incomingQueueIsErrorFreeKey] = @(incomingQueueIsErrorFree);
285 values[outgoingQueueIsErrorFreeKey] = @(outgoingQueueIsErrorFree);
286
287 BOOL weThinkWeAreInSync = inCircle && hasTLKs && syncedClassARecently && syncedClassCRecently && incomingQueueIsErrorFree && outgoingQueueIsErrorFree;
288 NSString* inSyncKey = [NSString stringWithFormat:@"%@-%@", viewName, CKKSAnalyticsInSync];
289 values[inSyncKey] = @(weThinkWeAreInSync);
290
291 return values;
292 }];
293 }
294 }
295
296 -(void)dealloc {
297 [self clearAllViews];
298 }
299
300 dispatch_queue_t globalZoneStateQueue = NULL;
301 dispatch_once_t globalZoneStateQueueOnce;
302
303 // We can't load the rate limiter in an init method, as the method might end up calling itself (if the database layer isn't yet initialized).
304 // Lazy-load it here.
305 - (CKKSRateLimiter*)getGlobalRateLimiter {
306 dispatch_once(&globalZoneStateQueueOnce, ^{
307 globalZoneStateQueue = dispatch_queue_create("CKKS global zone state", DISPATCH_QUEUE_SERIAL_WITH_AUTORELEASE_POOL);
308 });
309
310 if(_globalRateLimiter != nil) {
311 return _globalRateLimiter;
312 }
313
314 __block CKKSRateLimiter* blocklimit = nil;
315
316 dispatch_sync(globalZoneStateQueue, ^{
317 NSError* error = nil;
318
319 // Special object containing state for all zones. Currently, just the rate limiter.
320 CKKSZoneStateEntry* allEntry = [CKKSZoneStateEntry tryFromDatabase: @"all" error:&error];
321
322 if(error) {
323 ckkserror_global("manager", " couldn't load global zone state: %@", error);
324 }
325
326 if(!error && allEntry.rateLimiter) {
327 blocklimit = allEntry.rateLimiter;
328 } else {
329 blocklimit = [[CKKSRateLimiter alloc] init];
330 }
331 });
332 _globalRateLimiter = blocklimit;
333 return _globalRateLimiter;
334 }
335
336 - (void)lockStateChangeNotification:(bool)unlocked
337 {
338 if (unlocked) {
339 [[CKKSAnalytics logger] setDateProperty:[NSDate date] forKey:CKKSAnalyticsLastUnlock];
340 }
341 }
342
343 #pragma mark - View List handling
344
345 - (NSSet<NSString*>*)defaultViewList {
346 NSSet<NSString*>* fullList = [OTSOSActualAdapter sosCKKSViewList];
347
348 // Not a great hack: if this platform is an aTV or a HomePod, then filter its list of views
349 bool filter = false;
350
351 #if TARGET_OS_TV
352 filter = true;
353 #elif TARGET_OS_WATCH
354 filter = false;
355 #elif TARGET_OS_IOS
356 filter = !OctagonPlatformSupportsSOS();
357 #elif TARGET_OS_OSX
358 filter = false;
359 #endif
360
361 if(filter) {
362 // For now, use a hardcoded allow list for TV/HomePod
363 NSMutableSet<NSString*>* allowList = [NSMutableSet setWithArray:@[@"Home", @"LimitedPeersAllowed"]];
364 [allowList intersectSet:fullList];
365 return allowList;
366 }
367
368 return fullList;
369 }
370
371 - (NSSet<NSString*>*)viewList {
372 return [self.views.allKeys copy];
373 }
374
375 - (BOOL)setCurrentSyncingPolicy:(TPSyncingPolicy* _Nullable)syncingPolicy
376 {
377 return [self setCurrentSyncingPolicy:syncingPolicy policyIsFresh:NO];
378 }
379
380 - (BOOL)setCurrentSyncingPolicy:(TPSyncingPolicy* _Nullable)syncingPolicy policyIsFresh:(BOOL)policyIsFresh
381 {
382 if(syncingPolicy == nil) {
383 ckksnotice_global("ckks-policy", "Nil syncing policy presented; ignoring");
384 return NO;
385 }
386
387 NSSet<NSString*>* viewNames = syncingPolicy.viewList;
388 ckksnotice_global("ckks-policy", "New syncing policy: %@ views: %@", syncingPolicy, viewNames);
389
390 if(![self useCKKSViewsFromPolicy]) {
391 // Thanks, but no thanks.
392 viewNames = [self defaultViewList];
393 ckksnotice_global("ckks-policy", "Reverting to default view list: %@", viewNames);
394 }
395
396 if(self.viewAllowList) {
397 ckksnotice_global("ckks-policy", "Intersecting view list with allow list: %@", self.viewAllowList);
398 NSMutableSet<NSString*>* set = [viewNames mutableCopy];
399 [set intersectSet:self.viewAllowList];
400
401 viewNames = set;
402 ckksnotice_global("ckks-policy", "Final list: %@", viewNames);
403 }
404
405 // We need to not be synchronized on self.views before issuing commands to CKKS views.
406 // So, store the pointers for use after the critical section.
407 NSArray<CKKSKeychainView*>* activeViews = nil;
408 BOOL scanAll = NO;
409 BOOL viewsChanged = NO;
410
411 @synchronized(self.views) {
412 self.policy = syncingPolicy;
413
414 NSArray* previousViewNames = [self.views.allKeys copy];
415
416 // First, shut down any views that are no longer in the set
417 for(NSString* viewName in previousViewNames) {
418 if(![viewNames containsObject:viewName]) {
419 ckksnotice_global("ckks-policy", "Stopping old view %@", viewName);
420 [self clearView:viewName];
421 viewsChanged = YES;
422 }
423 }
424
425 for(NSString* viewName in viewNames) {
426 CKKSKeychainView* view = nil;
427
428 if([previousViewNames containsObject:viewName]) {
429 view = [self findView:viewName];
430 ckksinfo_global("ckks-policy", "Already have view %@", view);
431
432 } else {
433 view = [self findOrCreateView:viewName];
434 ckksnotice_global("ckks-policy", "Created new view %@", view);
435 viewsChanged = YES;
436 }
437 }
438
439 activeViews = [self.views.allValues copy];
440
441 if(self.itemModificationsBeforePolicyLoaded) {
442 ckksnotice_global("ckks-policy", "Issuing scan suggestions to handle missed items");
443 scanAll = YES;
444 self.itemModificationsBeforePolicyLoaded = NO;
445 }
446 }
447
448 for(CKKSKeychainView* view in activeViews) {
449 [view setCurrentSyncingPolicy:self.policy policyIsFresh:policyIsFresh];
450
451 if(scanAll) {
452 [view scanLocalItems:@"item-added-before-policy"];
453 }
454 }
455
456 // The policy is considered loaded once the views have been created
457 [self.policyLoaded fulfill];
458 return viewsChanged;
459 }
460
461 - (void)setSyncingViewsAllowList:(NSSet<NSString*>*)viewNames
462 {
463 self.viewAllowList = viewNames;
464 }
465
466 - (void)resetSyncingPolicy
467 {
468 ckksnotice_global("ckks-policy", "Setting policy to nil");
469 self.policy = nil;
470 self.policyLoaded = [[CKKSCondition alloc] init];
471
472 self.startCKOperationAtViewCreation = NO;
473 }
474
475 #pragma mark - View Handling
476
477 - (void)setView: (CKKSKeychainView*) obj {
478 CKKSKeychainView* kcv = nil;
479
480 @synchronized(self.views) {
481 kcv = self.views[obj.zoneName];
482 self.views[obj.zoneName] = obj;
483 }
484
485 if(kcv) {
486 [kcv cancelAllOperations];
487 }
488 }
489
490 - (void)clearAllViews {
491 NSArray<CKKSKeychainView*>* tempviews = nil;
492 @synchronized(self.views) {
493 tempviews = [self.views.allValues copy];
494 [self.views removeAllObjects];
495
496 self.startCKOperationAtViewCreation = NO;
497 }
498
499 for(CKKSKeychainView* view in tempviews) {
500 [view cancelAllOperations];
501 }
502 }
503
504 - (void)clearView:(NSString*) viewName {
505 CKKSKeychainView* kcv = nil;
506 @synchronized(self.views) {
507 kcv = self.views[viewName];
508 self.views[viewName] = nil;
509 }
510
511 if(kcv) {
512 [kcv cancelAllOperations];
513 }
514 }
515
516 - (CKKSKeychainView*)findView:(NSString*)viewName {
517 if(!viewName) {
518 return nil;
519 }
520
521 @synchronized(self.views) {
522 return self.views[viewName];
523 }
524 }
525
526 - (CKKSKeychainView* _Nullable)findView:(NSString*)viewName error:(NSError**)error
527 {
528 if([self.policyLoaded wait:5*NSEC_PER_SEC] != 0) {
529 ckkserror_global("ckks", "Haven't yet received a syncing policy; expect failure finding views");
530
531 if([self useCKKSViewsFromPolicy]) {
532 if(error) {
533 *error = [NSError errorWithDomain:CKKSErrorDomain
534 code:CKKSErrorPolicyNotLoaded
535 userInfo:@{NSLocalizedDescriptionKey: [NSString stringWithFormat: @"CKKS syncing policy not yet loaded; cannot find view '%@'", viewName]}];
536
537 }
538 return nil;
539 }
540 }
541
542 @synchronized(self.views) {
543 CKKSKeychainView* view = self.views[viewName];
544 if(!view) {
545 if(error) {
546 *error = [NSError errorWithDomain:CKKSErrorDomain
547 code:CKKSNoSuchView
548 userInfo:@{NSLocalizedDescriptionKey: [NSString stringWithFormat: @"No syncing view for '%@'", viewName]}];
549 }
550 return nil;
551 }
552
553 return view;
554 }
555 }
556
557 - (CKKSKeychainView*)findOrCreateView:(NSString*)viewName {
558 @synchronized(self.views) {
559 CKKSKeychainView* kcv = self.views[viewName];
560 if(kcv) {
561 return kcv;
562 }
563
564 self.views[viewName] = [[CKKSKeychainView alloc] initWithContainer: self.container
565 zoneName: viewName
566 accountTracker: self.accountTracker
567 lockStateTracker: self.lockStateTracker
568 reachabilityTracker: self.reachabilityTracker
569 changeFetcher:self.zoneChangeFetcher
570 zoneModifier:self.zoneModifier
571 savedTLKNotifier: self.savedTLKNotifier
572 cloudKitClassDependencies:self.cloudKitClassDependencies];
573
574 if(self.startCKOperationAtViewCreation) {
575 [self.views[viewName] beginCloudKitOperation];
576 }
577 return self.views[viewName];
578 }
579 }
580
581 - (NSSet<CKKSKeychainView*>*)currentViews
582 {
583 @synchronized (self.views) {
584 NSMutableSet<CKKSKeychainView*>* viewObjects = [NSMutableSet set];
585 for(NSString* viewName in self.views) {
586 [viewObjects addObject:self.views[viewName]];
587 }
588 return viewObjects;
589 }
590 }
591
592 - (void)createViews
593 {
594 if(![self useCKKSViewsFromPolicy]) {
595 // In the future, the CKKSViewManager needs to persist its policy property through daemon restarts
596 // and load it here, before creating whatever views it was told to (in a previous daemon lifetime)
597 for (NSString* viewName in [self defaultViewList]) {
598 CKKSKeychainView* view = [self findOrCreateView:viewName];
599 (void)view;
600 }
601 } else {
602 ckksnotice_global("ckks-views", "Not loading default view list due to enabled CKKS4All");
603 }
604 }
605
606 - (void)beginCloudKitOperationOfAllViews
607 {
608 self.startCKOperationAtViewCreation = YES;
609
610 for (CKKSKeychainView* view in self.views.allValues) {
611 [view beginCloudKitOperation];
612 }
613 }
614
615 - (void)haltZone:(NSString*)viewName
616 {
617 @synchronized(self.views) {
618 CKKSKeychainView* view = self.views[viewName];
619 [view halt];
620 [view cancelAllOperations];
621 self.views[viewName] = nil;
622 }
623 }
624
625 - (CKKSKeychainView*)restartZone:(NSString*)viewName {
626 [self haltZone:viewName];
627 CKKSKeychainView* view = [self findOrCreateView: viewName];
628
629 [view setCurrentSyncingPolicy:self.policy policyIsFresh:NO];
630
631 return view;
632 }
633
634 - (NSString*)viewNameForViewHint: (NSString*) viewHint {
635 // For now, choose view based on viewhints.
636 if(viewHint && ![viewHint isEqual: [NSNull null]]) {
637 return viewHint;
638 }
639
640 // If there isn't a provided view hint, use the "keychain" view if we're testing. Otherwise, nil.
641 if(SecCKKSTestsEnabled()) {
642 return @"keychain";
643 } else {
644 return nil;
645 }
646 }
647
648 - (void) setOverrideCKKSViewsFromPolicy:(BOOL)value {
649 _overrideCKKSViewsFromPolicy = YES;
650 _valueCKKSViewsFromPolicy = value;
651 }
652
653 - (BOOL)useCKKSViewsFromPolicy {
654 if (self.overrideCKKSViewsFromPolicy) {
655 return self.valueCKKSViewsFromPolicy;
656 } else {
657 BOOL viewsFromPolicy = os_feature_enabled(Security, CKKSViewsFromPolicy);
658
659 static dispatch_once_t onceToken;
660 dispatch_once(&onceToken, ^{
661 ckksnotice_global("ckks", "ViewsFromPolicy feature flag: %@", viewsFromPolicy ? @"on" : @"off");
662 });
663 return viewsFromPolicy;
664 }
665 }
666
667 - (NSString* _Nullable)viewNameForItem:(SecDbItemRef)item
668 {
669 if ([self useCKKSViewsFromPolicy]) {
670 CFErrorRef cferror = NULL;
671 NSMutableDictionary *dict = (__bridge_transfer NSMutableDictionary*) SecDbItemCopyPListWithMask(item, kSecDbSyncFlag, &cferror);
672
673 if(cferror) {
674 ckkserror_global("ckks", "Couldn't fetch attributes from item: %@", cferror);
675 CFReleaseNull(cferror);
676 return nil;
677 }
678
679 // Ensure that we've added the class name, because SecDbItemCopyPListWithMask doesn't do that for some reason.
680 dict[(__bridge NSString*)kSecClass] = (__bridge NSString*)item->class->name;
681
682 NSString* view = [self.policy mapDictionaryToView:dict];
683 if (view == nil) {
684 ckkserror_global("ckks", "No view returned from policy (%@): %@", self.policy, item);
685 return nil;
686 }
687
688 return view;
689 } else {
690 CFErrorRef cferror = NULL;
691 NSString* viewHint = (__bridge NSString*) SecDbItemGetValue(item, &v7vwht, &cferror);
692
693 if(cferror) {
694 ckkserror_global("ckks", "Couldn't fetch the viewhint for some reason: %@", cferror);
695 CFReleaseNull(cferror);
696 viewHint = nil;
697 }
698
699 return [self viewNameForViewHint: viewHint];
700 }
701 }
702
703 - (void)registerSyncStatusCallback: (NSString*) uuid callback: (SecBoolNSErrorCallback) callback {
704 // Someone is requesting future notification of this item.
705 @synchronized(self.pendingSyncCallbacks) {
706 ckksnotice_global("ckkscallback", "registered callback for UUID: %@", uuid);
707 self.pendingSyncCallbacks[uuid] = callback;
708 }
709 }
710
711 - (SecBoolNSErrorCallback _Nullable)claimCallbackForUUID:(NSString* _Nullable)uuid
712 {
713 if(!uuid) {
714 return nil;
715 }
716
717 @synchronized(self.pendingSyncCallbacks) {
718 SecBoolNSErrorCallback callback = self.pendingSyncCallbacks[uuid];
719
720 if(callback) {
721 ckksnotice_global("ckkscallback", "fetched UUID: %@", uuid);
722 }
723
724 self.pendingSyncCallbacks[uuid] = nil;
725 return callback;
726 }
727 }
728
729 - (NSSet<NSString*>*)pendingCallbackUUIDs
730 {
731 @synchronized(self.pendingSyncCallbacks) {
732 return [[self.pendingSyncCallbacks allKeys] copy];
733 }
734 }
735
736 - (void)cloudkitAccountStateChange:(CKAccountInfo* _Nullable)oldAccountInfo to:(CKAccountInfo*)currentAccountInfo
737 {
738 if(currentAccountInfo.accountStatus == CKAccountStatusAvailable && currentAccountInfo.hasValidCredentials) {
739 // Account is okay!
740 } else {
741 @synchronized(self.pendingSyncCallbacks) {
742 if(self.pendingSyncCallbacks.count > 0) {
743 ckksnotice_global("ckkscallback", "No CK account; failing all pending sync callbacks");
744
745 for(NSString* uuid in [self.pendingSyncCallbacks allKeys]) {
746 [CKKSViewManager callSyncCallbackWithErrorNoAccount:self.pendingSyncCallbacks[uuid]];
747 }
748
749 [self.pendingSyncCallbacks removeAllObjects];
750 }
751 }
752 }
753 }
754
755 + (void)callSyncCallbackWithErrorNoAccount:(SecBoolNSErrorCallback)syncCallback
756 {
757 // I don't love using this domain, but PCS depends on it
758 syncCallback(false, [NSError errorWithDomain:@"securityd"
759 code:errSecNotLoggedIn
760 description:@"No iCloud account available; item is not expected to sync"]);
761 }
762
763 - (void) handleKeychainEventDbConnection: (SecDbConnectionRef) dbconn source:(SecDbTransactionSource)txionSource added: (SecDbItemRef) added deleted: (SecDbItemRef) deleted {
764
765 SecDbItemRef modified = added ? added : deleted;
766
767 NSString* keyViewName = [CKKSKey isItemKeyForKeychainView: modified];
768
769 if(keyViewName) {
770 // This might be some key material for this view! Poke it.
771 CKKSKeychainView* view = [self findView: keyViewName];
772
773 if(!SecCKKSTestDisableKeyNotifications()) {
774 ckksnotice("ckks", view, "Potential new key material from %@ (source %lu)",
775 keyViewName, (unsigned long)txionSource);
776 [view keyStateMachineRequestProcess];
777 } else {
778 ckksnotice("ckks", view, "Ignoring potential new key material from %@ (source %lu)",
779 keyViewName, (unsigned long)txionSource);
780 }
781 return;
782 }
783
784 bool addedSync = added && SecDbItemIsSyncable(added);
785 bool deletedSync = deleted && SecDbItemIsSyncable(deleted);
786
787 if(!addedSync && !deletedSync) {
788 // Local-only change. Skip with prejudice.
789 ckksinfo_global("ckks", "skipping sync of non-sync item (%d, %d)", addedSync, deletedSync);
790 return;
791 }
792
793 NSString* viewName = nil;
794
795 @synchronized(self.views) {
796 if([self useCKKSViewsFromPolicy] && !self.policy) {
797 ckkserror_global("ckks", "No policy configured(%@). Skipping item: %@", self.policy, modified);
798 self.itemModificationsBeforePolicyLoaded = YES;
799
800 return;
801 }
802 }
803
804 viewName = [self viewNameForItem:modified];
805
806 if(!viewName) {
807 ckksnotice_global("ckks", "No intended CKKS view for item; skipping: %@", modified);
808 return;
809 }
810
811 // Looks like a normal item. Proceed!
812 CKKSKeychainView* view = [self findView:viewName];
813
814 if(!view) {
815 ckksnotice_global("ckks", "No CKKS view for %@, skipping: %@", viewName, modified);
816
817 NSString* uuid = (__bridge NSString*) SecDbItemGetValue(modified, &v10itemuuid, NULL);
818 SecBoolNSErrorCallback syncCallback = [self claimCallbackForUUID:uuid];
819
820 if(syncCallback) {
821 syncCallback(false, [NSError errorWithDomain:CKKSErrorDomain
822 code:CKKSNoSuchView
823 userInfo:@{NSLocalizedDescriptionKey: [NSString stringWithFormat: @"No syncing view for '%@'", viewName]}]);
824 }
825 return;
826 }
827
828 ckksnotice("ckks", view, "Routing item to zone %@: %@", viewName, modified);
829 [view handleKeychainEventDbConnection:dbconn
830 source:txionSource
831 added:added
832 deleted:deleted
833 rateLimiter:self.globalRateLimiter];
834 }
835
836 -(void)setCurrentItemForAccessGroup:(NSData* _Nonnull)newItemPersistentRef
837 hash:(NSData*)newItemSHA1
838 accessGroup:(NSString*)accessGroup
839 identifier:(NSString*)identifier
840 viewHint:(NSString*)viewHint
841 replacing:(NSData* _Nullable)oldCurrentItemPersistentRef
842 hash:(NSData*)oldItemSHA1
843 complete:(void (^) (NSError* operror)) complete
844 {
845 NSError* viewError = nil;
846 CKKSKeychainView* view = [self findView:viewHint error:&viewError];
847
848 if(!view) {
849 ckksnotice_global("ckks", "No CKKS view for %@, skipping setcurrent request: %@", viewHint, viewError);
850 complete(viewError);
851 return;
852 }
853
854 [view setCurrentItemForAccessGroup:newItemPersistentRef
855 hash:newItemSHA1
856 accessGroup:accessGroup
857 identifier:identifier
858 replacing:oldCurrentItemPersistentRef
859 hash:oldItemSHA1
860 complete:complete];
861 }
862
863 -(void)getCurrentItemForAccessGroup:(NSString*)accessGroup
864 identifier:(NSString*)identifier
865 viewHint:(NSString*)viewHint
866 fetchCloudValue:(bool)fetchCloudValue
867 complete:(void (^) (NSString* uuid, NSError* operror)) complete
868 {
869 NSError* viewError = nil;
870 CKKSKeychainView* view = [self findView:viewHint error:&viewError];
871 if(!view) {
872 ckksnotice_global("ckks", "No CKKS view for %@, skipping current fetch request: %@", viewHint, viewError);
873 complete(NULL, viewError);
874 return;
875 }
876
877 [view getCurrentItemForAccessGroup:accessGroup
878 identifier:identifier
879 fetchCloudValue:fetchCloudValue
880 complete:complete];
881 }
882
883 + (instancetype)manager
884 {
885 return [OTManager manager].viewManager;
886 }
887
888 - (void)cancelPendingOperations {
889 [self.savedTLKNotifier cancel];
890 }
891
892 -(void)notifyNewTLKsInKeychain {
893 // Why two functions here? Limitation of OCMock, unfortunately: can't stub and expect the same method
894 ckksnotice_global("ckksbackup", "New TLKs have arrived");
895 [self syncBackupAndNotifyAboutSync];
896 }
897
898 - (void)syncBackupAndNotifyAboutSync {
899 SOSAccount* account = (__bridge SOSAccount*)SOSKeychainAccountGetSharedAccount();
900
901 if(!account) {
902 ckksnotice_global("ckks", "Failed to get account object");
903 return;
904 }
905
906 [account performTransaction:^(SOSAccountTransaction * _Nonnull txn) {
907 CFErrorRef error = NULL;
908 NSSet* ignore = CFBridgingRelease(SOSAccountCopyBackupPeersAndForceSync(txn, &error));
909 (void)ignore;
910
911 if(error) {
912 ckkserror_global("backup", "Couldn't process sync with backup peers: %@", error);
913 } else {
914 ckksnotice_global("ckksbackup", "telling CloudServices about TLK arrival");
915 notify_post(kSecItemBackupNotification);
916 };
917 }];
918 }
919
920
921
922 - (NSArray<CKKSKeychainBackedKey*>* _Nullable)currentTLKsFilteredByPolicy:(BOOL)restrictToPolicy error:(NSError**)error
923 {
924 NSError* localError = nil;
925 NSArray<CKKSKeychainView*>* actualViews = [self views:nil operation:@"current TLKs" error:&localError];
926 if(localError) {
927 ckkserror_global("ckks", "Error getting views: %@", localError);
928 if(error) {
929 *error = localError;
930 }
931 return nil;
932 }
933
934 NSMutableArray<CKKSResultOperation<CKKSKeySetProviderOperationProtocol>*>* keyFetchOperations = [NSMutableArray array];
935 for (CKKSKeychainView* view in actualViews) {
936 if(restrictToPolicy && [self useCKKSViewsFromPolicy] && ![self.policy.viewsToPiggybackTLKs containsObject:view.zoneName]) {
937 continue;
938 }
939
940 CKKSResultOperation<CKKSKeySetProviderOperationProtocol>* op = [view findKeySet:NO];
941 [op timeout:10*NSEC_PER_SEC];
942 [keyFetchOperations addObject:op];
943 }
944
945 NSMutableArray<CKKSKeychainBackedKey*>* tlks = [NSMutableArray array];
946
947 for(CKKSResultOperation<CKKSKeySetProviderOperationProtocol>* op in keyFetchOperations) {
948 [op waitUntilFinished];
949
950 if(op.error) {
951 ckkserror_global("ckks", "Error getting keyset: %@", op.error);
952 if(error) {
953 *error = op.error;
954 }
955 } else {
956 if(op.keyset.tlk) {
957 // Keys provided by this function must have the key material loaded
958 NSError* loadError = nil;
959 [op.keyset.tlk ensureKeyLoaded:&loadError];
960 if(loadError) {
961 ckkserror_global("ckks", "Error loading key: %@", loadError);
962 if(error) {
963 *error = loadError;
964 }
965 } else {
966 [tlks addObject:[op.keyset.tlk.keycore copy]];
967 }
968 } else {
969 ckkserror_global("ckks", "Do not have TLK: %@", op.keyset);
970 }
971 }
972 }
973
974 return tlks;
975 }
976
977 #pragma mark - RPCs to manage and report state
978
979 - (void)performanceCounters:(void(^)(NSDictionary <NSString *, NSNumber *> *counter))reply {
980 reply(@{});
981 }
982
983 - (NSArray<CKKSKeychainView*>*)views:(NSString*)viewName operation:(NSString*)opName error:(NSError**)error
984 {
985 return [self views:viewName operation:opName errorOnPolicyMissing:YES error:error];
986 }
987
988 - (NSArray<CKKSKeychainView*>*)views:(NSString*)viewName
989 operation:(NSString*)opName
990 errorOnPolicyMissing:(BOOL)errorOnPolicyMissing
991 error:(NSError**)error
992 {
993 NSArray* actualViews = nil;
994
995 // Ensure we've actually set up, but don't wait too long. Clients get impatient.
996 if([self.completedSecCKKSInitialize wait:5*NSEC_PER_SEC]) {
997 ckkserror_global("ckks", "Haven't yet initialized zones; expect failure fetching views");
998 }
999
1000 // If the caller doesn't mind if the policy is missing, wait some, but not the full 5s
1001 BOOL policyLoaded = [self.policyLoaded wait:(errorOnPolicyMissing ? 5 : 0.5)*NSEC_PER_SEC] == 0;
1002 if(!policyLoaded) {
1003 ckkserror_global("ckks", "Haven't yet received a policy; expect failure fetching views");
1004 }
1005
1006 if(viewName) {
1007 CKKSKeychainView* view = errorOnPolicyMissing ? [self findView:viewName error:error] : [self findView:viewName];
1008 ckksnotice_global("ckks", "Received a %@ request for zone %@ (%@)", opName, viewName, view);
1009
1010 if(!view) {
1011 return nil;
1012 }
1013
1014 actualViews = @[view];
1015
1016 } else {
1017 if(!policyLoaded && [self useCKKSViewsFromPolicy] && errorOnPolicyMissing) {
1018 if(error) {
1019 if(error) {
1020 *error = [NSError errorWithDomain:CKKSErrorDomain
1021 code:CKKSErrorPolicyNotLoaded
1022 userInfo:@{NSLocalizedDescriptionKey: @"CKKS syncing policy not yet loaded; cannot list all views"}];
1023 }
1024 }
1025 return nil;
1026 }
1027
1028 @synchronized(self.views) {
1029 actualViews = [self.views.allValues copy];
1030 ckksnotice_global("ckks", "Received a %@ request for all zones: %@", opName, actualViews);
1031 }
1032 }
1033 actualViews = [actualViews sortedArrayUsingDescriptors:@[[NSSortDescriptor sortDescriptorWithKey:@"zoneName" ascending:YES]]];
1034 return actualViews;
1035 }
1036
1037 - (void)rpcResetLocal:(NSString*)viewName reply: (void(^)(NSError* result)) reply {
1038 NSError* localError = nil;
1039 NSArray* actualViews = [self views:viewName operation:@"local reset" error:&localError];
1040 if(localError) {
1041 ckkserror_global("ckks", "Error getting view %@: %@", viewName, localError);
1042 reply(localError);
1043 return;
1044 }
1045
1046 CKKSResultOperation* op = [CKKSResultOperation named:@"local-reset-zones-waiter" withBlockTakingSelf:^(CKKSResultOperation * _Nonnull strongOp) {
1047 if(!strongOp.error) {
1048 ckksnotice_global("ckksreset", "Completed rpcResetLocal");
1049 } else {
1050 ckksnotice_global("ckks", "Completed rpcResetLocal with error: %@", strongOp.error);
1051 }
1052 reply(CKXPCSuitableError(strongOp.error));
1053 }];
1054
1055 for(CKKSKeychainView* view in actualViews) {
1056 ckksnotice("ckksreset", view, "Beginning local reset for %@", view);
1057 [op addSuccessDependency:[view resetLocalData]];
1058 }
1059
1060 [op timeout:120*NSEC_PER_SEC];
1061 [self.operationQueue addOperation: op];
1062 }
1063
1064 - (void)rpcResetCloudKit:(NSString*)viewName reason:(NSString *)reason reply:(void(^)(NSError* result)) reply {
1065 NSError* localError = nil;
1066 NSArray* actualViews = [self views:viewName operation:@"CloudKit reset" error:&localError];
1067 if(localError) {
1068 ckkserror_global("ckks", "Error getting view %@: %@", viewName, localError);
1069 reply(localError);
1070 return;
1071 }
1072
1073 CKKSResultOperation* op = [CKKSResultOperation named:@"cloudkit-reset-zones-waiter" withBlock:^() {}];
1074
1075 // Use the completion block instead of the operation block, so that it runs even if the cancel fires
1076 __weak __typeof(op) weakOp = op;
1077 [op setCompletionBlock:^{
1078 __strong __typeof(op) strongOp = weakOp;
1079 if(!strongOp.error) {
1080 ckksnotice_global("ckksreset", "Completed rpcResetCloudKit");
1081 } else {
1082 ckksnotice_global("ckksreset", "Completed rpcResetCloudKit with error: %@", strongOp.error);
1083 }
1084 reply(CKXPCSuitableError(strongOp.error));
1085 }];
1086
1087 for(CKKSKeychainView* view in actualViews) {
1088 NSString *operationGroupName = [NSString stringWithFormat:@"api-reset-%@", reason];
1089 ckksnotice("ckksreset", view, "Beginning CloudKit reset for %@: %@", view, reason);
1090 [op addSuccessDependency:[view resetCloudKitZone:[CKOperationGroup CKKSGroupWithName:operationGroupName]]];
1091 }
1092
1093 [op timeout:120*NSEC_PER_SEC];
1094 [self.operationQueue addOperation: op];
1095 }
1096
1097 - (void)rpcResync:(NSString*)viewName reply: (void(^)(NSError* result)) reply {
1098 NSError* localError = nil;
1099 NSArray* actualViews = [self views:viewName operation:@"CloudKit resync" error:&localError];
1100 if(localError) {
1101 ckkserror_global("ckks", "Error getting view %@: %@", viewName, localError);
1102 reply(localError);
1103 return;
1104 }
1105
1106 CKKSResultOperation* op = [[CKKSResultOperation alloc] init];
1107 op.name = @"rpc-resync-cloudkit";
1108 __weak __typeof(op) weakOp = op;
1109
1110 // Use the completion block instead of the operation block, so that it runs even if the cancel fires
1111 [op setCompletionBlock:^{
1112 __strong __typeof(op) strongOp = weakOp;
1113 ckksnotice_global("ckks", "Ending rsync-CloudKit rpc with %@", strongOp.error);
1114 reply(CKXPCSuitableError(strongOp.error));
1115 }];
1116
1117 for(CKKSKeychainView* view in actualViews) {
1118 ckksnotice("ckksresync", view, "Beginning resync (CloudKit) for %@", view);
1119
1120 CKKSSynchronizeOperation* resyncOp = [view resyncWithCloud];
1121 [op addSuccessDependency:resyncOp];
1122 }
1123
1124 [op timeout:120*NSEC_PER_SEC];
1125 [self.operationQueue addOperation:op];
1126 }
1127
1128 - (void)rpcResyncLocal:(NSString*)viewName reply:(void(^)(NSError* result))reply {
1129 NSError* localError = nil;
1130 NSArray* actualViews = [self views:viewName operation:@"local resync" error:&localError];
1131 if(localError) {
1132 ckkserror_global("ckks", "Error getting view %@: %@", viewName, localError);
1133 reply(localError);
1134 return;
1135 }
1136
1137 CKKSResultOperation* op = [[CKKSResultOperation alloc] init];
1138 op.name = @"rpc-resync-local";
1139 __weak __typeof(op) weakOp = op;
1140 // Use the completion block instead of the operation block, so that it runs even if the cancel fires
1141 [op setCompletionBlock:^{
1142 __strong __typeof(op) strongOp = weakOp;
1143 ckksnotice_global("ckks", "Ending rsync-local rpc with %@", strongOp.error);
1144 reply(CKXPCSuitableError(strongOp.error));
1145 }];
1146
1147 for(CKKSKeychainView* view in actualViews) {
1148 ckksnotice("ckksresync", view, "Beginning resync (local) for %@", view);
1149
1150 CKKSLocalSynchronizeOperation* resyncOp = [view resyncLocal];
1151 [op addSuccessDependency:resyncOp];
1152 }
1153
1154 [op timeout:120*NSEC_PER_SEC];
1155 }
1156
1157 - (void)rpcStatus: (NSString*)viewName
1158 fast:(bool)fast
1159 reply:(void(^)(NSArray<NSDictionary*>* result, NSError* error)) reply
1160 {
1161 NSMutableArray* a = [[NSMutableArray alloc] init];
1162
1163 // Now, query the views about their status. Don't wait for the policy to be loaded
1164 NSError* error = nil;
1165 NSArray* actualViews = [self views:viewName operation:@"status" errorOnPolicyMissing:NO error:&error];
1166 if(!actualViews || error) {
1167 reply(nil, error);
1168 return;
1169 }
1170
1171 WEAKIFY(self);
1172 CKKSResultOperation* statusOp = [CKKSResultOperation named:@"status-rpc" withBlock:^{
1173 STRONGIFY(self);
1174
1175 if (fast == false) {
1176 // Get account state, even wait for it a little
1177 [self.accountTracker.ckdeviceIDInitialized wait:1*NSEC_PER_SEC];
1178 NSString *deviceID = self.accountTracker.ckdeviceID;
1179 NSError *deviceIDError = self.accountTracker.ckdeviceIDError;
1180 NSDate *lastCKKSPush = [[CKKSAnalytics logger] datePropertyForKey:CKKSAnalyticsLastCKKSPush];
1181
1182 #define stringify(obj) CKKSNilToNSNull([obj description])
1183 NSDictionary* global = @{
1184 @"view": @"global",
1185 @"reachability": self.reachabilityTracker.currentReachability ? @"network" : @"no-network",
1186 @"ckdeviceID": CKKSNilToNSNull(deviceID),
1187 @"ckdeviceIDError": CKKSNilToNSNull(deviceIDError),
1188 @"lockstatetracker": stringify(self.lockStateTracker),
1189 @"cloudkitRetryAfter": stringify(self.zoneModifier.cloudkitRetryAfter),
1190 @"lastCKKSPush": CKKSNilToNSNull(lastCKKSPush),
1191 @"policy": stringify(self.policy),
1192 @"viewsFromPolicy": [self useCKKSViewsFromPolicy] ? @"yes" : @"no",
1193 };
1194 [a addObject: global];
1195 }
1196
1197 for(CKKSKeychainView* view in actualViews) {
1198 NSDictionary* status = nil;
1199 ckksnotice("ckks", view, "Fetching status for %@", view.zoneName);
1200 if (fast) {
1201 status = [view fastStatus];
1202 } else {
1203 status = [view status];
1204 }
1205 ckksinfo("ckks", view, "Status is %@", status);
1206 if(status) {
1207 [a addObject: status];
1208 }
1209 }
1210 reply(a, nil);
1211 }];
1212
1213 // If we're signed in, give the views a few seconds to enter what they consider to be a non-transient state (in case this daemon just launched)
1214 if([self.accountTracker.ckAccountInfoInitialized wait:5*NSEC_PER_SEC]) {
1215 ckkserror_global("account", "Haven't yet figured out cloudkit account state");
1216 }
1217
1218 if(self.accountTracker.currentCKAccountInfo.accountStatus == CKAccountStatusAvailable) {
1219 if (![self waitForTrustReady]) {
1220 ckkserror_global("trust", "Haven't yet figured out trust status");
1221 }
1222
1223 for(CKKSKeychainView* view in actualViews) {
1224 OctagonStateMultiStateArrivalWatcher* waitForTransient = [[OctagonStateMultiStateArrivalWatcher alloc] initNamed:@"rpc-watcher"
1225 serialQueue:view.queue
1226 states:CKKSKeyStateNonTransientStates()];
1227 [waitForTransient timeout:5*NSEC_PER_SEC];
1228 [view.stateMachine registerMultiStateArrivalWatcher:waitForTransient];
1229
1230 [statusOp addDependency:waitForTransient.result];
1231 }
1232 }
1233 [self.operationQueue addOperation:statusOp];
1234
1235 return;
1236 }
1237
1238 - (void)rpcStatus:(NSString*)viewName reply:(void (^)(NSArray<NSDictionary*>* result, NSError* error))reply
1239 {
1240 [self rpcStatus:viewName fast:false reply:reply];
1241 }
1242
1243 - (void)rpcFastStatus:(NSString*)viewName reply:(void (^)(NSArray<NSDictionary*>* result, NSError* error))reply
1244 {
1245 [self rpcStatus:viewName fast:true reply:reply];
1246 }
1247
1248 - (void)rpcFetchAndProcessChanges:(NSString*)viewName reply: (void(^)(NSError* result))reply {
1249 [self rpcFetchAndProcessChanges:viewName classA:false reply: (void(^)(NSError* result))reply];
1250 }
1251
1252 - (void)rpcFetchAndProcessClassAChanges:(NSString*)viewName reply: (void(^)(NSError* result))reply {
1253 [self rpcFetchAndProcessChanges:viewName classA:true reply:(void(^)(NSError* result))reply];
1254 }
1255
1256 - (void)rpcFetchAndProcessChanges:(NSString*)viewName classA:(bool)classAError reply: (void(^)(NSError* result)) reply {
1257 NSError* error = nil;
1258 NSArray* actualViews = [self views:viewName operation:@"fetch" error:&error];
1259 if(!actualViews || error) {
1260 reply(error);
1261 return;
1262 }
1263
1264 CKKSResultOperation* blockOp = [[CKKSResultOperation alloc] init];
1265 blockOp.name = @"rpc-fetch-and-process-result";
1266 __weak __typeof(blockOp) weakBlockOp = blockOp;
1267 // Use the completion block instead of the operation block, so that it runs even if the cancel fires
1268 [blockOp setCompletionBlock:^{
1269 __strong __typeof(blockOp) strongBlockOp = weakBlockOp;
1270 [strongBlockOp allDependentsSuccessful];
1271 reply(CKXPCSuitableError(strongBlockOp.error));
1272 }];
1273
1274 for(CKKSKeychainView* view in actualViews) {
1275 ckksnotice("ckks", view, "Beginning fetch for %@", view);
1276
1277 CKKSResultOperation* op = [view processIncomingQueue:classAError after:[view.zoneChangeFetcher requestSuccessfulFetch: CKKSFetchBecauseAPIFetchRequest]];
1278 [blockOp addDependency:op];
1279 }
1280
1281 [self.operationQueue addOperation: [blockOp timeout:(SecCKKSTestsEnabled() ? NSEC_PER_SEC * 5 : NSEC_PER_SEC * 120)]];
1282 }
1283
1284 - (void)rpcPushOutgoingChanges:(NSString*)viewName reply: (void(^)(NSError* result))reply {
1285 NSError* error = nil;
1286 NSArray* actualViews = [self views:viewName operation:@"push" error:&error];
1287 if(!actualViews || error) {
1288 reply(error);
1289 return;
1290 }
1291
1292 CKKSResultOperation* blockOp = [[CKKSResultOperation alloc] init];
1293 blockOp.name = @"rpc-push";
1294 __weak __typeof(blockOp) weakBlockOp = blockOp;
1295 // Use the completion block instead of the operation block, so that it runs even if the cancel fires
1296 [blockOp setCompletionBlock:^{
1297 __strong __typeof(blockOp) strongBlockOp = weakBlockOp;
1298 [strongBlockOp allDependentsSuccessful];
1299 reply(CKXPCSuitableError(strongBlockOp.error));
1300 }];
1301
1302 for(CKKSKeychainView* view in actualViews) {
1303 ckksnotice("ckks-rpc", view, "Beginning push for %@", view);
1304
1305 CKKSResultOperation* op = [view processOutgoingQueue: [CKOperationGroup CKKSGroupWithName:@"rpc-push"]];
1306 [blockOp addDependency:op];
1307 }
1308
1309 [self.operationQueue addOperation: [blockOp timeout:(SecCKKSTestsEnabled() ? NSEC_PER_SEC * 2 : NSEC_PER_SEC * 120)]];
1310 }
1311
1312 - (void)rpcGetCKDeviceIDWithReply:(void (^)(NSString *))reply {
1313 reply(self.accountTracker.ckdeviceID);
1314 }
1315
1316 - (void)rpcCKMetric:(NSString *)eventName attributes:(NSDictionary *)attributes reply:(void (^)(NSError *))reply
1317 {
1318 if (eventName == NULL) {
1319 reply([NSError errorWithDomain:CKKSErrorDomain
1320 code:CKKSNoMetric
1321 description:@"No metric name"]);
1322 return;
1323 }
1324 SecEventMetric *metric = [[SecEventMetric alloc] initWithEventName:eventName];
1325 [attributes enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL * _Nonnull __unused stop) {
1326 metric[key] = obj;
1327 }];
1328 [[SecMetrics managerObject] submitEvent:metric];
1329 reply(NULL);
1330 }
1331
1332 -(void)xpc24HrNotification {
1333 // XPC has poked us and said we should do some cleanup!
1334 ckksnotice_global("ckks", "Received a 24hr notification from XPC");
1335
1336 if (![self waitForTrustReady]) {
1337 ckksnotice_global("ckks", "Trust not ready, still going ahead");
1338 }
1339
1340 [[CKKSAnalytics logger] dailyCoreAnalyticsMetrics:@"com.apple.security.CKKSHealthSummary"];
1341
1342 // For now, poke the views and tell them to update their device states if they'd like
1343 NSArray* actualViews = nil;
1344 @synchronized(self.views) {
1345 // Can't safely iterate a mutable collection, so copy it.
1346 actualViews = self.views.allValues;
1347 }
1348
1349 CKOperationGroup* group = [CKOperationGroup CKKSGroupWithName:@"periodic-device-state-update"];
1350 for(CKKSKeychainView* view in actualViews) {
1351 ckksnotice("ckks", view, "Starting device state XPC update");
1352 // Let the update know it should rate-limit itself
1353 [view updateDeviceState:true waitForKeyHierarchyInitialization:30*NSEC_PER_SEC ckoperationGroup:group];
1354 [view xpc24HrNotification];
1355 }
1356 }
1357
1358 - (void)haltAll
1359 {
1360 @synchronized(self.views) {
1361 for(CKKSKeychainView* view in self.views.allValues) {
1362 [view halt];
1363 }
1364 }
1365
1366 [self.zoneModifier halt];
1367 }
1368
1369 #endif // OCTAGON
1370 @end