]> git.saurik.com Git - apple/security.git/blob - keychain/ckks/CKKSViewManager.m
Security-58286.31.2.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 "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
52 #import <SecurityFoundation/SFKey.h>
53 #import <SecurityFoundation/SFKey_Private.h>
54 #endif
55
56 @interface CKKSViewManager () <NSXPCListenerDelegate>
57 #if OCTAGON
58 @property NSXPCListener *listener;
59
60 // Once you set these, all CKKSKeychainViews created will use them
61 @property (readonly) Class<CKKSFetchRecordZoneChangesOperation> fetchRecordZoneChangesOperationClass;
62 @property (readonly) Class<CKKSFetchRecordsOperation> fetchRecordsOperationClass;
63 @property (readonly) Class<CKKSQueryOperation> queryOperationClass;
64 @property (readonly) Class<CKKSModifySubscriptionsOperation> modifySubscriptionsOperationClass;
65 @property (readonly) Class<CKKSModifyRecordZonesOperation> modifyRecordZonesOperationClass;
66 @property (readonly) Class<CKKSAPSConnection> apsConnectionClass;
67 @property (readonly) Class<CKKSNotifier> notifierClass;
68 @property (readonly) Class<CKKSNSNotificationCenter> nsnotificationCenterClass;
69
70 @property NSMutableDictionary<NSString*, CKKSKeychainView*>* views;
71
72 @property NSMutableDictionary<NSString*, SecBoolNSErrorCallback>* pendingSyncCallbacks;
73 @property CKKSNearFutureScheduler* savedTLKNotifier;;
74 @property NSOperationQueue* operationQueue;
75
76 @property NSMapTable<dispatch_queue_t, id<CKKSPeerUpdateListener>>* peerChangeListeners;
77 #endif
78 @end
79
80 @implementation CKKSViewManager
81 #if OCTAGON
82
83 - (instancetype)initCloudKitWithContainerName: (NSString*) containerName usePCS:(bool)usePCS {
84 return [self initWithContainerName:containerName
85 usePCS:usePCS
86 fetchRecordZoneChangesOperationClass:[CKFetchRecordZoneChangesOperation class]
87 fetchRecordsOperationClass:[CKFetchRecordsOperation class]
88 queryOperationClass:[CKQueryOperation class]
89 modifySubscriptionsOperationClass:[CKModifySubscriptionsOperation class]
90 modifyRecordZonesOperationClass:[CKModifyRecordZonesOperation class]
91 apsConnectionClass:[APSConnection class]
92 nsnotificationCenterClass:[NSNotificationCenter class]
93 notifierClass:[CKKSNotifyPostNotifier class]
94 setupHold:nil];
95 }
96
97 - (instancetype)initWithContainerName: (NSString*) containerName
98 usePCS:(bool)usePCS
99 fetchRecordZoneChangesOperationClass: (Class<CKKSFetchRecordZoneChangesOperation>) fetchRecordZoneChangesOperationClass
100 fetchRecordsOperationClass: (Class<CKKSFetchRecordsOperation>)fetchRecordsOperationClass
101 queryOperationClass:(Class<CKKSQueryOperation>)queryOperationClass
102 modifySubscriptionsOperationClass: (Class<CKKSModifySubscriptionsOperation>) modifySubscriptionsOperationClass
103 modifyRecordZonesOperationClass: (Class<CKKSModifyRecordZonesOperation>) modifyRecordZonesOperationClass
104 apsConnectionClass: (Class<CKKSAPSConnection>) apsConnectionClass
105 nsnotificationCenterClass: (Class<CKKSNSNotificationCenter>) nsnotificationCenterClass
106 notifierClass: (Class<CKKSNotifier>) notifierClass
107 setupHold: (NSOperation*) setupHold {
108 if(self = [super init]) {
109 _fetchRecordZoneChangesOperationClass = fetchRecordZoneChangesOperationClass;
110 _fetchRecordsOperationClass = fetchRecordsOperationClass;
111 _queryOperationClass = queryOperationClass;
112 _modifySubscriptionsOperationClass = modifySubscriptionsOperationClass;
113 _modifyRecordZonesOperationClass = modifyRecordZonesOperationClass;
114 _apsConnectionClass = apsConnectionClass;
115 _nsnotificationCenterClass = nsnotificationCenterClass;
116 _notifierClass = notifierClass;
117
118 _container = [self makeCKContainer: containerName usePCS:usePCS];
119 _accountTracker = [[CKKSCKAccountStateTracker alloc] init:self.container nsnotificationCenterClass:nsnotificationCenterClass];
120 _lockStateTracker = [[CKKSLockStateTracker alloc] init];
121
122 _operationQueue = [[NSOperationQueue alloc] init];
123
124 // Backwards from how we'd like, but it's the best way to have weak pointers to CKKSPeerUpdateListener.
125 _peerChangeListeners = [NSMapTable strongToWeakObjectsMapTable];
126
127 _views = [[NSMutableDictionary alloc] init];
128 _pendingSyncCallbacks = [[NSMutableDictionary alloc] init];
129
130 _zoneStartupDependency = setupHold;
131 _initializeNewZones = false;
132
133 _completedSecCKKSInitialize = [[CKKSCondition alloc] init];
134
135 __weak __typeof(self) weakSelf = self;
136 _savedTLKNotifier = [[CKKSNearFutureScheduler alloc] initWithName: @"newtlks" delay:5*NSEC_PER_SEC keepProcessAlive:true block:^{
137 [weakSelf notifyNewTLKsInKeychain];
138 }];
139
140 _listener = [NSXPCListener anonymousListener];
141 _listener.delegate = self;
142 [_listener resume];
143
144 // If this is a live server, register with notify
145 if(!SecCKKSTestsEnabled()) {
146 int token = 0;
147 notify_register_dispatch(kSOSCCCircleOctagonKeysChangedNotification, &token, dispatch_get_global_queue(QOS_CLASS_UTILITY, 0), ^(int t) {
148 // Since SOS doesn't change the self peer, we can reliably just send "trusted peers changed"; it'll be mostly right
149 secnotice("ckksshare", "Received a notification that the SOS Octagon peer set changed");
150 [weakSelf sendTrustedPeerSetChangedUpdate];
151 });
152 }
153 }
154 return self;
155 }
156
157 -(CKContainer*)makeCKContainer:(NSString*)containerName usePCS:(bool)usePCS {
158 CKContainer* container = [CKContainer containerWithIdentifier:containerName];
159 if(!usePCS) {
160 CKContainerOptions* containerOptions = [[CKContainerOptions alloc] init];
161 containerOptions.bypassPCSEncryption = YES;
162
163 // We don't have a great way to set these, so replace the entire container object
164 container = [[CKContainer alloc] initWithContainerID: container.containerID options:containerOptions];
165 }
166 return container;
167 }
168
169 -(void)dealloc {
170 [self clearAllViews];
171 }
172
173 dispatch_queue_t globalZoneStateQueue = NULL;
174 dispatch_once_t globalZoneStateQueueOnce;
175
176 // 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).
177 // Lazy-load it here.
178 - (CKKSRateLimiter*)getGlobalRateLimiter {
179 dispatch_once(&globalZoneStateQueueOnce, ^{
180 globalZoneStateQueue = dispatch_queue_create("CKKS global zone state", DISPATCH_QUEUE_SERIAL);
181 });
182
183 if(_globalRateLimiter != nil) {
184 return _globalRateLimiter;
185 }
186
187 __block CKKSRateLimiter* blocklimit = nil;
188
189 dispatch_sync(globalZoneStateQueue, ^{
190 NSError* error = nil;
191
192 // Special object containing state for all zones. Currently, just the rate limiter.
193 CKKSZoneStateEntry* allEntry = [CKKSZoneStateEntry tryFromDatabase: @"all" error:&error];
194
195 if(error) {
196 secerror("CKKSViewManager: couldn't load global zone state: %@", error);
197 }
198
199 if(!error && allEntry.rateLimiter) {
200 blocklimit = allEntry.rateLimiter;
201 } else {
202 blocklimit = [[CKKSRateLimiter alloc] init];
203 }
204 });
205 _globalRateLimiter = blocklimit;
206 return _globalRateLimiter;
207 }
208
209 // Mostly exists to be mocked out.
210 -(NSSet*)viewList {
211 return CFBridgingRelease(SOSViewCopyViewSet(kViewSetCKKS));
212 }
213
214 - (void)setView: (CKKSKeychainView*) obj {
215 CKKSKeychainView* kcv = nil;
216
217 @synchronized(self.views) {
218 kcv = self.views[obj.zoneName];
219 self.views[obj.zoneName] = obj;
220 }
221
222 if(kcv) {
223 [kcv cancelAllOperations];
224 }
225 }
226
227 - (void)clearAllViews {
228 NSArray<CKKSKeychainView*>* tempviews = nil;
229 @synchronized(self.views) {
230 tempviews = [self.views.allValues copy];
231 [self.views removeAllObjects];
232 }
233
234 for(CKKSKeychainView* view in tempviews) {
235 [view cancelAllOperations];
236 }
237 }
238
239 - (void)clearView:(NSString*) viewName {
240 CKKSKeychainView* kcv = nil;
241 @synchronized(self.views) {
242 kcv = self.views[viewName];
243 self.views[viewName] = nil;
244 }
245
246 if(kcv) {
247 [kcv cancelAllOperations];
248 }
249 }
250
251 - (CKKSKeychainView*)findView:(NSString*)viewName {
252 if(!viewName) {
253 return nil;
254 }
255 @synchronized(self.views) {
256 return self.views[viewName];
257 }
258 }
259
260 - (CKKSKeychainView*)findOrCreateView:(NSString*)viewName {
261 @synchronized(self.views) {
262 CKKSKeychainView* kcv = self.views[viewName];
263 if(kcv) {
264 return kcv;
265 }
266
267 self.views[viewName] = [[CKKSKeychainView alloc] initWithContainer: self.container
268 zoneName: viewName
269 accountTracker: self.accountTracker
270 lockStateTracker: self.lockStateTracker
271 savedTLKNotifier: self.savedTLKNotifier
272 peerProvider:self
273 fetchRecordZoneChangesOperationClass: self.fetchRecordZoneChangesOperationClass
274 fetchRecordsOperationClass: self.fetchRecordsOperationClass
275 queryOperationClass:self.queryOperationClass
276 modifySubscriptionsOperationClass: self.modifySubscriptionsOperationClass
277 modifyRecordZonesOperationClass: self.modifyRecordZonesOperationClass
278 apsConnectionClass: self.apsConnectionClass
279 notifierClass: self.notifierClass];
280
281 if(self.zoneStartupDependency) {
282 [self.views[viewName].zoneSetupOperation addDependency: self.zoneStartupDependency];
283 }
284
285 if(self.initializeNewZones) {
286 [self.views[viewName] initializeZone];
287 }
288
289 return self.views[viewName];
290 }
291 }
292 + (CKKSKeychainView*)findOrCreateView:(NSString*)viewName {
293 return [[CKKSViewManager manager] findOrCreateView: viewName];
294 }
295
296 - (NSDictionary<NSString *,NSString *> *)activeTLKs
297 {
298 NSMutableDictionary<NSString *,NSString *> *tlks = [NSMutableDictionary new];
299 @synchronized(self.views) {
300 for (NSString *name in self.views) {
301 CKKSKeychainView *view = self.views[name];
302 NSString *tlk = view.lastActiveTLKUUID;
303 if (tlk) {
304 tlks[name] = tlk;
305 }
306 }
307 }
308 return tlks;
309 }
310
311 - (CKKSKeychainView*)restartZone:(NSString*)viewName {
312 @synchronized(self.views) {
313 self.views[viewName] = nil;
314 }
315 return [self findOrCreateView: viewName];
316 }
317
318 // Allows all views to begin initializing, and opens the floodgates so that new views will be initalized immediately
319 - (void)initializeZones {
320 if(!SecCKKSIsEnabled()) {
321 secnotice("ckks", "Not initializing CKKS view set as CKKS is disabled");
322 return;
323 }
324
325 @synchronized(self.views) {
326 self.initializeNewZones = true;
327
328 NSSet* viewSet = [self viewList];
329 for(NSString* s in viewSet) {
330 [self findOrCreateView:s]; // initializes any newly-created views
331 }
332 }
333 }
334
335 - (NSString*)viewNameForViewHint: (NSString*) viewHint {
336 // For now, choose view based on viewhints.
337 if(viewHint && ![viewHint isEqual: [NSNull null]]) {
338 return viewHint;
339 }
340
341 // If there isn't a provided view hint, use the "keychain" view if we're testing. Otherwise, nil.
342 if(SecCKKSTestsEnabled()) {
343 return @"keychain";
344 } else {
345 return nil;
346 }
347 }
348
349 - (NSString*)viewNameForItem: (SecDbItemRef) item {
350 CFErrorRef cferror = NULL;
351 NSString* viewHint = (__bridge NSString*) SecDbItemGetValue(item, &v7vwht, &cferror);
352
353 if(cferror) {
354 secerror("ckks: Couldn't fetch the viewhint for some reason: %@", cferror);
355 CFReleaseNull(cferror);
356 viewHint = nil;
357 }
358
359 return [self viewNameForViewHint: viewHint];
360 }
361
362 - (NSString*)viewNameForAttributes: (NSDictionary*) item {
363 return [self viewNameForViewHint: item[(id)kSecAttrSyncViewHint]];
364 }
365
366 - (void)registerSyncStatusCallback: (NSString*) uuid callback: (SecBoolNSErrorCallback) callback {
367 // Someone is requesting future notification of this item.
368 @synchronized(self.pendingSyncCallbacks) {
369 self.pendingSyncCallbacks[uuid] = callback;
370 }
371 }
372
373 - (void) handleKeychainEventDbConnection: (SecDbConnectionRef) dbconn source:(SecDbTransactionSource)txionSource added: (SecDbItemRef) added deleted: (SecDbItemRef) deleted {
374
375 SecDbItemRef modified = added ? added : deleted;
376
377 NSString* viewName = [self viewNameForItem: modified];
378 NSString* keyViewName = [CKKSKey isItemKeyForKeychainView: modified];
379
380 if(keyViewName) {
381 // This might be some key material for this view! Poke it.
382 CKKSKeychainView* view = [self findView: keyViewName];
383
384 if(!SecCKKSTestDisableKeyNotifications()) {
385 ckksnotice("ckks", view, "Potential new key material from %@ (source %lu)", keyViewName, txionSource);
386 [view keyStateMachineRequestProcess];
387 } else {
388 ckksnotice("ckks", view, "Ignoring potential new key material from %@ (source %lu)", keyViewName, txionSource);
389 }
390 return;
391 }
392
393 // When SOS is in charge of a view, CKKS is not.
394 // Since this isn't a CKKS key item, we don't care about it.
395 if(txionSource == kSecDbSOSTransaction) {
396 secinfo("ckks", "Ignoring new non-CKKS item in kSecDbSOSTransaction notification");
397 }
398
399 // Looks like a normal item. Proceed!
400 CKKSKeychainView* view = [self findView:viewName];
401
402 NSString* uuid = (__bridge NSString*) SecDbItemGetValue(modified, &v10itemuuid, NULL);
403 SecBoolNSErrorCallback syncCallback = nil;
404 if(uuid) {
405 @synchronized(self.pendingSyncCallbacks) {
406 syncCallback = self.pendingSyncCallbacks[uuid];
407 self.pendingSyncCallbacks[uuid] = nil;
408
409 if(syncCallback) {
410 secinfo("ckks", "Have a pending callback for %@; passing along", uuid);
411 }
412 }
413 }
414
415 if(!view) {
416 secinfo("ckks", "No CKKS view for %@, skipping: %@", viewName, modified);
417 if(syncCallback) {
418 syncCallback(false, [NSError errorWithDomain:@"securityd"
419 code:kSOSCCNoSuchView
420 userInfo:@{NSLocalizedDescriptionKey: [NSString stringWithFormat: @"No syncing view for '%@'", viewName]}]);
421 }
422 return;
423 }
424
425 ckksnotice("ckks", view, "Routing item to zone %@: %@", viewName, modified);
426 [view handleKeychainEventDbConnection: dbconn added:added deleted:deleted rateLimiter:self.globalRateLimiter syncCallback: syncCallback];
427 }
428
429 -(void)setCurrentItemForAccessGroup:(SecDbItemRef)newItem
430 hash:(NSData*)newItemSHA1
431 accessGroup:(NSString*)accessGroup
432 identifier:(NSString*)identifier
433 viewHint:(NSString*)viewHint
434 replacing:(SecDbItemRef)oldItem
435 hash:(NSData*)oldItemSHA1
436 complete:(void (^) (NSError* operror)) complete
437 {
438 CKKSKeychainView* view = [self findView:viewHint];
439
440 if(!view) {
441 secnotice("ckks", "No CKKS view for %@, skipping current request", viewHint);
442 complete([NSError errorWithDomain:CKKSErrorDomain
443 code:CKKSNoSuchView
444 description:[NSString stringWithFormat: @"No syncing view for view hint '%@'", viewHint]]);
445 return;
446 }
447
448 [view setCurrentItemForAccessGroup:newItem
449 hash:newItemSHA1
450 accessGroup:accessGroup
451 identifier:identifier
452 replacing:oldItem
453 hash:oldItemSHA1
454 complete:complete];
455 }
456
457 -(void)getCurrentItemForAccessGroup:(NSString*)accessGroup
458 identifier:(NSString*)identifier
459 viewHint:(NSString*)viewHint
460 fetchCloudValue:(bool)fetchCloudValue
461 complete:(void (^) (NSString* uuid, NSError* operror)) complete
462 {
463 CKKSKeychainView* view = [self findView:viewHint];
464 if(!view) {
465 secnotice("ckks", "No CKKS view for %@, skipping current fetch request", viewHint);
466 complete(NULL, [NSError errorWithDomain:CKKSErrorDomain
467 code:CKKSNoSuchView
468 description:[NSString stringWithFormat: @"No view for '%@'", viewHint]]);
469 return;
470 }
471
472 [view getCurrentItemForAccessGroup:accessGroup
473 identifier:identifier
474 fetchCloudValue:fetchCloudValue
475 complete:complete];
476 }
477
478
479 + (instancetype) manager {
480 return [self resetManager: false setTo: nil];
481 }
482
483 + (instancetype) resetManager: (bool) reset setTo: (CKKSViewManager*) obj {
484 static CKKSViewManager* manager = nil;
485
486 if([CKDatabase class] == nil) {
487 secerror("CKKS: CloudKit.framework appears to not be linked. Can't create CKKS objects.");
488 return nil;
489 }
490
491 if(!manager || reset || obj) {
492 @synchronized([self class]) {
493 if(obj != nil) {
494 [manager clearAllViews];
495 manager = obj;
496 } else {
497 if(reset) {
498 [manager clearAllViews];
499 manager = nil;
500 } else if (manager == nil) {
501 manager = [[CKKSViewManager alloc] initCloudKitWithContainerName:SecCKKSContainerName usePCS:SecCKKSContainerUsePCS];
502 }
503 }
504 }
505 }
506
507 return manager;
508 }
509
510 - (void)cancelPendingOperations {
511 [self.savedTLKNotifier cancel];
512 }
513
514 -(void)notifyNewTLKsInKeychain {
515 // Why two functions here? Limitation of OCMock, unfortunately: can't stub and expect the same method
516 secnotice("ckksbackup", "New TLKs have arrived");
517 [self syncBackupAndNotifyAboutSync];
518 }
519
520 - (void)syncBackupAndNotifyAboutSync {
521 SOSAccount* account = (__bridge SOSAccount*)SOSKeychainAccountGetSharedAccount();
522
523 [account performTransaction:^(SOSAccountTransaction * _Nonnull txn) {
524 CFErrorRef error = NULL;
525 NSSet* ignore = CFBridgingRelease(SOSAccountCopyBackupPeersAndForceSync(txn, &error));
526 (void)ignore;
527
528 if(error) {
529 secerror("ckksbackup: Couldn't process sync with backup peers: %@", error);
530 } else {
531 secnotice("ckksbackup", "telling CloudServices about TLK arrival");
532 notify_post(kSecItemBackupNotification);
533 };
534 }];
535 }
536
537 #pragma mark - XPC Endpoint
538
539 - (xpc_endpoint_t)xpcControlEndpoint {
540 return [_listener.endpoint _endpoint];
541 }
542
543 - (BOOL)listener:(__unused NSXPCListener *)listener shouldAcceptNewConnection:(NSXPCConnection *)newConnection {
544 NSNumber *num = [newConnection valueForEntitlement:(__bridge NSString *)kSecEntitlementPrivateCKKS];
545 if (![num isKindOfClass:[NSNumber class]] || ![num boolValue]) {
546 secinfo("ckks", "Client pid: %d doesn't have entitlement: %@",
547 [newConnection processIdentifier], kSecEntitlementPrivateCKKS);
548 return NO;
549 }
550 newConnection.exportedInterface = CKKSSetupControlProtocol([NSXPCInterface interfaceWithProtocol:@protocol(CKKSControlProtocol)]);
551 newConnection.exportedObject = self;
552
553 [newConnection resume];
554
555 return YES;
556 }
557 #pragma mark - RPCs to manage and report state
558
559 - (void)performanceCounters:(void(^)(NSDictionary <NSString *, NSNumber *> *counter))reply {
560 reply(@{});
561 }
562
563 - (void)rpcResetLocal:(NSString*)viewName reply: (void(^)(NSError* result)) reply {
564 NSArray* actualViews = nil;
565 if(viewName) {
566 secnotice("ckksreset", "Received a local reset RPC for zone %@", viewName);
567 CKKSKeychainView* view = self.views[viewName];
568
569 if(!view) {
570 secerror("ckks: Zone %@ does not exist!", viewName);
571 reply([NSError errorWithDomain:@"securityd"
572 code:kSOSCCNoSuchView
573 userInfo:@{NSLocalizedDescriptionKey: [NSString stringWithFormat: @"No view for '%@'", viewName]}]);
574 return;
575 }
576
577 actualViews = @[view];
578 } else {
579 secnotice("ckksreset", "Received a local reset RPC for all zones");
580 @synchronized(self.views) {
581 // Can't safely iterate a mutable collection, so copy it.
582 actualViews = [self.views.allValues copy];
583 }
584 }
585
586 CKKSResultOperation* op = [CKKSResultOperation named:@"local-reset-zones-waiter" withBlock:^{}];
587
588 for(CKKSKeychainView* view in actualViews) {
589 ckksnotice("ckksreset", view, "Beginning local reset for %@", view);
590 [op addSuccessDependency:[view resetLocalData]];
591 }
592
593 [op timeout:120*NSEC_PER_SEC];
594 [self.operationQueue addOperation: op];
595
596 [op waitUntilFinished];
597 if(op.error) {
598 secnotice("ckksreset", "Completed rpcResetLocal");
599 } else {
600 secnotice("ckksreset", "Completed rpcResetLocal with error: %@", op.error);
601 }
602 reply(CKXPCSuitableError(op.error));
603 }
604
605 - (void)rpcResetCloudKit:(NSString*)viewName reply: (void(^)(NSError* result)) reply {
606 NSArray* actualViews = nil;
607 if(viewName) {
608 secnotice("ckksreset", "Received a cloudkit reset RPC for zone %@", viewName);
609 CKKSKeychainView* view = self.views[viewName];
610
611 if(!view) {
612 secerror("ckks: Zone %@ does not exist!", viewName);
613 reply([NSError errorWithDomain:@"securityd"
614 code:kSOSCCNoSuchView
615 userInfo:@{NSLocalizedDescriptionKey: [NSString stringWithFormat: @"No view for '%@'", viewName]}]);
616 return;
617 }
618
619 actualViews = @[view];
620 } else {
621 secnotice("ckksreset", "Received a cloudkit reset RPC for all zones");
622 @synchronized(self.views) {
623 // Can't safely iterate a mutable collection, so copy it.
624 actualViews = [self.views.allValues copy];
625 }
626 }
627
628 CKKSResultOperation* op = [CKKSResultOperation named:@"cloudkit-reset-zones-waiter" withBlock:^{}];
629
630 for(CKKSKeychainView* view in actualViews) {
631 ckksnotice("ckksreset", view, "Beginning CloudKit reset for %@", view);
632 [op addSuccessDependency:[view resetCloudKitZone]];
633 }
634
635 [op timeout:120*NSEC_PER_SEC];
636 [self.operationQueue addOperation: op];
637
638 [op waitUntilFinished];
639 if(op.error) {
640 secnotice("ckksreset", "Completed rpcResetCloudKit");
641 } else {
642 secnotice("ckksreset", "Completed rpcResetCloudKit with error: %@", op.error);
643 }
644 reply(CKXPCSuitableError(op.error));
645 }
646
647 - (void)rpcResync:(NSString*)viewName reply: (void(^)(NSError* result)) reply {
648 secnotice("ckksresync", "Received a resync RPC for zone %@. Beginning resync...", viewName);
649
650 NSArray* actualViews = nil;
651 if(viewName) {
652 secnotice("ckks", "Received a resync RPC for zone %@", viewName);
653 CKKSKeychainView* view = self.views[viewName];
654
655 if(!view) {
656 secerror("ckks: Zone %@ does not exist!", viewName);
657 reply(nil);
658 return;
659 }
660
661 actualViews = @[view];
662
663 } else {
664 @synchronized(self.views) {
665 // Can't safely iterate a mutable collection, so copy it.
666 actualViews = [self.views.allValues copy];
667 }
668 }
669
670 CKKSResultOperation* op = [[CKKSResultOperation alloc] init];
671 op.name = @"rpc-resync";
672 __weak __typeof(op) weakOp = op;
673 [op addExecutionBlock:^{
674 __strong __typeof(op) strongOp = weakOp;
675 secnotice("ckks", "Ending rsync rpc with %@", strongOp.error);
676 }];
677
678 for(CKKSKeychainView* view in actualViews) {
679 ckksnotice("ckksresync", view, "Beginning resync for %@", view);
680
681 CKKSSynchronizeOperation* resyncOp = [view resyncWithCloud];
682 [op addSuccessDependency:resyncOp];
683 }
684
685 [op timeout:120*NSEC_PER_SEC];
686 [self.operationQueue addOperation:op];
687 [op waitUntilFinished];
688 reply(CKXPCSuitableError(op.error));
689 }
690
691 - (void)rpcStatus: (NSString*)viewName reply: (void(^)(NSArray<NSDictionary*>* result, NSError* error)) reply {
692 NSMutableArray* a = [[NSMutableArray alloc] init];
693
694 // The first element is always the current global state (non-view-specific)
695 NSError* selfPeersError = nil;
696 CKKSSelves* selves = [self fetchSelfPeers:&selfPeersError];
697 NSError* trustedPeersError = nil;
698 NSSet<id<CKKSPeer>>* peers = [self fetchTrustedPeers:&trustedPeersError];
699
700
701 NSMutableArray<NSString*>* mutTrustedPeers = [[NSMutableArray alloc] init];
702 [peers enumerateObjectsUsingBlock:^(id<CKKSPeer> _Nonnull obj, BOOL * _Nonnull stop) {
703 [mutTrustedPeers addObject: [obj description]];
704 }];
705
706 #define stringify(obj) CKKSNilToNSNull([obj description])
707 NSDictionary* global = @{
708 @"view": @"global",
709 @"selfPeers": stringify(selves),
710 @"selfPeersError": CKKSNilToNSNull(selfPeersError),
711 @"trustedPeers": CKKSNilToNSNull(mutTrustedPeers),
712 @"trustedPeersError": CKKSNilToNSNull(trustedPeersError),
713 };
714 [a addObject: global];
715
716 // Now, query the views about their status
717 NSArray* actualViews = nil;
718 if(viewName) {
719 secnotice("ckks", "Received a status RPC for zone %@", viewName);
720 CKKSKeychainView* view = self.views[viewName];
721
722 if(!view) {
723 secerror("ckks: Zone %@ does not exist!", viewName);
724 reply(nil, nil);
725 return;
726 }
727
728 actualViews = @[view];
729
730 } else {
731 @synchronized(self.views) {
732 // Can't safely iterate a mutable collection, so copy it.
733 actualViews = self.views.allValues;
734 }
735 }
736
737 for(CKKSKeychainView* view in actualViews) {
738 ckksnotice("ckks", view, "Fetching status for %@", view.zoneName);
739 NSDictionary* status = [view status];
740 ckksinfo("ckks", view, "Status is %@", status);
741 if(status) {
742 [a addObject: status];
743 }
744 }
745 reply(a, nil);
746 return;
747 }
748
749 - (void)rpcFetchAndProcessChanges:(NSString*)viewName reply: (void(^)(NSError* result))reply {
750 [self rpcFetchAndProcessChanges:viewName classA:false reply: (void(^)(NSError* result))reply];
751 }
752
753 - (void)rpcFetchAndProcessClassAChanges:(NSString*)viewName reply: (void(^)(NSError* result))reply {
754 [self rpcFetchAndProcessChanges:viewName classA:true reply:(void(^)(NSError* result))reply];
755 }
756
757 - (void)rpcFetchAndProcessChanges:(NSString*)viewName classA:(bool)classAError reply: (void(^)(NSError* result)) reply {
758 NSArray* actualViews = nil;
759 if(viewName) {
760 secnotice("ckks", "Received a fetch RPC for zone %@", viewName);
761 CKKSKeychainView* view = self.views[viewName];
762
763 if(!view) {
764 secerror("ckks: Zone %@ does not exist!", viewName);
765 reply([NSError errorWithDomain:@"securityd"
766 code:kSOSCCNoSuchView
767 userInfo:@{NSLocalizedDescriptionKey: [NSString stringWithFormat: @"No view for '%@'", viewName]}]);
768 return;
769 }
770
771 actualViews = @[view];
772 } else {
773 secnotice("ckks", "Received a fetch RPC for all zones");
774 @synchronized(self.views) {
775 // Can't safely iterate a mutable collection, so copy it.
776 actualViews = [self.views.allValues copy];
777 }
778 }
779
780 CKKSResultOperation* blockOp = [[CKKSResultOperation alloc] init];
781 blockOp.name = @"rpc-fetch-and-process-result";
782 __weak __typeof(blockOp) weakBlockOp = blockOp;
783 // Use the completion block instead of the operation block, so that it runs even if the cancel fires
784 [blockOp setCompletionBlock:^{
785 __strong __typeof(blockOp) strongBlockOp = weakBlockOp;
786 [strongBlockOp allDependentsSuccessful];
787 reply(CKXPCSuitableError(strongBlockOp.error));
788 }];
789
790 for(CKKSKeychainView* view in actualViews) {
791 ckksnotice("ckks", view, "Beginning fetch for %@", view);
792
793 CKKSResultOperation* op = [view processIncomingQueue:classAError after:[view.zoneChangeFetcher requestSuccessfulFetch: CKKSFetchBecauseAPIFetchRequest]];
794 [blockOp addDependency:op];
795 }
796
797 [self.operationQueue addOperation: [blockOp timeout:60*NSEC_PER_SEC]];
798 }
799
800 - (void)rpcPushOutgoingChanges:(NSString*)viewName reply: (void(^)(NSError* result))reply {
801 NSArray* actualViews = nil;
802 if(viewName) {
803 secnotice("ckks", "Received a push RPC for zone %@", viewName);
804 CKKSKeychainView* view = self.views[viewName];
805
806 if(!view) {
807 secerror("ckks: Zone %@ does not exist!", viewName);
808 reply([NSError errorWithDomain:@"securityd"
809 code:kSOSCCNoSuchView
810 userInfo:@{NSLocalizedDescriptionKey: [NSString stringWithFormat: @"No view for '%@'", viewName]}]);
811 return;
812 }
813
814 actualViews = @[view];
815 } else {
816 secnotice("ckks", "Received a push RPC for all zones");
817 @synchronized(self.views) {
818 // Can't safely iterate a mutable collection, so copy it.
819 actualViews = [self.views.allValues copy];
820 }
821 }
822
823 CKKSResultOperation* blockOp = [[CKKSResultOperation alloc] init];
824 blockOp.name = @"rpc-push";
825 __weak __typeof(blockOp) weakBlockOp = blockOp;
826 // Use the completion block instead of the operation block, so that it runs even if the cancel fires
827 [blockOp setCompletionBlock:^{
828 __strong __typeof(blockOp) strongBlockOp = weakBlockOp;
829 [strongBlockOp allDependentsSuccessful];
830 reply(CKXPCSuitableError(strongBlockOp.error));
831 }];
832
833 for(CKKSKeychainView* view in actualViews) {
834 ckksnotice("ckks-rpc", view, "Beginning push for %@", view);
835
836 CKKSResultOperation* op = [view processOutgoingQueue: [CKOperationGroup CKKSGroupWithName:@"rpc-push"]];
837 [blockOp addDependency:op];
838 }
839
840 [self.operationQueue addOperation: [blockOp timeout:60*NSEC_PER_SEC]];
841 }
842
843 - (void)rpcGetAnalyticsSysdiagnoseWithReply:(void (^)(NSString* sysdiagnose, NSError* error))reply
844 {
845 NSError* error = nil;
846 NSString* sysdiagnose = [[CKKSAnalyticsLogger logger] getSysdiagnoseDumpWithError:&error];
847 reply(sysdiagnose, CKXPCSuitableError(error));
848 }
849
850 - (void)rpcGetAnalyticsJSONWithReply:(void (^)(NSData* json, NSError* error))reply
851 {
852 NSError* error = nil;
853 NSData* json = [[CKKSAnalyticsLogger logger] getLoggingJSON:true error:&error];
854 reply(json, CKXPCSuitableError(error));
855 }
856
857 - (void)rpcForceUploadAnalyticsWithReply:(void (^)(BOOL success, NSError* error))reply
858 {
859 NSError* error = nil;
860 BOOL result = [[CKKSAnalyticsLogger logger] forceUploadWithError:&error];
861 reply(result, CKXPCSuitableError(error));
862 }
863
864 -(void)xpc24HrNotification {
865 // XPC has poked us and said we should do some cleanup!
866
867 // For now, poke the views and tell them to update their device states if they'd like
868 NSArray* actualViews = nil;
869 @synchronized(self.views) {
870 // Can't safely iterate a mutable collection, so copy it.
871 actualViews = self.views.allValues;
872 }
873
874 secnotice("ckks", "Received a 24hr notification from XPC");
875 CKOperationGroup* group = [CKOperationGroup CKKSGroupWithName:@"periodic-device-state-update"];
876 for(CKKSKeychainView* view in actualViews) {
877 ckksnotice("ckks", view, "Starting device state XPC update");
878 // Let the update know it should rate-limit itself
879 [view updateDeviceState:true waitForKeyHierarchyInitialization:30*NSEC_PER_SEC ckoperationGroup:group];
880 }
881 }
882
883 #pragma mark - CKKSPeerProvider implementation
884
885 - (CKKSSelves*)fetchSelfPeers:(NSError* __autoreleasing *)error {
886 __block SFECKeyPair* signingPrivateKey = nil;
887 __block SFECKeyPair* encryptionPrivateKey = nil;
888
889 __block NSError* localerror = nil;
890
891 // Wait for this to initialize, but don't worry if it isn't.
892 [self.accountTracker.accountCirclePeerIDInitialized wait:500*NSEC_PER_MSEC];
893 NSString* peerID = self.accountTracker.accountCirclePeerID;
894 if(!peerID || self.accountTracker.accountCirclePeerIDError) {
895 secerror("ckkspeer: Error fetching self peer : %@", self.accountTracker.accountCirclePeerIDError);
896 if(error) {
897 *error = self.accountTracker.accountCirclePeerIDError;
898 }
899 return nil;
900 }
901
902 SOSCCPerformWithOctagonSigningKey(^(SecKeyRef signingSecKey, CFErrorRef cferror) {
903 if(cferror) {
904 localerror = (__bridge NSError*)cferror;
905 return;
906 }
907 if (!cferror && signingSecKey) {
908 signingPrivateKey = [[SFECKeyPair alloc] initWithSecKey:signingSecKey];
909 }
910 });
911
912 SOSCCPerformWithOctagonEncryptionKey(^(SecKeyRef encryptionSecKey, CFErrorRef cferror) {
913 if(cferror) {
914 localerror = (__bridge NSError*)cferror;
915 return;
916 }
917 if (!cferror && encryptionSecKey) {
918 encryptionPrivateKey = [[SFECKeyPair alloc] initWithSecKey:encryptionSecKey];
919 }
920 });
921
922 if(localerror) {
923 secerror("ckkspeer: Error fetching self encryption keys: %@", localerror);
924 if(error) {
925 *error = localerror;
926 }
927 return nil;
928 }
929
930 CKKSSOSSelfPeer* selfPeer = [[CKKSSOSSelfPeer alloc] initWithSOSPeerID:peerID
931 encryptionKey:encryptionPrivateKey
932 signingKey:signingPrivateKey];
933 CKKSSelves* selves = [[CKKSSelves alloc] initWithCurrent:selfPeer allSelves:nil];
934 return selves;
935 }
936
937 - (NSSet<id<CKKSPeer>>*)fetchTrustedPeers:(NSError* __autoreleasing *)error {
938 __block NSMutableSet<id<CKKSPeer>>* peerSet = [NSMutableSet set];
939
940 SOSCCPerformWithTrustedPeers(^(CFSetRef sosPeerInfoRefs, CFErrorRef cfTrustedPeersError) {
941 if(cfTrustedPeersError) {
942 secerror("ckks: Error fetching trusted peers: %@", cfTrustedPeersError);
943 if(error) {
944 *error = (__bridge NSError*)cfTrustedPeersError;
945 }
946 }
947
948 CFSetForEach(sosPeerInfoRefs, ^(const void* voidPeer) {
949 CFErrorRef cfPeerError = NULL;
950 SOSPeerInfoRef sosPeerInfoRef = (SOSPeerInfoRef)voidPeer;
951
952 if(!sosPeerInfoRef) {
953 return;
954 }
955
956 CFStringRef cfpeerID = SOSPeerInfoGetPeerID(sosPeerInfoRef);
957 SecKeyRef cfOctagonSigningKey = SOSPeerInfoCopyOctagonSigningPublicKey(sosPeerInfoRef, &cfPeerError);
958 SecKeyRef cfOctagonEncryptionKey = SOSPeerInfoCopyOctagonEncryptionPublicKey(sosPeerInfoRef, &cfPeerError);
959
960 if(cfPeerError) {
961 secerror("ckkspeer: error fetching octagon keys for peer: %@ %@", sosPeerInfoRef, cfPeerError);
962 } else {
963 SFECPublicKey* signingPublicKey = cfOctagonSigningKey ? [[SFECPublicKey alloc] initWithSecKey:cfOctagonSigningKey] : nil;
964 SFECPublicKey* encryptionPublicKey = cfOctagonEncryptionKey ? [[SFECPublicKey alloc] initWithSecKey:cfOctagonEncryptionKey] : nil;
965
966 CKKSSOSPeer* peer = [[CKKSSOSPeer alloc] initWithSOSPeerID:(__bridge NSString*)cfpeerID
967 encryptionPublicKey:encryptionPublicKey
968 signingPublicKey:signingPublicKey];
969 [peerSet addObject:peer];
970 }
971
972 CFReleaseNull(cfOctagonSigningKey);
973 CFReleaseNull(cfOctagonEncryptionKey);
974 });
975 });
976
977 return peerSet;
978 }
979
980 - (void)registerForPeerChangeUpdates:(id<CKKSPeerUpdateListener>)listener {
981 bool alreadyRegisteredListener = false;
982 NSEnumerator *enumerator = [self.peerChangeListeners objectEnumerator];
983 id<CKKSPeerUpdateListener> value;
984
985 while ((value = [enumerator nextObject])) {
986 // do pointer comparison
987 alreadyRegisteredListener |= (value == listener);
988 }
989
990 if(listener && !alreadyRegisteredListener) {
991 NSString* queueName = [NSString stringWithFormat: @"ck-peer-change-%@", listener];
992
993 dispatch_queue_t objQueue = dispatch_queue_create([queueName UTF8String], DISPATCH_QUEUE_SERIAL);
994 [self.peerChangeListeners setObject: listener forKey: objQueue];
995 }
996 }
997
998 - (void)iteratePeerListenersOnTheirQueue:(void (^)(id<CKKSPeerUpdateListener>))block {
999 NSEnumerator *enumerator = [self.peerChangeListeners keyEnumerator];
1000 dispatch_queue_t dq;
1001
1002 // Queue up the changes for each listener.
1003 while ((dq = [enumerator nextObject])) {
1004 id<CKKSPeerUpdateListener> listener = [self.peerChangeListeners objectForKey: dq];
1005 __weak id<CKKSPeerUpdateListener> weakListener = listener;
1006
1007 if(listener) {
1008 dispatch_async(dq, ^{
1009 __strong id<CKKSPeerUpdateListener> strongListener = weakListener;
1010 block(strongListener);
1011 });
1012 }
1013 }
1014 }
1015
1016 - (void)sendSelfPeerChangedUpdate {
1017 [self iteratePeerListenersOnTheirQueue: ^(id<CKKSPeerUpdateListener> listener) {
1018 [listener selfPeerChanged];
1019 }];
1020 }
1021
1022 - (void)sendTrustedPeerSetChangedUpdate {
1023 [self iteratePeerListenersOnTheirQueue: ^(id<CKKSPeerUpdateListener> listener) {
1024 [listener trustedPeerSetChanged];
1025 }];
1026 }
1027 #endif // OCTAGON
1028 @end