2 * Copyright (c) 2017 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 <sys/sysctl.h>
27 #import <os/feature_private.h>
29 #import <Security/Security.h>
31 #include <utilities/SecFileLocations.h>
32 #include <Security/SecRandomP.h>
33 #import <SecurityFoundation/SFKey_Private.h>
35 #import "keychain/TrustedPeersHelper/TrustedPeersHelperProtocol.h"
36 #import <TrustedPeers/TrustedPeers.h>
37 #import "keychain/ckks/CKKS.h"
38 #import "keychain/ckks/CKKSAnalytics.h"
39 #import "keychain/ckks/CKKSResultOperation.h"
41 #import "keychain/ckks/OctagonAPSReceiver.h"
42 #import "keychain/analytics/CKKSLaunchSequence.h"
43 #import "keychain/ot/OTDeviceInformationAdapter.h"
46 #import "keychain/ot/OctagonStateMachine.h"
47 #import "keychain/ot/OctagonStateMachineHelpers.h"
48 #import "keychain/ot/OctagonCKKSPeerAdapter.h"
49 #import "keychain/ot/OctagonCheckTrustStateOperation.h"
50 #import "keychain/ot/OTStates.h"
51 #import "keychain/ot/OTFollowup.h"
52 #import "keychain/ot/OTAuthKitAdapter.h"
53 #import "keychain/ot/OTConstants.h"
54 #import "keychain/ot/OTOperationDependencies.h"
55 #import "keychain/ot/OTClique.h"
56 #import "keychain/ot/OTCuttlefishContext.h"
57 #import "keychain/ot/OTPrepareOperation.h"
58 #import "keychain/ot/OTSOSAdapter.h"
59 #import "keychain/ot/OTSOSUpgradeOperation.h"
60 #import "keychain/ot/OTUpdateTPHOperation.h"
61 #import "keychain/ot/OTEpochOperation.h"
62 #import "keychain/ot/OTClientVoucherOperation.h"
63 #import "keychain/ot/OTLeaveCliqueOperation.h"
64 #import "keychain/ot/OTRemovePeersOperation.h"
65 #import "keychain/ot/OTJoinWithVoucherOperation.h"
66 #import "keychain/ot/OTVouchWithBottleOperation.h"
67 #import "keychain/ot/OTVouchWithRecoveryKeyOperation.h"
68 #import "keychain/ot/OTEstablishOperation.h"
69 #import "keychain/ot/OTLocalCKKSResetOperation.h"
70 #import "keychain/ot/OTUpdateTrustedDeviceListOperation.h"
71 #import "keychain/ot/OTSOSUpdatePreapprovalsOperation.h"
72 #import "keychain/ot/OTResetOperation.h"
73 #import "keychain/ot/OTLocalCuttlefishReset.h"
74 #import "keychain/ot/OTSetRecoveryKeyOperation.h"
75 #import "keychain/ot/OTResetCKKSZonesLackingTLKsOperation.h"
76 #import "keychain/ot/OTUploadNewCKKSTLKsOperation.h"
77 #import "keychain/ot/OTCuttlefishAccountStateHolder.h"
78 #import "keychain/ot/ObjCImprovements.h"
79 #import "keychain/ot/proto/generated_source/OTAccountMetadataClassC.h"
80 #import "keychain/ot/OTTriggerEscrowUpdateOperation.h"
81 #import "keychain/ot/OTCheckHealthOperation.h"
82 #import "keychain/ot/OTEnsureOctagonKeyConsistency.h"
83 #import "keychain/ot/OTDetermineHSA2AccountStatusOperation.h"
84 #import "keychain/ckks/CKKSAccountStateTracker.h"
85 #import "keychain/ckks/CloudKitCategories.h"
86 #import "keychain/escrowrequest/EscrowRequestServer.h"
89 #import "keychain/otpaird/OTPairingClient.h"
90 #endif /* TARGET_OS_WATCH */
92 #import "keychain/ckks/CKKSViewManager.h"
93 #import "keychain/ckks/CKKSKeychainView.h"
95 #import "utilities/SecTapToRadar.h"
97 #import "keychain/categories/NSError+UsefulConstructors.h"
98 #import <CoreCDP/CDPAccount.h>
101 NSString* OTCuttlefishContextErrorDomain = @"otcuttlefish";
102 static dispatch_time_t OctagonStateTransitionDefaultTimeout = 10*NSEC_PER_SEC;
104 @class CKKSLockStateTracker;
106 @interface OTCuttlefishContext () <OTCuttlefishAccountStateHolderNotifier>
111 NSString* _bottleSalt;
113 NSArray<NSData*>* _preapprovedKeys;
114 NSString* _recoveryKey;
115 CuttlefishResetReason _resetReason;
116 BOOL _skipRateLimitingCheck;
119 @property CKKSLaunchSequence* launchSequence;
120 @property NSOperationQueue* operationQueue;
121 @property (nonatomic, strong) OTCuttlefishAccountStateHolder *accountMetadataStore;
122 @property OTFollowup *followupHandler;
124 @property (readonly) id<CKKSCloudKitAccountStateTrackingProvider, CKKSOctagonStatusMemoizer> accountStateTracker;
125 @property CKAccountInfo* cloudKitAccountInfo;
126 @property CKKSCondition *cloudKitAccountStateKnown;
128 @property BOOL getViewsSuccess;
130 @property CKKSNearFutureScheduler* suggestTLKUploadNotifier;
132 // Dependencies (for injection)
133 @property id<OTSOSAdapter> sosAdapter;
134 @property id<CKKSPeerProvider> octagonAdapter;
135 @property id<OTDeviceInformationAdapter> deviceAdapter;
136 @property (readonly) Class<OctagonAPSConnection> apsConnectionClass;
137 @property (readonly) Class<SecEscrowRequestable> escrowRequestClass;
139 @property (nonatomic) BOOL postedRepairCFU;
140 @property (nonatomic) BOOL postedEscrowRepairCFU;
141 @property (nonatomic) BOOL postedRecoveryKeyCFU;
143 @property (nonatomic) BOOL initialBecomeUntrustedPosted;
147 @implementation OTCuttlefishContext
149 - (instancetype)initWithContainerName:(NSString*)containerName
150 contextID:(NSString*)contextID
151 cuttlefish:(id<NSXPCProxyCreating>)cuttlefish
152 sosAdapter:(id<OTSOSAdapter>)sosAdapter
153 authKitAdapter:(id<OTAuthKitAdapter>)authKitAdapter
154 ckksViewManager:(CKKSViewManager* _Nullable)viewManager
155 lockStateTracker:(CKKSLockStateTracker*)lockStateTracker
156 accountStateTracker:(id<CKKSCloudKitAccountStateTrackingProvider, CKKSOctagonStatusMemoizer>)accountStateTracker
157 deviceInformationAdapter:(id<OTDeviceInformationAdapter>)deviceInformationAdapter
158 apsConnectionClass:(Class<OctagonAPSConnection>)apsConnectionClass
159 escrowRequestClass:(Class<SecEscrowRequestable>)escrowRequestClass
160 cdpd:(id<OctagonFollowUpControllerProtocol>)cdpd
162 if ((self = [super init])) {
165 _containerName = containerName;
166 _contextID = contextID;
168 _viewManager = viewManager;
169 _postedRepairCFU = NO;
170 _postedRecoveryKeyCFU = NO;
171 _postedEscrowRepairCFU = NO;
173 _initialBecomeUntrustedPosted = NO;
175 _apsConnectionClass = apsConnectionClass;
176 _launchSequence = [[CKKSLaunchSequence alloc] initWithRocketName:@"com.apple.octagon.launch"];
178 _queue = dispatch_queue_create("com.apple.security.otcuttlefishcontext", DISPATCH_QUEUE_SERIAL);
179 _operationQueue = [[NSOperationQueue alloc] init];
180 _cloudKitAccountStateKnown = [[CKKSCondition alloc] init];
182 _accountMetadataStore = [[OTCuttlefishAccountStateHolder alloc] initWithQueue:_queue
183 container:_containerName
185 [_accountMetadataStore registerNotification:self];
187 _stateMachine = [[OctagonStateMachine alloc] initWithName:@"octagon"
188 states:[NSSet setWithArray:[OctagonStateMap() allKeys]]
189 flags:AllOctagonFlags()
190 initialState:OctagonStateInitializing
193 lockStateTracker:lockStateTracker];
195 _sosAdapter = sosAdapter;
196 [_sosAdapter registerForPeerChangeUpdates:self];
197 _authKitAdapter = authKitAdapter;
198 _deviceAdapter = deviceInformationAdapter;
199 [_deviceAdapter registerForDeviceNameUpdates:self];
201 _cuttlefishXPCWrapper = [[CuttlefishXPCWrapper alloc] initWithCuttlefishXPCConnection:cuttlefish];
202 _lockStateTracker = lockStateTracker;
203 _accountStateTracker = accountStateTracker;
205 _followupHandler = [[OTFollowup alloc] initWithFollowupController:cdpd];
207 [accountStateTracker registerForNotificationsOfCloudKitAccountStatusChange:self];
208 [_authKitAdapter registerNotification:self];
210 _escrowRequestClass = escrowRequestClass;
212 _suggestTLKUploadNotifier = [[CKKSNearFutureScheduler alloc] initWithName:@"octagon-tlk-request"
213 delay:500*NSEC_PER_MSEC
214 keepProcessAlive:false
215 dependencyDescriptionCode:0
218 secnotice("octagon-ckks", "Adding flag for CKKS TLK upload");
219 [self.stateMachine handleFlag:OctagonFlagCKKSRequestsTLKUpload];
227 // TODO: how to invalidate this?
228 //[self.cuttlefishXPCWrapper invalidate];
231 - (void)notifyTrustChanged:(OTAccountMetadataClassC_TrustState)trustState {
233 secnotice("octagon", "Changing trust status to: %@",
234 (trustState == OTAccountMetadataClassC_TrustState_TRUSTED) ? @"Trusted" : @"Untrusted");
237 * We are posting the legacy SOS notification if we don't use SOS
238 * need to rework clients to use a new signal instead of SOS.
240 if (!OctagonPlatformSupportsSOS()) {
241 notify_post(kSOSCCCircleChangedNotification);
244 notify_post(OTTrustStatusChangeNotification);
247 - (void)accountStateUpdated:(OTAccountMetadataClassC*)newState from:(OTAccountMetadataClassC *)oldState
249 if (newState.icloudAccountState == OTAccountMetadataClassC_AccountState_ACCOUNT_AVAILABLE && oldState.icloudAccountState != OTAccountMetadataClassC_AccountState_ACCOUNT_AVAILABLE) {
250 [self.launchSequence addEvent:@"iCloudAccount"];
253 if (newState.trustState == OTAccountMetadataClassC_TrustState_TRUSTED && oldState.trustState != OTAccountMetadataClassC_TrustState_TRUSTED) {
254 [self.launchSequence addEvent:@"Trusted"];
256 if (newState.trustState != OTAccountMetadataClassC_TrustState_TRUSTED && oldState.trustState == OTAccountMetadataClassC_TrustState_TRUSTED) {
257 [self.launchSequence addEvent:@"Untrusted"];
258 [self notifyTrustChanged:newState.trustState];
262 - (NSString*)description
264 return [NSString stringWithFormat:@"<OTCuttlefishContext: %@, %@>", self.containerName, self.contextID];
267 - (void)machinesAdded:(NSArray<NSString*>*)machineIDs altDSID:(NSString*)altDSID
270 NSError* metadataError = nil;
271 OTAccountMetadataClassC* accountMetadata = [self.accountMetadataStore loadOrCreateAccountMetadata:&metadataError];
273 if(!accountMetadata || metadataError) {
274 // TODO: collect a sysdiagnose here if the error is not "device is in class D"
275 secerror("octagon-authkit: Unable to load account metadata: %@", metadataError);
276 [self requestTrustedDeviceListRefresh];
280 if(!altDSID || ![accountMetadata.altDSID isEqualToString:altDSID]) {
281 secnotice("octagon-authkit", "Machines-added push is for wrong altDSID (%@); current altDSID (%@)", altDSID, accountMetadata.altDSID);
285 secnotice("octagon-authkit", "adding machines for altDSID(%@): %@", altDSID, machineIDs);
287 [self.cuttlefishXPCWrapper addAllowedMachineIDsWithContainer:self.containerName
288 context:self.contextID
289 machineIDs:machineIDs
290 reply:^(NSError* error) {
293 secerror("octagon-authkit: addAllow errored: %@", error);
294 [self requestTrustedDeviceListRefresh];
296 secnotice("octagon-authkit", "addAllow succeeded");
298 OctagonPendingFlag *pendingFlag = [[OctagonPendingFlag alloc] initWithFlag:OctagonFlagCuttlefishNotification
299 conditions:OctagonPendingConditionsDeviceUnlocked];
300 [self.stateMachine handlePendingFlag:pendingFlag];
305 - (void)machinesRemoved:(NSArray<NSString*>*)machineIDs altDSID:(NSString*)altDSID
309 NSError* metadataError = nil;
310 OTAccountMetadataClassC* accountMetadata = [self.accountMetadataStore loadOrCreateAccountMetadata:&metadataError];
312 if(!accountMetadata || metadataError) {
313 // TODO: collect a sysdiagnose here if the error is not "device is in class D"
314 secerror("octagon-authkit: Unable to load account metadata: %@", metadataError);
315 [self requestTrustedDeviceListRefresh];
319 if(!altDSID || ![accountMetadata.altDSID isEqualToString:altDSID]) {
320 secnotice("octagon-authkit", "Machines-removed push is for wrong altDSID (%@); current altDSID (%@)", altDSID, accountMetadata.altDSID);
324 secnotice("octagon-authkit", "removing machines for altDSID(%@): %@", altDSID, machineIDs);
326 [self.cuttlefishXPCWrapper removeAllowedMachineIDsWithContainer:self.containerName
327 context:self.contextID
328 machineIDs:machineIDs
329 reply:^(NSError* _Nullable error) {
332 secerror("octagon-authkit: removeAllow errored: %@", error);
334 secnotice("octagon-authkit", "removeAllow succeeded");
337 // We don't necessarily trust remove pushes; they could be delayed past when an add has occurred.
338 // Request that the full list be rechecked.
339 [self requestTrustedDeviceListRefresh];
343 - (void)incompleteNotificationOfMachineIDListChange
345 secnotice("octagon", "incomplete machine ID list notification -- refreshing device list");
346 [self requestTrustedDeviceListRefresh];
350 - (void)cloudkitAccountStateChange:(CKAccountInfo* _Nullable)oldAccountInfo
351 to:(CKAccountInfo*)currentAccountInfo
353 dispatch_sync(self.queue, ^{
354 // We don't persist the CK account state; rather, we fetch it anew on every daemon launch.
355 // But, we also have to integrate it into our asynchronous state machine.
356 // So, record the current CK account value, and trigger state machine reprocessing.
358 secnotice("octagon", "Told of a new CK account status: %@", currentAccountInfo);
359 self.cloudKitAccountInfo = currentAccountInfo;
360 [self.stateMachine _onqueuePokeStateMachine];
362 // But, having the state machine perform the signout is confusing: it would need to make decisions based
363 // on things other than the current state. So, use the RPC mechanism to give it input.
364 // If we receive a sign-in before the sign-out rpc runs, the state machine will be sufficient to get back into
365 // the in-account state.
367 // Also let other clients now that we have CK account status
368 [self.cloudKitAccountStateKnown fulfill];
371 if(!(currentAccountInfo.accountStatus == CKAccountStatusAvailable)) {
372 secnotice("octagon", "Informed that the CK account is now unavailable: %@", currentAccountInfo);
374 // Add a state machine request to return to OctagonStateWaitingForCloudKitAccount
375 [self.stateMachine doSimpleStateMachineRPC:@"cloudkit-account-gone"
376 op:[OctagonStateTransitionOperation named:@"cloudkit-account-gone"
377 entering:OctagonStateWaitingForCloudKitAccount]
378 sourceStates:OctagonInAccountStates()
379 reply:^(NSError* error) {}];
383 - (BOOL)accountAvailable:(NSString*)altDSID error:(NSError**)error
385 secnotice("octagon", "Account available with altDSID: %@ %@", altDSID, self);
387 self.launchSequence.firstLaunch = true;
389 NSError* localError = nil;
390 [self.accountMetadataStore persistAccountChanges:^OTAccountMetadataClassC * _Nonnull(OTAccountMetadataClassC * _Nonnull metadata) {
391 // Do not set the account available bit here, since we need to check if it's HSA2. The initializing state should do that for us...
392 metadata.altDSID = altDSID;
395 } error:&localError];
398 secerror("octagon: unable to persist new account availability: %@", localError);
401 [self.stateMachine handleFlag:OctagonFlagAccountIsAvailable];
411 - (void) moveToCheckTrustedState
413 CKKSResultOperation<OctagonStateTransitionOperationProtocol>* checkTrust
414 = [OctagonStateTransitionOperation named:[NSString stringWithFormat:@"%@", @"check-trust-state"]
415 entering:OctagonStateCheckTrustState];
417 NSSet* sourceStates = [NSSet setWithArray: @[OctagonStateUntrusted]];
419 OctagonStateTransitionRequest<OctagonStateTransitionOperation*>* request = [[OctagonStateTransitionRequest alloc] init:@"check-trust-state"
420 sourceStates:sourceStates
421 serialQueue:self.queue
422 timeout:OctagonStateTransitionDefaultTimeout
423 transitionOp:checkTrust];
424 [self.stateMachine handleExternalRequest:request];
428 - (BOOL)idmsTrustLevelChanged:(NSError**)error
430 [self.stateMachine handleFlag:OctagonFlagIDMSLevelChanged];
434 - (BOOL)accountNoLongerAvailable:(NSError**)error
436 OctagonStateTransitionOperation* attemptOp = [OctagonStateTransitionOperation named:@"octagon-account-gone"
437 intending:OctagonStateNoAccountDoReset
438 errorState:OctagonStateNoAccountDoReset
439 withBlockTakingSelf:^(OctagonStateTransitionOperation * _Nonnull op) {
440 __block NSError* localError = nil;
442 secnotice("octagon", "Account now unavailable: %@", self);
443 [self.accountMetadataStore persistAccountChanges:^OTAccountMetadataClassC *(OTAccountMetadataClassC * metadata) {
444 metadata.icloudAccountState = OTAccountMetadataClassC_AccountState_NO_ACCOUNT;
445 metadata.altDSID = nil;
446 metadata.trustState = OTAccountMetadataClassC_TrustState_UNKNOWN;
449 } error:&localError];
452 secerror("octagon: unable to persist new account availability: %@", localError);
455 [self.accountStateTracker setHSA2iCloudAccountStatus:CKKSAccountStatusNoAccount];
457 // Bring CKKS down, too
458 for (id key in self.viewManager.views) {
459 CKKSKeychainView* view = self.viewManager.views[key];
460 secnotice("octagon-ckks", "Informing %@ of new untrusted status (due to account disappearance)", view);
461 [view endTrustedOperation];
464 op.error = localError;
467 // Signout works from literally any state. Goodbye, account!
468 NSSet* sourceStates = [NSSet setWithArray: OctagonStateMap().allKeys];
469 OctagonStateTransitionRequest<OctagonStateTransitionOperation*>* request = [[OctagonStateTransitionRequest alloc] init:@"account-not-available"
470 sourceStates:sourceStates
471 serialQueue:self.queue
472 timeout:OctagonStateTransitionDefaultTimeout
473 transitionOp:attemptOp];
474 [self.stateMachine handleExternalRequest:request];
479 - (void)resetOctagonStateMachine
481 OctagonStateTransitionOperation* op = [OctagonStateTransitionOperation named:@"resetting-state-machine"
482 entering:OctagonStateInitializing];
483 NSMutableSet* sourceStates = [NSMutableSet setWithArray: OctagonStateMap().allKeys];
485 OctagonStateTransitionRequest<OctagonStateTransitionOperation*>* request = [[OctagonStateTransitionRequest alloc] init:@"resetting-state-machine"
486 sourceStates:sourceStates
487 serialQueue:self.queue
488 timeout:OctagonStateTransitionDefaultTimeout
491 [self.stateMachine handleExternalRequest:request];
495 - (void)localReset:(nonnull void (^)(NSError * _Nullable))reply
497 OTLocalResetOperation* pendingOp = [[OTLocalResetOperation alloc] init:self.containerName
498 contextID:self.contextID
499 intendedState:OctagonStateBecomeUntrusted
500 errorState:OctagonStateError
501 cuttlefishXPCWrapper:self.cuttlefishXPCWrapper];
503 NSMutableSet* sourceStates = [NSMutableSet setWithArray: OctagonStateMap().allKeys];
504 [self.stateMachine doSimpleStateMachineRPC:@"local-reset" op:pendingOp sourceStates:sourceStates reply:reply];
507 - (NSDictionary*)establishStatePathDictionary
510 OctagonStateReEnactDeviceList: @{
511 OctagonStateReEnactPrepare: @{
512 OctagonStateReEnactReadyToEstablish: @{
513 OctagonStateEscrowTriggerUpdate: @{
514 OctagonStateBecomeReady: @{
515 OctagonStateReady: [OctagonStateTransitionPathStep success],
519 // Error handling extra states:
520 OctagonStateEstablishCKKSReset: @{
521 OctagonStateEstablishAfterCKKSReset: @{
522 OctagonStateEscrowTriggerUpdate: @{
523 OctagonStateBecomeReady: @{
524 OctagonStateReady: [OctagonStateTransitionPathStep success],
535 - (void)rpcEstablish:(nonnull NSString *)altDSID
536 reply:(nonnull void (^)(NSError * _Nullable))reply
538 // The reset flow can split into an error-handling path halfway through; this is okay
539 OctagonStateTransitionPath* path = [OctagonStateTransitionPath pathFromDictionary:[self establishStatePathDictionary]];
541 [self.stateMachine doWatchedStateMachineRPC:@"establish"
542 sourceStates:OctagonInAccountStates()
547 - (void)rpcResetAndEstablish:(CuttlefishResetReason)resetReason reply:(nonnull void (^)(NSError * _Nullable))reply
549 _resetReason = resetReason;
551 // The reset flow can split into an error-handling path halfway through; this is okay
552 OctagonStateTransitionPath* path = [OctagonStateTransitionPath pathFromDictionary: @{
553 OctagonStateResetBecomeUntrusted: @{
554 OctagonStateResetAndEstablish: @{
555 OctagonStateResetAnyMissingTLKCKKSViews: [self establishStatePathDictionary]
560 // Now, take the state machine from any in-account state to the beginning of the reset flow.
561 [self.stateMachine doWatchedStateMachineRPC:@"rpc-reset-and-establish"
562 sourceStates:OctagonInAccountStates()
567 - (void)rpcLeaveClique:(nonnull void (^)(NSError * _Nullable))reply
569 OTLeaveCliqueOperation* op = [[OTLeaveCliqueOperation alloc] initWithDependencies:self.operationDependencies
570 intendedState:OctagonStateBecomeUntrusted
571 errorState:OctagonStateError];
573 NSSet* sourceStates = [NSSet setWithObject: OctagonStateReady];
574 [self.stateMachine doSimpleStateMachineRPC:@"leave-clique" op:op sourceStates:sourceStates reply:reply];
577 - (void)rpcRemoveFriendsInClique:(NSArray<NSString*>*)peerIDs
578 reply:(void (^)(NSError * _Nullable))reply
580 OTRemovePeersOperation* op = [[OTRemovePeersOperation alloc] initWithDependencies:self.operationDependencies
581 intendedState:OctagonStateBecomeReady
582 errorState:OctagonStateBecomeReady
585 NSSet* sourceStates = [NSSet setWithObject: OctagonStateReady];
586 [self.stateMachine doSimpleStateMachineRPC:@"remove-friends" op:op sourceStates:sourceStates reply:reply];
589 - (OTDeviceInformation*)prepareInformation
591 NSError* error = nil;
592 NSString* machineID = [self.authKitAdapter machineID:&error];
594 if(!machineID || error) {
595 secerror("octagon: Unable to fetch machine ID; expect signin to fail: %@", error);
598 return [[OTDeviceInformation alloc] initForContainerName:self.containerName
599 contextID:self.contextID
602 modelID:self.deviceAdapter.modelID
603 deviceName:self.deviceAdapter.deviceName
604 serialNumber:self.deviceAdapter.serialNumber
605 osVersion:self.deviceAdapter.osVersion];
608 - (OTOperationDependencies*)operationDependencies
610 return [[OTOperationDependencies alloc] initForContainer:self.containerName
611 contextID:self.contextID
612 stateHolder:self.accountMetadataStore
613 flagHandler:self.stateMachine
614 sosAdapter:self.sosAdapter
615 octagonAdapter:self.octagonAdapter
616 authKitAdapter:self.authKitAdapter
617 deviceInfoAdapter:self.deviceAdapter
618 viewManager:self.viewManager
619 lockStateTracker:self.lockStateTracker
620 cuttlefishXPCWrapper:self.cuttlefishXPCWrapper
621 escrowRequestClass:self.escrowRequestClass];
624 - (void)startOctagonStateMachine
626 [self.stateMachine startOperation];
629 - (void)handlePairingRestart:(OTJoiningConfiguration*)config
631 if(self.pairingUUID == nil){
632 secnotice("octagon-pairing", "received new pairing UUID (%@)", config.pairingUUID);
633 self.pairingUUID = config.pairingUUID;
636 if(![self.pairingUUID isEqualToString:config.pairingUUID]){
637 secnotice("octagon-pairing", "current pairing UUID (%@) does not match config UUID (%@)", self.pairingUUID, config.pairingUUID);
639 dispatch_semaphore_t sema = dispatch_semaphore_create(0);
640 [self localReset:^(NSError * _Nullable localResetError) {
641 if(localResetError) {
642 secerror("localReset returned an error: %@", localResetError);
644 secnotice("octagon", "localReset succeeded");
645 self.pairingUUID = config.pairingUUID;
647 dispatch_semaphore_signal(sema);
649 if (0 != dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 10))) {
650 secerror("octagon: Timed out waiting for local reset to complete");
655 #pragma mark --- State Machine Transitions
657 - (CKKSResultOperation<OctagonStateTransitionOperationProtocol>* _Nullable)_onqueueNextStateMachineTransition:(OctagonState*)currentState
658 flags:(nonnull OctagonFlags *)flags
659 pendingFlags:(nonnull id<OctagonStateOnqueuePendingFlagHandler>)pendingFlagHandler
661 dispatch_assert_queue(self.queue);
665 [self.launchSequence addEvent:currentState];
667 // If We're initializing, or there was some recent update to the account state,
668 // attempt to see what state we should enter.
669 if([currentState isEqualToString: OctagonStateInitializing]) {
670 return [self initializingOperation];
673 if([currentState isEqualToString:OctagonStateWaitForHSA2]) {
674 if([flags _onqueueContains:OctagonFlagIDMSLevelChanged]) {
675 [flags _onqueueRemoveFlag:OctagonFlagIDMSLevelChanged];
676 return [OctagonStateTransitionOperation named:@"hsa2-check"
677 entering:OctagonStateDetermineiCloudAccountState];
680 secnotice("octagon", "Waiting for an HSA2 account");
684 if([currentState isEqualToString:OctagonStateWaitingForCloudKitAccount]) {
685 // Here, integrate the memoized CK account state into our state machine
686 if(self.cloudKitAccountInfo && self.cloudKitAccountInfo.accountStatus == CKAccountStatusAvailable) {
687 secnotice("octagon", "CloudKit reports an account is available!");
688 return [OctagonStateTransitionOperation named:@"ck-available"
689 entering:OctagonStateCloudKitNewlyAvailable];
691 secnotice("octagon", "Waiting for a CloudKit account; current state is %@", self.cloudKitAccountInfo ?: @"uninitialized");
696 if([currentState isEqualToString:OctagonStateCloudKitNewlyAvailable]) {
697 return [self cloudKitAccountNewlyAvailableOperation];
700 if([currentState isEqualToString:OctagonStateCheckTrustState]) {
701 return [[OctagonCheckTrustStateOperation alloc] initWithDependencies:self.operationDependencies
702 intendedState:OctagonStateBecomeUntrusted
703 errorState:OctagonStateBecomeUntrusted];
705 #pragma mark --- Octagon Health Check States
706 if([currentState isEqualToString:OctagonStateHSA2HealthCheck]) {
707 return [[OTDetermineHSA2AccountStatusOperation alloc] initWithDependencies:self.operationDependencies
708 stateIfHSA2:OctagonStateSecurityTrustCheck
709 stateIfNotHSA2:OctagonStateWaitForHSA2
710 stateIfNoAccount:OctagonStateNoAccount
711 errorState:OctagonStateError];
714 if([currentState isEqualToString:OctagonStateSecurityTrustCheck]) {
715 return [self evaluateSecdOctagonTrust];
718 if([currentState isEqualToString:OctagonStateTPHTrustCheck]) {
719 return [self evaluateTPHOctagonTrust];
722 if([currentState isEqualToString:OctagonStateCuttlefishTrustCheck]) {
723 return [self cuttlefishTrustEvaluation];
726 if ([currentState isEqualToString:OctagonStatePostRepairCFU]) {
727 return [self postRepairCFUAndBecomeUntrusted];
730 if ([currentState isEqualToString:OctagonStateHealthCheckReset]) {
731 // A small violation of state machines...
732 _resetReason = CuttlefishResetReasonHealthCheck;
733 return [OctagonStateTransitionOperation named:@"begin-reset"
734 entering:OctagonStateResetBecomeUntrusted];
737 #pragma mark --- Watch Pairing States
740 if([currentState isEqualToString:OctagonStateStartCompanionPairing]) {
741 return [self startCompanionPairingOperation];
743 #endif /* TARGET_OS_WATCH */
745 if([currentState isEqualToString:OctagonStateBecomeUntrusted]) {
746 return [self becomeUntrustedOperation:OctagonStateUntrusted];
749 if([currentState isEqualToString:OctagonStateBecomeReady]) {
750 return [self becomeReadyOperation];
753 if([currentState isEqualToString:OctagonStateNoAccount]) {
754 // We only want to move out of untrusted if something useful has happened!
755 if([flags _onqueueContains:OctagonFlagAccountIsAvailable]) {
756 [flags _onqueueRemoveFlag:OctagonFlagAccountIsAvailable];
757 secnotice("octagon", "Account is available! Attempting initializing op!");
758 return [OctagonStateTransitionOperation named:@"account-probably-present"
759 entering:OctagonStateInitializing];
763 if([currentState isEqualToString:OctagonStateUntrusted]) {
764 // We only want to move out of untrusted if something useful has happened!
765 if([flags _onqueueContains:OctagonFlagEgoPeerPreapproved]) {
766 [flags _onqueueRemoveFlag:OctagonFlagEgoPeerPreapproved];
767 if(self.sosAdapter.sosEnabled) {
768 secnotice("octagon", "Preapproved flag is high. Attempt SOS upgrade again!");
769 return [OctagonStateTransitionOperation named:@"ck-available"
770 entering:OctagonStateAttemptSOSUpgrade];
773 secnotice("octagon", "We are untrusted, but it seems someone preapproves us now. Unfortunately, this platform doesn't support SOS.");
777 if([flags _onqueueContains:OctagonFlagAttemptSOSUpgrade]) {
778 [flags _onqueueRemoveFlag:OctagonFlagAttemptSOSUpgrade];
779 if(self.sosAdapter.sosEnabled) {
780 secnotice("octagon", "Attempt SOS upgrade again!");
781 return [OctagonStateTransitionOperation named:@"attempt-sos-upgrade"
782 entering:OctagonStateAttemptSOSUpgrade];
785 secnotice("octagon", "We are untrusted, but this platform doesn't support SOS.");
789 if([flags _onqueueContains:OctagonFlagCuttlefishNotification]) {
790 [flags _onqueueRemoveFlag:OctagonFlagCuttlefishNotification];
791 secnotice("octagon", "Updating TPH (while untrusted) due to push");
792 return [OctagonStateTransitionOperation named:@"untrusted-update"
793 entering:OctagonStateUntrustedUpdated];
796 // We're untrusted; no need for the IDMS level flag anymore
797 if([flags _onqueueContains:OctagonFlagIDMSLevelChanged]) {
798 [flags _onqueueRemoveFlag:OctagonFlagIDMSLevelChanged];
802 if([currentState isEqualToString:OctagonStateUntrustedUpdated]) {
803 return [[OTUpdateTPHOperation alloc] initWithDependencies:self.operationDependencies
804 intendedState:OctagonStateUntrusted
805 errorState:OctagonStateError
806 retryFlag:OctagonFlagCuttlefishNotification];
809 if([currentState isEqualToString:OctagonStateDetermineiCloudAccountState]) {
810 secnotice("octagon", "Determine iCloud account status");
812 // TODO replace with OTDetermineHSA2AccountStatusOperation in <rdar://problem/54094162> Octagon: ensure Octagon operations can't occur on SA accounts
813 return [OctagonStateTransitionOperation named:@"octagon-determine-icloud-state"
814 intending:OctagonStateNoAccount
815 errorState:OctagonStateError
816 withBlockTakingSelf:^(OctagonStateTransitionOperation * _Nonnull op) {
819 NSError *authKitError = nil;
820 NSString* primaryAccountAltDSID = [self.authKitAdapter primaryiCloudAccountAltDSID:&authKitError];
822 dispatch_sync(self.queue, ^{
823 NSError* error = nil;
825 if(primaryAccountAltDSID != nil) {
826 secnotice("octagon", "iCloud account is present; checking HSA2 status");
828 bool hsa2 = [self.authKitAdapter accountIsHSA2ByAltDSID:primaryAccountAltDSID];
829 secnotice("octagon", "HSA2 is %@", hsa2 ? @"enabled" : @"disabled");
831 [self.accountMetadataStore _onqueuePersistAccountChanges:^OTAccountMetadataClassC *(OTAccountMetadataClassC * metadata) {
833 metadata.icloudAccountState = OTAccountMetadataClassC_AccountState_ACCOUNT_AVAILABLE;
835 metadata.icloudAccountState = OTAccountMetadataClassC_AccountState_NO_ACCOUNT;
837 metadata.altDSID = primaryAccountAltDSID;
841 // If there's an HSA2 account, return to 'initializing' here, as we want to centralize decisions on what to do next
843 op.nextState = OctagonStateInitializing;
845 [self.accountStateTracker setHSA2iCloudAccountStatus:CKKSAccountStatusNoAccount];
846 op.nextState = OctagonStateWaitForHSA2;
850 secnotice("octagon", "iCloud account is not present: %@", authKitError);
852 [self.accountMetadataStore _onqueuePersistAccountChanges:^OTAccountMetadataClassC *(OTAccountMetadataClassC * metadata) {
853 metadata.icloudAccountState = OTAccountMetadataClassC_AccountState_NO_ACCOUNT;
854 metadata.altDSID = nil;
858 op.nextState = OctagonStateNoAccount;
862 secerror("octagon: unable to save new account state: %@", error);
868 if([currentState isEqualToString:OctagonStateNoAccountDoReset]) {
869 secnotice("octagon", "Attempting local-reset as part of signout");
870 return [[OTLocalResetOperation alloc] init:self.containerName
871 contextID:self.contextID
872 intendedState:OctagonStateNoAccount
873 errorState:OctagonStateNoAccount
874 cuttlefishXPCWrapper:self.cuttlefishXPCWrapper];
877 if([currentState isEqualToString:OctagonStateEnsureConsistency]) {
878 secnotice("octagon", "Ensuring consistency of things that might've changed");
879 if(self.sosAdapter.sosEnabled) {
880 return [[OTEnsureOctagonKeyConsistency alloc] initWithDependencies:self.operationDependencies
881 intendedState:OctagonStateEnsureUpdatePreapprovals
882 errorState:OctagonStateBecomeReady];
885 // Add further consistency checks here.
886 return [OctagonStateTransitionOperation named:@"no-consistency-checks"
887 entering:OctagonStateBecomeReady];
890 if([currentState isEqualToString:OctagonStateEnsureUpdatePreapprovals]) {
891 secnotice("octagon", "SOS is enabled; ensuring preapprovals are correct");
892 return [[OTSOSUpdatePreapprovalsOperation alloc] initWithDependencies:self.operationDependencies
893 intendedState:OctagonStateBecomeReady
894 sosNotPresentState:OctagonStateBecomeReady
895 errorState:OctagonStateBecomeReady];
898 if([currentState isEqualToString:OctagonStateAttemptSOSUpgrade] && OctagonPerformSOSUpgrade()) {
899 secnotice("octagon", "Investigating SOS status");
900 return [[OTSOSUpgradeOperation alloc] initWithDependencies:self.operationDependencies
901 intendedState:OctagonStateBecomeReady
902 ckksConflictState:OctagonStateSOSUpgradeCKKSReset
903 errorState:OctagonStateBecomeUntrusted
904 deviceInfo:self.prepareInformation];
906 } else if([currentState isEqualToString:OctagonStateSOSUpgradeCKKSReset]) {
907 return [[OTLocalCKKSResetOperation alloc] initWithDependencies:self.operationDependencies
908 intendedState:OctagonStateSOSUpgradeAfterCKKSReset
909 errorState:OctagonStateBecomeUntrusted];
911 } else if([currentState isEqualToString:OctagonStateSOSUpgradeAfterCKKSReset]) {
912 return [[OTSOSUpgradeOperation alloc] initWithDependencies:self.operationDependencies
913 intendedState:OctagonStateBecomeReady
914 ckksConflictState:OctagonStateBecomeUntrusted
915 errorState:OctagonStateBecomeUntrusted
916 deviceInfo:self.prepareInformation];
919 } else if([currentState isEqualToString:OctagonStateCreateIdentityForRecoveryKey]) {
920 OTPrepareOperation* op = [[OTPrepareOperation alloc] initWithDependencies:self.operationDependencies
921 intendedState:OctagonStateVouchWithRecoveryKey
922 errorState:OctagonStateBecomeUntrusted
923 deviceInfo:[self prepareInformation]
927 } else if([currentState isEqualToString:OctagonStateInitiatorCreateIdentity]) {
928 OTPrepareOperation* op = [[OTPrepareOperation alloc] initWithDependencies:self.operationDependencies
929 intendedState:OctagonStateInitiatorVouchWithBottle
930 errorState:OctagonStateBecomeUntrusted
931 deviceInfo:[self prepareInformation]
935 } else if([currentState isEqualToString:OctagonStateInitiatorVouchWithBottle]) {
936 OTVouchWithBottleOperation* pendingOp = [[OTVouchWithBottleOperation alloc] initWithDependencies:self.operationDependencies
937 intendedState:OctagonStateInitiatorUpdateDeviceList
938 errorState:OctagonStateBecomeUntrusted
941 bottleSalt:_bottleSalt];
943 CKKSResultOperation* callback = [CKKSResultOperation named:@"vouchWithBottle-callback"
945 secnotice("otrpc", "Returning a vouch with bottle call: %@, %@ %@", pendingOp.voucher, pendingOp.voucherSig, pendingOp.error);
946 self->_vouchSig = pendingOp.voucherSig;
947 self->_vouchData = pendingOp.voucher;
949 [callback addDependency:pendingOp];
950 [self.operationQueue addOperation: callback];
954 } else if([currentState isEqualToString:OctagonStateVouchWithRecoveryKey]) {
955 OTVouchWithRecoveryKeyOperation* pendingOp = [[OTVouchWithRecoveryKeyOperation alloc] initWithDependencies:self.operationDependencies
956 intendedState:OctagonStateInitiatorUpdateDeviceList
957 errorState:OctagonStateBecomeUntrusted
958 recoveryKey:_recoveryKey];
960 CKKSResultOperation* callback = [CKKSResultOperation named:@"vouchWithRecoveryKey-callback"
962 secnotice("otrpc", "Returning a vouch with recovery key call: %@, %@ %@", pendingOp.voucher, pendingOp.voucherSig, pendingOp.error);
963 self->_vouchSig = pendingOp.voucherSig;
964 self->_vouchData = pendingOp.voucher;
966 [callback addDependency:pendingOp];
967 [self.operationQueue addOperation: callback];
971 } else if([currentState isEqualToString:OctagonStateInitiatorUpdateDeviceList]) {
972 // As part of the 'initiate' flow, we need to update the trusted device list-you're probably on it already
973 OTUpdateTrustedDeviceListOperation* op = [[OTUpdateTrustedDeviceListOperation alloc] initWithDependencies:self.operationDependencies
974 intendedState:OctagonStateInitiatorJoin
975 listUpdatesState:OctagonStateInitiatorJoin
976 errorState:OctagonStateBecomeUntrusted
980 } else if ([currentState isEqualToString:OctagonStateInitiatorJoin]){
981 OTJoinWithVoucherOperation* op = [[OTJoinWithVoucherOperation alloc] initWithDependencies:self.operationDependencies
982 intendedState:OctagonStateBecomeReady
983 ckksConflictState:OctagonStateInitiatorJoinCKKSReset
984 errorState:OctagonStateBecomeUntrusted
985 voucherData:_vouchData
987 preapprovedKeys:_preapprovedKeys];
990 } else if([currentState isEqualToString:OctagonStateInitiatorJoinCKKSReset]) {
991 return [[OTLocalCKKSResetOperation alloc] initWithDependencies:self.operationDependencies
992 intendedState:OctagonStateInitiatorJoinAfterCKKSReset
993 errorState:OctagonStateBecomeUntrusted];
995 } else if ([currentState isEqualToString:OctagonStateInitiatorJoinAfterCKKSReset]){
996 return [[OTJoinWithVoucherOperation alloc] initWithDependencies:self.operationDependencies
997 intendedState:OctagonStateBecomeReady
998 ckksConflictState:OctagonStateBecomeUntrusted
999 errorState:OctagonStateBecomeUntrusted
1000 voucherData:_vouchData
1001 voucherSig:_vouchSig
1002 preapprovedKeys:_preapprovedKeys];
1004 } else if([currentState isEqualToString:OctagonStateResetBecomeUntrusted]) {
1005 return [self becomeUntrustedOperation:OctagonStateResetAndEstablish];
1007 } else if([currentState isEqualToString:OctagonStateResetAndEstablish]) {
1008 return [[OTResetOperation alloc] init:self.containerName
1009 contextID:self.contextID
1011 intendedState:OctagonStateResetAnyMissingTLKCKKSViews
1012 errorState:OctagonStateError
1013 cuttlefishXPCWrapper:self.cuttlefishXPCWrapper];
1015 } else if([currentState isEqualToString:OctagonStateResetAnyMissingTLKCKKSViews]) {
1016 return [[OTResetCKKSZonesLackingTLKsOperation alloc] initWithDependencies:self.operationDependencies
1017 intendedState:OctagonStateReEnactDeviceList
1018 errorState:OctagonStateError];
1020 } else if([currentState isEqualToString:OctagonStateReEnactDeviceList]) {
1021 return [[OTUpdateTrustedDeviceListOperation alloc] initWithDependencies:self.operationDependencies
1022 intendedState:OctagonStateReEnactPrepare
1023 listUpdatesState:OctagonStateReEnactPrepare
1024 errorState:OctagonStateError
1027 } else if([currentState isEqualToString:OctagonStateReEnactPrepare]) {
1028 // Note: Resetting the account returns epoch to 0.
1029 return [[OTPrepareOperation alloc] initWithDependencies:self.operationDependencies
1030 intendedState:OctagonStateReEnactReadyToEstablish
1031 errorState:OctagonStateError
1032 deviceInfo:[self prepareInformation]
1035 } else if([currentState isEqualToString:OctagonStateReEnactReadyToEstablish]) {
1036 return [[OTEstablishOperation alloc] initWithDependencies:self.operationDependencies
1037 intendedState:OctagonStateEscrowTriggerUpdate
1038 ckksConflictState:OctagonStateEstablishCKKSReset
1039 errorState:OctagonStateBecomeUntrusted];
1041 } else if([currentState isEqualToString:OctagonStateEstablishCKKSReset]) {
1042 return [[OTLocalCKKSResetOperation alloc] initWithDependencies:self.operationDependencies
1043 intendedState:OctagonStateEstablishAfterCKKSReset
1044 errorState:OctagonStateBecomeUntrusted];
1046 } else if([currentState isEqualToString:OctagonStateEstablishAfterCKKSReset]) {
1047 // If CKKS fails again, just go to "become untrusted"
1048 return [[OTEstablishOperation alloc] initWithDependencies:self.operationDependencies
1049 intendedState:OctagonStateEscrowTriggerUpdate
1050 ckksConflictState:OctagonStateBecomeUntrusted
1051 errorState:OctagonStateBecomeUntrusted];
1053 } else if ([currentState isEqualToString:OctagonStateEscrowTriggerUpdate]){
1055 return [[OTTriggerEscrowUpdateOperation alloc] initWithDependencies:self.operationDependencies
1056 intendedState:OctagonStateBecomeReady
1057 errorState:OctagonStateError];
1059 } else if([currentState isEqualToString: OctagonStateWaitForUnlock]) {
1060 if([flags _onqueueContains:OctagonFlagUnlocked]) {
1061 [flags _onqueueRemoveFlag:OctagonFlagUnlocked];
1062 return [OctagonStateTransitionOperation named:[NSString stringWithFormat:@"%@", @"initializing-after-unlock"]
1063 entering:OctagonStateInitializing];
1066 secnotice("octagon", "Requested to enter wait for unlock");
1067 [pendingFlagHandler _onqueueHandlePendingFlag:[[OctagonPendingFlag alloc] initWithFlag:OctagonFlagUnlocked
1068 conditions:OctagonPendingConditionsDeviceUnlocked]];
1071 } else if([currentState isEqualToString: OctagonStateUpdateSOSPreapprovals]) {
1072 secnotice("octagon", "Updating SOS preapprovals");
1074 // TODO: if this update fails, we need to redo it later.
1075 return [[OTSOSUpdatePreapprovalsOperation alloc] initWithDependencies:self.operationDependencies
1076 intendedState:OctagonStateReady
1077 sosNotPresentState:OctagonStateReady
1078 errorState:OctagonStateReady];
1080 } else if([currentState isEqualToString:OctagonStateAssistCKKSTLKUpload]) {
1081 return [[OTUploadNewCKKSTLKsOperation alloc] initWithDependencies:self.operationDependencies
1082 intendedState:OctagonStateReady
1083 ckksConflictState:OctagonStateAssistCKKSTLKUploadCKKSReset
1084 errorState:OctagonStateReady];
1086 } else if([currentState isEqualToString:OctagonStateAssistCKKSTLKUploadCKKSReset]) {
1087 return [[OTLocalCKKSResetOperation alloc] initWithDependencies:self.operationDependencies
1088 intendedState:OctagonStateAssistCKKSTLKUploadAfterCKKSReset
1089 errorState:OctagonStateBecomeReady];
1091 } else if([currentState isEqualToString:OctagonStateAssistCKKSTLKUploadAfterCKKSReset]) {
1092 // If CKKS fails again, just go to 'ready'
1093 return [[OTUploadNewCKKSTLKsOperation alloc] initWithDependencies:self.operationDependencies
1094 intendedState:OctagonStateReady
1095 ckksConflictState:OctagonStateReady
1096 errorState:OctagonStateReady];
1098 } else if([currentState isEqualToString:OctagonStateReady]) {
1099 if([flags _onqueueContains:OctagonFlagCKKSRequestsTLKUpload]) {
1100 [flags _onqueueRemoveFlag:OctagonFlagCKKSRequestsTLKUpload];
1101 return [OctagonStateTransitionOperation named:@"ckks-assist"
1102 entering:OctagonStateAssistCKKSTLKUpload];
1105 if([flags _onqueueContains:OctagonFlagCuttlefishNotification]) {
1106 [flags _onqueueRemoveFlag:OctagonFlagCuttlefishNotification];
1107 secnotice("octagon", "Updating TPH (while ready) due to push");
1108 return [OctagonStateTransitionOperation named:@"octagon-update"
1109 entering:OctagonStateReadyUpdated];
1112 if([flags _onqueueContains:OctagonFlagFetchAuthKitMachineIDList]) {
1113 [flags _onqueueRemoveFlag:OctagonFlagFetchAuthKitMachineIDList];
1115 secnotice("octagon", "Received an suggestion to update the machine ID list (while ready); updating trusted device list");
1117 // If the cached list changes due to this fetch, go into 'updated'. Otherwise, back into ready with you!
1118 return [[OTUpdateTrustedDeviceListOperation alloc] initWithDependencies:self.operationDependencies
1119 intendedState:OctagonStateReady
1120 listUpdatesState:OctagonStateReadyUpdated
1121 errorState:OctagonStateReady
1122 retryFlag:OctagonFlagFetchAuthKitMachineIDList];
1125 if([flags _onqueueContains:OctagonFlagAttemptSOSUpdatePreapprovals]) {
1126 [flags _onqueueRemoveFlag:OctagonFlagAttemptSOSUpdatePreapprovals];
1127 if(self.sosAdapter.sosEnabled) {
1128 secnotice("octagon", "Attempt SOS Update preapprovals again!");
1129 return [OctagonStateTransitionOperation named:@"attempt-sos-update-preapproval"
1130 entering:OctagonStateUpdateSOSPreapprovals];
1132 secnotice("octagon", "We are untrusted, but this platform doesn't support SOS.");
1136 if([flags _onqueueContains:OctagonFlagAttemptSOSConsistency]) {
1137 [flags _onqueueRemoveFlag:OctagonFlagAttemptSOSConsistency];
1138 if(self.sosAdapter.sosEnabled) {
1139 secnotice("octagon", "Attempting SOS consistency checks");
1140 return [OctagonStateTransitionOperation named:@"attempt-sos-update-preapproval"
1141 entering:OctagonStateEnsureConsistency];
1143 secnotice("octagon", "Someone would like us to check SOS consistency, but this platform doesn't support SOS.");
1147 if([flags _onqueueContains:OctagonFlagAccountIsAvailable]) {
1148 // We're in ready--we already know the account is available
1149 secnotice("octagon", "Removing 'account is available' flag");
1150 [flags _onqueueRemoveFlag:OctagonFlagAccountIsAvailable];
1153 // We're ready; no need for the IDMS level flag anymore
1154 if([flags _onqueueContains:OctagonFlagIDMSLevelChanged]) {
1155 secnotice("octagon", "Removing 'IDMS level changed' flag");
1156 [flags _onqueueRemoveFlag:OctagonFlagIDMSLevelChanged];
1159 secnotice("octagon", "Entering state ready");
1160 [[CKKSAnalytics logger] setDateProperty:[NSDate date] forKey:OctagonAnalyticsLastKeystateReady];
1161 [self.launchSequence launch];
1163 } else if([currentState isEqualToString:OctagonStateReadyUpdated]) {
1164 return [[OTUpdateTPHOperation alloc] initWithDependencies:self.operationDependencies
1165 intendedState:OctagonStateReady
1166 errorState:OctagonStateError
1167 retryFlag:OctagonFlagCuttlefishNotification];
1169 } else if ([currentState isEqualToString:OctagonStateError]) {
1175 - (CKKSResultOperation<OctagonStateTransitionOperationProtocol>* _Nullable)initializingOperation
1178 return [OctagonStateTransitionOperation named:@"octagon-initializing"
1179 intending:OctagonStateNoAccount
1180 errorState:OctagonStateError
1181 withBlockTakingSelf:^(OctagonStateTransitionOperation * _Nonnull op) {
1183 NSError* localError = nil;
1184 OTAccountMetadataClassC* account = [self.accountMetadataStore loadOrCreateAccountMetadata:&localError];
1185 if(localError && [self.lockStateTracker isLockedError:localError]){
1186 secnotice("octagon", "Device is locked! pending initialization on unlock");
1187 op.nextState = OctagonStateWaitForUnlock;
1191 if(localError || !account) {
1192 secnotice("octagon", "Error loading account data: %@", localError);
1193 op.nextState = OctagonStateNoAccount;
1195 } else if(account.icloudAccountState == OTAccountMetadataClassC_AccountState_ACCOUNT_AVAILABLE) {
1196 secnotice("octagon", "An HSA2 iCloud account exists; waiting for CloudKit to confirm");
1198 // Inform the account state tracker of our HSA2 account
1199 [self.accountStateTracker setHSA2iCloudAccountStatus:CKKSAccountStatusAvailable];
1201 // This seems an odd place to do this, but CKKS currently also tracks the CloudKit account state.
1202 // Since we think we have an HSA2 account, let CKKS figure out its own CloudKit state
1203 secnotice("octagon-ckks", "Initializing CKKS views");
1204 [self.viewManager createViews];
1205 [self.viewManager beginCloudKitOperationOfAllViews];
1207 op.nextState = OctagonStateWaitingForCloudKitAccount;
1209 } else if(account.icloudAccountState == OTAccountMetadataClassC_AccountState_NO_ACCOUNT && account.altDSID != nil) {
1210 secnotice("octagon", "An iCloud account exists, but doesn't appear to be HSA2. Let's check!");
1211 op.nextState = OctagonStateDetermineiCloudAccountState;
1213 } else if(account.icloudAccountState == OTAccountMetadataClassC_AccountState_NO_ACCOUNT) {
1214 [self.accountStateTracker setHSA2iCloudAccountStatus:CKKSAccountStatusNoAccount];
1216 secnotice("octagon", "No iCloud account available.");
1217 op.nextState = OctagonStateNoAccount;
1220 secnotice("octagon", "Unknown account state (%@). Determining...", [account icloudAccountStateAsString:account.icloudAccountState]);
1221 op.nextState = OctagonStateDetermineiCloudAccountState;
1226 - (CKKSResultOperation<OctagonStateTransitionOperationProtocol>* _Nullable)evaluateSecdOctagonTrust
1228 return [OctagonStateTransitionOperation named:@"octagon-health-securityd-trust-check"
1229 intending:OctagonStateTPHTrustCheck
1230 errorState:OctagonStatePostRepairCFU
1231 withBlockTakingSelf:^(OctagonStateTransitionOperation * _Nonnull op) {
1232 NSError* localError = nil;
1233 OTAccountMetadataClassC* account = [self.accountMetadataStore loadOrCreateAccountMetadata:&localError];
1234 if(account.peerID && account.trustState == OTAccountMetadataClassC_TrustState_TRUSTED) {
1235 secnotice("octagon-health", "peer is trusted: %@", account.peerID);
1236 op.nextState = OctagonStateTPHTrustCheck;
1239 secnotice("octagon-health", "trust state (%@). checking in with TPH", [account trustStateAsString:account.trustState]);
1240 op.nextState = [self repairAccountIfTrustedByTPHWithIntededState:OctagonStateTPHTrustCheck errorState:OctagonStatePostRepairCFU];
1245 - (CKKSResultOperation<OctagonStateTransitionOperationProtocol>* _Nullable)evaluateTPHOctagonTrust
1247 return [OctagonStateTransitionOperation named:@"octagon-health-tph-trust-check"
1248 intending:OctagonStateCuttlefishTrustCheck
1249 errorState:OctagonStatePostRepairCFU
1250 withBlockTakingSelf:^(OctagonStateTransitionOperation * _Nonnull op) {
1251 [self checkTrustStatusAndPostRepairCFUIfNecessary:^(CliqueStatus status, BOOL posted, BOOL hasIdentity, NSError *trustFromTPHError) {
1253 [[CKKSAnalytics logger] logResultForEvent:OctagonEventTPHHealthCheckStatus hardFailure:false result:trustFromTPHError];
1254 if(trustFromTPHError) {
1255 secerror("octagon-health: hit an error asking TPH for trust status: %@", trustFromTPHError);
1256 op.error = trustFromTPHError;
1257 op.nextState = OctagonStateError;
1259 if(hasIdentity == NO) {
1260 op.nextState = OctagonStateUntrusted;
1261 } else if(hasIdentity == YES && status == CliqueStatusIn){
1262 secnotice("octagon-health", "TPH says we're trusted and in");
1263 op.nextState = OctagonStateCuttlefishTrustCheck;
1264 } else if (hasIdentity == YES && status != CliqueStatusIn){
1265 secnotice("octagon-health", "TPH says we have an identity but we are not in Octagon, posted CFU: %d", !!posted);
1266 op.nextState = OctagonStatePostRepairCFU;
1268 secnotice("octagon-health", "weird shouldn't hit this catch all.. assuming untrusted");
1269 op.nextState = OctagonStateUntrusted;
1276 - (CKKSResultOperation<OctagonStateTransitionOperationProtocol>* _Nullable)cuttlefishTrustEvaluation
1279 OTCheckHealthOperation* op = [[OTCheckHealthOperation alloc] initWithDependencies:self.operationDependencies
1280 intendedState:OctagonStateBecomeReady
1281 errorState:OctagonStateBecomeReady
1282 deviceInfo:self.prepareInformation
1283 skipRateLimitedCheck:_skipRateLimitingCheck];
1285 CKKSResultOperation* callback = [CKKSResultOperation named:@"rpcHealthCheck"
1287 secnotice("octagon-health", "Returning from cuttlefish trust check call: postRepairCFU(%d), postEscrowCFU(%d), resetOctagon(%d)",
1288 op.postRepairCFU, op.postEscrowCFU, op.resetOctagon);
1289 if(op.postRepairCFU) {
1290 secnotice("octagon-health", "Posting Repair CFU");
1291 NSError* postRepairCFUError = nil;
1292 [self postRepairCFU:&postRepairCFUError];
1293 if(postRepairCFUError) {
1294 op.error = postRepairCFUError;
1297 if(op.postEscrowCFU) {
1298 //hold up, perhaps we already are pending an upload.
1299 NSError* shouldPostError = nil;
1300 BOOL shouldPost = [self shouldPostConfirmPasscodeCFU:&shouldPostError];
1301 if(shouldPostError) {
1302 secerror("octagon-health, hit an error evaluating prerecord status: %@", shouldPostError);
1303 op.error = shouldPostError;
1306 secnotice("octagon-health", "Posting Escrow CFU");
1307 NSError* postEscrowCFUError = nil;
1308 [self postConfirmPasscodeCFU:&postEscrowCFUError];
1309 if(postEscrowCFUError) {
1310 op.error = postEscrowCFUError;
1313 secnotice("octagon-health", "Not posting confirm passcode CFU, already pending a prerecord upload");
1317 [callback addDependency:op];
1318 [self.operationQueue addOperation: callback];
1322 - (CKKSResultOperation<OctagonStateTransitionOperationProtocol>* _Nullable)postRepairCFUAndBecomeUntrusted
1324 return [OctagonStateTransitionOperation named:@"octagon-health-post-repair-cfu"
1325 intending:OctagonStateUntrusted
1326 errorState:OctagonStateError
1327 withBlockTakingSelf:^(OctagonStateTransitionOperation * _Nonnull op) {
1328 [self checkTrustStatusAndPostRepairCFUIfNecessary:^(CliqueStatus status,
1331 NSError * _Nullable postError) {
1333 secerror("ocagon-health: failed to post repair cfu via state machine: %@", postError);
1335 secnotice("octagon-health", "posted repair cfu via state machine");
1338 op.nextState = OctagonStateUntrusted;
1342 - (CKKSResultOperation<OctagonStateTransitionOperationProtocol>* _Nullable)cloudKitAccountNewlyAvailableOperation
1345 return [OctagonStateTransitionOperation named:@"octagon-icloud-account-available"
1346 intending:OctagonStateCheckTrustState
1347 errorState:OctagonStateError
1348 withBlockTakingSelf:^(OctagonStateTransitionOperation * _Nonnull op) {
1350 // Register with APS, but don't bother to wait until it's complete.
1351 secnotice("octagon", "iCloud sign in occurred. Attemping to register with APS...");
1353 CKContainer* ckContainer = [CKContainer containerWithIdentifier:self.containerName];
1354 [ckContainer serverPreferredPushEnvironmentWithCompletionHandler: ^(NSString *apsPushEnvString, NSError *error) {
1358 secerror("octagonpush: received callback for released object");
1362 if(error || (apsPushEnvString == nil)) {
1363 secerror("octagonpush: Received error fetching preferred push environment (%@): %@", apsPushEnvString, error);
1365 secnotice("octagonpush", "Registering for environment '%@'", apsPushEnvString);
1367 OctagonAPSReceiver* aps = [OctagonAPSReceiver receiverForEnvironment:apsPushEnvString
1368 namedDelegatePort:SecCKKSAPSNamedPort
1369 apsConnectionClass:self.apsConnectionClass];
1370 [aps registerCuttlefishReceiver:self forContainerName:self.containerName];
1374 op.nextState = op.intendedState;
1378 - (OctagonState*) repairAccountIfTrustedByTPHWithIntededState:(OctagonState*)intendedState errorState:(OctagonState*)errorState
1380 __block OctagonState* nextState = intendedState;
1382 //let's check in with TPH real quick to make sure it agrees with our local assessment
1383 secnotice("octagon-health", "repairAccountIfTrustedByTPHWithIntededState: calling into TPH for trust status");
1385 OTOperationConfiguration *config = [[OTOperationConfiguration alloc]init];
1387 [self rpcTrustStatus:config reply:^(CliqueStatus status,
1388 NSString* egoPeerID,
1389 NSDictionary<NSString*, NSNumber*>* _Nullable peerCountByModelID,
1391 NSError * _Nullable error) {
1392 BOOL hasIdentity = egoPeerID != nil;
1393 secnotice("octagon-health", "repairAccountIfTrustedByTPHWithIntededState status: %ld, peerID: %@, isExcluded: %d error: %@", (long)status, egoPeerID, isExcluded, error);
1396 secnotice("octagon-health", "got an error from tph, returning to become_ready state: %@", error);
1397 nextState = OctagonStateBecomeReady;
1401 if(OctagonAuthoritativeTrustIsEnabled() && hasIdentity && status == CliqueStatusIn) {
1402 dispatch_semaphore_t sema = dispatch_semaphore_create(0);
1403 [self rpcStatus:^(NSDictionary *dump, NSError *dumpError) {
1405 secerror("octagon-health: error fetching ego peer id!: %@", dumpError);
1406 nextState = errorState;
1408 NSDictionary* egoInformation = dump[@"self"];
1409 NSString* peerID = egoInformation[@"peerID"];
1410 NSError* persistError = nil;
1411 BOOL persisted = [self.accountMetadataStore persistAccountChanges:^OTAccountMetadataClassC * _Nonnull(OTAccountMetadataClassC * _Nonnull metadata) {
1412 metadata.trustState = OTAccountMetadataClassC_TrustState_TRUSTED;
1413 metadata.icloudAccountState = OTAccountMetadataClassC_AccountState_ACCOUNT_AVAILABLE;
1414 metadata.peerID = peerID;
1416 } error:&persistError];
1417 if(!persisted || persistError) {
1418 secerror("octagon-health: couldn't persist results: %@", persistError);
1419 nextState = errorState;
1421 secnotice("octagon-health", "added trusted identity to account metadata");
1422 nextState = intendedState;
1425 dispatch_semaphore_signal(sema);
1427 if (0 != dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 10))) {
1428 secerror("octagon: Timed out checking trust status");
1430 } else if (OctagonAuthoritativeTrustIsEnabled() && (self.postedRepairCFU == NO) && hasIdentity && status != CliqueStatusIn){
1431 nextState = errorState;
1438 - (BOOL) didDeviceAttemptToJoinOctagon:(NSError**)error
1440 NSError* fetchAttemptError = nil;
1441 OTAccountMetadataClassC_AttemptedAJoinState attemptedAJoin = [self.accountMetadataStore fetchPersistedJoinAttempt:&fetchAttemptError];
1442 if(fetchAttemptError) {
1443 secerror("octagon: failed to fetch data indicating device attempted to join octagon, assuming it did: %@", fetchAttemptError);
1445 *error = fetchAttemptError;
1449 BOOL attempted = YES;
1450 switch (attemptedAJoin) {
1451 case OTAccountMetadataClassC_AttemptedAJoinState_NOTATTEMPTED:
1454 case OTAccountMetadataClassC_AttemptedAJoinState_ATTEMPTED:
1455 case OTAccountMetadataClassC_AttemptedAJoinState_UNKNOWN:
1462 - (void)checkTrustStatusAndPostRepairCFUIfNecessary:(void (^ _Nullable)(CliqueStatus status, BOOL posted, BOOL hasIdentity, NSError * _Nullable error))reply
1465 OTOperationConfiguration *configuration = [[OTOperationConfiguration alloc] init];
1466 [self rpcTrustStatus:configuration reply:^(CliqueStatus status,
1467 NSString* _Nullable egoPeerID,
1468 NSDictionary<NSString*, NSNumber*>* _Nullable peerCountByModelID,
1470 NSError * _Nullable error) {
1473 secnotice("octagon", "clique status: %@, egoPeerID: %@, peerCountByModelID: %@, isExcluded: %d error: %@", OTCliqueStatusToString(status), egoPeerID, peerCountByModelID, isExcluded, error);
1475 BOOL hasIdentity = egoPeerID != nil;
1476 if (error && error.code != errSecInteractionNotAllowed) {
1477 reply(status, NO, hasIdentity, error);
1482 // Are there any iphones or iPads? about? Only iOS devices can repair apple TVs.
1483 bool phonePeerPresent = false;
1484 for(NSString* modelID in peerCountByModelID.allKeys) {
1485 bool iPhone = [modelID hasPrefix:@"iPhone"];
1486 bool iPad = [modelID hasPrefix:@"iPad"];
1487 if(!iPhone && !iPad) {
1491 int count = [peerCountByModelID[modelID] intValue];
1493 secnotice("octagon", "Have %d peers with model %@", count, modelID);
1494 phonePeerPresent = true;
1498 if(!phonePeerPresent) {
1499 secnotice("octagon", "No iOS peers in account; not posting CFU");
1500 reply(status, NO, hasIdentity, nil);
1505 // On platforms with SOS, we only want to post a CFU if we've attempted to join at least once.
1506 // This prevents us from posting a CFU, then performing an SOS upgrade and succeeding.
1507 if(self.sosAdapter.sosEnabled) {
1508 NSError* fetchAttemptError = nil;
1509 BOOL attemptedToJoin = [self didDeviceAttemptToJoinOctagon:&fetchAttemptError];
1510 if(fetchAttemptError){
1511 secerror("octagon: failed to retrieve joining attempt information: %@", fetchAttemptError);
1512 attemptedToJoin = YES;
1515 if(!attemptedToJoin) {
1516 secnotice("octagon", "SOS is enabled and we haven't attempted to join; not posting CFU");
1517 reply(status, NO, hasIdentity, nil);
1522 if(OctagonAuthoritativeTrustIsEnabled() && (status == CliqueStatusNotIn || status == CliqueStatusAbsent || isExcluded)) {
1523 NSError* localError = nil;
1524 BOOL posted = [self postRepairCFU:&localError];
1525 reply(status, posted, hasIdentity, localError);
1528 reply(status, NO, hasIdentity, nil);
1534 - (CKKSResultOperation<OctagonStateTransitionOperationProtocol>* _Nullable)startCompanionPairingOperation
1537 return [OctagonStateTransitionOperation named:@"start-companion-pairing"
1538 intending:OctagonStateBecomeUntrusted
1539 errorState:OctagonStateError
1540 withBlockTakingSelf:^(OctagonStateTransitionOperation * _Nonnull op) {
1542 OTPairingInitiateWithCompletion(self.queue, ^(bool success, NSError *error) {
1544 secnotice("octagon", "companion pairing succeeded");
1547 error = [NSError errorWithDomain:(__bridge NSString *)kSecErrorDomain code:errSecInternalError userInfo:nil];
1549 secnotice("octagon", "companion pairing failed: %@", error);
1551 [[CKKSAnalytics logger] logResultForEvent:OctagonEventCompanionPairing hardFailure:false result:error];
1553 op.nextState = op.intendedState;
1556 #endif /* TARGET_OS_WATCH */
1558 - (CKKSResultOperation<OctagonStateTransitionOperationProtocol>* _Nullable)becomeUntrustedOperation:(OctagonState*)intendedState
1561 return [OctagonStateTransitionOperation named:@"octagon-become-untrusted"
1562 intending:intendedState
1563 errorState:OctagonStateError
1564 withBlockTakingSelf:^(OctagonStateTransitionOperation * _Nonnull op) {
1566 NSError* localError = nil;
1568 [self.accountStateTracker triggerOctagonStatusFetch];
1570 [self checkTrustStatusAndPostRepairCFUIfNecessary:^(CliqueStatus status, BOOL posted, BOOL hasIdentity, NSError * _Nullable postError) {
1572 [[CKKSAnalytics logger] logResultForEvent:OctagonEventCheckTrustForCFU hardFailure:false result:postError];
1574 secerror("octagon: cfu failed to post");
1576 secnotice("octagon", "clique status: %@, posted cfu: %d", OTCliqueStatusToString(status), !!posted);
1580 [self.accountMetadataStore persistAccountChanges:^OTAccountMetadataClassC * _Nonnull(OTAccountMetadataClassC * _Nonnull metadata) {
1581 metadata.trustState = OTAccountMetadataClassC_TrustState_UNTRUSTED;
1583 } error:&localError];
1586 secnotice("octagon", "Unable to set trust state: %@", localError);
1587 op.nextState = OctagonStateError;
1589 op.nextState = op.intendedState;
1592 for (id key in self.viewManager.views) {
1593 CKKSKeychainView* view = self.viewManager.views[key];
1594 secnotice("octagon-ckks", "Informing %@ of new untrusted status", view);
1595 [view endTrustedOperation];
1599 * Initial notification that we let the world know that trust is up and doing something
1601 if (!self.initialBecomeUntrustedPosted) {
1602 [self notifyTrustChanged:OTAccountMetadataClassC_TrustState_UNTRUSTED];
1603 self.initialBecomeUntrustedPosted = YES;
1606 self.octagonAdapter = nil;
1610 - (CKKSResultOperation<OctagonStateTransitionOperationProtocol>* _Nullable)becomeReadyOperation
1613 return [OctagonStateTransitionOperation named:@"octagon-ready"
1614 intending:OctagonStateReady
1615 errorState:OctagonStateError
1616 withBlockTakingSelf:^(OctagonStateTransitionOperation * _Nonnull op) {
1619 // Note: we don't modify the account metadata store here; that will have been done
1620 // by a join or upgrade operation, possibly long ago
1622 [self.accountStateTracker triggerOctagonStatusFetch];
1624 NSError* localError = nil;
1625 NSString* peerID = [self.accountMetadataStore getEgoPeerID:&localError];
1626 if(!peerID || localError) {
1627 secerror("octagon-ckks: No peer ID to pass to CKKS. Syncing will be disabled.");
1629 OctagonCKKSPeerAdapter* octagonAdapter = [[OctagonCKKSPeerAdapter alloc] initWithPeerID:peerID operationDependencies:[self operationDependencies]];
1631 // This octagon adapter must be able to load the self peer keys, or we're in trouble.
1632 NSError* egoPeerKeysError = nil;
1633 CKKSSelves* selves = [octagonAdapter fetchSelfPeers:&egoPeerKeysError];
1634 if(!selves || egoPeerKeysError) {
1635 secerror("octagon-ckks: Unable to fetch self peers for %@: %@", octagonAdapter, egoPeerKeysError);
1637 if([self.lockStateTracker isLockedError:egoPeerKeysError]) {
1638 secnotice("octagon-ckks", "Waiting for device unlock to proceed");
1639 op.nextState = OctagonStateWaitForUnlock;
1641 secnotice("octagon-ckks", "Error is scary; becoming untrusted");
1642 op.nextState = OctagonStateBecomeUntrusted;
1647 // stash a reference to the adapter so we can provided updates later
1648 self.octagonAdapter = octagonAdapter;
1650 // Start all our CKKS views!
1651 for (id key in self.viewManager.views) {
1652 CKKSKeychainView* view = self.viewManager.views[key];
1653 secnotice("octagon-ckks", "Informing CKKS view '%@' of trusted operation with self peer %@", view.zoneName, peerID);
1655 NSArray<id<CKKSPeerProvider>>* peerProviders = nil;
1657 if(self.sosAdapter.sosEnabled) {
1658 peerProviders = @[self.octagonAdapter, self.sosAdapter];
1661 peerProviders = @[self.octagonAdapter];
1664 [view beginTrustedOperation:peerProviders
1665 suggestTLKUpload:self.suggestTLKUploadNotifier];
1668 [self notifyTrustChanged:OTAccountMetadataClassC_TrustState_TRUSTED];
1670 op.nextState = op.intendedState;
1674 #pragma mark --- Utilities to run at times
1676 - (NSString * _Nullable)extractStringKey:(NSString * _Nonnull)key fromDictionary:(NSDictionary * _Nonnull)d
1678 NSString *value = d[key];
1679 if ([value isKindOfClass:[NSString class]]) {
1685 - (void)handleHealthRequest
1687 NSString *trustState = OTAccountMetadataClassC_TrustStateAsString(self.currentMemoizedTrustState);
1688 OctagonState* currentState = [self.stateMachine waitForState:OctagonStateReady wait:3*NSEC_PER_SEC];
1690 [self.cuttlefishXPCWrapper reportHealthWithContainer:self.containerName context:self.contextID stateMachineState:currentState trustState:trustState reply:^(NSError * _Nullable error) {
1692 secerror("octagon: health report is lost: %@", error);
1697 - (void)handleTTRRequest:(NSDictionary *)cfDictionary
1699 NSString *serialNumber = [self extractStringKey:@"s" fromDictionary:cfDictionary];
1700 NSString *ckDeviceId = [self extractStringKey:@"D" fromDictionary:cfDictionary];
1701 NSString *alert = [self extractStringKey:@"a" fromDictionary:cfDictionary];
1702 NSString *description = [self extractStringKey:@"d" fromDictionary:cfDictionary];
1703 NSString *radar = [self extractStringKey:@"R" fromDictionary:cfDictionary];
1704 NSString *componentName = [self extractStringKey:@"n" fromDictionary:cfDictionary];
1705 NSString *componentVersion = [self extractStringKey:@"v" fromDictionary:cfDictionary];
1706 NSString *componentID = [self extractStringKey:@"I" fromDictionary:cfDictionary];
1709 if (![self.deviceAdapter.serialNumber isEqualToString:serialNumber]) {
1710 secnotice("octagon", "TTR request not for me (sn)");
1715 NSString *selfDeviceID = self.viewManager.accountTracker.ckdeviceID;
1716 if (![selfDeviceID isEqualToString:serialNumber]) {
1717 secnotice("octagon", "TTR request not for me (deviceId)");
1722 if (alert == NULL || description == NULL || radar == NULL) {
1723 secerror("octagon: invalid type of TTR requeat: %@", cfDictionary);
1727 SecTapToRadar *ttr = [[SecTapToRadar alloc] initTapToRadar:alert
1728 description:description
1730 if (componentName && componentVersion && componentID) {
1731 ttr.componentName = componentName;
1732 ttr.componentVersion = componentVersion;
1733 ttr.componentID = componentID;
1738 // We can't make a APSIncomingMessage in the tests (no public constructor),
1739 // but we don't really care about anything in it but the userInfo dictionary anyway
1740 - (void)notifyContainerChange:(APSIncomingMessage* _Nullable)notification
1742 [self notifyContainerChangeWithUserInfo:notification.userInfo];
1745 - (void)notifyContainerChangeWithUserInfo:(NSDictionary*)userInfo
1747 secerror("OTCuttlefishContext: received a cuttlefish push notification (%@): %@",
1748 self.containerName, userInfo);
1750 NSDictionary *cfDictionary = userInfo[@"cf"];
1751 if ([cfDictionary isKindOfClass:[NSDictionary class]]) {
1752 NSString *command = [self extractStringKey:@"k" fromDictionary:cfDictionary];
1754 if ([command isEqualToString:@"h"]) {
1755 [self handleHealthRequest];
1756 } else if ([command isEqualToString:@"r"]) {
1757 [self handleTTRRequest:cfDictionary];
1759 secerror("octagon: unknown command: %@", command);
1765 if (self.apsRateLimiter == nil) {
1766 secnotice("octagon", "creating aps rate limiter");
1767 // If we're testing, for the initial delay, use 0.2 second. Otherwise, 2s.
1768 dispatch_time_t initialDelay = (SecCKKSReduceRateLimiting() ? 200 * NSEC_PER_MSEC : 2 * NSEC_PER_SEC);
1770 // If we're testing, for the initial delay, use 2 second. Otherwise, 30s.
1771 dispatch_time_t continuingDelay = (SecCKKSReduceRateLimiting() ? 2 * NSEC_PER_SEC : 30 * NSEC_PER_SEC);
1774 self.apsRateLimiter = [[CKKSNearFutureScheduler alloc] initWithName:@"aps-push-ratelimiter"
1775 initialDelay:initialDelay
1776 continuingDelay:continuingDelay
1777 keepProcessAlive:YES
1778 dependencyDescriptionCode:CKKSResultDescriptionNone
1784 secnotice("octagon-push-ratelimited", "notifying container of change for context: %@", self.contextID);
1785 OctagonPendingFlag *pendingFlag = [[OctagonPendingFlag alloc] initWithFlag:OctagonFlagCuttlefishNotification
1786 conditions:OctagonPendingConditionsDeviceUnlocked];
1788 [self.stateMachine handlePendingFlag:pendingFlag];
1792 [self.apsRateLimiter trigger];
1795 - (BOOL)waitForReady:(int64_t)timeOffset
1797 OctagonState* currentState = [self.stateMachine waitForState:OctagonStateReady wait:timeOffset];
1798 return [currentState isEqualToString:OctagonStateReady];
1802 - (OTAccountMetadataClassC_TrustState)currentMemoizedTrustState
1804 NSError* localError = nil;
1805 OTAccountMetadataClassC* accountMetadata = [self.accountMetadataStore loadOrCreateAccountMetadata:&localError];
1807 if(!accountMetadata) {
1808 secnotice("octagon", "Unable to fetch account metadata: %@", localError);
1809 return OTAccountMetadataClassC_TrustState_UNKNOWN;
1812 return accountMetadata.trustState;
1815 - (OTAccountMetadataClassC_AccountState)currentMemoizedAccountState
1817 NSError* localError = nil;
1818 OTAccountMetadataClassC* accountMetadata = [self.accountMetadataStore loadOrCreateAccountMetadata:&localError];
1820 if(!accountMetadata) {
1821 secnotice("octagon", "Unable to fetch account metadata: %@", localError);
1822 return OTAccountMetadataClassC_AccountState_UNKNOWN;
1825 return accountMetadata.icloudAccountState;
1828 - (NSDate* _Nullable) currentMemoizedLastHealthCheck
1830 NSError* localError = nil;
1831 OTAccountMetadataClassC* accountMetadata = [self.accountMetadataStore loadOrCreateAccountMetadata:&localError];
1833 if(!accountMetadata) {
1834 secnotice("octagon", "Unable to fetch account metadata: %@", localError);
1837 if(accountMetadata.lastHealthCheckup == 0) {
1840 return [[NSDate alloc] initWithTimeIntervalSince1970: accountMetadata.lastHealthCheckup];
1843 - (void)requestTrustedDeviceListRefresh
1845 [self.stateMachine handleFlag:OctagonFlagFetchAuthKitMachineIDList];
1848 #pragma mark --- Device Info update handling
1850 - (void)deviceNameUpdated {
1851 secnotice("octagon-devicename", "device name updated: %@", self.contextID);
1852 OctagonPendingFlag *pendingFlag = [[OctagonPendingFlag alloc] initWithFlag:OctagonFlagCuttlefishNotification
1853 conditions:OctagonPendingConditionsDeviceUnlocked];
1854 [self.stateMachine handlePendingFlag:pendingFlag];
1857 #pragma mark --- SOS update handling
1860 - (void)selfPeerChanged:(id<CKKSPeerProvider>)provider
1862 // Currently, we register for peer changes with just our SOS peer adapter, so the only reason this is called is to receive SOS updates
1863 // Ignore SOS self peer updates for now.
1866 - (void)trustedPeerSetChanged:(id<CKKSPeerProvider>)provider
1868 // Currently, we register for peer changes with just our SOS peer adapter, so the only reason this is called is to receive SOS updates
1869 secnotice("octagon-sos", "Received an update of an SOS trust set change");
1871 if(!self.sosAdapter.sosEnabled) {
1872 secnotice("octagon-sos", "This platform doesn't support SOS. This is probably a bug?");
1875 if (self.sosConsistencyRateLimiter == nil) {
1876 secnotice("octagon", "creating SOS consistency rate limiter");
1877 dispatch_time_t initialDelay = (SecCKKSReduceRateLimiting() ? 200 * NSEC_PER_MSEC : 2 * NSEC_PER_SEC);
1878 dispatch_time_t maximumDelay = (SecCKKSReduceRateLimiting() ? 10 * NSEC_PER_SEC : 30 * NSEC_PER_SEC);
1882 void (^block)(void) = ^{
1884 [self.stateMachine handleFlag:OctagonFlagAttemptSOSConsistency];
1887 self.sosConsistencyRateLimiter = [[CKKSNearFutureScheduler alloc] initWithName:@"sos-consistency-ratelimiter"
1888 initialDelay:initialDelay
1890 maximumDelay:maximumDelay
1891 keepProcessAlive:false
1892 dependencyDescriptionCode:CKKSResultDescriptionPendingZoneChangeFetchScheduling
1896 [self.sosConsistencyRateLimiter trigger];
1899 #pragma mark --- External Interfaces
1902 - (CKKSAccountStatus)checkForCKAccount:(OTOperationConfiguration * _Nullable)configuration {
1905 // Watches can be very, very slow getting the CK account state
1906 uint64_t timeout = (90 * NSEC_PER_SEC);
1908 uint64_t timeout = (10 * NSEC_PER_SEC);
1910 if (configuration.timeoutWaitForCKAccount != 0) {
1911 timeout = configuration.timeoutWaitForCKAccount;
1914 /* wait if account is not present yet */
1915 if([self.cloudKitAccountStateKnown wait:timeout] != 0) {
1916 secnotice("octagon-ck", "Unable to determine CloudKit account state?");
1917 return CKKSAccountStatusUnknown;
1921 __block bool haveAccount = true;
1922 dispatch_sync(self.queue, ^{
1923 if (self.cloudKitAccountInfo == NULL || self.cloudKitAccountInfo.accountStatus != CKKSAccountStatusAvailable) {
1924 haveAccount = false;
1927 return haveAccount ? CKKSAccountStatusAvailable : CKKSAccountStatusNoAccount;
1930 - (NSError *)errorNoiCloudAccount
1932 return [NSError errorWithDomain:OctagonErrorDomain
1933 code:OTErrorNotSignedIn
1934 description:@"User is not signed into iCloud."];
1937 //Initiator interfaces
1939 - (void)rpcPrepareIdentityAsApplicantWithConfiguration:(OTJoiningConfiguration*)config
1940 epoch:(uint64_t)epoch
1941 reply:(void (^)(NSString * _Nullable peerID,
1942 NSData * _Nullable permanentInfo,
1943 NSData * _Nullable permanentInfoSig,
1944 NSData * _Nullable stableInfo,
1945 NSData * _Nullable stableInfoSig,
1946 NSError * _Nullable error))reply
1948 if ([self checkForCKAccount:nil] != CKKSAccountStatusAvailable) {
1949 secnotice("octagon", "No cloudkit account present");
1950 reply(NULL, NULL, NULL, NULL, NULL, [self errorNoiCloudAccount]);
1954 secnotice("otrpc", "Preparing identity as applicant");
1955 OTPrepareOperation* pendingOp = [[OTPrepareOperation alloc] initWithDependencies:self.operationDependencies
1956 intendedState:OctagonStateInitiatorAwaitingVoucher
1957 errorState:OctagonStateBecomeUntrusted
1958 deviceInfo:[self prepareInformation]
1962 dispatch_time_t timeOut = 0;
1963 if(config.timeout != 0) {
1964 timeOut = config.timeout;
1965 } else if(!OctagonPlatformSupportsSOS()){
1966 // Non-iphone non-mac platforms can be slow; heuristically slow them down
1967 timeOut = 60*NSEC_PER_SEC;
1969 timeOut = 2*NSEC_PER_SEC;
1972 OctagonStateTransitionRequest<OTPrepareOperation*>* request = [[OctagonStateTransitionRequest alloc] init:@"prepareForApplicant"
1973 sourceStates:[NSSet setWithArray:@[OctagonStateUntrusted, OctagonStateNoAccount, OctagonStateMachineNotStarted]]
1974 serialQueue:self.queue
1976 transitionOp:pendingOp];
1978 CKKSResultOperation* callback = [CKKSResultOperation named:@"rpcPrepare-callback"
1980 secnotice("otrpc", "Returning a prepare call: %@ %@", pendingOp.peerID, pendingOp.error);
1981 reply(pendingOp.peerID,
1982 pendingOp.permanentInfo,
1983 pendingOp.permanentInfoSig,
1984 pendingOp.stableInfo,
1985 pendingOp.stableInfoSig,
1988 [callback addDependency:pendingOp];
1989 [self.operationQueue addOperation: callback];
1991 [self.stateMachine handleExternalRequest:request];
1996 -(void)joinWithBottle:(NSString*)bottleID
1997 entropy:(NSData *)entropy
1998 bottleSalt:(NSString *)bottleSalt
1999 reply:(void (^)(NSError * _Nullable error))reply
2001 _bottleID = bottleID;
2003 _bottleSalt = bottleSalt;
2004 OTOperationConfiguration *configuration = [[OTOperationConfiguration alloc] init];
2006 if ([self checkForCKAccount:configuration] != CKKSAccountStatusAvailable) {
2007 secnotice("octagon", "No cloudkit account present");
2008 reply([self errorNoiCloudAccount]);
2012 OctagonStateTransitionPath* path = [OctagonStateTransitionPath pathFromDictionary:@{
2013 OctagonStateInitiatorCreateIdentity: @{
2014 OctagonStateInitiatorVouchWithBottle: [self joinStatePathDictionary],
2018 [self.stateMachine doWatchedStateMachineRPC:@"rpc-join-with-bottle"
2019 sourceStates:OctagonInAccountStates()
2024 -(void)joinWithRecoveryKey:(NSString*)recoveryKey
2025 reply:(void (^)(NSError * _Nullable error))reply
2027 _recoveryKey = recoveryKey;
2028 OTOperationConfiguration *configuration = [[OTOperationConfiguration alloc] init];
2030 if ([self checkForCKAccount:configuration] != CKKSAccountStatusAvailable) {
2031 secnotice("octagon", "No cloudkit account present");
2032 reply([self errorNoiCloudAccount]);
2036 OctagonStateTransitionPath* path = [OctagonStateTransitionPath pathFromDictionary:@{
2037 OctagonStateCreateIdentityForRecoveryKey: @{
2038 OctagonStateVouchWithRecoveryKey: [self joinStatePathDictionary],
2042 [self.stateMachine doWatchedStateMachineRPC:@"rpc-join-with-recovery-key"
2043 sourceStates:OctagonInAccountStates()
2048 - (NSDictionary*)joinStatePathDictionary
2051 OctagonStateInitiatorUpdateDeviceList: @{
2052 OctagonStateInitiatorJoin: @{
2053 OctagonStateBecomeReady: @{
2054 OctagonStateReady: [OctagonStateTransitionPathStep success],
2057 OctagonStateInitiatorJoinCKKSReset: @{
2058 OctagonStateInitiatorJoinAfterCKKSReset: @{
2059 OctagonStateBecomeReady: @{
2060 OctagonStateReady: [OctagonStateTransitionPathStep success]
2069 - (void)rpcJoin:(NSData*)vouchData
2070 vouchSig:(NSData*)vouchSig
2071 preapprovedKeys:(NSArray<NSData*>* _Nullable)preapprovedKeys
2072 reply:(void (^)(NSError * _Nullable error))reply
2075 _vouchData = vouchData;
2076 _vouchSig = vouchSig;
2077 _preapprovedKeys = preapprovedKeys;
2079 if ([self checkForCKAccount:nil] != CKKSAccountStatusAvailable) {
2080 secnotice("octagon", "No cloudkit account present");
2081 reply([self errorNoiCloudAccount]);
2085 NSMutableSet* sourceStates = [NSMutableSet setWithObject:OctagonStateInitiatorAwaitingVoucher];
2087 OctagonStateTransitionPath* path = [OctagonStateTransitionPath pathFromDictionary:[self joinStatePathDictionary]];
2089 [self.stateMachine doWatchedStateMachineRPC:@"rpc-join"
2090 sourceStates:sourceStates
2095 - (NSDictionary *)ckksPeerStatus:(id<CKKSPeer>)peer
2097 NSMutableDictionary *peerStatus = [NSMutableDictionary dictionary];
2100 peerStatus[@"peerID"] = peer.peerID;
2102 NSData *spki = peer.publicSigningKey.encodeSubjectPublicKeyInfo;
2104 peerStatus[@"signingSPKI"] = [spki base64EncodedStringWithOptions:0];
2105 peerStatus[@"signingSPKIHash"] = [TPHashBuilder hashWithAlgo:kTPHashAlgoSHA256 ofData:spki];
2110 - (NSArray *)sosTrustedPeersStatus
2112 NSError *localError = nil;
2113 NSSet<id<CKKSRemotePeerProtocol>>* _Nullable peers = [self.sosAdapter fetchTrustedPeers:&localError];
2114 if (peers == nil || localError) {
2115 secnotice("octagon", "No SOS peers present: %@, skipping in status", localError);
2118 NSMutableArray<NSDictionary *>* trustedSOSPeers = [NSMutableArray array];
2120 for (id<CKKSPeer> peer in peers) {
2121 NSDictionary *peerStatus = [self ckksPeerStatus:peer];
2123 [trustedSOSPeers addObject:peerStatus];
2126 return trustedSOSPeers;
2129 - (NSDictionary *)sosSelvesStatus
2131 NSError *localError = nil;
2133 CKKSSelves* selves = [self.sosAdapter fetchSelfPeers:&localError];
2134 if (selves == nil || localError) {
2135 secnotice("octagon", "No SOS selves present: %@, skipping in status", localError);
2138 NSMutableDictionary* selvesSOSPeers = [NSMutableDictionary dictionary];
2140 selvesSOSPeers[@"currentSelf"] = [self ckksPeerStatus:selves.currentSelf];
2143 * If we have past selves, include them too
2145 NSMutableSet* pastSelves = [selves.allSelves mutableCopy];
2146 [pastSelves removeObject:selves.currentSelf];
2147 if (pastSelves.count) {
2148 NSMutableArray<NSDictionary *>* pastSelvesStatus = [NSMutableArray array];
2150 for (id<CKKSPeer> peer in pastSelves) {
2151 NSDictionary *peerStatus = [self ckksPeerStatus:peer];
2153 [pastSelvesStatus addObject:peerStatus];
2156 selvesSOSPeers[@"pastSelves"] = pastSelvesStatus;
2158 return selvesSOSPeers;
2161 - (void)rpcStatus:(void (^)(NSDictionary* _Nullable result, NSError* _Nullable error))reply
2163 __block NSMutableDictionary* result = [NSMutableDictionary dictionary];
2165 result[@"containerName"] = self.containerName;
2166 result[@"contextID"] = self.contextID;
2168 if ([self checkForCKAccount:nil] != CKKSAccountStatusAvailable) {
2169 secnotice("octagon", "No cloudkit account present");
2170 reply(NULL, [self errorNoiCloudAccount]);
2174 if([self.stateMachine.paused wait:3*NSEC_PER_SEC] != 0) {
2175 secnotice("octagon", "Returning status of unpaused state machine for container (%@) and context (%@)", self.containerName, self.contextID);
2176 result[@"stateUnpaused"] = @1;
2179 // This will try to allow the state machine to pause
2180 result[@"state"] = self.stateMachine.currentState;
2181 result[@"statePendingFlags"] = [self.stateMachine dumpPendingFlags];
2182 result[@"stateFlags"] = [self.stateMachine.flags dumpFlags];
2184 result[@"memoizedTrustState"] = @(self.currentMemoizedTrustState);
2185 result[@"memoizedAccountState"] = @(self.currentMemoizedAccountState);
2186 result[@"octagonLaunchSeqence"] = [self.launchSequence eventsByTime];
2187 result[@"memoizedlastHealthCheck"] = self.currentMemoizedLastHealthCheck ? self.currentMemoizedLastHealthCheck : @"Never checked";
2188 if (self.sosAdapter.sosEnabled) {
2189 result[@"sosTrustedPeersStatus"] = [self sosTrustedPeersStatus];
2190 result[@"sosSelvesStatus"] = [self sosSelvesStatus];
2195 id<SecEscrowRequestable> request = [self.escrowRequestClass request:&error];
2196 result[@"escrowRequest"] = [request fetchStatuses:&error];
2199 result[@"CoreFollowUp"] = [self.followupHandler sysdiagnoseStatus];
2200 result[@"lastOctagonPush"] = [[CKKSAnalytics logger] datePropertyForKey:CKKSAnalyticsLastOctagonPush];
2203 [self.cuttlefishXPCWrapper dumpWithContainer:self.containerName
2204 context:self.contextID
2205 reply:^(NSDictionary * _Nullable dump, NSError * _Nullable dumpError) {
2206 secnotice("octagon", "Finished dump for status RPC");
2208 result[@"contextDumpError"] = dumpError;
2210 result[@"contextDump"] = dump;
2216 - (void)rpcFetchEgoPeerID:(void (^)(NSString* peerID, NSError* error))reply
2218 // We've memoized this peer ID. Use the memorized version...
2219 NSError* localError = nil;
2220 NSString* peerID = [self.accountMetadataStore getEgoPeerID:&localError];
2223 secnotice("octagon", "Returning peer ID: %@", peerID);
2225 secnotice("octagon", "Unable to fetch peer ID: %@", localError);
2227 reply(peerID, localError);
2230 - (void)rpcFetchDeviceNamesByPeerID:(void (^)(NSDictionary<NSString*, NSString*>* _Nullable peers, NSError* _Nullable error))reply
2232 if ([self checkForCKAccount:nil] != CKKSAccountStatusAvailable) {
2233 secnotice("octagon", "No cloudkit account present");
2234 reply(NULL, [self errorNoiCloudAccount]);
2238 // As this isn't a state-modifying operation, we don't need to go through the state machine.
2239 [self.cuttlefishXPCWrapper dumpWithContainer:self.containerName
2240 context:self.contextID
2241 reply:^(NSDictionary * _Nullable dump, NSError * _Nullable dumpError) {
2242 // Pull out our peers
2244 secnotice("octagon", "Unable to dump info: %@", dumpError);
2245 reply(nil, dumpError);
2249 NSDictionary* selfInfo = dump[@"self"];
2250 NSArray* peers = dump[@"peers"];
2251 NSArray* trustedPeerIDs = selfInfo[@"dynamicInfo"][@"included"];
2253 NSMutableDictionary<NSString*, NSString*>* peerMap = [NSMutableDictionary dictionary];
2255 for(NSString* peerID in trustedPeerIDs) {
2256 NSDictionary* peerMatchingID = nil;
2258 for(NSDictionary* peer in peers) {
2259 if([peer[@"peerID"] isEqualToString:peerID]) {
2260 peerMatchingID = peer;
2265 if(!peerMatchingID) {
2266 secerror("octagon: have a trusted peer ID without peer information: %@", peerID);
2270 peerMap[peerID] = peerMatchingID[@"stableInfo"][@"device_name"];
2273 reply(peerMap, nil);
2277 - (void)rpcSetRecoveryKey:(NSString*)recoveryKey reply:(void (^)(NSError * _Nullable error))reply
2279 OTSetRecoveryKeyOperation *pendingOp = [[OTSetRecoveryKeyOperation alloc] initWithDependencies:self.operationDependencies
2280 recoveryKey:recoveryKey];
2282 CKKSResultOperation* callback = [CKKSResultOperation named:@"setRecoveryKey-callback"
2284 secnotice("otrpc", "Returning a set recovery key call: %@", pendingOp.error);
2285 reply(pendingOp.error);
2288 [callback addDependency:pendingOp];
2289 [self.operationQueue addOperation:callback];
2290 [self.operationQueue addOperation:pendingOp];
2293 - (void)rpcTrustStatusCachedStatus:(OTAccountMetadataClassC*)account
2294 reply:(void (^)(CliqueStatus status,
2295 NSString* egoPeerID,
2296 NSDictionary<NSString*, NSNumber*>* _Nullable peerCountByModelID,
2298 NSError *error))reply
2300 CliqueStatus status = CliqueStatusAbsent;
2302 if (account.trustState == OTAccountMetadataClassC_TrustState_TRUSTED) {
2303 status = CliqueStatusIn;
2304 } else if (account.trustState == OTAccountMetadataClassC_TrustState_UNTRUSTED) {
2305 status = CliqueStatusNotIn;
2308 secnotice("octagon", "returning cached clique status: %@", OTCliqueStatusToString(status));
2309 reply(status, account.peerID, nil, NO, NULL);
2313 - (void)rpcTrustStatus:(OTOperationConfiguration *)configuration
2314 reply:(void (^)(CliqueStatus status,
2315 NSString* _Nullable peerID,
2316 NSDictionary<NSString*, NSNumber*>* _Nullable peerCountByModelID,
2318 NSError *error))reply
2320 __block NSError* localError = nil;
2322 OTAccountMetadataClassC* account = [self.accountMetadataStore loadOrCreateAccountMetadata:&localError];
2323 if(localError && [self.lockStateTracker isLockedError:localError]){
2324 secnotice("octagon", "Device is locked! pending initialization on unlock");
2325 reply(CliqueStatusError, nil, nil, NO, localError);
2329 if(account.icloudAccountState == OTAccountMetadataClassC_AccountState_NO_ACCOUNT) {
2330 secnotice("octagon", "no account! returning clique status 'no account'");
2331 reply(CliqueStatusNoCloudKitAccount, nil, nil, NO, NULL);
2335 if (configuration.useCachedAccountStatus) {
2336 [self rpcTrustStatusCachedStatus:account reply:reply];
2340 CKKSAccountStatus ckAccountStatus = [self checkForCKAccount:configuration];
2341 if(ckAccountStatus == CKKSAccountStatusNoAccount) {
2342 secnotice("octagon", "No cloudkit account present");
2343 reply(CliqueStatusNoCloudKitAccount, nil, nil, NO, NULL);
2345 } else if(ckAccountStatus == CKKSAccountStatusUnknown) {
2346 secnotice("octagon", "Unknown cloudkit account status, returning cached trust value");
2347 [self rpcTrustStatusCachedStatus:account reply:reply];
2351 __block NSString* peerID = nil;
2352 __block NSDictionary<NSString*, NSNumber*>* peerModelCounts = nil;
2353 __block BOOL excluded = NO;
2354 __block CliqueStatus trustStatus = CliqueStatusError;
2356 [self.cuttlefishXPCWrapper trustStatusWithContainer:self.containerName
2357 context:self.contextID
2358 reply:^(TrustedPeersHelperEgoPeerStatus *egoStatus,
2359 NSError *xpcError) {
2360 TPPeerStatus status = egoStatus.egoStatus;
2361 peerID = egoStatus.egoPeerID;
2362 excluded = egoStatus.isExcluded;
2363 peerModelCounts = egoStatus.viablePeerCountsByModelID;
2364 localError = xpcError;
2367 secnotice("octagon", "error fetching trust status: %@", xpcError);
2369 secnotice("octagon", "trust status: %@", TPPeerStatusToString(status));
2371 if((status&TPPeerStatusExcluded) == TPPeerStatusExcluded){
2372 trustStatus = CliqueStatusNotIn;
2374 else if((status&TPPeerStatusPartiallyReciprocated) == TPPeerStatusPartiallyReciprocated){
2375 trustStatus = CliqueStatusIn;
2377 else if((status&TPPeerStatusAncientEpoch) == TPPeerStatusAncientEpoch){
2378 //FIX ME HANDLE THIS CASE
2379 trustStatus= CliqueStatusIn;
2381 else if((status&TPPeerStatusOutdatedEpoch) == TPPeerStatusOutdatedEpoch){
2382 //FIX ME HANDLE THIS CASE
2383 trustStatus = CliqueStatusIn;
2385 else if((status&TPPeerStatusFullyReciprocated) == TPPeerStatusFullyReciprocated){
2386 trustStatus = CliqueStatusIn;
2388 else if((status&TPPeerStatusUnknown) == TPPeerStatusUnknown){
2389 trustStatus = CliqueStatusAbsent;
2391 else if ((status&TPPeerStatusSelfTrust) == TPPeerStatusSelfTrust) {
2392 trustStatus = CliqueStatusIn;
2395 secnotice("octagon", "TPPeerStatus is empty");
2396 trustStatus = CliqueStatusAbsent;
2401 if(trustStatus == CliqueStatusIn && self.postedRepairCFU == YES){
2402 NSError* clearError = nil;
2403 [self.followupHandler clearFollowUp:OTFollowupContextTypeStateRepair error:&clearError];
2404 // TODO(caw): should we clear this flag if `clearFollowUpForContext` fails?
2405 self.postedRepairCFU = NO;
2407 reply(trustStatus, peerID, peerModelCounts, excluded, localError);
2410 - (void)rpcFetchAllViableBottles:(void (^)(NSArray<NSString*>* _Nullable sortedBottleIDs, NSArray<NSString*>* _Nullable sortedPartialEscrowRecordIDs, NSError* _Nullable error))reply
2412 if ([self checkForCKAccount:nil] != CKKSAccountStatusAvailable) {
2413 secnotice("octagon", "No cloudkit account present");
2414 reply(NULL, NULL, [self errorNoiCloudAccount]);
2418 // As this isn't a state-modifying operation, we don't need to go through the state machine.
2419 [self.cuttlefishXPCWrapper fetchViableBottlesWithContainer:self.containerName
2420 context:self.contextID
2421 reply:^(NSArray<NSString*>* _Nullable sortedEscrowRecordIDs, NSArray<NSString*>* _Nullable sortedPartialEscrowRecordIDs, NSError * _Nullable error) {
2423 secerror("octagon: error fetching all viable bottles: %@", error);
2424 reply(nil, nil, error);
2426 secnotice("octagon", "fetched viable bottles: %@", sortedEscrowRecordIDs);
2427 secnotice("octagon", "fetched partially viable bottles: %@", sortedPartialEscrowRecordIDs);
2428 reply(sortedEscrowRecordIDs, sortedPartialEscrowRecordIDs, error);
2433 - (void)fetchEscrowContents:(void (^)(NSData* _Nullable entropy,
2434 NSString* _Nullable bottleID,
2435 NSData* _Nullable signingPublicKey,
2436 NSError* _Nullable error))reply
2438 // As this isn't a state-modifying operation, we don't need to go through the state machine.
2439 [self.cuttlefishXPCWrapper fetchEscrowContentsWithContainer:self.containerName
2440 context:self.contextID
2441 reply:^(NSData * _Nullable entropy, NSString * _Nullable bottleID, NSData * _Nullable signingPublicKey, NSError * _Nullable error) {
2443 secerror("octagon: error fetching escrow contents: %@", error);
2444 reply(nil, nil, nil, error);
2446 secnotice("octagon", "fetched escrow contents for bottle: %@", bottleID);
2447 reply(entropy, bottleID, signingPublicKey, error);
2452 - (void)rpcValidatePeers:(void (^)(NSDictionary* _Nullable result, NSError* _Nullable error))reply
2454 __block NSMutableDictionary* result = [NSMutableDictionary dictionary];
2456 result[@"containerName"] = self.containerName;
2457 result[@"contextID"] = self.contextID;
2458 result[@"state"] = [self.stateMachine waitForState:OctagonStateReady wait:3*NSEC_PER_SEC];
2460 if ([self checkForCKAccount:nil] != CKKSAccountStatusAvailable) {
2461 secnotice("octagon", "No cloudkit account present");
2462 reply(NULL, [self errorNoiCloudAccount]);
2466 [self.cuttlefishXPCWrapper validatePeersWithContainer:self.containerName
2467 context:self.contextID
2468 reply:^(NSDictionary * _Nullable validateData, NSError * _Nullable dumpError) {
2469 secnotice("octagon", "Finished validatePeers for status RPC");
2471 result[@"error"] = dumpError;
2473 result[@"validate"] = validateData;
2480 #pragma mark --- Testing
2481 - (void) setAccountStateHolder:(OTCuttlefishAccountStateHolder*)accountMetadataStore
2483 self.accountMetadataStore = accountMetadataStore;
2486 - (void)setPostedBool:(BOOL)posted
2488 self.postedRepairCFU = posted;
2491 #pragma mark --- Health Checker
2493 - (BOOL)postRepairCFU:(NSError**)error
2495 NSError* localError = nil;
2496 BOOL postSuccess = NO;
2497 if (self.postedRepairCFU == NO) {
2498 [self.followupHandler postFollowUp:OTFollowupContextTypeStateRepair error:&localError];
2500 secerror("octagon-health: CoreCDP repair failed: %@", localError);
2502 *error = localError;
2506 secnotice("octagon-health", "CoreCDP post repair success");
2507 self.postedRepairCFU = YES;
2511 secnotice("octagon-health", "already posted a repair CFU!");
2516 - (BOOL)shouldPostConfirmPasscodeCFU:(NSError**)error
2518 NSError* localError = nil;
2519 id<SecEscrowRequestable> request = [self.escrowRequestClass request:&localError];
2520 if(!request || localError) {
2521 secnotice("octagon-health", "Unable to acquire a EscrowRequest object: %@", localError);
2523 *error = localError;
2527 BOOL pendingUpload = [request pendingEscrowUpload:&localError];
2530 secnotice("octagon-health", "Failed to check escrow prerecord status: %@", localError);
2532 *error = localError;
2537 if(pendingUpload == YES) {
2538 secnotice("octagon-health", "prerecord is pending, NOT posting CFU");
2541 secnotice("octagon-health", "no pending prerecords, posting CFU");
2546 - (void)postConfirmPasscodeCFU:(NSError**)error
2548 NSError* localError = nil;
2549 if (self.postedEscrowRepairCFU == NO) {
2550 [self.followupHandler postFollowUp:OTFollowupContextTypeOfflinePasscodeChange error:&localError];
2552 secerror("octagon-health: CoreCDP offline passcode change failed: %@", localError);
2553 *error = localError;
2556 secnotice("octagon-health", "CoreCDP offline passcode change success");
2557 self.postedEscrowRepairCFU = YES;
2560 secnotice("octagon-health", "already posted escrow CFU");
2564 - (void)postRecoveryKeyCFU:(NSError**)error
2566 NSError* localError = nil;
2567 if (self.postedRecoveryKeyCFU == NO) {
2568 [self.followupHandler postFollowUp:OTFollowupContextTypeRecoveryKeyRepair error:&localError];
2570 secerror("octagon-health: CoreCDP recovery key cfu failed: %@", localError);
2573 secnotice("octagon-health", "CoreCDP recovery key cfu success");
2574 self.postedRecoveryKeyCFU = YES;
2577 secnotice("octagon-health", "already posted recovery key CFU");
2581 - (void)checkOctagonHealth:(BOOL)skipRateLimitingCheck reply:(void (^)(NSError * _Nullable error))reply
2583 secnotice("octagon-health", "Beginning checking overall Octagon Trust");
2585 _skipRateLimitingCheck = skipRateLimitingCheck;
2587 // Ending in "waitforunlock" is okay for a health check
2588 [self.stateMachine doWatchedStateMachineRPC:@"octagon-trust-health-check"
2589 sourceStates:OctagonHealthSourceStates()
2590 path:[OctagonStateTransitionPath pathFromDictionary:@{
2591 OctagonStateHSA2HealthCheck: @{
2592 OctagonStateSecurityTrustCheck: @{
2593 OctagonStateTPHTrustCheck: @{
2594 OctagonStateCuttlefishTrustCheck: @{
2595 OctagonStateBecomeReady: @{
2596 OctagonStateReady: [OctagonStateTransitionPathStep success],
2597 OctagonStateWaitForUnlock: [OctagonStateTransitionPathStep success],
2599 // Cuttlefish can suggest we reset the world. Consider reaching here a success,
2600 // instead of tracking the whole reset.
2601 OctagonStateHealthCheckReset: [OctagonStateTransitionPathStep success],
2605 OctagonStateWaitForHSA2: [OctagonStateTransitionPathStep success],
2611 - (void)attemptSOSUpgrade:(void (^)(NSError* _Nullable error))reply
2613 secnotice("octagon-sos", "attempting to perform an sos upgrade");
2615 if ([self checkForCKAccount:nil] != CKKSAccountStatusAvailable) {
2616 secnotice("octagon-sos", "No cloudkit account present");
2617 reply([self errorNoiCloudAccount]);
2621 NSSet* sourceStates = [NSSet setWithArray: @[OctagonStateUntrusted]];
2623 OTSOSUpgradeOperation *pendingOp = [[OTSOSUpgradeOperation alloc] initWithDependencies:self.operationDependencies
2624 intendedState:OctagonStateBecomeReady
2625 ckksConflictState:OctagonStateBecomeUntrusted
2626 errorState:OctagonStateBecomeUntrusted
2627 deviceInfo:self.prepareInformation];
2629 OctagonStateTransitionRequest<OTSOSUpgradeOperation*>* request = [[OctagonStateTransitionRequest alloc] init:@"attempt-sos-upgrade"
2630 sourceStates:sourceStates
2631 serialQueue:self.queue
2632 timeout:OctagonStateTransitionDefaultTimeout
2633 transitionOp:pendingOp];
2635 CKKSResultOperation* callback = [CKKSResultOperation named:@"sos-upgrade-callback"
2637 secnotice("otrpc", "Returning from an sos upgrade attempt: %@", pendingOp.error);
2638 [[CKKSAnalytics logger] logResultForEvent:OctagonEventUpgradePreapprovedJoinAfterPairing hardFailure:false result:pendingOp.error];
2639 reply(pendingOp.error);
2642 [callback addDependency:pendingOp];
2643 [self.operationQueue addOperation: callback];
2645 [self.stateMachine handleExternalRequest:request];
2648 - (void)waitForOctagonUpgrade:(void (^)(NSError* error))reply
2650 secnotice("octagon-sos", "waitForOctagonUpgrade");
2652 if (!self.sosAdapter.sosEnabled) {
2653 secnotice("octagon-sos", "sos not enabled, nothing to do for waitForOctagonUpgrade");
2658 if ([self.stateMachine isPaused]) {
2659 if ([[self.stateMachine currentState] isEqualToString:OctagonStateReady]) {
2660 secnotice("octagon-sos", "waitForOctagonUpgrade: already ready, returning");
2665 if ([[self.stateMachine waitForState:OctagonStateReady wait:10*NSEC_PER_SEC] isEqualToString:OctagonStateReady]) {
2666 secnotice("octagon-sos", "waitForOctagonUpgrade: in ready (after waiting), returning");
2670 secnotice("octagon-sos", "waitForOctagonUpgrade: fail to get to ready after timeout, attempting upgrade");
2674 NSSet* sourceStates = [NSSet setWithArray: @[OctagonStateUntrusted]];
2676 OctagonStateTransitionPath* path = [OctagonStateTransitionPath pathFromDictionary:@{
2677 OctagonStateAttemptSOSUpgrade: @{
2678 OctagonStateBecomeReady: @{
2679 OctagonStateReady: [OctagonStateTransitionPathStep success],
2684 [self.stateMachine doWatchedStateMachineRPC:@"sos-upgrade-to-ready"
2685 sourceStates:sourceStates
2690 - (void)clearPendingCFUFlags
2692 self.postedRecoveryKeyCFU = NO;
2693 self.postedEscrowRepairCFU = NO;
2694 self.postedRepairCFU = NO;
2697 // Metrics passthroughs
2699 - (BOOL)machineIDOnMemoizedList:(NSString*)machineID error:(NSError**)error
2701 __block BOOL onList = NO;
2702 __block NSError* reterror = nil;
2703 [self.cuttlefishXPCWrapper fetchAllowedMachineIDsWithContainer:self.containerName
2704 context:self.contextID
2705 reply:^(NSSet<NSString *> * _Nonnull machineIDs, NSError * _Nullable miderror) {
2707 secnotice("octagon-metrics", "Failed to fetch allowed machineIDs: %@", miderror);
2708 reterror = miderror;
2710 if([machineIDs containsObject:machineID]) {
2713 secnotice("octagon-metrics", "MID (%@) on list: %@", machineID, onList ? @"yes" : @"no");
2717 if(reterror && error) {
2723 - (NSNumber* _Nullable)numberOfPeersInModelWithMachineID:(NSString*)machineID error:(NSError**)error
2725 __block NSNumber* ret = nil;
2726 __block NSError* retError = nil;
2727 [self.cuttlefishXPCWrapper trustStatusWithContainer:self.containerName
2728 context:self.contextID
2729 reply:^(TrustedPeersHelperEgoPeerStatus *egoStatus,
2730 NSError *xpcError) {
2732 secnotice("octagon-metrics", "Unable to fetch trust status: %@", xpcError);
2733 retError = xpcError;
2735 ret = egoStatus.peerCountsByMachineID[machineID] ?: @(0);
2736 secnotice("octagon-metrics", "Number of peers with machineID (%@): %@", machineID, ret);
2740 if(retError && error) {