]> git.saurik.com Git - apple/security.git/blob - keychain/ckks/CKKSViewManager.m
Security-58286.270.3.0.1.tar.gz
[apple/security.git] / keychain / ckks / CKKSViewManager.m
1 /*
2 * Copyright (c) 2016 Apple Inc. All Rights Reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
11 * file.
12 *
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 */
23
24 #import "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 "keychain/categories/NSError+UsefulConstructors.h"
34
35 #import "keychain/ot/OTDefines.h"
36
37 #import "SecEntitlements.h"
38
39 #include <securityd/SecDbItem.h>
40 #include <securityd/SecDbKeychainItem.h>
41 #include <securityd/SecItemSchema.h>
42 #include <Security/SecureObjectSync/SOSViews.h>
43
44 #import <Foundation/NSXPCConnection.h>
45 #import <Foundation/NSXPCConnection_Private.h>
46
47 #include <Security/SecureObjectSync/SOSAccount.h>
48 #include <Security/SecItemBackup.h>
49
50 #if OCTAGON
51 #import <CloudKit/CloudKit.h>
52 #import <CloudKit/CloudKit_Private.h>
53
54 #import <SecurityFoundation/SFKey.h>
55 #import <SecurityFoundation/SFKey_Private.h>
56
57 #import "CKKSAnalytics.h"
58 #endif
59
60 @interface CKKSViewManager () <NSXPCListenerDelegate>
61 #if OCTAGON
62 @property NSXPCListener *listener;
63
64 // Once you set these, all CKKSKeychainViews created will use them
65 @property (readonly) Class<CKKSFetchRecordZoneChangesOperation> fetchRecordZoneChangesOperationClass;
66 @property (readonly) Class<CKKSFetchRecordsOperation> fetchRecordsOperationClass;
67 @property (readonly) Class<CKKSQueryOperation> queryOperationClass;
68 @property (readonly) Class<CKKSModifySubscriptionsOperation> modifySubscriptionsOperationClass;
69 @property (readonly) Class<CKKSModifyRecordZonesOperation> modifyRecordZonesOperationClass;
70 @property (readonly) Class<CKKSAPSConnection> apsConnectionClass;
71 @property (readonly) Class<CKKSNotifier> notifierClass;
72 @property (readonly) Class<CKKSNSNotificationCenter> nsnotificationCenterClass;
73
74 @property NSMutableDictionary<NSString*, CKKSKeychainView*>* views;
75
76 @property NSMutableDictionary<NSString*, SecBoolNSErrorCallback>* pendingSyncCallbacks;
77 @property CKKSNearFutureScheduler* savedTLKNotifier;;
78 @property NSOperationQueue* operationQueue;
79
80 @property NSMapTable<dispatch_queue_t, id<CKKSPeerUpdateListener>>* peerChangeListeners;
81 #endif
82 @end
83
84 #if OCTAGON
85 @interface CKKSViewManager (lockstateTracker) <CKKSLockStateNotification>
86 @end
87 #endif
88
89 @implementation CKKSViewManager
90 #if OCTAGON
91
92 - (instancetype)initCloudKitWithContainerName: (NSString*) containerName usePCS:(bool)usePCS {
93 return [self initWithContainerName:containerName
94 usePCS:usePCS
95 fetchRecordZoneChangesOperationClass:[CKFetchRecordZoneChangesOperation class]
96 fetchRecordsOperationClass:[CKFetchRecordsOperation class]
97 queryOperationClass:[CKQueryOperation class]
98 modifySubscriptionsOperationClass:[CKModifySubscriptionsOperation class]
99 modifyRecordZonesOperationClass:[CKModifyRecordZonesOperation class]
100 apsConnectionClass:[APSConnection class]
101 nsnotificationCenterClass:[NSNotificationCenter class]
102 notifierClass:[CKKSNotifyPostNotifier class]];
103 }
104
105 - (instancetype)initWithContainerName: (NSString*) containerName
106 usePCS: (bool)usePCS
107 fetchRecordZoneChangesOperationClass: (Class<CKKSFetchRecordZoneChangesOperation>) fetchRecordZoneChangesOperationClass
108 fetchRecordsOperationClass: (Class<CKKSFetchRecordsOperation>)fetchRecordsOperationClass
109 queryOperationClass: (Class<CKKSQueryOperation>)queryOperationClass
110 modifySubscriptionsOperationClass: (Class<CKKSModifySubscriptionsOperation>) modifySubscriptionsOperationClass
111 modifyRecordZonesOperationClass: (Class<CKKSModifyRecordZonesOperation>) modifyRecordZonesOperationClass
112 apsConnectionClass: (Class<CKKSAPSConnection>) apsConnectionClass
113 nsnotificationCenterClass: (Class<CKKSNSNotificationCenter>) nsnotificationCenterClass
114 notifierClass: (Class<CKKSNotifier>) notifierClass
115 {
116 if(self = [super init]) {
117 _fetchRecordZoneChangesOperationClass = fetchRecordZoneChangesOperationClass;
118 _fetchRecordsOperationClass = fetchRecordsOperationClass;
119 _queryOperationClass = queryOperationClass;
120 _modifySubscriptionsOperationClass = modifySubscriptionsOperationClass;
121 _modifyRecordZonesOperationClass = modifyRecordZonesOperationClass;
122 _apsConnectionClass = apsConnectionClass;
123 _nsnotificationCenterClass = nsnotificationCenterClass;
124 _notifierClass = notifierClass;
125
126 _container = [self makeCKContainer: containerName usePCS:usePCS];
127 _accountTracker = [[CKKSCKAccountStateTracker alloc] init:self.container nsnotificationCenterClass:nsnotificationCenterClass];
128 _lockStateTracker = [[CKKSLockStateTracker alloc] init];
129 [_lockStateTracker addLockStateObserver:self];
130 _reachabilityTracker = [[CKKSReachabilityTracker alloc] init];
131
132 _zoneChangeFetcher = [[CKKSZoneChangeFetcher alloc] initWithContainer:_container
133 fetchClass:fetchRecordZoneChangesOperationClass
134 reachabilityTracker:_reachabilityTracker];
135
136 _operationQueue = [[NSOperationQueue alloc] init];
137
138 // Backwards from how we'd like, but it's the best way to have weak pointers to CKKSPeerUpdateListener.
139 _peerChangeListeners = [NSMapTable strongToWeakObjectsMapTable];
140
141 _views = [[NSMutableDictionary alloc] init];
142 _pendingSyncCallbacks = [[NSMutableDictionary alloc] init];
143
144 _initializeNewZones = false;
145
146 _completedSecCKKSInitialize = [[CKKSCondition alloc] init];
147
148 __weak __typeof(self) weakSelf = self;
149 _savedTLKNotifier = [[CKKSNearFutureScheduler alloc] initWithName:@"newtlks"
150 delay:5*NSEC_PER_SEC
151 keepProcessAlive:true
152 dependencyDescriptionCode:CKKSResultDescriptionNone
153 block:^{
154 [weakSelf notifyNewTLKsInKeychain];
155 }];
156
157 _listener = [NSXPCListener anonymousListener];
158 _listener.delegate = self;
159 [_listener resume];
160
161 // If this is a live server, register with notify
162 if(!SecCKKSTestsEnabled()) {
163 int token = 0;
164 notify_register_dispatch(kSOSCCCircleOctagonKeysChangedNotification, &token, dispatch_get_global_queue(QOS_CLASS_UTILITY, 0), ^(int t) {
165 // Since SOS doesn't change the self peer, we can reliably just send "trusted peers changed"; it'll be mostly right
166 secnotice("ckksshare", "Received a notification that the SOS Octagon peer set changed");
167 [weakSelf sendTrustedPeerSetChangedUpdate];
168 });
169 }
170 }
171 return self;
172 }
173
174 -(CKContainer*)makeCKContainer:(NSString*)containerName usePCS:(bool)usePCS {
175 CKContainer* container = [CKContainer containerWithIdentifier:containerName];
176 if(!usePCS) {
177 CKContainerOptions* containerOptions = [[CKContainerOptions alloc] init];
178 containerOptions.bypassPCSEncryption = YES;
179
180 // We don't have a great way to set these, so replace the entire container object
181 container = [[CKContainer alloc] initWithContainerID: container.containerID options:containerOptions];
182 }
183 return container;
184 }
185
186 - (void)setupAnalytics
187 {
188 __weak __typeof(self) weakSelf = self;
189
190 // Tests shouldn't continue here; it leads to entitlement crashes with CloudKit if the mocks aren't enabled when this function runs
191 if(SecCKKSTestsEnabled()) {
192 return;
193 }
194
195 [[CKKSAnalytics logger] AddMultiSamplerForName:@"CKKS-healthSummary" withTimeInterval:SFAnalyticsSamplerIntervalOncePerReport block:^NSDictionary<NSString *,NSNumber *> *{
196 __strong __typeof(self) strongSelf = weakSelf;
197 if(!strongSelf) {
198 return nil;
199 }
200
201 NSMutableDictionary* values = [NSMutableDictionary dictionary];
202 BOOL inCircle = (strongSelf.accountTracker.currentCircleStatus.status == kSOSCCInCircle);
203 if (inCircle) {
204 [[CKKSAnalytics logger] setDateProperty:[NSDate date] forKey:CKKSAnalyticsLastInCircle];
205 }
206 values[CKKSAnalyticsInCircle] = @(inCircle);
207
208 BOOL validCredentials = strongSelf.accountTracker.currentCKAccountInfo.hasValidCredentials;
209 if (!validCredentials) {
210 values[CKKSAnalyticsValidCredentials] = @(validCredentials);
211 }
212
213 NSArray<NSString *>* keys = @[ CKKSAnalyticsLastUnlock, CKKSAnalyticsLastInCircle];
214 for (NSString * key in keys) {
215 NSDate *date = [[CKKSAnalytics logger] datePropertyForKey:key];
216 values[key] = @([CKKSAnalytics fuzzyDaysSinceDate:date]);
217 }
218 return values;
219 }];
220
221 for (NSString* viewName in [self viewList]) {
222 [[CKKSAnalytics logger] AddMultiSamplerForName:[NSString stringWithFormat:@"CKKS-%@-healthSummary", viewName] withTimeInterval:SFAnalyticsSamplerIntervalOncePerReport block:^NSDictionary<NSString *,NSNumber *> *{
223 __strong __typeof(self) strongSelf = weakSelf;
224 if(!strongSelf) {
225 return nil;
226 }
227 BOOL inCircle = strongSelf.accountTracker && strongSelf.accountTracker.currentCircleStatus.status == kSOSCCInCircle;
228 NSMutableDictionary* values = [NSMutableDictionary dictionary];
229 CKKSKeychainView* view = [strongSelf findOrCreateView:viewName];
230 NSDate* dateOfLastSyncClassA = [[CKKSAnalytics logger] dateOfLastSuccessForEvent:CKKSEventProcessIncomingQueueClassA inView:view];
231 NSDate* dateOfLastSyncClassC = [[CKKSAnalytics logger] dateOfLastSuccessForEvent:CKKSEventProcessIncomingQueueClassC inView:view];
232 NSDate* dateOfLastKSR = [[CKKSAnalytics logger] datePropertyForKey:CKKSAnalyticsLastKeystateReady inView:view];
233
234 NSInteger fuzzyDaysSinceClassASync = [CKKSAnalytics fuzzyDaysSinceDate:dateOfLastSyncClassA];
235 NSInteger fuzzyDaysSinceClassCSync = [CKKSAnalytics fuzzyDaysSinceDate:dateOfLastSyncClassC];
236 NSInteger fuzzyDaysSinceKSR = [CKKSAnalytics fuzzyDaysSinceDate:dateOfLastKSR];
237 [values setValue:@(fuzzyDaysSinceClassASync) forKey:[NSString stringWithFormat:@"%@-daysSinceClassASync", viewName]];
238 [values setValue:@(fuzzyDaysSinceClassCSync) forKey:[NSString stringWithFormat:@"%@-daysSinceClassCSync", viewName]];
239 [values setValue:@(fuzzyDaysSinceKSR) forKey:[NSString stringWithFormat:@"%@-daysSinceLastKeystateReady", viewName]];
240
241 BOOL hasTLKs = [view.keyHierarchyState isEqualToString:SecCKKSZoneKeyStateReady] || [view.keyHierarchyState isEqualToString:SecCKKSZoneKeyStateReadyPendingUnlock];
242 /* only synced recently if between [0...7, ie withing 7 days */
243 BOOL syncedClassARecently = fuzzyDaysSinceClassASync >= 0 && fuzzyDaysSinceClassASync < 7;
244 BOOL syncedClassCRecently = fuzzyDaysSinceClassCSync >= 0 && fuzzyDaysSinceClassCSync < 7;
245 BOOL incomingQueueIsErrorFree = view.lastIncomingQueueOperation.error == nil;
246 BOOL outgoingQueueIsErrorFree = view.lastOutgoingQueueOperation.error == nil;
247
248 NSString* hasTLKsKey = [NSString stringWithFormat:@"%@-%@", viewName, CKKSAnalyticsHasTLKs];
249 NSString* syncedClassARecentlyKey = [NSString stringWithFormat:@"%@-%@", viewName, CKKSAnalyticsSyncedClassARecently];
250 NSString* syncedClassCRecentlyKey = [NSString stringWithFormat:@"%@-%@", viewName, CKKSAnalyticsSyncedClassCRecently];
251 NSString* incomingQueueIsErrorFreeKey = [NSString stringWithFormat:@"%@-%@", viewName, CKKSAnalyticsIncomingQueueIsErrorFree];
252 NSString* outgoingQueueIsErrorFreeKey = [NSString stringWithFormat:@"%@-%@", viewName, CKKSAnalyticsOutgoingQueueIsErrorFree];
253
254 values[hasTLKsKey] = @(hasTLKs);
255 values[syncedClassARecentlyKey] = @(syncedClassARecently);
256 values[syncedClassCRecentlyKey] = @(syncedClassCRecently);
257 values[incomingQueueIsErrorFreeKey] = @(incomingQueueIsErrorFree);
258 values[outgoingQueueIsErrorFreeKey] = @(outgoingQueueIsErrorFree);
259
260 BOOL weThinkWeAreInSync = inCircle && hasTLKs && syncedClassARecently && syncedClassCRecently && incomingQueueIsErrorFree && outgoingQueueIsErrorFree;
261 NSString* inSyncKey = [NSString stringWithFormat:@"%@-%@", viewName, CKKSAnalyticsInSync];
262 values[inSyncKey] = @(weThinkWeAreInSync);
263
264 return values;
265 }];
266 }
267 }
268
269 -(void)dealloc {
270 [self clearAllViews];
271 }
272
273 dispatch_queue_t globalZoneStateQueue = NULL;
274 dispatch_once_t globalZoneStateQueueOnce;
275
276 // 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).
277 // Lazy-load it here.
278 - (CKKSRateLimiter*)getGlobalRateLimiter {
279 dispatch_once(&globalZoneStateQueueOnce, ^{
280 globalZoneStateQueue = dispatch_queue_create("CKKS global zone state", DISPATCH_QUEUE_SERIAL_WITH_AUTORELEASE_POOL);
281 });
282
283 if(_globalRateLimiter != nil) {
284 return _globalRateLimiter;
285 }
286
287 __block CKKSRateLimiter* blocklimit = nil;
288
289 dispatch_sync(globalZoneStateQueue, ^{
290 NSError* error = nil;
291
292 // Special object containing state for all zones. Currently, just the rate limiter.
293 CKKSZoneStateEntry* allEntry = [CKKSZoneStateEntry tryFromDatabase: @"all" error:&error];
294
295 if(error) {
296 secerror("CKKSViewManager: couldn't load global zone state: %@", error);
297 }
298
299 if(!error && allEntry.rateLimiter) {
300 blocklimit = allEntry.rateLimiter;
301 } else {
302 blocklimit = [[CKKSRateLimiter alloc] init];
303 }
304 });
305 _globalRateLimiter = blocklimit;
306 return _globalRateLimiter;
307 }
308
309 - (void)lockStateChangeNotification:(bool)unlocked
310 {
311 if (unlocked) {
312 [[CKKSAnalytics logger] setDateProperty:[NSDate date] forKey:CKKSAnalyticsLastUnlock];
313 }
314 }
315
316 // Mostly exists to be mocked out.
317 -(NSSet*)viewList {
318 return CFBridgingRelease(SOSViewCopyViewSet(kViewSetCKKS));
319 }
320
321 - (void)setView: (CKKSKeychainView*) obj {
322 CKKSKeychainView* kcv = nil;
323
324 @synchronized(self.views) {
325 kcv = self.views[obj.zoneName];
326 self.views[obj.zoneName] = obj;
327 }
328
329 if(kcv) {
330 [kcv cancelAllOperations];
331 }
332 }
333
334 - (void)clearAllViews {
335 NSArray<CKKSKeychainView*>* tempviews = nil;
336 @synchronized(self.views) {
337 tempviews = [self.views.allValues copy];
338 [self.views removeAllObjects];
339 }
340
341 for(CKKSKeychainView* view in tempviews) {
342 [view cancelAllOperations];
343 }
344 }
345
346 - (void)clearView:(NSString*) viewName {
347 CKKSKeychainView* kcv = nil;
348 @synchronized(self.views) {
349 kcv = self.views[viewName];
350 self.views[viewName] = nil;
351 }
352
353 if(kcv) {
354 [kcv cancelAllOperations];
355 }
356 }
357
358 - (CKKSKeychainView*)findView:(NSString*)viewName {
359 if(!viewName) {
360 return nil;
361 }
362 @synchronized(self.views) {
363 return self.views[viewName];
364 }
365 }
366
367 - (CKKSKeychainView*)findOrCreateView:(NSString*)viewName {
368 @synchronized(self.views) {
369 CKKSKeychainView* kcv = self.views[viewName];
370 if(kcv) {
371 return kcv;
372 }
373
374 self.views[viewName] = [[CKKSKeychainView alloc] initWithContainer: self.container
375 zoneName: viewName
376 accountTracker: self.accountTracker
377 lockStateTracker: self.lockStateTracker
378 reachabilityTracker: self.reachabilityTracker
379 changeFetcher:self.zoneChangeFetcher
380 savedTLKNotifier: self.savedTLKNotifier
381 peerProvider:self
382 fetchRecordZoneChangesOperationClass: self.fetchRecordZoneChangesOperationClass
383 fetchRecordsOperationClass: self.fetchRecordsOperationClass
384 queryOperationClass:self.queryOperationClass
385 modifySubscriptionsOperationClass: self.modifySubscriptionsOperationClass
386 modifyRecordZonesOperationClass: self.modifyRecordZonesOperationClass
387 apsConnectionClass: self.apsConnectionClass
388 notifierClass: self.notifierClass];
389
390 if(self.initializeNewZones) {
391 [self.views[viewName] initializeZone];
392 }
393
394 return self.views[viewName];
395 }
396 }
397
398 - (NSDictionary<NSString *,NSString *> *)activeTLKs
399 {
400 NSMutableDictionary<NSString *,NSString *> *tlks = [NSMutableDictionary new];
401 @synchronized(self.views) {
402 for (NSString *name in self.views) {
403 CKKSKeychainView *view = self.views[name];
404 NSString *tlk = view.lastActiveTLKUUID;
405 if (tlk) {
406 tlks[name] = tlk;
407 }
408 }
409 }
410 return tlks;
411 }
412
413 - (CKKSKeychainView*)restartZone:(NSString*)viewName {
414 @synchronized(self.views) {
415 [self.views[viewName] halt];
416 self.views[viewName] = nil;
417 }
418 return [self findOrCreateView: viewName];
419 }
420
421 // Allows all views to begin initializing, and opens the floodgates so that new views will be initalized immediately
422 - (void)initializeZones {
423 if(!SecCKKSIsEnabled()) {
424 secnotice("ckks", "Not initializing CKKS view set as CKKS is disabled");
425 return;
426 }
427
428 @synchronized(self.views) {
429 self.initializeNewZones = true;
430
431 NSSet* viewSet = [self viewList];
432 for(NSString* s in viewSet) {
433 [self findOrCreateView:s]; // initializes any newly-created views
434 }
435 }
436
437 [self setupAnalytics];
438 }
439
440 - (NSString*)viewNameForViewHint: (NSString*) viewHint {
441 // For now, choose view based on viewhints.
442 if(viewHint && ![viewHint isEqual: [NSNull null]]) {
443 return viewHint;
444 }
445
446 // If there isn't a provided view hint, use the "keychain" view if we're testing. Otherwise, nil.
447 if(SecCKKSTestsEnabled()) {
448 return @"keychain";
449 } else {
450 return nil;
451 }
452 }
453
454 - (NSString*)viewNameForItem: (SecDbItemRef) item {
455 CFErrorRef cferror = NULL;
456 NSString* viewHint = (__bridge NSString*) SecDbItemGetValue(item, &v7vwht, &cferror);
457
458 if(cferror) {
459 secerror("ckks: Couldn't fetch the viewhint for some reason: %@", cferror);
460 CFReleaseNull(cferror);
461 viewHint = nil;
462 }
463
464 return [self viewNameForViewHint: viewHint];
465 }
466
467 - (NSString*)viewNameForAttributes: (NSDictionary*) item {
468 return [self viewNameForViewHint: item[(id)kSecAttrSyncViewHint]];
469 }
470
471 - (void)registerSyncStatusCallback: (NSString*) uuid callback: (SecBoolNSErrorCallback) callback {
472 // Someone is requesting future notification of this item.
473 @synchronized(self.pendingSyncCallbacks) {
474 self.pendingSyncCallbacks[uuid] = callback;
475 }
476 }
477
478 - (void) handleKeychainEventDbConnection: (SecDbConnectionRef) dbconn source:(SecDbTransactionSource)txionSource added: (SecDbItemRef) added deleted: (SecDbItemRef) deleted {
479
480 SecDbItemRef modified = added ? added : deleted;
481
482 NSString* viewName = [self viewNameForItem: modified];
483 NSString* keyViewName = [CKKSKey isItemKeyForKeychainView: modified];
484
485 if(keyViewName) {
486 // This might be some key material for this view! Poke it.
487 CKKSKeychainView* view = [self findView: keyViewName];
488
489 if(!SecCKKSTestDisableKeyNotifications()) {
490 ckksnotice("ckks", view, "Potential new key material from %@ (source %lu)",
491 keyViewName, (unsigned long)txionSource);
492 [view keyStateMachineRequestProcess];
493 } else {
494 ckksnotice("ckks", view, "Ignoring potential new key material from %@ (source %lu)",
495 keyViewName, (unsigned long)txionSource);
496 }
497 return;
498 }
499
500 // When SOS is in charge of a view, CKKS is not.
501 // Since this isn't a CKKS key item, we don't care about it.
502 if(txionSource == kSecDbSOSTransaction) {
503 secinfo("ckks", "Ignoring new non-CKKS item in kSecDbSOSTransaction notification");
504 }
505
506 // Looks like a normal item. Proceed!
507 CKKSKeychainView* view = [self findView:viewName];
508
509 NSString* uuid = (__bridge NSString*) SecDbItemGetValue(modified, &v10itemuuid, NULL);
510 SecBoolNSErrorCallback syncCallback = nil;
511 if(uuid) {
512 @synchronized(self.pendingSyncCallbacks) {
513 syncCallback = self.pendingSyncCallbacks[uuid];
514 self.pendingSyncCallbacks[uuid] = nil;
515
516 if(syncCallback) {
517 secinfo("ckks", "Have a pending callback for %@; passing along", uuid);
518 }
519 }
520 }
521
522 if(!view) {
523 secinfo("ckks", "No CKKS view for %@, skipping: %@", viewName, modified);
524 if(syncCallback) {
525 syncCallback(false, [NSError errorWithDomain:@"securityd"
526 code:kSOSCCNoSuchView
527 userInfo:@{NSLocalizedDescriptionKey: [NSString stringWithFormat: @"No syncing view for '%@'", viewName]}]);
528 }
529 return;
530 }
531
532 ckksnotice("ckks", view, "Routing item to zone %@: %@", viewName, modified);
533 [view handleKeychainEventDbConnection: dbconn added:added deleted:deleted rateLimiter:self.globalRateLimiter syncCallback: syncCallback];
534 }
535
536 -(void)setCurrentItemForAccessGroup:(NSData* _Nonnull)newItemPersistentRef
537 hash:(NSData*)newItemSHA1
538 accessGroup:(NSString*)accessGroup
539 identifier:(NSString*)identifier
540 viewHint:(NSString*)viewHint
541 replacing:(NSData* _Nullable)oldCurrentItemPersistentRef
542 hash:(NSData*)oldItemSHA1
543 complete:(void (^) (NSError* operror)) complete
544 {
545 CKKSKeychainView* view = [self findView:viewHint];
546
547 if(!view) {
548 secnotice("ckks", "No CKKS view for %@, skipping current request", viewHint);
549 complete([NSError errorWithDomain:CKKSErrorDomain
550 code:CKKSNoSuchView
551 description:[NSString stringWithFormat: @"No syncing view for view hint '%@'", viewHint]]);
552 return;
553 }
554
555 [view setCurrentItemForAccessGroup:newItemPersistentRef
556 hash:newItemSHA1
557 accessGroup:accessGroup
558 identifier:identifier
559 replacing:oldCurrentItemPersistentRef
560 hash:oldItemSHA1
561 complete:complete];
562 }
563
564 -(void)getCurrentItemForAccessGroup:(NSString*)accessGroup
565 identifier:(NSString*)identifier
566 viewHint:(NSString*)viewHint
567 fetchCloudValue:(bool)fetchCloudValue
568 complete:(void (^) (NSString* uuid, NSError* operror)) complete
569 {
570 CKKSKeychainView* view = [self findView:viewHint];
571 if(!view) {
572 secnotice("ckks", "No CKKS view for %@, skipping current fetch request", viewHint);
573 complete(NULL, [NSError errorWithDomain:CKKSErrorDomain
574 code:CKKSNoSuchView
575 description:[NSString stringWithFormat: @"No view for '%@'", viewHint]]);
576 return;
577 }
578
579 [view getCurrentItemForAccessGroup:accessGroup
580 identifier:identifier
581 fetchCloudValue:fetchCloudValue
582 complete:complete];
583 }
584
585
586 + (instancetype) manager {
587 return [self resetManager: false setTo: nil];
588 }
589
590 + (instancetype) resetManager: (bool) reset setTo: (CKKSViewManager*) obj {
591 static CKKSViewManager* manager = nil;
592
593 if([CKDatabase class] == nil) {
594 secerror("CKKS: CloudKit.framework appears to not be linked. Can't create CKKS objects.");
595 return nil;
596 }
597
598 if(!manager || reset || obj) {
599 @synchronized([self class]) {
600 if(obj != nil) {
601 [manager clearAllViews];
602 manager = obj;
603 } else {
604 if(reset) {
605 [manager clearAllViews];
606 manager = nil;
607 } else if (manager == nil && SecCKKSIsEnabled()) {
608 manager = [[CKKSViewManager alloc] initCloudKitWithContainerName:SecCKKSContainerName usePCS:SecCKKSContainerUsePCS];
609 }
610 }
611 }
612 }
613
614 return manager;
615 }
616
617 - (void)cancelPendingOperations {
618 [self.savedTLKNotifier cancel];
619 }
620
621 -(void)notifyNewTLKsInKeychain {
622 // Why two functions here? Limitation of OCMock, unfortunately: can't stub and expect the same method
623 secnotice("ckksbackup", "New TLKs have arrived");
624 [self syncBackupAndNotifyAboutSync];
625 }
626
627 - (void)syncBackupAndNotifyAboutSync {
628 SOSAccount* account = (__bridge SOSAccount*)SOSKeychainAccountGetSharedAccount();
629
630 if(!account) {
631 secnotice("ckks", "Failed to get account object");
632 return;
633 }
634
635 [account performTransaction:^(SOSAccountTransaction * _Nonnull txn) {
636 CFErrorRef error = NULL;
637 NSSet* ignore = CFBridgingRelease(SOSAccountCopyBackupPeersAndForceSync(txn, &error));
638 (void)ignore;
639
640 if(error) {
641 secerror("ckksbackup: Couldn't process sync with backup peers: %@", error);
642 } else {
643 secnotice("ckksbackup", "telling CloudServices about TLK arrival");
644 notify_post(kSecItemBackupNotification);
645 };
646 }];
647 }
648
649 #pragma mark - RPCs to manage and report state
650
651 - (void)performanceCounters:(void(^)(NSDictionary <NSString *, NSNumber *> *counter))reply {
652 reply(@{});
653 }
654
655 - (NSArray<CKKSKeychainView*>*)views:(NSString*)viewName operation:(NSString*)opName error:(NSError**)error
656 {
657 NSArray* actualViews = nil;
658
659 // Ensure we've actually set up, but don't wait too long. Clients get impatient.
660 if([self.completedSecCKKSInitialize wait:5*NSEC_PER_SEC]) {
661 secerror("ckks: Haven't yet initialized zones; expect failure fetching views");
662 }
663
664 @synchronized(self.views) {
665 if(viewName) {
666 CKKSKeychainView* view = self.views[viewName];
667 secnotice("ckks", "Received a %@ request for zone %@ (%@)", opName, viewName, view);
668
669 if(!view) {
670 if(error) {
671 *error = [NSError errorWithDomain:CKKSErrorDomain
672 code:CKKSNoSuchView
673 userInfo:@{NSLocalizedDescriptionKey: [NSString stringWithFormat: @"No view for '%@'", viewName]}];
674 }
675 return nil;
676 }
677
678 actualViews = @[view];
679 } else {
680 actualViews = [self.views.allValues copy];
681 secnotice("ckks", "Received a %@ request for all zones: %@", opName, actualViews);
682 }
683 }
684 actualViews = [actualViews sortedArrayUsingDescriptors:@[[NSSortDescriptor sortDescriptorWithKey:@"zoneName" ascending:YES]]];
685 return actualViews;
686 }
687
688 - (void)rpcResetLocal:(NSString*)viewName reply: (void(^)(NSError* result)) reply {
689 NSError* localError = nil;
690 NSArray* actualViews = [self views:viewName operation:@"local reset" error:&localError];
691 if(localError) {
692 secerror("ckks: Error getting view %@: %@", viewName, localError);
693 reply(localError);
694 return;
695 }
696
697 CKKSResultOperation* op = [CKKSResultOperation named:@"local-reset-zones-waiter" withBlockTakingSelf:^(CKKSResultOperation * _Nonnull strongOp) {
698 if(!strongOp.error) {
699 secnotice("ckksreset", "Completed rpcResetLocal");
700 } else {
701 secnotice("ckks", "Completed rpcResetLocal with error: %@", strongOp.error);
702 }
703 reply(CKXPCSuitableError(strongOp.error));
704 }];
705
706 for(CKKSKeychainView* view in actualViews) {
707 ckksnotice("ckksreset", view, "Beginning local reset for %@", view);
708 [op addSuccessDependency:[view resetLocalData]];
709 }
710
711 [op timeout:120*NSEC_PER_SEC];
712 [self.operationQueue addOperation: op];
713 }
714
715 #pragma clang diagnostic push
716 #pragma clang diagnostic ignored "-Wdeprecated-implementations"
717 - (void)rpcResetCloudKit:(NSString*)viewName reply: (void(^)(NSError* result)) reply {
718 [self rpcResetCloudKit:viewName reason:@"unknown" reply:reply];
719 }
720 #pragma clang diagnostic pop
721
722 - (void)rpcResetCloudKit:(NSString*)viewName reason:(NSString *)reason reply:(void(^)(NSError* result)) reply {
723 NSError* localError = nil;
724 NSArray* actualViews = [self views:viewName operation:@"CloudKit reset" error:&localError];
725 if(localError) {
726 secerror("ckks: Error getting view %@: %@", viewName, localError);
727 reply(localError);
728 return;
729 }
730
731 CKKSResultOperation* op = [CKKSResultOperation named:@"cloudkit-reset-zones-waiter" withBlockTakingSelf:^(CKKSResultOperation * _Nonnull strongOp) {
732 if(!strongOp.error) {
733 secnotice("ckksreset", "Completed rpcResetCloudKit");
734 } else {
735 secnotice("ckksreset", "Completed rpcResetCloudKit with error: %@", strongOp.error);
736 }
737 reply(CKXPCSuitableError(strongOp.error));
738 }];
739
740 for(CKKSKeychainView* view in actualViews) {
741 NSString *operationGroupName = [NSString stringWithFormat:@"api-reset-%@", reason];
742 ckksnotice("ckksreset", view, "Beginning CloudKit reset for %@: %@", view, reason);
743 [op addSuccessDependency:[view resetCloudKitZone:[CKOperationGroup CKKSGroupWithName:operationGroupName]]];
744 }
745
746 [op timeout:120*NSEC_PER_SEC];
747 [self.operationQueue addOperation: op];
748 }
749
750 - (void)rpcResync:(NSString*)viewName reply: (void(^)(NSError* result)) reply {
751 NSError* localError = nil;
752 NSArray* actualViews = [self views:viewName operation:@"CloudKit resync" error:&localError];
753 if(localError) {
754 secerror("ckks: Error getting view %@: %@", viewName, localError);
755 reply(localError);
756 return;
757 }
758
759 CKKSResultOperation* op = [[CKKSResultOperation alloc] init];
760 op.name = @"rpc-resync-cloudkit";
761 __weak __typeof(op) weakOp = op;
762 [op addExecutionBlock:^{
763 __strong __typeof(op) strongOp = weakOp;
764 secnotice("ckks", "Ending rsync-CloudKit rpc with %@", strongOp.error);
765 }];
766
767 for(CKKSKeychainView* view in actualViews) {
768 ckksnotice("ckksresync", view, "Beginning resync (CloudKit) for %@", view);
769
770 CKKSSynchronizeOperation* resyncOp = [view resyncWithCloud];
771 [op addSuccessDependency:resyncOp];
772 }
773
774 [op timeout:120*NSEC_PER_SEC];
775 [self.operationQueue addOperation:op];
776 [op waitUntilFinished];
777 reply(CKXPCSuitableError(op.error));
778 }
779
780 - (void)rpcResyncLocal:(NSString*)viewName reply:(void(^)(NSError* result))reply {
781 NSError* localError = nil;
782 NSArray* actualViews = [self views:viewName operation:@"local resync" error:&localError];
783 if(localError) {
784 secerror("ckks: Error getting view %@: %@", viewName, localError);
785 reply(localError);
786 return;
787 }
788
789 CKKSResultOperation* op = [[CKKSResultOperation alloc] init];
790 op.name = @"rpc-resync-local";
791 __weak __typeof(op) weakOp = op;
792 [op addExecutionBlock:^{
793 __strong __typeof(op) strongOp = weakOp;
794 secnotice("ckks", "Ending rsync-local rpc with %@", strongOp.error);
795 reply(CKXPCSuitableError(strongOp.error));
796 }];
797
798 for(CKKSKeychainView* view in actualViews) {
799 ckksnotice("ckksresync", view, "Beginning resync (local) for %@", view);
800
801 CKKSLocalSynchronizeOperation* resyncOp = [view resyncLocal];
802 [op addSuccessDependency:resyncOp];
803 }
804
805 [op timeout:120*NSEC_PER_SEC];
806 }
807
808 - (void)rpcStatus: (NSString*)viewName
809 global:(bool)reportGlobal
810 reply:(void(^)(NSArray<NSDictionary*>* result, NSError* error)) reply
811 viewBlock:(NSDictionary * (^)(CKKSKeychainView* view))viewBlock
812 {
813 NSMutableArray* a = [[NSMutableArray alloc] init];
814
815 // Now, query the views about their status
816 NSError* error = nil;
817 NSArray* actualViews = [self views:viewName operation:@"status" error:&error];
818 if(!actualViews || error) {
819 reply(nil, error);
820 return;
821 }
822
823 __weak __typeof(self) weakSelf = self;
824 CKKSResultOperation* statusOp = [CKKSResultOperation named:@"status-rpc" withBlock:^{
825 __strong __typeof(self) strongSelf = weakSelf;
826
827 if (reportGlobal) {
828 // The first element is always the current global state (non-view-specific)
829 NSError* selfPeersError = nil;
830 CKKSSelves* selves = [strongSelf fetchSelfPeers:&selfPeersError];
831 NSError* trustedPeersError = nil;
832 NSSet<id<CKKSPeer>>* peers = [strongSelf fetchTrustedPeers:&trustedPeersError];
833
834 // Get account state, even wait for it a little
835 [self.accountTracker.ckdeviceIDInitialized wait:1*NSEC_PER_SEC];
836 NSString *deviceID = self.accountTracker.ckdeviceID;
837 NSError *deviceIDError = self.accountTracker.ckdeviceIDError;
838
839 NSMutableArray<NSString*>* mutTrustedPeers = [[NSMutableArray alloc] init];
840 [peers enumerateObjectsUsingBlock:^(id<CKKSPeer> _Nonnull obj, BOOL * _Nonnull stop) {
841 [mutTrustedPeers addObject: [obj description]];
842 }];
843
844 #define stringify(obj) CKKSNilToNSNull([obj description])
845 NSDictionary* global = @{
846 @"view": @"global",
847 @"selfPeers": stringify(selves),
848 @"selfPeersError": CKKSNilToNSNull(selfPeersError),
849 @"trustedPeers": CKKSNilToNSNull(mutTrustedPeers),
850 @"trustedPeersError": CKKSNilToNSNull(trustedPeersError),
851 @"reachability": strongSelf.reachabilityTracker.currentReachability ? @"network" : @"no-network",
852 @"ckdeviceID": CKKSNilToNSNull(deviceID),
853 @"ckdeviceIDError": CKKSNilToNSNull(deviceIDError),
854 };
855 [a addObject: global];
856 }
857
858 for(CKKSKeychainView* view in actualViews) {
859 ckksnotice("ckks", view, "Fetching status for %@", view.zoneName);
860 NSDictionary* status = viewBlock(view);
861 ckksinfo("ckks", view, "Status is %@", status);
862 if(status) {
863 [a addObject: status];
864 }
865 }
866 reply(a, nil);
867 }];
868
869 // If we're signed in, give the views a few seconds to enter what they consider to be a non-transient state (in case this daemon just launched)
870 if([self.accountTracker.currentComputedAccountStatusValid wait:5*NSEC_PER_SEC]) {
871 secerror("ckks status: Haven't yet figured out login state");
872 }
873
874 if(self.accountTracker.currentComputedAccountStatus == CKKSAccountStatusAvailable) {
875 CKKSResultOperation* blockOp = [CKKSResultOperation named:@"wait-for-status" withBlock:^{}];
876 [blockOp timeout:8*NSEC_PER_SEC];
877 for(CKKSKeychainView* view in actualViews) {
878 [blockOp addNullableDependency:view.keyStateNonTransientDependency];
879 [statusOp addDependency:blockOp];
880 }
881 [self.operationQueue addOperation:blockOp];
882 }
883 [self.operationQueue addOperation:statusOp];
884
885 return;
886 }
887
888 - (void)rpcStatus:(NSString*)viewName reply:(void (^)(NSArray<NSDictionary*>* result, NSError* error))reply
889 {
890 [self rpcStatus:viewName global:true reply:reply viewBlock:^NSDictionary *(CKKSKeychainView *view) {
891 return [view status];
892 }];
893 }
894
895 - (void)rpcFastStatus:(NSString*)viewName reply:(void (^)(NSArray<NSDictionary*>* result, NSError* error))reply
896 {
897 [self rpcStatus:viewName global:false reply:reply viewBlock:^NSDictionary *(CKKSKeychainView *view) {
898 return [view fastStatus];
899 }];
900 }
901
902 - (void)rpcFetchAndProcessChanges:(NSString*)viewName reply: (void(^)(NSError* result))reply {
903 [self rpcFetchAndProcessChanges:viewName classA:false reply: (void(^)(NSError* result))reply];
904 }
905
906 - (void)rpcFetchAndProcessClassAChanges:(NSString*)viewName reply: (void(^)(NSError* result))reply {
907 [self rpcFetchAndProcessChanges:viewName classA:true reply:(void(^)(NSError* result))reply];
908 }
909
910 - (void)rpcFetchAndProcessChanges:(NSString*)viewName classA:(bool)classAError reply: (void(^)(NSError* result)) reply {
911 NSError* error = nil;
912 NSArray* actualViews = [self views:viewName operation:@"fetch" error:&error];
913 if(!actualViews || error) {
914 reply(error);
915 return;
916 }
917
918 CKKSResultOperation* blockOp = [[CKKSResultOperation alloc] init];
919 blockOp.name = @"rpc-fetch-and-process-result";
920 __weak __typeof(blockOp) weakBlockOp = blockOp;
921 // Use the completion block instead of the operation block, so that it runs even if the cancel fires
922 [blockOp setCompletionBlock:^{
923 __strong __typeof(blockOp) strongBlockOp = weakBlockOp;
924 [strongBlockOp allDependentsSuccessful];
925 reply(CKXPCSuitableError(strongBlockOp.error));
926 }];
927
928 for(CKKSKeychainView* view in actualViews) {
929 ckksnotice("ckks", view, "Beginning fetch for %@", view);
930
931 CKKSResultOperation* op = [view processIncomingQueue:classAError after:[view.zoneChangeFetcher requestSuccessfulFetch: CKKSFetchBecauseAPIFetchRequest]];
932 [blockOp addDependency:op];
933 }
934
935 [self.operationQueue addOperation: [blockOp timeout:(SecCKKSTestsEnabled() ? NSEC_PER_SEC * 5 : NSEC_PER_SEC * 120)]];
936 }
937
938 - (void)rpcPushOutgoingChanges:(NSString*)viewName reply: (void(^)(NSError* result))reply {
939 NSError* error = nil;
940 NSArray* actualViews = [self views:viewName operation:@"push" error:&error];
941 if(!actualViews || error) {
942 reply(error);
943 return;
944 }
945
946 CKKSResultOperation* blockOp = [[CKKSResultOperation alloc] init];
947 blockOp.name = @"rpc-push";
948 __weak __typeof(blockOp) weakBlockOp = blockOp;
949 // Use the completion block instead of the operation block, so that it runs even if the cancel fires
950 [blockOp setCompletionBlock:^{
951 __strong __typeof(blockOp) strongBlockOp = weakBlockOp;
952 [strongBlockOp allDependentsSuccessful];
953 reply(CKXPCSuitableError(strongBlockOp.error));
954 }];
955
956 for(CKKSKeychainView* view in actualViews) {
957 ckksnotice("ckks-rpc", view, "Beginning push for %@", view);
958
959 CKKSResultOperation* op = [view processOutgoingQueue: [CKOperationGroup CKKSGroupWithName:@"rpc-push"]];
960 [blockOp addDependency:op];
961 }
962
963 [self.operationQueue addOperation: [blockOp timeout:(SecCKKSTestsEnabled() ? NSEC_PER_SEC * 2 : NSEC_PER_SEC * 120)]];
964 }
965
966 - (void)rpcGetCKDeviceIDWithReply:(void (^)(NSString *))reply {
967 reply(self.accountTracker.ckdeviceID);
968 }
969
970 -(void)xpc24HrNotification {
971 // XPC has poked us and said we should do some cleanup!
972
973 // For now, poke the views and tell them to update their device states if they'd like
974 NSArray* actualViews = nil;
975 @synchronized(self.views) {
976 // Can't safely iterate a mutable collection, so copy it.
977 actualViews = self.views.allValues;
978 }
979
980 secnotice("ckks", "Received a 24hr notification from XPC");
981 CKOperationGroup* group = [CKOperationGroup CKKSGroupWithName:@"periodic-device-state-update"];
982 for(CKKSKeychainView* view in actualViews) {
983 ckksnotice("ckks", view, "Starting device state XPC update");
984 // Let the update know it should rate-limit itself
985 [view updateDeviceState:true waitForKeyHierarchyInitialization:30*NSEC_PER_SEC ckoperationGroup:group];
986 }
987 }
988
989 - (NSArray<NSDictionary *> * _Nullable)loadRestoredBottledKeysOfType:(OctagonKeyType)keyType error:(NSError**)error
990 {
991 CFTypeRef result = NULL;
992 NSMutableArray* bottledPeerKeychainItems = nil;
993
994 NSDictionary* query = @{
995 (id)kSecClass : (id)kSecClassInternetPassword,
996 (id)kSecAttrAccessible: (id)kSecAttrAccessibleWhenUnlocked,
997 (id)kSecAttrNoLegacy : @YES,
998 (id)kSecAttrType : [[NSNumber alloc]initWithInt: keyType],
999 (id)kSecAttrServer : (keyType == 1) ? @"Octagon Signing Key" : @"Octagon Encryption Key",
1000 (id)kSecAttrAccessGroup: @"com.apple.security.ckks",
1001 (id)kSecMatchLimit : (id)kSecMatchLimitAll,
1002 (id)kSecReturnAttributes: @YES,
1003 (id)kSecReturnData: @YES,
1004 };
1005 OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, &result);
1006
1007 if(status == errSecSuccess && result && isArray(result)) {
1008 bottledPeerKeychainItems = CFBridgingRelease(result);
1009 result = NULL;
1010 } else {
1011 if(error) {
1012 *error = [NSError errorWithDomain:NSOSStatusErrorDomain
1013 code:status
1014 description:@"could not load bottled peer keys"];
1015 }
1016 CFReleaseNull(result);
1017 }
1018
1019 return bottledPeerKeychainItems;
1020 }
1021
1022 -(NSDictionary *) keychainItemForPeerID:(NSString*)neededPeerID
1023 keychainItems:(NSArray<NSDictionary*> *)keychainItems
1024 escrowSigningPubKeyHash:(NSString *)hashWeNeedToMatch
1025 {
1026 NSDictionary* peerItem = nil;
1027
1028 for(NSDictionary* item in keychainItems){
1029 if(item && [item count] > 0){
1030 NSString* peerIDFromItem = [item objectForKey:(id)kSecAttrAccount];
1031 NSString* hashToConsider = [item objectForKey:(id)kSecAttrLabel];
1032 if([peerIDFromItem isEqualToString:neededPeerID] &&
1033 [hashWeNeedToMatch isEqualToString:hashToConsider])
1034 {
1035 peerItem = [item copy];
1036 break;
1037 }
1038 }
1039 }
1040
1041 return peerItem;
1042 }
1043
1044 - (NSSet<id<CKKSSelfPeer>>*)pastSelves:(NSError**)error
1045 {
1046 NSError* localError = nil;
1047
1048 // get bottled peer identities from the keychain
1049 NSMutableSet<id<CKKSSelfPeer>>* allSelves = [NSMutableSet set];
1050 NSArray<NSDictionary*>* signingKeys = [self loadRestoredBottledKeysOfType:OctagonSigningKey error:&localError];
1051 if(!signingKeys) {
1052 // Item not found isn't actually an error here
1053 if(error && !(localError && [localError.domain isEqualToString: NSOSStatusErrorDomain] && localError.code == errSecItemNotFound)) {
1054 *error = localError;
1055 }
1056
1057 return allSelves;
1058 }
1059
1060 NSArray<NSDictionary*>* encryptionKeys = [self loadRestoredBottledKeysOfType:OctagonEncryptionKey error:&localError];
1061 if(!encryptionKeys) {
1062 if(error && !(localError && [localError.domain isEqualToString: NSOSStatusErrorDomain] && localError.code == errSecItemNotFound)) {
1063 *error = localError;
1064 }
1065 return allSelves;
1066 }
1067
1068 for(NSDictionary* signingKey in signingKeys) {
1069 NSError* peerError = nil;
1070 NSString* peerid = signingKey[(id)kSecAttrAccount];
1071 NSString* hash = signingKey[(id)kSecAttrLabel]; // escrow signing pub key hash
1072
1073 //use peer id AND escrow signing public key hash to look up the matching item in encryptionKeys list
1074 NSDictionary* encryptionKeyItem = [self keychainItemForPeerID:peerid keychainItems:encryptionKeys escrowSigningPubKeyHash:hash];
1075 if(!encryptionKeyItem) {
1076 secerror("octagon: no encryption key available to pair with signing key %@,%@", peerid, hash);
1077 continue;
1078 }
1079
1080 NSData* signingKeyData = signingKey[(id)kSecValueData];
1081 if(!signingKeyData) {
1082 secerror("octagon: no signing key data for %@,%@", peerid,hash);
1083 continue;
1084 }
1085
1086 SFECKeyPair* restoredSigningKey = [[SFECKeyPair alloc] initWithData:signingKeyData
1087 specifier:[[SFECKeySpecifier alloc] initWithCurve:SFEllipticCurveNistp384]
1088 error:&peerError];
1089 if(!restoredSigningKey) {
1090 secerror("octagon: couldn't make signing key for %@,%@: %@", peerid, hash, peerError);
1091 continue;
1092 }
1093
1094 NSData* encryptionKeyData = [encryptionKeyItem objectForKey:(id)kSecValueData];
1095 if(!encryptionKeyData) {
1096 secerror("octagon: no encryption key data for %@,%@", peerid,hash);
1097 continue;
1098 }
1099
1100 SFECKeyPair* restoredEncryptionKey = [[SFECKeyPair alloc] initWithData:encryptionKeyData
1101 specifier:[[SFECKeySpecifier alloc] initWithCurve:SFEllipticCurveNistp384]
1102 error:&peerError];
1103 if(!restoredEncryptionKey) {
1104 secerror("octagon: couldn't make encryption key for %@,%@: %@", peerid,hash, peerError);
1105 continue;
1106 }
1107
1108 //create the SOS self peer
1109 CKKSSOSSelfPeer* restoredIdentity = [[CKKSSOSSelfPeer alloc]initWithSOSPeerID:peerid encryptionKey:restoredEncryptionKey signingKey:restoredSigningKey];
1110
1111 if(restoredIdentity){
1112 secnotice("octagon","adding bottled peer identity: %@", restoredIdentity);
1113 [allSelves addObject:restoredIdentity];
1114 } else {
1115 secerror("octagon: could not create restored identity from: %@: %@", peerid, peerError);
1116 }
1117 }
1118 return allSelves;
1119 }
1120
1121 - (id<CKKSSelfPeer> _Nullable)currentSOSSelf:(NSError**)error
1122 {
1123 __block SFECKeyPair* signingPrivateKey = nil;
1124 __block SFECKeyPair* encryptionPrivateKey = nil;
1125
1126 __block NSError* localerror = nil;
1127
1128 // Wait for this to initialize, but don't worry if it isn't.
1129 [self.accountTracker.accountCirclePeerIDInitialized wait:500*NSEC_PER_MSEC];
1130 NSString* peerID = self.accountTracker.accountCirclePeerID;
1131 if(!peerID || self.accountTracker.accountCirclePeerIDError) {
1132 secerror("ckkspeer: Error fetching self peer : %@", self.accountTracker.accountCirclePeerIDError);
1133 if(error) {
1134 *error = self.accountTracker.accountCirclePeerIDError;
1135 }
1136 return nil;
1137 }
1138
1139 SOSCCPerformWithAllOctagonKeys(^(SecKeyRef octagonEncryptionKey, SecKeyRef octagonSigningKey, CFErrorRef cferror) {
1140 if(cferror) {
1141 localerror = (__bridge NSError*)cferror;
1142 return;
1143 }
1144 if (!cferror && octagonEncryptionKey && octagonSigningKey) {
1145 signingPrivateKey = [[SFECKeyPair alloc] initWithSecKey:octagonSigningKey];
1146 encryptionPrivateKey = [[SFECKeyPair alloc] initWithSecKey:octagonEncryptionKey];
1147 } else {
1148 localerror = [NSError errorWithDomain:CKKSErrorDomain
1149 code:CKKSNoPeersAvailable
1150 description:@"Not all SOS peer keys available, but no error returned"];
1151 }
1152 });
1153
1154 if(localerror) {
1155 if(![self.lockStateTracker isLockedError:localerror]) {
1156 secerror("ckkspeer: Error fetching self encryption keys: %@", localerror);
1157 }
1158 if(error) {
1159 *error = localerror;
1160 }
1161 return nil;
1162 }
1163
1164 CKKSSOSSelfPeer* selfPeer = [[CKKSSOSSelfPeer alloc] initWithSOSPeerID:peerID
1165 encryptionKey:encryptionPrivateKey
1166 signingKey:signingPrivateKey];
1167 return selfPeer;
1168 }
1169
1170 #pragma mark - CKKSPeerProvider implementation
1171
1172 - (CKKSSelves*)fetchSelfPeers:(NSError* __autoreleasing *)error {
1173 NSError* localError = nil;
1174
1175 id<CKKSSelfPeer> selfPeer = [self currentSOSSelf:&localError];
1176 if(!selfPeer || localError) {
1177 if(![self.lockStateTracker isLockedError:localError]) {
1178 secerror("ckks: Error fetching current SOS self: %@", localError);
1179 }
1180 if(error) {
1181 *error = localError;
1182 }
1183 return nil;
1184 }
1185
1186 NSSet<id<CKKSSelfPeer>>* allSelves = [self pastSelves:&localError];
1187 if(!allSelves || localError) {
1188 secerror("ckks: Error fetching past selves: %@", localError);
1189 if(error) {
1190 *error = localError;
1191 }
1192 return nil;
1193 }
1194
1195 CKKSSelves* selves = [[CKKSSelves alloc] initWithCurrent:selfPeer allSelves:allSelves];
1196 return selves;
1197 }
1198
1199 - (NSSet<id<CKKSPeer>>*)fetchTrustedPeers:(NSError* __autoreleasing *)error {
1200 __block NSMutableSet<id<CKKSPeer>>* peerSet = [NSMutableSet set];
1201
1202 SOSCCPerformWithTrustedPeers(^(CFSetRef sosPeerInfoRefs, CFErrorRef cfTrustedPeersError) {
1203 if(cfTrustedPeersError) {
1204 secerror("ckks: Error fetching trusted peers: %@", cfTrustedPeersError);
1205 if(error) {
1206 *error = (__bridge NSError*)cfTrustedPeersError;
1207 }
1208 }
1209
1210 CFSetForEach(sosPeerInfoRefs, ^(const void* voidPeer) {
1211 CFErrorRef cfPeerError = NULL;
1212 SOSPeerInfoRef sosPeerInfoRef = (SOSPeerInfoRef)voidPeer;
1213
1214 if(!sosPeerInfoRef) {
1215 return;
1216 }
1217
1218 CFStringRef cfpeerID = SOSPeerInfoGetPeerID(sosPeerInfoRef);
1219 SecKeyRef cfOctagonSigningKey = NULL, cfOctagonEncryptionKey = NULL;
1220
1221 cfOctagonSigningKey = SOSPeerInfoCopyOctagonSigningPublicKey(sosPeerInfoRef, &cfPeerError);
1222 if (cfOctagonSigningKey) {
1223 cfOctagonEncryptionKey = SOSPeerInfoCopyOctagonEncryptionPublicKey(sosPeerInfoRef, &cfPeerError);
1224 }
1225
1226 if(cfOctagonSigningKey == NULL || cfOctagonEncryptionKey == NULL) {
1227 // Don't log non-debug for -50; it almost always just means this peer didn't have octagon keys
1228 if(cfPeerError == NULL
1229 || !(CFEqualSafe(CFErrorGetDomain(cfPeerError), kCFErrorDomainOSStatus) && (CFErrorGetCode(cfPeerError) == errSecParam)))
1230 {
1231 secerror("ckkspeer: error fetching octagon keys for peer: %@ %@", sosPeerInfoRef, cfPeerError);
1232 } else {
1233 secinfo("ckkspeer", "Peer(%@) doesn't have Octagon keys, but this is expected: %@", cfpeerID, cfPeerError);
1234 }
1235 }
1236
1237 // Add all peers to the trust set: old-style SOS peers will just have null keys
1238 SFECPublicKey* signingPublicKey = cfOctagonSigningKey ? [[SFECPublicKey alloc] initWithSecKey:cfOctagonSigningKey] : nil;
1239 SFECPublicKey* encryptionPublicKey = cfOctagonEncryptionKey ? [[SFECPublicKey alloc] initWithSecKey:cfOctagonEncryptionKey] : nil;
1240
1241 CKKSSOSPeer* peer = [[CKKSSOSPeer alloc] initWithSOSPeerID:(__bridge NSString*)cfpeerID
1242 encryptionPublicKey:encryptionPublicKey
1243 signingPublicKey:signingPublicKey];
1244 [peerSet addObject:peer];
1245
1246 CFReleaseNull(cfOctagonSigningKey);
1247 CFReleaseNull(cfOctagonEncryptionKey);
1248 CFReleaseNull(cfPeerError);
1249 });
1250 });
1251
1252 return peerSet;
1253 }
1254
1255 - (void)registerForPeerChangeUpdates:(id<CKKSPeerUpdateListener>)listener {
1256 @synchronized(self.peerChangeListeners) {
1257 bool alreadyRegisteredListener = false;
1258 NSEnumerator *enumerator = [self.peerChangeListeners objectEnumerator];
1259 id<CKKSPeerUpdateListener> value;
1260
1261 while ((value = [enumerator nextObject])) {
1262 // do pointer comparison
1263 alreadyRegisteredListener |= (value == listener);
1264 }
1265
1266 if(listener && !alreadyRegisteredListener) {
1267 NSString* queueName = [NSString stringWithFormat: @"ck-peer-change-%@", listener];
1268
1269 dispatch_queue_t objQueue = dispatch_queue_create([queueName UTF8String], DISPATCH_QUEUE_SERIAL_WITH_AUTORELEASE_POOL);
1270 [self.peerChangeListeners setObject: listener forKey: objQueue];
1271 }
1272 }
1273 }
1274
1275 - (void)iteratePeerListenersOnTheirQueue:(void (^)(id<CKKSPeerUpdateListener>))block {
1276 @synchronized(self.peerChangeListeners) {
1277 NSEnumerator *enumerator = [self.peerChangeListeners keyEnumerator];
1278 dispatch_queue_t dq;
1279
1280 // Queue up the changes for each listener.
1281 while ((dq = [enumerator nextObject])) {
1282 id<CKKSPeerUpdateListener> listener = [self.peerChangeListeners objectForKey: dq];
1283 __weak id<CKKSPeerUpdateListener> weakListener = listener;
1284
1285 if(listener) {
1286 dispatch_async(dq, ^{
1287 __strong id<CKKSPeerUpdateListener> strongListener = weakListener;
1288 block(strongListener);
1289 });
1290 }
1291 }
1292 }
1293 }
1294
1295 - (void)sendSelfPeerChangedUpdate {
1296 [self.completedSecCKKSInitialize wait:5*NSEC_PER_SEC]; // Wait for bringup, but don't worry if this times out
1297
1298 [self iteratePeerListenersOnTheirQueue: ^(id<CKKSPeerUpdateListener> listener) {
1299 [listener selfPeerChanged];
1300 }];
1301 }
1302
1303 - (void)sendTrustedPeerSetChangedUpdate {
1304 [self.completedSecCKKSInitialize wait:5*NSEC_PER_SEC]; // Wait for bringup, but don't worry if this times out
1305
1306 [self iteratePeerListenersOnTheirQueue: ^(id<CKKSPeerUpdateListener> listener) {
1307 [listener trustedPeerSetChanged];
1308 }];
1309 }
1310 #endif // OCTAGON
1311 @end