2 * Copyright (c) 2016 Apple Inc. All Rights Reserved.
4 * @APPLE_LICENSE_HEADER_START@
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
21 * @APPLE_LICENSE_HEADER_END@
25 #include <Security/SecEntitlements.h>
26 #import <Foundation/NSXPCConnection.h>
27 #import <Foundation/NSXPCConnection_Private.h>
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"
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"
54 #import <CloudKit/CloudKit.h>
55 #import <CloudKit/CloudKit_Private.h>
57 #import <SecurityFoundation/SFKey.h>
58 #import <SecurityFoundation/SFKey_Private.h>
59 #import "SecPasswordGenerate.h"
61 #import "keychain/categories/NSError+UsefulConstructors.h"
62 #include <CloudKit/CloudKit_Private.h>
63 #import <KeychainCircle/PairingChannel.h>
65 #import "keychain/escrowrequest/Framework/SecEscrowRequest.h"
66 #import "keychain/escrowrequest/EscrowRequestServer.h"
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
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>
79 #import <CoreCDP/CDPFollowUpController.h>
81 #import <SoftLinking/SoftLinking.h>
82 #import <CloudServices/SecureBackup.h>
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
91 #import "utilities/SecTapToRadar.h"
93 SOFT_LINK_OPTIONAL_FRAMEWORK(PrivateFrameworks, CloudServices);
94 SOFT_LINK_CLASS(CloudServices, SecureBackup);
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)
106 @interface OTManager (lockstateTracker) <CKKSLockStateNotification>
110 @interface OTManager () <NSXPCListenerDelegate>
111 @property NSXPCListener *listener;
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;
120 @property NSMutableDictionary<NSString*, OTCuttlefishContext*>* contexts;
121 @property NSMutableDictionary<NSString*, OTClientStateMachine*>* clients;
122 @property dispatch_queue_t queue;
124 @property id<NSXPCProxyCreating> cuttlefishXPCConnection;
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;
137 @implementation OTManager
138 @synthesize cuttlefishXPCConnection = _cuttlefishXPCConnection;
139 @synthesize sosAdapter = _sosAdapter;
140 @synthesize authKitAdapter = _authKitAdapter;
141 @synthesize deviceInformationAdapter = _deviceInformationAdapter;
145 // Under Octagon, the sos adapter is not considered essential.
146 id<OTSOSAdapter> sosAdapter = (OctagonPlatformSupportsSOS() ?
147 [[OTSOSActualAdapter alloc] initAsEssential:NO] :
148 [[OTSOSMissingAdapter alloc] init]);
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]];
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
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;
182 _cloudKitContainer = [CKKSViewManager makeCKContainer:SecCKKSContainerName usePCS:SecCKKSContainerUsePCS];
183 _accountStateTracker = [[CKKSAccountStateTracker alloc] init:_cloudKitContainer
184 nsnotificationCenterClass:cloudKitClassDependencies.nsnotificationCenterClass];
186 self.contexts = [NSMutableDictionary dictionary];
187 self.clients = [NSMutableDictionary dictionary];
189 self.queue = dispatch_queue_create("otmanager", DISPATCH_QUEUE_SERIAL);
191 _apsConnectionClass = apsConnectionClass;
192 _escrowRequestClass = escrowRequestClass;
196 _viewManager = [[CKKSViewManager alloc] initWithContainer:_cloudKitContainer
197 sosAdapter:sosAdapter
198 accountStateTracker:_accountStateTracker
199 lockStateTracker:lockStateTracker
200 cloudKitClassDependencies:cloudKitClassDependencies];
202 // The default CuttlefishContext always exists:
203 (void) [self contextForContainerName:OTCKContainerName contextID:OTDefaultContext];
205 secnotice("octagon", "otmanager init");
210 - (instancetype)initWithSOSAdapter:(id<OTSOSAdapter>)sosAdapter
211 lockStateTracker:(CKKSLockStateTracker*)lockStateTracker
212 cloudKitClassDependencies:(CKKSCloudKitClassDependencies*)cloudKitClassDependencies
214 if((self = [super init])) {
215 _sosAdapter = sosAdapter;
216 _lockStateTracker = lockStateTracker;
218 _cloudKitContainer = [CKKSViewManager makeCKContainer:SecCKKSContainerName usePCS:SecCKKSContainerUsePCS];
219 _accountStateTracker = [[CKKSAccountStateTracker alloc] init:_cloudKitContainer
220 nsnotificationCenterClass:cloudKitClassDependencies.nsnotificationCenterClass];
222 _viewManager = [[CKKSViewManager alloc] initWithContainer:_cloudKitContainer
223 sosAdapter:sosAdapter
224 accountStateTracker:_accountStateTracker
225 lockStateTracker:lockStateTracker
226 cloudKitClassDependencies:cloudKitClassDependencies];
231 - (void)initializeOctagon
233 secnotice("octagon", "Initializing Octagon...");
235 if(OctagonIsEnabled()) {
236 secnotice("octagon", "starting default state machine...");
237 OTCuttlefishContext* c = [self contextForContainerName:OTCKContainerName
238 contextID:OTDefaultContext];
240 [c startOctagonStateMachine];
241 [self registerForCircleChangedNotifications];
245 - (BOOL)waitForReady:(NSString* _Nullable)containerName context:(NSString*)context wait:(int64_t)wait
247 OTCuttlefishContext* c = [self contextForContainerName:containerName contextID:context];
248 return [c waitForReady:wait];
251 - (void) moveToCheckTrustedStateForContainer:(NSString* _Nullable)containerName context:(NSString*)context
253 OTCuttlefishContext* c = [self contextForContainerName:containerName
255 [c startOctagonStateMachine];
256 [c moveToCheckTrustedState];
259 - (void)registerForCircleChangedNotifications
261 __weak __typeof(self) weakSelf = self;
263 // If we're not in the tests, go ahead and register for a notification
264 if(!SecCKKSTestsEnabled()) {
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];
273 + (instancetype _Nullable)manager {
274 if(!OctagonIsEnabled()) {
275 secerror("octagon: Attempt to fetch a manager while Octagon is disabled");
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).");
285 return [self resetManager:false to:nil];
288 + (instancetype _Nullable)resetManager:(bool)reset to:(OTManager* _Nullable)obj
290 static OTManager* manager = nil;
292 if(!manager || reset || obj) {
293 @synchronized([self class]) {
299 } else if (manager == nil && OctagonIsEnabled()) {
300 manager = [[OTManager alloc] init];
309 - (void)ensureRampsInitialized
311 CKContainer* container = [CKKSViewManager manager].container;
312 CKDatabase* database = [container privateCloudDatabase];
313 CKRecordZoneID* zoneID = [[CKRecordZoneID alloc] initWithZoneName:kOTRampZoneName ownerName:CKCurrentUserDefaultName];
315 CKKSAccountStateTracker *accountTracker = [CKKSViewManager manager].accountTracker;
316 CKKSReachabilityTracker *reachabilityTracker = [CKKSViewManager manager].reachabilityTracker;
317 CKKSLockStateTracker *lockStateTracker = [CKKSViewManager manager].lockStateTracker;
319 if(!self.gbmidRamp) {
320 self.gbmidRamp = [[OTRamp alloc]initWithRecordName:kOTRampForGhostBustMIDName
321 localSettingName:@"ghostBustMID"
325 accountTracker:accountTracker
326 lockStateTracker:lockStateTracker
327 reachabilityTracker:reachabilityTracker
328 fetchRecordRecordsOperationClass:[CKFetchRecordsOperation class]];
331 if(!self.gbserialRamp) {
332 self.gbserialRamp = [[OTRamp alloc]initWithRecordName:kOTRampForghostBustSerialName
333 localSettingName:@"ghostBustSerial"
337 accountTracker:accountTracker
338 lockStateTracker:lockStateTracker
339 reachabilityTracker:reachabilityTracker
340 fetchRecordRecordsOperationClass:[CKFetchRecordsOperation class]];
343 if(!self.gbAgeRamp) {
344 self.gbAgeRamp = [[OTRamp alloc]initWithRecordName:kOTRampForghostBustAgeName
345 localSettingName:@"ghostBustAge"
349 accountTracker:accountTracker
350 lockStateTracker:lockStateTracker
351 reachabilityTracker:reachabilityTracker
352 fetchRecordRecordsOperationClass:[CKFetchRecordsOperation class]];
357 // MARK: SPI routines
361 - (void)signIn:(NSString*)altDSID
362 container:(NSString* _Nullable)containerName
363 context:(NSString*)contextID
364 reply:(void (^)(NSError * _Nullable signedInError))reply
366 SFAnalyticsActivityTracker *tracker = [[self.loggerClass logger] startLogSystemMetricsForActivityNamed:OctagonActivityAccountAvailable];
368 if(containerName == nil) {
369 containerName = OTCKContainerName;
372 NSError *error = nil;
373 OTCuttlefishContext* context = [self contextForContainerName:containerName contextID:contextID];
375 secnotice("octagon","signing in %@ for altDSID: %@", context, altDSID);
376 [context accountAvailable:altDSID error:&error];
378 [tracker stopWithEvent:OctagonEventSignIn result:error];
383 - (void)signOut:(NSString* _Nullable)containerName
384 context:(NSString*)contextID
385 reply:(void (^)(NSError * _Nullable signedOutError))reply
387 SFAnalyticsActivityTracker *tracker = [[self.loggerClass logger] startLogSystemMetricsForActivityNamed:OctagonActivityAccountNotAvailable];
389 if(containerName == nil) {
390 containerName = OTCKContainerName;
393 NSError* error = nil;
395 // TODO: should we compare DSIDs here?
396 OTCuttlefishContext* context = [self contextForContainerName:containerName contextID:contextID];
398 secnotice("octagon", "signing out of octagon trust: %@", context);
400 [context accountNoLongerAvailable:&error];
402 secnotice("octagon", "signing out failed: %@", error);
405 [tracker stopWithEvent:OctagonEventSignOut result:error];
410 - (void)notifyIDMSTrustLevelChangeForContainer:(NSString* _Nullable)containerName
411 context:(NSString*)contextID
412 reply:(void (^)(NSError * _Nullable error))reply
414 if(containerName == nil) {
415 containerName = OTCKContainerName;
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];
426 - (void)handleIdentityChangeForSigningKey:(SFECKeyPair*)peerSigningKey
427 ForEncryptionKey:(SFECKeyPair*)encryptionKey
428 ForPeerID:(NSString*)peerID
429 reply:(void (^)(BOOL result,
430 NSError* _Nullable error))reply
432 secnotice("octagon", "handleIdentityChangeForSigningKey: %@", peerID);
434 [NSError errorWithDomain:(__bridge NSString*)kSecErrorDomain
435 code:errSecUnimplemented
439 - (void)preflightBottledPeer:(NSString*)contextID
441 reply:(void (^)(NSData* _Nullable entropy,
442 NSString* _Nullable bottleID,
443 NSData* _Nullable signingPublicKey,
444 NSError* _Nullable error))reply
446 secnotice("octagon", "preflightBottledPeer: %@ %@", contextID, dsid);
450 [NSError errorWithDomain:(__bridge NSString*)kSecErrorDomain
451 code:errSecUnimplemented
455 - (void)launchBottledPeer:(NSString*)contextID
456 bottleID:(NSString*)bottleID
457 reply:(void (^ _Nullable)(NSError* _Nullable error))reply
459 secnotice("octagon", "launchBottledPeer");
460 reply([NSError errorWithDomain:(__bridge NSString*)kSecErrorDomain
461 code:errSecUnimplemented
465 - (void)restore:(NSString *)contextID dsid:(NSString *)dsid secret:(NSData*)secret escrowRecordID:(NSString*)escrowRecordID reply:(void (^)(NSData* signingKeyData, NSData* encryptionKeyData, NSError *))reply
467 secnotice("octagon", "restore");
470 [NSError errorWithDomain:(__bridge NSString*)kSecErrorDomain
471 code:errSecUnimplemented
475 - (void)scrubBottledPeer:(NSString*)contextID
476 bottleID:(NSString*)bottleID
477 reply:(void (^ _Nullable)(NSError* _Nullable error))reply
479 reply([NSError errorWithDomain:(__bridge NSString*)kSecErrorDomain
480 code:errSecUnimplemented
485 // MARK: OTCTL tool routines
488 -(void)reset:(void (^)(BOOL result, NSError *))reply
491 [NSError errorWithDomain:(__bridge NSString*)kSecErrorDomain
492 code:errSecUnimplemented
496 - (void)listOfEligibleBottledPeerRecords:(void (^)(NSArray* listOfRecords, NSError * _Nullable))reply
499 [NSError errorWithDomain:(__bridge NSString*)kSecErrorDomain
500 code:errSecUnimplemented
504 - (void)octagonEncryptionPublicKey:(void (^)(NSData* encryptionKey, NSError *))reply
507 [NSError errorWithDomain:(__bridge NSString*)kSecErrorDomain
508 code:errSecUnimplemented
512 -(void)octagonSigningPublicKey:(void (^)(NSData* encryptionKey, NSError *))reply
515 [NSError errorWithDomain:(__bridge NSString*)kSecErrorDomain
516 code:errSecUnimplemented
520 - (void)setCuttlefishXPCConnection:(id<NSXPCProxyCreating>)cuttlefishXPCConnection
522 _cuttlefishXPCConnection = cuttlefishXPCConnection;
525 - (id<NSXPCProxyCreating>)cuttlefishXPCConnection
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;
534 return _cuttlefishXPCConnection;
537 - (OTClientStateMachine*)clientStateMachineForContainerName:(NSString* _Nullable)containerName
538 contextID:(NSString*)contextID
539 clientName:(NSString*)clientName
541 __block OTClientStateMachine* client = nil;
543 if(containerName == nil) {
544 containerName = SecCKKSContainerName;
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];
552 client = [[OTClientStateMachine alloc] initWithContainerName:containerName
554 clientName:clientName
555 cuttlefish:self.cuttlefishXPCConnection];
557 self.clients[key] = client;
564 - (void)removeClientContextForContainerName:(NSString* _Nullable)containerName
565 clientName:(NSString*)clientName
567 if(containerName == nil) {
568 containerName = SecCKKSContainerName;
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);
578 - (void)removeContextForContainerName:(NSString*)containerName
579 contextID:(NSString*)contextID
581 dispatch_sync(self.queue, ^{
582 NSString* key = [NSString stringWithFormat:@"%@-%@", containerName, contextID];
583 self.contexts[key] = nil;
587 - (OTCuttlefishContext*)contextForContainerName:(NSString* _Nullable)containerName
588 contextID:(NSString*)contextID
590 return [self contextForContainerName:containerName
592 sosAdapter:self.sosAdapter
593 authKitAdapter:self.authKitAdapter
594 lockStateTracker:self.lockStateTracker
595 accountStateTracker:self.accountStateTracker
596 deviceInformationAdapter:self.deviceInformationAdapter];
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
607 __block OTCuttlefishContext* context = nil;
609 if(containerName == nil) {
610 containerName = SecCKKSContainerName;
613 dispatch_sync(self.queue, ^{
614 NSString* key = [NSString stringWithFormat:@"%@-%@", containerName, contextID];
616 context = self.contexts[key];
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;
627 context = [[OTCuttlefishContext alloc] initWithContainerName:containerName
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
639 self.contexts[key] = context;
646 - (void)clearAllContexts
649 dispatch_sync(self.queue, ^{
650 [self.contexts removeAllObjects];
655 - (void)fetchEgoPeerID:(NSString* _Nullable)container
656 context:(NSString*)context
657 reply:(void (^)(NSString* _Nullable peerID, NSError* _Nullable error))reply
660 container = OTCKContainerName;
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));
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,
677 NSError* _Nullable error))reply
680 container = OTCKContainerName;
682 secnotice("octagon", "Received a trust status for container (%@) and context (%@)", container, context);
683 OTCuttlefishContext* cfshContext = [self contextForContainerName:container contextID:context];
685 [cfshContext rpcTrustStatus:configuration reply:^(CliqueStatus status,
686 NSString * _Nullable peerID,
687 NSDictionary<NSString *,NSNumber *> * _Nullable peerCountByModelID,
690 NSError * _Nullable error) {
691 // Our clients don't need the whole breakout of peers, so just count for them
693 for(NSNumber* n in peerCountByModelID.allValues) {
694 peerCount += [n longValue];
697 reply(status, peerID, @(peerCount), isExcluded, error);
701 - (void)fetchCliqueStatus:(NSString* _Nullable)container
702 context:(NSString*)contextID
703 configuration:(OTOperationConfiguration *)configuration
704 reply:(void (^)(CliqueStatus cliqueStatus, NSError* _Nullable error))reply
707 container = OTCKContainerName;
709 if(configuration == nil) {
710 configuration = [[OTOperationConfiguration alloc] init];
713 __block OTCuttlefishContext* context = nil;
714 dispatch_sync(self.queue, ^{
715 NSString* key = [NSString stringWithFormat:@"%@-%@", container, contextID];
717 context = self.contexts[key];
721 reply(-1, [NSError errorWithDomain:OctagonErrorDomain
722 code:OTErrorNoSuchContext
723 description:[NSString stringWithFormat:@"No context for (%@,%@)", container, contextID]]);
728 [context rpcTrustStatus:configuration reply:^(CliqueStatus status,
730 NSDictionary<NSString *,NSNumber *> * _Nullable peerCountByModelID,
733 NSError * _Nullable error) {
734 reply(status, error);
738 - (void)status:(NSString* _Nullable)containerName
739 context:(NSString*)contextID
740 reply:(void (^)(NSDictionary* _Nullable result, NSError* _Nullable error))reply
743 containerName = OTCKContainerName;
746 secnotice("octagon", "Received a status RPC for container (%@) and context (%@)", containerName, contextID);
748 __block OTCuttlefishContext* context = nil;
749 dispatch_sync(self.queue, ^{
750 NSString* key = [NSString stringWithFormat:@"%@-%@", containerName, contextID];
752 context = self.contexts[key];
756 reply(nil, [NSError errorWithDomain:OctagonErrorDomain
757 code:OTErrorNoSuchContext
758 description:[NSString stringWithFormat:@"No context for (%@,%@)", containerName, contextID]]);
762 [context rpcStatus:reply];
765 - (void)startOctagonStateMachine:(NSString* _Nullable)container
766 context:(NSString*)context
767 reply:(void (^)(NSError* _Nullable error))reply
769 secnotice("octagon", "Received a start-state-machine RPC for container (%@) and context (%@)", container, context);
771 OTCuttlefishContext* cfshContext = [self contextForContainerName:container contextID:context];
772 [cfshContext startOctagonStateMachine];
776 - (void)resetAndEstablish:(NSString *)container
777 context:(NSString *)context
778 altDSID:(NSString*)altDSID
779 resetReason:(CuttlefishResetReason)resetReason
780 reply:(void (^)(NSError * _Nullable))reply
782 SFAnalyticsActivityTracker *tracker = [[self.loggerClass logger] startLogSystemMetricsForActivityNamed:OctagonActivityResetAndEstablish];
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);
792 - (void)establish:(NSString *)container
793 context:(NSString *)context
794 altDSID:(NSString*)altDSID
795 reply:(void (^)(NSError * _Nullable))reply
797 SFAnalyticsActivityTracker *tracker = [[self.loggerClass logger] startLogSystemMetricsForActivityNamed:OctagonActivityEstablish];
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);
807 - (void)leaveClique:(NSString* _Nullable)container
808 context:(NSString*)context
809 reply:(void (^)(NSError* _Nullable error))reply
811 SFAnalyticsActivityTracker *tracker = [[self.loggerClass logger] startLogSystemMetricsForActivityNamed:OctagonActivityLeaveClique];
813 OTCuttlefishContext* cfshContext = [self contextForContainerName:container contextID:context];
814 [cfshContext startOctagonStateMachine];
815 [cfshContext rpcLeaveClique:^(NSError* leaveError) {
816 [tracker stopWithEvent:OctagonEventLeaveClique result:leaveError];
821 - (void)removeFriendsInClique:(NSString* _Nullable)container
822 context:(NSString*)context
823 peerIDs:(NSArray<NSString*>*)peerIDs
824 reply:(void (^)(NSError* _Nullable error))reply
826 SFAnalyticsActivityTracker *tracker = [[self.loggerClass logger] startLogSystemMetricsForActivityNamed:OctagonActivityRemoveFriendsInClique];
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);
836 - (void)peerDeviceNamesByPeerID:(NSString* _Nullable)container
837 context:(NSString*)context
838 reply:(void (^)(NSDictionary<NSString*, NSString*>* _Nullable peers, NSError* _Nullable error))reply
840 OTCuttlefishContext* cfshContext = [self contextForContainerName:container contextID:context];
841 [cfshContext rpcFetchDeviceNamesByPeerID:reply];
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
848 SFAnalyticsActivityTracker *tracker = [[self.loggerClass logger] startLogSystemMetricsForActivityNamed:OctagonActivityFetchAllViableBottles];
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);
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
866 SFAnalyticsActivityTracker *tracker = [[self.loggerClass logger] startLogSystemMetricsForActivityNamed:OctagonActivityFetchEscrowContents];
868 OTCuttlefishContext* cfshContext = [self contextForContainerName:containerName contextID:contextID];
869 [cfshContext fetchEscrowContents:^(NSData *entropy,
871 NSData *signingPublicKey,
873 [tracker stopWithEvent:OctagonEventFetchEscrowContents result:error];
874 reply(entropy, bottleID, signingPublicKey, error);
879 // MARK: Pairing Routines as Initiator
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
889 OTCuttlefishContext* cfshContext = [self contextForContainerName:config.containerName contextID:config.contextID];
890 [cfshContext handlePairingRestart:config];
891 [cfshContext startOctagonStateMachine];
892 [cfshContext rpcPrepareIdentityAsApplicantWithConfiguration:config
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);
904 - (void)rpcJoinWithConfiguration:(OTJoiningConfiguration*)config
905 vouchData:(NSData*)vouchData
906 vouchSig:(NSData*)vouchSig
907 reply:(void (^)(NSError * _Nullable error))reply
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) {
918 // MARK: Pairing Routines as Acceptor
921 - (void)rpcEpochWithConfiguration:(OTJoiningConfiguration*)config
922 reply:(void (^)(uint64_t epoch,
923 NSError * _Nullable error))reply
925 OTCuttlefishContext* acceptorCfshContext = [self contextForContainerName:config.containerName contextID:config.contextID];
926 [acceptorCfshContext startOctagonStateMachine];
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];
931 OTClientStateMachine *clientStateMachine = [self clientStateMachineForContainerName:config.containerName contextID:config.contextID clientName:config.pairingUUID];
933 [clientStateMachine startOctagonStateMachine];
935 [clientStateMachine rpcEpoch:acceptorCfshContext reply:^(uint64_t epoch, NSError * _Nullable error) {
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
948 OTCuttlefishContext* acceptorCfshContext = [self contextForContainerName:config.containerName contextID:config.contextID];
949 [acceptorCfshContext startOctagonStateMachine];
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];
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);
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];
968 secnotice("octagon", "restore via bottle invoked for container: %@, context: %@", containerName, contextID);
970 OTCuttlefishContext* cfshContext = [self contextForContainerName:containerName contextID:contextID];
972 [cfshContext startOctagonStateMachine];
974 [cfshContext joinWithBottle:bottleID entropy:entropy bottleSalt:bottleSalt reply:^(NSError *error) {
975 [tracker stopWithEvent:OctagonEventBottledPeerRestore result:error];
981 // MARK: Ghost busting using ramp records
984 -(BOOL) ghostbustByMidEnabled {
985 [self ensureRampsInitialized];
986 return [self.gbmidRamp checkRampStateWithError:nil];
989 -(BOOL) ghostbustBySerialEnabled {
990 [self ensureRampsInitialized];
991 return [self.gbserialRamp checkRampStateWithError:nil];
994 -(BOOL) ghostbustByAgeEnabled {
995 [self ensureRampsInitialized];
996 return [self.gbAgeRamp checkRampStateWithError:nil];
1003 - (void)setupAnalytics
1007 [[self.loggerClass logger] AddMultiSamplerForName:@"Octagon-healthSummary"
1008 withTimeInterval:SFAnalyticsSamplerIntervalOncePerReport
1009 block:^NSDictionary<NSString *,NSNumber *> *{
1012 // We actually only care about the default context for the default container
1013 OTCuttlefishContext* cuttlefishContext = [self contextForContainerName:OTCKContainerName contextID:OTDefaultContext];
1015 secnotice("octagon-analytics", "Reporting analytics for container: %@, context: %@", OTCKContainerName, OTDefaultContext);
1017 NSMutableDictionary* values = [NSMutableDictionary dictionary];
1019 NSError* error = nil;
1020 SOSCCStatus sosStatus = [self.sosAdapter circleStatus:&error];
1022 secnotice("octagon-analytics", "Error fetching SOS status: %@", error);
1024 values[OctagonAnalyticsSOSStatus] = @((int)sosStatus);
1025 NSDate* dateOfLastPPJ = [[CKKSAnalytics logger] datePropertyForKey:OctagonEventUpgradePreflightPreapprovedJoin];
1026 values[OctagonAnalyticsDateOfLastPreflightPreapprovedJoin] = @([CKKSAnalytics fuzzyDaysSinceDate:dateOfLastPPJ]);
1028 values[OctagonAnalyticsStateMachineState] = OctagonStateMap()[cuttlefishContext.stateMachine.currentState];
1030 NSError* metadataError = nil;
1031 OTAccountMetadataClassC* metadata = [cuttlefishContext.accountMetadataStore loadOrCreateAccountMetadata:&metadataError];
1032 if(!metadata || metadataError) {
1033 secnotice("octagon-analytics", "Error fetching Octagon metadata: %@", metadataError);
1035 values[OctagonAnalyticIcloudAccountState] = metadata ? @(metadata.icloudAccountState) : nil;
1036 values[OctagonAnalyticCDPBitStatus] = metadata? @(metadata.cdpState) : nil;
1037 values[OctagonAnalyticsTrustState] = metadata ? @(metadata.trustState) : nil;
1039 TPSyncingPolicy* syncingPolicy = [metadata getTPSyncingPolicy];
1040 values[OctagonAnalyticsUserControllableViewsSyncing] = syncingPolicy ? @(syncingPolicy.syncUserControllableViewsAsBoolean) : nil;
1042 NSDate* healthCheck = [cuttlefishContext currentMemoizedLastHealthCheck];
1043 values[OctagonAnalyticsLastHealthCheck] = @([CKKSAnalytics fuzzyDaysSinceDate:healthCheck]);
1045 NSDate* dateOfLastKSR = [[CKKSAnalytics logger] datePropertyForKey: OctagonAnalyticsLastKeystateReady];
1046 values[OctagonAnalyticsLastKeystateReady] = @([CKKSAnalytics fuzzyDaysSinceDate:dateOfLastKSR]);
1048 if(metadata && metadata.icloudAccountState == OTAccountMetadataClassC_AccountState_ACCOUNT_AVAILABLE) {
1049 values[OctagonAnalyticsAttemptedJoin] = @(metadata.attemptedJoin);
1051 NSError* machineIDError = nil;
1052 NSString* machineID = [cuttlefishContext.authKitAdapter machineID:&machineIDError];
1053 if(machineIDError) {
1054 secnotice("octagon-analytics", "Error fetching machine ID: %@", metadataError);
1057 values[OctagonAnalyticsHaveMachineID] = @(machineID != nil);
1060 NSError* midOnListError = nil;
1061 BOOL midOnList = [cuttlefishContext machineIDOnMemoizedList:machineID error:&midOnListError];
1063 if(midOnListError) {
1064 secnotice("octagon-analytics", "Error fetching 'mid on list': %@", midOnListError);
1066 values[OctagonAnalyticsMIDOnMemoizedList] = @(midOnList);
1069 NSError* peersWithMIDError = nil;
1070 NSNumber* peersWithMID = [cuttlefishContext numberOfPeersInModelWithMachineID:machineID error:&peersWithMIDError];
1071 if(peersWithMID && peersWithMIDError == nil) {
1072 values[OctagonAnalyticsPeersWithMID] = peersWithMID;
1074 secnotice("octagon-analytics", "Error fetching how many peers have our MID: %@", midOnListError);
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.
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);
1090 for (NSString *type in [self cdpContextTypes]) {
1091 NSString *metricName = [NSString stringWithFormat:@"%@%@", OctagonAnalyticsCDPStateRun, type];
1092 NSString *countName = [NSString stringWithFormat:@"%@%@Tries", OctagonAnalyticsCDPStateRun, type];
1094 NSDate *lastFailure = [[CKKSAnalytics logger] datePropertyForKey:metricName];
1096 values[metricName] = @([CKKSAnalytics fuzzyDaysSinceDate:lastFailure]);
1097 values[countName] = [[CKKSAnalytics logger] numberPropertyForKey:countName];
1102 id<SecEscrowRequestable> request = [self.escrowRequestClass request:&error];
1104 values[OctagonAnalyticsPrerecordPending] = @([request pendingEscrowUpload:&error]);
1106 secnotice("octagon-analytics", "Error fetching pendingEscrowUpload status: %@", error);
1109 secnotice("octagon-analytics", "Error fetching escrowRequestClass: %@", error);
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]);
1123 NSString *altDSID = primaryAccount.aa_altDSID;
1125 values[OctagonAnalyticsSecureBackupTermsAccepted] = @([getSecureBackupClass() getAcceptedTermsForAltDSID:altDSID withError:nil] != nil);
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];
1141 - (NSArray<OTCliqueCDPContextType> *)cdpContextTypes
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,
1155 return contextTypes;
1159 // MARK: Recovery Key
1162 - (void) createRecoveryKey:(NSString* _Nullable)containerName
1163 contextID:(NSString *)contextID
1164 recoveryKey:(NSString *)recoveryKey
1165 reply:(void (^)( NSError *error))reply
1167 secnotice("octagon", "Setting recovery key for container: %@, context: %@", containerName, contextID);
1169 SFAnalyticsActivityTracker *tracker = [[self.loggerClass logger] startLogSystemMetricsForActivityNamed:OctagonActivitySetRecoveryKey];
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);
1179 CFErrorRef validateError = NULL;
1180 bool res = SecPasswordValidatePasswordFormat(kSecPasswordTypeiCloudRecoveryKey, (__bridge CFStringRef)recoveryKey, &validateError);
1182 NSError *validateErrorWrapper = nil;
1183 NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
1184 userInfo[NSLocalizedDescriptionKey] = @"malformed recovery key";
1186 userInfo[NSUnderlyingErrorKey] = CFBridgingRelease(validateError);
1189 validateErrorWrapper = [NSError errorWithDomain:OctagonErrorDomain code:OTErrorRecoveryKeyMalformed userInfo:userInfo];
1191 secerror("recovery failed validation with error:%@", validateError);
1193 [tracker stopWithEvent:OctagonEventSetRecoveryKeyValidationFailed result:validateErrorWrapper];
1194 reply(validateErrorWrapper);
1198 OTCuttlefishContext* cfshContext = [self contextForContainerName:containerName contextID:contextID];
1200 [cfshContext startOctagonStateMachine];
1202 [cfshContext rpcSetRecoveryKey:recoveryKey reply:^(NSError * _Nullable error) {
1203 [tracker stopWithEvent:OctagonEventRecoveryKey result:error];
1208 - (void) joinWithRecoveryKey:(NSString* _Nullable)containerName
1209 contextID:(NSString *)contextID
1210 recoveryKey:(NSString*)recoveryKey
1211 reply:(void (^)(NSError * _Nullable))reply
1213 secnotice("octagon", "join with recovery key invoked for container: %@, context: %@", containerName, contextID);
1215 SFAnalyticsActivityTracker *tracker = [[self.loggerClass logger] startLogSystemMetricsForActivityNamed:OctagonActivityJoinWithRecoveryKey];
1217 CFErrorRef validateError = NULL;
1218 bool res = SecPasswordValidatePasswordFormat(kSecPasswordTypeiCloudRecoveryKey, (__bridge CFStringRef)recoveryKey, &validateError);
1220 NSError *validateErrorWrapper = nil;
1221 NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
1222 userInfo[NSLocalizedDescriptionKey] = @"malformed recovery key";
1224 userInfo[NSUnderlyingErrorKey] = CFBridgingRelease(validateError);
1227 validateErrorWrapper = [NSError errorWithDomain:OctagonErrorDomain code:OTErrorRecoveryKeyMalformed userInfo:userInfo];
1229 secerror("recovery failed validation with error:%@", validateError);
1231 [tracker stopWithEvent:OctagonEventJoinRecoveryKeyValidationFailed result:validateErrorWrapper];
1232 reply(validateErrorWrapper);
1236 OTCuttlefishContext* cfshContext = [self contextForContainerName:containerName contextID:contextID];
1238 [cfshContext startOctagonStateMachine];
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
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];
1253 secerror("octagon, recovery key is not enrolled in octagon, resetting octagon circle");
1254 [[self.loggerClass logger] logResultForEvent:OctagonEventJoinRecoveryKeyCircleReset hardFailure:NO result:error];
1256 [cfshContext rpcResetAndEstablish:CuttlefishResetReasonRecoveryKey reply:^(NSError *resetError) {
1258 secerror("octagon, failed to reset octagon");
1259 [tracker stopWithEvent:OctagonEventJoinRecoveryKeyCircleResetFailed result:resetError];
1263 // Now enroll the recovery key
1264 secnotice("octagon", "attempting enrolling recovery key");
1265 [self createRecoveryKey:containerName contextID:contextID recoveryKey:recoveryKey reply:^(NSError *enrollError) {
1267 secerror("octagon, failed to enroll new recovery key: %@", enrollError);
1268 [tracker stopWithEvent:OctagonEventJoinRecoveryKeyEnrollFailed result:enrollError];
1272 secnotice("octagon", "successfully enrolled recovery key");
1273 [tracker stopWithEvent:OctagonEventRecoveryKey result:nil];
1281 secerror("octagon, join with recovery key failed: %d", (int)[error code]);
1282 [tracker stopWithEvent:OctagonEventJoinRecoveryKeyFailed result:error];
1288 - (void)xpc24HrNotification
1290 secnotice("octagon-health", "Received 24hr xpc notification");
1292 [self healthCheck:OTCKContainerName context:OTDefaultContext skipRateLimitingCheck:NO reply:^(NSError * _Nullable error) {
1294 secerror("octagon: error attempting to check octagon health: %@", error);
1296 secnotice("octagon", "health check success");
1301 - (void)healthCheck:(NSString *)container context:(NSString *)context skipRateLimitingCheck:(BOOL)skipRateLimitingCheck reply:(void (^)(NSError *_Nullable error))reply
1303 OTCuttlefishContext* cfshContext = [self contextForContainerName:container contextID:context];
1305 secnotice("octagon", "notifying container of change");
1307 [cfshContext notifyContainerChange:nil];
1309 [cfshContext checkOctagonHealth:skipRateLimitingCheck reply:^(NSError *error) {
1318 - (void)setSOSEnabledForPlatformFlag:(bool) value
1320 self.sosEnabledForPlatform = value;
1323 - (void)allContextsHalt
1325 for(OTCuttlefishContext* context in self.contexts.allValues) {
1326 [context.stateMachine haltOperation];
1328 // Also, clear the viewManager strong pointer
1329 [context clearCKKSViewManager];
1333 - (void)allContextsDisablePendingFlags
1335 for(OTCuttlefishContext* context in self.contexts.allValues) {
1336 [context.stateMachine disablePendingFlags];
1340 - (bool)allContextsPause:(uint64_t)within
1342 for(OTCuttlefishContext* context in self.contexts.allValues) {
1343 if(context.stateMachine.currentState != OctagonStateMachineNotStarted) {
1344 if([context.stateMachine.paused wait:within] != 0) {
1352 - (void)waitForOctagonUpgrade:(NSString* _Nullable)containerName
1353 context:(NSString*)context
1354 reply:(void (^)(NSError* error))reply
1357 secnotice("octagon-sos", "Attempting wait for octagon upgrade");
1358 OTCuttlefishContext* cfshContext = [self contextForContainerName:containerName contextID:context];
1360 [cfshContext startOctagonStateMachine];
1362 [cfshContext waitForOctagonUpgrade:^(NSError * _Nonnull error) {
1367 - (OTFollowupContextType)cliqueCDPTypeToFollowupContextType:(OTCliqueCDPContextType)type
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;
1384 return OTFollowupContextTypeNone;
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
1395 NSString* metricName = [NSString stringWithFormat:@"%@%@", OctagonAnalyticsCDPStateRun, type];
1396 NSString* countName = [NSString stringWithFormat:@"%@%@Tries", OctagonAnalyticsCDPStateRun, type];
1398 [[CKKSAnalytics logger] logResultForEvent:metricName
1402 [[CKKSAnalytics logger] setDateProperty:[NSDate date] forKey:metricName];
1403 [[CKKSAnalytics logger] incrementIntegerPropertyForKey:countName];
1405 [[CKKSAnalytics logger] setDateProperty:NULL forKey:metricName];
1406 [[CKKSAnalytics logger] setNumberProperty:NULL forKey:countName];
1409 // Always return without error
1413 - (void)tapToRadar:(NSString *)action
1414 description:(NSString *)description
1415 radar:(NSString *)radar
1416 reply:(void (^)(NSError * _Nullable))reply
1418 SecTapToRadar *ttr = [[SecTapToRadar alloc] initTapToRadar:action description:description radar:radar];
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");
1428 if(!containerName) {
1429 containerName = OTCKContainerName;
1432 OTCuttlefishContext* cfshContext = [self contextForContainerName:containerName contextID:contextID];
1435 reply([NSError errorWithDomain:OctagonErrorDomain
1436 code:OTErrorNoSuchContext
1437 description:[NSString stringWithFormat:@"No context for (%@,%@)", containerName, contextID]]);
1441 [cfshContext rpcRefetchCKKSPolicy:^(NSError* error) {
1442 secnotice("octagon-ckks", "refetch-ckks-policy result: %@", error ?: @"no error");
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");
1452 if(!containerName) {
1453 containerName = OTCKContainerName;
1456 OTCuttlefishContext* cfshContext = [self contextForContainerName:containerName contextID:contextID];
1459 reply(OTCDPStatusUnknown, [NSError errorWithDomain:OctagonErrorDomain
1460 code:OTErrorNoSuchContext
1461 description:[NSString stringWithFormat:@"No context for (%@,%@)", containerName, contextID]]);
1465 NSError* error = nil;
1466 OTCDPStatus status = [cfshContext getCDPStatus:&error];
1468 reply(status, error);
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;
1480 OTCuttlefishContext* cfshContext = [self contextForContainerName:containerName contextID:contextID];
1483 reply([NSError errorWithDomain:OctagonErrorDomain
1484 code:OTErrorNoSuchContext
1485 description:[NSString stringWithFormat:@"No context for (%@,%@)", containerName, contextID]]);
1489 NSError* localError = nil;
1490 [cfshContext setCDPEnabled:&localError];
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 {
1501 secnotice("octagon-fetch-escrow-records", "fetching records");
1502 if(!containerName) {
1503 containerName = OTCKContainerName;
1506 OTCuttlefishContext* cfshContext = [self contextForContainerName:containerName contextID:contextID];
1509 reply(nil, [NSError errorWithDomain:OctagonErrorDomain
1510 code:OTErrorNoSuchContext
1511 description:[NSString stringWithFormat:@"No context for (%@,%@)", containerName, contextID]]);
1515 [cfshContext rpcFetchAllViableEscrowRecords:forceFetch reply:^(NSArray<NSData *> * _Nullable records, NSError * _Nullable error) {
1517 secerror("octagon-fetch-escrow-records: error fetching records: %@", error);
1521 secnotice("octagon-fetch-escrow-records", "successfully fetched records");
1522 reply(records, nil);
1526 - (void)invalidateEscrowCache:(NSString * _Nullable)containerName
1527 contextID:(NSString*)contextID
1528 reply:(nonnull void (^)(NSError * _Nullable error))reply {
1530 secnotice("octagon-remove-escrow-cache", "beginning removing escrow cache!");
1531 if(!containerName) {
1532 containerName = OTCKContainerName;
1535 OTCuttlefishContext* cfshContext = [self contextForContainerName:containerName contextID:contextID];
1538 reply([NSError errorWithDomain:OctagonErrorDomain
1539 code:OTErrorNoSuchContext
1540 description:[NSString stringWithFormat:@"No context for (%@,%@)", containerName, contextID]]);
1544 [cfshContext rpcInvalidateEscrowCache:^(NSError * _Nullable invalidateError) {
1545 if(invalidateError) {
1546 secerror("octagon-remove-escrow-cache: error invalidating escrow cache: %@", invalidateError);
1547 reply(invalidateError);
1550 secnotice("octagon-remove-escrow-caches", "successfully invalidated escrow cache");
1555 - (void)setUserControllableViewsSyncStatus:(NSString* _Nullable)containerName
1556 contextID:(NSString*)contextID
1557 enabled:(BOOL)enabled
1558 reply:(void (^)(BOOL nowSyncing, NSError* _Nullable error))reply
1560 OTCuttlefishContext* cfshContext = [self contextForContainerName:containerName contextID:contextID];
1563 reply(NO, [NSError errorWithDomain:OctagonErrorDomain
1564 code:OTErrorNoSuchContext
1565 description:[NSString stringWithFormat:@"No context for (%@,%@)", containerName, contextID]]);
1569 [cfshContext rpcSetUserControllableViewsSyncingStatus:enabled reply:^(BOOL areSyncing, NSError * _Nullable error) {
1571 secerror("octagon-user-controllable-views: error setting status: %@", error);
1576 secnotice("octagon-user-controllable-views", "successfully set status to: %@", areSyncing ? @"enabled" : @"paused");
1577 reply(areSyncing, nil);
1581 - (void)fetchUserControllableViewsSyncStatus:(NSString* _Nullable)containerName
1582 contextID:(NSString*)contextID
1583 reply:(void (^)(BOOL nowSyncing, NSError* _Nullable error))reply
1585 OTCuttlefishContext* cfshContext = [self contextForContainerName:containerName contextID:contextID];
1588 reply(NO, [NSError errorWithDomain:OctagonErrorDomain
1589 code:OTErrorNoSuchContext
1590 description:[NSString stringWithFormat:@"No context for (%@,%@)", containerName, contextID]]);
1594 [cfshContext rpcFetchUserControllableViewsSyncingStatus:^(BOOL areSyncing, NSError * _Nullable error) {
1596 secerror("octagon-user-controllable-views: error fetching status: %@", error);
1601 secerror("octagon-user-controllable-views: succesfully fetched status as: %@", areSyncing ? @"enabled" : @"paused");
1602 reply(areSyncing, nil);