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 #import <CoreCDP/CDPAccount.h>
27 #import <os/feature_private.h>
28 #import <Security/Security.h>
29 #include <Security/SecRandomP.h>
30 #import <SecurityFoundation/SFKey_Private.h>
31 #include <sys/sysctl.h>
32 #import <TrustedPeers/TrustedPeers.h>
35 #import "keychain/TrustedPeersHelper/TrustedPeersHelperProtocol.h"
36 #include "keychain/SecureObjectSync/SOSInternal.h"
38 #import "keychain/analytics/CKKSLaunchSequence.h"
39 #import "keychain/categories/NSError+UsefulConstructors.h"
40 #import "keychain/ckks/CKKS.h"
41 #import "keychain/ckks/CKKSAccountStateTracker.h"
42 #import "keychain/ckks/CKKSAnalytics.h"
43 #import "keychain/ckks/CKKSKeychainView.h"
44 #import "keychain/ckks/CKKSResultOperation.h"
45 #import "keychain/ckks/CKKSViewManager.h"
46 #import "keychain/ckks/CloudKitCategories.h"
47 #import "keychain/ckks/OctagonAPSReceiver.h"
48 #import "keychain/escrowrequest/EscrowRequestServer.h"
49 #import "keychain/ot/OTAuthKitAdapter.h"
50 #import "keychain/ot/OTCheckHealthOperation.h"
51 #import "keychain/ot/OTClientVoucherOperation.h"
52 #import "keychain/ot/OTClique.h"
53 #import "keychain/ot/OTConstants.h"
54 #import "keychain/ot/OTCuttlefishAccountStateHolder.h"
55 #import "keychain/ot/OTCuttlefishContext.h"
56 #import "keychain/ot/OTDetermineCDPBitStatusOperation.h"
57 #import "keychain/ot/OTDetermineHSA2AccountStatusOperation.h"
58 #import "keychain/ot/OTDeviceInformationAdapter.h"
59 #import "keychain/ot/OTEnsureOctagonKeyConsistency.h"
60 #import "keychain/ot/OTPreloadOctagonKeysOperation.h"
61 #import "keychain/ot/OTEpochOperation.h"
62 #import "keychain/ot/OTEstablishOperation.h"
63 #import "keychain/ot/OTFetchViewsOperation.h"
64 #import "keychain/ot/OTFollowup.h"
65 #import "keychain/ot/OTJoinWithVoucherOperation.h"
66 #import "keychain/ot/OTLeaveCliqueOperation.h"
67 #import "keychain/ot/OTLocalCKKSResetOperation.h"
68 #import "keychain/ot/OTLocalCuttlefishReset.h"
69 #import "keychain/ot/OTModifyUserControllableViewStatusOperation.h"
70 #import "keychain/ot/OTOperationDependencies.h"
71 #import "keychain/ot/OTPrepareOperation.h"
72 #import "keychain/ot/OTRemovePeersOperation.h"
73 #import "keychain/ot/OTResetCKKSZonesLackingTLKsOperation.h"
74 #import "keychain/ot/OTResetOperation.h"
75 #import "keychain/ot/OTSOSAdapter.h"
76 #import "keychain/ot/OTSOSUpdatePreapprovalsOperation.h"
77 #import "keychain/ot/OTSOSUpgradeOperation.h"
78 #import "keychain/ot/OTSetCDPBitOperation.h"
79 #import "keychain/ot/OTSetRecoveryKeyOperation.h"
80 #import "keychain/ot/OTStates.h"
81 #import "keychain/ot/OTTriggerEscrowUpdateOperation.h"
82 #import "keychain/ot/OTUpdateTPHOperation.h"
83 #import "keychain/ot/OTUpdateTrustedDeviceListOperation.h"
84 #import "keychain/ot/OTUploadNewCKKSTLKsOperation.h"
85 #import "keychain/ot/OTVouchWithBottleOperation.h"
86 #import "keychain/ot/OTVouchWithRecoveryKeyOperation.h"
87 #import "keychain/ot/ObjCImprovements.h"
88 #import "keychain/ot/OctagonCKKSPeerAdapter.h"
89 #import "keychain/ot/OctagonCheckTrustStateOperation.h"
90 #import "keychain/ot/OctagonStateMachine.h"
91 #import "keychain/ot/OctagonStateMachineHelpers.h"
92 #import "keychain/ot/categories/OTAccountMetadataClassC+KeychainSupport.h"
93 #import "keychain/ot/proto/generated_source/OTAccountMetadataClassC.h"
94 #import "keychain/securityd/SOSCloudCircleServer.h"
96 #import "utilities/SecFileLocations.h"
97 #import "utilities/SecTapToRadar.h"
100 #import "keychain/otpaird/OTPairingClient.h"
101 #endif /* TARGET_OS_WATCH */
106 NSString* OTCuttlefishContextErrorDomain = @"otcuttlefish";
107 static dispatch_time_t OctagonStateTransitionDefaultTimeout = 10*NSEC_PER_SEC;
109 @class CKKSLockStateTracker;
111 @interface OTCuttlefishContext () <OTCuttlefishAccountStateHolderNotifier>
114 NSString* _bottleSalt;
116 NSString* _recoveryKey;
117 CuttlefishResetReason _resetReason;
118 BOOL _skipRateLimitingCheck;
121 @property CKKSLaunchSequence* launchSequence;
122 @property NSOperationQueue* operationQueue;
123 @property (nonatomic, strong) OTCuttlefishAccountStateHolder *accountMetadataStore;
124 @property OTFollowup *followupHandler;
126 @property (readonly) id<CKKSCloudKitAccountStateTrackingProvider, CKKSOctagonStatusMemoizer> accountStateTracker;
127 @property CKAccountInfo* cloudKitAccountInfo;
128 @property CKKSCondition *cloudKitAccountStateKnown;
130 @property CKKSNearFutureScheduler* suggestTLKUploadNotifier;
131 @property CKKSNearFutureScheduler* requestPolicyCheckNotifier;
133 @property OctagonAPSReceiver* apsReceiver;
136 @property (nullable) CKKSViewManager* viewManager;
138 // Dependencies (for injection)
139 @property id<OTSOSAdapter> sosAdapter;
140 @property id<CKKSPeerProvider> octagonAdapter;
141 @property (readonly) Class<SecEscrowRequestable> escrowRequestClass;
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 _apsReceiver = [OctagonAPSReceiver receiverForNamedDelegatePort:SecCKKSAPSNamedPort
169 apsConnectionClass:apsConnectionClass];
170 [_apsReceiver registerCuttlefishReceiver:self forContainerName:self.containerName];
172 _viewManager = viewManager;
174 _initialBecomeUntrustedPosted = NO;
176 _launchSequence = [[CKKSLaunchSequence alloc] initWithRocketName:@"com.apple.octagon.launch"];
178 _queue = dispatch_queue_create("com.apple.security.otcuttlefishcontext", DISPATCH_QUEUE_SERIAL);
179 _operationQueue = [[NSOperationQueue alloc] init];
180 _cloudKitAccountStateKnown = [[CKKSCondition alloc] init];
182 _accountMetadataStore = [[OTCuttlefishAccountStateHolder alloc] initWithQueue:_queue
183 container:_containerName
185 [_accountMetadataStore registerNotification:self];
187 _stateMachine = [[OctagonStateMachine alloc] initWithName:@"octagon"
188 states:[NSSet setWithArray:[OctagonStateMap() allKeys]]
189 flags:AllOctagonFlags()
190 initialState:OctagonStateInitializing
193 lockStateTracker:lockStateTracker];
195 _sosAdapter = sosAdapter;
196 [_sosAdapter registerForPeerChangeUpdates:self];
197 _authKitAdapter = authKitAdapter;
198 _deviceAdapter = deviceInformationAdapter;
199 [_deviceAdapter registerForDeviceNameUpdates:self];
201 _cuttlefishXPCWrapper = [[CuttlefishXPCWrapper alloc] initWithCuttlefishXPCConnection:cuttlefish];
202 _lockStateTracker = lockStateTracker;
203 _accountStateTracker = accountStateTracker;
205 _followupHandler = [[OTFollowup alloc] initWithFollowupController:cdpd];
207 [accountStateTracker registerForNotificationsOfCloudKitAccountStatusChange:self];
208 [_authKitAdapter registerNotification:self];
210 _escrowRequestClass = escrowRequestClass;
212 _suggestTLKUploadNotifier = [[CKKSNearFutureScheduler alloc] initWithName:@"octagon-tlk-request"
213 delay:500*NSEC_PER_MSEC
214 keepProcessAlive:false
215 dependencyDescriptionCode:0
218 secnotice("octagon-ckks", "Adding flag for CKKS TLK upload");
219 [self.stateMachine handleFlag:OctagonFlagCKKSRequestsTLKUpload];
222 _requestPolicyCheckNotifier = [[CKKSNearFutureScheduler alloc] initWithName:@"octagon-policy-check"
223 delay:500*NSEC_PER_MSEC
224 keepProcessAlive:false
225 dependencyDescriptionCode:0 block:^{
227 secnotice("octagon-ckks", "Adding flag for CKKS policy check");
228 [self.stateMachine handleFlag:OctagonFlagCKKSRequestsPolicyCheck];
234 - (void)clearCKKSViewManager
236 self.viewManager = nil;
241 // TODO: how to invalidate this?
242 //[self.cuttlefishXPCWrapper invalidate];
245 - (void)notifyTrustChanged:(OTAccountMetadataClassC_TrustState)trustState {
247 secnotice("octagon", "Changing trust status to: %@",
248 (trustState == OTAccountMetadataClassC_TrustState_TRUSTED) ? @"Trusted" : @"Untrusted");
251 * We are posting the legacy SOS notification if we don't use SOS
252 * need to rework clients to use a new signal instead of SOS.
254 if (!OctagonPlatformSupportsSOS()) {
255 notify_post(kSOSCCCircleChangedNotification);
258 notify_post(OTTrustStatusChangeNotification);
261 - (void)accountStateUpdated:(OTAccountMetadataClassC*)newState from:(OTAccountMetadataClassC *)oldState
263 if (newState.icloudAccountState == OTAccountMetadataClassC_AccountState_ACCOUNT_AVAILABLE && oldState.icloudAccountState != OTAccountMetadataClassC_AccountState_ACCOUNT_AVAILABLE) {
264 [self.launchSequence addEvent:@"iCloudAccount"];
267 if (newState.trustState == OTAccountMetadataClassC_TrustState_TRUSTED && oldState.trustState != OTAccountMetadataClassC_TrustState_TRUSTED) {
268 [self.launchSequence addEvent:@"Trusted"];
270 if (newState.trustState != OTAccountMetadataClassC_TrustState_TRUSTED && oldState.trustState == OTAccountMetadataClassC_TrustState_TRUSTED) {
271 [self.launchSequence addEvent:@"Untrusted"];
272 [self notifyTrustChanged:newState.trustState];
276 - (NSString*)description
278 return [NSString stringWithFormat:@"<OTCuttlefishContext: %@, %@>", self.containerName, self.contextID];
281 - (void)machinesAdded:(NSArray<NSString*>*)machineIDs altDSID:(NSString*)altDSID
284 NSError* metadataError = nil;
285 OTAccountMetadataClassC* accountMetadata = [self.accountMetadataStore loadOrCreateAccountMetadata:&metadataError];
287 if(!accountMetadata || metadataError) {
288 // TODO: collect a sysdiagnose here if the error is not "device is in class D"
289 secerror("octagon-authkit: Unable to load account metadata: %@", metadataError);
290 [self requestTrustedDeviceListRefresh];
294 if(!altDSID || ![accountMetadata.altDSID isEqualToString:altDSID]) {
295 secnotice("octagon-authkit", "Machines-added push is for wrong altDSID (%@); current altDSID (%@)", altDSID, accountMetadata.altDSID);
299 secnotice("octagon-authkit", "adding machines for altDSID(%@): %@", altDSID, machineIDs);
301 [self.cuttlefishXPCWrapper addAllowedMachineIDsWithContainer:self.containerName
302 context:self.contextID
303 machineIDs:machineIDs
304 reply:^(NSError* error) {
307 secerror("octagon-authkit: addAllow errored: %@", error);
308 [self requestTrustedDeviceListRefresh];
310 secnotice("octagon-authkit", "addAllow succeeded");
312 OctagonPendingFlag *pendingFlag = [[OctagonPendingFlag alloc] initWithFlag:OctagonFlagCuttlefishNotification
313 conditions:OctagonPendingConditionsDeviceUnlocked];
314 [self.stateMachine handlePendingFlag:pendingFlag];
319 - (void)machinesRemoved:(NSArray<NSString*>*)machineIDs altDSID:(NSString*)altDSID
323 NSError* metadataError = nil;
324 OTAccountMetadataClassC* accountMetadata = [self.accountMetadataStore loadOrCreateAccountMetadata:&metadataError];
326 if(!accountMetadata || metadataError) {
327 // TODO: collect a sysdiagnose here if the error is not "device is in class D"
328 secerror("octagon-authkit: Unable to load account metadata: %@", metadataError);
329 [self requestTrustedDeviceListRefresh];
333 if(!altDSID || ![accountMetadata.altDSID isEqualToString:altDSID]) {
334 secnotice("octagon-authkit", "Machines-removed push is for wrong altDSID (%@); current altDSID (%@)", altDSID, accountMetadata.altDSID);
338 secnotice("octagon-authkit", "removing machines for altDSID(%@): %@", altDSID, machineIDs);
340 [self.cuttlefishXPCWrapper removeAllowedMachineIDsWithContainer:self.containerName
341 context:self.contextID
342 machineIDs:machineIDs
343 reply:^(NSError* _Nullable error) {
346 secerror("octagon-authkit: removeAllow errored: %@", error);
348 secnotice("octagon-authkit", "removeAllow succeeded");
351 // We don't necessarily trust remove pushes; they could be delayed past when an add has occurred.
352 // Request that the full list be rechecked.
353 [self requestTrustedDeviceListRefresh];
357 - (void)incompleteNotificationOfMachineIDListChange
359 secnotice("octagon", "incomplete machine ID list notification -- refreshing device list");
360 [self requestTrustedDeviceListRefresh];
364 - (void)cloudkitAccountStateChange:(CKAccountInfo* _Nullable)oldAccountInfo
365 to:(CKAccountInfo*)currentAccountInfo
367 dispatch_sync(self.queue, ^{
368 // We don't persist the CK account state; rather, we fetch it anew on every daemon launch.
369 // But, we also have to integrate it into our asynchronous state machine.
370 // So, record the current CK account value, and trigger state machine reprocessing.
372 secnotice("octagon", "Told of a new CK account status: %@", currentAccountInfo);
373 self.cloudKitAccountInfo = currentAccountInfo;
374 [self.stateMachine _onqueuePokeStateMachine];
376 // But, having the state machine perform the signout is confusing: it would need to make decisions based
377 // on things other than the current state. So, use the RPC mechanism to give it input.
378 // If we receive a sign-in before the sign-out rpc runs, the state machine will be sufficient to get back into
379 // the in-account state.
381 // Also let other clients now that we have CK account status
382 [self.cloudKitAccountStateKnown fulfill];
385 if(!(currentAccountInfo.accountStatus == CKAccountStatusAvailable)) {
386 secnotice("octagon", "Informed that the CK account is now unavailable: %@", currentAccountInfo);
388 // Add a state machine request to return to OctagonStateWaitingForCloudKitAccount
389 [self.stateMachine doSimpleStateMachineRPC:@"cloudkit-account-gone"
390 op:[OctagonStateTransitionOperation named:@"cloudkit-account-gone"
391 entering:OctagonStateWaitingForCloudKitAccount]
392 sourceStates:OctagonInAccountStates()
393 reply:^(NSError* error) {}];
397 - (BOOL)accountAvailable:(NSString*)altDSID error:(NSError**)error
399 secnotice("octagon", "Account available with altDSID: %@ %@", altDSID, self);
401 self.launchSequence.firstLaunch = true;
403 NSError* localError = nil;
404 [self.accountMetadataStore persistAccountChanges:^OTAccountMetadataClassC * _Nonnull(OTAccountMetadataClassC * _Nonnull metadata) {
405 // 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...
406 metadata.altDSID = altDSID;
409 } error:&localError];
412 secerror("octagon: unable to persist new account availability: %@", localError);
415 [self.stateMachine handleFlag:OctagonFlagAccountIsAvailable];
417 if(OctagonIsOptimizationEnabled()){
418 [self.stateMachine handleFlag:OctagonFlagWarmEscrowRecordCache];
429 - (void) moveToCheckTrustedState
431 CKKSResultOperation<OctagonStateTransitionOperationProtocol>* checkTrust
432 = [OctagonStateTransitionOperation named:[NSString stringWithFormat:@"%@", @"check-trust-state"]
433 entering:OctagonStateCheckTrustState];
435 NSSet* sourceStates = [NSSet setWithArray: @[OctagonStateUntrusted]];
437 OctagonStateTransitionRequest<OctagonStateTransitionOperation*>* request = [[OctagonStateTransitionRequest alloc] init:@"check-trust-state"
438 sourceStates:sourceStates
439 serialQueue:self.queue
440 timeout:OctagonStateTransitionDefaultTimeout
441 transitionOp:checkTrust];
442 [self.stateMachine handleExternalRequest:request];
446 - (BOOL)idmsTrustLevelChanged:(NSError**)error
448 [self.stateMachine handleFlag:OctagonFlagIDMSLevelChanged];
452 - (BOOL)accountNoLongerAvailable:(NSError**)error
454 OctagonStateTransitionOperation* attemptOp = [OctagonStateTransitionOperation named:@"octagon-account-gone"
455 intending:OctagonStateNoAccountDoReset
456 errorState:OctagonStateNoAccountDoReset
457 withBlockTakingSelf:^(OctagonStateTransitionOperation * _Nonnull op) {
458 __block NSError* localError = nil;
460 secnotice("octagon", "Account now unavailable: %@", self);
461 [self.accountMetadataStore persistAccountChanges:^OTAccountMetadataClassC *(OTAccountMetadataClassC * metadata) {
462 metadata.icloudAccountState = OTAccountMetadataClassC_AccountState_NO_ACCOUNT;
463 metadata.altDSID = nil;
464 metadata.trustState = OTAccountMetadataClassC_TrustState_UNKNOWN;
465 metadata.cdpState = OTAccountMetadataClassC_CDPState_UNKNOWN;
468 } error:&localError];
471 secerror("octagon: unable to persist new account availability: %@", localError);
474 [self.accountStateTracker setHSA2iCloudAccountStatus:CKKSAccountStatusNoAccount];
476 // Bring CKKS down, too
477 for (id key in self.viewManager.views) {
478 CKKSKeychainView* view = self.viewManager.views[key];
479 secnotice("octagon-ckks", "Informing %@ of new untrusted status (due to account disappearance)", view);
480 [view endTrustedOperation];
483 op.error = localError;
486 // Signout works from literally any state. Goodbye, account!
487 NSSet* sourceStates = [NSSet setWithArray: OctagonStateMap().allKeys];
488 OctagonStateTransitionRequest<OctagonStateTransitionOperation*>* request = [[OctagonStateTransitionRequest alloc] init:@"account-not-available"
489 sourceStates:sourceStates
490 serialQueue:self.queue
491 timeout:OctagonStateTransitionDefaultTimeout
492 transitionOp:attemptOp];
493 [self.stateMachine handleExternalRequest:request];
498 - (OTCDPStatus)getCDPStatus:(NSError*__autoreleasing*)error
500 NSError* localError = nil;
501 OTAccountMetadataClassC* accountState = [self.accountMetadataStore loadOrCreateAccountMetadata:&localError];
504 secnotice("octagon-cdp-status", "error fetching account metadata: %@", localError);
509 return OTCDPStatusUnknown;
512 OTCDPStatus status = OTCDPStatusUnknown;
513 switch(accountState.cdpState) {
514 case OTAccountMetadataClassC_CDPState_UNKNOWN:
515 status = OTCDPStatusUnknown;
517 case OTAccountMetadataClassC_CDPState_DISABLED:
518 status = OTCDPStatusDisabled;
520 case OTAccountMetadataClassC_CDPState_ENABLED:
521 status = OTCDPStatusEnabled;
525 secnotice("octagon-cdp-status", "current cdp status is: %@", OTCDPStatusToString(status));
529 - (BOOL)setCDPEnabled:(NSError* __autoreleasing *)error
531 NSError* localError = nil;
532 [self.accountMetadataStore persistAccountChanges:^OTAccountMetadataClassC * _Nonnull(OTAccountMetadataClassC * _Nonnull metadata) {
533 metadata.cdpState = OTAccountMetadataClassC_CDPState_ENABLED;
535 } error:&localError];
537 [self.stateMachine handleFlag:OctagonFlagCDPEnabled];
540 secerror("octagon-cdp-status: unable to persist CDP enablement: %@", localError);
547 secnotice("octagon-cdp-status", "Successfully set CDP status bit to 'enabled''");
551 - (void)resetOctagonStateMachine
553 OctagonStateTransitionOperation* op = [OctagonStateTransitionOperation named:@"resetting-state-machine"
554 entering:OctagonStateInitializing];
555 NSMutableSet* sourceStates = [NSMutableSet setWithArray: OctagonStateMap().allKeys];
557 OctagonStateTransitionRequest<OctagonStateTransitionOperation*>* request = [[OctagonStateTransitionRequest alloc] init:@"resetting-state-machine"
558 sourceStates:sourceStates
559 serialQueue:self.queue
560 timeout:OctagonStateTransitionDefaultTimeout
563 [self.stateMachine handleExternalRequest:request];
567 - (void)localReset:(nonnull void (^)(NSError * _Nullable))reply
569 OTLocalResetOperation* pendingOp = [[OTLocalResetOperation alloc] initWithDependencies:self.operationDependencies
570 intendedState:OctagonStateInitializing
571 errorState:OctagonStateError];
573 NSMutableSet* sourceStates = [NSMutableSet setWithArray: OctagonStateMap().allKeys];
574 [self.stateMachine doSimpleStateMachineRPC:@"local-reset" op:pendingOp sourceStates:sourceStates reply:reply];
577 - (NSDictionary*)establishStatePathDictionary
580 OctagonStateEstablishEnableCDPBit: @{
581 OctagonStateReEnactDeviceList: @{
582 OctagonStateReEnactPrepare: @{
583 OctagonStateReEnactReadyToEstablish: @{
584 OctagonStateEscrowTriggerUpdate: @{
585 OctagonStateBecomeReady: @{
586 OctagonStateReady: [OctagonStateTransitionPathStep success],
590 // Error handling extra states:
591 OctagonStateEstablishCKKSReset: @{
592 OctagonStateEstablishAfterCKKSReset: @{
593 OctagonStateEscrowTriggerUpdate: @{
594 OctagonStateBecomeReady: @{
595 OctagonStateReady: [OctagonStateTransitionPathStep success],
607 - (void)rpcEstablish:(nonnull NSString *)altDSID
608 reply:(nonnull void (^)(NSError * _Nullable))reply
610 // The reset flow can split into an error-handling path halfway through; this is okay
611 OctagonStateTransitionPath* path = [OctagonStateTransitionPath pathFromDictionary:[self establishStatePathDictionary]];
613 [self.stateMachine doWatchedStateMachineRPC:@"establish"
614 sourceStates:OctagonInAccountStates()
619 - (void)rpcResetAndEstablish:(CuttlefishResetReason)resetReason reply:(nonnull void (^)(NSError * _Nullable))reply
621 _resetReason = resetReason;
623 // The reset flow can split into an error-handling path halfway through; this is okay
624 OctagonStateTransitionPath* path = [OctagonStateTransitionPath pathFromDictionary: @{
625 OctagonStateResetBecomeUntrusted: @{
626 OctagonStateResetAndEstablish: @{
627 OctagonStateResetAnyMissingTLKCKKSViews: [self establishStatePathDictionary]
632 // Now, take the state machine from any in-account state to the beginning of the reset flow.
633 [self.stateMachine doWatchedStateMachineRPC:@"rpc-reset-and-establish"
634 sourceStates:OctagonInAccountStates()
639 - (void)rpcLeaveClique:(nonnull void (^)(NSError * _Nullable))reply
641 OTLeaveCliqueOperation* op = [[OTLeaveCliqueOperation alloc] initWithDependencies:self.operationDependencies
642 intendedState:OctagonStateBecomeUntrusted
643 errorState:OctagonStateError];
645 NSSet* sourceStates = [NSSet setWithObject: OctagonStateReady];
646 [self.stateMachine doSimpleStateMachineRPC:@"leave-clique" op:op sourceStates:sourceStates reply:reply];
649 - (void)rpcRemoveFriendsInClique:(NSArray<NSString*>*)peerIDs
650 reply:(void (^)(NSError * _Nullable))reply
652 OTRemovePeersOperation* op = [[OTRemovePeersOperation alloc] initWithDependencies:self.operationDependencies
653 intendedState:OctagonStateBecomeReady
654 errorState:OctagonStateBecomeReady
657 NSSet* sourceStates = [NSSet setWithObject: OctagonStateReady];
658 [self.stateMachine doSimpleStateMachineRPC:@"remove-friends" op:op sourceStates:sourceStates reply:reply];
661 - (OTDeviceInformation*)prepareInformation
663 NSError* error = nil;
664 NSString* machineID = [self.authKitAdapter machineID:&error];
666 if(!machineID || error) {
667 secerror("octagon: Unable to fetch machine ID; expect signin to fail: %@", error);
670 return [[OTDeviceInformation alloc] initForContainerName:self.containerName
671 contextID:self.contextID
674 modelID:self.deviceAdapter.modelID
675 deviceName:self.deviceAdapter.deviceName
676 serialNumber:self.deviceAdapter.serialNumber
677 osVersion:self.deviceAdapter.osVersion];
680 - (OTOperationDependencies*)operationDependencies
682 return [[OTOperationDependencies alloc] initForContainer:self.containerName
683 contextID:self.contextID
684 stateHolder:self.accountMetadataStore
685 flagHandler:self.stateMachine
686 sosAdapter:self.sosAdapter
687 octagonAdapter:self.octagonAdapter
688 authKitAdapter:self.authKitAdapter
689 deviceInfoAdapter:self.deviceAdapter
690 viewManager:self.viewManager
691 lockStateTracker:self.lockStateTracker
692 cuttlefishXPCWrapper:self.cuttlefishXPCWrapper
693 escrowRequestClass:self.escrowRequestClass];
696 - (void)startOctagonStateMachine
698 [self.stateMachine startOperation];
701 - (void)handlePairingRestart:(OTJoiningConfiguration*)config
703 if(self.pairingUUID == nil){
704 secnotice("octagon-pairing", "received new pairing UUID (%@)", config.pairingUUID);
705 self.pairingUUID = config.pairingUUID;
708 if(![self.pairingUUID isEqualToString:config.pairingUUID]){
709 secnotice("octagon-pairing", "current pairing UUID (%@) does not match config UUID (%@)", self.pairingUUID, config.pairingUUID);
711 dispatch_semaphore_t sema = dispatch_semaphore_create(0);
712 [self localReset:^(NSError * _Nullable localResetError) {
713 if(localResetError) {
714 secerror("localReset returned an error: %@", localResetError);
716 secnotice("octagon", "localReset succeeded");
717 self.pairingUUID = config.pairingUUID;
719 dispatch_semaphore_signal(sema);
721 if (0 != dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 10))) {
722 secerror("octagon: Timed out waiting for local reset to complete");
727 #pragma mark --- State Machine Transitions
729 - (CKKSResultOperation<OctagonStateTransitionOperationProtocol>* _Nullable)_onqueueNextStateMachineTransition:(OctagonState*)currentState
730 flags:(nonnull OctagonFlags *)flags
731 pendingFlags:(nonnull id<OctagonStateOnqueuePendingFlagHandler>)pendingFlagHandler
733 dispatch_assert_queue(self.queue);
735 [self.launchSequence addEvent:currentState];
737 // If We're initializing, or there was some recent update to the account state,
738 // attempt to see what state we should enter.
739 if([currentState isEqualToString: OctagonStateInitializing]) {
740 return [self initializingOperation];
743 if([currentState isEqualToString:OctagonStateWaitForHSA2]) {
744 if([flags _onqueueContains:OctagonFlagIDMSLevelChanged]) {
745 [flags _onqueueRemoveFlag:OctagonFlagIDMSLevelChanged];
746 return [OctagonStateTransitionOperation named:@"hsa2-check"
747 entering:OctagonStateDetermineiCloudAccountState];
750 secnotice("octagon", "Waiting for an HSA2 account");
754 if([currentState isEqualToString:OctagonStateWaitingForCloudKitAccount]) {
755 // Here, integrate the memoized CK account state into our state machine
756 if(self.cloudKitAccountInfo && self.cloudKitAccountInfo.accountStatus == CKAccountStatusAvailable) {
757 secnotice("octagon", "CloudKit reports an account is available!");
758 return [OctagonStateTransitionOperation named:@"ck-available"
759 entering:OctagonStateCloudKitNewlyAvailable];
761 secnotice("octagon", "Waiting for a CloudKit account; current state is %@", self.cloudKitAccountInfo ?: @"uninitialized");
766 if([currentState isEqualToString:OctagonStateCloudKitNewlyAvailable]) {
767 OctagonFlag* warmEscrowCache = nil;
768 if([flags _onqueueContains:OctagonFlagWarmEscrowRecordCache] && OctagonIsOptimizationEnabled()) {
769 [flags _onqueueRemoveFlag:OctagonFlagWarmEscrowRecordCache];
770 warmEscrowCache = OctagonFlagWarmEscrowRecordCache;
772 return [self cloudKitAccountNewlyAvailableOperation:warmEscrowCache];
775 if([currentState isEqualToString:OctagonStateDetermineCDPState]) {
776 return [[OTDetermineCDPBitStatusOperation alloc] initWithDependencies:self.operationDependencies
777 intendedState:OctagonStateCheckTrustState
778 errorState:OctagonStateWaitForCDP];
781 if([currentState isEqualToString:OctagonStateWaitForCDP]) {
782 if([flags _onqueueContains:OctagonFlagCDPEnabled]) {
783 [flags _onqueueRemoveFlag:OctagonFlagCDPEnabled];
784 secnotice("octagon", "CDP is newly available!");
786 return [OctagonStateTransitionOperation named:@"cdp_enabled"
787 entering:OctagonStateDetermineiCloudAccountState];
789 } else if([flags _onqueueContains:OctagonFlagCuttlefishNotification]) {
790 [flags _onqueueRemoveFlag:OctagonFlagCuttlefishNotification];
791 return [OctagonStateTransitionOperation named:@"cdp_enabled_push_received"
792 entering:OctagonStateWaitForCDPUpdated];
799 if([currentState isEqualToString:OctagonStateWaitForCDPUpdated]) {
800 return [[OTUpdateTPHOperation alloc] initWithDependencies:self.operationDependencies
801 intendedState:OctagonStateDetermineCDPState
802 peerUnknownState:OctagonStateDetermineCDPState
803 errorState:OctagonStateError
804 retryFlag:OctagonFlagCuttlefishNotification];
807 if([currentState isEqualToString:OctagonStateCheckTrustState]) {
808 return [[OctagonCheckTrustStateOperation alloc] initWithDependencies:self.operationDependencies
809 intendedState:OctagonStateBecomeUntrusted
810 errorState:OctagonStateBecomeUntrusted];
812 #pragma mark --- Octagon Health Check States
813 if([currentState isEqualToString:OctagonStateHSA2HealthCheck]) {
814 return [[OTDetermineHSA2AccountStatusOperation alloc] initWithDependencies:self.operationDependencies
815 stateIfHSA2:OctagonStateCDPHealthCheck
816 stateIfNotHSA2:OctagonStateWaitForHSA2
817 stateIfNoAccount:OctagonStateNoAccount
818 errorState:OctagonStateError];
821 if([currentState isEqualToString:OctagonStateCDPHealthCheck]) {
822 return [[OTDetermineCDPBitStatusOperation alloc] initWithDependencies:self.operationDependencies
823 intendedState:OctagonStateSecurityTrustCheck
824 errorState:OctagonStateWaitForCDP];
827 if([currentState isEqualToString:OctagonStateSecurityTrustCheck]) {
828 return [self evaluateSecdOctagonTrust];
831 if([currentState isEqualToString:OctagonStateTPHTrustCheck]) {
832 return [self evaluateTPHOctagonTrust];
835 if([currentState isEqualToString:OctagonStateCuttlefishTrustCheck]) {
836 return [self cuttlefishTrustEvaluation];
839 if ([currentState isEqualToString:OctagonStatePostRepairCFU]) {
840 return [self postRepairCFUAndBecomeUntrusted];
843 if ([currentState isEqualToString:OctagonStateHealthCheckReset]) {
844 // A small violation of state machines...
845 _resetReason = CuttlefishResetReasonHealthCheck;
846 return [OctagonStateTransitionOperation named:@"begin-reset"
847 entering:OctagonStateResetBecomeUntrusted];
850 #pragma mark --- Watch Pairing States
853 if([currentState isEqualToString:OctagonStateStartCompanionPairing]) {
854 return [self startCompanionPairingOperation];
856 #endif /* TARGET_OS_WATCH */
858 if([currentState isEqualToString:OctagonStateBecomeUntrusted]) {
859 return [self becomeUntrustedOperation:OctagonStateUntrusted];
862 if([currentState isEqualToString:OctagonStateBecomeReady]) {
863 return [self becomeReadyOperation];
866 if([currentState isEqualToString:OctagonStateRefetchCKKSPolicy]) {
867 return [[OTFetchViewsOperation alloc] initWithDependencies:self.operationDependencies
868 intendedState:OctagonStateBecomeReady
869 errorState:OctagonStateError];
872 if([currentState isEqualToString:OctagonStateEnableUserControllableViews]) {
873 return [[OTModifyUserControllableViewStatusOperation alloc] initWithDependencies:self.operationDependencies
874 intendedViewStatus:TPPBPeerStableInfo_UserControllableViewStatus_ENABLED
875 intendedState:OctagonStateBecomeReady
876 peerMissingState:OctagonStateReadyUpdated
877 errorState:OctagonStateBecomeReady];
880 if([currentState isEqualToString:OctagonStateDisableUserControllableViews]) {
881 return [[OTModifyUserControllableViewStatusOperation alloc] initWithDependencies:self.operationDependencies
882 intendedViewStatus:TPPBPeerStableInfo_UserControllableViewStatus_DISABLED
883 intendedState:OctagonStateBecomeReady
884 peerMissingState:OctagonStateReadyUpdated
885 errorState:OctagonStateBecomeReady];
888 if([currentState isEqualToString:OctagonStateSetUserControllableViewsToPeerConsensus]) {
889 // Setting the status to FOLLOWING will either enable or disable the value, depending on our peers.
890 return [[OTModifyUserControllableViewStatusOperation alloc] initWithDependencies:self.operationDependencies
891 intendedViewStatus:TPPBPeerStableInfo_UserControllableViewStatus_FOLLOWING
892 intendedState:OctagonStateBecomeReady
893 peerMissingState:OctagonStateReadyUpdated
894 errorState:OctagonStateBecomeReady];
897 if([currentState isEqualToString:OctagonStateNoAccount]) {
898 // We only want to move out of untrusted if something useful has happened!
899 if([flags _onqueueContains:OctagonFlagAccountIsAvailable]) {
900 [flags _onqueueRemoveFlag:OctagonFlagAccountIsAvailable];
901 secnotice("octagon", "Account is available! Attempting initializing op!");
902 return [OctagonStateTransitionOperation named:@"account-probably-present"
903 entering:OctagonStateInitializing];
907 if([currentState isEqualToString:OctagonStateUntrusted]) {
908 // We only want to move out of untrusted if something useful has happened!
909 if([flags _onqueueContains:OctagonFlagEgoPeerPreapproved]) {
910 [flags _onqueueRemoveFlag:OctagonFlagEgoPeerPreapproved];
911 if(self.sosAdapter.sosEnabled) {
912 secnotice("octagon", "Preapproved flag is high. Attempt SOS upgrade again!");
913 return [OctagonStateTransitionOperation named:@"ck-available"
914 entering:OctagonStateAttemptSOSUpgrade];
917 secnotice("octagon", "We are untrusted, but it seems someone preapproves us now. Unfortunately, this platform doesn't support SOS.");
921 if([flags _onqueueContains:OctagonFlagAttemptSOSUpgrade]) {
922 [flags _onqueueRemoveFlag:OctagonFlagAttemptSOSUpgrade];
923 if(self.sosAdapter.sosEnabled) {
924 secnotice("octagon", "Attempt SOS upgrade again!");
925 return [OctagonStateTransitionOperation named:@"attempt-sos-upgrade"
926 entering:OctagonStateAttemptSOSUpgrade];
929 secnotice("octagon", "We are untrusted, but this platform doesn't support SOS.");
933 if([flags _onqueueContains:OctagonFlagAttemptSOSConsistency]) {
934 [flags _onqueueRemoveFlag:OctagonFlagAttemptSOSConsistency];
935 if(self.sosAdapter.sosEnabled) {
936 secnotice("octagon", "Attempting SOS upgrade again (due to a consistency notification)");
937 return [OctagonStateTransitionOperation named:@"attempt-sos-upgrade"
938 entering:OctagonStateAttemptSOSUpgrade];
940 secnotice("octagon", "Someone would like us to check SOS consistency, but this platform doesn't support SOS.");
944 if([flags _onqueueContains:OctagonFlagCuttlefishNotification]) {
945 [flags _onqueueRemoveFlag:OctagonFlagCuttlefishNotification];
946 secnotice("octagon", "Updating TPH (while untrusted) due to push");
947 return [OctagonStateTransitionOperation named:@"untrusted-update"
948 entering:OctagonStateUntrustedUpdated];
951 // We're untrusted; no need for the IDMS level flag anymore
952 if([flags _onqueueContains:OctagonFlagIDMSLevelChanged]) {
953 [flags _onqueueRemoveFlag:OctagonFlagIDMSLevelChanged];
956 // We're untrusted; no need for the CDP level flag anymore
957 if([flags _onqueueContains:OctagonFlagCDPEnabled]) {
958 secnotice("octagon", "Removing 'CDP enabled' flag");
959 [flags _onqueueRemoveFlag:OctagonFlagCDPEnabled];
963 if([currentState isEqualToString:OctagonStateUntrustedUpdated]) {
964 return [[OTUpdateTPHOperation alloc] initWithDependencies:self.operationDependencies
965 intendedState:OctagonStateUntrusted
966 peerUnknownState:OctagonStateBecomeUntrusted
967 errorState:OctagonStateError
968 retryFlag:OctagonFlagCuttlefishNotification];
971 if([currentState isEqualToString:OctagonStateDetermineiCloudAccountState]) {
972 secnotice("octagon", "Determine iCloud account status");
974 // If there's an HSA2 account, return to 'initializing' here, as we want to centralize decisions on what to do next
975 return [[OTDetermineHSA2AccountStatusOperation alloc] initWithDependencies:self.operationDependencies
976 stateIfHSA2:OctagonStateInitializing
977 stateIfNotHSA2:OctagonStateWaitForHSA2
978 stateIfNoAccount:OctagonStateNoAccount
979 errorState:OctagonStateError];
982 if([currentState isEqualToString:OctagonStateNoAccountDoReset]) {
983 secnotice("octagon", "Attempting local-reset as part of signout");
984 return [[OTLocalResetOperation alloc] initWithDependencies:self.operationDependencies
985 intendedState:OctagonStateNoAccount
986 errorState:OctagonStateNoAccount];
989 if([currentState isEqualToString:OctagonStateEnsureConsistency]) {
990 secnotice("octagon", "Ensuring consistency of things that might've changed");
991 if(self.sosAdapter.sosEnabled) {
992 return [[OTEnsureOctagonKeyConsistency alloc] initWithDependencies:self.operationDependencies
993 intendedState:OctagonStateEnsureUpdatePreapprovals
994 errorState:OctagonStateBecomeReady];
997 // Add further consistency checks here.
998 return [OctagonStateTransitionOperation named:@"no-consistency-checks"
999 entering:OctagonStateBecomeReady];
1002 if([currentState isEqualToString:OctagonStateBottlePreloadOctagonKeysInSOS]) {
1003 secnotice("octagon", "Preloading Octagon Keys on the SOS Account");
1004 if(self.sosAdapter.sosEnabled) {
1005 return [[OTPreloadOctagonKeysOperation alloc] initWithDependencies:self.operationDependencies
1006 intendedState:OctagonStateBecomeReady
1007 errorState:OctagonStateBecomeReady];
1009 // Add further consistency checks here.
1010 return [OctagonStateTransitionOperation named:@"no-preload-octagon-key"
1011 entering:OctagonStateBecomeReady];
1014 if([currentState isEqualToString:OctagonStateEnsureUpdatePreapprovals]) {
1015 secnotice("octagon", "SOS is enabled; ensuring preapprovals are correct");
1016 return [[OTSOSUpdatePreapprovalsOperation alloc] initWithDependencies:self.operationDependencies
1017 intendedState:OctagonStateBecomeReady
1018 sosNotPresentState:OctagonStateBecomeReady
1019 errorState:OctagonStateBecomeReady];
1022 if([currentState isEqualToString:OctagonStateAttemptSOSUpgradeDetermineCDPState]) {
1023 return [[OTDetermineCDPBitStatusOperation alloc] initWithDependencies:self.operationDependencies
1024 intendedState:OctagonStateAttemptSOSUpgrade
1025 errorState:OctagonStateWaitForCDP];
1028 if([currentState isEqualToString:OctagonStateAttemptSOSUpgrade] && OctagonPerformSOSUpgrade()) {
1029 secnotice("octagon", "Investigating SOS status");
1030 return [[OTSOSUpgradeOperation alloc] initWithDependencies:self.operationDependencies
1031 intendedState:OctagonStateBecomeReady
1032 ckksConflictState:OctagonStateSOSUpgradeCKKSReset
1033 errorState:OctagonStateBecomeUntrusted
1034 deviceInfo:self.prepareInformation
1035 policyOverride:self.policyOverride];
1037 } else if([currentState isEqualToString:OctagonStateSOSUpgradeCKKSReset]) {
1038 return [[OTLocalCKKSResetOperation alloc] initWithDependencies:self.operationDependencies
1039 intendedState:OctagonStateSOSUpgradeAfterCKKSReset
1040 errorState:OctagonStateBecomeUntrusted];
1042 } else if([currentState isEqualToString:OctagonStateSOSUpgradeAfterCKKSReset]) {
1043 return [[OTSOSUpgradeOperation alloc] initWithDependencies:self.operationDependencies
1044 intendedState:OctagonStateBecomeReady
1045 ckksConflictState:OctagonStateBecomeUntrusted
1046 errorState:OctagonStateBecomeUntrusted
1047 deviceInfo:self.prepareInformation
1048 policyOverride:self.policyOverride];
1051 } else if([currentState isEqualToString:OctagonStateCreateIdentityForRecoveryKey]) {
1052 return [[OTPrepareOperation alloc] initWithDependencies:self.operationDependencies
1053 intendedState:OctagonStateVouchWithRecoveryKey
1054 errorState:OctagonStateBecomeUntrusted
1055 deviceInfo:[self prepareInformation]
1056 policyOverride:self.policyOverride
1059 } else if([currentState isEqualToString:OctagonStateBottleJoinCreateIdentity]) {
1060 return [[OTPrepareOperation alloc] initWithDependencies:self.operationDependencies
1061 intendedState:OctagonStateBottleJoinVouchWithBottle
1062 errorState:OctagonStateBecomeUntrusted
1063 deviceInfo:[self prepareInformation]
1064 policyOverride:self.policyOverride
1067 } else if([currentState isEqualToString:OctagonStateBottleJoinVouchWithBottle]) {
1068 return [[OTVouchWithBottleOperation alloc] initWithDependencies:self.operationDependencies
1069 intendedState:OctagonStateInitiatorSetCDPBit
1070 errorState:OctagonStateBecomeUntrusted
1073 bottleSalt:_bottleSalt
1076 } else if([currentState isEqualToString:OctagonStateVouchWithRecoveryKey]) {
1077 return [[OTVouchWithRecoveryKeyOperation alloc] initWithDependencies:self.operationDependencies
1078 intendedState:OctagonStateInitiatorSetCDPBit
1079 errorState:OctagonStateBecomeUntrusted
1080 recoveryKey:_recoveryKey
1083 } else if([currentState isEqualToString:OctagonStateInitiatorSetCDPBit]) {
1084 return [[OTSetCDPBitOperation alloc] initWithDependencies:self.operationDependencies
1085 intendedState:OctagonStateInitiatorUpdateDeviceList
1086 errorState:OctagonStateDetermineCDPState];
1088 } else if([currentState isEqualToString:OctagonStateInitiatorUpdateDeviceList]) {
1089 // As part of the 'initiate' flow, we need to update the trusted device list-you're probably on it already
1090 OTUpdateTrustedDeviceListOperation* op = [[OTUpdateTrustedDeviceListOperation alloc] initWithDependencies:self.operationDependencies
1091 intendedState:OctagonStateInitiatorJoin
1092 listUpdatesState:OctagonStateInitiatorJoin
1093 errorState:OctagonStateBecomeUntrusted
1097 } else if ([currentState isEqualToString:OctagonStateInitiatorJoin]){
1098 return [[OTJoinWithVoucherOperation alloc] initWithDependencies:self.operationDependencies
1099 intendedState:OctagonStateBottlePreloadOctagonKeysInSOS
1100 ckksConflictState:OctagonStateInitiatorJoinCKKSReset
1101 errorState:OctagonStateBecomeUntrusted];
1103 } else if([currentState isEqualToString:OctagonStateInitiatorJoinCKKSReset]) {
1104 return [[OTLocalCKKSResetOperation alloc] initWithDependencies:self.operationDependencies
1105 intendedState:OctagonStateInitiatorJoinAfterCKKSReset
1106 errorState:OctagonStateBecomeUntrusted];
1108 } else if ([currentState isEqualToString:OctagonStateInitiatorJoinAfterCKKSReset]){
1109 return [[OTJoinWithVoucherOperation alloc] initWithDependencies:self.operationDependencies
1110 intendedState:OctagonStateBottlePreloadOctagonKeysInSOS
1111 ckksConflictState:OctagonStateBecomeUntrusted
1112 errorState:OctagonStateBecomeUntrusted];
1114 } else if([currentState isEqualToString:OctagonStateResetBecomeUntrusted]) {
1115 return [self becomeUntrustedOperation:OctagonStateResetAndEstablish];
1117 } else if([currentState isEqualToString:OctagonStateResetAndEstablish]) {
1118 return [[OTResetOperation alloc] init:self.containerName
1119 contextID:self.contextID
1121 intendedState:OctagonStateResetAnyMissingTLKCKKSViews
1122 errorState:OctagonStateError
1123 cuttlefishXPCWrapper:self.cuttlefishXPCWrapper];
1125 } else if([currentState isEqualToString:OctagonStateResetAnyMissingTLKCKKSViews]) {
1126 return [[OTResetCKKSZonesLackingTLKsOperation alloc] initWithDependencies:self.operationDependencies
1127 intendedState:OctagonStateEstablishEnableCDPBit
1128 errorState:OctagonStateError];
1130 } else if([currentState isEqualToString:OctagonStateEstablishEnableCDPBit]) {
1131 return [[OTSetCDPBitOperation alloc] initWithDependencies:self.operationDependencies
1132 intendedState:OctagonStateReEnactDeviceList
1133 errorState:OctagonStateError];
1135 } else if([currentState isEqualToString:OctagonStateReEnactDeviceList]) {
1136 return [[OTUpdateTrustedDeviceListOperation alloc] initWithDependencies:self.operationDependencies
1137 intendedState:OctagonStateReEnactPrepare
1138 listUpdatesState:OctagonStateReEnactPrepare
1139 errorState:OctagonStateBecomeUntrusted
1142 } else if([currentState isEqualToString:OctagonStateReEnactPrepare]) {
1143 // <rdar://problem/56270219> Octagon: use epoch transmitted across pairing channel
1144 // Note: Resetting the account returns epoch to 0.
1145 return [[OTPrepareOperation alloc] initWithDependencies:self.operationDependencies
1146 intendedState:OctagonStateReEnactReadyToEstablish
1147 errorState:OctagonStateError
1148 deviceInfo:[self prepareInformation]
1149 policyOverride:self.policyOverride
1152 } else if([currentState isEqualToString:OctagonStateReEnactReadyToEstablish]) {
1153 return [[OTEstablishOperation alloc] initWithDependencies:self.operationDependencies
1154 intendedState:OctagonStateEscrowTriggerUpdate
1155 ckksConflictState:OctagonStateEstablishCKKSReset
1156 errorState:OctagonStateBecomeUntrusted];
1158 } else if([currentState isEqualToString:OctagonStateEstablishCKKSReset]) {
1159 return [[OTLocalCKKSResetOperation alloc] initWithDependencies:self.operationDependencies
1160 intendedState:OctagonStateEstablishAfterCKKSReset
1161 errorState:OctagonStateBecomeUntrusted];
1163 } else if([currentState isEqualToString:OctagonStateEstablishAfterCKKSReset]) {
1164 // If CKKS fails again, just go to "become untrusted"
1165 return [[OTEstablishOperation alloc] initWithDependencies:self.operationDependencies
1166 intendedState:OctagonStateEscrowTriggerUpdate
1167 ckksConflictState:OctagonStateBecomeUntrusted
1168 errorState:OctagonStateBecomeUntrusted];
1170 } else if ([currentState isEqualToString:OctagonStateEscrowTriggerUpdate]){
1171 return [[OTTriggerEscrowUpdateOperation alloc] initWithDependencies:self.operationDependencies
1172 intendedState:OctagonStateBecomeReady
1173 errorState:OctagonStateError];
1175 } else if ([currentState isEqualToString:OctagonStateHealthCheckLeaveClique]) {
1176 return [[OTLeaveCliqueOperation alloc] initWithDependencies: self.operationDependencies
1177 intendedState: OctagonStateBecomeUntrusted
1178 errorState: OctagonStateBecomeUntrusted];
1180 } else if([currentState isEqualToString:OctagonStateWaitForClassCUnlock]) {
1181 if([flags _onqueueContains:OctagonFlagUnlocked]) {
1182 [flags _onqueueRemoveFlag:OctagonFlagUnlocked];
1183 return [OctagonStateTransitionOperation named:[NSString stringWithFormat:@"%@", @"initializing-after-initial-unlock"]
1184 entering:OctagonStateInitializing];
1187 [pendingFlagHandler _onqueueHandlePendingFlag:[[OctagonPendingFlag alloc] initWithFlag:OctagonFlagUnlocked
1188 conditions:OctagonPendingConditionsDeviceUnlocked]];
1191 } else if([currentState isEqualToString: OctagonStateWaitForUnlock]) {
1192 if([flags _onqueueContains:OctagonFlagUnlocked]) {
1193 [flags _onqueueRemoveFlag:OctagonFlagUnlocked];
1194 return [OctagonStateTransitionOperation named:[NSString stringWithFormat:@"%@", @"initializing-after-unlock"]
1195 entering:OctagonStateInitializing];
1198 secnotice("octagon", "Requested to enter wait for unlock");
1199 [pendingFlagHandler _onqueueHandlePendingFlag:[[OctagonPendingFlag alloc] initWithFlag:OctagonFlagUnlocked
1200 conditions:OctagonPendingConditionsDeviceUnlocked]];
1203 } else if([currentState isEqualToString: OctagonStateUpdateSOSPreapprovals]) {
1204 secnotice("octagon", "Updating SOS preapprovals");
1206 // TODO: if this update fails, we need to redo it later.
1207 return [[OTSOSUpdatePreapprovalsOperation alloc] initWithDependencies:self.operationDependencies
1208 intendedState:OctagonStateReady
1209 sosNotPresentState:OctagonStateReady
1210 errorState:OctagonStateReady];
1212 } else if([currentState isEqualToString:OctagonStateAssistCKKSTLKUpload]) {
1213 return [[OTUploadNewCKKSTLKsOperation alloc] initWithDependencies:self.operationDependencies
1214 intendedState:OctagonStateReady
1215 ckksConflictState:OctagonStateAssistCKKSTLKUploadCKKSReset
1216 peerMissingState:OctagonStateReadyUpdated
1217 errorState:OctagonStateReady];
1219 } else if([currentState isEqualToString:OctagonStateAssistCKKSTLKUploadCKKSReset]) {
1220 return [[OTLocalCKKSResetOperation alloc] initWithDependencies:self.operationDependencies
1221 intendedState:OctagonStateAssistCKKSTLKUploadAfterCKKSReset
1222 errorState:OctagonStateBecomeReady];
1224 } else if([currentState isEqualToString:OctagonStateAssistCKKSTLKUploadAfterCKKSReset]) {
1225 // If CKKS fails again, just go to 'ready'
1226 return [[OTUploadNewCKKSTLKsOperation alloc] initWithDependencies:self.operationDependencies
1227 intendedState:OctagonStateReady
1228 ckksConflictState:OctagonStateReady
1229 peerMissingState:OctagonStateReadyUpdated
1230 errorState:OctagonStateReady];
1232 } else if([currentState isEqualToString:OctagonStateReady]) {
1233 if([flags _onqueueContains:OctagonFlagCuttlefishNotification]) {
1234 [flags _onqueueRemoveFlag:OctagonFlagCuttlefishNotification];
1235 secnotice("octagon", "Updating TPH (while ready) due to push");
1236 return [OctagonStateTransitionOperation named:@"octagon-update"
1237 entering:OctagonStateReadyUpdated];
1240 if([flags _onqueueContains:OctagonFlagCKKSRequestsTLKUpload]) {
1241 [flags _onqueueRemoveFlag:OctagonFlagCKKSRequestsTLKUpload];
1242 return [OctagonStateTransitionOperation named:@"ckks-assist"
1243 entering:OctagonStateAssistCKKSTLKUpload];
1246 if([flags _onqueueContains:OctagonFlagAttemptBottleTLKExtraction]) {
1247 [flags _onqueueRemoveFlag:OctagonFlagAttemptBottleTLKExtraction];
1248 if(_bottleID && _entropy && _bottleSalt) {
1249 // Reuse the vouch-with-bottle operation. That'll make us a new voucher, but as a side effect, it will extract TLKs.
1250 return [[OTVouchWithBottleOperation alloc] initWithDependencies:self.operationDependencies
1251 intendedState:OctagonStateBecomeReady
1252 errorState:OctagonStateBecomeReady
1255 bottleSalt:_bottleSalt
1258 secnotice("octagon", "Received an suggestion to retry TLK extraction via bottle, but have no entropy stashed");
1262 if([flags _onqueueContains:OctagonFlagAttemptRecoveryKeyTLKExtraction]) {
1263 [flags _onqueueRemoveFlag:OctagonFlagAttemptRecoveryKeyTLKExtraction];
1265 // Reuse the vouch-with-rk operation. That'll make us a new voucher, but as a side effect, it will extract TLKs.
1266 return [[OTVouchWithRecoveryKeyOperation alloc] initWithDependencies:self.operationDependencies
1267 intendedState:OctagonStateBecomeReady
1268 errorState:OctagonStateBecomeReady
1269 recoveryKey:_recoveryKey
1272 secnotice("octagon", "Received an suggestion to retry TLK extraction via RK, but have no recovery key stashed");
1276 if([flags _onqueueContains:OctagonFlagFetchAuthKitMachineIDList]) {
1277 [flags _onqueueRemoveFlag:OctagonFlagFetchAuthKitMachineIDList];
1279 secnotice("octagon", "Received an suggestion to update the machine ID list (while ready); updating trusted device list");
1281 // If the cached list changes due to this fetch, go into 'updated'. Otherwise, back into ready with you!
1282 return [[OTUpdateTrustedDeviceListOperation alloc] initWithDependencies:self.operationDependencies
1283 intendedState:OctagonStateReady
1284 listUpdatesState:OctagonStateReadyUpdated
1285 errorState:OctagonStateReady
1286 retryFlag:OctagonFlagFetchAuthKitMachineIDList];
1289 if([flags _onqueueContains:OctagonFlagAttemptSOSUpdatePreapprovals]) {
1290 [flags _onqueueRemoveFlag:OctagonFlagAttemptSOSUpdatePreapprovals];
1291 if(self.sosAdapter.sosEnabled) {
1292 secnotice("octagon", "Attempt SOS Update preapprovals again!");
1293 return [OctagonStateTransitionOperation named:@"attempt-sos-update-preapproval"
1294 entering:OctagonStateUpdateSOSPreapprovals];
1296 secnotice("octagon", "We are untrusted, but this platform doesn't support SOS.");
1300 if([flags _onqueueContains:OctagonFlagAttemptSOSConsistency]) {
1301 [flags _onqueueRemoveFlag:OctagonFlagAttemptSOSConsistency];
1302 if(self.sosAdapter.sosEnabled) {
1303 secnotice("octagon", "Attempting SOS consistency checks");
1304 return [OctagonStateTransitionOperation named:@"attempt-sos-update-preapproval"
1305 entering:OctagonStateEnsureConsistency];
1307 secnotice("octagon", "Someone would like us to check SOS consistency, but this platform doesn't support SOS.");
1311 if([flags _onqueueContains:OctagonFlagAttemptUserControllableViewStatusUpgrade]) {
1312 [flags _onqueueRemoveFlag:OctagonFlagAttemptUserControllableViewStatusUpgrade];
1313 secnotice("octagon", "Attempting user-view control upgrade");
1314 return [OctagonStateTransitionOperation named:@"attempt-user-view-upgrade"
1315 entering:OctagonStateSetUserControllableViewsToPeerConsensus];
1318 if([flags _onqueueContains:OctagonFlagCKKSRequestsPolicyCheck]) {
1319 [flags _onqueueRemoveFlag:OctagonFlagCKKSRequestsPolicyCheck];
1320 secnotice("octagon", "Updating CKKS policy");
1321 return [OctagonStateTransitionOperation named:@"ckks-policy-update"
1322 entering:OctagonStateReadyUpdated];
1325 if([flags _onqueueContains:OctagonFlagCKKSViewSetChanged]) {
1326 // We want to tell CKKS that we're trusted again.
1327 [flags _onqueueRemoveFlag:OctagonFlagCKKSViewSetChanged];
1328 return [OctagonStateTransitionOperation named:@"ckks-update-trust"
1329 entering:OctagonStateBecomeReady];
1332 if([flags _onqueueContains:OctagonFlagAccountIsAvailable]) {
1333 // We're in ready--we already know the account is available
1334 secnotice("octagon", "Removing 'account is available' flag");
1335 [flags _onqueueRemoveFlag:OctagonFlagAccountIsAvailable];
1338 // We're ready; no need for the IDMS level flag anymore
1339 if([flags _onqueueContains:OctagonFlagIDMSLevelChanged]) {
1340 secnotice("octagon", "Removing 'IDMS level changed' flag");
1341 [flags _onqueueRemoveFlag:OctagonFlagIDMSLevelChanged];
1344 // We're ready; no need for the CDP level flag anymore
1345 if([flags _onqueueContains:OctagonFlagCDPEnabled]) {
1346 secnotice("octagon", "Removing 'CDP enabled' flag");
1347 [flags _onqueueRemoveFlag:OctagonFlagCDPEnabled];
1350 [[CKKSAnalytics logger] setDateProperty:[NSDate date] forKey:OctagonAnalyticsLastKeystateReady];
1351 [self.launchSequence launch];
1353 } else if([currentState isEqualToString:OctagonStateReadyUpdated]) {
1354 return [[OTUpdateTPHOperation alloc] initWithDependencies:self.operationDependencies
1355 intendedState:OctagonStateReady
1356 peerUnknownState:OctagonStateBecomeUntrusted
1357 errorState:OctagonStateError
1358 retryFlag:OctagonFlagCuttlefishNotification];
1362 if ([currentState isEqualToString:OctagonStateError]) {
1369 - (CKKSResultOperation<OctagonStateTransitionOperationProtocol>* _Nullable)initializingOperation
1372 return [OctagonStateTransitionOperation named:@"octagon-initializing"
1373 intending:OctagonStateNoAccount
1374 errorState:OctagonStateError
1375 withBlockTakingSelf:^(OctagonStateTransitionOperation * _Nonnull op) {
1377 NSError* localError = nil;
1378 OTAccountMetadataClassC* account = [self.accountMetadataStore loadOrCreateAccountMetadata:&localError];
1379 if(localError && [self.lockStateTracker isLockedError:localError]){
1380 secnotice("octagon", "Device is locked! pending initialization on unlock");
1381 // Since we can't load a class C item, we go to a different waitforunlock state.
1382 // That way, we'll be less likely for an RPC to break us.
1383 op.nextState = OctagonStateWaitForClassCUnlock;
1387 if(localError || !account) {
1388 secnotice("octagon", "Error loading account data: %@", localError);
1389 op.nextState = OctagonStateNoAccount;
1391 } else if(account.icloudAccountState == OTAccountMetadataClassC_AccountState_ACCOUNT_AVAILABLE) {
1392 secnotice("octagon", "An HSA2 iCloud account exists; waiting for CloudKit to confirm");
1394 // Inform the account state tracker of our HSA2 account
1395 [self.accountStateTracker setHSA2iCloudAccountStatus:CKKSAccountStatusAvailable];
1397 // This seems an odd place to do this, but CKKS currently also tracks the CloudKit account state.
1398 // Since we think we have an HSA2 account, let CKKS figure out its own CloudKit state
1399 secnotice("octagon-ckks", "Initializing CKKS views");
1400 [self.cuttlefishXPCWrapper fetchCurrentPolicyWithContainer:self.containerName
1401 context:self.contextID
1402 modelIDOverride:self.deviceAdapter.modelID
1403 reply:^(TPSyncingPolicy * _Nullable syncingPolicy,
1404 TPPBPeerStableInfo_UserControllableViewStatus userControllableViewStatusOfPeers,
1405 NSError * _Nullable policyError) {
1406 if(!syncingPolicy || policyError) {
1407 secnotice("octagon-ckks", "Unable to fetch initial syncing policy: %@", policyError);
1409 secnotice("octagon-ckks", "Fetched initial syncing policy: %@", syncingPolicy);
1410 [self.viewManager setCurrentSyncingPolicy:syncingPolicy];
1414 [self.viewManager createViews];
1415 [self.viewManager beginCloudKitOperationOfAllViews];
1417 op.nextState = OctagonStateWaitingForCloudKitAccount;
1419 } else if(account.icloudAccountState == OTAccountMetadataClassC_AccountState_NO_ACCOUNT && account.altDSID != nil) {
1420 secnotice("octagon", "An iCloud account exists, but doesn't appear to be HSA2. Let's check!");
1421 op.nextState = OctagonStateDetermineiCloudAccountState;
1423 } else if(account.icloudAccountState == OTAccountMetadataClassC_AccountState_NO_ACCOUNT) {
1424 [self.accountStateTracker setHSA2iCloudAccountStatus:CKKSAccountStatusNoAccount];
1426 secnotice("octagon", "No iCloud account available.");
1427 op.nextState = OctagonStateNoAccount;
1430 secnotice("octagon", "Unknown account state (%@). Determining...", [account icloudAccountStateAsString:account.icloudAccountState]);
1431 op.nextState = OctagonStateDetermineiCloudAccountState;
1436 - (CKKSResultOperation<OctagonStateTransitionOperationProtocol>* _Nullable)evaluateSecdOctagonTrust
1438 return [OctagonStateTransitionOperation named:@"octagon-health-securityd-trust-check"
1439 intending:OctagonStateTPHTrustCheck
1440 errorState:OctagonStatePostRepairCFU
1441 withBlockTakingSelf:^(OctagonStateTransitionOperation * _Nonnull op) {
1442 NSError* localError = nil;
1443 OTAccountMetadataClassC* account = [self.accountMetadataStore loadOrCreateAccountMetadata:&localError];
1444 if(account.peerID && account.trustState == OTAccountMetadataClassC_TrustState_TRUSTED) {
1445 secnotice("octagon-health", "peer is trusted: %@", account.peerID);
1446 op.nextState = OctagonStateTPHTrustCheck;
1449 secnotice("octagon-health", "trust state (%@). checking in with TPH", [account trustStateAsString:account.trustState]);
1450 op.nextState = [self repairAccountIfTrustedByTPHWithIntendedState:OctagonStateTPHTrustCheck];
1455 - (CKKSResultOperation<OctagonStateTransitionOperationProtocol>* _Nullable)evaluateTPHOctagonTrust
1457 return [OctagonStateTransitionOperation named:@"octagon-health-tph-trust-check"
1458 intending:OctagonStateCuttlefishTrustCheck
1459 errorState:OctagonStatePostRepairCFU
1460 withBlockTakingSelf:^(OctagonStateTransitionOperation * _Nonnull op) {
1461 [self checkTrustStatusAndPostRepairCFUIfNecessary:^(CliqueStatus status, BOOL posted, BOOL hasIdentity, BOOL isLocked, NSError *trustFromTPHError) {
1463 [[CKKSAnalytics logger] logResultForEvent:OctagonEventTPHHealthCheckStatus hardFailure:false result:trustFromTPHError];
1464 if(trustFromTPHError) {
1465 secerror("octagon-health: hit an error asking TPH for trust status: %@", trustFromTPHError);
1466 op.error = trustFromTPHError;
1467 op.nextState = OctagonStateError;
1469 if (isLocked == YES) {
1470 op.nextState = OctagonStateWaitForUnlock;
1471 secnotice("octagon-health", "TPH says device is locked!");
1472 } else if (hasIdentity == NO) {
1473 op.nextState = OctagonStateUntrusted;
1474 } else if(hasIdentity == YES && status == CliqueStatusIn){
1475 secnotice("octagon-health", "TPH says we're trusted and in");
1476 op.nextState = OctagonStateCuttlefishTrustCheck;
1477 } else if (hasIdentity == YES && status != CliqueStatusIn){
1478 secnotice("octagon-health", "TPH says we have an identity but we are not in Octagon, posted CFU: %d", !!posted);
1479 op.nextState = OctagonStatePostRepairCFU;
1481 secnotice("octagon-health", "weird shouldn't hit this catch all.. assuming untrusted");
1482 op.nextState = OctagonStateUntrusted;
1489 - (CKKSResultOperation<OctagonStateTransitionOperationProtocol>* _Nullable)cuttlefishTrustEvaluation
1492 OTCheckHealthOperation* op = [[OTCheckHealthOperation alloc] initWithDependencies:self.operationDependencies
1493 intendedState:OctagonStateBecomeReady
1494 errorState:OctagonStateBecomeReady
1495 deviceInfo:self.prepareInformation
1496 skipRateLimitedCheck:_skipRateLimitingCheck];
1498 CKKSResultOperation* callback = [CKKSResultOperation named:@"rpcHealthCheck"
1500 secnotice("octagon-health", "Returning from cuttlefish trust check call: postRepairCFU(%d), postEscrowCFU(%d), resetOctagon(%d), leaveTrust(%d)",
1501 op.postRepairCFU, op.postEscrowCFU, op.resetOctagon, op.leaveTrust);
1502 if(op.postRepairCFU) {
1503 secnotice("octagon-health", "Posting Repair CFU");
1504 NSError* postRepairCFUError = nil;
1505 [self postRepairCFU:&postRepairCFUError];
1506 if(postRepairCFUError) {
1507 op.error = postRepairCFUError;
1510 if(op.postEscrowCFU) {
1511 //hold up, perhaps we already are pending an upload.
1512 NSError* shouldPostError = nil;
1513 BOOL shouldPost = [self shouldPostConfirmPasscodeCFU:&shouldPostError];
1514 if(shouldPostError) {
1515 secerror("octagon-health, hit an error evaluating prerecord status: %@", shouldPostError);
1516 op.error = shouldPostError;
1519 secnotice("octagon-health", "Posting Escrow CFU");
1520 NSError* postEscrowCFUError = nil;
1521 BOOL ret = [self postConfirmPasscodeCFU:&postEscrowCFUError];
1523 op.error = postEscrowCFUError;
1526 secnotice("octagon-health", "Not posting confirm passcode CFU, already pending a prerecord upload");
1531 secnotice("octagon-health", "Leaving Octagon and SOS trust");
1532 NSError* leaveError = nil;
1533 if(![self leaveTrust:&leaveError]) {
1534 op.error = leaveError;
1538 [callback addDependency:op];
1539 [self.operationQueue addOperation: callback];
1543 - (CKKSResultOperation<OctagonStateTransitionOperationProtocol>* _Nullable)postRepairCFUAndBecomeUntrusted
1545 return [OctagonStateTransitionOperation named:@"octagon-health-post-repair-cfu"
1546 intending:OctagonStateUntrusted
1547 errorState:OctagonStateError
1548 withBlockTakingSelf:^(OctagonStateTransitionOperation * _Nonnull op) {
1549 __block BOOL deviceIsLocked = NO;
1550 [self checkTrustStatusAndPostRepairCFUIfNecessary:^(CliqueStatus status,
1554 NSError * _Nullable postError) {
1556 secerror("ocagon-health: failed to post repair cfu via state machine: %@", postError);
1557 } else if (isLocked) {
1558 deviceIsLocked = isLocked;
1559 secnotice("octagon-health", "device is locked, not posting cfu");
1561 secnotice("octagon-health", "posted repair cfu via state machine");
1564 if (deviceIsLocked == YES) {
1565 op.nextState = OctagonStateWaitForUnlock;
1567 op.nextState = OctagonStateUntrusted;
1572 - (CKKSResultOperation<OctagonStateTransitionOperationProtocol>* _Nullable)cloudKitAccountNewlyAvailableOperation:(OctagonFlag*)warmEscrowCache
1576 return [OctagonStateTransitionOperation named:@"octagon-icloud-account-available"
1577 intending:OctagonStateDetermineCDPState
1578 errorState:OctagonStateError
1579 withBlockTakingSelf:^(OctagonStateTransitionOperation * _Nonnull op) {
1581 secnotice("octagon", "iCloud sign in occurred. Attemping to register with APS...");
1582 // Check if escrow fetch flag is set to warm up the escrow cache
1583 if(warmEscrowCache != nil && OctagonIsOptimizationEnabled()) {
1584 secnotice("octagon-warm-escrowcache", "Beginning fetching escrow records to warm up the escrow cache in TPH");
1585 dispatch_async(dispatch_get_global_queue(QOS_CLASS_UTILITY, 0), ^{
1587 [self rpcFetchAllViableEscrowRecords:NO reply:^(NSArray<NSData *> *records, NSError *error) {
1589 secerror("octagon-warm-escrowcache: failed to fetch escrow records, %@", error);
1591 secnotice("octagon-warm-escrowcache", "Successfully fetched escrow records");
1596 // Register with APS, but don't bother to wait until it's complete.
1597 CKContainer* ckContainer = [CKContainer containerWithIdentifier:self.containerName];
1598 [ckContainer serverPreferredPushEnvironmentWithCompletionHandler: ^(NSString *apsPushEnvString, NSError *error) {
1602 secerror("octagonpush: received callback for released object");
1606 if(error || (apsPushEnvString == nil)) {
1607 secerror("octagonpush: Received error fetching preferred push environment (%@): %@", apsPushEnvString, error);
1609 secnotice("octagonpush", "Registering for environment '%@'", apsPushEnvString);
1611 [self.apsReceiver registerForEnvironment:apsPushEnvString];
1615 op.nextState = op.intendedState;
1619 - (OctagonState*) repairAccountIfTrustedByTPHWithIntendedState:(OctagonState*)intendedState
1621 __block OctagonState* nextState = intendedState;
1623 //let's check in with TPH real quick to make sure it agrees with our local assessment
1624 secnotice("octagon-health", "repairAccountIfTrustedByTPHWithIntendedState: calling into TPH for trust status");
1626 OTOperationConfiguration *config = [[OTOperationConfiguration alloc]init];
1628 [self rpcTrustStatus:config reply:^(CliqueStatus status,
1629 NSString* egoPeerID,
1630 NSDictionary<NSString*, NSNumber*>* _Nullable peerCountByModelID,
1633 NSError * _Nullable error) {
1634 BOOL hasIdentity = egoPeerID != nil;
1635 secnotice("octagon-health", "repairAccountIfTrustedByTPHWithIntendedState status: %ld, peerID: %@, isExcluded: %d error: %@", (long)status, egoPeerID, isExcluded, error);
1638 secnotice("octagon-health", "got an error from tph, returning to become_ready state: %@", error);
1639 nextState = OctagonStateBecomeReady;
1644 secnotice("octagon-health", "device is locked");
1645 nextState = OctagonStateWaitForUnlock;
1649 if(OctagonAuthoritativeTrustIsEnabled() && hasIdentity && status == CliqueStatusIn) {
1650 secnotice("octagon-health", "TPH believes we're trusted, accepting ego peerID as %@", egoPeerID);
1652 NSError* persistError = nil;
1653 BOOL persisted = [self.accountMetadataStore persistAccountChanges:^OTAccountMetadataClassC * _Nonnull(OTAccountMetadataClassC * _Nonnull metadata) {
1654 metadata.trustState = OTAccountMetadataClassC_TrustState_TRUSTED;
1655 metadata.peerID = egoPeerID;
1657 } error:&persistError];
1658 if(!persisted || persistError) {
1659 secerror("octagon-health: couldn't persist results: %@", persistError);
1660 nextState = OctagonStateError;
1662 secnotice("octagon-health", "added trusted identity to account metadata");
1663 nextState = intendedState;
1666 } else if (OctagonAuthoritativeTrustIsEnabled() && hasIdentity && status != CliqueStatusIn){
1667 secnotice("octagon-health", "TPH believes we're not trusted, requesting CFU post");
1668 nextState = OctagonStatePostRepairCFU;
1675 - (void)checkTrustStatusAndPostRepairCFUIfNecessary:(void (^ _Nullable)(CliqueStatus status, BOOL posted, BOOL hasIdentity, BOOL isLocked, NSError * _Nullable error))reply
1678 OTOperationConfiguration *configuration = [[OTOperationConfiguration alloc] init];
1679 [self rpcTrustStatus:configuration reply:^(CliqueStatus status,
1680 NSString* _Nullable egoPeerID,
1681 NSDictionary<NSString*, NSNumber*>* _Nullable peerCountByModelID,
1684 NSError * _Nullable error) {
1687 secnotice("octagon", "clique status: %@, egoPeerID: %@, peerCountByModelID: %@, isExcluded: %d error: %@", OTCliqueStatusToString(status), egoPeerID, peerCountByModelID, isExcluded, error);
1689 BOOL hasIdentity = egoPeerID != nil;
1690 if (error && error.code != errSecInteractionNotAllowed) {
1691 reply(status, NO, hasIdentity, isLocked, error);
1696 secnotice("octagon", "device is locked; not posting CFU");
1697 reply(status, NO, hasIdentity, isLocked, error);
1702 // Are there any iphones or iPads? about? Only iOS devices can repair apple TVs.
1703 bool phonePeerPresent = false;
1704 for(NSString* modelID in peerCountByModelID.allKeys) {
1705 bool iPhone = [modelID hasPrefix:@"iPhone"];
1706 bool iPad = [modelID hasPrefix:@"iPad"];
1707 if(!iPhone && !iPad) {
1711 int count = [peerCountByModelID[modelID] intValue];
1713 secnotice("octagon", "Have %d peers with model %@", count, modelID);
1714 phonePeerPresent = true;
1718 if(!phonePeerPresent) {
1719 secnotice("octagon", "No iOS peers in account; not posting CFU");
1720 reply(status, NO, hasIdentity, isLocked, nil);
1725 // On platforms with SOS, we only want to post a CFU if we've attempted to join at least once.
1726 // This prevents us from posting a CFU, then performing an SOS upgrade and succeeding.
1727 if(self.sosAdapter.sosEnabled) {
1728 NSError* fetchError = nil;
1729 OTAccountMetadataClassC* accountState = [self.accountMetadataStore loadOrCreateAccountMetadata:&fetchError];
1731 if(!accountState || fetchError){
1732 secerror("octagon: failed to retrieve joining attempt information: %@", fetchError);
1733 // fall through to below: posting the CFU is better than a false negative
1735 } else if(accountState.attemptedJoin == OTAccountMetadataClassC_AttemptedAJoinState_ATTEMPTED) {
1736 // Normal flow, fall through to below
1738 // Triple-check with SOS: if it's in a bad state, post the CFU anyway
1739 secnotice("octagon", "SOS is enabled and we haven't attempted to join; checking with SOS");
1741 NSError* circleError = nil;
1742 SOSCCStatus sosStatus = [self.sosAdapter circleStatus:&circleError];
1744 if(circleError && [circleError.domain isEqualToString:(__bridge NSString*)kSOSErrorDomain] && circleError.code == kSOSErrorNotReady) {
1745 secnotice("octagon", "SOS is not ready, not posting CFU until it becomes so");
1746 reply(status, NO, hasIdentity, isLocked, nil);
1749 } else if(circleError) {
1750 // Any other error probably indicates that there is some circle, but we're not in it
1751 secnotice("octagon", "SOS is in an unknown error state, posting CFU: %@", circleError);
1753 } else if(sosStatus == kSOSCCInCircle) {
1754 secnotice("octagon", "SOS is InCircle, not posting CFU");
1755 reply(status, NO, hasIdentity, isLocked, nil);
1758 secnotice("octagon", "SOS is %@, posting CFU", (__bridge NSString*)SOSCCGetStatusDescription(sosStatus));
1763 if(OctagonAuthoritativeTrustIsEnabled() && (status == CliqueStatusNotIn || status == CliqueStatusAbsent || isExcluded)) {
1764 NSError* localError = nil;
1765 BOOL posted = [self postRepairCFU:&localError];
1766 reply(status, posted, hasIdentity, isLocked, localError);
1769 reply(status, NO, hasIdentity, isLocked, nil);
1775 - (CKKSResultOperation<OctagonStateTransitionOperationProtocol>* _Nullable)startCompanionPairingOperation
1778 return [OctagonStateTransitionOperation named:@"start-companion-pairing"
1779 intending:OctagonStateBecomeUntrusted
1780 errorState:OctagonStateError
1781 withBlockTakingSelf:^(OctagonStateTransitionOperation * _Nonnull op) {
1783 OTPairingInitiateWithCompletion(self.queue, ^(bool success, NSError *error) {
1785 secnotice("octagon", "companion pairing succeeded");
1788 error = [NSError errorWithDomain:(__bridge NSString *)kSecErrorDomain code:errSecInternalError userInfo:nil];
1790 secnotice("octagon", "companion pairing failed: %@", error);
1792 [[CKKSAnalytics logger] logResultForEvent:OctagonEventCompanionPairing hardFailure:false result:error];
1794 op.nextState = op.intendedState;
1797 #endif /* TARGET_OS_WATCH */
1799 - (CKKSResultOperation<OctagonStateTransitionOperationProtocol>* _Nullable)becomeUntrustedOperation:(OctagonState*)intendedState
1802 return [OctagonStateTransitionOperation named:@"octagon-become-untrusted"
1803 intending:intendedState
1804 errorState:OctagonStateError
1805 withBlockTakingSelf:^(OctagonStateTransitionOperation * _Nonnull op) {
1807 NSError* localError = nil;
1809 // During testing, don't kick this off until it's needed
1810 if([self.contextID isEqualToString:OTDefaultContext]) {
1811 [self.accountStateTracker triggerOctagonStatusFetch];
1814 __block BOOL deviceIsLocked = NO;
1815 [self checkTrustStatusAndPostRepairCFUIfNecessary:^(CliqueStatus status, BOOL posted, BOOL hasIdentity, BOOL isLocked, NSError * _Nullable postError) {
1817 [[CKKSAnalytics logger] logResultForEvent:OctagonEventCheckTrustForCFU hardFailure:false result:postError];
1819 secerror("octagon: cfu failed to post");
1820 } else if (isLocked == YES) {
1821 deviceIsLocked = isLocked;
1822 secerror("octagon: device is locked, not posting cfu");
1824 secnotice("octagon", "clique status: %@, posted cfu: %d", OTCliqueStatusToString(status), !!posted);
1828 if (deviceIsLocked == YES) {
1829 secnotice("octagon", "device is locked, state moving to wait for unlock");
1830 op.nextState = OctagonStateWaitForUnlock;
1834 [self.accountMetadataStore persistAccountChanges:^OTAccountMetadataClassC * _Nonnull(OTAccountMetadataClassC * _Nonnull metadata) {
1835 metadata.trustState = OTAccountMetadataClassC_TrustState_UNTRUSTED;
1837 } error:&localError];
1840 secnotice("octagon", "Unable to set trust state: %@", localError);
1841 op.nextState = OctagonStateError;
1843 op.nextState = op.intendedState;
1846 for (id key in self.viewManager.views) {
1847 CKKSKeychainView* view = self.viewManager.views[key];
1848 secnotice("octagon-ckks", "Informing %@ of new untrusted status", view);
1849 [view endTrustedOperation];
1852 // We are no longer in a CKKS4All world. Tell SOS!
1853 if(self.sosAdapter.sosEnabled) {
1854 NSError* soserror = nil;
1855 [self.sosAdapter updateCKKS4AllStatus:NO error:&soserror];
1857 secnotice("octagon-ckks", "Unable to disable the CKKS4All status in SOS: %@", soserror);
1862 * Initial notification that we let the world know that trust is up and doing something
1864 if (!self.initialBecomeUntrustedPosted) {
1865 [self notifyTrustChanged:OTAccountMetadataClassC_TrustState_UNTRUSTED];
1866 self.initialBecomeUntrustedPosted = YES;
1869 self.octagonAdapter = nil;
1873 - (CKKSResultOperation<OctagonStateTransitionOperationProtocol>* _Nullable)becomeReadyOperation
1876 return [OctagonStateTransitionOperation named:@"octagon-ready"
1877 intending:OctagonStateReady
1878 errorState:OctagonStateError
1879 withBlockTakingSelf:^(OctagonStateTransitionOperation * _Nonnull op) {
1882 if([self.contextID isEqualToString:OTDefaultContext]) {
1883 [self.accountStateTracker triggerOctagonStatusFetch];
1886 // Note: we don't modify the account metadata trust state; that will have been done
1887 // by a join or upgrade operation, possibly long ago
1889 // but, we do set the 'attempted join' bit, just in case the device joined before we started setting this bit
1891 // Also, ensure that the CKKS policy is correctly present and set in the view manager
1892 __block NSString* peerID = nil;
1893 NSError* localError = nil;
1895 __block TPSyncingPolicy* policy = nil;
1897 [self.accountMetadataStore persistAccountChanges:^OTAccountMetadataClassC * _Nullable(OTAccountMetadataClassC * _Nonnull metadata) {
1898 peerID = metadata.peerID;
1900 policy = metadata.hasSyncingPolicy ? [metadata getTPSyncingPolicy] : nil;
1902 if(metadata.attemptedJoin == OTAccountMetadataClassC_AttemptedAJoinState_ATTEMPTED) {
1905 metadata.attemptedJoin = OTAccountMetadataClassC_AttemptedAJoinState_ATTEMPTED;
1907 } error:&localError];
1909 if(!peerID || localError) {
1910 secerror("octagon-ckks: No peer ID to pass to CKKS. Syncing will be disabled.");
1911 } else if(!policy) {
1912 secerror("octagon-ckks: No memoized CKKS policy, re-fetching");
1913 op.nextState = OctagonStateRefetchCKKSPolicy;
1917 if(policy.syncUserControllableViews == TPPBPeerStableInfo_UserControllableViewStatus_UNKNOWN) {
1918 secnotice("octagon-ckks", "Memoized CKKS policy has no opinion of user-controllable view status");
1919 // Suggest the update, whenever possible
1920 OctagonPendingFlag* pendingFlag = [[OctagonPendingFlag alloc] initWithFlag:OctagonFlagAttemptUserControllableViewStatusUpgrade
1921 conditions:OctagonPendingConditionsDeviceUnlocked];
1922 [self.stateMachine handlePendingFlag:pendingFlag];
1924 // We are now in a CKKS4All world. Tell SOS!
1925 if(self.sosAdapter.sosEnabled) {
1926 NSError* soserror = nil;
1927 [self.sosAdapter updateCKKS4AllStatus:YES error:&soserror];
1929 secnotice("octagon-ckks", "Unable to enable the CKKS4All status in SOS: %@", soserror);
1934 secnotice("octagon-ckks", "Initializing CKKS views with policy %@: %@", policy, policy.viewList);
1936 [self.viewManager setCurrentSyncingPolicy:policy];
1938 OctagonCKKSPeerAdapter* octagonAdapter = [[OctagonCKKSPeerAdapter alloc] initWithPeerID:peerID
1939 containerName:self.containerName
1940 contextID:self.contextID
1941 cuttlefishXPC:self.cuttlefishXPCWrapper];
1943 // This octagon adapter must be able to load the self peer keys, or we're in trouble.
1944 NSError* egoPeerKeysError = nil;
1945 CKKSSelves* selves = [octagonAdapter fetchSelfPeers:&egoPeerKeysError];
1946 if(!selves || egoPeerKeysError) {
1947 secerror("octagon-ckks: Unable to fetch self peers for %@: %@", octagonAdapter, egoPeerKeysError);
1949 if([self.lockStateTracker isLockedError:egoPeerKeysError]) {
1950 secnotice("octagon-ckks", "Waiting for device unlock to proceed");
1951 op.nextState = OctagonStateWaitForUnlock;
1953 secnotice("octagon-ckks", "Error is scary; becoming untrusted");
1954 op.nextState = OctagonStateBecomeUntrusted;
1959 // stash a reference to the adapter so we can provide updates later
1960 self.octagonAdapter = octagonAdapter;
1962 // Start all our CKKS views!
1963 for (id key in self.viewManager.views) {
1964 CKKSKeychainView* view = self.viewManager.views[key];
1965 secnotice("octagon-ckks", "Informing CKKS view '%@' of trusted operation with self peer %@", view.zoneName, peerID);
1967 NSArray<id<CKKSPeerProvider>>* peerProviders = nil;
1969 if(self.sosAdapter.sosEnabled) {
1970 peerProviders = @[self.octagonAdapter, self.sosAdapter];
1973 peerProviders = @[self.octagonAdapter];
1976 [view beginTrustedOperation:peerProviders
1977 suggestTLKUpload:self.suggestTLKUploadNotifier
1978 requestPolicyCheck:self.requestPolicyCheckNotifier];
1981 [self notifyTrustChanged:OTAccountMetadataClassC_TrustState_TRUSTED];
1983 op.nextState = op.intendedState;
1987 #pragma mark --- Utilities to run at times
1989 - (NSString * _Nullable)extractStringKey:(NSString * _Nonnull)key fromDictionary:(NSDictionary * _Nonnull)d
1991 NSString *value = d[key];
1992 if ([value isKindOfClass:[NSString class]]) {
1998 - (void)handleHealthRequest
2000 NSString *trustState = OTAccountMetadataClassC_TrustStateAsString(self.currentMemoizedTrustState);
2001 OctagonState* currentState = [self.stateMachine waitForState:OctagonStateReady wait:3*NSEC_PER_SEC];
2003 [self.cuttlefishXPCWrapper reportHealthWithContainer:self.containerName context:self.contextID stateMachineState:currentState trustState:trustState reply:^(NSError * _Nullable error) {
2005 secerror("octagon: health report is lost: %@", error);
2010 - (void)handleTTRRequest:(NSDictionary *)cfDictionary
2012 NSString *serialNumber = [self extractStringKey:@"s" fromDictionary:cfDictionary];
2013 NSString *ckDeviceId = [self extractStringKey:@"D" fromDictionary:cfDictionary];
2014 NSString *alert = [self extractStringKey:@"a" fromDictionary:cfDictionary];
2015 NSString *description = [self extractStringKey:@"d" fromDictionary:cfDictionary];
2016 NSString *radar = [self extractStringKey:@"R" fromDictionary:cfDictionary];
2017 NSString *componentName = [self extractStringKey:@"n" fromDictionary:cfDictionary];
2018 NSString *componentVersion = [self extractStringKey:@"v" fromDictionary:cfDictionary];
2019 NSString *componentID = [self extractStringKey:@"I" fromDictionary:cfDictionary];
2022 if (![self.deviceAdapter.serialNumber isEqualToString:serialNumber]) {
2023 secnotice("octagon", "TTR request not for me (sn)");
2028 NSString *selfDeviceID = self.viewManager.accountTracker.ckdeviceID;
2029 if (serialNumber == nil || ![selfDeviceID isEqualToString:serialNumber]) {
2030 secnotice("octagon", "TTR request not for me (deviceId)");
2035 if (alert == NULL || description == NULL || radar == NULL) {
2036 secerror("octagon: invalid type of TTR requeat: %@", cfDictionary);
2040 SecTapToRadar *ttr = [[SecTapToRadar alloc] initTapToRadar:alert
2041 description:description
2043 if (componentName && componentVersion && componentID) {
2044 ttr.componentName = componentName;
2045 ttr.componentVersion = componentVersion;
2046 ttr.componentID = componentID;
2051 // We can't make a APSIncomingMessage in the tests (no public constructor),
2052 // but we don't really care about anything in it but the userInfo dictionary anyway
2053 - (void)notifyContainerChange:(APSIncomingMessage* _Nullable)notification
2055 [self notifyContainerChangeWithUserInfo:notification.userInfo];
2058 - (void)notifyContainerChangeWithUserInfo:(NSDictionary*)userInfo
2060 secnotice("octagonpush", "received a cuttlefish push notification (%@): %@",
2061 self.containerName, userInfo);
2063 NSDictionary *cfDictionary = userInfo[@"cf"];
2064 if ([cfDictionary isKindOfClass:[NSDictionary class]]) {
2065 NSString *command = [self extractStringKey:@"k" fromDictionary:cfDictionary];
2067 if ([command isEqualToString:@"h"]) {
2068 [self handleHealthRequest];
2069 } else if ([command isEqualToString:@"r"]) {
2070 [self handleTTRRequest:cfDictionary];
2072 secerror("octagon: unknown command: %@", command);
2078 if (self.apsRateLimiter == nil) {
2079 secnotice("octagon", "creating aps rate limiter");
2080 // If we're testing, for the initial delay, use 0.2 second. Otherwise, 2s.
2081 dispatch_time_t initialDelay = (SecCKKSReduceRateLimiting() ? 200 * NSEC_PER_MSEC : 2 * NSEC_PER_SEC);
2083 // If we're testing, for the initial delay, use 2 second. Otherwise, 30s.
2084 dispatch_time_t continuingDelay = (SecCKKSReduceRateLimiting() ? 2 * NSEC_PER_SEC : 30 * NSEC_PER_SEC);
2087 self.apsRateLimiter = [[CKKSNearFutureScheduler alloc] initWithName:@"aps-push-ratelimiter"
2088 initialDelay:initialDelay
2089 continuingDelay:continuingDelay
2090 keepProcessAlive:YES
2091 dependencyDescriptionCode:CKKSResultDescriptionNone
2097 secnotice("octagon-push-ratelimited", "notifying container of change for context: %@", self.contextID);
2098 OctagonPendingFlag *pendingFlag = [[OctagonPendingFlag alloc] initWithFlag:OctagonFlagCuttlefishNotification
2099 conditions:OctagonPendingConditionsDeviceUnlocked];
2101 [self.stateMachine handlePendingFlag:pendingFlag];
2105 [self.apsRateLimiter trigger];
2108 - (BOOL)waitForReady:(int64_t)timeOffset
2110 OctagonState* currentState = [self.stateMachine waitForState:OctagonStateReady wait:timeOffset];
2111 return [currentState isEqualToString:OctagonStateReady];
2115 - (OTAccountMetadataClassC_TrustState)currentMemoizedTrustState
2117 NSError* localError = nil;
2118 OTAccountMetadataClassC* accountMetadata = [self.accountMetadataStore loadOrCreateAccountMetadata:&localError];
2120 if(!accountMetadata) {
2121 secnotice("octagon", "Unable to fetch account metadata: %@", localError);
2122 return OTAccountMetadataClassC_TrustState_UNKNOWN;
2125 return accountMetadata.trustState;
2128 - (OTAccountMetadataClassC_AccountState)currentMemoizedAccountState
2130 NSError* localError = nil;
2131 OTAccountMetadataClassC* accountMetadata = [self.accountMetadataStore loadOrCreateAccountMetadata:&localError];
2133 if(!accountMetadata) {
2134 secnotice("octagon", "Unable to fetch account metadata: %@", localError);
2135 return OTAccountMetadataClassC_AccountState_UNKNOWN;
2138 return accountMetadata.icloudAccountState;
2141 - (NSDate* _Nullable) currentMemoizedLastHealthCheck
2143 NSError* localError = nil;
2144 OTAccountMetadataClassC* accountMetadata = [self.accountMetadataStore loadOrCreateAccountMetadata:&localError];
2146 if(!accountMetadata) {
2147 secnotice("octagon", "Unable to fetch account metadata: %@", localError);
2150 if(accountMetadata.lastHealthCheckup == 0) {
2153 return [[NSDate alloc] initWithTimeIntervalSince1970: ((NSTimeInterval)accountMetadata.lastHealthCheckup) / 1000.0];
2156 - (void)requestTrustedDeviceListRefresh
2158 [self.stateMachine handleFlag:OctagonFlagFetchAuthKitMachineIDList];
2161 #pragma mark --- Device Info update handling
2163 - (void)deviceNameUpdated {
2164 secnotice("octagon-devicename", "device name updated: %@", self.contextID);
2165 OctagonPendingFlag *pendingFlag = [[OctagonPendingFlag alloc] initWithFlag:OctagonFlagCuttlefishNotification
2166 conditions:OctagonPendingConditionsDeviceUnlocked];
2167 [self.stateMachine handlePendingFlag:pendingFlag];
2170 #pragma mark --- SOS update handling
2173 - (void)selfPeerChanged:(id<CKKSPeerProvider>)provider
2175 // Currently, we register for peer changes with just our SOS peer adapter, so the only reason this is called is to receive SOS updates
2176 // Ignore SOS self peer updates for now.
2179 - (void)trustedPeerSetChanged:(id<CKKSPeerProvider>)provider
2181 // Currently, we register for peer changes with just our SOS peer adapter, so the only reason this is called is to receive SOS updates
2182 secnotice("octagon-sos", "Received an update of an SOS trust set change");
2184 if(!self.sosAdapter.sosEnabled) {
2185 secnotice("octagon-sos", "This platform doesn't support SOS. This is probably a bug?");
2188 if (self.sosConsistencyRateLimiter == nil) {
2189 secnotice("octagon", "creating SOS consistency rate limiter");
2190 dispatch_time_t initialDelay = (SecCKKSReduceRateLimiting() ? 200 * NSEC_PER_MSEC : 2 * NSEC_PER_SEC);
2191 dispatch_time_t maximumDelay = (SecCKKSReduceRateLimiting() ? 10 * NSEC_PER_SEC : 30 * NSEC_PER_SEC);
2195 void (^block)(void) = ^{
2197 [self.stateMachine handleFlag:OctagonFlagAttemptSOSConsistency];
2200 self.sosConsistencyRateLimiter = [[CKKSNearFutureScheduler alloc] initWithName:@"sos-consistency-ratelimiter"
2201 initialDelay:initialDelay
2203 maximumDelay:maximumDelay
2204 keepProcessAlive:false
2205 dependencyDescriptionCode:CKKSResultDescriptionPendingZoneChangeFetchScheduling
2209 [self.sosConsistencyRateLimiter trigger];
2212 #pragma mark --- External Interfaces
2215 - (CKKSAccountStatus)checkForCKAccount:(OTOperationConfiguration * _Nullable)configuration {
2218 // Watches can be very, very slow getting the CK account state
2219 uint64_t timeout = (90 * NSEC_PER_SEC);
2221 uint64_t timeout = (10 * NSEC_PER_SEC);
2223 if (configuration.timeoutWaitForCKAccount != 0) {
2224 timeout = configuration.timeoutWaitForCKAccount;
2227 /* wait if account is not present yet */
2228 if([self.cloudKitAccountStateKnown wait:timeout] != 0) {
2229 secnotice("octagon-ck", "Unable to determine CloudKit account state?");
2230 return CKKSAccountStatusUnknown;
2234 __block bool haveAccount = true;
2235 dispatch_sync(self.queue, ^{
2236 if (self.cloudKitAccountInfo == NULL || self.cloudKitAccountInfo.accountStatus != CKKSAccountStatusAvailable) {
2237 haveAccount = false;
2242 // Right after account sign-in, it's possible that the CK account exists, but that we just haven't learned about
2243 // it yet, and still have the 'no account' state cached. So, let's check in...
2244 secnotice("octagon-ck", "No CK account present. Attempting to refetch CK account status...");
2245 if(![self.accountStateTracker notifyCKAccountStatusChangeAndWait:timeout]) {
2246 secnotice("octagon-ck", "Fetching new CK account status did not complete in time");
2249 // After the above call finishes, we should have a fresh value in self.cloudKitAccountInfo
2250 dispatch_sync(self.queue, ^{
2251 haveAccount = self.cloudKitAccountInfo.accountStatus == CKKSAccountStatusAvailable;
2253 secnotice("octagon-ck", "After refetch, CK account status is %@", haveAccount ? @"present" : @"missing");
2255 return haveAccount ? CKKSAccountStatusAvailable : CKKSAccountStatusNoAccount;
2258 - (NSError *)errorNoiCloudAccount
2260 return [NSError errorWithDomain:OctagonErrorDomain
2261 code:OTErrorNotSignedIn
2262 description:@"User is not signed into iCloud."];
2265 //Initiator interfaces
2267 - (void)rpcPrepareIdentityAsApplicantWithConfiguration:(OTJoiningConfiguration*)config
2268 epoch:(uint64_t)epoch
2269 reply:(void (^)(NSString * _Nullable peerID,
2270 NSData * _Nullable permanentInfo,
2271 NSData * _Nullable permanentInfoSig,
2272 NSData * _Nullable stableInfo,
2273 NSData * _Nullable stableInfoSig,
2274 NSError * _Nullable error))reply
2276 if ([self checkForCKAccount:nil] != CKKSAccountStatusAvailable) {
2277 secnotice("octagon", "No cloudkit account present");
2278 reply(NULL, NULL, NULL, NULL, NULL, [self errorNoiCloudAccount]);
2282 secnotice("otrpc", "Preparing identity as applicant");
2284 OTPrepareOperation* pendingOp = [[OTPrepareOperation alloc] initWithDependencies:self.operationDependencies
2285 intendedState:OctagonStateInitiatorAwaitingVoucher
2286 errorState:OctagonStateBecomeUntrusted
2287 deviceInfo:[self prepareInformation]
2288 policyOverride:self.policyOverride
2291 dispatch_time_t timeOut = 0;
2292 if(config.timeout != 0) {
2293 timeOut = config.timeout;
2294 } else if(!OctagonPlatformSupportsSOS()){
2295 // Non-iphone non-mac platforms can be slow; heuristically slow them down
2296 timeOut = 60*NSEC_PER_SEC;
2298 timeOut = 2*NSEC_PER_SEC;
2301 OctagonStateTransitionRequest<OTPrepareOperation*>* request = [[OctagonStateTransitionRequest alloc] init:@"prepareForApplicant"
2302 sourceStates:[NSSet setWithArray:@[OctagonStateUntrusted, OctagonStateNoAccount, OctagonStateMachineNotStarted]]
2303 serialQueue:self.queue
2305 transitionOp:pendingOp];
2307 CKKSResultOperation* callback = [CKKSResultOperation named:@"rpcPrepare-callback"
2309 secnotice("otrpc", "Returning a prepare call: %@ %@", pendingOp.peerID, pendingOp.error);
2310 reply(pendingOp.peerID,
2311 pendingOp.permanentInfo,
2312 pendingOp.permanentInfoSig,
2313 pendingOp.stableInfo,
2314 pendingOp.stableInfoSig,
2317 [callback addDependency:pendingOp];
2318 [self.operationQueue addOperation: callback];
2320 [self.stateMachine handleExternalRequest:request];
2325 - (void)joinWithBottle:(NSString*)bottleID
2326 entropy:(NSData *)entropy
2327 bottleSalt:(NSString *)bottleSalt
2328 reply:(void (^)(NSError * _Nullable error))reply
2330 _bottleID = bottleID;
2332 _bottleSalt = bottleSalt;
2333 OTOperationConfiguration *configuration = [[OTOperationConfiguration alloc] init];
2335 if ([self checkForCKAccount:configuration] != CKKSAccountStatusAvailable) {
2336 secnotice("octagon", "No cloudkit account present");
2337 reply([self errorNoiCloudAccount]);
2341 OctagonStateTransitionPath* path = [OctagonStateTransitionPath pathFromDictionary:@{
2342 OctagonStateBottleJoinCreateIdentity: @{
2343 OctagonStateBottleJoinVouchWithBottle: [self joinStatePathDictionary],
2347 [self.stateMachine doWatchedStateMachineRPC:@"rpc-join-with-bottle"
2348 sourceStates:OctagonInAccountStates()
2353 -(void)joinWithRecoveryKey:(NSString*)recoveryKey
2354 reply:(void (^)(NSError * _Nullable error))reply
2356 _recoveryKey = recoveryKey;
2357 OTOperationConfiguration *configuration = [[OTOperationConfiguration alloc] init];
2359 if ([self checkForCKAccount:configuration] != CKKSAccountStatusAvailable) {
2360 secnotice("octagon", "No cloudkit account present");
2361 reply([self errorNoiCloudAccount]);
2365 OctagonStateTransitionPath* path = [OctagonStateTransitionPath pathFromDictionary:@{
2366 OctagonStateCreateIdentityForRecoveryKey: @{
2367 OctagonStateVouchWithRecoveryKey: [self joinStatePathDictionary],
2371 [self.stateMachine doWatchedStateMachineRPC:@"rpc-join-with-recovery-key"
2372 sourceStates:OctagonInAccountStates()
2377 - (NSDictionary*)joinStatePathDictionary
2380 OctagonStateInitiatorSetCDPBit: @{
2381 OctagonStateInitiatorUpdateDeviceList: @{
2382 OctagonStateInitiatorJoin: @{
2383 OctagonStateBottlePreloadOctagonKeysInSOS: @{
2384 OctagonStateBecomeReady: @{
2385 OctagonStateReady: [OctagonStateTransitionPathStep success],
2388 OctagonStateInitiatorJoinCKKSReset: @{
2389 OctagonStateInitiatorJoinAfterCKKSReset: @{
2390 OctagonStateBottlePreloadOctagonKeysInSOS: @{
2391 OctagonStateBecomeReady: @{
2392 OctagonStateReady: [OctagonStateTransitionPathStep success]
2403 - (void)rpcJoin:(NSData*)vouchData
2404 vouchSig:(NSData*)vouchSig
2405 reply:(void (^)(NSError * _Nullable error))reply
2408 if ([self checkForCKAccount:nil] != CKKSAccountStatusAvailable) {
2409 secnotice("octagon", "No cloudkit account present");
2410 reply([self errorNoiCloudAccount]);
2414 NSError* metadataError = nil;
2415 [self.accountMetadataStore persistAccountChanges:^OTAccountMetadataClassC * _Nullable(OTAccountMetadataClassC * _Nonnull metadata) {
2416 metadata.voucher = vouchData;
2417 metadata.voucherSignature = vouchSig;
2419 } error:&metadataError];
2421 secnotice("octagon", "Unable to save voucher for joining: %@", metadataError);
2422 reply(metadataError);
2426 NSMutableSet* sourceStates = [NSMutableSet setWithObject:OctagonStateInitiatorAwaitingVoucher];
2428 OctagonStateTransitionPath* path = [OctagonStateTransitionPath pathFromDictionary:[self joinStatePathDictionary]];
2430 [self.stateMachine doWatchedStateMachineRPC:@"rpc-join"
2431 sourceStates:sourceStates
2436 - (NSDictionary *)ckksPeerStatus:(id<CKKSPeer>)peer
2438 NSMutableDictionary *peerStatus = [NSMutableDictionary dictionary];
2441 peerStatus[@"peerID"] = peer.peerID;
2443 NSData *spki = peer.publicSigningKey.encodeSubjectPublicKeyInfo;
2445 peerStatus[@"signingSPKI"] = [spki base64EncodedStringWithOptions:0];
2446 peerStatus[@"signingSPKIHash"] = [TPHashBuilder hashWithAlgo:kTPHashAlgoSHA256 ofData:spki];
2451 - (NSArray *)sosTrustedPeersStatus
2453 NSError *localError = nil;
2454 NSSet<id<CKKSRemotePeerProtocol>>* _Nullable peers = [self.sosAdapter fetchTrustedPeers:&localError];
2455 if (peers == nil || localError) {
2456 secnotice("octagon", "No SOS peers present: %@, skipping in status", localError);
2459 NSMutableArray<NSDictionary *>* trustedSOSPeers = [NSMutableArray array];
2461 for (id<CKKSPeer> peer in peers) {
2462 NSDictionary *peerStatus = [self ckksPeerStatus:peer];
2464 [trustedSOSPeers addObject:peerStatus];
2467 return trustedSOSPeers;
2470 - (NSDictionary *)sosSelvesStatus
2472 NSError *localError = nil;
2474 CKKSSelves* selves = [self.sosAdapter fetchSelfPeers:&localError];
2475 if (selves == nil || localError) {
2476 secnotice("octagon", "No SOS selves present: %@, skipping in status", localError);
2479 NSMutableDictionary* selvesSOSPeers = [NSMutableDictionary dictionary];
2481 selvesSOSPeers[@"currentSelf"] = [self ckksPeerStatus:selves.currentSelf];
2484 * If we have past selves, include them too
2486 NSMutableSet* pastSelves = [selves.allSelves mutableCopy];
2487 [pastSelves removeObject:selves.currentSelf];
2488 if (pastSelves.count) {
2489 NSMutableArray<NSDictionary *>* pastSelvesStatus = [NSMutableArray array];
2491 for (id<CKKSPeer> peer in pastSelves) {
2492 NSDictionary *peerStatus = [self ckksPeerStatus:peer];
2494 [pastSelvesStatus addObject:peerStatus];
2497 selvesSOSPeers[@"pastSelves"] = pastSelvesStatus;
2499 return selvesSOSPeers;
2502 - (void)rpcStatus:(void (^)(NSDictionary* _Nullable result, NSError* _Nullable error))reply
2504 __block NSMutableDictionary* result = [NSMutableDictionary dictionary];
2506 result[@"containerName"] = self.containerName;
2507 result[@"contextID"] = self.contextID;
2509 if ([self checkForCKAccount:nil] != CKKSAccountStatusAvailable) {
2510 secnotice("octagon", "No cloudkit account present");
2511 reply(NULL, [self errorNoiCloudAccount]);
2515 if([self.stateMachine.paused wait:3*NSEC_PER_SEC] != 0) {
2516 secnotice("octagon", "Returning status of unpaused state machine for container (%@) and context (%@)", self.containerName, self.contextID);
2517 result[@"stateUnpaused"] = @1;
2520 // This will try to allow the state machine to pause
2521 result[@"state"] = self.stateMachine.currentState;
2522 result[@"statePendingFlags"] = [self.stateMachine dumpPendingFlags];
2523 result[@"stateFlags"] = [self.stateMachine.flags dumpFlags];
2525 NSError* metadataError = nil;
2526 OTAccountMetadataClassC* currentAccountMetadata = [self.accountMetadataStore loadOrCreateAccountMetadata:&metadataError];
2528 secnotice("octagon", "Failed to load account metaada for container (%@) and context (%@): %@", self.containerName, self.contextID, metadataError);
2531 result[@"memoizedTrustState"] = @(currentAccountMetadata.trustState);
2532 result[@"memoizedAccountState"] = @(currentAccountMetadata.icloudAccountState);
2533 result[@"memoizedCDPStatus"] = @(currentAccountMetadata.cdpState);
2534 result[@"octagonLaunchSeqence"] = [self.launchSequence eventsByTime];
2536 NSDate* lastHealthCheck = self.currentMemoizedLastHealthCheck;
2537 result[@"memoizedlastHealthCheck"] = lastHealthCheck ?: @"Never checked";
2538 if (self.sosAdapter.sosEnabled) {
2539 result[@"sosTrustedPeersStatus"] = [self sosTrustedPeersStatus];
2540 result[@"sosSelvesStatus"] = [self sosSelvesStatus];
2545 id<SecEscrowRequestable> request = [self.escrowRequestClass request:&error];
2546 result[@"escrowRequest"] = [request fetchStatuses:&error];
2549 result[@"CoreFollowUp"] = [self.followupHandler sysdiagnoseStatus];
2551 result[@"lastOctagonPush"] = [[CKKSAnalytics logger] datePropertyForKey:CKKSAnalyticsLastOctagonPush];
2552 result[@"pushEnvironments"] = [self.apsReceiver registeredPushEnvironments];
2554 [self.cuttlefishXPCWrapper dumpWithContainer:self.containerName
2555 context:self.contextID
2556 reply:^(NSDictionary * _Nullable dump, NSError * _Nullable dumpError) {
2557 secnotice("octagon", "Finished dump for status RPC");
2559 result[@"contextDumpError"] = dumpError;
2561 result[@"contextDump"] = dump;
2567 - (void)rpcFetchEgoPeerID:(void (^)(NSString* peerID, NSError* error))reply
2569 // We've memoized this peer ID. Use the memorized version...
2570 NSError* localError = nil;
2571 NSString* peerID = [self.accountMetadataStore getEgoPeerID:&localError];
2574 secnotice("octagon", "Returning peer ID: %@", peerID);
2576 secnotice("octagon", "Unable to fetch peer ID: %@", localError);
2578 reply(peerID, localError);
2581 - (void)rpcFetchDeviceNamesByPeerID:(void (^)(NSDictionary<NSString*, NSString*>* _Nullable peers, NSError* _Nullable error))reply
2583 if ([self checkForCKAccount:nil] != CKKSAccountStatusAvailable) {
2584 secnotice("octagon", "No cloudkit account present");
2585 reply(NULL, [self errorNoiCloudAccount]);
2589 // As this isn't a state-modifying operation, we don't need to go through the state machine.
2590 [self.cuttlefishXPCWrapper dumpWithContainer:self.containerName
2591 context:self.contextID
2592 reply:^(NSDictionary * _Nullable dump, NSError * _Nullable dumpError) {
2593 // Pull out our peers
2595 secnotice("octagon", "Unable to dump info: %@", dumpError);
2596 reply(nil, dumpError);
2600 NSDictionary* selfInfo = dump[@"self"];
2601 NSArray* peers = dump[@"peers"];
2602 NSArray* trustedPeerIDs = selfInfo[@"dynamicInfo"][@"included"];
2604 NSMutableDictionary<NSString*, NSString*>* peerMap = [NSMutableDictionary dictionary];
2606 for(NSString* peerID in trustedPeerIDs) {
2607 NSDictionary* peerMatchingID = nil;
2609 for(NSDictionary* peer in peers) {
2610 if([peer[@"peerID"] isEqualToString:peerID]) {
2611 peerMatchingID = peer;
2616 if(!peerMatchingID) {
2617 secerror("octagon: have a trusted peer ID without peer information: %@", peerID);
2621 peerMap[peerID] = peerMatchingID[@"stableInfo"][@"device_name"];
2624 reply(peerMap, nil);
2628 - (void)rpcSetRecoveryKey:(NSString*)recoveryKey reply:(void (^)(NSError * _Nullable error))reply
2630 OTSetRecoveryKeyOperation *pendingOp = [[OTSetRecoveryKeyOperation alloc] initWithDependencies:self.operationDependencies
2631 recoveryKey:recoveryKey];
2633 CKKSResultOperation* callback = [CKKSResultOperation named:@"setRecoveryKey-callback"
2635 secnotice("otrpc", "Returning a set recovery key call: %@", pendingOp.error);
2636 reply(pendingOp.error);
2639 [callback addDependency:pendingOp];
2640 [self.operationQueue addOperation:callback];
2641 [self.operationQueue addOperation:pendingOp];
2644 - (void)rpcTrustStatusCachedStatus:(OTAccountMetadataClassC*)account
2645 reply:(void (^)(CliqueStatus status,
2646 NSString* egoPeerID,
2647 NSDictionary<NSString*, NSNumber*>* _Nullable peerCountByModelID,
2650 NSError *error))reply
2652 CliqueStatus status = CliqueStatusAbsent;
2654 if (account.trustState == OTAccountMetadataClassC_TrustState_TRUSTED) {
2655 status = CliqueStatusIn;
2656 } else if (account.trustState == OTAccountMetadataClassC_TrustState_UNTRUSTED) {
2657 status = CliqueStatusNotIn;
2660 secinfo("octagon", "returning cached clique status: %@", OTCliqueStatusToString(status));
2661 reply(status, account.peerID, nil, NO, NO, NULL);
2665 - (void)rpcTrustStatus:(OTOperationConfiguration *)configuration
2666 reply:(void (^)(CliqueStatus status,
2667 NSString* _Nullable peerID,
2668 NSDictionary<NSString*, NSNumber*>* _Nullable peerCountByModelID,
2671 NSError *error))reply
2673 __block NSError* localError = nil;
2675 OTAccountMetadataClassC* account = [self.accountMetadataStore loadOrCreateAccountMetadata:&localError];
2676 if(localError && [self.lockStateTracker isLockedError:localError]){
2677 secnotice("octagon", "Device is locked! pending initialization on unlock");
2678 reply(CliqueStatusError, nil, nil, NO, NO, localError);
2682 if(account.icloudAccountState == OTAccountMetadataClassC_AccountState_NO_ACCOUNT) {
2683 secnotice("octagon", "no account! returning clique status 'no account'");
2684 reply(CliqueStatusNoCloudKitAccount, nil, nil, NO, NO, NULL);
2688 if (configuration.useCachedAccountStatus) {
2689 [self rpcTrustStatusCachedStatus:account reply:reply];
2693 CKKSAccountStatus ckAccountStatus = [self checkForCKAccount:configuration];
2694 if(ckAccountStatus == CKKSAccountStatusNoAccount) {
2695 secnotice("octagon", "No cloudkit account present");
2696 reply(CliqueStatusNoCloudKitAccount, nil, nil, NO, NO, NULL);
2698 } else if(ckAccountStatus == CKKSAccountStatusUnknown) {
2699 secnotice("octagon", "Unknown cloudkit account status, returning cached trust value");
2700 [self rpcTrustStatusCachedStatus:account reply:reply];
2704 __block NSString* peerID = nil;
2705 __block NSDictionary<NSString*, NSNumber*>* peerModelCounts = nil;
2706 __block BOOL excluded = NO;
2707 __block CliqueStatus trustStatus = CliqueStatusError;
2708 __block BOOL isLocked = NO;
2710 [self.cuttlefishXPCWrapper trustStatusWithContainer:self.containerName
2711 context:self.contextID
2712 reply:^(TrustedPeersHelperEgoPeerStatus *egoStatus,
2713 NSError *xpcError) {
2714 TPPeerStatus status = egoStatus.egoStatus;
2715 peerID = egoStatus.egoPeerID;
2716 excluded = egoStatus.isExcluded;
2717 peerModelCounts = egoStatus.viablePeerCountsByModelID;
2718 isLocked = egoStatus.isLocked;
2719 localError = xpcError;
2722 secnotice("octagon", "error fetching trust status: %@", xpcError);
2724 secnotice("octagon", "trust status: %@", TPPeerStatusToString(status));
2726 if((status&TPPeerStatusExcluded) == TPPeerStatusExcluded){
2727 trustStatus = CliqueStatusNotIn;
2729 else if((status&TPPeerStatusPartiallyReciprocated) == TPPeerStatusPartiallyReciprocated){
2730 trustStatus = CliqueStatusIn;
2732 else if((status&TPPeerStatusAncientEpoch) == TPPeerStatusAncientEpoch){
2733 //FIX ME HANDLE THIS CASE
2734 trustStatus= CliqueStatusIn;
2736 else if((status&TPPeerStatusOutdatedEpoch) == TPPeerStatusOutdatedEpoch){
2737 //FIX ME HANDLE THIS CASE
2738 trustStatus = CliqueStatusIn;
2740 else if((status&TPPeerStatusFullyReciprocated) == TPPeerStatusFullyReciprocated){
2741 trustStatus = CliqueStatusIn;
2743 else if ((status&TPPeerStatusSelfTrust) == TPPeerStatusSelfTrust) {
2744 trustStatus = CliqueStatusIn;
2746 else if ((status&TPPeerStatusIgnored) == TPPeerStatusIgnored) {
2747 trustStatus = CliqueStatusNotIn;
2749 else if((status&TPPeerStatusUnknown) == TPPeerStatusUnknown){
2750 trustStatus = CliqueStatusAbsent;
2753 secnotice("octagon", "TPPeerStatus is empty");
2754 trustStatus = CliqueStatusAbsent;
2759 reply(trustStatus, peerID, peerModelCounts, excluded, isLocked, localError);
2762 - (void)rpcFetchAllViableBottles:(void (^)(NSArray<NSString*>* _Nullable sortedBottleIDs, NSArray<NSString*>* _Nullable sortedPartialEscrowRecordIDs, NSError* _Nullable error))reply
2764 if ([self checkForCKAccount:nil] != CKKSAccountStatusAvailable) {
2765 secnotice("octagon", "No cloudkit account present");
2766 reply(NULL, NULL, [self errorNoiCloudAccount]);
2770 // As this isn't a state-modifying operation, we don't need to go through the state machine.
2771 [self.cuttlefishXPCWrapper fetchViableBottlesWithContainer:self.containerName
2772 context:self.contextID
2773 reply:^(NSArray<NSString*>* _Nullable sortedEscrowRecordIDs, NSArray<NSString*>* _Nullable sortedPartialEscrowRecordIDs, NSError * _Nullable error) {
2775 secerror("octagon: error fetching all viable bottles: %@", error);
2776 reply(nil, nil, error);
2778 secnotice("octagon", "fetched viable bottles: %@", sortedEscrowRecordIDs);
2779 secnotice("octagon", "fetched partially viable bottles: %@", sortedPartialEscrowRecordIDs);
2780 reply(sortedEscrowRecordIDs, sortedPartialEscrowRecordIDs, error);
2785 - (void)rpcFetchAllViableEscrowRecords:(BOOL)forceFetch reply:(void (^)(NSArray<NSData*>* _Nullable records,
2786 NSError* _Nullable error))reply
2788 if ([self checkForCKAccount:nil] != CKKSAccountStatusAvailable) {
2789 secnotice("octagon", "No cloudkit account present");
2790 reply(NULL, [self errorNoiCloudAccount]);
2794 // As this isn't a state-modifying operation, we don't need to go through the state machine.
2795 [self.cuttlefishXPCWrapper fetchViableEscrowRecordsWithContainer:self.containerName
2796 context:self.contextID
2797 forceFetch:(BOOL)forceFetch
2798 reply:^(NSArray<NSData *> * _Nullable records, NSError * _Nullable error) {
2800 secerror("octagon: error fetching all viable escrow records: %@", error);
2803 secnotice("octagon", "fetched escrow records: %@", records);
2805 reply(records, error);
2810 - (void)rpcInvalidateEscrowCache:(void (^)(NSError* _Nullable error))reply
2812 if ([self checkForCKAccount:nil] != CKKSAccountStatusAvailable) {
2813 secnotice("octagon", "No cloudkit account present");
2814 reply([self errorNoiCloudAccount]);
2818 // As this isn't a state-modifying operation, we don't need to go through the state machine.
2819 [self.cuttlefishXPCWrapper removeEscrowCacheWithContainer:self.containerName context:self.contextID reply:^(NSError * _Nullable removeError) {
2821 secerror("octagon: failed to remove escrow cache: %@", removeError);
2824 secnotice("octagon", "successfully removed escrow cache");
2830 - (void)fetchEscrowContents:(void (^)(NSData* _Nullable entropy,
2831 NSString* _Nullable bottleID,
2832 NSData* _Nullable signingPublicKey,
2833 NSError* _Nullable error))reply
2835 // As this isn't a state-modifying operation, we don't need to go through the state machine.
2836 [self.cuttlefishXPCWrapper fetchEscrowContentsWithContainer:self.containerName
2837 context:self.contextID
2838 reply:^(NSData * _Nullable entropy, NSString * _Nullable bottleID, NSData * _Nullable signingPublicKey, NSError * _Nullable error) {
2840 secerror("octagon: error fetching escrow contents: %@", error);
2841 reply(nil, nil, nil, error);
2843 secnotice("octagon", "fetched escrow contents for bottle: %@", bottleID);
2844 reply(entropy, bottleID, signingPublicKey, error);
2849 - (void)rpcValidatePeers:(void (^)(NSDictionary* _Nullable result, NSError* _Nullable error))reply
2851 __block NSMutableDictionary* result = [NSMutableDictionary dictionary];
2853 result[@"containerName"] = self.containerName;
2854 result[@"contextID"] = self.contextID;
2855 result[@"state"] = [self.stateMachine waitForState:OctagonStateReady wait:3*NSEC_PER_SEC];
2857 if ([self checkForCKAccount:nil] != CKKSAccountStatusAvailable) {
2858 secnotice("octagon", "No cloudkit account present");
2859 reply(NULL, [self errorNoiCloudAccount]);
2863 [self.cuttlefishXPCWrapper validatePeersWithContainer:self.containerName
2864 context:self.contextID
2865 reply:^(NSDictionary * _Nullable validateData, NSError * _Nullable dumpError) {
2866 secnotice("octagon", "Finished validatePeers for status RPC");
2868 result[@"error"] = dumpError;
2870 result[@"validate"] = validateData;
2876 - (void)rpcRefetchCKKSPolicy:(void (^)(NSError * _Nullable error))reply
2878 [self.stateMachine doWatchedStateMachineRPC:@"octagon-refetch-ckks-policy"
2879 sourceStates:[NSMutableSet setWithArray: @[OctagonStateReady]]
2880 path:[OctagonStateTransitionPath pathFromDictionary:@{
2881 OctagonStateRefetchCKKSPolicy: @{
2882 OctagonStateBecomeReady: @{
2883 OctagonStateReady: [OctagonStateTransitionPathStep success],
2890 - (void)rpcFetchUserControllableViewsSyncingStatus:(void (^)(BOOL areSyncing, NSError* _Nullable error))reply
2892 if(self.viewManager.policy) {
2893 BOOL syncing = self.viewManager.policy.syncUserControllableViewsAsBoolean;
2895 secnotice("octagon-ckks", "Returning user-controllable status as %@ (%@)",
2896 syncing ? @"enabled" : @"disabled",
2897 TPPBPeerStableInfo_UserControllableViewStatusAsString(self.viewManager.policy.syncUserControllableViews));
2899 reply(syncing, nil);
2903 // No loaded policy? Let's trigger a fetch.
2904 [self rpcRefetchCKKSPolicy:^(NSError * _Nullable error) {
2906 secnotice("octagon-ckks", "Failed to fetch policy: %@", error);
2911 if(self.viewManager.policy) {
2912 BOOL syncing = self.viewManager.policy.syncUserControllableViewsAsBoolean;
2914 secnotice("octagon-ckks", "Returning user-controllable status as %@ (%@)",
2915 syncing ? @"enabled" : @"disabled",
2916 TPPBPeerStableInfo_UserControllableViewStatusAsString(self.viewManager.policy.syncUserControllableViews));
2918 reply(syncing, nil);
2922 // The tests sometimes don't have a viewManager. If we're in the tests, try this last-ditch effort:
2923 if(SecCKKSTestsEnabled()) {
2924 NSError* metadataError = nil;
2925 OTAccountMetadataClassC* accountState = [self.accountMetadataStore loadOrCreateAccountMetadata:&metadataError];
2927 secnotice("octagon-ckks", "Error fetching acount state: %@", metadataError);
2929 TPSyncingPolicy* policy = [accountState getTPSyncingPolicy];
2931 BOOL syncing = policy.syncUserControllableViewsAsBoolean;
2932 secnotice("octagon-ckks", "Returning user-controllable status (fetched from account state) as %@ (%@)",
2933 syncing ? @"enabled" : @"disabled",
2934 TPPBPeerStableInfo_UserControllableViewStatusAsString(self.viewManager.policy.syncUserControllableViews));
2935 reply(syncing, nil);
2940 secnotice("octagon-ckks", "Policy missing even after a refetch?");
2941 reply(NO, [NSError errorWithDomain:OctagonErrorDomain
2942 code:OTErrorSyncPolicyMissing
2943 description:@"Sync policy is missing even after refetching"]);
2949 - (void)rpcSetUserControllableViewsSyncingStatus:(BOOL)status reply:(void (^)(BOOL areSyncing, NSError* _Nullable error))reply
2952 // TVs can't set this value.
2953 secnotice("octagon-ckks", "Rejecting set of user-controllable sync status due to platform");
2954 reply(NO, [NSError errorWithDomain:OctagonErrorDomain
2955 code:OTErrorNotSupported
2956 description:@"This platform does not support setting the user-controllable view syncing status"]);
2959 OctagonState* firstState = status ? OctagonStateEnableUserControllableViews : OctagonStateDisableUserControllableViews;
2961 secnotice("octagon-ckks", "Settting user-controllable sync status as '%@'", status ? @"enabled" : @"disabled");
2963 [self.stateMachine doWatchedStateMachineRPC:@"octagon-set-policy"
2964 sourceStates:[NSMutableSet setWithArray: @[OctagonStateReady]]
2965 path:[OctagonStateTransitionPath pathFromDictionary:@{
2967 OctagonStateBecomeReady: @{
2968 OctagonStateReady: [OctagonStateTransitionPathStep success],
2972 reply:^(NSError * _Nullable error) {
2974 secnotice("octagon-ckks", "Failed to set sync policy to '%@': %@", status ? @"enabled" : @"disabled", error);
2979 if(self.viewManager.policy) {
2980 BOOL finalStatus = self.viewManager.policy.syncUserControllableViewsAsBoolean;
2981 secnotice("octagon-ckks", "User-controllable sync status is set as '%@'", finalStatus ? @"enabled" : @"disabled");
2982 reply(finalStatus, nil);
2986 // The tests sometimes don't have a viewManager. If we're in the tests, try this last-ditch effort:
2987 if(SecCKKSTestsEnabled()) {
2988 NSError* metadataError = nil;
2989 OTAccountMetadataClassC* accountState = [self.accountMetadataStore loadOrCreateAccountMetadata:&metadataError];
2991 secnotice("octagon-ckks", "Error fetching acount state: %@", metadataError);
2993 TPSyncingPolicy* policy = [accountState getTPSyncingPolicy];
2995 BOOL syncing = policy.syncUserControllableViewsAsBoolean;
2996 secnotice("octagon-ckks", "Returning user-controllable status (fetched from account state) as %@ (%@)",
2997 syncing ? @"enabled" : @"disabled",
2998 TPPBPeerStableInfo_UserControllableViewStatusAsString(self.viewManager.policy.syncUserControllableViews));
2999 reply(syncing, nil);
3004 secnotice("octagon-ckks", "Policy missing even after a refetch?");
3005 reply(NO, [NSError errorWithDomain:OctagonErrorDomain
3006 code:OTErrorSyncPolicyMissing
3007 description:@"Sync policy is missing even after refetching"]);
3013 #pragma mark --- Health Checker
3015 - (BOOL)postRepairCFU:(NSError**)error
3017 NSError* localError = nil;
3018 BOOL postSuccess = NO;
3019 [self.followupHandler postFollowUp:OTFollowupContextTypeStateRepair error:&localError];
3021 secerror("octagon-health: CoreCDP repair failed: %@", localError);
3023 *error = localError;
3027 secnotice("octagon-health", "CoreCDP post repair success");
3033 - (BOOL)shouldPostConfirmPasscodeCFU:(NSError**)error
3035 NSError* localError = nil;
3036 id<SecEscrowRequestable> request = [self.escrowRequestClass request:&localError];
3037 if(!request || localError) {
3038 secnotice("octagon-health", "Unable to acquire a EscrowRequest object: %@", localError);
3040 *error = localError;
3044 BOOL pendingUpload = [request pendingEscrowUpload:&localError];
3047 secnotice("octagon-health", "Failed to check escrow prerecord status: %@", localError);
3049 *error = localError;
3054 if(pendingUpload == YES) {
3055 secnotice("octagon-health", "prerecord is pending, NOT posting CFU");
3058 secnotice("octagon-health", "no pending prerecords, posting CFU");
3063 - (BOOL)leaveTrust:(NSError**)error
3065 if (OctagonPlatformSupportsSOS()) {
3066 CFErrorRef cfError = NULL;
3067 bool left = SOSCCRemoveThisDeviceFromCircle_Server(&cfError);
3069 if(!left || cfError) {
3070 secerror("failed to leave SOS circle: %@", cfError);
3072 *error = (NSError*)CFBridgingRelease(cfError);
3074 CFReleaseNull(cfError);
3079 secnotice("octagon-health", "Successfully left SOS");
3083 - (BOOL)postConfirmPasscodeCFU:(NSError**)error
3086 NSError* localError = nil;
3087 ret = [self.followupHandler postFollowUp:OTFollowupContextTypeOfflinePasscodeChange error:&localError];
3089 secerror("octagon-health: CoreCDP offline passcode change failed: %@", localError);
3090 *error = localError;
3095 - (void)checkOctagonHealth:(BOOL)skipRateLimitingCheck reply:(void (^)(NSError * _Nullable error))reply
3097 secnotice("octagon-health", "Beginning checking overall Octagon Trust");
3099 _skipRateLimitingCheck = skipRateLimitingCheck;
3101 // Ending in "waitforunlock" is okay for a health check
3102 [self.stateMachine doWatchedStateMachineRPC:@"octagon-trust-health-check"
3103 sourceStates:OctagonHealthSourceStates()
3104 path:[OctagonStateTransitionPath pathFromDictionary:@{
3105 OctagonStateHSA2HealthCheck: @{
3106 OctagonStateCDPHealthCheck: @{
3107 OctagonStateSecurityTrustCheck: @{
3108 OctagonStateTPHTrustCheck: @{
3109 OctagonStateCuttlefishTrustCheck: @{
3110 OctagonStateBecomeReady: @{
3111 OctagonStateReady: [OctagonStateTransitionPathStep success],
3112 OctagonStateWaitForUnlock: [OctagonStateTransitionPathStep success],
3114 // Cuttlefish can suggest we reset the world. Consider reaching here a success,
3115 // instead of tracking the whole reset.
3116 OctagonStateHealthCheckReset: [OctagonStateTransitionPathStep success],
3117 OctagonStateHealthCheckLeaveClique: [OctagonStateTransitionPathStep success],
3119 OctagonStateWaitForUnlock: [OctagonStateTransitionPathStep success],
3122 OctagonStateWaitForCDP: [OctagonStateTransitionPathStep success],
3124 OctagonStateWaitForHSA2: [OctagonStateTransitionPathStep success],
3130 - (void)waitForOctagonUpgrade:(void (^)(NSError* error))reply
3132 secnotice("octagon-sos", "waitForOctagonUpgrade");
3134 NSError* localError = nil;
3136 if (!self.sosAdapter.sosEnabled) {
3137 secnotice("octagon-sos", "sos not enabled, nothing to do for waitForOctagonUpgrade");
3140 } else if ([self.sosAdapter circleStatus:&localError] != kSOSCCInCircle) {
3141 secnotice("octagon-sos", "SOS circle status: %d, cannot perform sos upgrade", [self.sosAdapter circleStatus:&localError]);
3142 if (localError == nil) {
3143 localError = [NSError errorWithDomain:(__bridge NSString*)kSOSErrorDomain code:kSOSErrorNoCircle userInfo:@{NSLocalizedDescriptionKey : @"Not in circle"}];
3145 secerror("octagon-sos: error retrieving circle status: %@", localError);
3150 secnotice("octagon-sos", "in sos circle!, attempting upgrade");
3153 if ([self.stateMachine isPaused]) {
3154 if ([[self.stateMachine currentState] isEqualToString:OctagonStateReady]) {
3155 secnotice("octagon-sos", "waitForOctagonUpgrade: already ready, returning");
3160 if ([[self.stateMachine waitForState:OctagonStateReady wait:10*NSEC_PER_SEC] isEqualToString:OctagonStateReady]) {
3161 secnotice("octagon-sos", "waitForOctagonUpgrade: in ready (after waiting), returning");
3165 secnotice("octagon-sos", "waitForOctagonUpgrade: fail to get to ready after timeout, attempting upgrade");
3169 NSSet* sourceStates = [NSSet setWithArray: @[OctagonStateWaitForCDP,
3170 OctagonStateUntrusted]];
3172 OctagonStateTransitionPath* path = [OctagonStateTransitionPath pathFromDictionary:@{
3173 OctagonStateAttemptSOSUpgradeDetermineCDPState: @{
3174 OctagonStateAttemptSOSUpgrade: @{
3175 OctagonStateBecomeReady: @{
3176 OctagonStateReady: [OctagonStateTransitionPathStep success],
3182 [self.stateMachine doWatchedStateMachineRPC:@"sos-upgrade-to-ready"
3183 sourceStates:sourceStates
3188 // Metrics passthroughs
3190 - (BOOL)machineIDOnMemoizedList:(NSString*)machineID error:(NSError**)error
3192 __block BOOL onList = NO;
3193 __block NSError* reterror = nil;
3194 [self.cuttlefishXPCWrapper fetchAllowedMachineIDsWithContainer:self.containerName
3195 context:self.contextID
3196 reply:^(NSSet<NSString *> * _Nonnull machineIDs, NSError * _Nullable miderror) {
3198 secnotice("octagon-metrics", "Failed to fetch allowed machineIDs: %@", miderror);
3199 reterror = miderror;
3201 if([machineIDs containsObject:machineID]) {
3204 secnotice("octagon-metrics", "MID (%@) on list: %@", machineID, onList ? @"yes" : @"no");
3208 if(reterror && error) {
3214 - (NSNumber* _Nullable)numberOfPeersInModelWithMachineID:(NSString*)machineID error:(NSError**)error
3216 __block NSNumber* ret = nil;
3217 __block NSError* retError = nil;
3218 [self.cuttlefishXPCWrapper trustStatusWithContainer:self.containerName
3219 context:self.contextID
3220 reply:^(TrustedPeersHelperEgoPeerStatus *egoStatus,
3221 NSError *xpcError) {
3223 secnotice("octagon-metrics", "Unable to fetch trust status: %@", xpcError);
3224 retError = xpcError;
3226 ret = egoStatus.peerCountsByMachineID[machineID] ?: @(0);
3227 secnotice("octagon-metrics", "Number of peers with machineID (%@): %@", machineID, ret);
3231 if(retError && error) {