]> git.saurik.com Git - apple/security.git/blob - keychain/ckks/CKKSViewManager.m
7b6b38f5b50a3dbef82e5b0680f8ff32ada70744
[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 "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"
34
35 #import "SecEntitlements.h"
36
37 #include <securityd/SecDbItem.h>
38 #include <securityd/SecDbKeychainItem.h>
39 #include <securityd/SecItemSchema.h>
40 #include <Security/SecureObjectSync/SOSViews.h>
41
42 #import <Foundation/NSXPCConnection.h>
43 #import <Foundation/NSXPCConnection_Private.h>
44
45 #include <Security/SecureObjectSync/SOSAccount.h>
46 #include <Security/SecItemBackup.h>
47
48 #if OCTAGON
49 #import <CloudKit/CloudKit.h>
50 #import <CloudKit/CloudKit_Private.h>
51 #endif
52
53 @interface CKKSViewManager () <NSXPCListenerDelegate>
54 #if OCTAGON
55 @property NSXPCListener *listener;
56
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;
64
65 @property NSMutableDictionary<NSString*, CKKSKeychainView*>* views;
66
67 @property NSMutableDictionary<NSString*, SecBoolNSErrorCallback>* pendingSyncCallbacks;
68 @property CKKSNearFutureScheduler* savedTLKNotifier;;
69 @property NSOperationQueue* operationQueue;
70 #endif
71 @end
72
73 @implementation CKKSViewManager
74 #if OCTAGON
75
76 - (instancetype)initCloudKitWithContainerName: (NSString*) containerName usePCS:(bool)usePCS {
77 return [self initWithContainerName:containerName
78 usePCS:usePCS
79 fetchRecordZoneChangesOperationClass:[CKFetchRecordZoneChangesOperation class]
80 modifySubscriptionsOperationClass:[CKModifySubscriptionsOperation class]
81 modifyRecordZonesOperationClass:[CKModifyRecordZonesOperation class]
82 apsConnectionClass:[APSConnection class]
83 nsnotificationCenterClass:[NSNotificationCenter class]
84 notifierClass:[CKKSNotifyPostNotifier class]
85 setupHold:nil];
86 }
87
88 - (instancetype)initWithContainerName: (NSString*) containerName
89 usePCS:(bool)usePCS
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;
104
105 _container = [self makeCKContainer: containerName usePCS:usePCS];
106 _accountTracker = [[CKKSCKAccountStateTracker alloc] init:self.container nsnotificationCenterClass:nsnotificationCenterClass];
107 _lockStateTracker = [[CKKSLockStateTracker alloc] init];
108
109 _operationQueue = [[NSOperationQueue alloc] init];
110
111 _views = [[NSMutableDictionary alloc] init];
112 _pendingSyncCallbacks = [[NSMutableDictionary alloc] init];
113
114 _zoneStartupDependency = setupHold;
115 _initializeNewZones = false;
116
117 _completedSecCKKSInitialize = [[CKKSCondition alloc] init];
118
119 __weak __typeof(self) weakSelf = self;
120 _savedTLKNotifier = [[CKKSNearFutureScheduler alloc] initWithName: @"newtlks" delay:5*NSEC_PER_SEC keepProcessAlive:true block:^{
121 [weakSelf notifyNewTLKsInKeychain];
122 }];
123
124 _listener = [NSXPCListener anonymousListener];
125 _listener.delegate = self;
126 [_listener resume];
127 }
128 return self;
129 }
130
131 -(CKContainer*)makeCKContainer:(NSString*)containerName usePCS:(bool)usePCS {
132 CKContainer* container = [CKContainer containerWithIdentifier:containerName];
133 if(!usePCS) {
134 CKContainerOptions* containerOptions = [[CKContainerOptions alloc] init];
135 containerOptions.bypassPCSEncryption = YES;
136
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];
139 }
140 return container;
141 }
142
143 -(void)dealloc {
144 [self clearAllViews];
145 }
146
147 dispatch_queue_t globalZoneStateQueue = NULL;
148 dispatch_once_t globalZoneStateQueueOnce;
149
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);
155 });
156
157 if(_globalRateLimiter != nil) {
158 return _globalRateLimiter;
159 }
160
161 __block CKKSRateLimiter* blocklimit = nil;
162
163 dispatch_sync(globalZoneStateQueue, ^{
164 NSError* error = nil;
165
166 // Special object containing state for all zones. Currently, just the rate limiter.
167 CKKSZoneStateEntry* allEntry = [CKKSZoneStateEntry tryFromDatabase: @"all" error:&error];
168
169 if(error) {
170 secerror("CKKSViewManager: couldn't load global zone state: %@", error);
171 }
172
173 if(!error && allEntry.rateLimiter) {
174 blocklimit = allEntry.rateLimiter;
175 } else {
176 blocklimit = [[CKKSRateLimiter alloc] init];
177 }
178 });
179 _globalRateLimiter = blocklimit;
180 return _globalRateLimiter;
181 }
182
183 // Mostly exists to be mocked out.
184 +(NSSet*)viewList {
185 return CFBridgingRelease(SOSViewCopyViewSet(kViewSetCKKS));
186 }
187
188 - (void)setView: (CKKSKeychainView*) obj {
189 CKKSKeychainView* kcv = nil;
190
191 @synchronized(self.views) {
192 kcv = self.views[obj.zoneName];
193 self.views[obj.zoneName] = obj;
194 }
195
196 if(kcv) {
197 [kcv cancelAllOperations];
198 }
199 }
200
201 - (void)clearAllViews {
202 NSArray<CKKSKeychainView*>* tempviews = nil;
203 @synchronized(self.views) {
204 tempviews = [self.views.allValues copy];
205 [self.views removeAllObjects];
206 }
207
208 for(CKKSKeychainView* view in tempviews) {
209 [view cancelAllOperations];
210 }
211 }
212
213 - (void)clearView:(NSString*) viewName {
214 CKKSKeychainView* kcv = nil;
215 @synchronized(self.views) {
216 kcv = self.views[viewName];
217 self.views[viewName] = nil;
218 }
219
220 if(kcv) {
221 [kcv cancelAllOperations];
222 }
223 }
224
225 - (CKKSKeychainView*)findView:(NSString*)viewName {
226 if(!viewName) {
227 return nil;
228 }
229 @synchronized(self.views) {
230 return self.views[viewName];
231 }
232 }
233
234 - (CKKSKeychainView*)findOrCreateView:(NSString*)viewName {
235 @synchronized(self.views) {
236 CKKSKeychainView* kcv = self.views[viewName];
237 if(kcv) {
238 return kcv;
239 }
240
241 self.views[viewName] = [[CKKSKeychainView alloc] initWithContainer: self.container
242 zoneName: viewName
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];
251
252 if(self.zoneStartupDependency) {
253 [self.views[viewName].zoneSetupOperation addDependency: self.zoneStartupDependency];
254 }
255
256 if(self.initializeNewZones) {
257 [self.views[viewName] initializeZone];
258 }
259
260 return self.views[viewName];
261 }
262 }
263 + (CKKSKeychainView*)findOrCreateView:(NSString*)viewName {
264 return [[CKKSViewManager manager] findOrCreateView: viewName];
265 }
266
267 - (NSDictionary<NSString *,NSString *> *)activeTLKs
268 {
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;
274 if (tlk) {
275 tlks[name] = tlk;
276 }
277 }
278 }
279 return tlks;
280 }
281
282 - (CKKSKeychainView*)restartZone:(NSString*)viewName {
283 @synchronized(self.views) {
284 self.views[viewName] = nil;
285 }
286 return [self findOrCreateView: viewName];
287 }
288
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");
293 return;
294 }
295
296 @synchronized(self.views) {
297 self.initializeNewZones = true;
298
299 NSSet* viewSet = [CKKSViewManager viewList];
300 for(NSString* s in viewSet) {
301 [self findOrCreateView:s]; // initializes any newly-created views
302 }
303 }
304 }
305
306 - (NSString*)viewNameForViewHint: (NSString*) viewHint {
307 // For now, choose view based on viewhints.
308 if(viewHint && ![viewHint isEqual: [NSNull null]]) {
309 return viewHint;
310 }
311
312 // If there isn't a provided view hint, use the "keychain" view if we're testing. Otherwise, nil.
313 if(SecCKKSTestsEnabled()) {
314 return @"keychain";
315 } else {
316 return nil;
317 }
318 }
319
320 - (NSString*)viewNameForItem: (SecDbItemRef) item {
321 CFErrorRef cferror = NULL;
322 NSString* viewHint = (__bridge NSString*) SecDbItemGetValue(item, &v7vwht, &cferror);
323
324 if(cferror) {
325 secerror("ckks: Couldn't fetch the viewhint for some reason: %@", cferror);
326 CFReleaseNull(cferror);
327 viewHint = nil;
328 }
329
330 return [self viewNameForViewHint: viewHint];
331 }
332
333 - (NSString*)viewNameForAttributes: (NSDictionary*) item {
334 return [self viewNameForViewHint: item[(id)kSecAttrSyncViewHint]];
335 }
336
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;
341 }
342 }
343
344 - (void) handleKeychainEventDbConnection: (SecDbConnectionRef) dbconn source:(SecDbTransactionSource)txionSource added: (SecDbItemRef) added deleted: (SecDbItemRef) deleted {
345
346 SecDbItemRef modified = added ? added : deleted;
347
348 NSString* viewName = [self viewNameForItem: modified];
349 NSString* keyViewName = [CKKSKey isItemKeyForKeychainView: modified];
350
351 if(keyViewName) {
352 // This might be some key material for this view! Poke it.
353 CKKSKeychainView* view = [self findView: keyViewName];
354
355 if(!SecCKKSTestDisableKeyNotifications()) {
356 ckksnotice("ckks", view, "Potential new key material from %@ (source %lu)", keyViewName, txionSource);
357 [view keyStateMachineRequestProcess];
358 } else {
359 ckksnotice("ckks", view, "Ignoring potential new key material from %@ (source %lu)", keyViewName, txionSource);
360 }
361 return;
362 }
363
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");
368 }
369
370 // Looks like a normal item. Proceed!
371 CKKSKeychainView* view = [self findView:viewName];
372
373 NSString* uuid = (__bridge NSString*) SecDbItemGetValue(modified, &v10itemuuid, NULL);
374 SecBoolNSErrorCallback syncCallback = nil;
375 if(uuid) {
376 @synchronized(self.pendingSyncCallbacks) {
377 syncCallback = self.pendingSyncCallbacks[uuid];
378 self.pendingSyncCallbacks[uuid] = nil;
379
380 if(syncCallback) {
381 secinfo("ckks", "Have a pending callback for %@; passing along", uuid);
382 }
383 }
384 }
385
386 if(!view) {
387 secinfo("ckks", "No CKKS view for %@, skipping: %@", viewName, modified);
388 if(syncCallback) {
389 syncCallback(false, [NSError errorWithDomain:@"securityd"
390 code:kSOSCCNoSuchView
391 userInfo:@{NSLocalizedDescriptionKey: [NSString stringWithFormat: @"No syncing view for '%@'", viewName]}]);
392 }
393 return;
394 }
395
396 ckksnotice("ckks", view, "Routing item to zone %@: %@", viewName, modified);
397 [view handleKeychainEventDbConnection: dbconn added:added deleted:deleted rateLimiter:self.globalRateLimiter syncCallback: syncCallback];
398 }
399
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
408 {
409 CKKSKeychainView* view = [self findView:viewHint];
410
411 if(!view) {
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]}]);
416 return;
417 }
418
419 [view setCurrentItemForAccessGroup:newItem
420 hash:newItemSHA1
421 accessGroup:accessGroup
422 identifier:identifier
423 replacing:oldItem
424 hash:oldItemSHA1
425 complete:complete];
426 }
427
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
433 {
434 CKKSKeychainView* view = [self findView:viewHint];
435 if(!view) {
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]}]);
440 return;
441 }
442
443 [view getCurrentItemForAccessGroup:accessGroup
444 identifier:identifier
445 fetchCloudValue:fetchCloudValue
446 complete:complete];
447 }
448
449
450 + (instancetype) manager {
451 return [self resetManager: false setTo: nil];
452 }
453
454 + (instancetype) resetManager: (bool) reset setTo: (CKKSViewManager*) obj {
455 static CKKSViewManager* manager = nil;
456
457 if([CKDatabase class] == nil) {
458 secerror("CKKS: CloudKit.framework appears to not be linked. Can't create CKKS objects.");
459 return nil;
460 }
461
462 if(!manager || reset || obj) {
463 @synchronized([self class]) {
464 if(obj != nil) {
465 [manager clearAllViews];
466 manager = obj;
467 } else {
468 if(reset) {
469 [manager clearAllViews];
470 manager = nil;
471 } else if (manager == nil) {
472 manager = [[CKKSViewManager alloc] initCloudKitWithContainerName:SecCKKSContainerName usePCS:SecCKKSContainerUsePCS];
473 }
474 }
475 }
476 }
477
478 return manager;
479 }
480
481 - (void)cancelPendingOperations {
482 [self.savedTLKNotifier cancel];
483 }
484
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];
489 }
490
491 +(void)syncBackupAndNotifyAboutSync {
492 SOSAccount* account = (__bridge SOSAccount*)SOSKeychainAccountGetSharedAccount();
493
494 [account performTransaction:^(SOSAccountTransaction * _Nonnull txn) {
495 CFErrorRef error = NULL;
496 NSSet* ignore = CFBridgingRelease(SOSAccountCopyBackupPeersAndForceSync(txn, &error));
497 (void)ignore;
498
499 if(error) {
500 secerror("ckksbackup: Couldn't process sync with backup peers: %@", error);
501 } else {
502 secnotice("ckksbackup", "telling CloudServices about TLK arrival");
503 notify_post(kSecItemBackupNotification);
504 };
505 }];
506 }
507
508 #pragma mark - XPC Endpoint
509
510 - (xpc_endpoint_t)xpcControlEndpoint {
511 return [_listener.endpoint _endpoint];
512 }
513
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);
519 return NO;
520 }
521 newConnection.exportedInterface = CKKSSetupControlProtocol([NSXPCInterface interfaceWithProtocol:@protocol(CKKSControlProtocol)]);
522 newConnection.exportedObject = self;
523
524 [newConnection resume];
525
526 return YES;
527 }
528 #pragma mark - RPCs to manage and report state
529
530 - (void)performanceCounters:(void(^)(NSDictionary <NSString *, NSNumber *> *counter))reply {
531 reply(@{ @"fake" : @(10) });
532 }
533
534 - (void)rpcResetLocal:(NSString*)viewName reply: (void(^)(NSError* result)) reply {
535 NSArray* actualViews = nil;
536 if(viewName) {
537 secnotice("ckksreset", "Received a local reset RPC for zone %@", viewName);
538 CKKSKeychainView* view = self.views[viewName];
539
540 if(!view) {
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]}]);
545 return;
546 }
547
548 actualViews = @[view];
549 } else {
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];
554 }
555 }
556
557 CKKSResultOperation* op = [CKKSResultOperation named:@"local-reset-zones-waiter" withBlock:^{}];
558
559 for(CKKSKeychainView* view in actualViews) {
560 ckksnotice("ckksreset", view, "Beginning local reset for %@", view);
561 [op addSuccessDependency:[view resetLocalData]];
562 }
563
564 [op timeout:120*NSEC_PER_SEC];
565 [self.operationQueue addOperation: op];
566
567 [op waitUntilFinished];
568 if(op.error) {
569 secnotice("ckksreset", "Completed rpcResetLocal");
570 } else {
571 secnotice("ckksreset", "Completed rpcResetLocal with error: %@", op.error);
572 }
573 reply(op.error);
574 }
575
576 - (void)rpcResetCloudKit:(NSString*)viewName reply: (void(^)(NSError* result)) reply {
577 NSArray* actualViews = nil;
578 if(viewName) {
579 secnotice("ckksreset", "Received a cloudkit reset RPC for zone %@", viewName);
580 CKKSKeychainView* view = self.views[viewName];
581
582 if(!view) {
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]}]);
587 return;
588 }
589
590 actualViews = @[view];
591 } else {
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];
596 }
597 }
598
599 CKKSResultOperation* op = [CKKSResultOperation named:@"cloudkit-reset-zones-waiter" withBlock:^{}];
600
601 for(CKKSKeychainView* view in actualViews) {
602 ckksnotice("ckksreset", view, "Beginning CloudKit reset for %@", view);
603 [op addSuccessDependency:[view resetCloudKitZone]];
604 }
605
606 [op timeout:120*NSEC_PER_SEC];
607 [self.operationQueue addOperation: op];
608
609 [op waitUntilFinished];
610 if(op.error) {
611 secnotice("ckksreset", "Completed rpcResetCloudKit");
612 } else {
613 secnotice("ckksreset", "Completed rpcResetCloudKit with error: %@", op.error);
614 }
615 reply(op.error);
616 }
617
618 - (void)rpcResync:(NSString*)viewName reply: (void(^)(NSError* result)) reply {
619 secnotice("ckksresync", "Received a resync RPC for zone %@. Beginning resync...", viewName);
620
621 NSArray* actualViews = nil;
622 if(viewName) {
623 secnotice("ckks", "Received a resync RPC for zone %@", viewName);
624 CKKSKeychainView* view = self.views[viewName];
625
626 if(!view) {
627 secerror("ckks: Zone %@ does not exist!", viewName);
628 reply(nil);
629 return;
630 }
631
632 actualViews = @[view];
633
634 } else {
635 @synchronized(self.views) {
636 // Can't safely iterate a mutable collection, so copy it.
637 actualViews = [self.views.allValues copy];
638 }
639 }
640
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);
647 }];
648
649 for(CKKSKeychainView* view in actualViews) {
650 ckksnotice("ckksresync", view, "Beginning resync for %@", view);
651
652 CKKSSynchronizeOperation* resyncOp = [view resyncWithCloud];
653 [op addSuccessDependency:resyncOp];
654 }
655
656 [op timeout:120*NSEC_PER_SEC];
657 [self.operationQueue addOperation:op];
658 [op waitUntilFinished];
659 reply(op.error);
660 }
661
662 - (void)rpcStatus: (NSString*)viewName reply: (void(^)(NSArray<NSDictionary*>* result, NSError* error)) reply {
663 NSMutableArray* a = [[NSMutableArray alloc] init];
664
665 NSArray* actualViews = nil;
666 if(viewName) {
667 secnotice("ckks", "Received a status RPC for zone %@", viewName);
668 CKKSKeychainView* view = self.views[viewName];
669
670 if(!view) {
671 secerror("ckks: Zone %@ does not exist!", viewName);
672 reply(nil, nil);
673 return;
674 }
675
676 actualViews = @[view];
677
678 } else {
679 @synchronized(self.views) {
680 // Can't safely iterate a mutable collection, so copy it.
681 actualViews = self.views.allValues;
682 }
683 }
684
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);
689 if(status) {
690 [a addObject: status];
691 }
692 }
693 reply(a, nil);
694 return;
695 }
696
697 - (void)rpcFetchAndProcessChanges:(NSString*)viewName reply: (void(^)(NSError* result))reply {
698 [self rpcFetchAndProcessChanges:viewName classA:false reply: (void(^)(NSError* result))reply];
699 }
700
701 - (void)rpcFetchAndProcessClassAChanges:(NSString*)viewName reply: (void(^)(NSError* result))reply {
702 [self rpcFetchAndProcessChanges:viewName classA:true reply:(void(^)(NSError* result))reply];
703 }
704
705 - (void)rpcFetchAndProcessChanges:(NSString*)viewName classA:(bool)classAError reply: (void(^)(NSError* result)) reply {
706 NSArray* actualViews = nil;
707 if(viewName) {
708 secnotice("ckks", "Received a fetch RPC for zone %@", viewName);
709 CKKSKeychainView* view = self.views[viewName];
710
711 if(!view) {
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]}]);
716 return;
717 }
718
719 actualViews = @[view];
720 } else {
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];
725 }
726 }
727
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);
736 }];
737
738 for(CKKSKeychainView* view in actualViews) {
739 ckksnotice("ckks", view, "Beginning fetch for %@", view);
740
741 CKKSResultOperation* op = [view processIncomingQueue:classAError after:[view.zoneChangeFetcher requestSuccessfulFetch: CKKSFetchBecauseAPIFetchRequest]];
742 [blockOp addDependency:op];
743 }
744
745 [self.operationQueue addOperation: [blockOp timeout:60*NSEC_PER_SEC]];
746 }
747
748 - (void)rpcPushOutgoingChanges:(NSString*)viewName reply: (void(^)(NSError* result))reply {
749 NSArray* actualViews = nil;
750 if(viewName) {
751 secnotice("ckks", "Received a push RPC for zone %@", viewName);
752 CKKSKeychainView* view = self.views[viewName];
753
754 if(!view) {
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]}]);
759 return;
760 }
761
762 actualViews = @[view];
763 } else {
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];
768 }
769 }
770
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);
779 }];
780
781 for(CKKSKeychainView* view in actualViews) {
782 ckksnotice("ckks-rpc", view, "Beginning push for %@", view);
783
784 CKKSResultOperation* op = [view processOutgoingQueue: [CKOperationGroup CKKSGroupWithName:@"rpc-push"]];
785 [blockOp addDependency:op];
786 }
787
788 [self.operationQueue addOperation: [blockOp timeout:60*NSEC_PER_SEC]];
789 }
790
791 - (void)rpcGetAnalyticsSysdiagnoseWithReply:(void (^)(NSString* sysdiagnose, NSError* error))reply
792 {
793 NSError* error = nil;
794 NSString* sysdiagnose = [[CKKSAnalyticsLogger logger] getSysdiagnoseDumpWithError:&error];
795 reply(sysdiagnose, error);
796 }
797
798 - (void)rpcGetAnalyticsJSONWithReply:(void (^)(NSData* json, NSError* error))reply
799 {
800 NSError* error = nil;
801 NSData* json = [[CKKSAnalyticsLogger logger] getLoggingJSONWithError:&error];
802 reply(json, error);
803 }
804
805 - (void)rpcForceUploadAnalyticsWithReply:(void (^)(BOOL success, NSError* error))reply
806 {
807 NSError* error = nil;
808 BOOL result = [[CKKSAnalyticsLogger logger] forceUploadWithError:&error];
809 reply(result, error);
810 }
811
812 -(void)xpc24HrNotification {
813 // XPC has poked us and said we should do some cleanup!
814
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;
820 }
821
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];
828 }
829 }
830
831 #endif // OCTAGON
832 @end