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