]> git.saurik.com Git - apple/security.git/blob - keychain/ot/OTManager.m
Security-59754.60.13.tar.gz
[apple/security.git] / keychain / ot / OTManager.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
25 #include <Security/SecEntitlements.h>
26 #import <Foundation/NSXPCConnection.h>
27 #import <Foundation/NSXPCConnection_Private.h>
28
29 #if OCTAGON
30 #import <TargetConditionals.h>
31 #import "keychain/ot/ObjCImprovements.h"
32 #import "keychain/ot/OTControlProtocol.h"
33 #import "keychain/ot/OTControl.h"
34 #import "keychain/ot/OTClique.h"
35 #import "keychain/ot/OTManager.h"
36 #import "keychain/ot/OTDefines.h"
37 #import "keychain/ot/OTRamping.h"
38 #import "keychain/ot/OT.h"
39 #import "keychain/ot/OTConstants.h"
40 #import "keychain/ot/OTCuttlefishContext.h"
41 #import "keychain/ot/OTClientStateMachine.h"
42 #import "keychain/ot/OTStates.h"
43 #import "keychain/ot/OTJoiningConfiguration.h"
44 #import "keychain/ot/OTSOSAdapter.h"
45
46 #import "keychain/categories/NSError+UsefulConstructors.h"
47 #import "keychain/ckks/CKKSAnalytics.h"
48 #import "keychain/ckks/CKKSNearFutureScheduler.h"
49 #import "keychain/ckks/CKKS.h"
50 #import "keychain/ckks/CKKSViewManager.h"
51 #import "keychain/ckks/CKKSLockStateTracker.h"
52 #import "keychain/ckks/CKKSCloudKitClassDependencies.h"
53
54 #import <CloudKit/CloudKit.h>
55 #import <CloudKit/CloudKit_Private.h>
56
57 #import <SecurityFoundation/SFKey.h>
58 #import <SecurityFoundation/SFKey_Private.h>
59 #import "SecPasswordGenerate.h"
60
61 #import "keychain/categories/NSError+UsefulConstructors.h"
62 #include <CloudKit/CloudKit_Private.h>
63 #import <KeychainCircle/PairingChannel.h>
64
65 #import "keychain/escrowrequest/Framework/SecEscrowRequest.h"
66 #import "keychain/escrowrequest/EscrowRequestServer.h"
67
68 // If your callbacks might pass back a CK error, you should use XPCSanitizeError()
69 // Otherwise, XPC might crash on the other side if they haven't linked CloudKit.framework.
70 #define XPCSanitizeError CKXPCSuitableError
71
72 #import <Accounts/Accounts.h>
73 #import <Accounts/ACAccountStore_Private.h>
74 #import <Accounts/ACAccountType_Private.h>
75 #import <Accounts/ACAccountStore.h>
76 #import <AppleAccount/ACAccountStore+AppleAccount.h>
77 #import <AppleAccount/ACAccount+AppleAccount.h>
78
79 #import <CoreCDP/CDPFollowUpController.h>
80
81 #import <SoftLinking/SoftLinking.h>
82 #import <CloudServices/SecureBackup.h>
83
84 #import "keychain/TrustedPeersHelper/TPHObjcTranslation.h"
85 #import "keychain/SecureObjectSync/SOSAccountTransaction.h"
86 #pragma clang diagnostic push
87 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
88 #import "keychain/SecureObjectSync/SOSAccount.h"
89 #pragma clang diagnostic pop
90
91 #import "utilities/SecTapToRadar.h"
92
93 SOFT_LINK_OPTIONAL_FRAMEWORK(PrivateFrameworks, CloudServices);
94 SOFT_LINK_CLASS(CloudServices, SecureBackup);
95
96 static NSString* const kOTRampForEnrollmentRecordName = @"metadata_rampstate_enroll";
97 static NSString* const kOTRampForRestoreRecordName = @"metadata_rampstate_restore";
98 static NSString* const kOTRampForCFURecordName = @"metadata_rampstate_cfu";
99 static NSString* const kOTRampForGhostBustMIDName = @"metadata_rampstate_ghostBustMID";
100 static NSString* const kOTRampForghostBustSerialName = @"metadata_rampstate_ghostBustSerial";
101 static NSString* const kOTRampForghostBustAgeName = @"metadata_rampstate_ghostBustAge";
102 static NSString* const kOTRampZoneName = @"metadata_zone";
103 #define NUM_NSECS_IN_24_HRS (86400 * NSEC_PER_SEC)
104
105 #if OCTAGON
106 @interface OTManager (lockstateTracker) <CKKSLockStateNotification>
107 @end
108 #endif
109
110 @interface OTManager () <NSXPCListenerDelegate>
111 @property NSXPCListener *listener;
112
113 @property (nonatomic, strong) OTRamp *gbmidRamp;
114 @property (nonatomic, strong) OTRamp *gbserialRamp;
115 @property (nonatomic, strong) OTRamp *gbAgeRamp;
116 @property (nonatomic, strong) CKKSLockStateTracker *lockStateTracker;
117 @property (nonatomic, strong) id<OctagonFollowUpControllerProtocol> cdpd;
118
119 // Current contexts
120 @property NSMutableDictionary<NSString*, OTCuttlefishContext*>* contexts;
121 @property NSMutableDictionary<NSString*, OTClientStateMachine*>* clients;
122 @property dispatch_queue_t queue;
123
124 @property id<NSXPCProxyCreating> cuttlefishXPCConnection;
125
126 // Dependencies for injection
127 @property (readonly) id<OTSOSAdapter> sosAdapter;
128 @property (readonly) id<OTAuthKitAdapter> authKitAdapter;
129 @property (readonly) id<OTDeviceInformationAdapter> deviceInformationAdapter;
130 @property (readonly) Class<OctagonAPSConnection> apsConnectionClass;
131 @property (readonly) Class<SecEscrowRequestable> escrowRequestClass;
132 // If this is nil, all logging is disabled
133 @property (readonly, nullable) Class<SFAnalyticsProtocol> loggerClass;
134 @property (nonatomic) BOOL sosEnabledForPlatform;
135 @end
136
137 @implementation OTManager
138 @synthesize cuttlefishXPCConnection = _cuttlefishXPCConnection;
139 @synthesize sosAdapter = _sosAdapter;
140 @synthesize authKitAdapter = _authKitAdapter;
141 @synthesize deviceInformationAdapter = _deviceInformationAdapter;
142
143 - (instancetype)init
144 {
145 // Under Octagon, the sos adapter is not considered essential.
146 id<OTSOSAdapter> sosAdapter = (OctagonPlatformSupportsSOS() ?
147 [[OTSOSActualAdapter alloc] initAsEssential:NO] :
148 [[OTSOSMissingAdapter alloc] init]);
149
150 return [self initWithSOSAdapter:sosAdapter
151 authKitAdapter:[[OTAuthKitActualAdapter alloc] init]
152 deviceInformationAdapter:[[OTDeviceInformationActualAdapter alloc] init]
153 apsConnectionClass:[APSConnection class]
154 escrowRequestClass:[EscrowRequestServer class] // Use the server class here to skip the XPC layer
155 loggerClass:[CKKSAnalytics class]
156 lockStateTracker:[CKKSLockStateTracker globalTracker]
157 cloudKitClassDependencies:[CKKSCloudKitClassDependencies forLiveCloudKit]
158 cuttlefishXPCConnection:nil
159 cdpd:[[CDPFollowUpController alloc] init]];
160 }
161
162 - (instancetype)initWithSOSAdapter:(id<OTSOSAdapter>)sosAdapter
163 authKitAdapter:(id<OTAuthKitAdapter>)authKitAdapter
164 deviceInformationAdapter:(id<OTDeviceInformationAdapter>)deviceInformationAdapter
165 apsConnectionClass:(Class<OctagonAPSConnection>)apsConnectionClass
166 escrowRequestClass:(Class<SecEscrowRequestable>)escrowRequestClass
167 loggerClass:(Class<SFAnalyticsProtocol>)loggerClass
168 lockStateTracker:(CKKSLockStateTracker*)lockStateTracker
169 cloudKitClassDependencies:(CKKSCloudKitClassDependencies*)cloudKitClassDependencies
170 cuttlefishXPCConnection:(id<NSXPCProxyCreating>)cuttlefishXPCConnection
171 cdpd:(id<OctagonFollowUpControllerProtocol>)cdpd
172 {
173 if((self = [super init])) {
174 _sosAdapter = sosAdapter;
175 _authKitAdapter = authKitAdapter;
176 _deviceInformationAdapter = deviceInformationAdapter;
177 _loggerClass = loggerClass;
178 _lockStateTracker = lockStateTracker;
179 _sosEnabledForPlatform = OctagonPlatformSupportsSOS();
180 _cuttlefishXPCConnection = cuttlefishXPCConnection;
181
182 _cloudKitContainer = [CKKSViewManager makeCKContainer:SecCKKSContainerName usePCS:SecCKKSContainerUsePCS];
183 _accountStateTracker = [[CKKSAccountStateTracker alloc] init:_cloudKitContainer
184 nsnotificationCenterClass:cloudKitClassDependencies.nsnotificationCenterClass];
185
186 self.contexts = [NSMutableDictionary dictionary];
187 self.clients = [NSMutableDictionary dictionary];
188
189 self.queue = dispatch_queue_create("otmanager", DISPATCH_QUEUE_SERIAL);
190
191 _apsConnectionClass = apsConnectionClass;
192 _escrowRequestClass = escrowRequestClass;
193
194 _cdpd = cdpd;
195
196 _viewManager = [[CKKSViewManager alloc] initWithContainer:_cloudKitContainer
197 sosAdapter:sosAdapter
198 accountStateTracker:_accountStateTracker
199 lockStateTracker:lockStateTracker
200 cloudKitClassDependencies:cloudKitClassDependencies];
201
202 // The default CuttlefishContext always exists:
203 (void) [self contextForContainerName:OTCKContainerName contextID:OTDefaultContext];
204
205 secnotice("octagon", "otmanager init");
206 }
207 return self;
208 }
209
210 - (instancetype)initWithSOSAdapter:(id<OTSOSAdapter>)sosAdapter
211 lockStateTracker:(CKKSLockStateTracker*)lockStateTracker
212 cloudKitClassDependencies:(CKKSCloudKitClassDependencies*)cloudKitClassDependencies
213 {
214 if((self = [super init])) {
215 _sosAdapter = sosAdapter;
216 _lockStateTracker = lockStateTracker;
217
218 _cloudKitContainer = [CKKSViewManager makeCKContainer:SecCKKSContainerName usePCS:SecCKKSContainerUsePCS];
219 _accountStateTracker = [[CKKSAccountStateTracker alloc] init:_cloudKitContainer
220 nsnotificationCenterClass:cloudKitClassDependencies.nsnotificationCenterClass];
221
222 _viewManager = [[CKKSViewManager alloc] initWithContainer:_cloudKitContainer
223 sosAdapter:sosAdapter
224 accountStateTracker:_accountStateTracker
225 lockStateTracker:lockStateTracker
226 cloudKitClassDependencies:cloudKitClassDependencies];
227 }
228 return self;
229 }
230
231 - (void)initializeOctagon
232 {
233 secnotice("octagon", "Initializing Octagon...");
234
235 if(OctagonIsEnabled()) {
236 secnotice("octagon", "starting default state machine...");
237 OTCuttlefishContext* c = [self contextForContainerName:OTCKContainerName
238 contextID:OTDefaultContext];
239
240 [c startOctagonStateMachine];
241 [self registerForCircleChangedNotifications];
242 }
243 }
244
245 - (BOOL)waitForReady:(NSString* _Nullable)containerName context:(NSString*)context wait:(int64_t)wait
246 {
247 OTCuttlefishContext* c = [self contextForContainerName:containerName contextID:context];
248 return [c waitForReady:wait];
249 }
250
251 - (void) moveToCheckTrustedStateForContainer:(NSString* _Nullable)containerName context:(NSString*)context
252 {
253 OTCuttlefishContext* c = [self contextForContainerName:containerName
254 contextID:context];
255 [c startOctagonStateMachine];
256 [c moveToCheckTrustedState];
257 }
258
259 - (void)registerForCircleChangedNotifications
260 {
261 __weak __typeof(self) weakSelf = self;
262
263 // If we're not in the tests, go ahead and register for a notification
264 if(!SecCKKSTestsEnabled()) {
265 int token = 0;
266 notify_register_dispatch(kSOSCCCircleChangedNotification, &token, dispatch_get_global_queue(QOS_CLASS_UTILITY, 0), ^(int t) {
267 secnotice("octagon", "circle changed notification called, checking trust state");
268 [weakSelf moveToCheckTrustedStateForContainer:OTCKContainerName context:OTDefaultContext];
269 });
270 }
271 }
272
273 + (instancetype _Nullable)manager {
274 if(!OctagonIsEnabled()) {
275 secerror("octagon: Attempt to fetch a manager while Octagon is disabled");
276 return nil;
277 }
278
279 if([CKDatabase class] == nil) {
280 // CloudKit is not linked. We cannot bring Octagon up.
281 secerror("Octagon: CloudKit.framework appears to not be linked. Cannot create an Octagon manager (on pain of crash).");
282 return nil;
283 }
284
285 return [self resetManager:false to:nil];
286 }
287
288 + (instancetype _Nullable)resetManager:(bool)reset to:(OTManager* _Nullable)obj
289 {
290 static OTManager* manager = nil;
291
292 if(!manager || reset || obj) {
293 @synchronized([self class]) {
294 if(obj != nil) {
295 manager = obj;
296 } else {
297 if(reset) {
298 manager = nil;
299 } else if (manager == nil && OctagonIsEnabled()) {
300 manager = [[OTManager alloc] init];
301 }
302 }
303 }
304 }
305
306 return manager;
307 }
308
309 - (void)ensureRampsInitialized
310 {
311 CKContainer* container = [CKKSViewManager manager].container;
312 CKDatabase* database = [container privateCloudDatabase];
313 CKRecordZoneID* zoneID = [[CKRecordZoneID alloc] initWithZoneName:kOTRampZoneName ownerName:CKCurrentUserDefaultName];
314
315 CKKSAccountStateTracker *accountTracker = [CKKSViewManager manager].accountTracker;
316 CKKSReachabilityTracker *reachabilityTracker = [CKKSViewManager manager].reachabilityTracker;
317 CKKSLockStateTracker *lockStateTracker = [CKKSViewManager manager].lockStateTracker;
318
319 if(!self.gbmidRamp) {
320 self.gbmidRamp = [[OTRamp alloc]initWithRecordName:kOTRampForGhostBustMIDName
321 localSettingName:@"ghostBustMID"
322 container:container
323 database:database
324 zoneID:zoneID
325 accountTracker:accountTracker
326 lockStateTracker:lockStateTracker
327 reachabilityTracker:reachabilityTracker
328 fetchRecordRecordsOperationClass:[CKFetchRecordsOperation class]];
329 }
330
331 if(!self.gbserialRamp) {
332 self.gbserialRamp = [[OTRamp alloc]initWithRecordName:kOTRampForghostBustSerialName
333 localSettingName:@"ghostBustSerial"
334 container:container
335 database:database
336 zoneID:zoneID
337 accountTracker:accountTracker
338 lockStateTracker:lockStateTracker
339 reachabilityTracker:reachabilityTracker
340 fetchRecordRecordsOperationClass:[CKFetchRecordsOperation class]];
341 }
342
343 if(!self.gbAgeRamp) {
344 self.gbAgeRamp = [[OTRamp alloc]initWithRecordName:kOTRampForghostBustAgeName
345 localSettingName:@"ghostBustAge"
346 container:container
347 database:database
348 zoneID:zoneID
349 accountTracker:accountTracker
350 lockStateTracker:lockStateTracker
351 reachabilityTracker:reachabilityTracker
352 fetchRecordRecordsOperationClass:[CKFetchRecordsOperation class]];
353 }
354 }
355
356 ////
357 // MARK: SPI routines
358 ////
359
360
361 - (void)signIn:(NSString*)altDSID
362 container:(NSString* _Nullable)containerName
363 context:(NSString*)contextID
364 reply:(void (^)(NSError * _Nullable signedInError))reply
365 {
366 SFAnalyticsActivityTracker *tracker = [[self.loggerClass logger] startLogSystemMetricsForActivityNamed:OctagonActivityAccountAvailable];
367
368 if(containerName == nil) {
369 containerName = OTCKContainerName;
370 }
371
372 NSError *error = nil;
373 OTCuttlefishContext* context = [self contextForContainerName:containerName contextID:contextID];
374
375 secnotice("octagon","signing in %@ for altDSID: %@", context, altDSID);
376 [context accountAvailable:altDSID error:&error];
377
378 [tracker stopWithEvent:OctagonEventSignIn result:error];
379
380 reply(error);
381 }
382
383 - (void)signOut:(NSString* _Nullable)containerName
384 context:(NSString*)contextID
385 reply:(void (^)(NSError * _Nullable signedOutError))reply
386 {
387 SFAnalyticsActivityTracker *tracker = [[self.loggerClass logger] startLogSystemMetricsForActivityNamed:OctagonActivityAccountNotAvailable];
388
389 if(containerName == nil) {
390 containerName = OTCKContainerName;
391 }
392
393 NSError* error = nil;
394
395 // TODO: should we compare DSIDs here?
396 OTCuttlefishContext* context = [self contextForContainerName:containerName contextID:contextID];
397
398 secnotice("octagon", "signing out of octagon trust: %@", context);
399
400 [context accountNoLongerAvailable:&error];
401 if(error) {
402 secnotice("octagon", "signing out failed: %@", error);
403 }
404
405 [tracker stopWithEvent:OctagonEventSignOut result:error];
406
407 reply(error);
408 }
409
410 - (void)notifyIDMSTrustLevelChangeForContainer:(NSString* _Nullable)containerName
411 context:(NSString*)contextID
412 reply:(void (^)(NSError * _Nullable error))reply
413 {
414 if(containerName == nil) {
415 containerName = OTCKContainerName;
416 }
417
418 NSError *error = nil;
419 OTCuttlefishContext* context = [self contextForContainerName:containerName contextID:contextID];
420 secnotice("octagon","received a notification of IDMS trust level change in %@", context);
421 [context idmsTrustLevelChanged:&error];
422
423 reply(error);
424 }
425
426 - (void)handleIdentityChangeForSigningKey:(SFECKeyPair*)peerSigningKey
427 ForEncryptionKey:(SFECKeyPair*)encryptionKey
428 ForPeerID:(NSString*)peerID
429 reply:(void (^)(BOOL result,
430 NSError* _Nullable error))reply
431 {
432 secnotice("octagon", "handleIdentityChangeForSigningKey: %@", peerID);
433 reply(NO,
434 [NSError errorWithDomain:(__bridge NSString*)kSecErrorDomain
435 code:errSecUnimplemented
436 userInfo:nil]);
437 }
438
439 - (void)preflightBottledPeer:(NSString*)contextID
440 dsid:(NSString*)dsid
441 reply:(void (^)(NSData* _Nullable entropy,
442 NSString* _Nullable bottleID,
443 NSData* _Nullable signingPublicKey,
444 NSError* _Nullable error))reply
445 {
446 secnotice("octagon", "preflightBottledPeer: %@ %@", contextID, dsid);
447 reply(nil,
448 nil,
449 nil,
450 [NSError errorWithDomain:(__bridge NSString*)kSecErrorDomain
451 code:errSecUnimplemented
452 userInfo:nil]);
453 }
454
455 - (void)launchBottledPeer:(NSString*)contextID
456 bottleID:(NSString*)bottleID
457 reply:(void (^ _Nullable)(NSError* _Nullable error))reply
458 {
459 secnotice("octagon", "launchBottledPeer");
460 reply([NSError errorWithDomain:(__bridge NSString*)kSecErrorDomain
461 code:errSecUnimplemented
462 userInfo:nil]);
463 }
464
465 - (void)restore:(NSString *)contextID dsid:(NSString *)dsid secret:(NSData*)secret escrowRecordID:(NSString*)escrowRecordID reply:(void (^)(NSData* signingKeyData, NSData* encryptionKeyData, NSError *))reply
466 {
467 secnotice("octagon", "restore");
468 reply(nil,
469 nil,
470 [NSError errorWithDomain:(__bridge NSString*)kSecErrorDomain
471 code:errSecUnimplemented
472 userInfo:nil]);
473 }
474
475 - (void)scrubBottledPeer:(NSString*)contextID
476 bottleID:(NSString*)bottleID
477 reply:(void (^ _Nullable)(NSError* _Nullable error))reply
478 {
479 reply([NSError errorWithDomain:(__bridge NSString*)kSecErrorDomain
480 code:errSecUnimplemented
481 userInfo:nil]);
482 }
483
484 ////
485 // MARK: OTCTL tool routines
486 ////
487
488 -(void)reset:(void (^)(BOOL result, NSError *))reply
489 {
490 reply(NO,
491 [NSError errorWithDomain:(__bridge NSString*)kSecErrorDomain
492 code:errSecUnimplemented
493 userInfo:nil]);
494 }
495
496 - (void)listOfEligibleBottledPeerRecords:(void (^)(NSArray* listOfRecords, NSError * _Nullable))reply
497 {
498 reply(@[],
499 [NSError errorWithDomain:(__bridge NSString*)kSecErrorDomain
500 code:errSecUnimplemented
501 userInfo:nil]);
502 }
503
504 - (void)octagonEncryptionPublicKey:(void (^)(NSData* encryptionKey, NSError *))reply
505 {
506 reply(nil,
507 [NSError errorWithDomain:(__bridge NSString*)kSecErrorDomain
508 code:errSecUnimplemented
509 userInfo:nil]);
510 }
511
512 -(void)octagonSigningPublicKey:(void (^)(NSData* encryptionKey, NSError *))reply
513 {
514 reply(nil,
515 [NSError errorWithDomain:(__bridge NSString*)kSecErrorDomain
516 code:errSecUnimplemented
517 userInfo:nil]);
518 }
519
520 - (void)setCuttlefishXPCConnection:(id<NSXPCProxyCreating>)cuttlefishXPCConnection
521 {
522 _cuttlefishXPCConnection = cuttlefishXPCConnection;
523 }
524
525 - (id<NSXPCProxyCreating>)cuttlefishXPCConnection
526 {
527 if(!_cuttlefishXPCConnection) {
528 NSXPCConnection* xpcConnection = [[NSXPCConnection alloc] initWithServiceName:@"com.apple.TrustedPeersHelper"];
529 xpcConnection.remoteObjectInterface = TrustedPeersHelperSetupProtocol([NSXPCInterface interfaceWithProtocol:@protocol(TrustedPeersHelperProtocol)]);
530 [xpcConnection resume];
531 _cuttlefishXPCConnection = xpcConnection;
532 }
533
534 return _cuttlefishXPCConnection;
535 }
536
537 - (OTClientStateMachine*)clientStateMachineForContainerName:(NSString* _Nullable)containerName
538 contextID:(NSString*)contextID
539 clientName:(NSString*)clientName
540 {
541 __block OTClientStateMachine* client = nil;
542
543 if(containerName == nil) {
544 containerName = SecCKKSContainerName;
545 }
546
547 dispatch_sync(self.queue, ^{
548 NSString* key = [NSString stringWithFormat:@"%@-%@", containerName, clientName];
549 secnotice("octagon-client", "fetching context for key: %@", key);
550 client = self.clients[key];
551 if(!client) {
552 client = [[OTClientStateMachine alloc] initWithContainerName:containerName
553 contextID:contextID
554 clientName:clientName
555 cuttlefish:self.cuttlefishXPCConnection];
556
557 self.clients[key] = client;
558 }
559 });
560
561 return client;
562 }
563
564 - (void)removeClientContextForContainerName:(NSString* _Nullable)containerName
565 clientName:(NSString*)clientName
566 {
567 if(containerName == nil) {
568 containerName = SecCKKSContainerName;
569 }
570
571 dispatch_sync(self.queue, ^{
572 NSString* key = [NSString stringWithFormat:@"%@-%@", containerName, clientName];
573 [self.clients removeObjectForKey:key];
574 secnotice("octagon", "removed client context with key: %@", key);
575 });
576 }
577
578 - (void)removeContextForContainerName:(NSString*)containerName
579 contextID:(NSString*)contextID
580 {
581 dispatch_sync(self.queue, ^{
582 NSString* key = [NSString stringWithFormat:@"%@-%@", containerName, contextID];
583 self.contexts[key] = nil;
584 });
585 }
586
587 - (OTCuttlefishContext*)contextForContainerName:(NSString* _Nullable)containerName
588 contextID:(NSString*)contextID
589 {
590 return [self contextForContainerName:containerName
591 contextID:contextID
592 sosAdapter:self.sosAdapter
593 authKitAdapter:self.authKitAdapter
594 lockStateTracker:self.lockStateTracker
595 accountStateTracker:self.accountStateTracker
596 deviceInformationAdapter:self.deviceInformationAdapter];
597 }
598
599 - (OTCuttlefishContext*)contextForContainerName:(NSString* _Nullable)containerName
600 contextID:(NSString*)contextID
601 sosAdapter:(id<OTSOSAdapter>)sosAdapter
602 authKitAdapter:(id<OTAuthKitAdapter>)authKitAdapter
603 lockStateTracker:(CKKSLockStateTracker*)lockStateTracker
604 accountStateTracker:(CKKSAccountStateTracker*)accountStateTracker
605 deviceInformationAdapter:(id<OTDeviceInformationAdapter>)deviceInformationAdapter
606 {
607 __block OTCuttlefishContext* context = nil;
608
609 if(containerName == nil) {
610 containerName = SecCKKSContainerName;
611 }
612
613 dispatch_sync(self.queue, ^{
614 NSString* key = [NSString stringWithFormat:@"%@-%@", containerName, contextID];
615
616 context = self.contexts[key];
617
618 // Right now, CKKS can only handle one session per address space (and SQL database).
619 // Therefore, only the primary OTCuttlefishContext gets to own the view manager.
620 CKKSViewManager* viewManager = nil;
621 if([containerName isEqualToString:SecCKKSContainerName] &&
622 [contextID isEqualToString:OTDefaultContext]) {
623 viewManager = self.viewManager;
624 }
625
626 if(!context) {
627 context = [[OTCuttlefishContext alloc] initWithContainerName:containerName
628 contextID:contextID
629 cuttlefish:self.cuttlefishXPCConnection
630 sosAdapter:sosAdapter
631 authKitAdapter:authKitAdapter
632 ckksViewManager:viewManager
633 lockStateTracker:lockStateTracker
634 accountStateTracker:accountStateTracker
635 deviceInformationAdapter:deviceInformationAdapter
636 apsConnectionClass:self.apsConnectionClass
637 escrowRequestClass:self.escrowRequestClass
638 cdpd:self.cdpd];
639 self.contexts[key] = context;
640 }
641 });
642
643 return context;
644 }
645
646 - (void)clearAllContexts
647 {
648 if(self.contexts) {
649 dispatch_sync(self.queue, ^{
650 [self.contexts removeAllObjects];
651 });
652 }
653 }
654
655 - (void)fetchEgoPeerID:(NSString* _Nullable)container
656 context:(NSString*)context
657 reply:(void (^)(NSString* _Nullable peerID, NSError* _Nullable error))reply
658 {
659 if(!container) {
660 container = OTCKContainerName;
661 }
662 secnotice("octagon", "Received a fetch peer ID for container (%@) and context (%@)", container, context);
663 OTCuttlefishContext* cfshContext = [self contextForContainerName:container contextID:context];
664 [cfshContext rpcFetchEgoPeerID:^(NSString * _Nullable peerID,
665 NSError * _Nullable error) {
666 reply(peerID, XPCSanitizeError(error));
667 }];
668 }
669
670 - (void)fetchTrustStatus:(NSString *)container
671 context:(NSString *)context
672 configuration:(OTOperationConfiguration *)configuration
673 reply:(void (^)(CliqueStatus status,
674 NSString* _Nullable peerID,
675 NSNumber * _Nullable numberOfPeersInOctagon,
676 BOOL isExcluded,
677 NSError* _Nullable error))reply
678 {
679 if(!container) {
680 container = OTCKContainerName;
681 }
682 secnotice("octagon", "Received a trust status for container (%@) and context (%@)", container, context);
683 OTCuttlefishContext* cfshContext = [self contextForContainerName:container contextID:context];
684
685 [cfshContext rpcTrustStatus:configuration reply:^(CliqueStatus status,
686 NSString * _Nullable peerID,
687 NSDictionary<NSString *,NSNumber *> * _Nullable peerCountByModelID,
688 BOOL isExcluded,
689 BOOL isLocked,
690 NSError * _Nullable error) {
691 // Our clients don't need the whole breakout of peers, so just count for them
692 long peerCount = 0;
693 for(NSNumber* n in peerCountByModelID.allValues) {
694 peerCount += [n longValue];
695 }
696
697 reply(status, peerID, @(peerCount), isExcluded, error);
698 }];
699 }
700
701 - (void)fetchCliqueStatus:(NSString* _Nullable)container
702 context:(NSString*)contextID
703 configuration:(OTOperationConfiguration *)configuration
704 reply:(void (^)(CliqueStatus cliqueStatus, NSError* _Nullable error))reply
705 {
706 if(!container) {
707 container = OTCKContainerName;
708 }
709 if(configuration == nil) {
710 configuration = [[OTOperationConfiguration alloc] init];
711 }
712
713 __block OTCuttlefishContext* context = nil;
714 dispatch_sync(self.queue, ^{
715 NSString* key = [NSString stringWithFormat:@"%@-%@", container, contextID];
716
717 context = self.contexts[key];
718 });
719
720 if(!context) {
721 reply(-1, [NSError errorWithDomain:OctagonErrorDomain
722 code:OTErrorNoSuchContext
723 description:[NSString stringWithFormat:@"No context for (%@,%@)", container, contextID]]);
724 return;
725 }
726
727
728 [context rpcTrustStatus:configuration reply:^(CliqueStatus status,
729 NSString* egoPeerID,
730 NSDictionary<NSString *,NSNumber *> * _Nullable peerCountByModelID,
731 BOOL isExcluded,
732 BOOL isLocked,
733 NSError * _Nullable error) {
734 reply(status, error);
735 }];
736 }
737
738 - (void)status:(NSString* _Nullable)containerName
739 context:(NSString*)contextID
740 reply:(void (^)(NSDictionary* _Nullable result, NSError* _Nullable error))reply
741 {
742 if(!containerName) {
743 containerName = OTCKContainerName;
744 }
745
746 secnotice("octagon", "Received a status RPC for container (%@) and context (%@)", containerName, contextID);
747
748 __block OTCuttlefishContext* context = nil;
749 dispatch_sync(self.queue, ^{
750 NSString* key = [NSString stringWithFormat:@"%@-%@", containerName, contextID];
751
752 context = self.contexts[key];
753 });
754
755 if(!context) {
756 reply(nil, [NSError errorWithDomain:OctagonErrorDomain
757 code:OTErrorNoSuchContext
758 description:[NSString stringWithFormat:@"No context for (%@,%@)", containerName, contextID]]);
759 return;
760 }
761
762 [context rpcStatus:reply];
763 }
764
765 - (void)startOctagonStateMachine:(NSString* _Nullable)container
766 context:(NSString*)context
767 reply:(void (^)(NSError* _Nullable error))reply
768 {
769 secnotice("octagon", "Received a start-state-machine RPC for container (%@) and context (%@)", container, context);
770
771 OTCuttlefishContext* cfshContext = [self contextForContainerName:container contextID:context];
772 [cfshContext startOctagonStateMachine];
773 reply(nil);
774 }
775
776 - (void)resetAndEstablish:(NSString *)container
777 context:(NSString *)context
778 altDSID:(NSString*)altDSID
779 resetReason:(CuttlefishResetReason)resetReason
780 reply:(void (^)(NSError * _Nullable))reply
781 {
782 SFAnalyticsActivityTracker *tracker = [[self.loggerClass logger] startLogSystemMetricsForActivityNamed:OctagonActivityResetAndEstablish];
783
784 OTCuttlefishContext* cfshContext = [self contextForContainerName:container contextID:context];
785 [cfshContext startOctagonStateMachine];
786 [cfshContext rpcResetAndEstablish:resetReason reply:^(NSError* resetAndEstablishError) {
787 [tracker stopWithEvent:OctagonEventResetAndEstablish result:resetAndEstablishError];
788 reply(resetAndEstablishError);
789 }];
790 }
791
792 - (void)establish:(NSString *)container
793 context:(NSString *)context
794 altDSID:(NSString*)altDSID
795 reply:(void (^)(NSError * _Nullable))reply
796 {
797 SFAnalyticsActivityTracker *tracker = [[self.loggerClass logger] startLogSystemMetricsForActivityNamed:OctagonActivityEstablish];
798
799 OTCuttlefishContext* cfshContext = [self contextForContainerName:container contextID:context];
800 [cfshContext startOctagonStateMachine];
801 [cfshContext rpcEstablish:altDSID reply:^(NSError* establishError) {
802 [tracker stopWithEvent:OctagonEventEstablish result:establishError];
803 reply(establishError);
804 }];
805 }
806
807 - (void)leaveClique:(NSString* _Nullable)container
808 context:(NSString*)context
809 reply:(void (^)(NSError* _Nullable error))reply
810 {
811 SFAnalyticsActivityTracker *tracker = [[self.loggerClass logger] startLogSystemMetricsForActivityNamed:OctagonActivityLeaveClique];
812
813 OTCuttlefishContext* cfshContext = [self contextForContainerName:container contextID:context];
814 [cfshContext startOctagonStateMachine];
815 [cfshContext rpcLeaveClique:^(NSError* leaveError) {
816 [tracker stopWithEvent:OctagonEventLeaveClique result:leaveError];
817 reply(leaveError);
818 }];
819 }
820
821 - (void)removeFriendsInClique:(NSString* _Nullable)container
822 context:(NSString*)context
823 peerIDs:(NSArray<NSString*>*)peerIDs
824 reply:(void (^)(NSError* _Nullable error))reply
825 {
826 SFAnalyticsActivityTracker *tracker = [[self.loggerClass logger] startLogSystemMetricsForActivityNamed:OctagonActivityRemoveFriendsInClique];
827
828 OTCuttlefishContext* cfshContext = [self contextForContainerName:container contextID:context];
829 [cfshContext startOctagonStateMachine];
830 [cfshContext rpcRemoveFriendsInClique:peerIDs reply:^(NSError* removeFriendsError) {
831 [tracker stopWithEvent:OctagonEventRemoveFriendsInClique result:removeFriendsError];
832 reply(removeFriendsError);
833 }];
834 }
835
836 - (void)peerDeviceNamesByPeerID:(NSString* _Nullable)container
837 context:(NSString*)context
838 reply:(void (^)(NSDictionary<NSString*, NSString*>* _Nullable peers, NSError* _Nullable error))reply
839 {
840 OTCuttlefishContext* cfshContext = [self contextForContainerName:container contextID:context];
841 [cfshContext rpcFetchDeviceNamesByPeerID:reply];
842 }
843
844 - (void)fetchAllViableBottles:(NSString* _Nullable)container
845 context:(NSString*)context
846 reply:(void (^)(NSArray<NSString*>* _Nullable sortedBottleIDs, NSArray<NSString*>* _Nullable sortedPartialBottleIDs, NSError* _Nullable error))reply
847 {
848 SFAnalyticsActivityTracker *tracker = [[self.loggerClass logger] startLogSystemMetricsForActivityNamed:OctagonActivityFetchAllViableBottles];
849
850 OTCuttlefishContext* cfshContext = [self contextForContainerName:container contextID:context];
851 [cfshContext rpcFetchAllViableBottles:^(NSArray<NSString *> * _Nullable sortedBottleIDs,
852 NSArray<NSString *> * _Nullable sortedPartialEscrowRecordIDs,
853 NSError * _Nullable error) {
854 [tracker stopWithEvent:OctagonEventFetchAllBottles result:error];
855 reply(sortedBottleIDs, sortedPartialEscrowRecordIDs, error);
856 }];
857 }
858
859 - (void)fetchEscrowContents:(NSString* _Nullable)containerName
860 contextID:(NSString *)contextID
861 reply:(void (^)(NSData* _Nullable entropy,
862 NSString* _Nullable bottleID,
863 NSData* _Nullable signingPublicKey,
864 NSError* _Nullable error))reply
865 {
866 SFAnalyticsActivityTracker *tracker = [[self.loggerClass logger] startLogSystemMetricsForActivityNamed:OctagonActivityFetchEscrowContents];
867
868 OTCuttlefishContext* cfshContext = [self contextForContainerName:containerName contextID:contextID];
869 [cfshContext fetchEscrowContents:^(NSData *entropy,
870 NSString *bottleID,
871 NSData *signingPublicKey,
872 NSError *error) {
873 [tracker stopWithEvent:OctagonEventFetchEscrowContents result:error];
874 reply(entropy, bottleID, signingPublicKey, error);
875 }];
876 }
877
878 ////
879 // MARK: Pairing Routines as Initiator
880 ////
881 - (void)rpcPrepareIdentityAsApplicantWithConfiguration:(OTJoiningConfiguration*)config
882 reply:(void (^)(NSString * _Nullable peerID,
883 NSData * _Nullable permanentInfo,
884 NSData * _Nullable permanentInfoSig,
885 NSData * _Nullable stableInfo,
886 NSData * _Nullable stableInfoSig,
887 NSError * _Nullable error))reply
888 {
889 OTCuttlefishContext* cfshContext = [self contextForContainerName:config.containerName contextID:config.contextID];
890 [cfshContext handlePairingRestart:config];
891 [cfshContext startOctagonStateMachine];
892 [cfshContext rpcPrepareIdentityAsApplicantWithConfiguration:config
893 epoch:config.epoch
894 reply:^(NSString * _Nullable peerID,
895 NSData * _Nullable permanentInfo,
896 NSData * _Nullable permanentInfoSig,
897 NSData * _Nullable stableInfo,
898 NSData * _Nullable stableInfoSig,
899 NSError * _Nullable error) {
900 reply(peerID, permanentInfo, permanentInfoSig, stableInfo, stableInfoSig, error);
901 }];
902 }
903
904 - (void)rpcJoinWithConfiguration:(OTJoiningConfiguration*)config
905 vouchData:(NSData*)vouchData
906 vouchSig:(NSData*)vouchSig
907 reply:(void (^)(NSError * _Nullable error))reply
908 {
909 OTCuttlefishContext* cfshContext = [self contextForContainerName:config.containerName contextID:config.contextID];
910 [cfshContext handlePairingRestart:config];
911 [cfshContext startOctagonStateMachine];
912 [cfshContext rpcJoin:vouchData vouchSig:vouchSig reply:^(NSError * _Nullable error) {
913 reply(error);
914 }];
915 }
916
917 ////
918 // MARK: Pairing Routines as Acceptor
919 ////
920
921 - (void)rpcEpochWithConfiguration:(OTJoiningConfiguration*)config
922 reply:(void (^)(uint64_t epoch,
923 NSError * _Nullable error))reply
924 {
925 OTCuttlefishContext* acceptorCfshContext = [self contextForContainerName:config.containerName contextID:config.contextID];
926 [acceptorCfshContext startOctagonStateMachine];
927
928 // Let's assume that the new device's machine ID has made it to the IDMS list by now, and let's refresh our idea of that list
929 [acceptorCfshContext requestTrustedDeviceListRefresh];
930
931 OTClientStateMachine *clientStateMachine = [self clientStateMachineForContainerName:config.containerName contextID:config.contextID clientName:config.pairingUUID];
932
933 [clientStateMachine startOctagonStateMachine];
934
935 [clientStateMachine rpcEpoch:acceptorCfshContext reply:^(uint64_t epoch, NSError * _Nullable error) {
936 reply(epoch, error);
937 }];
938 }
939
940 - (void)rpcVoucherWithConfiguration:(OTJoiningConfiguration*)config
941 peerID:(NSString*)peerID
942 permanentInfo:(NSData *)permanentInfo
943 permanentInfoSig:(NSData *)permanentInfoSig
944 stableInfo:(NSData *)stableInfo
945 stableInfoSig:(NSData *)stableInfoSig
946 reply:(void (^)(NSData* voucher, NSData* voucherSig, NSError * _Nullable error))reply
947 {
948 OTCuttlefishContext* acceptorCfshContext = [self contextForContainerName:config.containerName contextID:config.contextID];
949 [acceptorCfshContext startOctagonStateMachine];
950
951 // Let's assume that the new device's machine ID has made it to the IDMS list by now, and let's refresh our idea of that list
952 [acceptorCfshContext requestTrustedDeviceListRefresh];
953 OTClientStateMachine *clientStateMachine = [self clientStateMachineForContainerName:config.containerName contextID:config.contextID clientName:config.pairingUUID];
954
955 [clientStateMachine rpcVoucher:acceptorCfshContext peerID:peerID permanentInfo:permanentInfo permanentInfoSig:permanentInfoSig stableInfo:stableInfo stableInfoSig:stableInfoSig reply:^(NSData *voucher, NSData *voucherSig, NSError *error) {
956 reply(voucher, voucherSig, error);
957 }];
958 }
959
960 - (void)restore:(NSString * _Nullable)containerName
961 contextID:(nonnull NSString *)contextID
962 bottleSalt:(nonnull NSString *)bottleSalt
963 entropy:(nonnull NSData *)entropy
964 bottleID:(nonnull NSString *)bottleID
965 reply:(nonnull void (^)(NSError * _Nullable))reply {
966 SFAnalyticsActivityTracker *tracker = [[self.loggerClass logger] startLogSystemMetricsForActivityNamed:OctagonActivityBottledPeerRestore];
967
968 secnotice("octagon", "restore via bottle invoked for container: %@, context: %@", containerName, contextID);
969
970 OTCuttlefishContext* cfshContext = [self contextForContainerName:containerName contextID:contextID];
971
972 [cfshContext startOctagonStateMachine];
973
974 [cfshContext joinWithBottle:bottleID entropy:entropy bottleSalt:bottleSalt reply:^(NSError *error) {
975 [tracker stopWithEvent:OctagonEventBottledPeerRestore result:error];
976 reply(error);
977 }];
978 }
979
980 ////
981 // MARK: Ghost busting using ramp records
982 ////
983
984 -(BOOL) ghostbustByMidEnabled {
985 [self ensureRampsInitialized];
986 return [self.gbmidRamp checkRampStateWithError:nil];
987 }
988
989 -(BOOL) ghostbustBySerialEnabled {
990 [self ensureRampsInitialized];
991 return [self.gbserialRamp checkRampStateWithError:nil];
992 }
993
994 -(BOOL) ghostbustByAgeEnabled {
995 [self ensureRampsInitialized];
996 return [self.gbAgeRamp checkRampStateWithError:nil];
997 }
998
999 ////
1000 // MARK: Analytics
1001 ////
1002
1003 - (void)setupAnalytics
1004 {
1005 WEAKIFY(self);
1006
1007 [[self.loggerClass logger] AddMultiSamplerForName:@"Octagon-healthSummary"
1008 withTimeInterval:SFAnalyticsSamplerIntervalOncePerReport
1009 block:^NSDictionary<NSString *,NSNumber *> *{
1010 STRONGIFY(self);
1011
1012 // We actually only care about the default context for the default container
1013 OTCuttlefishContext* cuttlefishContext = [self contextForContainerName:OTCKContainerName contextID:OTDefaultContext];
1014
1015 secnotice("octagon-analytics", "Reporting analytics for container: %@, context: %@", OTCKContainerName, OTDefaultContext);
1016
1017 NSMutableDictionary* values = [NSMutableDictionary dictionary];
1018
1019 NSError* error = nil;
1020 SOSCCStatus sosStatus = [self.sosAdapter circleStatus:&error];
1021 if(error) {
1022 secnotice("octagon-analytics", "Error fetching SOS status: %@", error);
1023 }
1024 values[OctagonAnalyticsSOSStatus] = @((int)sosStatus);
1025 NSDate* dateOfLastPPJ = [[CKKSAnalytics logger] datePropertyForKey:OctagonEventUpgradePreflightPreapprovedJoin];
1026 values[OctagonAnalyticsDateOfLastPreflightPreapprovedJoin] = @([CKKSAnalytics fuzzyDaysSinceDate:dateOfLastPPJ]);
1027
1028 values[OctagonAnalyticsStateMachineState] = OctagonStateMap()[cuttlefishContext.stateMachine.currentState];
1029
1030 NSError* metadataError = nil;
1031 OTAccountMetadataClassC* metadata = [cuttlefishContext.accountMetadataStore loadOrCreateAccountMetadata:&metadataError];
1032 if(!metadata || metadataError) {
1033 secnotice("octagon-analytics", "Error fetching Octagon metadata: %@", metadataError);
1034 }
1035 values[OctagonAnalyticIcloudAccountState] = metadata ? @(metadata.icloudAccountState) : nil;
1036 values[OctagonAnalyticCDPBitStatus] = metadata? @(metadata.cdpState) : nil;
1037 values[OctagonAnalyticsTrustState] = metadata ? @(metadata.trustState) : nil;
1038
1039 TPSyncingPolicy* syncingPolicy = [metadata getTPSyncingPolicy];
1040 values[OctagonAnalyticsUserControllableViewsSyncing] = syncingPolicy ? @(syncingPolicy.syncUserControllableViewsAsBoolean) : nil;
1041
1042 NSDate* healthCheck = [cuttlefishContext currentMemoizedLastHealthCheck];
1043 values[OctagonAnalyticsLastHealthCheck] = @([CKKSAnalytics fuzzyDaysSinceDate:healthCheck]);
1044
1045 NSDate* dateOfLastKSR = [[CKKSAnalytics logger] datePropertyForKey: OctagonAnalyticsLastKeystateReady];
1046 values[OctagonAnalyticsLastKeystateReady] = @([CKKSAnalytics fuzzyDaysSinceDate:dateOfLastKSR]);
1047
1048 if(metadata && metadata.icloudAccountState == OTAccountMetadataClassC_AccountState_ACCOUNT_AVAILABLE) {
1049 values[OctagonAnalyticsAttemptedJoin] = @(metadata.attemptedJoin);
1050
1051 NSError* machineIDError = nil;
1052 NSString* machineID = [cuttlefishContext.authKitAdapter machineID:&machineIDError];
1053 if(machineIDError) {
1054 secnotice("octagon-analytics", "Error fetching machine ID: %@", metadataError);
1055 }
1056
1057 values[OctagonAnalyticsHaveMachineID] = @(machineID != nil);
1058
1059 if(machineID) {
1060 NSError* midOnListError = nil;
1061 BOOL midOnList = [cuttlefishContext machineIDOnMemoizedList:machineID error:&midOnListError];
1062
1063 if(midOnListError) {
1064 secnotice("octagon-analytics", "Error fetching 'mid on list': %@", midOnListError);
1065 } else {
1066 values[OctagonAnalyticsMIDOnMemoizedList] = @(midOnList);
1067 }
1068
1069 NSError* peersWithMIDError = nil;
1070 NSNumber* peersWithMID = [cuttlefishContext numberOfPeersInModelWithMachineID:machineID error:&peersWithMIDError];
1071 if(peersWithMID && peersWithMIDError == nil) {
1072 values[OctagonAnalyticsPeersWithMID] = peersWithMID;
1073 } else {
1074 secnotice("octagon-analytics", "Error fetching how many peers have our MID: %@", midOnListError);
1075 }
1076 }
1077 }
1078
1079 // Track CFU usage and success/failure metrics
1080 // 1. Users in a good state will have no outstanding CFU, and will not have done a CFU
1081 // 2. Users in a bad state who have not repsonded to the CFU (repaired) will have a pending CFU.
1082 // 3. Users in a bad state who have acted on the CFU will have no pending CFU, but will have CFU failures.
1083 //
1084 // We also record the time window between the last followup completion and invocation.
1085 NSDate* dateOfLastFollowup = [[CKKSAnalytics logger] datePropertyForKey: OctagonAnalyticsLastCoreFollowup];
1086 values[OctagonAnalyticsLastCoreFollowup] = @([CKKSAnalytics fuzzyDaysSinceDate:dateOfLastFollowup]);
1087 // We used to report this, but it was never set
1088 //values[OctagonAnalyticsCoreFollowupStatus] = @(followupHandler.followupStatus);
1089
1090 for (NSString *type in [self cdpContextTypes]) {
1091 NSString *metricName = [NSString stringWithFormat:@"%@%@", OctagonAnalyticsCDPStateRun, type];
1092 NSString *countName = [NSString stringWithFormat:@"%@%@Tries", OctagonAnalyticsCDPStateRun, type];
1093
1094 NSDate *lastFailure = [[CKKSAnalytics logger] datePropertyForKey:metricName];
1095 if (lastFailure) {
1096 values[metricName] = @([CKKSAnalytics fuzzyDaysSinceDate:lastFailure]);
1097 values[countName] = [[CKKSAnalytics logger] numberPropertyForKey:countName];
1098 }
1099 }
1100
1101 // SecEscrowRequest
1102 id<SecEscrowRequestable> request = [self.escrowRequestClass request:&error];
1103 if (request) {
1104 values[OctagonAnalyticsPrerecordPending] = @([request pendingEscrowUpload:&error]);
1105 if (error) {
1106 secnotice("octagon-analytics", "Error fetching pendingEscrowUpload status: %@", error);
1107 }
1108 } else {
1109 secnotice("octagon-analytics", "Error fetching escrowRequestClass: %@", error);
1110 }
1111
1112 {
1113 ACAccountStore *store = [[ACAccountStore alloc] init];
1114 ACAccount* primaryAccount = [store aa_primaryAppleAccount];
1115 if(primaryAccount) {
1116 values[OctagonAnalyticsKVSProvisioned] = @([primaryAccount isProvisionedForDataclass:ACAccountDataclassKeyValue]);
1117 values[OctagonAnalyticsKVSEnabled] = @([primaryAccount isEnabledForDataclass:ACAccountDataclassKeyValue]);
1118 values[OctagonAnalyticsKeychainSyncProvisioned] = @([primaryAccount isProvisionedForDataclass:ACAccountDataclassKeychainSync]);
1119 values[OctagonAnalyticsKeychainSyncEnabled] = @([primaryAccount isEnabledForDataclass:ACAccountDataclassKeychainSync]);
1120 values[OctagonAnalyticsCloudKitProvisioned] = @([primaryAccount isProvisionedForDataclass:ACAccountDataclassCKDatabaseService]);
1121 values[OctagonAnalyticsCloudKitEnabled] = @([primaryAccount isEnabledForDataclass:ACAccountDataclassCKDatabaseService]);
1122
1123 NSString *altDSID = primaryAccount.aa_altDSID;
1124 if(altDSID) {
1125 values[OctagonAnalyticsSecureBackupTermsAccepted] = @([getSecureBackupClass() getAcceptedTermsForAltDSID:altDSID withError:nil] != nil);
1126 }
1127 }
1128 }
1129
1130 return values;
1131 }];
1132
1133 [[self.loggerClass logger] AddMultiSamplerForName:@"CFU-healthSummary"
1134 withTimeInterval:SFAnalyticsSamplerIntervalOncePerReport
1135 block:^NSDictionary<NSString *,NSNumber *> *{
1136 OTCuttlefishContext* cuttlefishContext = [self contextForContainerName:OTCKContainerName contextID:OTDefaultContext];
1137 return [cuttlefishContext.followupHandler sfaStatus];
1138 }];
1139 }
1140
1141 - (NSArray<OTCliqueCDPContextType> *)cdpContextTypes
1142 {
1143 static NSArray<OTCliqueCDPContextType> *contextTypes = nil;
1144 static dispatch_once_t onceToken;
1145 dispatch_once(&onceToken, ^{
1146 contextTypes = @[OTCliqueCDPContextTypeNone,
1147 OTCliqueCDPContextTypeSignIn,
1148 OTCliqueCDPContextTypeRepair,
1149 OTCliqueCDPContextTypeFinishPasscodeChange,
1150 OTCliqueCDPContextTypeRecoveryKeyGenerate,
1151 OTCliqueCDPContextTypeRecoveryKeyNew,
1152 OTCliqueCDPContextTypeUpdatePasscode,
1153 ];
1154 });
1155 return contextTypes;
1156 }
1157
1158 ////
1159 // MARK: Recovery Key
1160 ////
1161
1162 - (void) createRecoveryKey:(NSString* _Nullable)containerName
1163 contextID:(NSString *)contextID
1164 recoveryKey:(NSString *)recoveryKey
1165 reply:(void (^)( NSError *error))reply
1166 {
1167 secnotice("octagon", "Setting recovery key for container: %@, context: %@", containerName, contextID);
1168
1169 SFAnalyticsActivityTracker *tracker = [[self.loggerClass logger] startLogSystemMetricsForActivityNamed:OctagonActivitySetRecoveryKey];
1170
1171 if (!self.sosEnabledForPlatform) {
1172 secnotice("octagon-recovery", "Device does not participate in SOS; cannot enroll recovery key in Octagon");
1173 NSError* notFullPeerError = [NSError errorWithDomain:OctagonErrorDomain code:OTErrorLimitedPeer userInfo:@{NSLocalizedDescriptionKey : @"Device is considered a limited peer, cannot enroll recovery key in Octagon"}];
1174 [tracker stopWithEvent:OctagonEventRecoveryKey result:notFullPeerError];
1175 reply(notFullPeerError);
1176 return;
1177 }
1178
1179 CFErrorRef validateError = NULL;
1180 bool res = SecPasswordValidatePasswordFormat(kSecPasswordTypeiCloudRecoveryKey, (__bridge CFStringRef)recoveryKey, &validateError);
1181 if (!res) {
1182 NSError *validateErrorWrapper = nil;
1183 NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
1184 userInfo[NSLocalizedDescriptionKey] = @"malformed recovery key";
1185 if(validateError) {
1186 userInfo[NSUnderlyingErrorKey] = CFBridgingRelease(validateError);
1187 }
1188
1189 validateErrorWrapper = [NSError errorWithDomain:OctagonErrorDomain code:OTErrorRecoveryKeyMalformed userInfo:userInfo];
1190
1191 secerror("recovery failed validation with error:%@", validateError);
1192
1193 [tracker stopWithEvent:OctagonEventSetRecoveryKeyValidationFailed result:validateErrorWrapper];
1194 reply(validateErrorWrapper);
1195 return;
1196 }
1197
1198 OTCuttlefishContext* cfshContext = [self contextForContainerName:containerName contextID:contextID];
1199
1200 [cfshContext startOctagonStateMachine];
1201
1202 [cfshContext rpcSetRecoveryKey:recoveryKey reply:^(NSError * _Nullable error) {
1203 [tracker stopWithEvent:OctagonEventRecoveryKey result:error];
1204 reply(error);
1205 }];
1206 }
1207
1208 - (void) joinWithRecoveryKey:(NSString* _Nullable)containerName
1209 contextID:(NSString *)contextID
1210 recoveryKey:(NSString*)recoveryKey
1211 reply:(void (^)(NSError * _Nullable))reply
1212 {
1213 secnotice("octagon", "join with recovery key invoked for container: %@, context: %@", containerName, contextID);
1214
1215 SFAnalyticsActivityTracker *tracker = [[self.loggerClass logger] startLogSystemMetricsForActivityNamed:OctagonActivityJoinWithRecoveryKey];
1216
1217 CFErrorRef validateError = NULL;
1218 bool res = SecPasswordValidatePasswordFormat(kSecPasswordTypeiCloudRecoveryKey, (__bridge CFStringRef)recoveryKey, &validateError);
1219 if (!res) {
1220 NSError *validateErrorWrapper = nil;
1221 NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
1222 userInfo[NSLocalizedDescriptionKey] = @"malformed recovery key";
1223 if(validateError) {
1224 userInfo[NSUnderlyingErrorKey] = CFBridgingRelease(validateError);
1225 }
1226
1227 validateErrorWrapper = [NSError errorWithDomain:OctagonErrorDomain code:OTErrorRecoveryKeyMalformed userInfo:userInfo];
1228
1229 secerror("recovery failed validation with error:%@", validateError);
1230
1231 [tracker stopWithEvent:OctagonEventJoinRecoveryKeyValidationFailed result:validateErrorWrapper];
1232 reply(validateErrorWrapper);
1233 return;
1234 }
1235
1236 OTCuttlefishContext* cfshContext = [self contextForContainerName:containerName contextID:contextID];
1237
1238 [cfshContext startOctagonStateMachine];
1239
1240 [cfshContext joinWithRecoveryKey:recoveryKey reply:^(NSError *error) {
1241 if ((error.code == TrustedPeersHelperErrorCodeNotEnrolled || error.code == TrustedPeersHelperErrorCodeUntrustedRecoveryKeys)
1242 && [error.domain isEqualToString:TrustedPeersHelperErrorDomain]){
1243 // If we hit either of these errors, and the local device thinks it should be able to set a recovery key,
1244 // let's reset and establish octagon then enroll this recovery key in the new circle
1245
1246 if (!self.sosEnabledForPlatform) {
1247 secerror("octagon: recovery key is not enrolled in octagon, and current device can't set recovery keys");
1248 [tracker stopWithEvent:OctagonEventJoinRecoveryKeyCircleResetFailed result:error];
1249 reply(error);
1250 return;
1251 }
1252
1253 secerror("octagon, recovery key is not enrolled in octagon, resetting octagon circle");
1254 [[self.loggerClass logger] logResultForEvent:OctagonEventJoinRecoveryKeyCircleReset hardFailure:NO result:error];
1255
1256 [cfshContext rpcResetAndEstablish:CuttlefishResetReasonRecoveryKey reply:^(NSError *resetError) {
1257 if (resetError) {
1258 secerror("octagon, failed to reset octagon");
1259 [tracker stopWithEvent:OctagonEventJoinRecoveryKeyCircleResetFailed result:resetError];
1260 reply(resetError);
1261 return;
1262 } else {
1263 // Now enroll the recovery key
1264 secnotice("octagon", "attempting enrolling recovery key");
1265 [self createRecoveryKey:containerName contextID:contextID recoveryKey:recoveryKey reply:^(NSError *enrollError) {
1266 if(enrollError){
1267 secerror("octagon, failed to enroll new recovery key: %@", enrollError);
1268 [tracker stopWithEvent:OctagonEventJoinRecoveryKeyEnrollFailed result:enrollError];
1269 reply(enrollError);
1270 return;
1271 }else{
1272 secnotice("octagon", "successfully enrolled recovery key");
1273 [tracker stopWithEvent:OctagonEventRecoveryKey result:nil];
1274 reply (nil);
1275 return;
1276 }
1277 }];
1278 }
1279 }];
1280 } else {
1281 secerror("octagon, join with recovery key failed: %d", (int)[error code]);
1282 [tracker stopWithEvent:OctagonEventJoinRecoveryKeyFailed result:error];
1283 reply(error);
1284 }
1285 }];
1286 }
1287
1288 - (void)xpc24HrNotification
1289 {
1290 secnotice("octagon-health", "Received 24hr xpc notification");
1291
1292 [self healthCheck:OTCKContainerName context:OTDefaultContext skipRateLimitingCheck:NO reply:^(NSError * _Nullable error) {
1293 if(error) {
1294 secerror("octagon: error attempting to check octagon health: %@", error);
1295 } else {
1296 secnotice("octagon", "health check success");
1297 }
1298 }];
1299 }
1300
1301 - (void)healthCheck:(NSString *)container context:(NSString *)context skipRateLimitingCheck:(BOOL)skipRateLimitingCheck reply:(void (^)(NSError *_Nullable error))reply
1302 {
1303 OTCuttlefishContext* cfshContext = [self contextForContainerName:container contextID:context];
1304
1305 secnotice("octagon", "notifying container of change");
1306
1307 [cfshContext notifyContainerChange:nil];
1308
1309 [cfshContext checkOctagonHealth:skipRateLimitingCheck reply:^(NSError *error) {
1310 if(error) {
1311 reply(error);
1312 } else {
1313 reply(nil);
1314 }
1315 }];
1316 }
1317
1318 - (void)setSOSEnabledForPlatformFlag:(bool) value
1319 {
1320 self.sosEnabledForPlatform = value;
1321 }
1322
1323 - (void)allContextsHalt
1324 {
1325 for(OTCuttlefishContext* context in self.contexts.allValues) {
1326 [context.stateMachine haltOperation];
1327
1328 // Also, clear the viewManager strong pointer
1329 [context clearCKKSViewManager];
1330 }
1331 }
1332
1333 - (void)allContextsDisablePendingFlags
1334 {
1335 for(OTCuttlefishContext* context in self.contexts.allValues) {
1336 [context.stateMachine disablePendingFlags];
1337 }
1338 }
1339
1340 - (bool)allContextsPause:(uint64_t)within
1341 {
1342 for(OTCuttlefishContext* context in self.contexts.allValues) {
1343 if(context.stateMachine.currentState != OctagonStateMachineNotStarted) {
1344 if([context.stateMachine.paused wait:within] != 0) {
1345 return false;
1346 }
1347 }
1348 }
1349 return true;
1350 }
1351
1352 - (void)waitForOctagonUpgrade:(NSString* _Nullable)containerName
1353 context:(NSString*)context
1354 reply:(void (^)(NSError* error))reply
1355
1356 {
1357 secnotice("octagon-sos", "Attempting wait for octagon upgrade");
1358 OTCuttlefishContext* cfshContext = [self contextForContainerName:containerName contextID:context];
1359
1360 [cfshContext startOctagonStateMachine];
1361
1362 [cfshContext waitForOctagonUpgrade:^(NSError * _Nonnull error) {
1363 reply(error);
1364 }];
1365 }
1366
1367 - (OTFollowupContextType)cliqueCDPTypeToFollowupContextType:(OTCliqueCDPContextType)type
1368 {
1369 if ([type isEqualToString:OTCliqueCDPContextTypeNone]) {
1370 return OTFollowupContextTypeNone;
1371 } else if ([type isEqualToString:OTCliqueCDPContextTypeSignIn]) {
1372 return OTFollowupContextTypeNone;
1373 } else if ([type isEqualToString:OTCliqueCDPContextTypeRepair]) {
1374 return OTFollowupContextTypeStateRepair;
1375 } else if ([type isEqualToString:OTCliqueCDPContextTypeFinishPasscodeChange]) {
1376 return OTFollowupContextTypeOfflinePasscodeChange;
1377 } else if ([type isEqualToString:OTCliqueCDPContextTypeRecoveryKeyGenerate]) {
1378 return OTFollowupContextTypeRecoveryKeyRepair;
1379 } else if ([type isEqualToString:OTCliqueCDPContextTypeRecoveryKeyNew]) {
1380 return OTFollowupContextTypeRecoveryKeyRepair;
1381 } else if ([type isEqualToString:OTCliqueCDPContextTypeUpdatePasscode]) {
1382 return OTFollowupContextTypeNone;
1383 } else {
1384 return OTFollowupContextTypeNone;
1385 }
1386 }
1387
1388 - (void)postCDPFollowupResult:(BOOL)success
1389 type:(OTCliqueCDPContextType)type
1390 error:(NSError * _Nullable)error
1391 containerName:(NSString* _Nullable)containerName
1392 contextName:(NSString *)contextName
1393 reply:(void (^)(NSError *error))reply
1394 {
1395 NSString* metricName = [NSString stringWithFormat:@"%@%@", OctagonAnalyticsCDPStateRun, type];
1396 NSString* countName = [NSString stringWithFormat:@"%@%@Tries", OctagonAnalyticsCDPStateRun, type];
1397
1398 [[CKKSAnalytics logger] logResultForEvent:metricName
1399 hardFailure:NO
1400 result:error];
1401 if (error) {
1402 [[CKKSAnalytics logger] setDateProperty:[NSDate date] forKey:metricName];
1403 [[CKKSAnalytics logger] incrementIntegerPropertyForKey:countName];
1404 } else {
1405 [[CKKSAnalytics logger] setDateProperty:NULL forKey:metricName];
1406 [[CKKSAnalytics logger] setNumberProperty:NULL forKey:countName];
1407 }
1408
1409 // Always return without error
1410 reply(nil);
1411 }
1412
1413 - (void)tapToRadar:(NSString *)action
1414 description:(NSString *)description
1415 radar:(NSString *)radar
1416 reply:(void (^)(NSError * _Nullable))reply
1417 {
1418 SecTapToRadar *ttr = [[SecTapToRadar alloc] initTapToRadar:action description:description radar:radar];
1419 [ttr trigger];
1420 reply(NULL);
1421 }
1422
1423 - (void)refetchCKKSPolicy:(NSString * _Nullable)containerName
1424 contextID:(nonnull NSString *)contextID
1425 reply:(nonnull void (^)(NSError * _Nullable))reply {
1426 secnotice("octagon-ckks", "refetch-ckks-policy");
1427
1428 if(!containerName) {
1429 containerName = OTCKContainerName;
1430 }
1431
1432 OTCuttlefishContext* cfshContext = [self contextForContainerName:containerName contextID:contextID];
1433
1434 if(!cfshContext) {
1435 reply([NSError errorWithDomain:OctagonErrorDomain
1436 code:OTErrorNoSuchContext
1437 description:[NSString stringWithFormat:@"No context for (%@,%@)", containerName, contextID]]);
1438 return;
1439 }
1440
1441 [cfshContext rpcRefetchCKKSPolicy:^(NSError* error) {
1442 secnotice("octagon-ckks", "refetch-ckks-policy result: %@", error ?: @"no error");
1443 reply(error);
1444 }];
1445 }
1446
1447 - (void)getCDPStatus:(NSString * _Nullable)containerName
1448 contextID:(nonnull NSString *)contextID
1449 reply:(nonnull void (^)(OTCDPStatus, NSError * _Nullable))reply {
1450 secnotice("octagon-cdp", "get-cdp-status");
1451
1452 if(!containerName) {
1453 containerName = OTCKContainerName;
1454 }
1455
1456 OTCuttlefishContext* cfshContext = [self contextForContainerName:containerName contextID:contextID];
1457
1458 if(!cfshContext) {
1459 reply(OTCDPStatusUnknown, [NSError errorWithDomain:OctagonErrorDomain
1460 code:OTErrorNoSuchContext
1461 description:[NSString stringWithFormat:@"No context for (%@,%@)", containerName, contextID]]);
1462 return;
1463 }
1464
1465 NSError* error = nil;
1466 OTCDPStatus status = [cfshContext getCDPStatus:&error];
1467
1468 reply(status, error);
1469 }
1470
1471
1472 - (void)setCDPEnabled:(NSString * _Nullable)containerName
1473 contextID:(nonnull NSString *)contextID
1474 reply:(nonnull void (^)(NSError * _Nullable))reply {
1475 secnotice("octagon-cdp", "set-cdp-enabled");
1476 if(!containerName) {
1477 containerName = OTCKContainerName;
1478 }
1479
1480 OTCuttlefishContext* cfshContext = [self contextForContainerName:containerName contextID:contextID];
1481
1482 if(!cfshContext) {
1483 reply([NSError errorWithDomain:OctagonErrorDomain
1484 code:OTErrorNoSuchContext
1485 description:[NSString stringWithFormat:@"No context for (%@,%@)", containerName, contextID]]);
1486 return;
1487 }
1488
1489 NSError* localError = nil;
1490 [cfshContext setCDPEnabled:&localError];
1491
1492 reply(localError);
1493 }
1494
1495 - (void)fetchEscrowRecords:(NSString * _Nullable)containerName
1496 contextID:(NSString*)contextID
1497 forceFetch:(BOOL)forceFetch
1498 reply:(nonnull void (^)(NSArray<NSData *> * _Nullable records,
1499 NSError * _Nullable error))reply {
1500
1501 secnotice("octagon-fetch-escrow-records", "fetching records");
1502 if(!containerName) {
1503 containerName = OTCKContainerName;
1504 }
1505
1506 OTCuttlefishContext* cfshContext = [self contextForContainerName:containerName contextID:contextID];
1507
1508 if(!cfshContext) {
1509 reply(nil, [NSError errorWithDomain:OctagonErrorDomain
1510 code:OTErrorNoSuchContext
1511 description:[NSString stringWithFormat:@"No context for (%@,%@)", containerName, contextID]]);
1512 return;
1513 }
1514
1515 [cfshContext rpcFetchAllViableEscrowRecords:forceFetch reply:^(NSArray<NSData *> * _Nullable records, NSError * _Nullable error) {
1516 if(error) {
1517 secerror("octagon-fetch-escrow-records: error fetching records: %@", error);
1518 reply(nil, error);
1519 return;
1520 }
1521 secnotice("octagon-fetch-escrow-records", "successfully fetched records");
1522 reply(records, nil);
1523 }];
1524 }
1525
1526 - (void)invalidateEscrowCache:(NSString * _Nullable)containerName
1527 contextID:(NSString*)contextID
1528 reply:(nonnull void (^)(NSError * _Nullable error))reply {
1529
1530 secnotice("octagon-remove-escrow-cache", "beginning removing escrow cache!");
1531 if(!containerName) {
1532 containerName = OTCKContainerName;
1533 }
1534
1535 OTCuttlefishContext* cfshContext = [self contextForContainerName:containerName contextID:contextID];
1536
1537 if(!cfshContext) {
1538 reply([NSError errorWithDomain:OctagonErrorDomain
1539 code:OTErrorNoSuchContext
1540 description:[NSString stringWithFormat:@"No context for (%@,%@)", containerName, contextID]]);
1541 return;
1542 }
1543
1544 [cfshContext rpcInvalidateEscrowCache:^(NSError * _Nullable invalidateError) {
1545 if(invalidateError) {
1546 secerror("octagon-remove-escrow-cache: error invalidating escrow cache: %@", invalidateError);
1547 reply(invalidateError);
1548 return;
1549 }
1550 secnotice("octagon-remove-escrow-caches", "successfully invalidated escrow cache");
1551 reply(nil);
1552 }];
1553 }
1554
1555 - (void)setUserControllableViewsSyncStatus:(NSString* _Nullable)containerName
1556 contextID:(NSString*)contextID
1557 enabled:(BOOL)enabled
1558 reply:(void (^)(BOOL nowSyncing, NSError* _Nullable error))reply
1559 {
1560 OTCuttlefishContext* cfshContext = [self contextForContainerName:containerName contextID:contextID];
1561
1562 if(!cfshContext) {
1563 reply(NO, [NSError errorWithDomain:OctagonErrorDomain
1564 code:OTErrorNoSuchContext
1565 description:[NSString stringWithFormat:@"No context for (%@,%@)", containerName, contextID]]);
1566 return;
1567 }
1568
1569 [cfshContext rpcSetUserControllableViewsSyncingStatus:enabled reply:^(BOOL areSyncing, NSError * _Nullable error) {
1570 if(error) {
1571 secerror("octagon-user-controllable-views: error setting status: %@", error);
1572 reply(NO, error);
1573 return;
1574 }
1575
1576 secnotice("octagon-user-controllable-views", "successfully set status to: %@", areSyncing ? @"enabled" : @"paused");
1577 reply(areSyncing, nil);
1578 }];
1579 }
1580
1581 - (void)fetchUserControllableViewsSyncStatus:(NSString* _Nullable)containerName
1582 contextID:(NSString*)contextID
1583 reply:(void (^)(BOOL nowSyncing, NSError* _Nullable error))reply
1584 {
1585 OTCuttlefishContext* cfshContext = [self contextForContainerName:containerName contextID:contextID];
1586
1587 if(!cfshContext) {
1588 reply(NO, [NSError errorWithDomain:OctagonErrorDomain
1589 code:OTErrorNoSuchContext
1590 description:[NSString stringWithFormat:@"No context for (%@,%@)", containerName, contextID]]);
1591 return;
1592 }
1593
1594 [cfshContext rpcFetchUserControllableViewsSyncingStatus:^(BOOL areSyncing, NSError * _Nullable error) {
1595 if(error) {
1596 secerror("octagon-user-controllable-views: error fetching status: %@", error);
1597 reply(NO, error);
1598 return;
1599 }
1600
1601 secerror("octagon-user-controllable-views: succesfully fetched status as: %@", areSyncing ? @"enabled" : @"paused");
1602 reply(areSyncing, nil);
1603 }];
1604 }
1605
1606 @end
1607
1608 #endif