2 * Copyright (c) 2016 Apple Inc. All Rights Reserved.
4 * @APPLE_LICENSE_HEADER_START@
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
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.
21 * @APPLE_LICENSE_HEADER_END@
24 #import "keychain/ckks/CKKSViewManager.h"
25 #import "keychain/ckks/CKKSKeychainView.h"
26 #import "keychain/ckks/CKKSSynchronizeOperation.h"
27 #import "keychain/ckks/CKKSKey.h"
28 #import "keychain/ckks/CKKSZoneStateEntry.h"
29 #import "keychain/ckks/CKKSNearFutureScheduler.h"
30 #import "keychain/ckks/CKKSNotifier.h"
31 #import "keychain/ckks/CKKSCondition.h"
32 #import "keychain/ckks/CloudKitCategories.h"
33 #import "CKKSAnalyticsLogger.h"
35 #import "SecEntitlements.h"
37 #include <securityd/SecDbItem.h>
38 #include <securityd/SecDbKeychainItem.h>
39 #include <securityd/SecItemSchema.h>
40 #include <Security/SecureObjectSync/SOSViews.h>
42 #import <Foundation/NSXPCConnection.h>
43 #import <Foundation/NSXPCConnection_Private.h>
45 #include <Security/SecureObjectSync/SOSAccount.h>
46 #include <Security/SecItemBackup.h>
49 #import <CloudKit/CloudKit.h>
50 #import <CloudKit/CloudKit_Private.h>
53 @interface CKKSViewManager () <NSXPCListenerDelegate>
55 @property NSXPCListener *listener;
57 // Once you set these, all CKKSKeychainViews created will use them
58 @property (readonly) Class<CKKSFetchRecordZoneChangesOperation> fetchRecordZoneChangesOperationClass;
59 @property (readonly) Class<CKKSModifySubscriptionsOperation> modifySubscriptionsOperationClass;
60 @property (readonly) Class<CKKSModifyRecordZonesOperation> modifyRecordZonesOperationClass;
61 @property (readonly) Class<CKKSAPSConnection> apsConnectionClass;
62 @property (readonly) Class<CKKSNotifier> notifierClass;
63 @property (readonly) Class<CKKSNSNotificationCenter> nsnotificationCenterClass;
65 @property NSMutableDictionary<NSString*, CKKSKeychainView*>* views;
67 @property NSMutableDictionary<NSString*, SecBoolNSErrorCallback>* pendingSyncCallbacks;
68 @property CKKSNearFutureScheduler* savedTLKNotifier;;
69 @property NSOperationQueue* operationQueue;
73 @implementation CKKSViewManager
76 - (instancetype)initCloudKitWithContainerName: (NSString*) containerName usePCS:(bool)usePCS {
77 return [self initWithContainerName:containerName
79 fetchRecordZoneChangesOperationClass:[CKFetchRecordZoneChangesOperation class]
80 modifySubscriptionsOperationClass:[CKModifySubscriptionsOperation class]
81 modifyRecordZonesOperationClass:[CKModifyRecordZonesOperation class]
82 apsConnectionClass:[APSConnection class]
83 nsnotificationCenterClass:[NSNotificationCenter class]
84 notifierClass:[CKKSNotifyPostNotifier class]
88 - (instancetype)initWithContainerName: (NSString*) containerName
90 fetchRecordZoneChangesOperationClass: (Class<CKKSFetchRecordZoneChangesOperation>) fetchRecordZoneChangesOperationClass
91 modifySubscriptionsOperationClass: (Class<CKKSModifySubscriptionsOperation>) modifySubscriptionsOperationClass
92 modifyRecordZonesOperationClass: (Class<CKKSModifyRecordZonesOperation>) modifyRecordZonesOperationClass
93 apsConnectionClass: (Class<CKKSAPSConnection>) apsConnectionClass
94 nsnotificationCenterClass: (Class<CKKSNSNotificationCenter>) nsnotificationCenterClass
95 notifierClass: (Class<CKKSNotifier>) notifierClass
96 setupHold: (NSOperation*) setupHold {
97 if(self = [super init]) {
98 _fetchRecordZoneChangesOperationClass = fetchRecordZoneChangesOperationClass;
99 _modifySubscriptionsOperationClass = modifySubscriptionsOperationClass;
100 _modifyRecordZonesOperationClass = modifyRecordZonesOperationClass;
101 _apsConnectionClass = apsConnectionClass;
102 _nsnotificationCenterClass = nsnotificationCenterClass;
103 _notifierClass = notifierClass;
105 _container = [self makeCKContainer: containerName usePCS:usePCS];
106 _accountTracker = [[CKKSCKAccountStateTracker alloc] init:self.container nsnotificationCenterClass:nsnotificationCenterClass];
107 _lockStateTracker = [[CKKSLockStateTracker alloc] init];
109 _operationQueue = [[NSOperationQueue alloc] init];
111 _views = [[NSMutableDictionary alloc] init];
112 _pendingSyncCallbacks = [[NSMutableDictionary alloc] init];
114 _zoneStartupDependency = setupHold;
115 _initializeNewZones = false;
117 _completedSecCKKSInitialize = [[CKKSCondition alloc] init];
119 __weak __typeof(self) weakSelf = self;
120 _savedTLKNotifier = [[CKKSNearFutureScheduler alloc] initWithName: @"newtlks" delay:5*NSEC_PER_SEC keepProcessAlive:true block:^{
121 [weakSelf notifyNewTLKsInKeychain];
124 _listener = [NSXPCListener anonymousListener];
125 _listener.delegate = self;
131 -(CKContainer*)makeCKContainer:(NSString*)containerName usePCS:(bool)usePCS {
132 CKContainer* container = [CKContainer containerWithIdentifier:containerName];
134 CKContainerOptions* containerOptions = [[CKContainerOptions alloc] init];
135 containerOptions.bypassPCSEncryption = YES;
137 // We don't have a great way to set these, so replace the entire container object
138 container = [[CKContainer alloc] initWithContainerID: container.containerID options:containerOptions];
144 [self clearAllViews];
147 dispatch_queue_t globalZoneStateQueue = NULL;
148 dispatch_once_t globalZoneStateQueueOnce;
150 // 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).
151 // Lazy-load it here.
152 - (CKKSRateLimiter*)getGlobalRateLimiter {
153 dispatch_once(&globalZoneStateQueueOnce, ^{
154 globalZoneStateQueue = dispatch_queue_create("CKKS global zone state", DISPATCH_QUEUE_SERIAL);
157 if(_globalRateLimiter != nil) {
158 return _globalRateLimiter;
161 __block CKKSRateLimiter* blocklimit = nil;
163 dispatch_sync(globalZoneStateQueue, ^{
164 NSError* error = nil;
166 // Special object containing state for all zones. Currently, just the rate limiter.
167 CKKSZoneStateEntry* allEntry = [CKKSZoneStateEntry tryFromDatabase: @"all" error:&error];
170 secerror("CKKSViewManager: couldn't load global zone state: %@", error);
173 if(!error && allEntry.rateLimiter) {
174 blocklimit = allEntry.rateLimiter;
176 blocklimit = [[CKKSRateLimiter alloc] init];
179 _globalRateLimiter = blocklimit;
180 return _globalRateLimiter;
183 // Mostly exists to be mocked out.
185 return CFBridgingRelease(SOSViewCopyViewSet(kViewSetCKKS));
188 - (void)setView: (CKKSKeychainView*) obj {
189 CKKSKeychainView* kcv = nil;
191 @synchronized(self.views) {
192 kcv = self.views[obj.zoneName];
193 self.views[obj.zoneName] = obj;
197 [kcv cancelAllOperations];
201 - (void)clearAllViews {
202 NSArray<CKKSKeychainView*>* tempviews = nil;
203 @synchronized(self.views) {
204 tempviews = [self.views.allValues copy];
205 [self.views removeAllObjects];
208 for(CKKSKeychainView* view in tempviews) {
209 [view cancelAllOperations];
213 - (void)clearView:(NSString*) viewName {
214 CKKSKeychainView* kcv = nil;
215 @synchronized(self.views) {
216 kcv = self.views[viewName];
217 self.views[viewName] = nil;
221 [kcv cancelAllOperations];
225 - (CKKSKeychainView*)findView:(NSString*)viewName {
229 @synchronized(self.views) {
230 return self.views[viewName];
234 - (CKKSKeychainView*)findOrCreateView:(NSString*)viewName {
235 @synchronized(self.views) {
236 CKKSKeychainView* kcv = self.views[viewName];
241 self.views[viewName] = [[CKKSKeychainView alloc] initWithContainer: self.container
243 accountTracker: self.accountTracker
244 lockStateTracker: self.lockStateTracker
245 savedTLKNotifier: self.savedTLKNotifier
246 fetchRecordZoneChangesOperationClass: self.fetchRecordZoneChangesOperationClass
247 modifySubscriptionsOperationClass: self.modifySubscriptionsOperationClass
248 modifyRecordZonesOperationClass: self.modifyRecordZonesOperationClass
249 apsConnectionClass: self.apsConnectionClass
250 notifierClass: self.notifierClass];
252 if(self.zoneStartupDependency) {
253 [self.views[viewName].zoneSetupOperation addDependency: self.zoneStartupDependency];
256 if(self.initializeNewZones) {
257 [self.views[viewName] initializeZone];
260 return self.views[viewName];
263 + (CKKSKeychainView*)findOrCreateView:(NSString*)viewName {
264 return [[CKKSViewManager manager] findOrCreateView: viewName];
267 - (NSDictionary<NSString *,NSString *> *)activeTLKs
269 NSMutableDictionary<NSString *,NSString *> *tlks = [NSMutableDictionary new];
270 @synchronized(self.views) {
271 for (NSString *name in self.views) {
272 CKKSKeychainView *view = self.views[name];
273 NSString *tlk = view.lastActiveTLKUUID;
282 - (CKKSKeychainView*)restartZone:(NSString*)viewName {
283 @synchronized(self.views) {
284 self.views[viewName] = nil;
286 return [self findOrCreateView: viewName];
289 // Allows all views to begin initializing, and opens the floodgates so that new views will be initalized immediately
290 - (void)initializeZones {
291 if(!SecCKKSIsEnabled()) {
292 secnotice("ckks", "Not initializing CKKS view set as CKKS is disabled");
296 @synchronized(self.views) {
297 self.initializeNewZones = true;
299 NSSet* viewSet = [CKKSViewManager viewList];
300 for(NSString* s in viewSet) {
301 [self findOrCreateView:s]; // initializes any newly-created views
306 - (NSString*)viewNameForViewHint: (NSString*) viewHint {
307 // For now, choose view based on viewhints.
308 if(viewHint && ![viewHint isEqual: [NSNull null]]) {
312 // If there isn't a provided view hint, use the "keychain" view if we're testing. Otherwise, nil.
313 if(SecCKKSTestsEnabled()) {
320 - (NSString*)viewNameForItem: (SecDbItemRef) item {
321 CFErrorRef cferror = NULL;
322 NSString* viewHint = (__bridge NSString*) SecDbItemGetValue(item, &v7vwht, &cferror);
325 secerror("ckks: Couldn't fetch the viewhint for some reason: %@", cferror);
326 CFReleaseNull(cferror);
330 return [self viewNameForViewHint: viewHint];
333 - (NSString*)viewNameForAttributes: (NSDictionary*) item {
334 return [self viewNameForViewHint: item[(id)kSecAttrSyncViewHint]];
337 - (void)registerSyncStatusCallback: (NSString*) uuid callback: (SecBoolNSErrorCallback) callback {
338 // Someone is requesting future notification of this item.
339 @synchronized(self.pendingSyncCallbacks) {
340 self.pendingSyncCallbacks[uuid] = callback;
344 - (void) handleKeychainEventDbConnection: (SecDbConnectionRef) dbconn source:(SecDbTransactionSource)txionSource added: (SecDbItemRef) added deleted: (SecDbItemRef) deleted {
346 SecDbItemRef modified = added ? added : deleted;
348 NSString* viewName = [self viewNameForItem: modified];
349 NSString* keyViewName = [CKKSKey isItemKeyForKeychainView: modified];
352 // This might be some key material for this view! Poke it.
353 CKKSKeychainView* view = [self findView: keyViewName];
355 if(!SecCKKSTestDisableKeyNotifications()) {
356 ckksnotice("ckks", view, "Potential new key material from %@ (source %lu)", keyViewName, txionSource);
357 [view keyStateMachineRequestProcess];
359 ckksnotice("ckks", view, "Ignoring potential new key material from %@ (source %lu)", keyViewName, txionSource);
364 // When SOS is in charge of a view, CKKS is not.
365 // Since this isn't a CKKS key item, we don't care about it.
366 if(txionSource == kSecDbSOSTransaction) {
367 secinfo("ckks", "Ignoring new non-CKKS item in kSecDbSOSTransaction notification");
370 // Looks like a normal item. Proceed!
371 CKKSKeychainView* view = [self findView:viewName];
373 NSString* uuid = (__bridge NSString*) SecDbItemGetValue(modified, &v10itemuuid, NULL);
374 SecBoolNSErrorCallback syncCallback = nil;
376 @synchronized(self.pendingSyncCallbacks) {
377 syncCallback = self.pendingSyncCallbacks[uuid];
378 self.pendingSyncCallbacks[uuid] = nil;
381 secinfo("ckks", "Have a pending callback for %@; passing along", uuid);
387 secinfo("ckks", "No CKKS view for %@, skipping: %@", viewName, modified);
389 syncCallback(false, [NSError errorWithDomain:@"securityd"
390 code:kSOSCCNoSuchView
391 userInfo:@{NSLocalizedDescriptionKey: [NSString stringWithFormat: @"No syncing view for '%@'", viewName]}]);
396 ckksnotice("ckks", view, "Routing item to zone %@: %@", viewName, modified);
397 [view handleKeychainEventDbConnection: dbconn added:added deleted:deleted rateLimiter:self.globalRateLimiter syncCallback: syncCallback];
400 -(void)setCurrentItemForAccessGroup:(SecDbItemRef)newItem
401 hash:(NSData*)newItemSHA1
402 accessGroup:(NSString*)accessGroup
403 identifier:(NSString*)identifier
404 viewHint:(NSString*)viewHint
405 replacing:(SecDbItemRef)oldItem
406 hash:(NSData*)oldItemSHA1
407 complete:(void (^) (NSError* operror)) complete
409 CKKSKeychainView* view = [self findView:viewHint];
412 secinfo("ckks", "No CKKS view for %@, skipping current request", viewHint);
413 complete([NSError errorWithDomain:@"securityd"
414 code:kSOSCCNoSuchView
415 userInfo:@{NSLocalizedDescriptionKey: [NSString stringWithFormat: @"No syncing view for view hint '%@'", viewHint]}]);
419 [view setCurrentItemForAccessGroup:newItem
421 accessGroup:accessGroup
422 identifier:identifier
428 -(void)getCurrentItemForAccessGroup:(NSString*)accessGroup
429 identifier:(NSString*)identifier
430 viewHint:(NSString*)viewHint
431 fetchCloudValue:(bool)fetchCloudValue
432 complete:(void (^) (NSString* uuid, NSError* operror)) complete
434 CKKSKeychainView* view = [self findView:viewHint];
436 secinfo("ckks", "No CKKS view for %@, skipping current fetch request", viewHint);
437 complete(NULL, [NSError errorWithDomain:@"securityd"
438 code:kSOSCCNoSuchView
439 userInfo:@{NSLocalizedDescriptionKey: [NSString stringWithFormat: @"No view for '%@'", viewHint]}]);
443 [view getCurrentItemForAccessGroup:accessGroup
444 identifier:identifier
445 fetchCloudValue:fetchCloudValue
450 + (instancetype) manager {
451 return [self resetManager: false setTo: nil];
454 + (instancetype) resetManager: (bool) reset setTo: (CKKSViewManager*) obj {
455 static CKKSViewManager* manager = nil;
457 if([CKDatabase class] == nil) {
458 secerror("CKKS: CloudKit.framework appears to not be linked. Can't create CKKS objects.");
462 if(!manager || reset || obj) {
463 @synchronized([self class]) {
465 [manager clearAllViews];
469 [manager clearAllViews];
471 } else if (manager == nil) {
472 manager = [[CKKSViewManager alloc] initCloudKitWithContainerName:SecCKKSContainerName usePCS:SecCKKSContainerUsePCS];
481 - (void)cancelPendingOperations {
482 [self.savedTLKNotifier cancel];
485 -(void)notifyNewTLKsInKeychain {
486 // Why two functions here? Limitation of OCMock, unfortunately: can't stub and expect the same method
487 secnotice("ckksbackup", "New TLKs have arrived");
488 [CKKSViewManager syncBackupAndNotifyAboutSync];
491 +(void)syncBackupAndNotifyAboutSync {
492 SOSAccount* account = (__bridge SOSAccount*)SOSKeychainAccountGetSharedAccount();
494 [account performTransaction:^(SOSAccountTransaction * _Nonnull txn) {
495 CFErrorRef error = NULL;
496 NSSet* ignore = CFBridgingRelease(SOSAccountCopyBackupPeersAndForceSync(txn, &error));
500 secerror("ckksbackup: Couldn't process sync with backup peers: %@", error);
502 secnotice("ckksbackup", "telling CloudServices about TLK arrival");
503 notify_post(kSecItemBackupNotification);
508 #pragma mark - XPC Endpoint
510 - (xpc_endpoint_t)xpcControlEndpoint {
511 return [_listener.endpoint _endpoint];
514 - (BOOL)listener:(__unused NSXPCListener *)listener shouldAcceptNewConnection:(NSXPCConnection *)newConnection {
515 NSNumber *num = [newConnection valueForEntitlement:(__bridge NSString *)kSecEntitlementPrivateCKKS];
516 if (![num isKindOfClass:[NSNumber class]] || ![num boolValue]) {
517 secinfo("ckks", "Client pid: %d doesn't have entitlement: %@",
518 [newConnection processIdentifier], kSecEntitlementPrivateCKKS);
521 newConnection.exportedInterface = CKKSSetupControlProtocol([NSXPCInterface interfaceWithProtocol:@protocol(CKKSControlProtocol)]);
522 newConnection.exportedObject = self;
524 [newConnection resume];
528 #pragma mark - RPCs to manage and report state
530 - (void)performanceCounters:(void(^)(NSDictionary <NSString *, NSNumber *> *counter))reply {
531 reply(@{ @"fake" : @(10) });
534 - (void)rpcResetLocal:(NSString*)viewName reply: (void(^)(NSError* result)) reply {
535 NSArray* actualViews = nil;
537 secnotice("ckksreset", "Received a local reset RPC for zone %@", viewName);
538 CKKSKeychainView* view = self.views[viewName];
541 secerror("ckks: Zone %@ does not exist!", viewName);
542 reply([NSError errorWithDomain:@"securityd"
543 code:kSOSCCNoSuchView
544 userInfo:@{NSLocalizedDescriptionKey: [NSString stringWithFormat: @"No view for '%@'", viewName]}]);
548 actualViews = @[view];
550 secnotice("ckksreset", "Received a local reset RPC for all zones");
551 @synchronized(self.views) {
552 // Can't safely iterate a mutable collection, so copy it.
553 actualViews = [self.views.allValues copy];
557 CKKSResultOperation* op = [CKKSResultOperation named:@"local-reset-zones-waiter" withBlock:^{}];
559 for(CKKSKeychainView* view in actualViews) {
560 ckksnotice("ckksreset", view, "Beginning local reset for %@", view);
561 [op addSuccessDependency:[view resetLocalData]];
564 [op timeout:120*NSEC_PER_SEC];
565 [self.operationQueue addOperation: op];
567 [op waitUntilFinished];
569 secnotice("ckksreset", "Completed rpcResetLocal");
571 secnotice("ckksreset", "Completed rpcResetLocal with error: %@", op.error);
576 - (void)rpcResetCloudKit:(NSString*)viewName reply: (void(^)(NSError* result)) reply {
577 NSArray* actualViews = nil;
579 secnotice("ckksreset", "Received a cloudkit reset RPC for zone %@", viewName);
580 CKKSKeychainView* view = self.views[viewName];
583 secerror("ckks: Zone %@ does not exist!", viewName);
584 reply([NSError errorWithDomain:@"securityd"
585 code:kSOSCCNoSuchView
586 userInfo:@{NSLocalizedDescriptionKey: [NSString stringWithFormat: @"No view for '%@'", viewName]}]);
590 actualViews = @[view];
592 secnotice("ckksreset", "Received a cloudkit reset RPC for all zones");
593 @synchronized(self.views) {
594 // Can't safely iterate a mutable collection, so copy it.
595 actualViews = [self.views.allValues copy];
599 CKKSResultOperation* op = [CKKSResultOperation named:@"cloudkit-reset-zones-waiter" withBlock:^{}];
601 for(CKKSKeychainView* view in actualViews) {
602 ckksnotice("ckksreset", view, "Beginning CloudKit reset for %@", view);
603 [op addSuccessDependency:[view resetCloudKitZone]];
606 [op timeout:120*NSEC_PER_SEC];
607 [self.operationQueue addOperation: op];
609 [op waitUntilFinished];
611 secnotice("ckksreset", "Completed rpcResetCloudKit");
613 secnotice("ckksreset", "Completed rpcResetCloudKit with error: %@", op.error);
618 - (void)rpcResync:(NSString*)viewName reply: (void(^)(NSError* result)) reply {
619 secnotice("ckksresync", "Received a resync RPC for zone %@. Beginning resync...", viewName);
621 NSArray* actualViews = nil;
623 secnotice("ckks", "Received a resync RPC for zone %@", viewName);
624 CKKSKeychainView* view = self.views[viewName];
627 secerror("ckks: Zone %@ does not exist!", viewName);
632 actualViews = @[view];
635 @synchronized(self.views) {
636 // Can't safely iterate a mutable collection, so copy it.
637 actualViews = [self.views.allValues copy];
641 CKKSResultOperation* op = [[CKKSResultOperation alloc] init];
642 op.name = @"rpc-resync";
643 __weak __typeof(op) weakOp = op;
644 [op addExecutionBlock:^{
645 __strong __typeof(op) strongOp = weakOp;
646 secnotice("ckks", "Ending rsync rpc with %@", strongOp.error);
649 for(CKKSKeychainView* view in actualViews) {
650 ckksnotice("ckksresync", view, "Beginning resync for %@", view);
652 CKKSSynchronizeOperation* resyncOp = [view resyncWithCloud];
653 [op addSuccessDependency:resyncOp];
656 [op timeout:120*NSEC_PER_SEC];
657 [self.operationQueue addOperation:op];
658 [op waitUntilFinished];
662 - (void)rpcStatus: (NSString*)viewName reply: (void(^)(NSArray<NSDictionary*>* result, NSError* error)) reply {
663 NSMutableArray* a = [[NSMutableArray alloc] init];
665 NSArray* actualViews = nil;
667 secnotice("ckks", "Received a status RPC for zone %@", viewName);
668 CKKSKeychainView* view = self.views[viewName];
671 secerror("ckks: Zone %@ does not exist!", viewName);
676 actualViews = @[view];
679 @synchronized(self.views) {
680 // Can't safely iterate a mutable collection, so copy it.
681 actualViews = self.views.allValues;
685 for(CKKSKeychainView* view in actualViews) {
686 ckksnotice("ckks", view, "Fetching status for %@", view.zoneName);
687 NSDictionary* status = [view status];
688 ckksinfo("ckks", view, "Status is %@", status);
690 [a addObject: status];
697 - (void)rpcFetchAndProcessChanges:(NSString*)viewName reply: (void(^)(NSError* result))reply {
698 [self rpcFetchAndProcessChanges:viewName classA:false reply: (void(^)(NSError* result))reply];
701 - (void)rpcFetchAndProcessClassAChanges:(NSString*)viewName reply: (void(^)(NSError* result))reply {
702 [self rpcFetchAndProcessChanges:viewName classA:true reply:(void(^)(NSError* result))reply];
705 - (void)rpcFetchAndProcessChanges:(NSString*)viewName classA:(bool)classAError reply: (void(^)(NSError* result)) reply {
706 NSArray* actualViews = nil;
708 secnotice("ckks", "Received a fetch RPC for zone %@", viewName);
709 CKKSKeychainView* view = self.views[viewName];
712 secerror("ckks: Zone %@ does not exist!", viewName);
713 reply([NSError errorWithDomain:@"securityd"
714 code:kSOSCCNoSuchView
715 userInfo:@{NSLocalizedDescriptionKey: [NSString stringWithFormat: @"No view for '%@'", viewName]}]);
719 actualViews = @[view];
721 secnotice("ckks", "Received a fetch RPC for all zones");
722 @synchronized(self.views) {
723 // Can't safely iterate a mutable collection, so copy it.
724 actualViews = [self.views.allValues copy];
728 CKKSResultOperation* blockOp = [[CKKSResultOperation alloc] init];
729 blockOp.name = @"rpc-fetch-and-process-result";
730 __weak __typeof(blockOp) weakBlockOp = blockOp;
731 // Use the completion block instead of the operation block, so that it runs even if the cancel fires
732 [blockOp setCompletionBlock:^{
733 __strong __typeof(blockOp) strongBlockOp = weakBlockOp;
734 [strongBlockOp allDependentsSuccessful];
735 reply(strongBlockOp.error);
738 for(CKKSKeychainView* view in actualViews) {
739 ckksnotice("ckks", view, "Beginning fetch for %@", view);
741 CKKSResultOperation* op = [view processIncomingQueue:classAError after:[view.zoneChangeFetcher requestSuccessfulFetch: CKKSFetchBecauseAPIFetchRequest]];
742 [blockOp addDependency:op];
745 [self.operationQueue addOperation: [blockOp timeout:60*NSEC_PER_SEC]];
748 - (void)rpcPushOutgoingChanges:(NSString*)viewName reply: (void(^)(NSError* result))reply {
749 NSArray* actualViews = nil;
751 secnotice("ckks", "Received a push RPC for zone %@", viewName);
752 CKKSKeychainView* view = self.views[viewName];
755 secerror("ckks: Zone %@ does not exist!", viewName);
756 reply([NSError errorWithDomain:@"securityd"
757 code:kSOSCCNoSuchView
758 userInfo:@{NSLocalizedDescriptionKey: [NSString stringWithFormat: @"No view for '%@'", viewName]}]);
762 actualViews = @[view];
764 secnotice("ckks", "Received a push RPC for all zones");
765 @synchronized(self.views) {
766 // Can't safely iterate a mutable collection, so copy it.
767 actualViews = [self.views.allValues copy];
771 CKKSResultOperation* blockOp = [[CKKSResultOperation alloc] init];
772 blockOp.name = @"rpc-push";
773 __weak __typeof(blockOp) weakBlockOp = blockOp;
774 // Use the completion block instead of the operation block, so that it runs even if the cancel fires
775 [blockOp setCompletionBlock:^{
776 __strong __typeof(blockOp) strongBlockOp = weakBlockOp;
777 [strongBlockOp allDependentsSuccessful];
778 reply(strongBlockOp.error);
781 for(CKKSKeychainView* view in actualViews) {
782 ckksnotice("ckks-rpc", view, "Beginning push for %@", view);
784 CKKSResultOperation* op = [view processOutgoingQueue: [CKOperationGroup CKKSGroupWithName:@"rpc-push"]];
785 [blockOp addDependency:op];
788 [self.operationQueue addOperation: [blockOp timeout:60*NSEC_PER_SEC]];
791 - (void)rpcGetAnalyticsSysdiagnoseWithReply:(void (^)(NSString* sysdiagnose, NSError* error))reply
793 NSError* error = nil;
794 NSString* sysdiagnose = [[CKKSAnalyticsLogger logger] getSysdiagnoseDumpWithError:&error];
795 reply(sysdiagnose, error);
798 - (void)rpcGetAnalyticsJSONWithReply:(void (^)(NSData* json, NSError* error))reply
800 NSError* error = nil;
801 NSData* json = [[CKKSAnalyticsLogger logger] getLoggingJSONWithError:&error];
805 - (void)rpcForceUploadAnalyticsWithReply:(void (^)(BOOL success, NSError* error))reply
807 NSError* error = nil;
808 BOOL result = [[CKKSAnalyticsLogger logger] forceUploadWithError:&error];
809 reply(result, error);
812 -(void)xpc24HrNotification {
813 // XPC has poked us and said we should do some cleanup!
815 // For now, poke the views and tell them to update their device states if they'd like
816 NSArray* actualViews = nil;
817 @synchronized(self.views) {
818 // Can't safely iterate a mutable collection, so copy it.
819 actualViews = self.views.allValues;
822 secnotice("ckks", "Received a 24hr notification from XPC");
823 CKOperationGroup* group = [CKOperationGroup CKKSGroupWithName:@"periodic-device-state-update"];
824 for(CKKSKeychainView* view in actualViews) {
825 ckksnotice("ckks", view, "Starting device state XPC update");
826 // Let the update know it should rate-limit itself
827 [view updateDeviceState:true ckoperationGroup:group];