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 "keychain/TrustedPeersHelper/TPHObjcTranslation.h"
82 #import "keychain/SecureObjectSync/SOSAccountTransaction.h"
83 #pragma clang diagnostic push
84 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
85 #import "keychain/SecureObjectSync/SOSAccount.h"
86 #pragma clang diagnostic pop
88 #import "utilities/SecTapToRadar.h"
90 static NSString* const kOTRampForEnrollmentRecordName = @"metadata_rampstate_enroll";
91 static NSString* const kOTRampForRestoreRecordName = @"metadata_rampstate_restore";
92 static NSString* const kOTRampForCFURecordName = @"metadata_rampstate_cfu";
93 static NSString* const kOTRampForGhostBustMIDName = @"metadata_rampstate_ghostBustMID";
94 static NSString* const kOTRampForghostBustSerialName = @"metadata_rampstate_ghostBustSerial";
95 static NSString* const kOTRampForghostBustAgeName = @"metadata_rampstate_ghostBustAge";
96 static NSString* const kOTRampZoneName = @"metadata_zone";
97 #define NUM_NSECS_IN_24_HRS (86400 * NSEC_PER_SEC)
100 @interface OTManager (lockstateTracker) <CKKSLockStateNotification>
104 @interface OTManager () <NSXPCListenerDelegate>
105 @property NSXPCListener *listener;
107 @property (nonatomic, strong) OTRamp *gbmidRamp;
108 @property (nonatomic, strong) OTRamp *gbserialRamp;
109 @property (nonatomic, strong) OTRamp *gbAgeRamp;
110 @property (nonatomic, strong) CKKSLockStateTracker *lockStateTracker;
111 @property (nonatomic, strong) id<OctagonFollowUpControllerProtocol> cdpd;
114 @property NSMutableDictionary<NSString*, OTCuttlefishContext*>* contexts;
115 @property NSMutableDictionary<NSString*, OTClientStateMachine*>* clients;
116 @property dispatch_queue_t queue;
118 @property id<NSXPCProxyCreating> cuttlefishXPCConnection;
120 // Dependencies for injection
121 @property (readonly) id<OTSOSAdapter> sosAdapter;
122 @property (readonly) id<OTAuthKitAdapter> authKitAdapter;
123 @property (readonly) id<OTDeviceInformationAdapter> deviceInformationAdapter;
124 @property (readonly) Class<OctagonAPSConnection> apsConnectionClass;
125 @property (readonly) Class<SecEscrowRequestable> escrowRequestClass;
126 // If this is nil, all logging is disabled
127 @property (readonly, nullable) Class<SFAnalyticsProtocol> loggerClass;
128 @property (nonatomic) BOOL sosEnabledForPlatform;
131 @implementation OTManager
132 @synthesize cuttlefishXPCConnection = _cuttlefishXPCConnection;
133 @synthesize sosAdapter = _sosAdapter;
134 @synthesize authKitAdapter = _authKitAdapter;
135 @synthesize deviceInformationAdapter = _deviceInformationAdapter;
139 // Under Octagon, the sos adapter is not considered essential.
140 id<OTSOSAdapter> sosAdapter = (OctagonPlatformSupportsSOS() ?
141 [[OTSOSActualAdapter alloc] initAsEssential:NO] :
142 [[OTSOSMissingAdapter alloc] init]);
144 return [self initWithSOSAdapter:sosAdapter
145 authKitAdapter:[[OTAuthKitActualAdapter alloc] init]
146 deviceInformationAdapter:[[OTDeviceInformationActualAdapter alloc] init]
147 apsConnectionClass:[APSConnection class]
148 escrowRequestClass:[EscrowRequestServer class] // Use the server class here to skip the XPC layer
149 loggerClass:[CKKSAnalytics class]
150 lockStateTracker:[CKKSLockStateTracker globalTracker]
151 cloudKitClassDependencies:[CKKSCloudKitClassDependencies forLiveCloudKit]
152 cuttlefishXPCConnection:nil
153 cdpd:[[CDPFollowUpController alloc] init]];
156 - (instancetype)initWithSOSAdapter:(id<OTSOSAdapter>)sosAdapter
157 authKitAdapter:(id<OTAuthKitAdapter>)authKitAdapter
158 deviceInformationAdapter:(id<OTDeviceInformationAdapter>)deviceInformationAdapter
159 apsConnectionClass:(Class<OctagonAPSConnection>)apsConnectionClass
160 escrowRequestClass:(Class<SecEscrowRequestable>)escrowRequestClass
161 loggerClass:(Class<SFAnalyticsProtocol>)loggerClass
162 lockStateTracker:(CKKSLockStateTracker*)lockStateTracker
163 cloudKitClassDependencies:(CKKSCloudKitClassDependencies*)cloudKitClassDependencies
164 cuttlefishXPCConnection:(id<NSXPCProxyCreating>)cuttlefishXPCConnection
165 cdpd:(id<OctagonFollowUpControllerProtocol>)cdpd
167 if((self = [super init])) {
168 _sosAdapter = sosAdapter;
169 _authKitAdapter = authKitAdapter;
170 _deviceInformationAdapter = deviceInformationAdapter;
171 _loggerClass = loggerClass;
172 _lockStateTracker = lockStateTracker;
173 _sosEnabledForPlatform = OctagonPlatformSupportsSOS();
174 _cuttlefishXPCConnection = cuttlefishXPCConnection;
176 _cloudKitContainer = [CKKSViewManager makeCKContainer:SecCKKSContainerName usePCS:SecCKKSContainerUsePCS];
177 _accountStateTracker = [[CKKSAccountStateTracker alloc] init:_cloudKitContainer
178 nsnotificationCenterClass:cloudKitClassDependencies.nsnotificationCenterClass];
180 self.contexts = [NSMutableDictionary dictionary];
181 self.clients = [NSMutableDictionary dictionary];
183 self.queue = dispatch_queue_create("otmanager", DISPATCH_QUEUE_SERIAL);
185 _apsConnectionClass = apsConnectionClass;
186 _escrowRequestClass = escrowRequestClass;
190 _viewManager = [[CKKSViewManager alloc] initWithContainer:_cloudKitContainer
191 sosAdapter:sosAdapter
192 accountStateTracker:_accountStateTracker
193 lockStateTracker:lockStateTracker
194 cloudKitClassDependencies:cloudKitClassDependencies];
196 // The default CuttlefishContext always exists:
197 (void) [self contextForContainerName:OTCKContainerName contextID:OTDefaultContext];
199 secnotice("octagon", "otmanager init");
204 - (instancetype)initWithSOSAdapter:(id<OTSOSAdapter>)sosAdapter
205 lockStateTracker:(CKKSLockStateTracker*)lockStateTracker
206 cloudKitClassDependencies:(CKKSCloudKitClassDependencies*)cloudKitClassDependencies
208 if((self = [super init])) {
209 _sosAdapter = sosAdapter;
210 _lockStateTracker = lockStateTracker;
212 _cloudKitContainer = [CKKSViewManager makeCKContainer:SecCKKSContainerName usePCS:SecCKKSContainerUsePCS];
213 _accountStateTracker = [[CKKSAccountStateTracker alloc] init:_cloudKitContainer
214 nsnotificationCenterClass:cloudKitClassDependencies.nsnotificationCenterClass];
216 _viewManager = [[CKKSViewManager alloc] initWithContainer:_cloudKitContainer
217 sosAdapter:sosAdapter
218 accountStateTracker:_accountStateTracker
219 lockStateTracker:lockStateTracker
220 cloudKitClassDependencies:cloudKitClassDependencies];
225 - (void)initializeOctagon
227 secnotice("octagon", "Initializing Octagon...");
229 if(OctagonIsEnabled()) {
230 secnotice("octagon", "starting default state machine...");
231 OTCuttlefishContext* c = [self contextForContainerName:OTCKContainerName
232 contextID:OTDefaultContext];
234 [c startOctagonStateMachine];
235 [self registerForCircleChangedNotifications];
239 - (BOOL)waitForReady:(NSString* _Nullable)containerName context:(NSString*)context wait:(int64_t)wait
241 OTCuttlefishContext* c = [self contextForContainerName:containerName contextID:context];
242 return [c waitForReady:wait];
245 - (void) moveToCheckTrustedStateForContainer:(NSString* _Nullable)containerName context:(NSString*)context
247 OTCuttlefishContext* c = [self contextForContainerName:containerName
249 [c startOctagonStateMachine];
250 [c moveToCheckTrustedState];
253 - (void)registerForCircleChangedNotifications
255 __weak __typeof(self) weakSelf = self;
257 // If we're not in the tests, go ahead and register for a notification
258 if(!SecCKKSTestsEnabled()) {
260 notify_register_dispatch(kSOSCCCircleChangedNotification, &token, dispatch_get_global_queue(QOS_CLASS_UTILITY, 0), ^(int t) {
261 secnotice("octagon", "circle changed notification called, checking trust state");
262 [weakSelf moveToCheckTrustedStateForContainer:OTCKContainerName context:OTDefaultContext];
267 + (instancetype _Nullable)manager {
268 if(!OctagonIsEnabled()) {
269 secerror("octagon: Attempt to fetch a manager while Octagon is disabled");
273 return [self resetManager:false to:nil];
276 + (instancetype _Nullable)resetManager:(bool)reset to:(OTManager* _Nullable)obj
278 static OTManager* manager = nil;
280 if(!manager || reset || obj) {
281 @synchronized([self class]) {
287 } else if (manager == nil && OctagonIsEnabled()) {
288 manager = [[OTManager alloc] init];
297 - (void)ensureRampsInitialized
299 CKContainer* container = [CKKSViewManager manager].container;
300 CKDatabase* database = [container privateCloudDatabase];
301 CKRecordZoneID* zoneID = [[CKRecordZoneID alloc] initWithZoneName:kOTRampZoneName ownerName:CKCurrentUserDefaultName];
303 CKKSAccountStateTracker *accountTracker = [CKKSViewManager manager].accountTracker;
304 CKKSReachabilityTracker *reachabilityTracker = [CKKSViewManager manager].reachabilityTracker;
305 CKKSLockStateTracker *lockStateTracker = [CKKSViewManager manager].lockStateTracker;
307 if(!self.gbmidRamp) {
308 self.gbmidRamp = [[OTRamp alloc]initWithRecordName:kOTRampForGhostBustMIDName
309 localSettingName:@"ghostBustMID"
313 accountTracker:accountTracker
314 lockStateTracker:lockStateTracker
315 reachabilityTracker:reachabilityTracker
316 fetchRecordRecordsOperationClass:[CKFetchRecordsOperation class]];
319 if(!self.gbserialRamp) {
320 self.gbserialRamp = [[OTRamp alloc]initWithRecordName:kOTRampForghostBustSerialName
321 localSettingName:@"ghostBustSerial"
325 accountTracker:accountTracker
326 lockStateTracker:lockStateTracker
327 reachabilityTracker:reachabilityTracker
328 fetchRecordRecordsOperationClass:[CKFetchRecordsOperation class]];
331 if(!self.gbAgeRamp) {
332 self.gbAgeRamp = [[OTRamp alloc]initWithRecordName:kOTRampForghostBustAgeName
333 localSettingName:@"ghostBustAge"
337 accountTracker:accountTracker
338 lockStateTracker:lockStateTracker
339 reachabilityTracker:reachabilityTracker
340 fetchRecordRecordsOperationClass:[CKFetchRecordsOperation class]];
345 // MARK: SPI routines
349 - (void)signIn:(NSString*)altDSID
350 container:(NSString* _Nullable)containerName
351 context:(NSString*)contextID
352 reply:(void (^)(NSError * _Nullable signedInError))reply
354 SFAnalyticsActivityTracker *tracker = [[self.loggerClass logger] startLogSystemMetricsForActivityNamed:OctagonActivityAccountAvailable];
356 if(containerName == nil) {
357 containerName = OTCKContainerName;
360 NSError *error = nil;
361 OTCuttlefishContext* context = [self contextForContainerName:containerName contextID:contextID];
363 secnotice("octagon","signing in %@ for altDSID: %@", context, altDSID);
364 [context accountAvailable:altDSID error:&error];
366 [tracker stopWithEvent:OctagonEventSignIn result:error];
371 - (void)signOut:(NSString* _Nullable)containerName
372 context:(NSString*)contextID
373 reply:(void (^)(NSError * _Nullable signedOutError))reply
375 SFAnalyticsActivityTracker *tracker = [[self.loggerClass logger] startLogSystemMetricsForActivityNamed:OctagonActivityAccountNotAvailable];
377 if(containerName == nil) {
378 containerName = OTCKContainerName;
381 NSError* error = nil;
383 // TODO: should we compare DSIDs here?
384 OTCuttlefishContext* context = [self contextForContainerName:containerName contextID:contextID];
386 secnotice("octagon", "signing out of octagon trust: %@", context);
388 [context accountNoLongerAvailable:&error];
390 secnotice("octagon", "signing out failed: %@", error);
393 [tracker stopWithEvent:OctagonEventSignOut result:error];
398 - (void)notifyIDMSTrustLevelChangeForContainer:(NSString* _Nullable)containerName
399 context:(NSString*)contextID
400 reply:(void (^)(NSError * _Nullable error))reply
402 if(containerName == nil) {
403 containerName = OTCKContainerName;
406 NSError *error = nil;
407 OTCuttlefishContext* context = [self contextForContainerName:containerName contextID:contextID];
408 secnotice("octagon","received a notification of IDMS trust level change in %@", context);
409 [context idmsTrustLevelChanged:&error];
414 - (void)handleIdentityChangeForSigningKey:(SFECKeyPair*)peerSigningKey
415 ForEncryptionKey:(SFECKeyPair*)encryptionKey
416 ForPeerID:(NSString*)peerID
417 reply:(void (^)(BOOL result,
418 NSError* _Nullable error))reply
420 secnotice("octagon", "handleIdentityChangeForSigningKey: %@", peerID);
422 [NSError errorWithDomain:(__bridge NSString*)kSecErrorDomain
423 code:errSecUnimplemented
427 - (void)preflightBottledPeer:(NSString*)contextID
429 reply:(void (^)(NSData* _Nullable entropy,
430 NSString* _Nullable bottleID,
431 NSData* _Nullable signingPublicKey,
432 NSError* _Nullable error))reply
434 secnotice("octagon", "preflightBottledPeer: %@ %@", contextID, dsid);
438 [NSError errorWithDomain:(__bridge NSString*)kSecErrorDomain
439 code:errSecUnimplemented
443 - (void)launchBottledPeer:(NSString*)contextID
444 bottleID:(NSString*)bottleID
445 reply:(void (^ _Nullable)(NSError* _Nullable error))reply
447 secnotice("octagon", "launchBottledPeer");
448 reply([NSError errorWithDomain:(__bridge NSString*)kSecErrorDomain
449 code:errSecUnimplemented
453 - (void)restore:(NSString *)contextID dsid:(NSString *)dsid secret:(NSData*)secret escrowRecordID:(NSString*)escrowRecordID reply:(void (^)(NSData* signingKeyData, NSData* encryptionKeyData, NSError *))reply
455 secnotice("octagon", "restore");
458 [NSError errorWithDomain:(__bridge NSString*)kSecErrorDomain
459 code:errSecUnimplemented
463 - (void)scrubBottledPeer:(NSString*)contextID
464 bottleID:(NSString*)bottleID
465 reply:(void (^ _Nullable)(NSError* _Nullable error))reply
467 reply([NSError errorWithDomain:(__bridge NSString*)kSecErrorDomain
468 code:errSecUnimplemented
473 // MARK: OTCTL tool routines
476 -(void)reset:(void (^)(BOOL result, NSError *))reply
479 [NSError errorWithDomain:(__bridge NSString*)kSecErrorDomain
480 code:errSecUnimplemented
484 - (void)listOfEligibleBottledPeerRecords:(void (^)(NSArray* listOfRecords, NSError * _Nullable))reply
487 [NSError errorWithDomain:(__bridge NSString*)kSecErrorDomain
488 code:errSecUnimplemented
492 - (void)octagonEncryptionPublicKey:(void (^)(NSData* encryptionKey, NSError *))reply
495 [NSError errorWithDomain:(__bridge NSString*)kSecErrorDomain
496 code:errSecUnimplemented
500 -(void)octagonSigningPublicKey:(void (^)(NSData* encryptionKey, NSError *))reply
503 [NSError errorWithDomain:(__bridge NSString*)kSecErrorDomain
504 code:errSecUnimplemented
508 - (void)setCuttlefishXPCConnection:(id<NSXPCProxyCreating>)cuttlefishXPCConnection
510 _cuttlefishXPCConnection = cuttlefishXPCConnection;
513 - (id<NSXPCProxyCreating>)cuttlefishXPCConnection
515 if(!_cuttlefishXPCConnection) {
516 NSXPCConnection* xpcConnection = [[NSXPCConnection alloc] initWithServiceName:@"com.apple.TrustedPeersHelper"];
517 xpcConnection.remoteObjectInterface = TrustedPeersHelperSetupProtocol([NSXPCInterface interfaceWithProtocol:@protocol(TrustedPeersHelperProtocol)]);
518 [xpcConnection resume];
519 _cuttlefishXPCConnection = xpcConnection;
522 return _cuttlefishXPCConnection;
525 - (OTClientStateMachine*)clientStateMachineForContainerName:(NSString* _Nullable)containerName
526 contextID:(NSString*)contextID
527 clientName:(NSString*)clientName
529 __block OTClientStateMachine* client = nil;
531 if(containerName == nil) {
532 containerName = SecCKKSContainerName;
535 dispatch_sync(self.queue, ^{
536 NSString* key = [NSString stringWithFormat:@"%@-%@", containerName, clientName];
537 secnotice("octagon-client", "fetching context for key: %@", key);
538 client = self.clients[key];
540 client = [[OTClientStateMachine alloc] initWithContainerName:containerName
542 clientName:clientName
543 cuttlefish:self.cuttlefishXPCConnection];
545 self.clients[key] = client;
552 - (void)removeClientContextForContainerName:(NSString* _Nullable)containerName
553 clientName:(NSString*)clientName
555 if(containerName == nil) {
556 containerName = SecCKKSContainerName;
559 dispatch_sync(self.queue, ^{
560 NSString* key = [NSString stringWithFormat:@"%@-%@", containerName, clientName];
561 [self.clients removeObjectForKey:key];
562 secnotice("octagon", "removed client context with key: %@", key);
566 - (void)removeContextForContainerName:(NSString*)containerName
567 contextID:(NSString*)contextID
569 dispatch_sync(self.queue, ^{
570 NSString* key = [NSString stringWithFormat:@"%@-%@", containerName, contextID];
571 self.contexts[key] = nil;
575 - (OTCuttlefishContext*)contextForContainerName:(NSString* _Nullable)containerName
576 contextID:(NSString*)contextID
578 return [self contextForContainerName:containerName
580 sosAdapter:self.sosAdapter
581 authKitAdapter:self.authKitAdapter
582 lockStateTracker:self.lockStateTracker
583 accountStateTracker:self.accountStateTracker
584 deviceInformationAdapter:self.deviceInformationAdapter];
587 - (OTCuttlefishContext*)contextForContainerName:(NSString* _Nullable)containerName
588 contextID:(NSString*)contextID
589 sosAdapter:(id<OTSOSAdapter>)sosAdapter
590 authKitAdapter:(id<OTAuthKitAdapter>)authKitAdapter
591 lockStateTracker:(CKKSLockStateTracker*)lockStateTracker
592 accountStateTracker:(CKKSAccountStateTracker*)accountStateTracker
593 deviceInformationAdapter:(id<OTDeviceInformationAdapter>)deviceInformationAdapter
595 __block OTCuttlefishContext* context = nil;
597 if(containerName == nil) {
598 containerName = SecCKKSContainerName;
601 dispatch_sync(self.queue, ^{
602 NSString* key = [NSString stringWithFormat:@"%@-%@", containerName, contextID];
604 context = self.contexts[key];
606 // Right now, CKKS can only handle one session per address space (and SQL database).
607 // Therefore, only the primary OTCuttlefishContext gets to own the view manager.
608 CKKSViewManager* viewManager = nil;
609 if([containerName isEqualToString:SecCKKSContainerName] &&
610 [contextID isEqualToString:OTDefaultContext]) {
611 viewManager = self.viewManager;
615 context = [[OTCuttlefishContext alloc] initWithContainerName:containerName
617 cuttlefish:self.cuttlefishXPCConnection
618 sosAdapter:sosAdapter
619 authKitAdapter:authKitAdapter
620 ckksViewManager:viewManager
621 lockStateTracker:lockStateTracker
622 accountStateTracker:accountStateTracker
623 deviceInformationAdapter:deviceInformationAdapter
624 apsConnectionClass:self.apsConnectionClass
625 escrowRequestClass:self.escrowRequestClass
627 self.contexts[key] = context;
634 - (void)clearAllContexts
637 dispatch_sync(self.queue, ^{
638 [self.contexts removeAllObjects];
643 - (void)fetchEgoPeerID:(NSString* _Nullable)container
644 context:(NSString*)context
645 reply:(void (^)(NSString* _Nullable peerID, NSError* _Nullable error))reply
648 container = OTCKContainerName;
650 secnotice("octagon", "Received a fetch peer ID for container (%@) and context (%@)", container, context);
651 OTCuttlefishContext* cfshContext = [self contextForContainerName:container contextID:context];
652 [cfshContext rpcFetchEgoPeerID:^(NSString * _Nullable peerID,
653 NSError * _Nullable error) {
654 reply(peerID, XPCSanitizeError(error));
658 - (void)fetchTrustStatus:(NSString *)container
659 context:(NSString *)context
660 configuration:(OTOperationConfiguration *)configuration
661 reply:(void (^)(CliqueStatus status,
662 NSString* _Nullable peerID,
663 NSNumber * _Nullable numberOfPeersInOctagon,
665 NSError* _Nullable error))reply
668 container = OTCKContainerName;
670 secnotice("octagon", "Received a trust status for container (%@) and context (%@)", container, context);
671 OTCuttlefishContext* cfshContext = [self contextForContainerName:container contextID:context];
673 [cfshContext rpcTrustStatus:configuration reply:^(CliqueStatus status,
674 NSString * _Nullable peerID,
675 NSDictionary<NSString *,NSNumber *> * _Nullable peerCountByModelID,
677 NSError * _Nullable error) {
678 // Our clients don't need the whole breakout of peers, so just count for them
680 for(NSNumber* n in peerCountByModelID.allValues) {
681 peerCount += [n longValue];
684 reply(status, peerID, @(peerCount), isExcluded, error);
688 - (void)fetchCliqueStatus:(NSString* _Nullable)container
689 context:(NSString*)contextID
690 configuration:(OTOperationConfiguration *)configuration
691 reply:(void (^)(CliqueStatus cliqueStatus, NSError* _Nullable error))reply
694 container = OTCKContainerName;
696 if(configuration == nil) {
697 configuration = [[OTOperationConfiguration alloc] init];
700 __block OTCuttlefishContext* context = nil;
701 dispatch_sync(self.queue, ^{
702 NSString* key = [NSString stringWithFormat:@"%@-%@", container, contextID];
704 context = self.contexts[key];
708 reply(-1, [NSError errorWithDomain:OctagonErrorDomain
709 code:OTErrorNoSuchContext
710 description:[NSString stringWithFormat:@"No context for (%@,%@)", container, contextID]]);
715 [context rpcTrustStatus:configuration reply:^(CliqueStatus status,
717 NSDictionary<NSString *,NSNumber *> * _Nullable peerCountByModelID,
719 NSError * _Nullable error) {
720 reply(status, error);
724 - (void)status:(NSString* _Nullable)containerName
725 context:(NSString*)contextID
726 reply:(void (^)(NSDictionary* _Nullable result, NSError* _Nullable error))reply
729 containerName = OTCKContainerName;
732 secnotice("octagon", "Received a status RPC for container (%@) and context (%@)", containerName, contextID);
734 __block OTCuttlefishContext* context = nil;
735 dispatch_sync(self.queue, ^{
736 NSString* key = [NSString stringWithFormat:@"%@-%@", containerName, contextID];
738 context = self.contexts[key];
742 reply(nil, [NSError errorWithDomain:OctagonErrorDomain
743 code:OTErrorNoSuchContext
744 description:[NSString stringWithFormat:@"No context for (%@,%@)", containerName, contextID]]);
748 [context rpcStatus:reply];
751 - (void)startOctagonStateMachine:(NSString* _Nullable)container
752 context:(NSString*)context
753 reply:(void (^)(NSError* _Nullable error))reply
755 secnotice("octagon", "Received a start-state-machine RPC for container (%@) and context (%@)", container, context);
757 OTCuttlefishContext* cfshContext = [self contextForContainerName:container contextID:context];
758 [cfshContext startOctagonStateMachine];
762 - (void)resetAndEstablish:(NSString *)container
763 context:(NSString *)context
764 altDSID:(NSString*)altDSID
765 resetReason:(CuttlefishResetReason)resetReason
766 reply:(void (^)(NSError * _Nullable))reply
768 SFAnalyticsActivityTracker *tracker = [[self.loggerClass logger] startLogSystemMetricsForActivityNamed:OctagonActivityResetAndEstablish];
770 OTCuttlefishContext* cfshContext = [self contextForContainerName:container contextID:context];
771 [cfshContext startOctagonStateMachine];
772 [cfshContext rpcResetAndEstablish:resetReason reply:^(NSError* resetAndEstablishError) {
773 [tracker stopWithEvent:OctagonEventResetAndEstablish result:resetAndEstablishError];
774 reply(resetAndEstablishError);
778 - (void)establish:(NSString *)container
779 context:(NSString *)context
780 altDSID:(NSString*)altDSID
781 reply:(void (^)(NSError * _Nullable))reply
783 SFAnalyticsActivityTracker *tracker = [[self.loggerClass logger] startLogSystemMetricsForActivityNamed:OctagonActivityEstablish];
785 OTCuttlefishContext* cfshContext = [self contextForContainerName:container contextID:context];
786 [cfshContext startOctagonStateMachine];
787 [cfshContext rpcEstablish:altDSID reply:^(NSError* establishError) {
788 [tracker stopWithEvent:OctagonEventEstablish result:establishError];
789 reply(establishError);
793 - (void)leaveClique:(NSString* _Nullable)container
794 context:(NSString*)context
795 reply:(void (^)(NSError* _Nullable error))reply
797 SFAnalyticsActivityTracker *tracker = [[self.loggerClass logger] startLogSystemMetricsForActivityNamed:OctagonActivityLeaveClique];
799 OTCuttlefishContext* cfshContext = [self contextForContainerName:container contextID:context];
800 [cfshContext startOctagonStateMachine];
801 [cfshContext rpcLeaveClique:^(NSError* leaveError) {
802 [tracker stopWithEvent:OctagonEventLeaveClique result:leaveError];
807 - (void)removeFriendsInClique:(NSString* _Nullable)container
808 context:(NSString*)context
809 peerIDs:(NSArray<NSString*>*)peerIDs
810 reply:(void (^)(NSError* _Nullable error))reply
812 SFAnalyticsActivityTracker *tracker = [[self.loggerClass logger] startLogSystemMetricsForActivityNamed:OctagonActivityRemoveFriendsInClique];
814 OTCuttlefishContext* cfshContext = [self contextForContainerName:container contextID:context];
815 [cfshContext startOctagonStateMachine];
816 [cfshContext rpcRemoveFriendsInClique:peerIDs reply:^(NSError* removeFriendsError) {
817 [tracker stopWithEvent:OctagonEventRemoveFriendsInClique result:removeFriendsError];
818 reply(removeFriendsError);
822 - (void)peerDeviceNamesByPeerID:(NSString* _Nullable)container
823 context:(NSString*)context
824 reply:(void (^)(NSDictionary<NSString*, NSString*>* _Nullable peers, NSError* _Nullable error))reply
826 OTCuttlefishContext* cfshContext = [self contextForContainerName:container contextID:context];
827 [cfshContext rpcFetchDeviceNamesByPeerID:reply];
830 - (void)fetchAllViableBottles:(NSString* _Nullable)container
831 context:(NSString*)context
832 reply:(void (^)(NSArray<NSString*>* _Nullable sortedBottleIDs, NSArray<NSString*>* _Nullable sortedPartialBottleIDs, NSError* _Nullable error))reply
834 SFAnalyticsActivityTracker *tracker = [[self.loggerClass logger] startLogSystemMetricsForActivityNamed:OctagonActivityFetchAllViableBottles];
836 OTCuttlefishContext* cfshContext = [self contextForContainerName:container contextID:context];
837 [cfshContext rpcFetchAllViableBottles:^(NSArray<NSString *> * _Nullable sortedBottleIDs,
838 NSArray<NSString *> * _Nullable sortedPartialEscrowRecordIDs,
839 NSError * _Nullable error) {
840 [tracker stopWithEvent:OctagonEventFetchAllBottles result:error];
841 reply(sortedBottleIDs, sortedPartialEscrowRecordIDs, error);
845 - (void)fetchEscrowContents:(NSString* _Nullable)containerName
846 contextID:(NSString *)contextID
847 reply:(void (^)(NSData* _Nullable entropy,
848 NSString* _Nullable bottleID,
849 NSData* _Nullable signingPublicKey,
850 NSError* _Nullable error))reply
852 SFAnalyticsActivityTracker *tracker = [[self.loggerClass logger] startLogSystemMetricsForActivityNamed:OctagonActivityFetchEscrowContents];
854 OTCuttlefishContext* cfshContext = [self contextForContainerName:containerName contextID:contextID];
855 [cfshContext fetchEscrowContents:^(NSData *entropy,
857 NSData *signingPublicKey,
859 [tracker stopWithEvent:OctagonEventFetchEscrowContents result:error];
860 reply(entropy, bottleID, signingPublicKey, error);
865 // MARK: Pairing Routines as Initiator
867 - (void)rpcPrepareIdentityAsApplicantWithConfiguration:(OTJoiningConfiguration*)config
868 reply:(void (^)(NSString * _Nullable peerID,
869 NSData * _Nullable permanentInfo,
870 NSData * _Nullable permanentInfoSig,
871 NSData * _Nullable stableInfo,
872 NSData * _Nullable stableInfoSig,
873 NSError * _Nullable error))reply
875 OTCuttlefishContext* cfshContext = [self contextForContainerName:config.containerName contextID:config.contextID];
876 [cfshContext handlePairingRestart:config];
877 [cfshContext startOctagonStateMachine];
878 [cfshContext rpcPrepareIdentityAsApplicantWithConfiguration:config
880 reply:^(NSString * _Nullable peerID,
881 NSData * _Nullable permanentInfo,
882 NSData * _Nullable permanentInfoSig,
883 NSData * _Nullable stableInfo,
884 NSData * _Nullable stableInfoSig,
885 NSError * _Nullable error) {
886 reply(peerID, permanentInfo, permanentInfoSig, stableInfo, stableInfoSig, error);
890 - (void)rpcJoinWithConfiguration:(OTJoiningConfiguration*)config
891 vouchData:(NSData*)vouchData
892 vouchSig:(NSData*)vouchSig
893 reply:(void (^)(NSError * _Nullable error))reply
895 OTCuttlefishContext* cfshContext = [self contextForContainerName:config.containerName contextID:config.contextID];
896 [cfshContext handlePairingRestart:config];
897 [cfshContext startOctagonStateMachine];
898 [cfshContext rpcJoin:vouchData vouchSig:vouchSig reply:^(NSError * _Nullable error) {
904 // MARK: Pairing Routines as Acceptor
907 - (void)rpcEpochWithConfiguration:(OTJoiningConfiguration*)config
908 reply:(void (^)(uint64_t epoch,
909 NSError * _Nullable error))reply
911 OTCuttlefishContext* acceptorCfshContext = [self contextForContainerName:config.containerName contextID:config.contextID];
912 [acceptorCfshContext startOctagonStateMachine];
914 // 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
915 [acceptorCfshContext requestTrustedDeviceListRefresh];
917 OTClientStateMachine *clientStateMachine = [self clientStateMachineForContainerName:config.containerName contextID:config.contextID clientName:config.pairingUUID];
919 [clientStateMachine startOctagonStateMachine];
921 [clientStateMachine rpcEpoch:acceptorCfshContext reply:^(uint64_t epoch, NSError * _Nullable error) {
926 - (void)rpcVoucherWithConfiguration:(OTJoiningConfiguration*)config
927 peerID:(NSString*)peerID
928 permanentInfo:(NSData *)permanentInfo
929 permanentInfoSig:(NSData *)permanentInfoSig
930 stableInfo:(NSData *)stableInfo
931 stableInfoSig:(NSData *)stableInfoSig
932 reply:(void (^)(NSData* voucher, NSData* voucherSig, NSError * _Nullable error))reply
934 OTCuttlefishContext* acceptorCfshContext = [self contextForContainerName:config.containerName contextID:config.contextID];
935 [acceptorCfshContext startOctagonStateMachine];
937 // 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
938 [acceptorCfshContext requestTrustedDeviceListRefresh];
939 OTClientStateMachine *clientStateMachine = [self clientStateMachineForContainerName:config.containerName contextID:config.contextID clientName:config.pairingUUID];
941 [clientStateMachine rpcVoucher:acceptorCfshContext peerID:peerID permanentInfo:permanentInfo permanentInfoSig:permanentInfoSig stableInfo:stableInfo stableInfoSig:stableInfoSig reply:^(NSData *voucher, NSData *voucherSig, NSError *error) {
942 reply(voucher, voucherSig, error);
946 - (void)restore:(NSString * _Nullable)containerName
947 contextID:(nonnull NSString *)contextID
948 bottleSalt:(nonnull NSString *)bottleSalt
949 entropy:(nonnull NSData *)entropy
950 bottleID:(nonnull NSString *)bottleID
951 reply:(nonnull void (^)(NSError * _Nullable))reply {
952 SFAnalyticsActivityTracker *tracker = [[self.loggerClass logger] startLogSystemMetricsForActivityNamed:OctagonActivityBottledPeerRestore];
954 secnotice("octagon", "restore via bottle invoked for container: %@, context: %@", containerName, contextID);
956 OTCuttlefishContext* cfshContext = [self contextForContainerName:containerName contextID:contextID];
958 [cfshContext startOctagonStateMachine];
960 [cfshContext joinWithBottle:bottleID entropy:entropy bottleSalt:bottleSalt reply:^(NSError *error) {
961 [tracker stopWithEvent:OctagonEventBottledPeerRestore result:error];
967 // MARK: Ghost busting using ramp records
970 -(BOOL) ghostbustByMidEnabled {
971 [self ensureRampsInitialized];
972 return [self.gbmidRamp checkRampStateWithError:nil];
975 -(BOOL) ghostbustBySerialEnabled {
976 [self ensureRampsInitialized];
977 return [self.gbserialRamp checkRampStateWithError:nil];
980 -(BOOL) ghostbustByAgeEnabled {
981 [self ensureRampsInitialized];
982 return [self.gbAgeRamp checkRampStateWithError:nil];
989 - (void)setupAnalytics
993 [[self.loggerClass logger] AddMultiSamplerForName:@"Octagon-healthSummary"
994 withTimeInterval:SFAnalyticsSamplerIntervalOncePerReport
995 block:^NSDictionary<NSString *,NSNumber *> *{
998 // We actually only care about the default context for the default container
999 OTCuttlefishContext* cuttlefishContext = [self contextForContainerName:OTCKContainerName contextID:OTDefaultContext];
1001 secnotice("octagon-analytics", "Reporting analytics for container: %@, context: %@", OTCKContainerName, OTDefaultContext);
1003 NSMutableDictionary* values = [NSMutableDictionary dictionary];
1005 NSError* error = nil;
1006 SOSCCStatus sosStatus = [self.sosAdapter circleStatus:&error];
1008 secnotice("octagon-analytics", "Error fetching SOS status: %@", error);
1010 values[OctagonAnalyticsSOSStatus] = @((int)sosStatus);
1011 NSDate* dateOfLastPPJ = [[CKKSAnalytics logger] datePropertyForKey:OctagonEventUpgradePreflightPreapprovedJoin];
1012 values[OctagonAnalyticsDateOfLastPreflightPreapprovedJoin] = @([CKKSAnalytics fuzzyDaysSinceDate:dateOfLastPPJ]);
1014 values[OctagonAnalyticsStateMachineState] = OctagonStateMap()[cuttlefishContext.stateMachine.currentState];
1016 NSError* metadataError = nil;
1017 OTAccountMetadataClassC* metadata = [cuttlefishContext.accountMetadataStore loadOrCreateAccountMetadata:&metadataError];
1018 if(!metadata || metadataError) {
1019 secnotice("octagon-analytics", "Error fetching Octagon metadata: %@", metadataError);
1021 values[OctagonAnalyticIcloudAccountState] = metadata ? @(metadata.icloudAccountState) : nil;
1022 values[OctagonAnalyticCDPBitStatus] = metadata? @(metadata.cdpState) : nil;
1023 values[OctagonAnalyticsTrustState] = metadata ? @(metadata.trustState) : nil;
1025 NSDate* healthCheck = [cuttlefishContext currentMemoizedLastHealthCheck];
1026 values[OctagonAnalyticsLastHealthCheck] = @([CKKSAnalytics fuzzyDaysSinceDate:healthCheck]);
1028 NSDate* dateOfLastKSR = [[CKKSAnalytics logger] datePropertyForKey: OctagonAnalyticsLastKeystateReady];
1029 values[OctagonAnalyticsLastKeystateReady] = @([CKKSAnalytics fuzzyDaysSinceDate:dateOfLastKSR]);
1031 if(metadata && metadata.icloudAccountState == OTAccountMetadataClassC_AccountState_ACCOUNT_AVAILABLE) {
1032 values[OctagonAnalyticsAttemptedJoin] = @(metadata.attemptedJoin);
1034 NSError* machineIDError = nil;
1035 NSString* machineID = [cuttlefishContext.authKitAdapter machineID:&machineIDError];
1036 if(machineIDError) {
1037 secnotice("octagon-analytics", "Error fetching machine ID: %@", metadataError);
1040 values[OctagonAnalyticsHaveMachineID] = @(machineID != nil);
1043 NSError* midOnListError = nil;
1044 BOOL midOnList = [cuttlefishContext machineIDOnMemoizedList:machineID error:&midOnListError];
1046 if(midOnListError) {
1047 secnotice("octagon-analytics", "Error fetching 'mid on list': %@", midOnListError);
1049 values[OctagonAnalyticsMIDOnMemoizedList] = @(midOnList);
1052 NSError* peersWithMIDError = nil;
1053 NSNumber* peersWithMID = [cuttlefishContext numberOfPeersInModelWithMachineID:machineID error:&peersWithMIDError];
1054 if(peersWithMID && peersWithMIDError == nil) {
1055 values[OctagonAnalyticsPeersWithMID] = peersWithMID;
1057 secnotice("octagon-analytics", "Error fetching how many peers have our MID: %@", midOnListError);
1062 // Track CFU usage and success/failure metrics
1063 // 1. Users in a good state will have no outstanding CFU, and will not have done a CFU
1064 // 2. Users in a bad state who have not repsonded to the CFU (repaired) will have a pending CFU.
1065 // 3. Users in a bad state who have acted on the CFU will have no pending CFU, but will have CFU failures.
1067 // We also record the time window between the last followup completion and invocation.
1068 NSDate* dateOfLastFollowup = [[CKKSAnalytics logger] datePropertyForKey: OctagonAnalyticsLastCoreFollowup];
1069 values[OctagonAnalyticsLastCoreFollowup] = @([CKKSAnalytics fuzzyDaysSinceDate:dateOfLastFollowup]);
1070 // We used to report this, but it was never set
1071 //values[OctagonAnalyticsCoreFollowupStatus] = @(followupHandler.followupStatus);
1073 for (NSString *type in [self cdpContextTypes]) {
1074 NSString *metricName = [NSString stringWithFormat:@"%@%@", OctagonAnalyticsCDPStateRun, type];
1075 NSString *countName = [NSString stringWithFormat:@"%@%@Tries", OctagonAnalyticsCDPStateRun, type];
1077 NSDate *lastFailure = [[CKKSAnalytics logger] datePropertyForKey:metricName];
1079 values[metricName] = @([CKKSAnalytics fuzzyDaysSinceDate:lastFailure]);
1080 values[countName] = [[CKKSAnalytics logger] numberPropertyForKey:countName];
1085 id<SecEscrowRequestable> request = [self.escrowRequestClass request:&error];
1087 values[OctagonAnalyticsPrerecordPending] = @([request pendingEscrowUpload:&error]);
1089 secnotice("octagon-analytics", "Error fetching pendingEscrowUpload status: %@", error);
1092 secnotice("octagon-analytics", "Error fetching escrowRequestClass: %@", error);
1096 ACAccountStore *store = [[ACAccountStore alloc] init];
1097 ACAccount* primaryAccount = [store aa_primaryAppleAccount];
1098 if(primaryAccount) {
1099 values[OctagonAnalyticsKVSProvisioned] = @([primaryAccount isProvisionedForDataclass:ACAccountDataclassKeyValue]);
1100 values[OctagonAnalyticsKVSEnabled] = @([primaryAccount isEnabledForDataclass:ACAccountDataclassKeyValue]);
1101 values[OctagonAnalyticsKeychainSyncProvisioned] = @([primaryAccount isProvisionedForDataclass:ACAccountDataclassKeychainSync]);
1102 values[OctagonAnalyticsKeychainSyncEnabled] = @([primaryAccount isEnabledForDataclass:ACAccountDataclassKeychainSync]);
1103 values[OctagonAnalyticsCloudKitProvisioned] = @([primaryAccount isProvisionedForDataclass:ACAccountDataclassCKDatabaseService]);
1104 values[OctagonAnalyticsCloudKitEnabled] = @([primaryAccount isEnabledForDataclass:ACAccountDataclassCKDatabaseService]);
1111 [[self.loggerClass logger] AddMultiSamplerForName:@"CFU-healthSummary"
1112 withTimeInterval:SFAnalyticsSamplerIntervalOncePerReport
1113 block:^NSDictionary<NSString *,NSNumber *> *{
1114 OTCuttlefishContext* cuttlefishContext = [self contextForContainerName:OTCKContainerName contextID:OTDefaultContext];
1115 return [cuttlefishContext.followupHandler sfaStatus];
1119 - (NSArray<OTCliqueCDPContextType> *)cdpContextTypes
1121 static NSArray<OTCliqueCDPContextType> *contextTypes = nil;
1122 static dispatch_once_t onceToken;
1123 dispatch_once(&onceToken, ^{
1124 contextTypes = @[OTCliqueCDPContextTypeNone,
1125 OTCliqueCDPContextTypeSignIn,
1126 OTCliqueCDPContextTypeRepair,
1127 OTCliqueCDPContextTypeFinishPasscodeChange,
1128 OTCliqueCDPContextTypeRecoveryKeyGenerate,
1129 OTCliqueCDPContextTypeRecoveryKeyNew,
1130 OTCliqueCDPContextTypeUpdatePasscode,
1133 return contextTypes;
1137 // MARK: Recovery Key
1140 - (void) createRecoveryKey:(NSString* _Nullable)containerName
1141 contextID:(NSString *)contextID
1142 recoveryKey:(NSString *)recoveryKey
1143 reply:(void (^)( NSError *error))reply
1145 secnotice("octagon", "Setting recovery key for container: %@, context: %@", containerName, contextID);
1147 SFAnalyticsActivityTracker *tracker = [[self.loggerClass logger] startLogSystemMetricsForActivityNamed:OctagonActivitySetRecoveryKey];
1149 if (!self.sosEnabledForPlatform) {
1150 secnotice("octagon-recovery", "Device is considered a limited peer, cannot enroll recovery key in Octagon");
1151 NSError* notFullPeerError = [NSError errorWithDomain:OctagonErrorDomain code:OTErrorLimitedPeer userInfo:@{NSLocalizedDescriptionKey : @"Device is considered a limited peer, cannot enroll recovery key in Octagon"}];
1152 [tracker stopWithEvent:OctagonEventRecoveryKey result:notFullPeerError];
1153 reply(notFullPeerError);
1157 CFErrorRef validateError = NULL;
1158 bool res = SecPasswordValidatePasswordFormat(kSecPasswordTypeiCloudRecoveryKey, (__bridge CFStringRef)recoveryKey, &validateError);
1160 NSError *validateErrorWrapper = nil;
1161 NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
1162 userInfo[NSLocalizedDescriptionKey] = @"malformed recovery key";
1164 userInfo[NSUnderlyingErrorKey] = CFBridgingRelease(validateError);
1167 validateErrorWrapper = [NSError errorWithDomain:OctagonErrorDomain code:OTErrorRecoveryKeyMalformed userInfo:userInfo];
1169 secerror("recovery failed validation with error:%@", validateError);
1171 [tracker stopWithEvent:OctagonEventSetRecoveryKeyValidationFailed result:validateErrorWrapper];
1172 reply(validateErrorWrapper);
1176 OTCuttlefishContext* cfshContext = [self contextForContainerName:containerName contextID:contextID];
1178 [cfshContext startOctagonStateMachine];
1180 [cfshContext rpcSetRecoveryKey:recoveryKey reply:^(NSError * _Nullable error) {
1181 [tracker stopWithEvent:OctagonEventRecoveryKey result:error];
1186 - (void) joinWithRecoveryKey:(NSString* _Nullable)containerName
1187 contextID:(NSString *)contextID
1188 recoveryKey:(NSString*)recoveryKey
1189 reply:(void (^)(NSError * _Nullable))reply
1191 secnotice("octagon", "join with recovery key invoked for container: %@, context: %@", containerName, contextID);
1193 SFAnalyticsActivityTracker *tracker = [[self.loggerClass logger] startLogSystemMetricsForActivityNamed:OctagonActivityJoinWithRecoveryKey];
1195 CFErrorRef validateError = NULL;
1196 bool res = SecPasswordValidatePasswordFormat(kSecPasswordTypeiCloudRecoveryKey, (__bridge CFStringRef)recoveryKey, &validateError);
1198 NSError *validateErrorWrapper = nil;
1199 NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
1200 userInfo[NSLocalizedDescriptionKey] = @"malformed recovery key";
1202 userInfo[NSUnderlyingErrorKey] = CFBridgingRelease(validateError);
1205 validateErrorWrapper = [NSError errorWithDomain:OctagonErrorDomain code:OTErrorRecoveryKeyMalformed userInfo:userInfo];
1207 secerror("recovery failed validation with error:%@", validateError);
1209 [tracker stopWithEvent:OctagonEventJoinRecoveryKeyValidationFailed result:validateErrorWrapper];
1210 reply(validateErrorWrapper);
1214 OTCuttlefishContext* cfshContext = [self contextForContainerName:containerName contextID:contextID];
1216 [cfshContext startOctagonStateMachine];
1218 [cfshContext joinWithRecoveryKey:recoveryKey reply:^(NSError *error) {
1219 if ((error.code == TrustedPeersHelperErrorCodeNotEnrolled || error.code == TrustedPeersHelperErrorCodeUntrustedRecoveryKeys)
1220 && [error.domain isEqualToString:TrustedPeersHelperErrorDomain]){
1221 // If we hit either of these errors, let's reset and establish octagon then enroll this recovery key in the new circle
1222 secerror("octagon, recovery key is not enrolled in octagon, resetting octagon circle");
1223 [[self.loggerClass logger] logResultForEvent:OctagonEventJoinRecoveryKeyCircleReset hardFailure:NO result:error];
1225 [cfshContext rpcResetAndEstablish:CuttlefishResetReasonRecoveryKey reply:^(NSError *resetError) {
1227 secerror("octagon, failed to reset octagon");
1228 [tracker stopWithEvent:OctagonEventJoinRecoveryKeyCircleResetFailed result:resetError];
1232 // Now enroll the recovery key
1233 secnotice("octagon", "attempting enrolling recovery key");
1234 if (self.sosEnabledForPlatform) {
1235 [self createRecoveryKey:containerName contextID:contextID recoveryKey:recoveryKey reply:^(NSError *enrollError) {
1237 secerror("octagon, failed to enroll new recovery key: %@", enrollError);
1238 [tracker stopWithEvent:OctagonEventJoinRecoveryKeyEnrollFailed result:enrollError];
1242 secnotice("octagon", "successfully enrolled recovery key");
1243 [tracker stopWithEvent:OctagonEventRecoveryKey result:nil];
1249 secnotice("octagon", "Limited Peer, can't enroll recovery key");
1256 secerror("octagon, join with recovery key failed: %d", (int)[error code]);
1257 [tracker stopWithEvent:OctagonEventJoinRecoveryKeyFailed result:error];
1263 - (void)xpc24HrNotification
1265 secnotice("octagon-health", "Received 24hr xpc notification");
1267 [self healthCheck:OTCKContainerName context:OTDefaultContext skipRateLimitingCheck:NO reply:^(NSError * _Nullable error) {
1269 secerror("octagon: error attempting to check octagon health: %@", error);
1271 secnotice("octagon", "health check success");
1276 - (void)healthCheck:(NSString *)container context:(NSString *)context skipRateLimitingCheck:(BOOL)skipRateLimitingCheck reply:(void (^)(NSError *_Nullable error))reply
1278 OTCuttlefishContext* cfshContext = [self contextForContainerName:container contextID:context];
1280 secnotice("octagon", "notifying container of change");
1282 [cfshContext notifyContainerChange:nil];
1284 [cfshContext checkOctagonHealth:skipRateLimitingCheck reply:^(NSError *error) {
1293 - (void)setSOSEnabledForPlatformFlag:(bool) value
1295 self.sosEnabledForPlatform = value;
1298 - (void)allContextsHalt
1300 for(OTCuttlefishContext* context in self.contexts.allValues) {
1301 [context.stateMachine haltOperation];
1303 // Also, clear the viewManager strong pointer
1304 [context clearCKKSViewManager];
1308 - (void)allContextsDisablePendingFlags
1310 for(OTCuttlefishContext* context in self.contexts.allValues) {
1311 [context.stateMachine disablePendingFlags];
1315 - (bool)allContextsPause:(uint64_t)within
1317 for(OTCuttlefishContext* context in self.contexts.allValues) {
1318 if(context.stateMachine.currentState != OctagonStateMachineNotStarted) {
1319 if([context.stateMachine.paused wait:within] != 0) {
1327 - (void)attemptSosUpgrade:(NSString* _Nullable)containerName
1328 context:(NSString*)context
1329 reply:(void (^)(NSError* error))reply
1332 secnotice("octagon-sos", "Attempting sos upgrade");
1333 OTCuttlefishContext* cfshContext = [self contextForContainerName:containerName contextID:context];
1335 [cfshContext startOctagonStateMachine];
1337 [cfshContext attemptSOSUpgrade:^(NSError *error) {
1342 - (void)waitForOctagonUpgrade:(NSString* _Nullable)containerName
1343 context:(NSString*)context
1344 reply:(void (^)(NSError* error))reply
1347 secnotice("octagon-sos", "waitForOctagonUpgrade");
1348 OTCuttlefishContext* cfshContext = [self contextForContainerName:containerName contextID:context];
1350 [cfshContext startOctagonStateMachine];
1352 [cfshContext waitForOctagonUpgrade:^(NSError * _Nonnull error) {
1357 - (OTFollowupContextType)cliqueCDPTypeToFollowupContextType:(OTCliqueCDPContextType)type
1359 if ([type isEqualToString:OTCliqueCDPContextTypeNone]) {
1360 return OTFollowupContextTypeNone;
1361 } else if ([type isEqualToString:OTCliqueCDPContextTypeSignIn]) {
1362 return OTFollowupContextTypeNone;
1363 } else if ([type isEqualToString:OTCliqueCDPContextTypeRepair]) {
1364 return OTFollowupContextTypeStateRepair;
1365 } else if ([type isEqualToString:OTCliqueCDPContextTypeFinishPasscodeChange]) {
1366 return OTFollowupContextTypeOfflinePasscodeChange;
1367 } else if ([type isEqualToString:OTCliqueCDPContextTypeRecoveryKeyGenerate]) {
1368 return OTFollowupContextTypeRecoveryKeyRepair;
1369 } else if ([type isEqualToString:OTCliqueCDPContextTypeRecoveryKeyNew]) {
1370 return OTFollowupContextTypeRecoveryKeyRepair;
1371 } else if ([type isEqualToString:OTCliqueCDPContextTypeUpdatePasscode]) {
1372 return OTFollowupContextTypeNone;
1374 return OTFollowupContextTypeNone;
1378 - (void)postCDPFollowupResult:(BOOL)success
1379 type:(OTCliqueCDPContextType)type
1380 error:(NSError * _Nullable)error
1381 containerName:(NSString* _Nullable)containerName
1382 contextName:(NSString *)contextName
1383 reply:(void (^)(NSError *error))reply
1385 OTCuttlefishContext *cuttlefishContext = [self contextForContainerName:containerName contextID:contextName];
1387 NSString* metricName = [NSString stringWithFormat:@"%@%@", OctagonAnalyticsCDPStateRun, type];
1388 NSString* countName = [NSString stringWithFormat:@"%@%@Tries", OctagonAnalyticsCDPStateRun, type];
1390 [[CKKSAnalytics logger] logResultForEvent:metricName
1394 [[CKKSAnalytics logger] setDateProperty:[NSDate date] forKey:metricName];
1395 [[CKKSAnalytics logger] incrementIntegerPropertyForKey:countName];
1397 [[CKKSAnalytics logger] setDateProperty:NULL forKey:metricName];
1398 [[CKKSAnalytics logger] setNumberProperty:NULL forKey:countName];
1401 // Always return without error
1405 - (void)tapToRadar:(NSString *)action
1406 description:(NSString *)description
1407 radar:(NSString *)radar
1408 reply:(void (^)(NSError * _Nullable))reply
1410 SecTapToRadar *ttr = [[SecTapToRadar alloc] initTapToRadar:action description:description radar:radar];
1415 - (void)refetchCKKSPolicy:(NSString * _Nullable)containerName
1416 contextID:(nonnull NSString *)contextID
1417 reply:(nonnull void (^)(NSError * _Nullable))reply {
1418 secnotice("octagon-ckks", "refetch-ckks-policy");
1420 if(!containerName) {
1421 containerName = OTCKContainerName;
1424 OTCuttlefishContext* cfshContext = [self contextForContainerName:containerName contextID:contextID];
1427 reply([NSError errorWithDomain:OctagonErrorDomain
1428 code:OTErrorNoSuchContext
1429 description:[NSString stringWithFormat:@"No context for (%@,%@)", containerName, contextID]]);
1433 [cfshContext rpcRefetchCKKSPolicy:^(NSError* error) {
1434 secnotice("octagon-ckks", "refetch-ckks-policy result: %@", error ?: @"no error");
1440 - (void)getCDPStatus:(NSString * _Nullable)containerName
1441 contextID:(nonnull NSString *)contextID
1442 reply:(nonnull void (^)(OTCDPStatus, NSError * _Nullable))reply {
1443 secnotice("octagon-cdp", "get-cdp-status");
1445 if(!containerName) {
1446 containerName = OTCKContainerName;
1449 OTCuttlefishContext* cfshContext = [self contextForContainerName:containerName contextID:contextID];
1452 reply(OTCDPStatusUnknown, [NSError errorWithDomain:OctagonErrorDomain
1453 code:OTErrorNoSuchContext
1454 description:[NSString stringWithFormat:@"No context for (%@,%@)", containerName, contextID]]);
1458 NSError* error = nil;
1459 OTCDPStatus status = [cfshContext getCDPStatus:&error];
1461 reply(status, error);
1465 - (void)setCDPEnabled:(NSString * _Nullable)containerName
1466 contextID:(nonnull NSString *)contextID
1467 reply:(nonnull void (^)(NSError * _Nullable))reply {
1468 secnotice("octagon-cdp", "set-cdp-enabled");
1469 if(!containerName) {
1470 containerName = OTCKContainerName;
1473 OTCuttlefishContext* cfshContext = [self contextForContainerName:containerName contextID:contextID];
1476 reply([NSError errorWithDomain:OctagonErrorDomain
1477 code:OTErrorNoSuchContext
1478 description:[NSString stringWithFormat:@"No context for (%@,%@)", containerName, contextID]]);
1482 NSError* localError = nil;
1483 [cfshContext setCDPEnabled:&localError];