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 initialState:OctagonStateInitializing
192 lockStateTracker:lockStateTracker];
194 _sosAdapter = sosAdapter;
195 [_sosAdapter registerForPeerChangeUpdates:self];
196 _authKitAdapter = authKitAdapter;
197 _deviceAdapter = deviceInformationAdapter;
198 [_deviceAdapter registerForDeviceNameUpdates:self];
200 _cuttlefishXPCWrapper = [[CuttlefishXPCWrapper alloc] initWithCuttlefishXPCConnection:cuttlefish];
201 _lockStateTracker = lockStateTracker;
202 _accountStateTracker = accountStateTracker;
204 _followupHandler = [[OTFollowup alloc] initWithFollowupController:cdpd];
206 [accountStateTracker registerForNotificationsOfCloudKitAccountStatusChange:self];
207 [_authKitAdapter registerNotification:self];
209 _escrowRequestClass = escrowRequestClass;
211 _suggestTLKUploadNotifier = [[CKKSNearFutureScheduler alloc] initWithName:@"octagon-tlk-request"
212 delay:500*NSEC_PER_MSEC
213 keepProcessAlive:false
214 dependencyDescriptionCode:0
217 secnotice("octagon-ckks", "Adding flag for CKKS TLK upload");
218 [self.stateMachine handleFlag:OctagonFlagCKKSRequestsTLKUpload];
226 // TODO: how to invalidate this?
227 //[self.cuttlefishXPCWrapper invalidate];
230 - (void)notifyTrustChanged:(OTAccountMetadataClassC_TrustState)trustState {
232 secnotice("octagon", "Changing trust status to: %@",
233 (trustState == OTAccountMetadataClassC_TrustState_TRUSTED) ? @"Trusted" : @"Untrusted");
236 * We are posting the legacy SOS notification if we don't use SOS
237 * need to rework clients to use a new signal instead of SOS.
239 if (!OctagonPlatformSupportsSOS()) {
240 notify_post(kSOSCCCircleChangedNotification);
243 notify_post(OTTrustStatusChangeNotification);
246 - (void)accountStateUpdated:(OTAccountMetadataClassC*)newState from:(OTAccountMetadataClassC *)oldState
248 if (newState.icloudAccountState == OTAccountMetadataClassC_AccountState_ACCOUNT_AVAILABLE && oldState.icloudAccountState != OTAccountMetadataClassC_AccountState_ACCOUNT_AVAILABLE) {
249 [self.launchSequence addEvent:@"iCloudAccount"];
252 if (newState.trustState == OTAccountMetadataClassC_TrustState_TRUSTED && oldState.trustState != OTAccountMetadataClassC_TrustState_TRUSTED) {
253 [self.launchSequence addEvent:@"Trusted"];
255 if (newState.trustState != OTAccountMetadataClassC_TrustState_TRUSTED && oldState.trustState == OTAccountMetadataClassC_TrustState_TRUSTED) {
256 [self.launchSequence addEvent:@"Untrusted"];
257 [self notifyTrustChanged:newState.trustState];
261 - (NSString*)description
263 return [NSString stringWithFormat:@"<OTCuttlefishContext: %@, %@>", self.containerName, self.contextID];
266 - (void)machinesAdded:(NSArray<NSString*>*)machineIDs altDSID:(NSString*)altDSID
269 NSError* metadataError = nil;
270 OTAccountMetadataClassC* accountMetadata = [self.accountMetadataStore loadOrCreateAccountMetadata:&metadataError];
272 if(!accountMetadata || metadataError) {
273 // TODO: collect a sysdiagnose here if the error is not "device is in class D"
274 secerror("octagon-authkit: Unable to load account metadata: %@", metadataError);
275 [self requestTrustedDeviceListRefresh];
279 if(!altDSID || ![accountMetadata.altDSID isEqualToString:altDSID]) {
280 secnotice("octagon-authkit", "Machines-added push is for wrong altDSID (%@); current altDSID (%@)", altDSID, accountMetadata.altDSID);
284 secnotice("octagon-authkit", "adding machines for altDSID(%@): %@", altDSID, machineIDs);
286 [self.cuttlefishXPCWrapper addAllowedMachineIDsWithContainer:self.containerName
287 context:self.contextID
288 machineIDs:machineIDs
289 reply:^(NSError* error) {
292 secerror("octagon-authkit: addAllow errored: %@", error);
293 [self requestTrustedDeviceListRefresh];
295 secnotice("octagon-authkit", "addAllow succeeded");
297 OctagonPendingFlag *pendingFlag = [[OctagonPendingFlag alloc] initWithFlag:OctagonFlagCuttlefishNotification
298 conditions:OctagonPendingConditionsDeviceUnlocked];
299 [self.stateMachine handlePendingFlag:pendingFlag];
304 - (void)machinesRemoved:(NSArray<NSString*>*)machineIDs altDSID:(NSString*)altDSID
308 NSError* metadataError = nil;
309 OTAccountMetadataClassC* accountMetadata = [self.accountMetadataStore loadOrCreateAccountMetadata:&metadataError];
311 if(!accountMetadata || metadataError) {
312 // TODO: collect a sysdiagnose here if the error is not "device is in class D"
313 secerror("octagon-authkit: Unable to load account metadata: %@", metadataError);
314 [self requestTrustedDeviceListRefresh];
318 if(!altDSID || ![accountMetadata.altDSID isEqualToString:altDSID]) {
319 secnotice("octagon-authkit", "Machines-removed push is for wrong altDSID (%@); current altDSID (%@)", altDSID, accountMetadata.altDSID);
323 secnotice("octagon-authkit", "removing machines for altDSID(%@): %@", altDSID, machineIDs);
325 [self.cuttlefishXPCWrapper removeAllowedMachineIDsWithContainer:self.containerName
326 context:self.contextID
327 machineIDs:machineIDs
328 reply:^(NSError* _Nullable error) {
331 secerror("octagon-authkit: removeAllow errored: %@", error);
333 secnotice("octagon-authkit", "removeAllow succeeded");
336 // We don't necessarily trust remove pushes; they could be delayed past when an add has occurred.
337 // Request that the full list be rechecked.
338 [self requestTrustedDeviceListRefresh];
342 - (void)incompleteNotificationOfMachineIDListChange
344 secnotice("octagon", "incomplete machine ID list notification -- refreshing device list");
345 [self requestTrustedDeviceListRefresh];
349 - (void)cloudkitAccountStateChange:(CKAccountInfo* _Nullable)oldAccountInfo
350 to:(CKAccountInfo*)currentAccountInfo
352 dispatch_sync(self.queue, ^{
353 // We don't persist the CK account state; rather, we fetch it anew on every daemon launch.
354 // But, we also have to integrate it into our asynchronous state machine.
355 // So, record the current CK account value, and trigger state machine reprocessing.
357 secnotice("octagon", "Told of a new CK account status: %@", currentAccountInfo);
358 self.cloudKitAccountInfo = currentAccountInfo;
359 [self.stateMachine _onqueuePokeStateMachine];
361 // But, having the state machine perform the signout is confusing: it would need to make decisions based
362 // on things other than the current state. So, use the RPC mechanism to give it input.
363 // If we receive a sign-in before the sign-out rpc runs, the state machine will be sufficient to get back into
364 // the in-account state.
366 // Also let other clients now that we have CK account status
367 [self.cloudKitAccountStateKnown fulfill];
370 if(!(currentAccountInfo.accountStatus == CKAccountStatusAvailable)) {
371 secnotice("octagon", "Informed that the CK account is now unavailable: %@", currentAccountInfo);
373 // Add a state machine request to return to OctagonStateWaitingForCloudKitAccount
374 [self.stateMachine doSimpleStateMachineRPC:@"cloudkit-account-gone"
375 op:[OctagonStateTransitionOperation named:@"cloudkit-account-gone"
376 entering:OctagonStateWaitingForCloudKitAccount]
377 sourceStates:OctagonInAccountStates()
378 reply:^(NSError* error) {}];
382 - (BOOL)accountAvailable:(NSString*)altDSID error:(NSError**)error
384 __block NSError* localError = nil;
385 secnotice("octagon", "Account available with altDSID: %@ %@", altDSID, self);
387 self.launchSequence.firstLaunch = true;
389 dispatch_sync(self.queue, ^{
390 [self.accountMetadataStore _onqueuePersistAccountChanges:^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);
402 [self.stateMachine handleFlag:OctagonFlagAccountIsAvailable];
412 - (void) moveToCheckTrustedState
414 CKKSResultOperation<OctagonStateTransitionOperationProtocol>* checkTrust
415 = [OctagonStateTransitionOperation named:[NSString stringWithFormat:@"%@", @"check-trust-state"]
416 entering:OctagonStateCheckTrustState];
418 NSSet* sourceStates = [NSSet setWithArray: @[OctagonStateUntrusted]];
420 OctagonStateTransitionRequest<OctagonStateTransitionOperation*>* request = [[OctagonStateTransitionRequest alloc] init:@"check-trust-state"
421 sourceStates:sourceStates
422 serialQueue:self.queue
423 timeout:OctagonStateTransitionDefaultTimeout
424 transitionOp:checkTrust];
425 [self.stateMachine handleExternalRequest:request];
429 - (BOOL)idmsTrustLevelChanged:(NSError**)error
431 [self.stateMachine handleFlag:OctagonFlagIDMSLevelChanged];
435 - (BOOL)accountNoLongerAvailable:(NSError**)error
437 OctagonStateTransitionOperation* attemptOp = [OctagonStateTransitionOperation named:@"octagon-account-gone"
438 intending:OctagonStateNoAccountDoReset
439 errorState:OctagonStateNoAccountDoReset
440 timeout:OctagonStateTransitionDefaultTimeout
441 withBlockTakingSelf:^(OctagonStateTransitionOperation * _Nonnull op) {
442 __block NSError* localError = nil;
444 secnotice("octagon", "Account now unavailable: %@", self);
445 [self.accountMetadataStore persistAccountChanges:^OTAccountMetadataClassC *(OTAccountMetadataClassC * metadata) {
446 metadata.icloudAccountState = OTAccountMetadataClassC_AccountState_NO_ACCOUNT;
447 metadata.altDSID = nil;
448 metadata.trustState = OTAccountMetadataClassC_TrustState_UNKNOWN;
451 } error:&localError];
454 secerror("octagon: unable to persist new account availability: %@", localError);
457 [self.accountStateTracker setHSA2iCloudAccountStatus:CKKSAccountStatusNoAccount];
459 // Bring CKKS down, too
460 for (id key in self.viewManager.views) {
461 CKKSKeychainView* view = self.viewManager.views[key];
462 secnotice("octagon-ckks", "Informing %@ of new untrusted status (due to account disappearance)", view);
463 [view endTrustedOperation];
466 op.error = localError;
469 // Signout works from literally any state. Goodbye, account!
470 NSSet* sourceStates = [NSSet setWithArray: OctagonStateMap().allKeys];
471 OctagonStateTransitionRequest<OctagonStateTransitionOperation*>* request = [[OctagonStateTransitionRequest alloc] init:@"account-not-available"
472 sourceStates:sourceStates
473 serialQueue:self.queue
474 timeout:OctagonStateTransitionDefaultTimeout
475 transitionOp:attemptOp];
476 [self.stateMachine handleExternalRequest:request];
481 - (void)resetOctagonStateMachine
483 OctagonStateTransitionOperation* op = [OctagonStateTransitionOperation named:@"resetting-state-machine"
484 entering:OctagonStateInitializing];
485 NSMutableSet* sourceStates = [NSMutableSet setWithArray: OctagonStateMap().allKeys];
487 OctagonStateTransitionRequest<OctagonStateTransitionOperation*>* request = [[OctagonStateTransitionRequest alloc] init:@"resetting-state-machine"
488 sourceStates:sourceStates
489 serialQueue:self.queue
490 timeout:OctagonStateTransitionDefaultTimeout
493 [self.stateMachine handleExternalRequest:request];
497 - (void)localReset:(nonnull void (^)(NSError * _Nullable))reply
499 OTLocalResetOperation* pendingOp = [[OTLocalResetOperation alloc] init:self.containerName
500 contextID:self.contextID
501 intendedState:OctagonStateBecomeUntrusted
502 errorState:OctagonStateError
503 cuttlefishXPCWrapper:self.cuttlefishXPCWrapper];
505 NSMutableSet* sourceStates = [NSMutableSet setWithArray: OctagonStateMap().allKeys];
506 [self.stateMachine doSimpleStateMachineRPC:@"local-reset" op:pendingOp sourceStates:sourceStates reply:reply];
509 - (NSDictionary*)establishStatePathDictionary
512 OctagonStateReEnactDeviceList: @{
513 OctagonStateReEnactPrepare: @{
514 OctagonStateReEnactReadyToEstablish: @{
515 OctagonStateEscrowTriggerUpdate: @{
516 OctagonStateBecomeReady: @{
517 OctagonStateReady: [OctagonStateTransitionPathStep success],
521 // Error handling extra states:
522 OctagonStateEstablishCKKSReset: @{
523 OctagonStateEstablishAfterCKKSReset: @{
524 OctagonStateEscrowTriggerUpdate: @{
525 OctagonStateBecomeReady: @{
526 OctagonStateReady: [OctagonStateTransitionPathStep success],
537 - (void)rpcEstablish:(nonnull NSString *)altDSID
538 reply:(nonnull void (^)(NSError * _Nullable))reply
540 // The reset flow can split into an error-handling path halfway through; this is okay
541 OctagonStateTransitionPath* path = [OctagonStateTransitionPath pathFromDictionary:[self establishStatePathDictionary]];
543 [self.stateMachine doWatchedStateMachineRPC:@"establish"
544 sourceStates:OctagonInAccountStates()
549 - (void)rpcResetAndEstablish:(CuttlefishResetReason)resetReason reply:(nonnull void (^)(NSError * _Nullable))reply
551 _resetReason = resetReason;
553 // The reset flow can split into an error-handling path halfway through; this is okay
554 OctagonStateTransitionPath* path = [OctagonStateTransitionPath pathFromDictionary: @{
555 OctagonStateResetBecomeUntrusted: @{
556 OctagonStateResetAndEstablish: @{
557 OctagonStateResetAnyMissingTLKCKKSViews: [self establishStatePathDictionary]
562 // Now, take the state machine from any in-account state to the beginning of the reset flow.
563 [self.stateMachine doWatchedStateMachineRPC:@"rpc-reset-and-establish"
564 sourceStates:OctagonInAccountStates()
569 - (void)rpcLeaveClique:(nonnull void (^)(NSError * _Nullable))reply
571 OTLeaveCliqueOperation* op = [[OTLeaveCliqueOperation alloc] initWithDependencies:self.operationDependencies
572 intendedState:OctagonStateBecomeUntrusted
573 errorState:OctagonStateError];
575 NSSet* sourceStates = [NSSet setWithObject: OctagonStateReady];
576 [self.stateMachine doSimpleStateMachineRPC:@"leave-clique" op:op sourceStates:sourceStates reply:reply];
579 - (void)rpcRemoveFriendsInClique:(NSArray<NSString*>*)peerIDs
580 reply:(void (^)(NSError * _Nullable))reply
582 OTRemovePeersOperation* op = [[OTRemovePeersOperation alloc] initWithDependencies:self.operationDependencies
583 intendedState:OctagonStateBecomeReady
584 errorState:OctagonStateBecomeReady
587 NSSet* sourceStates = [NSSet setWithObject: OctagonStateReady];
588 [self.stateMachine doSimpleStateMachineRPC:@"remove-friends" op:op sourceStates:sourceStates reply:reply];
591 - (OTDeviceInformation*)prepareInformation
593 NSError* error = nil;
594 NSString* machineID = [self.authKitAdapter machineID:&error];
596 if(!machineID || error) {
597 secerror("octagon: Unable to fetch machine ID; expect signin to fail: %@", error);
600 return [[OTDeviceInformation alloc] initForContainerName:self.containerName
601 contextID:self.contextID
604 modelID:self.deviceAdapter.modelID
605 deviceName:self.deviceAdapter.deviceName
606 serialNumber:self.deviceAdapter.serialNumber
607 osVersion:self.deviceAdapter.osVersion];
610 - (OTOperationDependencies*)operationDependencies
612 return [[OTOperationDependencies alloc] initForContainer:self.containerName
613 contextID:self.contextID
614 stateHolder:self.accountMetadataStore
615 flagHandler:self.stateMachine
616 sosAdapter:self.sosAdapter
617 octagonAdapter:self.octagonAdapter
618 authKitAdapter:self.authKitAdapter
619 deviceInfoAdapter:self.deviceAdapter
620 viewManager:self.viewManager
621 lockStateTracker:self.lockStateTracker
622 cuttlefishXPCWrapper:self.cuttlefishXPCWrapper
623 escrowRequestClass:self.escrowRequestClass];
626 - (void)startOctagonStateMachine
628 [self.stateMachine startOperation];
631 - (void)handlePairingRestart:(OTJoiningConfiguration*)config
633 if(self.pairingUUID == nil){
634 secnotice("octagon-pairing", "received new pairing UUID (%@)", config.pairingUUID);
635 self.pairingUUID = config.pairingUUID;
638 if(![self.pairingUUID isEqualToString:config.pairingUUID]){
639 secnotice("octagon-pairing", "current pairing UUID (%@) does not match config UUID (%@)", self.pairingUUID, config.pairingUUID);
641 dispatch_semaphore_t sema = dispatch_semaphore_create(0);
642 [self localReset:^(NSError * _Nullable localResetError) {
643 if(localResetError) {
644 secerror("localReset returned an error: %@", localResetError);
646 secnotice("octagon", "localReset succeeded");
647 self.pairingUUID = config.pairingUUID;
649 dispatch_semaphore_signal(sema);
651 if (0 != dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 10))) {
652 secerror("octagon: Timed out waiting for local reset to complete");
657 #pragma mark --- State Machine Transitions
659 - (CKKSResultOperation<OctagonStateTransitionOperationProtocol>* _Nullable)_onqueueNextStateMachineTransition:(OctagonState*)currentState
660 flags:(nonnull OctagonFlags *)flags
661 pendingFlags:(nonnull id<OctagonStateOnqueuePendingFlagHandler>)pendingFlagHandler
663 dispatch_assert_queue(self.queue);
667 [self.launchSequence addEvent:currentState];
669 // If We're initializing, or there was some recent update to the account state,
670 // attempt to see what state we should enter.
671 if([currentState isEqualToString: OctagonStateInitializing]) {
672 return [self initializingOperation];
675 if([currentState isEqualToString:OctagonStateWaitForHSA2]) {
676 if([flags _onqueueContains:OctagonFlagIDMSLevelChanged]) {
677 [flags _onqueueRemoveFlag:OctagonFlagIDMSLevelChanged];
678 return [OctagonStateTransitionOperation named:@"hsa2-check"
679 entering:OctagonStateDetermineiCloudAccountState];
682 secnotice("octagon", "Waiting for an HSA2 account");
686 if([currentState isEqualToString:OctagonStateWaitingForCloudKitAccount]) {
687 // Here, integrate the memoized CK account state into our state machine
688 if(self.cloudKitAccountInfo && self.cloudKitAccountInfo.accountStatus == CKAccountStatusAvailable) {
689 secnotice("octagon", "CloudKit reports an account is available!");
690 return [OctagonStateTransitionOperation named:@"ck-available"
691 entering:OctagonStateCloudKitNewlyAvailable];
693 secnotice("octagon", "Waiting for a CloudKit account; current state is %@", self.cloudKitAccountInfo ?: @"uninitialized");
698 if([currentState isEqualToString:OctagonStateCloudKitNewlyAvailable]) {
699 return [self cloudKitAccountNewlyAvailableOperation];
702 if([currentState isEqualToString:OctagonStateCheckTrustState]) {
703 return [[OctagonCheckTrustStateOperation alloc] initWithDependencies:self.operationDependencies
704 intendedState:OctagonStateBecomeUntrusted
705 errorState:OctagonStateBecomeUntrusted];
707 #pragma mark --- Octagon Health Check States
708 if([currentState isEqualToString:OctagonStateHSA2HealthCheck]) {
709 return [[OTDetermineHSA2AccountStatusOperation alloc] initWithDependencies:self.operationDependencies
710 stateIfHSA2:OctagonStateSecurityTrustCheck
711 stateIfNotHSA2:OctagonStateWaitForHSA2
712 stateIfNoAccount:OctagonStateNoAccount
713 errorState:OctagonStateError];
716 if([currentState isEqualToString:OctagonStateSecurityTrustCheck]) {
717 return [self evaluateSecdOctagonTrust];
720 if([currentState isEqualToString:OctagonStateTPHTrustCheck]) {
721 return [self evaluateTPHOctagonTrust];
724 if([currentState isEqualToString:OctagonStateCuttlefishTrustCheck]) {
725 return [self cuttlefishTrustEvaluation];
728 if ([currentState isEqualToString:OctagonStatePostRepairCFU]) {
729 return [self postRepairCFUAndBecomeUntrusted];
732 if ([currentState isEqualToString:OctagonStateHealthCheckReset]) {
733 // A small violation of state machines...
734 _resetReason = CuttlefishResetReasonHealthCheck;
735 return [OctagonStateTransitionOperation named:@"begin-reset"
736 entering:OctagonStateResetBecomeUntrusted];
739 #pragma mark --- Watch Pairing States
742 if([currentState isEqualToString:OctagonStateStartCompanionPairing]) {
743 return [self startCompanionPairingOperation];
745 #endif /* TARGET_OS_WATCH */
747 if([currentState isEqualToString:OctagonStateBecomeUntrusted]) {
748 return [self becomeUntrustedOperation:OctagonStateUntrusted];
751 if([currentState isEqualToString:OctagonStateBecomeReady]) {
752 return [self becomeReadyOperation];
755 if([currentState isEqualToString:OctagonStateNoAccount]) {
756 // We only want to move out of untrusted if something useful has happened!
757 if([flags _onqueueContains:OctagonFlagAccountIsAvailable]) {
758 [flags _onqueueRemoveFlag:OctagonFlagAccountIsAvailable];
759 secnotice("octagon", "Account is available! Attempting initializing op!");
760 return [OctagonStateTransitionOperation named:@"account-probably-present"
761 entering:OctagonStateInitializing];
765 if([currentState isEqualToString:OctagonStateUntrusted]) {
766 // We only want to move out of untrusted if something useful has happened!
767 if([flags _onqueueContains:OctagonFlagEgoPeerPreapproved]) {
768 [flags _onqueueRemoveFlag:OctagonFlagEgoPeerPreapproved];
769 if(self.sosAdapter.sosEnabled) {
770 secnotice("octagon", "Preapproved flag is high. Attempt SOS upgrade again!");
771 return [OctagonStateTransitionOperation named:@"ck-available"
772 entering:OctagonStateAttemptSOSUpgrade];
775 secnotice("octagon", "We are untrusted, but it seems someone preapproves us now. Unfortunately, this platform doesn't support SOS.");
779 if([flags _onqueueContains:OctagonFlagAttemptSOSUpgrade]) {
780 [flags _onqueueRemoveFlag:OctagonFlagAttemptSOSUpgrade];
781 if(self.sosAdapter.sosEnabled) {
782 secnotice("octagon", "Attempt SOS upgrade again!");
783 return [OctagonStateTransitionOperation named:@"attempt-sos-upgrade"
784 entering:OctagonStateAttemptSOSUpgrade];
787 secnotice("octagon", "We are untrusted, but this platform doesn't support SOS.");
791 if([flags _onqueueContains:OctagonFlagCuttlefishNotification]) {
792 [flags _onqueueRemoveFlag:OctagonFlagCuttlefishNotification];
793 secnotice("octagon", "Updating TPH (while untrusted) due to push");
794 return [OctagonStateTransitionOperation named:@"untrusted-update"
795 entering:OctagonStateUntrustedUpdated];
798 // We're untrusted; no need for the IDMS level flag anymore
799 if([flags _onqueueContains:OctagonFlagIDMSLevelChanged]) {
800 [flags _onqueueRemoveFlag:OctagonFlagIDMSLevelChanged];
804 if([currentState isEqualToString:OctagonStateUntrustedUpdated]) {
805 return [[OTUpdateTPHOperation alloc] initWithDependencies:self.operationDependencies
806 intendedState:OctagonStateUntrusted
807 errorState:OctagonStateError
808 retryFlag:OctagonFlagCuttlefishNotification];
811 if([currentState isEqualToString:OctagonStateDetermineiCloudAccountState]) {
812 secnotice("octagon", "Determine iCloud account status");
814 // TODO replace with OTDetermineHSA2AccountStatusOperation in <rdar://problem/54094162> Octagon: ensure Octagon operations can't occur on SA accounts
815 return [OctagonStateTransitionOperation named:@"octagon-determine-icloud-state"
816 intending:OctagonStateNoAccount
817 errorState:OctagonStateError
818 timeout:OctagonStateTransitionDefaultTimeout
819 withBlockTakingSelf:^(OctagonStateTransitionOperation * _Nonnull op) {
822 NSString* primaryAccountAltDSID = self.authKitAdapter.primaryiCloudAccountAltDSID;
824 dispatch_sync(self.queue, ^{
825 NSError* error = nil;
827 if(primaryAccountAltDSID != nil) {
828 secnotice("octagon", "iCloud account is present; checking HSA2 status");
830 bool hsa2 = [self.authKitAdapter accountIsHSA2ByAltDSID:primaryAccountAltDSID];
831 secnotice("octagon", "HSA2 is %@", hsa2 ? @"enabled" : @"disabled");
833 [self.accountMetadataStore _onqueuePersistAccountChanges:^OTAccountMetadataClassC *(OTAccountMetadataClassC * metadata) {
835 metadata.icloudAccountState = OTAccountMetadataClassC_AccountState_ACCOUNT_AVAILABLE;
837 metadata.icloudAccountState = OTAccountMetadataClassC_AccountState_NO_ACCOUNT;
839 metadata.altDSID = primaryAccountAltDSID;
843 // If there's an HSA2 account, return to 'initializing' here, as we want to centralize decisions on what to do next
845 op.nextState = OctagonStateInitializing;
847 [self.accountStateTracker setHSA2iCloudAccountStatus:CKKSAccountStatusNoAccount];
848 op.nextState = OctagonStateWaitForHSA2;
852 secnotice("octagon", "iCloud account is not present");
854 [self.accountMetadataStore _onqueuePersistAccountChanges:^OTAccountMetadataClassC *(OTAccountMetadataClassC * metadata) {
855 metadata.icloudAccountState = OTAccountMetadataClassC_AccountState_NO_ACCOUNT;
856 metadata.altDSID = nil;
860 op.nextState = OctagonStateNoAccount;
864 secerror("octagon: unable to save new account state: %@", error);
870 if([currentState isEqualToString:OctagonStateNoAccountDoReset]) {
871 secnotice("octagon", "Attempting local-reset as part of signout");
872 return [[OTLocalResetOperation alloc] init:self.containerName
873 contextID:self.contextID
874 intendedState:OctagonStateNoAccount
875 errorState:OctagonStateNoAccount
876 cuttlefishXPCWrapper:self.cuttlefishXPCWrapper];
879 if([currentState isEqualToString:OctagonStateEnsureConsistency]) {
880 secnotice("octagon", "Ensuring consistency of things that might've changed");
881 if(self.sosAdapter.sosEnabled) {
882 return [[OTEnsureOctagonKeyConsistency alloc] initWithDependencies:self.operationDependencies
883 intendedState:OctagonStateEnsureUpdatePreapprovals
884 errorState:OctagonStateBecomeReady];
887 // Add further consistency checks here.
888 return [OctagonStateTransitionOperation named:@"no-consistency-checks"
889 entering:OctagonStateBecomeReady];
892 if([currentState isEqualToString:OctagonStateEnsureUpdatePreapprovals]) {
893 secnotice("octagon", "SOS is enabled; ensuring preapprovals are correct");
894 return [[OTSOSUpdatePreapprovalsOperation alloc] initWithDependencies:self.operationDependencies
895 intendedState:OctagonStateBecomeReady
896 sosNotPresentState:OctagonStateBecomeReady
897 errorState:OctagonStateBecomeReady];
900 if([currentState isEqualToString:OctagonStateAttemptSOSUpgrade] && OctagonPerformSOSUpgrade()) {
901 secnotice("octagon", "Investigating SOS status");
902 return [[OTSOSUpgradeOperation alloc] initWithDependencies:self.operationDependencies
903 intendedState:OctagonStateBecomeReady
904 ckksConflictState:OctagonStateSOSUpgradeCKKSReset
905 errorState:OctagonStateBecomeUntrusted
906 deviceInfo:self.prepareInformation];
908 } else if([currentState isEqualToString:OctagonStateSOSUpgradeCKKSReset]) {
909 return [[OTLocalCKKSResetOperation alloc] initWithDependencies:self.operationDependencies
910 intendedState:OctagonStateSOSUpgradeAfterCKKSReset
911 errorState:OctagonStateBecomeUntrusted];
913 } else if([currentState isEqualToString:OctagonStateSOSUpgradeAfterCKKSReset]) {
914 return [[OTSOSUpgradeOperation alloc] initWithDependencies:self.operationDependencies
915 intendedState:OctagonStateBecomeReady
916 ckksConflictState:OctagonStateBecomeUntrusted
917 errorState:OctagonStateBecomeUntrusted
918 deviceInfo:self.prepareInformation];
921 } else if([currentState isEqualToString:OctagonStateCreateIdentityForRecoveryKey]) {
922 OTPrepareOperation* op = [[OTPrepareOperation alloc] initWithDependencies:self.operationDependencies
923 intendedState:OctagonStateVouchWithRecoveryKey
924 errorState:OctagonStateBecomeUntrusted
925 deviceInfo:[self prepareInformation]
929 } else if([currentState isEqualToString:OctagonStateInitiatorCreateIdentity]) {
930 OTPrepareOperation* op = [[OTPrepareOperation alloc] initWithDependencies:self.operationDependencies
931 intendedState:OctagonStateInitiatorVouchWithBottle
932 errorState:OctagonStateBecomeUntrusted
933 deviceInfo:[self prepareInformation]
937 } else if([currentState isEqualToString:OctagonStateInitiatorVouchWithBottle]) {
938 OTVouchWithBottleOperation* pendingOp = [[OTVouchWithBottleOperation alloc] initWithDependencies:self.operationDependencies
939 intendedState:OctagonStateInitiatorUpdateDeviceList
940 errorState:OctagonStateBecomeUntrusted
943 bottleSalt:_bottleSalt];
945 CKKSResultOperation* callback = [CKKSResultOperation named:@"vouchWithBottle-callback"
947 secnotice("otrpc", "Returning a vouch with bottle call: %@, %@ %@", pendingOp.voucher, pendingOp.voucherSig, pendingOp.error);
948 self->_vouchSig = pendingOp.voucherSig;
949 self->_vouchData = pendingOp.voucher;
951 [callback addDependency:pendingOp];
952 [self.operationQueue addOperation: callback];
956 } else if([currentState isEqualToString:OctagonStateVouchWithRecoveryKey]) {
957 OTVouchWithRecoveryKeyOperation* pendingOp = [[OTVouchWithRecoveryKeyOperation alloc] initWithDependencies:self.operationDependencies
958 intendedState:OctagonStateInitiatorUpdateDeviceList
959 errorState:OctagonStateBecomeUntrusted
960 recoveryKey:_recoveryKey];
962 CKKSResultOperation* callback = [CKKSResultOperation named:@"vouchWithRecoveryKey-callback"
964 secnotice("otrpc", "Returning a vouch with recovery key call: %@, %@ %@", pendingOp.voucher, pendingOp.voucherSig, pendingOp.error);
965 self->_vouchSig = pendingOp.voucherSig;
966 self->_vouchData = pendingOp.voucher;
968 [callback addDependency:pendingOp];
969 [self.operationQueue addOperation: callback];
973 } else if([currentState isEqualToString:OctagonStateInitiatorUpdateDeviceList]) {
974 // As part of the 'initiate' flow, we need to update the trusted device list-you're probably on it already
975 OTUpdateTrustedDeviceListOperation* op = [[OTUpdateTrustedDeviceListOperation alloc] initWithDependencies:self.operationDependencies
976 intendedState:OctagonStateInitiatorJoin
977 listUpdatesState:OctagonStateInitiatorJoin
978 errorState:OctagonStateBecomeUntrusted
982 } else if ([currentState isEqualToString:OctagonStateInitiatorJoin]){
983 OTJoinWithVoucherOperation* op = [[OTJoinWithVoucherOperation alloc] initWithDependencies:self.operationDependencies
984 intendedState:OctagonStateBecomeReady
985 ckksConflictState:OctagonStateInitiatorJoinCKKSReset
986 errorState:OctagonStateBecomeUntrusted
987 voucherData:_vouchData
989 preapprovedKeys:_preapprovedKeys];
992 } else if([currentState isEqualToString:OctagonStateInitiatorJoinCKKSReset]) {
993 return [[OTLocalCKKSResetOperation alloc] initWithDependencies:self.operationDependencies
994 intendedState:OctagonStateInitiatorJoinAfterCKKSReset
995 errorState:OctagonStateBecomeUntrusted];
997 } else if ([currentState isEqualToString:OctagonStateInitiatorJoinAfterCKKSReset]){
998 return [[OTJoinWithVoucherOperation alloc] initWithDependencies:self.operationDependencies
999 intendedState:OctagonStateBecomeReady
1000 ckksConflictState:OctagonStateBecomeUntrusted
1001 errorState:OctagonStateBecomeUntrusted
1002 voucherData:_vouchData
1003 voucherSig:_vouchSig
1004 preapprovedKeys:_preapprovedKeys];
1006 } else if([currentState isEqualToString:OctagonStateResetBecomeUntrusted]) {
1007 return [self becomeUntrustedOperation:OctagonStateResetAndEstablish];
1009 } else if([currentState isEqualToString:OctagonStateResetAndEstablish]) {
1010 return [[OTResetOperation alloc] init:self.containerName
1011 contextID:self.contextID
1013 intendedState:OctagonStateResetAnyMissingTLKCKKSViews
1014 errorState:OctagonStateError
1015 cuttlefishXPCWrapper:self.cuttlefishXPCWrapper];
1017 } else if([currentState isEqualToString:OctagonStateResetAnyMissingTLKCKKSViews]) {
1018 return [[OTResetCKKSZonesLackingTLKsOperation alloc] initWithDependencies:self.operationDependencies
1019 intendedState:OctagonStateReEnactDeviceList
1020 errorState:OctagonStateError];
1022 } else if([currentState isEqualToString:OctagonStateReEnactDeviceList]) {
1023 return [[OTUpdateTrustedDeviceListOperation alloc] initWithDependencies:self.operationDependencies
1024 intendedState:OctagonStateReEnactPrepare
1025 listUpdatesState:OctagonStateReEnactPrepare
1026 errorState:OctagonStateError
1029 } else if([currentState isEqualToString:OctagonStateReEnactPrepare]) {
1030 // Note: Resetting the account returns epoch to 0.
1031 return [[OTPrepareOperation alloc] initWithDependencies:self.operationDependencies
1032 intendedState:OctagonStateReEnactReadyToEstablish
1033 errorState:OctagonStateError
1034 deviceInfo:[self prepareInformation]
1037 } else if([currentState isEqualToString:OctagonStateReEnactReadyToEstablish]) {
1038 return [[OTEstablishOperation alloc] initWithDependencies:self.operationDependencies
1039 intendedState:OctagonStateEscrowTriggerUpdate
1040 ckksConflictState:OctagonStateEstablishCKKSReset
1041 errorState:OctagonStateBecomeUntrusted];
1043 } else if([currentState isEqualToString:OctagonStateEstablishCKKSReset]) {
1044 return [[OTLocalCKKSResetOperation alloc] initWithDependencies:self.operationDependencies
1045 intendedState:OctagonStateEstablishAfterCKKSReset
1046 errorState:OctagonStateBecomeUntrusted];
1048 } else if([currentState isEqualToString:OctagonStateEstablishAfterCKKSReset]) {
1049 // If CKKS fails again, just go to "become untrusted"
1050 return [[OTEstablishOperation alloc] initWithDependencies:self.operationDependencies
1051 intendedState:OctagonStateEscrowTriggerUpdate
1052 ckksConflictState:OctagonStateBecomeUntrusted
1053 errorState:OctagonStateBecomeUntrusted];
1055 } else if ([currentState isEqualToString:OctagonStateEscrowTriggerUpdate]){
1057 return [[OTTriggerEscrowUpdateOperation alloc] initWithDependencies:self.operationDependencies
1058 intendedState:OctagonStateBecomeReady
1059 errorState:OctagonStateError];
1061 } else if([currentState isEqualToString: OctagonStateWaitForUnlock]) {
1062 if([flags _onqueueContains:OctagonFlagUnlocked]) {
1063 [flags _onqueueRemoveFlag:OctagonFlagUnlocked];
1064 return [OctagonStateTransitionOperation named:[NSString stringWithFormat:@"%@", @"initializing-after-unlock"]
1065 entering:OctagonStateInitializing];
1068 secnotice("octagon", "Requested to enter wait for unlock");
1069 [pendingFlagHandler _onqueueHandlePendingFlag:[[OctagonPendingFlag alloc] initWithFlag:OctagonFlagUnlocked
1070 conditions:OctagonPendingConditionsDeviceUnlocked]];
1073 } else if([currentState isEqualToString: OctagonStateUpdateSOSPreapprovals]) {
1074 secnotice("octagon", "Updating SOS preapprovals");
1076 // TODO: if this update fails, we need to redo it later.
1077 return [[OTSOSUpdatePreapprovalsOperation alloc] initWithDependencies:self.operationDependencies
1078 intendedState:OctagonStateReady
1079 sosNotPresentState:OctagonStateReady
1080 errorState:OctagonStateReady];
1082 } else if([currentState isEqualToString:OctagonStateAssistCKKSTLKUpload]) {
1083 return [[OTUploadNewCKKSTLKsOperation alloc] initWithDependencies:self.operationDependencies
1084 intendedState:OctagonStateReady
1085 ckksConflictState:OctagonStateAssistCKKSTLKUploadCKKSReset
1086 errorState:OctagonStateReady];
1088 } else if([currentState isEqualToString:OctagonStateAssistCKKSTLKUploadCKKSReset]) {
1089 return [[OTLocalCKKSResetOperation alloc] initWithDependencies:self.operationDependencies
1090 intendedState:OctagonStateAssistCKKSTLKUploadAfterCKKSReset
1091 errorState:OctagonStateBecomeReady];
1093 } else if([currentState isEqualToString:OctagonStateAssistCKKSTLKUploadAfterCKKSReset]) {
1094 // If CKKS fails again, just go to 'ready'
1095 return [[OTUploadNewCKKSTLKsOperation alloc] initWithDependencies:self.operationDependencies
1096 intendedState:OctagonStateReady
1097 ckksConflictState:OctagonStateReady
1098 errorState:OctagonStateReady];
1100 } else if([currentState isEqualToString:OctagonStateReady]) {
1101 if([flags _onqueueContains:OctagonFlagCKKSRequestsTLKUpload]) {
1102 [flags _onqueueRemoveFlag:OctagonFlagCKKSRequestsTLKUpload];
1103 return [OctagonStateTransitionOperation named:@"ckks-assist"
1104 entering:OctagonStateAssistCKKSTLKUpload];
1107 if([flags _onqueueContains:OctagonFlagCuttlefishNotification]) {
1108 [flags _onqueueRemoveFlag:OctagonFlagCuttlefishNotification];
1109 secnotice("octagon", "Updating TPH (while ready) due to push");
1110 return [OctagonStateTransitionOperation named:@"octagon-update"
1111 entering:OctagonStateReadyUpdated];
1114 if([flags _onqueueContains:OctagonFlagFetchAuthKitMachineIDList]) {
1115 [flags _onqueueRemoveFlag:OctagonFlagFetchAuthKitMachineIDList];
1116 secnotice("octagon", "Received an suggestion to update the machine ID list (while ready); updating trusted device list");
1118 // If the cached list changes due to this fetch, go into 'updated'. Otherwise, back into ready with you!
1119 return [[OTUpdateTrustedDeviceListOperation alloc] initWithDependencies:self.operationDependencies
1120 intendedState:OctagonStateReady
1121 listUpdatesState:OctagonStateReadyUpdated
1122 errorState:OctagonStateReady
1123 retryFlag:OctagonFlagFetchAuthKitMachineIDList];
1126 if([flags _onqueueContains:OctagonFlagAttemptSOSUpdatePreapprovals]) {
1127 [flags _onqueueRemoveFlag:OctagonFlagAttemptSOSUpdatePreapprovals];
1128 if(self.sosAdapter.sosEnabled) {
1129 secnotice("octagon", "Attempt SOS Update preapprovals again!");
1130 return [OctagonStateTransitionOperation named:@"attempt-sos-update-preapproval"
1131 entering:OctagonStateUpdateSOSPreapprovals];
1133 secnotice("octagon", "We are untrusted, but this platform doesn't support SOS.");
1137 if([flags _onqueueContains:OctagonFlagAttemptSOSConsistency]) {
1138 [flags _onqueueRemoveFlag:OctagonFlagAttemptSOSConsistency];
1139 if(self.sosAdapter.sosEnabled) {
1140 secnotice("octagon", "Attempting SOS consistency checks");
1141 return [OctagonStateTransitionOperation named:@"attempt-sos-update-preapproval"
1142 entering:OctagonStateEnsureConsistency];
1144 secnotice("octagon", "Someone would like us to check SOS consistency, but this platform doesn't support SOS.");
1148 if([flags _onqueueContains:OctagonFlagAccountIsAvailable]) {
1149 // We're in ready--we already know the account is available
1150 secnotice("octagon", "Removing 'account is available' flag");
1151 [flags _onqueueRemoveFlag:OctagonFlagAccountIsAvailable];
1154 // We're ready; no need for the IDMS level flag anymore
1155 if([flags _onqueueContains:OctagonFlagIDMSLevelChanged]) {
1156 secnotice("octagon", "Removing 'IDMS level changed' flag");
1157 [flags _onqueueRemoveFlag:OctagonFlagIDMSLevelChanged];
1160 secnotice("octagon", "Entering state ready");
1161 [[CKKSAnalytics logger] setDateProperty:[NSDate date] forKey:OctagonAnalyticsLastKeystateReady];
1162 [self.launchSequence launch];
1164 } else if([currentState isEqualToString:OctagonStateReadyUpdated]) {
1165 return [[OTUpdateTPHOperation alloc] initWithDependencies:self.operationDependencies
1166 intendedState:OctagonStateReady
1167 errorState:OctagonStateError
1168 retryFlag:OctagonFlagCuttlefishNotification];
1170 } else if ([currentState isEqualToString:OctagonStateError]) {
1176 - (CKKSResultOperation<OctagonStateTransitionOperationProtocol>* _Nullable)initializingOperation
1179 return [OctagonStateTransitionOperation named:@"octagon-initializing"
1180 intending:OctagonStateNoAccount
1181 errorState:OctagonStateError
1182 timeout:OctagonStateTransitionDefaultTimeout
1183 withBlockTakingSelf:^(OctagonStateTransitionOperation * _Nonnull op) {
1185 NSError* localError = nil;
1186 OTAccountMetadataClassC* account = [self.accountMetadataStore loadOrCreateAccountMetadata:&localError];
1187 if(localError && [self.lockStateTracker isLockedError:localError]){
1188 secnotice("octagon", "Device is locked! pending initialization on unlock");
1189 op.nextState = OctagonStateWaitForUnlock;
1193 if(localError || !account) {
1194 secnotice("octagon", "Error loading account data: %@", localError);
1195 op.nextState = OctagonStateNoAccount;
1197 } else if(account.icloudAccountState == OTAccountMetadataClassC_AccountState_ACCOUNT_AVAILABLE) {
1198 secnotice("octagon", "An HSA2 iCloud account exists; waiting for CloudKit to confirm");
1200 // Inform the account state tracker of our HSA2 account
1201 [self.accountStateTracker setHSA2iCloudAccountStatus:CKKSAccountStatusAvailable];
1203 // This seems an odd place to do this, but CKKS currently also tracks the CloudKit account state.
1204 // Since we think we have an HSA2 account, let CKKS figure out its own CloudKit state
1205 secnotice("octagon-ckks", "Initializing CKKS views");
1206 [self.viewManager createViews];
1207 [self.viewManager beginCloudKitOperationOfAllViews];
1209 op.nextState = OctagonStateWaitingForCloudKitAccount;
1211 } else if(account.icloudAccountState == OTAccountMetadataClassC_AccountState_NO_ACCOUNT && account.altDSID != nil) {
1212 secnotice("octagon", "An iCloud account exists, but doesn't appear to be HSA2. Let's check!");
1213 op.nextState = OctagonStateDetermineiCloudAccountState;
1215 } else if(account.icloudAccountState == OTAccountMetadataClassC_AccountState_NO_ACCOUNT) {
1216 [self.accountStateTracker setHSA2iCloudAccountStatus:CKKSAccountStatusNoAccount];
1218 secnotice("octagon", "No iCloud account available.");
1219 op.nextState = OctagonStateNoAccount;
1222 secnotice("octagon", "Unknown account state (%@). Determining...", [account icloudAccountStateAsString:account.icloudAccountState]);
1223 op.nextState = OctagonStateDetermineiCloudAccountState;
1228 - (CKKSResultOperation<OctagonStateTransitionOperationProtocol>* _Nullable)evaluateSecdOctagonTrust
1230 return [OctagonStateTransitionOperation named:@"octagon-health-securityd-trust-check"
1231 intending:OctagonStateTPHTrustCheck
1232 errorState:OctagonStatePostRepairCFU
1233 timeout:OctagonStateTransitionDefaultTimeout
1234 withBlockTakingSelf:^(OctagonStateTransitionOperation * _Nonnull op) {
1235 NSError* localError = nil;
1236 OTAccountMetadataClassC* account = [self.accountMetadataStore loadOrCreateAccountMetadata:&localError];
1237 if(account.peerID && account.trustState == OTAccountMetadataClassC_TrustState_TRUSTED) {
1238 secnotice("octagon-health", "peer is trusted: %@", account.peerID);
1239 op.nextState = OctagonStateTPHTrustCheck;
1242 secnotice("octagon-health", "trust state (%@). checking in with TPH", [account trustStateAsString:account.trustState]);
1243 op.nextState = [self repairAccountIfTrustedByTPHWithIntededState:OctagonStateTPHTrustCheck errorState:OctagonStatePostRepairCFU];
1248 - (CKKSResultOperation<OctagonStateTransitionOperationProtocol>* _Nullable)evaluateTPHOctagonTrust
1250 return [OctagonStateTransitionOperation named:@"octagon-health-tph-trust-check"
1251 intending:OctagonStateCuttlefishTrustCheck
1252 errorState:OctagonStatePostRepairCFU
1253 timeout:OctagonStateTransitionDefaultTimeout
1254 withBlockTakingSelf:^(OctagonStateTransitionOperation * _Nonnull op) {
1255 [self checkTrustStatusAndPostRepairCFUIfNecessary:^(CliqueStatus status, BOOL posted, BOOL hasIdentity, NSError *trustFromTPHError) {
1257 [[CKKSAnalytics logger] logResultForEvent:OctagonEventTPHHealthCheckStatus hardFailure:false result:trustFromTPHError];
1258 if(trustFromTPHError) {
1259 secerror("octagon-health: hit an error asking TPH for trust status: %@", trustFromTPHError);
1260 op.error = trustFromTPHError;
1261 op.nextState = OctagonStateError;
1263 if(hasIdentity == NO) {
1264 op.nextState = OctagonStateUntrusted;
1265 } else if(hasIdentity == YES && status == CliqueStatusIn){
1266 secnotice("octagon-health", "TPH says we're trusted and in");
1267 op.nextState = OctagonStateCuttlefishTrustCheck;
1268 } else if (hasIdentity == YES && status != CliqueStatusIn){
1269 secnotice("octagon-health", "TPH says we have an identity but we are not in Octagon, posted CFU: %d", !!posted);
1270 op.nextState = OctagonStatePostRepairCFU;
1272 secnotice("octagon-health", "weird shouldn't hit this catch all.. assuming untrusted");
1273 op.nextState = OctagonStateUntrusted;
1280 - (CKKSResultOperation<OctagonStateTransitionOperationProtocol>* _Nullable)cuttlefishTrustEvaluation
1283 OTCheckHealthOperation* op = [[OTCheckHealthOperation alloc] initWithDependencies:self.operationDependencies
1284 intendedState:OctagonStateBecomeReady
1285 errorState:OctagonStateBecomeReady
1286 deviceInfo:self.prepareInformation
1287 skipRateLimitedCheck:_skipRateLimitingCheck];
1289 CKKSResultOperation* callback = [CKKSResultOperation named:@"rpcHealthCheck"
1291 secnotice("octagon-health", "Returning from cuttlefish trust check call: postRepairCFU(%d), postEscrowCFU(%d), resetOctagon(%d)",
1292 op.postRepairCFU, op.postEscrowCFU, op.resetOctagon);
1293 if(op.postRepairCFU) {
1294 secnotice("octagon-health", "Posting Repair CFU");
1295 NSError* postRepairCFUError = nil;
1296 [self postRepairCFU:&postRepairCFUError];
1297 if(postRepairCFUError) {
1298 op.error = postRepairCFUError;
1301 if(op.postEscrowCFU) {
1302 //hold up, perhaps we already are pending an upload.
1303 NSError* shouldPostError = nil;
1304 BOOL shouldPost = [self shouldPostConfirmPasscodeCFU:&shouldPostError];
1305 if(shouldPostError) {
1306 secerror("octagon-health, hit an error evaluating prerecord status: %@", shouldPostError);
1307 op.error = shouldPostError;
1310 secnotice("octagon-health", "Posting Escrow CFU");
1311 NSError* postEscrowCFUError = nil;
1312 [self postConfirmPasscodeCFU:&postEscrowCFUError];
1313 if(postEscrowCFUError) {
1314 op.error = postEscrowCFUError;
1317 secnotice("octagon-health", "Not posting confirm passcode CFU, already pending a prerecord upload");
1321 [callback addDependency:op];
1322 [self.operationQueue addOperation: callback];
1326 - (CKKSResultOperation<OctagonStateTransitionOperationProtocol>* _Nullable)postRepairCFUAndBecomeUntrusted
1328 return [OctagonStateTransitionOperation named:@"octagon-health-post-repair-cfu"
1329 intending:OctagonStateUntrusted
1330 errorState:OctagonStateError
1331 timeout:OctagonStateTransitionDefaultTimeout
1332 withBlockTakingSelf:^(OctagonStateTransitionOperation * _Nonnull op) {
1333 [self checkTrustStatusAndPostRepairCFUIfNecessary:^(CliqueStatus status,
1336 NSError * _Nullable postError) {
1338 secerror("ocagon-health: failed to post repair cfu via state machine: %@", postError);
1340 secnotice("octagon-health", "posted repair cfu via state machine");
1343 op.nextState = OctagonStateUntrusted;
1347 - (CKKSResultOperation<OctagonStateTransitionOperationProtocol>* _Nullable)cloudKitAccountNewlyAvailableOperation
1350 return [OctagonStateTransitionOperation named:@"octagon-icloud-account-available"
1351 intending:OctagonStateCheckTrustState
1352 errorState:OctagonStateError
1353 timeout:OctagonStateTransitionDefaultTimeout
1354 withBlockTakingSelf:^(OctagonStateTransitionOperation * _Nonnull op) {
1356 // Register with APS, but don't bother to wait until it's complete.
1357 secnotice("octagon", "iCloud sign in occurred. Attemping to register with APS...");
1359 CKContainer* ckContainer = [CKContainer containerWithIdentifier:self.containerName];
1360 [ckContainer serverPreferredPushEnvironmentWithCompletionHandler: ^(NSString *apsPushEnvString, NSError *error) {
1364 secerror("octagonpush: received callback for released object");
1368 if(error || (apsPushEnvString == nil)) {
1369 secerror("octagonpush: Received error fetching preferred push environment (%@): %@", apsPushEnvString, error);
1371 secnotice("octagonpush", "Registering for environment '%@'", apsPushEnvString);
1373 OctagonAPSReceiver* aps = [OctagonAPSReceiver receiverForEnvironment:apsPushEnvString
1374 namedDelegatePort:SecCKKSAPSNamedPort
1375 apsConnectionClass:self.apsConnectionClass];
1376 [aps registerCuttlefishReceiver:self forContainerName:self.containerName];
1380 op.nextState = op.intendedState;
1384 - (OctagonState*) repairAccountIfTrustedByTPHWithIntededState:(OctagonState*)intendedState errorState:(OctagonState*)errorState
1386 __block OctagonState* nextState = intendedState;
1388 //let's check in with TPH real quick to make sure it agrees with our local assessment
1389 secnotice("octagon-health", "repairAccountIfTrustedByTPHWithIntededState: calling into TPH for trust status");
1391 OTOperationConfiguration *config = [[OTOperationConfiguration alloc]init];
1393 [self rpcTrustStatus:config reply:^(CliqueStatus status,
1394 NSString* egoPeerID,
1395 NSDictionary<NSString*, NSNumber*>* _Nullable peerCountByModelID,
1397 NSError * _Nullable error) {
1398 BOOL hasIdentity = egoPeerID != nil;
1399 secnotice("octagon-health", "repairAccountIfTrustedByTPHWithIntededState status: %ld, peerID: %@, isExcluded: %d error: %@", (long)status, egoPeerID, isExcluded, error);
1402 secnotice("octagon-health", "got an error from tph, returning to become_ready state: %@", error);
1403 nextState = OctagonStateBecomeReady;
1407 if(OctagonAuthoritativeTrustIsEnabled() && hasIdentity && status == CliqueStatusIn) {
1408 dispatch_semaphore_t sema = dispatch_semaphore_create(0);
1409 [self rpcStatus:^(NSDictionary *dump, NSError *dumpError) {
1411 secerror("octagon-health: error fetching ego peer id!: %@", dumpError);
1412 nextState = errorState;
1414 NSDictionary* egoInformation = dump[@"self"];
1415 NSString* peerID = egoInformation[@"peerID"];
1416 NSError* persistError = nil;
1417 BOOL persisted = [self.accountMetadataStore persistAccountChanges:^OTAccountMetadataClassC * _Nonnull(OTAccountMetadataClassC * _Nonnull metadata) {
1418 metadata.trustState = OTAccountMetadataClassC_TrustState_TRUSTED;
1419 metadata.icloudAccountState = OTAccountMetadataClassC_AccountState_ACCOUNT_AVAILABLE;
1420 metadata.peerID = peerID;
1422 } error:&persistError];
1423 if(!persisted || persistError) {
1424 secerror("octagon-health: couldn't persist results: %@", persistError);
1425 nextState = errorState;
1427 secnotice("octagon-health", "added trusted identity to account metadata");
1428 nextState = intendedState;
1431 dispatch_semaphore_signal(sema);
1433 if (0 != dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 10))) {
1434 secerror("octagon: Timed out checking trust status");
1436 } else if (OctagonAuthoritativeTrustIsEnabled() && (self.postedRepairCFU == NO) && hasIdentity && status != CliqueStatusIn){
1437 nextState = errorState;
1444 - (BOOL) didDeviceAttemptToJoinOctagon:(NSError**)error
1446 NSError* fetchAttemptError = nil;
1447 OTAccountMetadataClassC_AttemptedAJoinState attemptedAJoin = [self.accountMetadataStore fetchPersistedJoinAttempt:&fetchAttemptError];
1448 if(fetchAttemptError) {
1449 secerror("octagon: failed to fetch data indicating device attempted to join octagon, assuming it did: %@", fetchAttemptError);
1451 *error = fetchAttemptError;
1455 BOOL attempted = YES;
1456 switch (attemptedAJoin) {
1457 case OTAccountMetadataClassC_AttemptedAJoinState_NOTATTEMPTED:
1460 case OTAccountMetadataClassC_AttemptedAJoinState_ATTEMPTED:
1461 case OTAccountMetadataClassC_AttemptedAJoinState_UNKNOWN:
1468 - (void)checkTrustStatusAndPostRepairCFUIfNecessary:(void (^ _Nullable)(CliqueStatus status, BOOL posted, BOOL hasIdentity, NSError * _Nullable error))reply
1471 OTOperationConfiguration *configuration = [[OTOperationConfiguration alloc] init];
1472 [self rpcTrustStatus:configuration reply:^(CliqueStatus status,
1473 NSString* _Nullable egoPeerID,
1474 NSDictionary<NSString*, NSNumber*>* _Nullable peerCountByModelID,
1476 NSError * _Nullable error) {
1479 secnotice("octagon", "clique status: %@, egoPeerID: %@, peerCountByModelID: %@, isExcluded: %d error: %@", OTCliqueStatusToString(status), egoPeerID, peerCountByModelID, isExcluded, error);
1481 BOOL hasIdentity = egoPeerID != nil;
1482 if (error && error.code != errSecInteractionNotAllowed) {
1483 reply(status, NO, hasIdentity, error);
1488 // Are there any iphones or iPads? about? Only iOS devices can repair apple TVs.
1489 bool phonePeerPresent = false;
1490 for(NSString* modelID in peerCountByModelID.allKeys) {
1491 bool iPhone = [modelID hasPrefix:@"iPhone"];
1492 bool iPad = [modelID hasPrefix:@"iPad"];
1493 if(!iPhone && !iPad) {
1497 int count = [peerCountByModelID[modelID] intValue];
1499 secnotice("octagon", "Have %d peers with model %@", count, modelID);
1500 phonePeerPresent = true;
1504 if(!phonePeerPresent) {
1505 secnotice("octagon", "No iOS peers in account; not posting CFU");
1506 reply(status, NO, hasIdentity, nil);
1511 // On platforms with SOS, we only want to post a CFU if we've attempted to join at least once.
1512 // This prevents us from posting a CFU, then performing an SOS upgrade and succeeding.
1513 if(self.sosAdapter.sosEnabled) {
1514 NSError* fetchAttemptError = nil;
1515 BOOL attemptedToJoin = [self didDeviceAttemptToJoinOctagon:&fetchAttemptError];
1516 if(fetchAttemptError){
1517 secerror("octagon: failed to retrieve joining attempt information: %@", fetchAttemptError);
1518 attemptedToJoin = YES;
1521 if(!attemptedToJoin) {
1522 secnotice("octagon", "SOS is enabled and we haven't attempted to join; not posting CFU");
1523 reply(status, NO, hasIdentity, nil);
1528 if(OctagonAuthoritativeTrustIsEnabled() && (status == CliqueStatusNotIn || status == CliqueStatusAbsent || isExcluded)) {
1529 NSError* localError = nil;
1530 BOOL posted = [self postRepairCFU:&localError];
1531 reply(status, posted, hasIdentity, localError);
1534 reply(status, NO, hasIdentity, nil);
1540 - (CKKSResultOperation<OctagonStateTransitionOperationProtocol>* _Nullable)startCompanionPairingOperation
1543 return [OctagonStateTransitionOperation named:@"start-companion-pairing"
1544 intending:OctagonStateBecomeUntrusted
1545 errorState:OctagonStateError
1546 timeout:OctagonStateTransitionDefaultTimeout
1547 withBlockTakingSelf:^(OctagonStateTransitionOperation * _Nonnull op) {
1549 OTPairingInitiateWithCompletion(self.queue, ^(bool success, NSError *error) {
1551 secnotice("octagon", "companion pairing succeeded");
1554 error = [NSError errorWithDomain:(__bridge NSString *)kSecErrorDomain code:errSecInternalError userInfo:nil];
1556 secnotice("octagon", "companion pairing failed: %@", error);
1558 [[CKKSAnalytics logger] logResultForEvent:OctagonEventCompanionPairing hardFailure:false result:error];
1560 op.nextState = op.intendedState;
1563 #endif /* TARGET_OS_WATCH */
1565 - (CKKSResultOperation<OctagonStateTransitionOperationProtocol>* _Nullable)becomeUntrustedOperation:(OctagonState*)intendedState
1568 return [OctagonStateTransitionOperation named:@"octagon-become-untrusted"
1569 intending:intendedState
1570 errorState:OctagonStateError
1571 timeout:OctagonStateTransitionDefaultTimeout
1572 withBlockTakingSelf:^(OctagonStateTransitionOperation * _Nonnull op) {
1574 NSError* localError = nil;
1576 [self.accountStateTracker triggerOctagonStatusFetch];
1578 [self checkTrustStatusAndPostRepairCFUIfNecessary:^(CliqueStatus status, BOOL posted, BOOL hasIdentity, NSError * _Nullable postError) {
1580 [[CKKSAnalytics logger] logResultForEvent:OctagonEventCheckTrustForCFU hardFailure:false result:postError];
1582 secerror("octagon: cfu failed to post");
1584 secnotice("octagon", "clique status: %@, posted cfu: %d", OTCliqueStatusToString(status), !!posted);
1588 [self.accountMetadataStore persistAccountChanges:^OTAccountMetadataClassC * _Nonnull(OTAccountMetadataClassC * _Nonnull metadata) {
1589 metadata.trustState = OTAccountMetadataClassC_TrustState_UNTRUSTED;
1591 } error:&localError];
1594 secnotice("octagon", "Unable to set trust state: %@", localError);
1595 op.nextState = OctagonStateError;
1597 op.nextState = op.intendedState;
1600 for (id key in self.viewManager.views) {
1601 CKKSKeychainView* view = self.viewManager.views[key];
1602 secnotice("octagon-ckks", "Informing %@ of new untrusted status", view);
1603 [view endTrustedOperation];
1607 * Initial notification that we let the world know that trust is up and doing something
1609 if (!self.initialBecomeUntrustedPosted) {
1610 [self notifyTrustChanged:OTAccountMetadataClassC_TrustState_UNTRUSTED];
1611 self.initialBecomeUntrustedPosted = YES;
1614 self.octagonAdapter = nil;
1618 - (CKKSResultOperation<OctagonStateTransitionOperationProtocol>* _Nullable)becomeReadyOperation
1621 return [OctagonStateTransitionOperation named:@"octagon-ready"
1622 intending:OctagonStateReady
1623 errorState:OctagonStateError
1624 timeout:OctagonStateTransitionDefaultTimeout
1625 withBlockTakingSelf:^(OctagonStateTransitionOperation * _Nonnull op) {
1628 // Note: we don't modify the account metadata store here; that will have been done
1629 // by a join or upgrade operation, possibly long ago
1631 [self.accountStateTracker triggerOctagonStatusFetch];
1633 NSError* localError = nil;
1634 NSString* peerID = [self.accountMetadataStore getEgoPeerID:&localError];
1635 if(!peerID || localError) {
1636 secerror("octagon-ckks: No peer ID to pass to CKKS. Syncing will be disabled.");
1638 OctagonCKKSPeerAdapter* octagonAdapter = [[OctagonCKKSPeerAdapter alloc] initWithPeerID:peerID operationDependencies:[self operationDependencies]];
1640 // This octagon adapter must be able to load the self peer keys, or we're in trouble.
1641 NSError* egoPeerKeysError = nil;
1642 CKKSSelves* selves = [octagonAdapter fetchSelfPeers:&egoPeerKeysError];
1643 if(!selves || egoPeerKeysError) {
1644 secerror("octagon-ckks: Unable to fetch self peers for %@: %@", octagonAdapter, egoPeerKeysError);
1646 if([self.lockStateTracker isLockedError:egoPeerKeysError]) {
1647 secnotice("octagon-ckks", "Waiting for device unlock to proceed");
1648 op.nextState = OctagonStateWaitForUnlock;
1650 secnotice("octagon-ckks", "Error is scary; becoming untrusted");
1651 op.nextState = OctagonStateBecomeUntrusted;
1656 // stash a reference to the adapter so we can provided updates later
1657 self.octagonAdapter = octagonAdapter;
1659 // Start all our CKKS views!
1660 for (id key in self.viewManager.views) {
1661 CKKSKeychainView* view = self.viewManager.views[key];
1662 secnotice("octagon-ckks", "Informing CKKS view '%@' of trusted operation with self peer %@", view.zoneName, peerID);
1664 NSArray<id<CKKSPeerProvider>>* peerProviders = nil;
1666 if(self.sosAdapter.sosEnabled) {
1667 peerProviders = @[self.octagonAdapter, self.sosAdapter];
1670 peerProviders = @[self.octagonAdapter];
1673 [view beginTrustedOperation:peerProviders
1674 suggestTLKUpload:self.suggestTLKUploadNotifier];
1677 [self notifyTrustChanged:OTAccountMetadataClassC_TrustState_TRUSTED];
1679 op.nextState = op.intendedState;
1683 #pragma mark --- Utilities to run at times
1685 - (NSString * _Nullable)extractStringKey:(NSString * _Nonnull)key fromDictionary:(NSDictionary * _Nonnull)d
1687 NSString *value = d[key];
1688 if ([value isKindOfClass:[NSString class]]) {
1694 - (void)handleHealthRequest
1696 NSString *trustState = OTAccountMetadataClassC_TrustStateAsString(self.currentMemoizedTrustState);
1697 OctagonState* currentState = [self.stateMachine waitForState:OctagonStateReady wait:3*NSEC_PER_SEC];
1699 [self.cuttlefishXPCWrapper reportHealthWithContainer:self.containerName context:self.contextID stateMachineState:currentState trustState:trustState reply:^(NSError * _Nullable error) {
1701 secerror("octagon: health report is lost: %@", error);
1706 - (void)handleTTRRequest:(NSDictionary *)cfDictionary
1708 NSString *serialNumber = [self extractStringKey:@"s" fromDictionary:cfDictionary];
1709 NSString *ckDeviceId = [self extractStringKey:@"D" fromDictionary:cfDictionary];
1710 NSString *alert = [self extractStringKey:@"a" fromDictionary:cfDictionary];
1711 NSString *description = [self extractStringKey:@"d" fromDictionary:cfDictionary];
1712 NSString *radar = [self extractStringKey:@"R" fromDictionary:cfDictionary];
1713 NSString *componentName = [self extractStringKey:@"n" fromDictionary:cfDictionary];
1714 NSString *componentVersion = [self extractStringKey:@"v" fromDictionary:cfDictionary];
1715 NSString *componentID = [self extractStringKey:@"I" fromDictionary:cfDictionary];
1718 if (![self.deviceAdapter.serialNumber isEqualToString:serialNumber]) {
1719 secnotice("octagon", "TTR request not for me (sn)");
1724 NSString *selfDeviceID = self.viewManager.accountTracker.ckdeviceID;
1725 if (![selfDeviceID isEqualToString:serialNumber]) {
1726 secnotice("octagon", "TTR request not for me (deviceId)");
1731 if (alert == NULL || description == NULL || radar == NULL) {
1732 secerror("octagon: invalid type of TTR requeat: %@", cfDictionary);
1736 SecTapToRadar *ttr = [[SecTapToRadar alloc] initTapToRadar:alert
1737 description:description
1739 if (componentName && componentVersion && componentID) {
1740 ttr.componentName = componentName;
1741 ttr.componentVersion = componentVersion;
1742 ttr.componentID = componentID;
1747 // We can't make a APSIncomingMessage in the tests (no public constructor),
1748 // but we don't really care about anything in it but the userInfo dictionary anyway
1749 - (void)notifyContainerChange:(APSIncomingMessage* _Nullable)notification
1751 [self notifyContainerChangeWithUserInfo:notification.userInfo];
1754 - (void)notifyContainerChangeWithUserInfo:(NSDictionary*)userInfo
1756 secerror("OTCuttlefishContext: received a cuttlefish push notification (%@): %@",
1757 self.containerName, userInfo);
1759 NSDictionary *cfDictionary = userInfo[@"cf"];
1760 if ([cfDictionary isKindOfClass:[NSDictionary class]]) {
1761 NSString *command = [self extractStringKey:@"k" fromDictionary:cfDictionary];
1763 if ([command isEqualToString:@"h"]) {
1764 [self handleHealthRequest];
1765 } else if ([command isEqualToString:@"r"]) {
1766 [self handleTTRRequest:cfDictionary];
1768 secerror("octagon: unknown command: %@", command);
1774 if (self.apsRateLimiter == nil) {
1775 secnotice("octagon", "creating aps rate limiter");
1776 // If we're testing, for the initial delay, use 0.2 second. Otherwise, 2s.
1777 dispatch_time_t initialDelay = (SecCKKSReduceRateLimiting() ? 200 * NSEC_PER_MSEC : 2 * NSEC_PER_SEC);
1779 // If we're testing, for the initial delay, use 2 second. Otherwise, 30s.
1780 dispatch_time_t continuingDelay = (SecCKKSReduceRateLimiting() ? 2 * NSEC_PER_SEC : 30 * NSEC_PER_SEC);
1783 self.apsRateLimiter = [[CKKSNearFutureScheduler alloc] initWithName:@"aps-push-ratelimiter"
1784 initialDelay:initialDelay
1785 continuingDelay:continuingDelay
1786 keepProcessAlive:YES
1787 dependencyDescriptionCode:CKKSResultDescriptionNone
1793 secnotice("octagon-push-ratelimited", "notifying container of change for context: %@", self.contextID);
1794 OctagonPendingFlag *pendingFlag = [[OctagonPendingFlag alloc] initWithFlag:OctagonFlagCuttlefishNotification
1795 conditions:OctagonPendingConditionsDeviceUnlocked];
1797 [self.stateMachine handlePendingFlag:pendingFlag];
1801 [self.apsRateLimiter trigger];
1804 - (BOOL)waitForReady:(int64_t)timeOffset
1806 OctagonState* currentState = [self.stateMachine waitForState:OctagonStateReady wait:timeOffset];
1807 return [currentState isEqualToString:OctagonStateReady];
1811 - (OTAccountMetadataClassC_TrustState)currentMemoizedTrustState
1813 NSError* localError = nil;
1814 OTAccountMetadataClassC* accountMetadata = [self.accountMetadataStore loadOrCreateAccountMetadata:&localError];
1816 if(!accountMetadata) {
1817 secnotice("octagon", "Unable to fetch account metadata: %@", localError);
1818 return OTAccountMetadataClassC_TrustState_UNKNOWN;
1821 return accountMetadata.trustState;
1824 - (OTAccountMetadataClassC_AccountState)currentMemoizedAccountState
1826 NSError* localError = nil;
1827 OTAccountMetadataClassC* accountMetadata = [self.accountMetadataStore loadOrCreateAccountMetadata:&localError];
1829 if(!accountMetadata) {
1830 secnotice("octagon", "Unable to fetch account metadata: %@", localError);
1831 return OTAccountMetadataClassC_AccountState_UNKNOWN;
1834 return accountMetadata.icloudAccountState;
1837 - (NSDate* _Nullable) currentMemoizedLastHealthCheck
1839 NSError* localError = nil;
1840 OTAccountMetadataClassC* accountMetadata = [self.accountMetadataStore loadOrCreateAccountMetadata:&localError];
1842 if(!accountMetadata) {
1843 secnotice("octagon", "Unable to fetch account metadata: %@", localError);
1846 if(accountMetadata.lastHealthCheckup == 0) {
1849 return [[NSDate alloc] initWithTimeIntervalSince1970: accountMetadata.lastHealthCheckup];
1852 - (void)requestTrustedDeviceListRefresh
1854 [self.stateMachine handleFlag:OctagonFlagFetchAuthKitMachineIDList];
1857 #pragma mark --- Device Info update handling
1859 - (void)deviceNameUpdated {
1860 secnotice("octagon-devicename", "device name updated: %@", self.contextID);
1861 OctagonPendingFlag *pendingFlag = [[OctagonPendingFlag alloc] initWithFlag:OctagonFlagCuttlefishNotification
1862 conditions:OctagonPendingConditionsDeviceUnlocked];
1863 [self.stateMachine handlePendingFlag:pendingFlag];
1866 #pragma mark --- SOS update handling
1869 - (void)selfPeerChanged:(id<CKKSPeerProvider>)provider
1871 // Currently, we register for peer changes with just our SOS peer adapter, so the only reason this is called is to receive SOS updates
1872 // Ignore SOS self peer updates for now.
1875 - (void)trustedPeerSetChanged:(id<CKKSPeerProvider>)provider
1877 // Currently, we register for peer changes with just our SOS peer adapter, so the only reason this is called is to receive SOS updates
1878 secnotice("octagon-sos", "Received an update of an SOS trust set change");
1880 if(!self.sosAdapter.sosEnabled) {
1881 secnotice("octagon-sos", "This platform doesn't support SOS. This is probably a bug?");
1884 if (self.sosConsistencyRateLimiter == nil) {
1885 secnotice("octagon", "creating SOS consistency rate limiter");
1886 dispatch_time_t initialDelay = (SecCKKSReduceRateLimiting() ? 200 * NSEC_PER_MSEC : 2 * NSEC_PER_SEC);
1887 dispatch_time_t maximumDelay = (SecCKKSReduceRateLimiting() ? 10 * NSEC_PER_SEC : 30 * NSEC_PER_SEC);
1891 void (^block)(void) = ^{
1893 [self.stateMachine handleFlag:OctagonFlagAttemptSOSConsistency];
1896 self.sosConsistencyRateLimiter = [[CKKSNearFutureScheduler alloc] initWithName:@"sos-consistency-ratelimiter"
1897 initialDelay:initialDelay
1899 maximumDelay:maximumDelay
1900 keepProcessAlive:false
1901 dependencyDescriptionCode:CKKSResultDescriptionPendingZoneChangeFetchScheduling
1905 [self.sosConsistencyRateLimiter trigger];
1908 #pragma mark --- External Interfaces
1911 - (CKKSAccountStatus)checkForCKAccount:(OTOperationConfiguration * _Nullable)configuration {
1914 // Watches can be very, very slow getting the CK account state
1915 uint64_t timeout = (90 * NSEC_PER_SEC);
1917 uint64_t timeout = (10 * NSEC_PER_SEC);
1919 if (configuration.timeoutWaitForCKAccount != 0) {
1920 timeout = configuration.timeoutWaitForCKAccount;
1923 /* wait if account is not present yet */
1924 if([self.cloudKitAccountStateKnown wait:timeout] != 0) {
1925 secnotice("octagon-ck", "Unable to determine CloudKit account state?");
1926 return CKKSAccountStatusUnknown;
1930 __block bool haveAccount = true;
1931 dispatch_sync(self.queue, ^{
1932 if (self.cloudKitAccountInfo == NULL || self.cloudKitAccountInfo.accountStatus != CKKSAccountStatusAvailable) {
1933 haveAccount = false;
1936 return haveAccount ? CKKSAccountStatusAvailable : CKKSAccountStatusNoAccount;
1939 - (NSError *)errorNoiCloudAccount
1941 return [NSError errorWithDomain:OctagonErrorDomain
1942 code:OTErrorNotSignedIn
1943 description:@"User is not signed into iCloud."];
1946 //Initiator interfaces
1948 - (void)rpcPrepareIdentityAsApplicantWithConfiguration:(OTJoiningConfiguration*)config
1949 epoch:(uint64_t)epoch
1950 reply:(void (^)(NSString * _Nullable peerID,
1951 NSData * _Nullable permanentInfo,
1952 NSData * _Nullable permanentInfoSig,
1953 NSData * _Nullable stableInfo,
1954 NSData * _Nullable stableInfoSig,
1955 NSError * _Nullable error))reply
1957 if ([self checkForCKAccount:nil] != CKKSAccountStatusAvailable) {
1958 secnotice("octagon", "No cloudkit account present");
1959 reply(NULL, NULL, NULL, NULL, NULL, [self errorNoiCloudAccount]);
1963 secnotice("otrpc", "Preparing identity as applicant");
1964 OTPrepareOperation* pendingOp = [[OTPrepareOperation alloc] initWithDependencies:self.operationDependencies
1965 intendedState:OctagonStateInitiatorAwaitingVoucher
1966 errorState:OctagonStateBecomeUntrusted
1967 deviceInfo:[self prepareInformation]
1971 dispatch_time_t timeOut = 0;
1972 if(config.timeout != 0) {
1973 timeOut = config.timeout;
1974 } else if(!OctagonPlatformSupportsSOS()){
1975 // Non-iphone non-mac platforms can be slow; heuristically slow them down
1976 timeOut = 60*NSEC_PER_SEC;
1978 timeOut = 2*NSEC_PER_SEC;
1981 OctagonStateTransitionRequest<OTPrepareOperation*>* request = [[OctagonStateTransitionRequest alloc] init:@"prepareForApplicant"
1982 sourceStates:[NSSet setWithArray:@[OctagonStateUntrusted, OctagonStateNoAccount, OctagonStateMachineNotStarted]]
1983 serialQueue:self.queue
1985 transitionOp:pendingOp];
1987 CKKSResultOperation* callback = [CKKSResultOperation named:@"rpcPrepare-callback"
1989 secnotice("otrpc", "Returning a prepare call: %@ %@", pendingOp.peerID, pendingOp.error);
1990 reply(pendingOp.peerID,
1991 pendingOp.permanentInfo,
1992 pendingOp.permanentInfoSig,
1993 pendingOp.stableInfo,
1994 pendingOp.stableInfoSig,
1997 [callback addDependency:pendingOp];
1998 [self.operationQueue addOperation: callback];
2000 [self.stateMachine handleExternalRequest:request];
2005 -(void)joinWithBottle:(NSString*)bottleID
2006 entropy:(NSData *)entropy
2007 bottleSalt:(NSString *)bottleSalt
2008 reply:(void (^)(NSError * _Nullable error))reply
2010 _bottleID = bottleID;
2012 _bottleSalt = bottleSalt;
2013 OTOperationConfiguration *configuration = [[OTOperationConfiguration alloc] init];
2015 if ([self checkForCKAccount:configuration] != CKKSAccountStatusAvailable) {
2016 secnotice("octagon", "No cloudkit account present");
2017 reply([self errorNoiCloudAccount]);
2021 OctagonStateTransitionPath* path = [OctagonStateTransitionPath pathFromDictionary:@{
2022 OctagonStateInitiatorCreateIdentity: @{
2023 OctagonStateInitiatorVouchWithBottle: [self joinStatePathDictionary],
2027 [self.stateMachine doWatchedStateMachineRPC:@"rpc-join-with-bottle"
2028 sourceStates:OctagonInAccountStates()
2033 -(void)joinWithRecoveryKey:(NSString*)recoveryKey
2034 reply:(void (^)(NSError * _Nullable error))reply
2036 _recoveryKey = recoveryKey;
2037 OTOperationConfiguration *configuration = [[OTOperationConfiguration alloc] init];
2039 if ([self checkForCKAccount:configuration] != CKKSAccountStatusAvailable) {
2040 secnotice("octagon", "No cloudkit account present");
2041 reply([self errorNoiCloudAccount]);
2045 OctagonStateTransitionPath* path = [OctagonStateTransitionPath pathFromDictionary:@{
2046 OctagonStateCreateIdentityForRecoveryKey: @{
2047 OctagonStateVouchWithRecoveryKey: [self joinStatePathDictionary],
2051 [self.stateMachine doWatchedStateMachineRPC:@"rpc-join-with-recovery-key"
2052 sourceStates:OctagonInAccountStates()
2057 - (NSDictionary*)joinStatePathDictionary
2060 OctagonStateInitiatorUpdateDeviceList: @{
2061 OctagonStateInitiatorJoin: @{
2062 OctagonStateBecomeReady: @{
2063 OctagonStateReady: [OctagonStateTransitionPathStep success],
2066 OctagonStateInitiatorJoinCKKSReset: @{
2067 OctagonStateInitiatorJoinAfterCKKSReset: @{
2068 OctagonStateBecomeReady: @{
2069 OctagonStateReady: [OctagonStateTransitionPathStep success]
2078 - (void)rpcJoin:(NSData*)vouchData
2079 vouchSig:(NSData*)vouchSig
2080 preapprovedKeys:(NSArray<NSData*>* _Nullable)preapprovedKeys
2081 reply:(void (^)(NSError * _Nullable error))reply
2084 _vouchData = vouchData;
2085 _vouchSig = vouchSig;
2086 _preapprovedKeys = preapprovedKeys;
2088 if ([self checkForCKAccount:nil] != CKKSAccountStatusAvailable) {
2089 secnotice("octagon", "No cloudkit account present");
2090 reply([self errorNoiCloudAccount]);
2094 NSMutableSet* sourceStates = [NSMutableSet setWithObject:OctagonStateInitiatorAwaitingVoucher];
2096 OctagonStateTransitionPath* path = [OctagonStateTransitionPath pathFromDictionary:[self joinStatePathDictionary]];
2098 [self.stateMachine doWatchedStateMachineRPC:@"rpc-join"
2099 sourceStates:sourceStates
2104 - (NSDictionary *)ckksPeerStatus:(id<CKKSPeer>)peer
2106 NSMutableDictionary *peerStatus = [NSMutableDictionary dictionary];
2109 peerStatus[@"peerID"] = peer.peerID;
2111 NSData *spki = peer.publicSigningKey.encodeSubjectPublicKeyInfo;
2113 peerStatus[@"signingSPKI"] = [spki base64EncodedStringWithOptions:0];
2114 peerStatus[@"signingSPKIHash"] = [TPHashBuilder hashWithAlgo:kTPHashAlgoSHA256 ofData:spki];
2119 - (NSArray *)sosTrustedPeersStatus
2121 NSError *localError = nil;
2122 NSSet<id<CKKSRemotePeerProtocol>>* _Nullable peers = [self.sosAdapter fetchTrustedPeers:&localError];
2123 if (peers == nil || localError) {
2124 secnotice("octagon", "No SOS peers present: %@, skipping in status", localError);
2127 NSMutableArray<NSDictionary *>* trustedSOSPeers = [NSMutableArray array];
2129 for (id<CKKSPeer> peer in peers) {
2130 NSDictionary *peerStatus = [self ckksPeerStatus:peer];
2132 [trustedSOSPeers addObject:peerStatus];
2135 return trustedSOSPeers;
2138 - (NSDictionary *)sosSelvesStatus
2140 NSError *localError = nil;
2142 CKKSSelves* selves = [self.sosAdapter fetchSelfPeers:&localError];
2143 if (selves == nil || localError) {
2144 secnotice("octagon", "No SOS selves present: %@, skipping in status", localError);
2147 NSMutableDictionary* selvesSOSPeers = [NSMutableDictionary dictionary];
2149 selvesSOSPeers[@"currentSelf"] = [self ckksPeerStatus:selves.currentSelf];
2152 * If we have past selves, include them too
2154 NSMutableSet* pastSelves = [selves.allSelves mutableCopy];
2155 [pastSelves removeObject:selves.currentSelf];
2156 if (pastSelves.count) {
2157 NSMutableArray<NSDictionary *>* pastSelvesStatus = [NSMutableArray array];
2159 for (id<CKKSPeer> peer in pastSelves) {
2160 NSDictionary *peerStatus = [self ckksPeerStatus:peer];
2162 [pastSelvesStatus addObject:peerStatus];
2165 selvesSOSPeers[@"pastSelves"] = pastSelvesStatus;
2167 return selvesSOSPeers;
2170 - (void)rpcStatus:(void (^)(NSDictionary* _Nullable result, NSError* _Nullable error))reply
2172 __block NSMutableDictionary* result = [NSMutableDictionary dictionary];
2174 result[@"containerName"] = self.containerName;
2175 result[@"contextID"] = self.contextID;
2177 if ([self checkForCKAccount:nil] != CKKSAccountStatusAvailable) {
2178 secnotice("octagon", "No cloudkit account present");
2179 reply(NULL, [self errorNoiCloudAccount]);
2183 if([self.stateMachine.paused wait:3*NSEC_PER_SEC] != 0) {
2184 secnotice("octagon", "Returning status of unpaused state machine for container (%@) and context (%@)", self.containerName, self.contextID);
2185 result[@"stateUnpaused"] = @1;
2188 // This will try to allow the state machine to pause
2189 result[@"state"] = self.stateMachine.currentState;
2190 result[@"statePendingFlags"] = [self.stateMachine dumpPendingFlags];
2191 result[@"stateFlags"] = [self.stateMachine.flags dumpFlags];
2193 result[@"memoizedTrustState"] = @(self.currentMemoizedTrustState);
2194 result[@"memoizedAccountState"] = @(self.currentMemoizedAccountState);
2195 result[@"octagonLaunchSeqence"] = [self.launchSequence eventsByTime];
2196 result[@"memoizedlastHealthCheck"] = self.currentMemoizedLastHealthCheck ? self.currentMemoizedLastHealthCheck : @"Never checked";
2197 if (self.sosAdapter.sosEnabled) {
2198 result[@"sosTrustedPeersStatus"] = [self sosTrustedPeersStatus];
2199 result[@"sosSelvesStatus"] = [self sosSelvesStatus];
2204 id<SecEscrowRequestable> request = [self.escrowRequestClass request:&error];
2205 result[@"escrowRequest"] = [request fetchStatuses:&error];
2208 result[@"CoreFollowUp"] = [self.followupHandler sysdiagnoseStatus];
2209 result[@"lastOctagonPush"] = [[CKKSAnalytics logger] datePropertyForKey:CKKSAnalyticsLastOctagonPush];
2212 [self.cuttlefishXPCWrapper dumpWithContainer:self.containerName
2213 context:self.contextID
2214 reply:^(NSDictionary * _Nullable dump, NSError * _Nullable dumpError) {
2215 secnotice("octagon", "Finished dump for status RPC");
2217 result[@"contextDumpError"] = dumpError;
2219 result[@"contextDump"] = dump;
2225 - (void)rpcFetchEgoPeerID:(void (^)(NSString* peerID, NSError* error))reply
2227 // We've memoized this peer ID. Use the memorized version...
2228 NSError* localError = nil;
2229 NSString* peerID = [self.accountMetadataStore getEgoPeerID:&localError];
2232 secnotice("octagon", "Returning peer ID: %@", peerID);
2234 secnotice("octagon", "Unable to fetch peer ID: %@", localError);
2236 reply(peerID, localError);
2239 - (void)rpcFetchDeviceNamesByPeerID:(void (^)(NSDictionary<NSString*, NSString*>* _Nullable peers, NSError* _Nullable error))reply
2241 if ([self checkForCKAccount:nil] != CKKSAccountStatusAvailable) {
2242 secnotice("octagon", "No cloudkit account present");
2243 reply(NULL, [self errorNoiCloudAccount]);
2247 // As this isn't a state-modifying operation, we don't need to go through the state machine.
2248 [self.cuttlefishXPCWrapper dumpWithContainer:self.containerName
2249 context:self.contextID
2250 reply:^(NSDictionary * _Nullable dump, NSError * _Nullable dumpError) {
2251 // Pull out our peers
2253 secnotice("octagon", "Unable to dump info: %@", dumpError);
2254 reply(nil, dumpError);
2258 NSDictionary* selfInfo = dump[@"self"];
2259 NSArray* peers = dump[@"peers"];
2260 NSArray* trustedPeerIDs = selfInfo[@"dynamicInfo"][@"included"];
2262 NSMutableDictionary<NSString*, NSString*>* peerMap = [NSMutableDictionary dictionary];
2264 for(NSString* peerID in trustedPeerIDs) {
2265 NSDictionary* peerMatchingID = nil;
2267 for(NSDictionary* peer in peers) {
2268 if([peer[@"peerID"] isEqualToString:peerID]) {
2269 peerMatchingID = peer;
2274 if(!peerMatchingID) {
2275 secerror("octagon: have a trusted peer ID without peer information: %@", peerID);
2279 peerMap[peerID] = peerMatchingID[@"stableInfo"][@"device_name"];
2282 reply(peerMap, nil);
2286 - (void)rpcSetRecoveryKey:(NSString*)recoveryKey reply:(void (^)(NSError * _Nullable error))reply
2288 OTSetRecoveryKeyOperation *pendingOp = [[OTSetRecoveryKeyOperation alloc] initWithDependencies:self.operationDependencies
2289 recoveryKey:recoveryKey];
2291 CKKSResultOperation* callback = [CKKSResultOperation named:@"setRecoveryKey-callback"
2293 secnotice("otrpc", "Returning a set recovery key call: %@", pendingOp.error);
2294 reply(pendingOp.error);
2297 [callback addDependency:pendingOp];
2298 [self.operationQueue addOperation:callback];
2299 [self.operationQueue addOperation:pendingOp];
2302 - (void)rpcTrustStatusCachedStatus:(OTAccountMetadataClassC*)account
2303 reply:(void (^)(CliqueStatus status,
2304 NSString* egoPeerID,
2305 NSDictionary<NSString*, NSNumber*>* _Nullable peerCountByModelID,
2307 NSError *error))reply
2309 CliqueStatus status = CliqueStatusAbsent;
2311 if (account.trustState == OTAccountMetadataClassC_TrustState_TRUSTED) {
2312 status = CliqueStatusIn;
2313 } else if (account.trustState == OTAccountMetadataClassC_TrustState_UNTRUSTED) {
2314 status = CliqueStatusNotIn;
2317 secnotice("octagon", "returning cached clique status: %@", OTCliqueStatusToString(status));
2318 reply(status, account.peerID, nil, NO, NULL);
2322 - (void)rpcTrustStatus:(OTOperationConfiguration *)configuration
2323 reply:(void (^)(CliqueStatus status,
2324 NSString* _Nullable peerID,
2325 NSDictionary<NSString*, NSNumber*>* _Nullable peerCountByModelID,
2327 NSError *error))reply
2329 __block NSError* localError = nil;
2331 OTAccountMetadataClassC* account = [self.accountMetadataStore loadOrCreateAccountMetadata:&localError];
2332 if(localError && [self.lockStateTracker isLockedError:localError]){
2333 secnotice("octagon", "Device is locked! pending initialization on unlock");
2334 reply(CliqueStatusError, nil, nil, NO, localError);
2338 if(account.icloudAccountState == OTAccountMetadataClassC_AccountState_NO_ACCOUNT) {
2339 secnotice("octagon", "no account! returning clique status 'no account'");
2340 reply(CliqueStatusNoCloudKitAccount, nil, nil, NO, NULL);
2344 if (configuration.useCachedAccountStatus) {
2345 [self rpcTrustStatusCachedStatus:account reply:reply];
2349 CKKSAccountStatus ckAccountStatus = [self checkForCKAccount:configuration];
2350 if(ckAccountStatus == CKKSAccountStatusNoAccount) {
2351 secnotice("octagon", "No cloudkit account present");
2352 reply(CliqueStatusNoCloudKitAccount, nil, nil, NO, NULL);
2354 } else if(ckAccountStatus == CKKSAccountStatusUnknown) {
2355 secnotice("octagon", "Unknown cloudkit account status, returning cached trust value");
2356 [self rpcTrustStatusCachedStatus:account reply:reply];
2360 __block NSString* peerID = nil;
2361 __block NSDictionary<NSString*, NSNumber*>* peerModelCounts = nil;
2362 __block BOOL excluded = NO;
2363 __block CliqueStatus trustStatus = CliqueStatusError;
2365 [self.cuttlefishXPCWrapper trustStatusWithContainer:self.containerName context:self.contextID reply:^(TrustedPeersHelperEgoPeerStatus *egoStatus,
2366 NSError *xpcError) {
2367 TPPeerStatus status = egoStatus.egoStatus;
2368 peerID = egoStatus.egoPeerID;
2369 excluded = egoStatus.isExcluded;
2370 peerModelCounts = egoStatus.viablePeerCountsByModelID;
2371 localError = xpcError;
2374 secnotice("octagon", "error fetching trust status: %@", xpcError);
2376 secnotice("octagon", "trust status: %@", TPPeerStatusToString(status));
2378 if((status&TPPeerStatusExcluded) == TPPeerStatusExcluded){
2379 trustStatus = CliqueStatusNotIn;
2381 else if((status&TPPeerStatusPartiallyReciprocated) == TPPeerStatusPartiallyReciprocated){
2382 trustStatus = CliqueStatusIn;
2384 else if((status&TPPeerStatusAncientEpoch) == TPPeerStatusAncientEpoch){
2385 //FIX ME HANDLE THIS CASE
2386 trustStatus= CliqueStatusIn;
2388 else if((status&TPPeerStatusOutdatedEpoch) == TPPeerStatusOutdatedEpoch){
2389 //FIX ME HANDLE THIS CASE
2390 trustStatus = CliqueStatusIn;
2392 else if((status&TPPeerStatusFullyReciprocated) == TPPeerStatusFullyReciprocated){
2393 trustStatus = CliqueStatusIn;
2395 else if((status&TPPeerStatusUnknown) == TPPeerStatusUnknown){
2396 trustStatus = CliqueStatusAbsent;
2398 else if ((status&TPPeerStatusSelfTrust) == TPPeerStatusSelfTrust) {
2399 trustStatus = CliqueStatusIn;
2402 secnotice("octagon", "TPPeerStatus is empty");
2403 trustStatus = CliqueStatusAbsent;
2408 if(trustStatus == CliqueStatusIn && self.postedRepairCFU == YES){
2409 NSError* clearError = nil;
2410 [self.followupHandler clearFollowUp:OTFollowupContextTypeStateRepair error:&clearError];
2411 // TODO(caw): should we clear this flag if `clearFollowUpForContext` fails?
2412 self.postedRepairCFU = NO;
2414 reply(trustStatus, peerID, peerModelCounts, excluded, localError);
2417 - (void)rpcFetchAllViableBottles:(void (^)(NSArray<NSString*>* _Nullable sortedBottleIDs, NSArray<NSString*>* _Nullable sortedPartialEscrowRecordIDs, NSError* _Nullable error))reply
2419 if ([self checkForCKAccount:nil] != CKKSAccountStatusAvailable) {
2420 secnotice("octagon", "No cloudkit account present");
2421 reply(NULL, NULL, [self errorNoiCloudAccount]);
2425 // As this isn't a state-modifying operation, we don't need to go through the state machine.
2426 [self.cuttlefishXPCWrapper fetchViableBottlesWithContainer:self.containerName
2427 context:self.contextID
2428 reply:^(NSArray<NSString*>* _Nullable sortedEscrowRecordIDs, NSArray<NSString*>* _Nullable sortedPartialEscrowRecordIDs, NSError * _Nullable error) {
2430 secerror("octagon: error fetching all viable bottles: %@", error);
2431 reply(nil, nil, error);
2433 secnotice("octagon", "fetched viable bottles: %@", sortedEscrowRecordIDs);
2434 secnotice("octagon", "fetched partially viable bottles: %@", sortedPartialEscrowRecordIDs);
2435 reply(sortedEscrowRecordIDs, sortedPartialEscrowRecordIDs, error);
2440 - (void)fetchEscrowContents:(void (^)(NSData* _Nullable entropy,
2441 NSString* _Nullable bottleID,
2442 NSData* _Nullable signingPublicKey,
2443 NSError* _Nullable error))reply
2445 // As this isn't a state-modifying operation, we don't need to go through the state machine.
2446 [self.cuttlefishXPCWrapper fetchEscrowContentsWithContainer:self.containerName
2447 context:self.contextID
2448 reply:^(NSData * _Nullable entropy, NSString * _Nullable bottleID, NSData * _Nullable signingPublicKey, NSError * _Nullable error) {
2450 secerror("octagon: error fetching escrow contents: %@", error);
2451 reply(nil, nil, nil, error);
2453 secnotice("octagon", "fetched escrow contents for bottle: %@", bottleID);
2454 reply(entropy, bottleID, signingPublicKey, error);
2459 - (void)rpcValidatePeers:(void (^)(NSDictionary* _Nullable result, NSError* _Nullable error))reply
2461 __block NSMutableDictionary* result = [NSMutableDictionary dictionary];
2463 result[@"containerName"] = self.containerName;
2464 result[@"contextID"] = self.contextID;
2465 result[@"state"] = [self.stateMachine waitForState:OctagonStateReady wait:3*NSEC_PER_SEC];
2467 if ([self checkForCKAccount:nil] != CKKSAccountStatusAvailable) {
2468 secnotice("octagon", "No cloudkit account present");
2469 reply(NULL, [self errorNoiCloudAccount]);
2473 [self.cuttlefishXPCWrapper validatePeersWithContainer:self.containerName
2474 context:self.contextID
2475 reply:^(NSDictionary * _Nullable validateData, NSError * _Nullable dumpError) {
2476 secnotice("octagon", "Finished validatePeers for status RPC");
2478 result[@"error"] = dumpError;
2480 result[@"validate"] = validateData;
2487 #pragma mark --- Testing
2488 - (void) setAccountStateHolder:(OTCuttlefishAccountStateHolder*)accountMetadataStore
2490 self.accountMetadataStore = accountMetadataStore;
2493 - (void)setPostedBool:(BOOL)posted
2495 self.postedRepairCFU = posted;
2498 #pragma mark --- Health Checker
2500 - (BOOL)postRepairCFU:(NSError**)error
2502 NSError* localError = nil;
2503 BOOL postSuccess = NO;
2504 if (self.postedRepairCFU == NO) {
2505 [self.followupHandler postFollowUp:OTFollowupContextTypeStateRepair error:&localError];
2507 secerror("octagon-health: CoreCDP repair failed: %@", localError);
2509 *error = localError;
2513 secnotice("octagon-health", "CoreCDP post repair success");
2514 self.postedRepairCFU = YES;
2518 secnotice("octagon-health", "already posted a repair CFU!");
2523 - (BOOL)shouldPostConfirmPasscodeCFU:(NSError**)error
2525 NSError* localError = nil;
2526 id<SecEscrowRequestable> request = [self.escrowRequestClass request:&localError];
2527 if(!request || localError) {
2528 secnotice("octagon-health", "Unable to acquire a EscrowRequest object: %@", localError);
2530 *error = localError;
2534 BOOL pendingUpload = [request pendingEscrowUpload:&localError];
2537 secnotice("octagon-health", "Failed to check escrow prerecord status: %@", localError);
2539 *error = localError;
2544 if(pendingUpload == YES) {
2545 secnotice("octagon-health", "prerecord is pending, NOT posting CFU");
2548 secnotice("octagon-health", "no pending prerecords, posting CFU");
2553 - (void)postConfirmPasscodeCFU:(NSError**)error
2555 NSError* localError = nil;
2556 if (self.postedEscrowRepairCFU == NO) {
2557 [self.followupHandler postFollowUp:OTFollowupContextTypeOfflinePasscodeChange error:&localError];
2559 secerror("octagon-health: CoreCDP offline passcode change failed: %@", localError);
2560 *error = localError;
2563 secnotice("octagon-health", "CoreCDP offline passcode change success");
2564 self.postedEscrowRepairCFU = YES;
2567 secnotice("octagon-health", "already posted escrow CFU");
2571 - (void)postRecoveryKeyCFU:(NSError**)error
2573 NSError* localError = nil;
2574 if (self.postedRecoveryKeyCFU == NO) {
2575 [self.followupHandler postFollowUp:OTFollowupContextTypeRecoveryKeyRepair error:&localError];
2577 secerror("octagon-health: CoreCDP recovery key cfu failed: %@", localError);
2580 secnotice("octagon-health", "CoreCDP recovery key cfu success");
2581 self.postedRecoveryKeyCFU = YES;
2584 secnotice("octagon-health", "already posted recovery key CFU");
2588 - (void)checkOctagonHealth:(BOOL)skipRateLimitingCheck reply:(void (^)(NSError * _Nullable error))reply
2590 secnotice("octagon-health", "Beginning checking overall Octagon Trust");
2592 _skipRateLimitingCheck = skipRateLimitingCheck;
2594 // Ending in "waitforunlock" is okay for a health check
2595 [self.stateMachine doWatchedStateMachineRPC:@"octagon-trust-health-check"
2596 sourceStates:OctagonHealthSourceStates()
2597 path:[OctagonStateTransitionPath pathFromDictionary:@{
2598 OctagonStateHSA2HealthCheck: @{
2599 OctagonStateSecurityTrustCheck: @{
2600 OctagonStateTPHTrustCheck: @{
2601 OctagonStateCuttlefishTrustCheck: @{
2602 OctagonStateBecomeReady: @{
2603 OctagonStateReady: [OctagonStateTransitionPathStep success],
2604 OctagonStateWaitForUnlock: [OctagonStateTransitionPathStep success],
2606 // Cuttlefish can suggest we reset the world. Consider reaching here a success,
2607 // instead of tracking the whole reset.
2608 OctagonStateHealthCheckReset: [OctagonStateTransitionPathStep success],
2612 OctagonStateWaitForHSA2: [OctagonStateTransitionPathStep success],
2618 - (void)attemptSOSUpgrade:(void (^)(NSError* _Nullable error))reply
2620 secnotice("octagon-sos", "attempting to perform an sos upgrade");
2622 if ([self checkForCKAccount:nil] != CKKSAccountStatusAvailable) {
2623 secnotice("octagon-sos", "No cloudkit account present");
2624 reply([self errorNoiCloudAccount]);
2628 NSSet* sourceStates = [NSSet setWithArray: @[OctagonStateUntrusted]];
2630 OTSOSUpgradeOperation *pendingOp = [[OTSOSUpgradeOperation alloc] initWithDependencies:self.operationDependencies
2631 intendedState:OctagonStateBecomeReady
2632 ckksConflictState:OctagonStateBecomeUntrusted
2633 errorState:OctagonStateBecomeUntrusted
2634 deviceInfo:self.prepareInformation];
2636 OctagonStateTransitionRequest<OTSOSUpgradeOperation*>* request = [[OctagonStateTransitionRequest alloc] init:@"attempt-sos-upgrade"
2637 sourceStates:sourceStates
2638 serialQueue:self.queue
2639 timeout:OctagonStateTransitionDefaultTimeout
2640 transitionOp:pendingOp];
2642 CKKSResultOperation* callback = [CKKSResultOperation named:@"sos-upgrade-callback"
2644 secnotice("otrpc", "Returning from an sos upgrade attempt: %@", pendingOp.error);
2645 [[CKKSAnalytics logger] logResultForEvent:OctagonEventUpgradePreapprovedJoinAfterPairing hardFailure:false result:pendingOp.error];
2646 reply(pendingOp.error);
2649 [callback addDependency:pendingOp];
2650 [self.operationQueue addOperation: callback];
2652 [self.stateMachine handleExternalRequest:request];
2655 - (void)waitForOctagonUpgrade:(void (^)(NSError* error))reply
2657 secnotice("octagon-sos", "waitForOctagonUpgrade");
2659 if (!self.sosAdapter.sosEnabled) {
2660 secnotice("octagon-sos", "sos not enabled, nothing to do for waitForOctagonUpgrade");
2665 if ([self.stateMachine isPaused]) {
2666 if ([[self.stateMachine currentState] isEqualToString:OctagonStateReady]) {
2667 secnotice("octagon-sos", "waitForOctagonUpgrade: already ready, returning");
2672 if ([[self.stateMachine waitForState:OctagonStateReady wait:10*NSEC_PER_SEC] isEqualToString:OctagonStateReady]) {
2673 secnotice("octagon-sos", "waitForOctagonUpgrade: in ready (after waiting), returning");
2677 secnotice("octagon-sos", "waitForOctagonUpgrade: fail to get to ready after timeout, attempting upgrade");
2681 NSSet* sourceStates = [NSSet setWithArray: @[OctagonStateUntrusted]];
2683 OctagonStateTransitionPath* path = [OctagonStateTransitionPath pathFromDictionary:@{
2684 OctagonStateAttemptSOSUpgrade: @{
2685 OctagonStateBecomeReady: @{
2686 OctagonStateReady: [OctagonStateTransitionPathStep success],
2691 [self.stateMachine doWatchedStateMachineRPC:@"sos-upgrade-to-ready"
2692 sourceStates:sourceStates
2697 - (void)clearPendingCFUFlags
2699 self.postedRecoveryKeyCFU = NO;
2700 self.postedEscrowRepairCFU = NO;
2701 self.postedRepairCFU = NO;