]> git.saurik.com Git - apple/security.git/blob - keychain/ot/OTCuttlefishContext.m
Security-59754.41.1.tar.gz
[apple/security.git] / keychain / ot / OTCuttlefishContext.m
1 /*
2 * Copyright (c) 2017 Apple Inc. All Rights Reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
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
11 * file.
12 *
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.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 */
23 #if OCTAGON
24
25 #import <CoreCDP/CDPAccount.h>
26 #import <notify.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>
33
34
35 #import "keychain/TrustedPeersHelper/TrustedPeersHelperProtocol.h"
36 #include "keychain/SecureObjectSync/SOSInternal.h"
37
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"
95
96 #import "utilities/SecFileLocations.h"
97 #import "utilities/SecTapToRadar.h"
98
99 #if TARGET_OS_WATCH
100 #import "keychain/otpaird/OTPairingClient.h"
101 #endif /* TARGET_OS_WATCH */
102
103
104
105
106 NSString* OTCuttlefishContextErrorDomain = @"otcuttlefish";
107 static dispatch_time_t OctagonStateTransitionDefaultTimeout = 10*NSEC_PER_SEC;
108
109 @class CKKSLockStateTracker;
110
111 @interface OTCuttlefishContext () <OTCuttlefishAccountStateHolderNotifier>
112 {
113 NSString* _bottleID;
114 NSString* _bottleSalt;
115 NSData* _entropy;
116 NSString* _recoveryKey;
117 CuttlefishResetReason _resetReason;
118 BOOL _skipRateLimitingCheck;
119 }
120
121 @property CKKSLaunchSequence* launchSequence;
122 @property NSOperationQueue* operationQueue;
123 @property (nonatomic, strong) OTCuttlefishAccountStateHolder *accountMetadataStore;
124 @property OTFollowup *followupHandler;
125
126 @property (readonly) id<CKKSCloudKitAccountStateTrackingProvider, CKKSOctagonStatusMemoizer> accountStateTracker;
127 @property CKAccountInfo* cloudKitAccountInfo;
128 @property CKKSCondition *cloudKitAccountStateKnown;
129
130 @property CKKSNearFutureScheduler* suggestTLKUploadNotifier;
131 @property CKKSNearFutureScheduler* requestPolicyCheckNotifier;
132
133 @property OctagonAPSReceiver* apsReceiver;
134
135 // Make writable
136 @property (nullable) CKKSViewManager* viewManager;
137
138 // Dependencies (for injection)
139 @property id<OTSOSAdapter> sosAdapter;
140 @property id<CKKSPeerProvider> octagonAdapter;
141 @property (readonly) Class<SecEscrowRequestable> escrowRequestClass;
142
143 @property (nonatomic) BOOL initialBecomeUntrustedPosted;
144
145 @end
146
147 @implementation OTCuttlefishContext
148
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
161 {
162 if ((self = [super init])) {
163 WEAKIFY(self);
164
165 _containerName = containerName;
166 _contextID = contextID;
167
168 _apsReceiver = [OctagonAPSReceiver receiverForNamedDelegatePort:SecCKKSAPSNamedPort
169 apsConnectionClass:apsConnectionClass];
170 [_apsReceiver registerCuttlefishReceiver:self forContainerName:self.containerName];
171
172 _viewManager = viewManager;
173
174 _initialBecomeUntrustedPosted = NO;
175
176 _launchSequence = [[CKKSLaunchSequence alloc] initWithRocketName:@"com.apple.octagon.launch"];
177
178 _queue = dispatch_queue_create("com.apple.security.otcuttlefishcontext", DISPATCH_QUEUE_SERIAL);
179 _operationQueue = [[NSOperationQueue alloc] init];
180 _cloudKitAccountStateKnown = [[CKKSCondition alloc] init];
181
182 _accountMetadataStore = [[OTCuttlefishAccountStateHolder alloc] initWithQueue:_queue
183 container:_containerName
184 context:_contextID];
185 [_accountMetadataStore registerNotification:self];
186
187 _stateMachine = [[OctagonStateMachine alloc] initWithName:@"octagon"
188 states:[NSSet setWithArray:[OctagonStateMap() allKeys]]
189 flags:AllOctagonFlags()
190 initialState:OctagonStateInitializing
191 queue:_queue
192 stateEngine:self
193 lockStateTracker:lockStateTracker];
194
195 _sosAdapter = sosAdapter;
196 [_sosAdapter registerForPeerChangeUpdates:self];
197 _authKitAdapter = authKitAdapter;
198 _deviceAdapter = deviceInformationAdapter;
199 [_deviceAdapter registerForDeviceNameUpdates:self];
200
201 _cuttlefishXPCWrapper = [[CuttlefishXPCWrapper alloc] initWithCuttlefishXPCConnection:cuttlefish];
202 _lockStateTracker = lockStateTracker;
203 _accountStateTracker = accountStateTracker;
204
205 _followupHandler = [[OTFollowup alloc] initWithFollowupController:cdpd];
206
207 [accountStateTracker registerForNotificationsOfCloudKitAccountStatusChange:self];
208 [_authKitAdapter registerNotification:self];
209
210 _escrowRequestClass = escrowRequestClass;
211
212 _suggestTLKUploadNotifier = [[CKKSNearFutureScheduler alloc] initWithName:@"octagon-tlk-request"
213 delay:500*NSEC_PER_MSEC
214 keepProcessAlive:false
215 dependencyDescriptionCode:0
216 block:^{
217 STRONGIFY(self);
218 secnotice("octagon-ckks", "Adding flag for CKKS TLK upload");
219 [self.stateMachine handleFlag:OctagonFlagCKKSRequestsTLKUpload];
220 }];
221
222 _requestPolicyCheckNotifier = [[CKKSNearFutureScheduler alloc] initWithName:@"octagon-policy-check"
223 delay:500*NSEC_PER_MSEC
224 keepProcessAlive:false
225 dependencyDescriptionCode:0 block:^{
226 STRONGIFY(self);
227 secnotice("octagon-ckks", "Adding flag for CKKS policy check");
228 [self.stateMachine handleFlag:OctagonFlagCKKSRequestsPolicyCheck];
229 }];
230 }
231 return self;
232 }
233
234 - (void)clearCKKSViewManager
235 {
236 self.viewManager = nil;
237 }
238
239 - (void)dealloc
240 {
241 // TODO: how to invalidate this?
242 //[self.cuttlefishXPCWrapper invalidate];
243 }
244
245 - (void)notifyTrustChanged:(OTAccountMetadataClassC_TrustState)trustState {
246
247 secnotice("octagon", "Changing trust status to: %@",
248 (trustState == OTAccountMetadataClassC_TrustState_TRUSTED) ? @"Trusted" : @"Untrusted");
249
250 /*
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.
253 */
254 if (!OctagonPlatformSupportsSOS()) {
255 notify_post(kSOSCCCircleChangedNotification);
256 }
257
258 notify_post(OTTrustStatusChangeNotification);
259 }
260
261 - (void)accountStateUpdated:(OTAccountMetadataClassC*)newState from:(OTAccountMetadataClassC *)oldState
262 {
263 if (newState.icloudAccountState == OTAccountMetadataClassC_AccountState_ACCOUNT_AVAILABLE && oldState.icloudAccountState != OTAccountMetadataClassC_AccountState_ACCOUNT_AVAILABLE) {
264 [self.launchSequence addEvent:@"iCloudAccount"];
265 }
266
267 if (newState.trustState == OTAccountMetadataClassC_TrustState_TRUSTED && oldState.trustState != OTAccountMetadataClassC_TrustState_TRUSTED) {
268 [self.launchSequence addEvent:@"Trusted"];
269 }
270 if (newState.trustState != OTAccountMetadataClassC_TrustState_TRUSTED && oldState.trustState == OTAccountMetadataClassC_TrustState_TRUSTED) {
271 [self.launchSequence addEvent:@"Untrusted"];
272 [self notifyTrustChanged:newState.trustState];
273 }
274 }
275
276 - (NSString*)description
277 {
278 return [NSString stringWithFormat:@"<OTCuttlefishContext: %@, %@>", self.containerName, self.contextID];
279 }
280
281 - (void)machinesAdded:(NSArray<NSString*>*)machineIDs altDSID:(NSString*)altDSID
282 {
283 WEAKIFY(self);
284 NSError* metadataError = nil;
285 OTAccountMetadataClassC* accountMetadata = [self.accountMetadataStore loadOrCreateAccountMetadata:&metadataError];
286
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];
291 return;
292 }
293
294 if(!altDSID || ![accountMetadata.altDSID isEqualToString:altDSID]) {
295 secnotice("octagon-authkit", "Machines-added push is for wrong altDSID (%@); current altDSID (%@)", altDSID, accountMetadata.altDSID);
296 return;
297 }
298
299 secnotice("octagon-authkit", "adding machines for altDSID(%@): %@", altDSID, machineIDs);
300
301 [self.cuttlefishXPCWrapper addAllowedMachineIDsWithContainer:self.containerName
302 context:self.contextID
303 machineIDs:machineIDs
304 reply:^(NSError* error) {
305 STRONGIFY(self);
306 if (error) {
307 secerror("octagon-authkit: addAllow errored: %@", error);
308 [self requestTrustedDeviceListRefresh];
309 } else {
310 secnotice("octagon-authkit", "addAllow succeeded");
311
312 OctagonPendingFlag *pendingFlag = [[OctagonPendingFlag alloc] initWithFlag:OctagonFlagCuttlefishNotification
313 conditions:OctagonPendingConditionsDeviceUnlocked];
314 [self.stateMachine handlePendingFlag:pendingFlag];
315 }
316 }];
317 }
318
319 - (void)machinesRemoved:(NSArray<NSString*>*)machineIDs altDSID:(NSString*)altDSID
320 {
321 WEAKIFY(self);
322
323 NSError* metadataError = nil;
324 OTAccountMetadataClassC* accountMetadata = [self.accountMetadataStore loadOrCreateAccountMetadata:&metadataError];
325
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];
330 return;
331 }
332
333 if(!altDSID || ![accountMetadata.altDSID isEqualToString:altDSID]) {
334 secnotice("octagon-authkit", "Machines-removed push is for wrong altDSID (%@); current altDSID (%@)", altDSID, accountMetadata.altDSID);
335 return;
336 }
337
338 secnotice("octagon-authkit", "removing machines for altDSID(%@): %@", altDSID, machineIDs);
339
340 [self.cuttlefishXPCWrapper removeAllowedMachineIDsWithContainer:self.containerName
341 context:self.contextID
342 machineIDs:machineIDs
343 reply:^(NSError* _Nullable error) {
344 STRONGIFY(self);
345 if (error) {
346 secerror("octagon-authkit: removeAllow errored: %@", error);
347 } else {
348 secnotice("octagon-authkit", "removeAllow succeeded");
349 }
350
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];
354 }];
355 }
356
357 - (void)incompleteNotificationOfMachineIDListChange
358 {
359 secnotice("octagon", "incomplete machine ID list notification -- refreshing device list");
360 [self requestTrustedDeviceListRefresh];
361 }
362
363
364 - (void)cloudkitAccountStateChange:(CKAccountInfo* _Nullable)oldAccountInfo
365 to:(CKAccountInfo*)currentAccountInfo
366 {
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.
371
372 secnotice("octagon", "Told of a new CK account status: %@", currentAccountInfo);
373 self.cloudKitAccountInfo = currentAccountInfo;
374 [self.stateMachine _onqueuePokeStateMachine];
375
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.
380
381 // Also let other clients now that we have CK account status
382 [self.cloudKitAccountStateKnown fulfill];
383 });
384
385 if(!(currentAccountInfo.accountStatus == CKAccountStatusAvailable)) {
386 secnotice("octagon", "Informed that the CK account is now unavailable: %@", currentAccountInfo);
387
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) {}];
394 }
395 }
396
397 - (BOOL)accountAvailable:(NSString*)altDSID error:(NSError**)error
398 {
399 secnotice("octagon", "Account available with altDSID: %@ %@", altDSID, self);
400
401 self.launchSequence.firstLaunch = true;
402
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;
407
408 return metadata;
409 } error:&localError];
410
411 if(localError) {
412 secerror("octagon: unable to persist new account availability: %@", localError);
413 }
414
415 [self.stateMachine handleFlag:OctagonFlagAccountIsAvailable];
416
417 if(OctagonIsOptimizationEnabled()){
418 [self.stateMachine handleFlag:OctagonFlagWarmEscrowRecordCache];
419 }
420
421 if(localError) {
422 if(error) {
423 *error = localError;
424 }
425 return NO;
426 }
427 return YES;
428 }
429 - (void) moveToCheckTrustedState
430 {
431 CKKSResultOperation<OctagonStateTransitionOperationProtocol>* checkTrust
432 = [OctagonStateTransitionOperation named:[NSString stringWithFormat:@"%@", @"check-trust-state"]
433 entering:OctagonStateCheckTrustState];
434
435 NSSet* sourceStates = [NSSet setWithArray: @[OctagonStateUntrusted]];
436
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];
443 }
444
445
446 - (BOOL)idmsTrustLevelChanged:(NSError**)error
447 {
448 [self.stateMachine handleFlag:OctagonFlagIDMSLevelChanged];
449 return YES;
450 }
451
452 - (BOOL)accountNoLongerAvailable:(NSError**)error
453 {
454 OctagonStateTransitionOperation* attemptOp = [OctagonStateTransitionOperation named:@"octagon-account-gone"
455 intending:OctagonStateNoAccountDoReset
456 errorState:OctagonStateNoAccountDoReset
457 withBlockTakingSelf:^(OctagonStateTransitionOperation * _Nonnull op) {
458 __block NSError* localError = nil;
459
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;
466
467 return metadata;
468 } error:&localError];
469
470 if(localError) {
471 secerror("octagon: unable to persist new account availability: %@", localError);
472 }
473
474 [self.accountStateTracker setHSA2iCloudAccountStatus:CKKSAccountStatusNoAccount];
475
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];
481 }
482
483 op.error = localError;
484 }];
485
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];
494
495 return YES;
496 }
497
498 - (OTCDPStatus)getCDPStatus:(NSError*__autoreleasing*)error
499 {
500 NSError* localError = nil;
501 OTAccountMetadataClassC* accountState = [self.accountMetadataStore loadOrCreateAccountMetadata:&localError];
502
503 if(localError) {
504 secnotice("octagon-cdp-status", "error fetching account metadata: %@", localError);
505 if(error) {
506 *error = localError;
507 }
508
509 return OTCDPStatusUnknown;
510 }
511
512 OTCDPStatus status = OTCDPStatusUnknown;
513 switch(accountState.cdpState) {
514 case OTAccountMetadataClassC_CDPState_UNKNOWN:
515 status = OTCDPStatusUnknown;
516 break;
517 case OTAccountMetadataClassC_CDPState_DISABLED:
518 status = OTCDPStatusDisabled;
519 break;
520 case OTAccountMetadataClassC_CDPState_ENABLED:
521 status = OTCDPStatusEnabled;
522 break;
523 }
524
525 secnotice("octagon-cdp-status", "current cdp status is: %@", OTCDPStatusToString(status));
526 return status;
527 }
528
529 - (BOOL)setCDPEnabled:(NSError* __autoreleasing *)error
530 {
531 NSError* localError = nil;
532 [self.accountMetadataStore persistAccountChanges:^OTAccountMetadataClassC * _Nonnull(OTAccountMetadataClassC * _Nonnull metadata) {
533 metadata.cdpState = OTAccountMetadataClassC_CDPState_ENABLED;
534 return metadata;
535 } error:&localError];
536
537 [self.stateMachine handleFlag:OctagonFlagCDPEnabled];
538
539 if(localError) {
540 secerror("octagon-cdp-status: unable to persist CDP enablement: %@", localError);
541 if(error) {
542 *error = localError;
543 }
544 return NO;
545 }
546
547 secnotice("octagon-cdp-status", "Successfully set CDP status bit to 'enabled''");
548 return YES;
549 }
550
551 - (void)resetOctagonStateMachine
552 {
553 OctagonStateTransitionOperation* op = [OctagonStateTransitionOperation named:@"resetting-state-machine"
554 entering:OctagonStateInitializing];
555 NSMutableSet* sourceStates = [NSMutableSet setWithArray: OctagonStateMap().allKeys];
556
557 OctagonStateTransitionRequest<OctagonStateTransitionOperation*>* request = [[OctagonStateTransitionRequest alloc] init:@"resetting-state-machine"
558 sourceStates:sourceStates
559 serialQueue:self.queue
560 timeout:OctagonStateTransitionDefaultTimeout
561 transitionOp:op];
562
563 [self.stateMachine handleExternalRequest:request];
564
565 }
566
567 - (void)localReset:(nonnull void (^)(NSError * _Nullable))reply
568 {
569 OTLocalResetOperation* pendingOp = [[OTLocalResetOperation alloc] initWithDependencies:self.operationDependencies
570 intendedState:OctagonStateInitializing
571 errorState:OctagonStateError];
572
573 NSMutableSet* sourceStates = [NSMutableSet setWithArray: OctagonStateMap().allKeys];
574 [self.stateMachine doSimpleStateMachineRPC:@"local-reset" op:pendingOp sourceStates:sourceStates reply:reply];
575 }
576
577 - (NSDictionary*)establishStatePathDictionary
578 {
579 return @{
580 OctagonStateEstablishEnableCDPBit: @{
581 OctagonStateReEnactDeviceList: @{
582 OctagonStateReEnactPrepare: @{
583 OctagonStateReEnactReadyToEstablish: @{
584 OctagonStateEscrowTriggerUpdate: @{
585 OctagonStateBecomeReady: @{
586 OctagonStateReady: [OctagonStateTransitionPathStep success],
587 },
588 },
589
590 // Error handling extra states:
591 OctagonStateEstablishCKKSReset: @{
592 OctagonStateEstablishAfterCKKSReset: @{
593 OctagonStateEscrowTriggerUpdate: @{
594 OctagonStateBecomeReady: @{
595 OctagonStateReady: [OctagonStateTransitionPathStep success],
596 },
597 },
598 },
599 },
600 },
601 },
602 },
603 },
604 };
605 }
606
607 - (void)rpcEstablish:(nonnull NSString *)altDSID
608 reply:(nonnull void (^)(NSError * _Nullable))reply
609 {
610 // The reset flow can split into an error-handling path halfway through; this is okay
611 OctagonStateTransitionPath* path = [OctagonStateTransitionPath pathFromDictionary:[self establishStatePathDictionary]];
612
613 [self.stateMachine doWatchedStateMachineRPC:@"establish"
614 sourceStates:OctagonInAccountStates()
615 path:path
616 reply:reply];
617 }
618
619 - (void)rpcResetAndEstablish:(CuttlefishResetReason)resetReason reply:(nonnull void (^)(NSError * _Nullable))reply
620 {
621 _resetReason = resetReason;
622
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]
628 },
629 },
630 }];
631
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()
635 path:path
636 reply:reply];
637 }
638
639 - (void)rpcLeaveClique:(nonnull void (^)(NSError * _Nullable))reply
640 {
641 OTLeaveCliqueOperation* op = [[OTLeaveCliqueOperation alloc] initWithDependencies:self.operationDependencies
642 intendedState:OctagonStateBecomeUntrusted
643 errorState:OctagonStateError];
644
645 NSSet* sourceStates = [NSSet setWithObject: OctagonStateReady];
646 [self.stateMachine doSimpleStateMachineRPC:@"leave-clique" op:op sourceStates:sourceStates reply:reply];
647 }
648
649 - (void)rpcRemoveFriendsInClique:(NSArray<NSString*>*)peerIDs
650 reply:(void (^)(NSError * _Nullable))reply
651 {
652 OTRemovePeersOperation* op = [[OTRemovePeersOperation alloc] initWithDependencies:self.operationDependencies
653 intendedState:OctagonStateBecomeReady
654 errorState:OctagonStateBecomeReady
655 peerIDs:peerIDs];
656
657 NSSet* sourceStates = [NSSet setWithObject: OctagonStateReady];
658 [self.stateMachine doSimpleStateMachineRPC:@"remove-friends" op:op sourceStates:sourceStates reply:reply];
659 }
660
661 - (OTDeviceInformation*)prepareInformation
662 {
663 NSError* error = nil;
664 NSString* machineID = [self.authKitAdapter machineID:&error];
665
666 if(!machineID || error) {
667 secerror("octagon: Unable to fetch machine ID; expect signin to fail: %@", error);
668 }
669
670 return [[OTDeviceInformation alloc] initForContainerName:self.containerName
671 contextID:self.contextID
672 epoch:0
673 machineID:machineID
674 modelID:self.deviceAdapter.modelID
675 deviceName:self.deviceAdapter.deviceName
676 serialNumber:self.deviceAdapter.serialNumber
677 osVersion:self.deviceAdapter.osVersion];
678 }
679
680 - (OTOperationDependencies*)operationDependencies
681 {
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];
694 }
695
696 - (void)startOctagonStateMachine
697 {
698 [self.stateMachine startOperation];
699 }
700
701 - (void)handlePairingRestart:(OTJoiningConfiguration*)config
702 {
703 if(self.pairingUUID == nil){
704 secnotice("octagon-pairing", "received new pairing UUID (%@)", config.pairingUUID);
705 self.pairingUUID = config.pairingUUID;
706 }
707
708 if(![self.pairingUUID isEqualToString:config.pairingUUID]){
709 secnotice("octagon-pairing", "current pairing UUID (%@) does not match config UUID (%@)", self.pairingUUID, config.pairingUUID);
710
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);
715 }else{
716 secnotice("octagon", "localReset succeeded");
717 self.pairingUUID = config.pairingUUID;
718 }
719 dispatch_semaphore_signal(sema);
720 }];
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");
723 }
724 }
725 }
726
727 #pragma mark --- State Machine Transitions
728
729 - (CKKSResultOperation<OctagonStateTransitionOperationProtocol>* _Nullable)_onqueueNextStateMachineTransition:(OctagonState*)currentState
730 flags:(nonnull OctagonFlags *)flags
731 pendingFlags:(nonnull id<OctagonStateOnqueuePendingFlagHandler>)pendingFlagHandler
732 {
733 dispatch_assert_queue(self.queue);
734
735 [self.launchSequence addEvent:currentState];
736
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];
741 }
742
743 if([currentState isEqualToString:OctagonStateWaitForHSA2]) {
744 if([flags _onqueueContains:OctagonFlagIDMSLevelChanged]) {
745 [flags _onqueueRemoveFlag:OctagonFlagIDMSLevelChanged];
746 return [OctagonStateTransitionOperation named:@"hsa2-check"
747 entering:OctagonStateDetermineiCloudAccountState];
748 }
749
750 secnotice("octagon", "Waiting for an HSA2 account");
751 return nil;
752 }
753
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];
760 } else {
761 secnotice("octagon", "Waiting for a CloudKit account; current state is %@", self.cloudKitAccountInfo ?: @"uninitialized");
762 return nil;
763 }
764 }
765
766 if([currentState isEqualToString:OctagonStateCloudKitNewlyAvailable]) {
767 OctagonFlag* warmEscrowCache = nil;
768 if([flags _onqueueContains:OctagonFlagWarmEscrowRecordCache] && OctagonIsOptimizationEnabled()) {
769 [flags _onqueueRemoveFlag:OctagonFlagWarmEscrowRecordCache];
770 warmEscrowCache = OctagonFlagWarmEscrowRecordCache;
771 }
772 return [self cloudKitAccountNewlyAvailableOperation:warmEscrowCache];
773 }
774
775 if([currentState isEqualToString:OctagonStateDetermineCDPState]) {
776 return [[OTDetermineCDPBitStatusOperation alloc] initWithDependencies:self.operationDependencies
777 intendedState:OctagonStateCheckTrustState
778 errorState:OctagonStateWaitForCDP];
779 }
780
781 if([currentState isEqualToString:OctagonStateWaitForCDP]) {
782 if([flags _onqueueContains:OctagonFlagCDPEnabled]) {
783 [flags _onqueueRemoveFlag:OctagonFlagCDPEnabled];
784 secnotice("octagon", "CDP is newly available!");
785
786 return [OctagonStateTransitionOperation named:@"cdp_enabled"
787 entering:OctagonStateDetermineiCloudAccountState];
788
789 } else if([flags _onqueueContains:OctagonFlagCuttlefishNotification]) {
790 [flags _onqueueRemoveFlag:OctagonFlagCuttlefishNotification];
791 return [OctagonStateTransitionOperation named:@"cdp_enabled_push_received"
792 entering:OctagonStateWaitForCDPUpdated];
793
794 } else {
795 return nil;
796 }
797 }
798
799 if([currentState isEqualToString:OctagonStateWaitForCDPUpdated]) {
800 return [[OTUpdateTPHOperation alloc] initWithDependencies:self.operationDependencies
801 intendedState:OctagonStateDetermineCDPState
802 peerUnknownState:OctagonStateDetermineCDPState
803 errorState:OctagonStateError
804 retryFlag:OctagonFlagCuttlefishNotification];
805 }
806
807 if([currentState isEqualToString:OctagonStateCheckTrustState]) {
808 return [[OctagonCheckTrustStateOperation alloc] initWithDependencies:self.operationDependencies
809 intendedState:OctagonStateBecomeUntrusted
810 errorState:OctagonStateBecomeUntrusted];
811 }
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];
819 }
820
821 if([currentState isEqualToString:OctagonStateCDPHealthCheck]) {
822 return [[OTDetermineCDPBitStatusOperation alloc] initWithDependencies:self.operationDependencies
823 intendedState:OctagonStateSecurityTrustCheck
824 errorState:OctagonStateWaitForCDP];
825 }
826
827 if([currentState isEqualToString:OctagonStateSecurityTrustCheck]) {
828 return [self evaluateSecdOctagonTrust];
829 }
830
831 if([currentState isEqualToString:OctagonStateTPHTrustCheck]) {
832 return [self evaluateTPHOctagonTrust];
833 }
834
835 if([currentState isEqualToString:OctagonStateCuttlefishTrustCheck]) {
836 return [self cuttlefishTrustEvaluation];
837 }
838
839 if ([currentState isEqualToString:OctagonStatePostRepairCFU]) {
840 return [self postRepairCFUAndBecomeUntrusted];
841 }
842
843 if ([currentState isEqualToString:OctagonStateHealthCheckReset]) {
844 // A small violation of state machines...
845 _resetReason = CuttlefishResetReasonHealthCheck;
846 return [OctagonStateTransitionOperation named:@"begin-reset"
847 entering:OctagonStateResetBecomeUntrusted];
848 }
849
850 #pragma mark --- Watch Pairing States
851
852 #if TARGET_OS_WATCH
853 if([currentState isEqualToString:OctagonStateStartCompanionPairing]) {
854 return [self startCompanionPairingOperation];
855 }
856 #endif /* TARGET_OS_WATCH */
857
858 if([currentState isEqualToString:OctagonStateBecomeUntrusted]) {
859 return [self becomeUntrustedOperation:OctagonStateUntrusted];
860 }
861
862 if([currentState isEqualToString:OctagonStateBecomeReady]) {
863 return [self becomeReadyOperation];
864 }
865
866 if([currentState isEqualToString:OctagonStateRefetchCKKSPolicy]) {
867 return [[OTFetchViewsOperation alloc] initWithDependencies:self.operationDependencies
868 intendedState:OctagonStateBecomeReady
869 errorState:OctagonStateError];
870 }
871
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];
878 }
879
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];
886 }
887
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];
895 }
896
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];
904 }
905 }
906
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];
915
916 } else {
917 secnotice("octagon", "We are untrusted, but it seems someone preapproves us now. Unfortunately, this platform doesn't support SOS.");
918 }
919 }
920
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];
927
928 } else {
929 secnotice("octagon", "We are untrusted, but this platform doesn't support SOS.");
930 }
931 }
932
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];
939 } else {
940 secnotice("octagon", "Someone would like us to check SOS consistency, but this platform doesn't support SOS.");
941 }
942 }
943
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];
949 }
950
951 // We're untrusted; no need for the IDMS level flag anymore
952 if([flags _onqueueContains:OctagonFlagIDMSLevelChanged]) {
953 [flags _onqueueRemoveFlag:OctagonFlagIDMSLevelChanged];
954 }
955
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];
960 }
961 }
962
963 if([currentState isEqualToString:OctagonStateUntrustedUpdated]) {
964 return [[OTUpdateTPHOperation alloc] initWithDependencies:self.operationDependencies
965 intendedState:OctagonStateUntrusted
966 peerUnknownState:OctagonStateBecomeUntrusted
967 errorState:OctagonStateError
968 retryFlag:OctagonFlagCuttlefishNotification];
969 }
970
971 if([currentState isEqualToString:OctagonStateDetermineiCloudAccountState]) {
972 secnotice("octagon", "Determine iCloud account status");
973
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];
980 }
981
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];
987 }
988
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];
995 }
996
997 // Add further consistency checks here.
998 return [OctagonStateTransitionOperation named:@"no-consistency-checks"
999 entering:OctagonStateBecomeReady];
1000 }
1001
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];
1008 }
1009 // Add further consistency checks here.
1010 return [OctagonStateTransitionOperation named:@"no-preload-octagon-key"
1011 entering:OctagonStateBecomeReady];
1012 }
1013
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];
1020 }
1021
1022 if([currentState isEqualToString:OctagonStateAttemptSOSUpgradeDetermineCDPState]) {
1023 return [[OTDetermineCDPBitStatusOperation alloc] initWithDependencies:self.operationDependencies
1024 intendedState:OctagonStateAttemptSOSUpgrade
1025 errorState:OctagonStateWaitForCDP];
1026 }
1027
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];
1036
1037 } else if([currentState isEqualToString:OctagonStateSOSUpgradeCKKSReset]) {
1038 return [[OTLocalCKKSResetOperation alloc] initWithDependencies:self.operationDependencies
1039 intendedState:OctagonStateSOSUpgradeAfterCKKSReset
1040 errorState:OctagonStateBecomeUntrusted];
1041
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];
1049
1050
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
1057 epoch:1];
1058
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
1065 epoch:1];
1066
1067 } else if([currentState isEqualToString:OctagonStateBottleJoinVouchWithBottle]) {
1068 return [[OTVouchWithBottleOperation alloc] initWithDependencies:self.operationDependencies
1069 intendedState:OctagonStateInitiatorSetCDPBit
1070 errorState:OctagonStateBecomeUntrusted
1071 bottleID:_bottleID
1072 entropy:_entropy
1073 bottleSalt:_bottleSalt
1074 saveVoucher:YES];
1075
1076 } else if([currentState isEqualToString:OctagonStateVouchWithRecoveryKey]) {
1077 return [[OTVouchWithRecoveryKeyOperation alloc] initWithDependencies:self.operationDependencies
1078 intendedState:OctagonStateInitiatorSetCDPBit
1079 errorState:OctagonStateBecomeUntrusted
1080 recoveryKey:_recoveryKey
1081 saveVoucher:YES];
1082
1083 } else if([currentState isEqualToString:OctagonStateInitiatorSetCDPBit]) {
1084 return [[OTSetCDPBitOperation alloc] initWithDependencies:self.operationDependencies
1085 intendedState:OctagonStateInitiatorUpdateDeviceList
1086 errorState:OctagonStateDetermineCDPState];
1087
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
1094 retryFlag:nil];
1095 return op;
1096
1097 } else if ([currentState isEqualToString:OctagonStateInitiatorJoin]){
1098 return [[OTJoinWithVoucherOperation alloc] initWithDependencies:self.operationDependencies
1099 intendedState:OctagonStateBottlePreloadOctagonKeysInSOS
1100 ckksConflictState:OctagonStateInitiatorJoinCKKSReset
1101 errorState:OctagonStateBecomeUntrusted];
1102
1103 } else if([currentState isEqualToString:OctagonStateInitiatorJoinCKKSReset]) {
1104 return [[OTLocalCKKSResetOperation alloc] initWithDependencies:self.operationDependencies
1105 intendedState:OctagonStateInitiatorJoinAfterCKKSReset
1106 errorState:OctagonStateBecomeUntrusted];
1107
1108 } else if ([currentState isEqualToString:OctagonStateInitiatorJoinAfterCKKSReset]){
1109 return [[OTJoinWithVoucherOperation alloc] initWithDependencies:self.operationDependencies
1110 intendedState:OctagonStateBottlePreloadOctagonKeysInSOS
1111 ckksConflictState:OctagonStateBecomeUntrusted
1112 errorState:OctagonStateBecomeUntrusted];
1113
1114 } else if([currentState isEqualToString:OctagonStateResetBecomeUntrusted]) {
1115 return [self becomeUntrustedOperation:OctagonStateResetAndEstablish];
1116
1117 } else if([currentState isEqualToString:OctagonStateResetAndEstablish]) {
1118 return [[OTResetOperation alloc] init:self.containerName
1119 contextID:self.contextID
1120 reason:_resetReason
1121 intendedState:OctagonStateResetAnyMissingTLKCKKSViews
1122 errorState:OctagonStateError
1123 cuttlefishXPCWrapper:self.cuttlefishXPCWrapper];
1124
1125 } else if([currentState isEqualToString:OctagonStateResetAnyMissingTLKCKKSViews]) {
1126 return [[OTResetCKKSZonesLackingTLKsOperation alloc] initWithDependencies:self.operationDependencies
1127 intendedState:OctagonStateEstablishEnableCDPBit
1128 errorState:OctagonStateError];
1129
1130 } else if([currentState isEqualToString:OctagonStateEstablishEnableCDPBit]) {
1131 return [[OTSetCDPBitOperation alloc] initWithDependencies:self.operationDependencies
1132 intendedState:OctagonStateReEnactDeviceList
1133 errorState:OctagonStateError];
1134
1135 } else if([currentState isEqualToString:OctagonStateReEnactDeviceList]) {
1136 return [[OTUpdateTrustedDeviceListOperation alloc] initWithDependencies:self.operationDependencies
1137 intendedState:OctagonStateReEnactPrepare
1138 listUpdatesState:OctagonStateReEnactPrepare
1139 errorState:OctagonStateBecomeUntrusted
1140 retryFlag:nil];
1141
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
1150 epoch:0];
1151
1152 } else if([currentState isEqualToString:OctagonStateReEnactReadyToEstablish]) {
1153 return [[OTEstablishOperation alloc] initWithDependencies:self.operationDependencies
1154 intendedState:OctagonStateEscrowTriggerUpdate
1155 ckksConflictState:OctagonStateEstablishCKKSReset
1156 errorState:OctagonStateBecomeUntrusted];
1157
1158 } else if([currentState isEqualToString:OctagonStateEstablishCKKSReset]) {
1159 return [[OTLocalCKKSResetOperation alloc] initWithDependencies:self.operationDependencies
1160 intendedState:OctagonStateEstablishAfterCKKSReset
1161 errorState:OctagonStateBecomeUntrusted];
1162
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];
1169
1170 } else if ([currentState isEqualToString:OctagonStateEscrowTriggerUpdate]){
1171 return [[OTTriggerEscrowUpdateOperation alloc] initWithDependencies:self.operationDependencies
1172 intendedState:OctagonStateBecomeReady
1173 errorState:OctagonStateError];
1174
1175 } else if ([currentState isEqualToString:OctagonStateHealthCheckLeaveClique]) {
1176 return [[OTLeaveCliqueOperation alloc] initWithDependencies: self.operationDependencies
1177 intendedState: OctagonStateBecomeUntrusted
1178 errorState: OctagonStateBecomeUntrusted];
1179
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];
1185 }
1186
1187 [pendingFlagHandler _onqueueHandlePendingFlag:[[OctagonPendingFlag alloc] initWithFlag:OctagonFlagUnlocked
1188 conditions:OctagonPendingConditionsDeviceUnlocked]];
1189 return nil;
1190
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];
1196 }
1197
1198 secnotice("octagon", "Requested to enter wait for unlock");
1199 [pendingFlagHandler _onqueueHandlePendingFlag:[[OctagonPendingFlag alloc] initWithFlag:OctagonFlagUnlocked
1200 conditions:OctagonPendingConditionsDeviceUnlocked]];
1201 return nil;
1202
1203 } else if([currentState isEqualToString: OctagonStateUpdateSOSPreapprovals]) {
1204 secnotice("octagon", "Updating SOS preapprovals");
1205
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];
1211
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];
1218
1219 } else if([currentState isEqualToString:OctagonStateAssistCKKSTLKUploadCKKSReset]) {
1220 return [[OTLocalCKKSResetOperation alloc] initWithDependencies:self.operationDependencies
1221 intendedState:OctagonStateAssistCKKSTLKUploadAfterCKKSReset
1222 errorState:OctagonStateBecomeReady];
1223
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];
1231
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];
1238 }
1239
1240 if([flags _onqueueContains:OctagonFlagCKKSRequestsTLKUpload]) {
1241 [flags _onqueueRemoveFlag:OctagonFlagCKKSRequestsTLKUpload];
1242 return [OctagonStateTransitionOperation named:@"ckks-assist"
1243 entering:OctagonStateAssistCKKSTLKUpload];
1244 }
1245
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
1253 bottleID:_bottleID
1254 entropy:_entropy
1255 bottleSalt:_bottleSalt
1256 saveVoucher:NO];
1257 } else {
1258 secnotice("octagon", "Received an suggestion to retry TLK extraction via bottle, but have no entropy stashed");
1259 }
1260 }
1261
1262 if([flags _onqueueContains:OctagonFlagAttemptRecoveryKeyTLKExtraction]) {
1263 [flags _onqueueRemoveFlag:OctagonFlagAttemptRecoveryKeyTLKExtraction];
1264 if(_recoveryKey) {
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
1270 saveVoucher:NO];
1271 } else {
1272 secnotice("octagon", "Received an suggestion to retry TLK extraction via RK, but have no recovery key stashed");
1273 }
1274 }
1275
1276 if([flags _onqueueContains:OctagonFlagFetchAuthKitMachineIDList]) {
1277 [flags _onqueueRemoveFlag:OctagonFlagFetchAuthKitMachineIDList];
1278
1279 secnotice("octagon", "Received an suggestion to update the machine ID list (while ready); updating trusted device list");
1280
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];
1287 }
1288
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];
1295 } else {
1296 secnotice("octagon", "We are untrusted, but this platform doesn't support SOS.");
1297 }
1298 }
1299
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];
1306 } else {
1307 secnotice("octagon", "Someone would like us to check SOS consistency, but this platform doesn't support SOS.");
1308 }
1309 }
1310
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];
1316 }
1317
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];
1323 }
1324
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];
1330 }
1331
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];
1336 }
1337
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];
1342 }
1343
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];
1348 }
1349
1350 [[CKKSAnalytics logger] setDateProperty:[NSDate date] forKey:OctagonAnalyticsLastKeystateReady];
1351 [self.launchSequence launch];
1352 return nil;
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];
1359
1360 }
1361
1362 if ([currentState isEqualToString:OctagonStateError]) {
1363 return nil;
1364 }
1365
1366 return nil;
1367 }
1368
1369 - (CKKSResultOperation<OctagonStateTransitionOperationProtocol>* _Nullable)initializingOperation
1370 {
1371 WEAKIFY(self);
1372 return [OctagonStateTransitionOperation named:@"octagon-initializing"
1373 intending:OctagonStateNoAccount
1374 errorState:OctagonStateError
1375 withBlockTakingSelf:^(OctagonStateTransitionOperation * _Nonnull op) {
1376 STRONGIFY(self);
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;
1384 return;
1385 }
1386
1387 if(localError || !account) {
1388 secnotice("octagon", "Error loading account data: %@", localError);
1389 op.nextState = OctagonStateNoAccount;
1390
1391 } else if(account.icloudAccountState == OTAccountMetadataClassC_AccountState_ACCOUNT_AVAILABLE) {
1392 secnotice("octagon", "An HSA2 iCloud account exists; waiting for CloudKit to confirm");
1393
1394 // Inform the account state tracker of our HSA2 account
1395 [self.accountStateTracker setHSA2iCloudAccountStatus:CKKSAccountStatusAvailable];
1396
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);
1408 } else {
1409 secnotice("octagon-ckks", "Fetched initial syncing policy: %@", syncingPolicy);
1410 [self.viewManager setCurrentSyncingPolicy:syncingPolicy];
1411 }
1412 }];
1413
1414 [self.viewManager createViews];
1415 [self.viewManager beginCloudKitOperationOfAllViews];
1416
1417 op.nextState = OctagonStateWaitingForCloudKitAccount;
1418
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;
1422
1423 } else if(account.icloudAccountState == OTAccountMetadataClassC_AccountState_NO_ACCOUNT) {
1424 [self.accountStateTracker setHSA2iCloudAccountStatus:CKKSAccountStatusNoAccount];
1425
1426 secnotice("octagon", "No iCloud account available.");
1427 op.nextState = OctagonStateNoAccount;
1428
1429 } else {
1430 secnotice("octagon", "Unknown account state (%@). Determining...", [account icloudAccountStateAsString:account.icloudAccountState]);
1431 op.nextState = OctagonStateDetermineiCloudAccountState;
1432 }
1433 }];
1434 }
1435
1436 - (CKKSResultOperation<OctagonStateTransitionOperationProtocol>* _Nullable)evaluateSecdOctagonTrust
1437 {
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;
1447
1448 } else {
1449 secnotice("octagon-health", "trust state (%@). checking in with TPH", [account trustStateAsString:account.trustState]);
1450 op.nextState = [self repairAccountIfTrustedByTPHWithIntendedState:OctagonStateTPHTrustCheck];
1451 }
1452 }];
1453 }
1454
1455 - (CKKSResultOperation<OctagonStateTransitionOperationProtocol>* _Nullable)evaluateTPHOctagonTrust
1456 {
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) {
1462
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;
1468 } else {
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;
1480 } else {
1481 secnotice("octagon-health", "weird shouldn't hit this catch all.. assuming untrusted");
1482 op.nextState = OctagonStateUntrusted;
1483 }
1484 }
1485 }];
1486 }];
1487 }
1488
1489 - (CKKSResultOperation<OctagonStateTransitionOperationProtocol>* _Nullable)cuttlefishTrustEvaluation
1490 {
1491
1492 OTCheckHealthOperation* op = [[OTCheckHealthOperation alloc] initWithDependencies:self.operationDependencies
1493 intendedState:OctagonStateBecomeReady
1494 errorState:OctagonStateBecomeReady
1495 deviceInfo:self.prepareInformation
1496 skipRateLimitedCheck:_skipRateLimitingCheck];
1497
1498 CKKSResultOperation* callback = [CKKSResultOperation named:@"rpcHealthCheck"
1499 withBlock:^{
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;
1508 }
1509 }
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;
1517 }
1518 if(shouldPost) {
1519 secnotice("octagon-health", "Posting Escrow CFU");
1520 NSError* postEscrowCFUError = nil;
1521 BOOL ret = [self postConfirmPasscodeCFU:&postEscrowCFUError];
1522 if(!ret) {
1523 op.error = postEscrowCFUError;
1524 }
1525 } else {
1526 secnotice("octagon-health", "Not posting confirm passcode CFU, already pending a prerecord upload");
1527 }
1528
1529 }
1530 if(op.leaveTrust){
1531 secnotice("octagon-health", "Leaving Octagon and SOS trust");
1532 NSError* leaveError = nil;
1533 if(![self leaveTrust:&leaveError]) {
1534 op.error = leaveError;
1535 }
1536 }
1537 }];
1538 [callback addDependency:op];
1539 [self.operationQueue addOperation: callback];
1540 return op;
1541 }
1542
1543 - (CKKSResultOperation<OctagonStateTransitionOperationProtocol>* _Nullable)postRepairCFUAndBecomeUntrusted
1544 {
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,
1551 BOOL posted,
1552 BOOL hasIdentity,
1553 BOOL isLocked,
1554 NSError * _Nullable postError) {
1555 if(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");
1560 } else {
1561 secnotice("octagon-health", "posted repair cfu via state machine");
1562 }
1563 }];
1564 if (deviceIsLocked == YES) {
1565 op.nextState = OctagonStateWaitForUnlock;
1566 } else {
1567 op.nextState = OctagonStateUntrusted;
1568 }
1569 }];
1570 }
1571
1572 - (CKKSResultOperation<OctagonStateTransitionOperationProtocol>* _Nullable)cloudKitAccountNewlyAvailableOperation:(OctagonFlag*)warmEscrowCache
1573 {
1574 WEAKIFY(self);
1575
1576 return [OctagonStateTransitionOperation named:@"octagon-icloud-account-available"
1577 intending:OctagonStateDetermineCDPState
1578 errorState:OctagonStateError
1579 withBlockTakingSelf:^(OctagonStateTransitionOperation * _Nonnull op) {
1580 STRONGIFY(self);
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), ^{
1586 STRONGIFY(self);
1587 [self rpcFetchAllViableEscrowRecords:NO reply:^(NSArray<NSData *> *records, NSError *error) {
1588 if(error){
1589 secerror("octagon-warm-escrowcache: failed to fetch escrow records, %@", error);
1590 } else {
1591 secnotice("octagon-warm-escrowcache", "Successfully fetched escrow records");
1592 }
1593 }];
1594 });
1595 }
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) {
1599 STRONGIFY(self);
1600
1601 if(!self) {
1602 secerror("octagonpush: received callback for released object");
1603 return;
1604 }
1605
1606 if(error || (apsPushEnvString == nil)) {
1607 secerror("octagonpush: Received error fetching preferred push environment (%@): %@", apsPushEnvString, error);
1608 } else {
1609 secnotice("octagonpush", "Registering for environment '%@'", apsPushEnvString);
1610
1611 [self.apsReceiver registerForEnvironment:apsPushEnvString];
1612 }
1613 }];
1614
1615 op.nextState = op.intendedState;
1616 }];
1617 }
1618
1619 - (OctagonState*) repairAccountIfTrustedByTPHWithIntendedState:(OctagonState*)intendedState
1620 {
1621 __block OctagonState* nextState = intendedState;
1622
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");
1625
1626 OTOperationConfiguration *config = [[OTOperationConfiguration alloc]init];
1627
1628 [self rpcTrustStatus:config reply:^(CliqueStatus status,
1629 NSString* egoPeerID,
1630 NSDictionary<NSString*, NSNumber*>* _Nullable peerCountByModelID,
1631 BOOL isExcluded,
1632 BOOL isLocked,
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);
1636
1637 if (error) {
1638 secnotice("octagon-health", "got an error from tph, returning to become_ready state: %@", error);
1639 nextState = OctagonStateBecomeReady;
1640 return;
1641 }
1642
1643 if (isLocked) {
1644 secnotice("octagon-health", "device is locked");
1645 nextState = OctagonStateWaitForUnlock;
1646 return;
1647 }
1648
1649 if(OctagonAuthoritativeTrustIsEnabled() && hasIdentity && status == CliqueStatusIn) {
1650 secnotice("octagon-health", "TPH believes we're trusted, accepting ego peerID as %@", egoPeerID);
1651
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;
1656 return metadata;
1657 } error:&persistError];
1658 if(!persisted || persistError) {
1659 secerror("octagon-health: couldn't persist results: %@", persistError);
1660 nextState = OctagonStateError;
1661 } else {
1662 secnotice("octagon-health", "added trusted identity to account metadata");
1663 nextState = intendedState;
1664 }
1665
1666 } else if (OctagonAuthoritativeTrustIsEnabled() && hasIdentity && status != CliqueStatusIn){
1667 secnotice("octagon-health", "TPH believes we're not trusted, requesting CFU post");
1668 nextState = OctagonStatePostRepairCFU;
1669 }
1670 }];
1671
1672 return nextState;
1673 }
1674
1675 - (void)checkTrustStatusAndPostRepairCFUIfNecessary:(void (^ _Nullable)(CliqueStatus status, BOOL posted, BOOL hasIdentity, BOOL isLocked, NSError * _Nullable error))reply
1676 {
1677 WEAKIFY(self);
1678 OTOperationConfiguration *configuration = [[OTOperationConfiguration alloc] init];
1679 [self rpcTrustStatus:configuration reply:^(CliqueStatus status,
1680 NSString* _Nullable egoPeerID,
1681 NSDictionary<NSString*, NSNumber*>* _Nullable peerCountByModelID,
1682 BOOL isExcluded,
1683 BOOL isLocked,
1684 NSError * _Nullable error) {
1685 STRONGIFY(self);
1686
1687 secnotice("octagon", "clique status: %@, egoPeerID: %@, peerCountByModelID: %@, isExcluded: %d error: %@", OTCliqueStatusToString(status), egoPeerID, peerCountByModelID, isExcluded, error);
1688
1689 BOOL hasIdentity = egoPeerID != nil;
1690 if (error && error.code != errSecInteractionNotAllowed) {
1691 reply(status, NO, hasIdentity, isLocked, error);
1692 return;
1693 }
1694
1695 if (isLocked) {
1696 secnotice("octagon", "device is locked; not posting CFU");
1697 reply(status, NO, hasIdentity, isLocked, error);
1698 return;
1699 }
1700
1701 #if TARGET_OS_TV
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) {
1708 continue;
1709 }
1710
1711 int count = [peerCountByModelID[modelID] intValue];
1712 if(count > 0) {
1713 secnotice("octagon", "Have %d peers with model %@", count, modelID);
1714 phonePeerPresent = true;
1715 break;
1716 }
1717 }
1718 if(!phonePeerPresent) {
1719 secnotice("octagon", "No iOS peers in account; not posting CFU");
1720 reply(status, NO, hasIdentity, isLocked, nil);
1721 return;
1722 }
1723 #endif
1724
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];
1730
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
1734
1735 } else if(accountState.attemptedJoin == OTAccountMetadataClassC_AttemptedAJoinState_ATTEMPTED) {
1736 // Normal flow, fall through to below
1737 } else {
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");
1740
1741 NSError* circleError = nil;
1742 SOSCCStatus sosStatus = [self.sosAdapter circleStatus:&circleError];
1743
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);
1747 return;
1748
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);
1752
1753 } else if(sosStatus == kSOSCCInCircle) {
1754 secnotice("octagon", "SOS is InCircle, not posting CFU");
1755 reply(status, NO, hasIdentity, isLocked, nil);
1756 return;
1757 } else {
1758 secnotice("octagon", "SOS is %@, posting CFU", (__bridge NSString*)SOSCCGetStatusDescription(sosStatus));
1759 }
1760 }
1761 }
1762
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);
1767 return;
1768 }
1769 reply(status, NO, hasIdentity, isLocked, nil);
1770 return;
1771 }];
1772 }
1773
1774 #if TARGET_OS_WATCH
1775 - (CKKSResultOperation<OctagonStateTransitionOperationProtocol>* _Nullable)startCompanionPairingOperation
1776 {
1777 WEAKIFY(self);
1778 return [OctagonStateTransitionOperation named:@"start-companion-pairing"
1779 intending:OctagonStateBecomeUntrusted
1780 errorState:OctagonStateError
1781 withBlockTakingSelf:^(OctagonStateTransitionOperation * _Nonnull op) {
1782 STRONGIFY(self);
1783 OTPairingInitiateWithCompletion(self.queue, ^(bool success, NSError *error) {
1784 if (success) {
1785 secnotice("octagon", "companion pairing succeeded");
1786 } else {
1787 if (error == nil) {
1788 error = [NSError errorWithDomain:(__bridge NSString *)kSecErrorDomain code:errSecInternalError userInfo:nil];
1789 }
1790 secnotice("octagon", "companion pairing failed: %@", error);
1791 }
1792 [[CKKSAnalytics logger] logResultForEvent:OctagonEventCompanionPairing hardFailure:false result:error];
1793 });
1794 op.nextState = op.intendedState;
1795 }];
1796 }
1797 #endif /* TARGET_OS_WATCH */
1798
1799 - (CKKSResultOperation<OctagonStateTransitionOperationProtocol>* _Nullable)becomeUntrustedOperation:(OctagonState*)intendedState
1800 {
1801 WEAKIFY(self);
1802 return [OctagonStateTransitionOperation named:@"octagon-become-untrusted"
1803 intending:intendedState
1804 errorState:OctagonStateError
1805 withBlockTakingSelf:^(OctagonStateTransitionOperation * _Nonnull op) {
1806 STRONGIFY(self);
1807 NSError* localError = nil;
1808
1809 // During testing, don't kick this off until it's needed
1810 if([self.contextID isEqualToString:OTDefaultContext]) {
1811 [self.accountStateTracker triggerOctagonStatusFetch];
1812 }
1813
1814 __block BOOL deviceIsLocked = NO;
1815 [self checkTrustStatusAndPostRepairCFUIfNecessary:^(CliqueStatus status, BOOL posted, BOOL hasIdentity, BOOL isLocked, NSError * _Nullable postError) {
1816
1817 [[CKKSAnalytics logger] logResultForEvent:OctagonEventCheckTrustForCFU hardFailure:false result:postError];
1818 if(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");
1823 } else {
1824 secnotice("octagon", "clique status: %@, posted cfu: %d", OTCliqueStatusToString(status), !!posted);
1825 }
1826 }];
1827
1828 if (deviceIsLocked == YES) {
1829 secnotice("octagon", "device is locked, state moving to wait for unlock");
1830 op.nextState = OctagonStateWaitForUnlock;
1831 return;
1832 }
1833
1834 [self.accountMetadataStore persistAccountChanges:^OTAccountMetadataClassC * _Nonnull(OTAccountMetadataClassC * _Nonnull metadata) {
1835 metadata.trustState = OTAccountMetadataClassC_TrustState_UNTRUSTED;
1836 return metadata;
1837 } error:&localError];
1838
1839 if(localError) {
1840 secnotice("octagon", "Unable to set trust state: %@", localError);
1841 op.nextState = OctagonStateError;
1842 } else {
1843 op.nextState = op.intendedState;
1844 }
1845
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];
1850 }
1851
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];
1856 if(soserror) {
1857 secnotice("octagon-ckks", "Unable to disable the CKKS4All status in SOS: %@", soserror);
1858 }
1859 }
1860
1861 /*
1862 * Initial notification that we let the world know that trust is up and doing something
1863 */
1864 if (!self.initialBecomeUntrustedPosted) {
1865 [self notifyTrustChanged:OTAccountMetadataClassC_TrustState_UNTRUSTED];
1866 self.initialBecomeUntrustedPosted = YES;
1867 }
1868
1869 self.octagonAdapter = nil;
1870 }];
1871 }
1872
1873 - (CKKSResultOperation<OctagonStateTransitionOperationProtocol>* _Nullable)becomeReadyOperation
1874 {
1875 WEAKIFY(self);
1876 return [OctagonStateTransitionOperation named:@"octagon-ready"
1877 intending:OctagonStateReady
1878 errorState:OctagonStateError
1879 withBlockTakingSelf:^(OctagonStateTransitionOperation * _Nonnull op) {
1880 STRONGIFY(self);
1881
1882 if([self.contextID isEqualToString:OTDefaultContext]) {
1883 [self.accountStateTracker triggerOctagonStatusFetch];
1884 }
1885
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
1888
1889 // but, we do set the 'attempted join' bit, just in case the device joined before we started setting this bit
1890
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;
1894
1895 __block TPSyncingPolicy* policy = nil;
1896
1897 [self.accountMetadataStore persistAccountChanges:^OTAccountMetadataClassC * _Nullable(OTAccountMetadataClassC * _Nonnull metadata) {
1898 peerID = metadata.peerID;
1899
1900 policy = metadata.hasSyncingPolicy ? [metadata getTPSyncingPolicy] : nil;
1901
1902 if(metadata.attemptedJoin == OTAccountMetadataClassC_AttemptedAJoinState_ATTEMPTED) {
1903 return nil;
1904 }
1905 metadata.attemptedJoin = OTAccountMetadataClassC_AttemptedAJoinState_ATTEMPTED;
1906 return metadata;
1907 } error:&localError];
1908
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;
1914 return;
1915
1916 } else {
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];
1923 } else {
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];
1928 if(soserror) {
1929 secnotice("octagon-ckks", "Unable to enable the CKKS4All status in SOS: %@", soserror);
1930 }
1931 }
1932 }
1933
1934 secnotice("octagon-ckks", "Initializing CKKS views with policy %@: %@", policy, policy.viewList);
1935
1936 [self.viewManager setCurrentSyncingPolicy:policy];
1937
1938 OctagonCKKSPeerAdapter* octagonAdapter = [[OctagonCKKSPeerAdapter alloc] initWithPeerID:peerID
1939 containerName:self.containerName
1940 contextID:self.contextID
1941 cuttlefishXPC:self.cuttlefishXPCWrapper];
1942
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);
1948
1949 if([self.lockStateTracker isLockedError:egoPeerKeysError]) {
1950 secnotice("octagon-ckks", "Waiting for device unlock to proceed");
1951 op.nextState = OctagonStateWaitForUnlock;
1952 } else {
1953 secnotice("octagon-ckks", "Error is scary; becoming untrusted");
1954 op.nextState = OctagonStateBecomeUntrusted;
1955 }
1956 return;
1957 }
1958
1959 // stash a reference to the adapter so we can provide updates later
1960 self.octagonAdapter = octagonAdapter;
1961
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);
1966
1967 NSArray<id<CKKSPeerProvider>>* peerProviders = nil;
1968
1969 if(self.sosAdapter.sosEnabled) {
1970 peerProviders = @[self.octagonAdapter, self.sosAdapter];
1971
1972 } else {
1973 peerProviders = @[self.octagonAdapter];
1974 }
1975
1976 [view beginTrustedOperation:peerProviders
1977 suggestTLKUpload:self.suggestTLKUploadNotifier
1978 requestPolicyCheck:self.requestPolicyCheckNotifier];
1979 }
1980 }
1981 [self notifyTrustChanged:OTAccountMetadataClassC_TrustState_TRUSTED];
1982
1983 op.nextState = op.intendedState;
1984 }];
1985 }
1986
1987 #pragma mark --- Utilities to run at times
1988
1989 - (NSString * _Nullable)extractStringKey:(NSString * _Nonnull)key fromDictionary:(NSDictionary * _Nonnull)d
1990 {
1991 NSString *value = d[key];
1992 if ([value isKindOfClass:[NSString class]]) {
1993 return value;
1994 }
1995 return NULL;
1996 }
1997
1998 - (void)handleHealthRequest
1999 {
2000 NSString *trustState = OTAccountMetadataClassC_TrustStateAsString(self.currentMemoizedTrustState);
2001 OctagonState* currentState = [self.stateMachine waitForState:OctagonStateReady wait:3*NSEC_PER_SEC];
2002
2003 [self.cuttlefishXPCWrapper reportHealthWithContainer:self.containerName context:self.contextID stateMachineState:currentState trustState:trustState reply:^(NSError * _Nullable error) {
2004 if (error) {
2005 secerror("octagon: health report is lost: %@", error);
2006 }
2007 }];
2008 }
2009
2010 - (void)handleTTRRequest:(NSDictionary *)cfDictionary
2011 {
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];
2020
2021 if (serialNumber) {
2022 if (![self.deviceAdapter.serialNumber isEqualToString:serialNumber]) {
2023 secnotice("octagon", "TTR request not for me (sn)");
2024 return;
2025 }
2026 }
2027 if (ckDeviceId) {
2028 NSString *selfDeviceID = self.viewManager.accountTracker.ckdeviceID;
2029 if (serialNumber == nil || ![selfDeviceID isEqualToString:serialNumber]) {
2030 secnotice("octagon", "TTR request not for me (deviceId)");
2031 return;
2032 }
2033 }
2034
2035 if (alert == NULL || description == NULL || radar == NULL) {
2036 secerror("octagon: invalid type of TTR requeat: %@", cfDictionary);
2037 return;
2038 }
2039
2040 SecTapToRadar *ttr = [[SecTapToRadar alloc] initTapToRadar:alert
2041 description:description
2042 radar:radar];
2043 if (componentName && componentVersion && componentID) {
2044 ttr.componentName = componentName;
2045 ttr.componentVersion = componentVersion;
2046 ttr.componentID = componentID;
2047 }
2048 [ttr trigger];
2049 }
2050
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
2054 {
2055 [self notifyContainerChangeWithUserInfo:notification.userInfo];
2056 }
2057
2058 - (void)notifyContainerChangeWithUserInfo:(NSDictionary*)userInfo
2059 {
2060 secnotice("octagonpush", "received a cuttlefish push notification (%@): %@",
2061 self.containerName, userInfo);
2062
2063 NSDictionary *cfDictionary = userInfo[@"cf"];
2064 if ([cfDictionary isKindOfClass:[NSDictionary class]]) {
2065 NSString *command = [self extractStringKey:@"k" fromDictionary:cfDictionary];
2066 if(command) {
2067 if ([command isEqualToString:@"h"]) {
2068 [self handleHealthRequest];
2069 } else if ([command isEqualToString:@"r"]) {
2070 [self handleTTRRequest:cfDictionary];
2071 } else {
2072 secerror("octagon: unknown command: %@", command);
2073 }
2074 return;
2075 }
2076 }
2077
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);
2082
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);
2085
2086 WEAKIFY(self);
2087 self.apsRateLimiter = [[CKKSNearFutureScheduler alloc] initWithName:@"aps-push-ratelimiter"
2088 initialDelay:initialDelay
2089 continuingDelay:continuingDelay
2090 keepProcessAlive:YES
2091 dependencyDescriptionCode:CKKSResultDescriptionNone
2092 block:^{
2093 STRONGIFY(self);
2094 if (self == nil) {
2095 return;
2096 }
2097 secnotice("octagon-push-ratelimited", "notifying container of change for context: %@", self.contextID);
2098 OctagonPendingFlag *pendingFlag = [[OctagonPendingFlag alloc] initWithFlag:OctagonFlagCuttlefishNotification
2099 conditions:OctagonPendingConditionsDeviceUnlocked];
2100
2101 [self.stateMachine handlePendingFlag:pendingFlag];
2102 }];
2103 }
2104
2105 [self.apsRateLimiter trigger];
2106 }
2107
2108 - (BOOL)waitForReady:(int64_t)timeOffset
2109 {
2110 OctagonState* currentState = [self.stateMachine waitForState:OctagonStateReady wait:timeOffset];
2111 return [currentState isEqualToString:OctagonStateReady];
2112
2113 }
2114
2115 - (OTAccountMetadataClassC_TrustState)currentMemoizedTrustState
2116 {
2117 NSError* localError = nil;
2118 OTAccountMetadataClassC* accountMetadata = [self.accountMetadataStore loadOrCreateAccountMetadata:&localError];
2119
2120 if(!accountMetadata) {
2121 secnotice("octagon", "Unable to fetch account metadata: %@", localError);
2122 return OTAccountMetadataClassC_TrustState_UNKNOWN;
2123 }
2124
2125 return accountMetadata.trustState;
2126 }
2127
2128 - (OTAccountMetadataClassC_AccountState)currentMemoizedAccountState
2129 {
2130 NSError* localError = nil;
2131 OTAccountMetadataClassC* accountMetadata = [self.accountMetadataStore loadOrCreateAccountMetadata:&localError];
2132
2133 if(!accountMetadata) {
2134 secnotice("octagon", "Unable to fetch account metadata: %@", localError);
2135 return OTAccountMetadataClassC_AccountState_UNKNOWN;
2136 }
2137
2138 return accountMetadata.icloudAccountState;
2139 }
2140
2141 - (NSDate* _Nullable) currentMemoizedLastHealthCheck
2142 {
2143 NSError* localError = nil;
2144 OTAccountMetadataClassC* accountMetadata = [self.accountMetadataStore loadOrCreateAccountMetadata:&localError];
2145
2146 if(!accountMetadata) {
2147 secnotice("octagon", "Unable to fetch account metadata: %@", localError);
2148 return nil;
2149 }
2150 if(accountMetadata.lastHealthCheckup == 0) {
2151 return nil;
2152 }
2153 return [[NSDate alloc] initWithTimeIntervalSince1970: ((NSTimeInterval)accountMetadata.lastHealthCheckup) / 1000.0];
2154 }
2155
2156 - (void)requestTrustedDeviceListRefresh
2157 {
2158 [self.stateMachine handleFlag:OctagonFlagFetchAuthKitMachineIDList];
2159 }
2160
2161 #pragma mark --- Device Info update handling
2162
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];
2168 }
2169
2170 #pragma mark --- SOS update handling
2171
2172
2173 - (void)selfPeerChanged:(id<CKKSPeerProvider>)provider
2174 {
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.
2177 }
2178
2179 - (void)trustedPeerSetChanged:(id<CKKSPeerProvider>)provider
2180 {
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");
2183
2184 if(!self.sosAdapter.sosEnabled) {
2185 secnotice("octagon-sos", "This platform doesn't support SOS. This is probably a bug?");
2186 }
2187
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);
2192
2193 WEAKIFY(self);
2194
2195 void (^block)(void) = ^{
2196 STRONGIFY(self);
2197 [self.stateMachine handleFlag:OctagonFlagAttemptSOSConsistency];
2198 };
2199
2200 self.sosConsistencyRateLimiter = [[CKKSNearFutureScheduler alloc] initWithName:@"sos-consistency-ratelimiter"
2201 initialDelay:initialDelay
2202 expontialBackoff:2
2203 maximumDelay:maximumDelay
2204 keepProcessAlive:false
2205 dependencyDescriptionCode:CKKSResultDescriptionPendingZoneChangeFetchScheduling
2206 block:block];
2207 }
2208
2209 [self.sosConsistencyRateLimiter trigger];
2210 }
2211
2212 #pragma mark --- External Interfaces
2213
2214 //Check for account
2215 - (CKKSAccountStatus)checkForCKAccount:(OTOperationConfiguration * _Nullable)configuration {
2216
2217 #if TARGET_OS_WATCH
2218 // Watches can be very, very slow getting the CK account state
2219 uint64_t timeout = (90 * NSEC_PER_SEC);
2220 #else
2221 uint64_t timeout = (10 * NSEC_PER_SEC);
2222 #endif
2223 if (configuration.timeoutWaitForCKAccount != 0) {
2224 timeout = configuration.timeoutWaitForCKAccount;
2225 }
2226 if (timeout) {
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;
2231 }
2232 }
2233
2234 __block bool haveAccount = true;
2235 dispatch_sync(self.queue, ^{
2236 if (self.cloudKitAccountInfo == NULL || self.cloudKitAccountInfo.accountStatus != CKKSAccountStatusAvailable) {
2237 haveAccount = false;
2238 }
2239 });
2240
2241 if(!haveAccount) {
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");
2247 }
2248
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;
2252 });
2253 secnotice("octagon-ck", "After refetch, CK account status is %@", haveAccount ? @"present" : @"missing");
2254 }
2255 return haveAccount ? CKKSAccountStatusAvailable : CKKSAccountStatusNoAccount;
2256 }
2257
2258 - (NSError *)errorNoiCloudAccount
2259 {
2260 return [NSError errorWithDomain:OctagonErrorDomain
2261 code:OTErrorNotSignedIn
2262 description:@"User is not signed into iCloud."];
2263 }
2264
2265 //Initiator interfaces
2266
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
2275 {
2276 if ([self checkForCKAccount:nil] != CKKSAccountStatusAvailable) {
2277 secnotice("octagon", "No cloudkit account present");
2278 reply(NULL, NULL, NULL, NULL, NULL, [self errorNoiCloudAccount]);
2279 return;
2280 }
2281
2282 secnotice("otrpc", "Preparing identity as applicant");
2283
2284 OTPrepareOperation* pendingOp = [[OTPrepareOperation alloc] initWithDependencies:self.operationDependencies
2285 intendedState:OctagonStateInitiatorAwaitingVoucher
2286 errorState:OctagonStateBecomeUntrusted
2287 deviceInfo:[self prepareInformation]
2288 policyOverride:self.policyOverride
2289 epoch:epoch];
2290
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;
2297 } else {
2298 timeOut = 2*NSEC_PER_SEC;
2299 }
2300
2301 OctagonStateTransitionRequest<OTPrepareOperation*>* request = [[OctagonStateTransitionRequest alloc] init:@"prepareForApplicant"
2302 sourceStates:[NSSet setWithArray:@[OctagonStateUntrusted, OctagonStateNoAccount, OctagonStateMachineNotStarted]]
2303 serialQueue:self.queue
2304 timeout:timeOut
2305 transitionOp:pendingOp];
2306
2307 CKKSResultOperation* callback = [CKKSResultOperation named:@"rpcPrepare-callback"
2308 withBlock:^{
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,
2315 pendingOp.error);
2316 }];
2317 [callback addDependency:pendingOp];
2318 [self.operationQueue addOperation: callback];
2319
2320 [self.stateMachine handleExternalRequest:request];
2321
2322 return;
2323 }
2324
2325 - (void)joinWithBottle:(NSString*)bottleID
2326 entropy:(NSData *)entropy
2327 bottleSalt:(NSString *)bottleSalt
2328 reply:(void (^)(NSError * _Nullable error))reply
2329 {
2330 _bottleID = bottleID;
2331 _entropy = entropy;
2332 _bottleSalt = bottleSalt;
2333 OTOperationConfiguration *configuration = [[OTOperationConfiguration alloc] init];
2334
2335 if ([self checkForCKAccount:configuration] != CKKSAccountStatusAvailable) {
2336 secnotice("octagon", "No cloudkit account present");
2337 reply([self errorNoiCloudAccount]);
2338 return;
2339 }
2340
2341 OctagonStateTransitionPath* path = [OctagonStateTransitionPath pathFromDictionary:@{
2342 OctagonStateBottleJoinCreateIdentity: @{
2343 OctagonStateBottleJoinVouchWithBottle: [self joinStatePathDictionary],
2344 },
2345 }];
2346
2347 [self.stateMachine doWatchedStateMachineRPC:@"rpc-join-with-bottle"
2348 sourceStates:OctagonInAccountStates()
2349 path:path
2350 reply:reply];
2351 }
2352
2353 -(void)joinWithRecoveryKey:(NSString*)recoveryKey
2354 reply:(void (^)(NSError * _Nullable error))reply
2355 {
2356 _recoveryKey = recoveryKey;
2357 OTOperationConfiguration *configuration = [[OTOperationConfiguration alloc] init];
2358
2359 if ([self checkForCKAccount:configuration] != CKKSAccountStatusAvailable) {
2360 secnotice("octagon", "No cloudkit account present");
2361 reply([self errorNoiCloudAccount]);
2362 return;
2363 }
2364
2365 OctagonStateTransitionPath* path = [OctagonStateTransitionPath pathFromDictionary:@{
2366 OctagonStateCreateIdentityForRecoveryKey: @{
2367 OctagonStateVouchWithRecoveryKey: [self joinStatePathDictionary],
2368 },
2369 }];
2370
2371 [self.stateMachine doWatchedStateMachineRPC:@"rpc-join-with-recovery-key"
2372 sourceStates:OctagonInAccountStates()
2373 path:path
2374 reply:reply];
2375 }
2376
2377 - (NSDictionary*)joinStatePathDictionary
2378 {
2379 return @{
2380 OctagonStateInitiatorSetCDPBit: @{
2381 OctagonStateInitiatorUpdateDeviceList: @{
2382 OctagonStateInitiatorJoin: @{
2383 OctagonStateBottlePreloadOctagonKeysInSOS: @{
2384 OctagonStateBecomeReady: @{
2385 OctagonStateReady: [OctagonStateTransitionPathStep success],
2386 },
2387 },
2388 OctagonStateInitiatorJoinCKKSReset: @{
2389 OctagonStateInitiatorJoinAfterCKKSReset: @{
2390 OctagonStateBottlePreloadOctagonKeysInSOS: @{
2391 OctagonStateBecomeReady: @{
2392 OctagonStateReady: [OctagonStateTransitionPathStep success]
2393 },
2394 },
2395 },
2396 },
2397 },
2398 },
2399 },
2400 };
2401 }
2402
2403 - (void)rpcJoin:(NSData*)vouchData
2404 vouchSig:(NSData*)vouchSig
2405 reply:(void (^)(NSError * _Nullable error))reply
2406 {
2407
2408 if ([self checkForCKAccount:nil] != CKKSAccountStatusAvailable) {
2409 secnotice("octagon", "No cloudkit account present");
2410 reply([self errorNoiCloudAccount]);
2411 return;
2412 }
2413
2414 NSError* metadataError = nil;
2415 [self.accountMetadataStore persistAccountChanges:^OTAccountMetadataClassC * _Nullable(OTAccountMetadataClassC * _Nonnull metadata) {
2416 metadata.voucher = vouchData;
2417 metadata.voucherSignature = vouchSig;
2418 return metadata;
2419 } error:&metadataError];
2420 if(metadataError) {
2421 secnotice("octagon", "Unable to save voucher for joining: %@", metadataError);
2422 reply(metadataError);
2423 return;
2424 }
2425
2426 NSMutableSet* sourceStates = [NSMutableSet setWithObject:OctagonStateInitiatorAwaitingVoucher];
2427
2428 OctagonStateTransitionPath* path = [OctagonStateTransitionPath pathFromDictionary:[self joinStatePathDictionary]];
2429
2430 [self.stateMachine doWatchedStateMachineRPC:@"rpc-join"
2431 sourceStates:sourceStates
2432 path:path
2433 reply:reply];
2434 }
2435
2436 - (NSDictionary *)ckksPeerStatus:(id<CKKSPeer>)peer
2437 {
2438 NSMutableDictionary *peerStatus = [NSMutableDictionary dictionary];
2439
2440 if (peer.peerID) {
2441 peerStatus[@"peerID"] = peer.peerID;
2442 }
2443 NSData *spki = peer.publicSigningKey.encodeSubjectPublicKeyInfo;
2444 if (spki) {
2445 peerStatus[@"signingSPKI"] = [spki base64EncodedStringWithOptions:0];
2446 peerStatus[@"signingSPKIHash"] = [TPHashBuilder hashWithAlgo:kTPHashAlgoSHA256 ofData:spki];
2447 }
2448 return peerStatus;
2449 }
2450
2451 - (NSArray *)sosTrustedPeersStatus
2452 {
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);
2457 return nil;
2458 }
2459 NSMutableArray<NSDictionary *>* trustedSOSPeers = [NSMutableArray array];
2460
2461 for (id<CKKSPeer> peer in peers) {
2462 NSDictionary *peerStatus = [self ckksPeerStatus:peer];
2463 if (peerStatus) {
2464 [trustedSOSPeers addObject:peerStatus];
2465 }
2466 }
2467 return trustedSOSPeers;
2468 }
2469
2470 - (NSDictionary *)sosSelvesStatus
2471 {
2472 NSError *localError = nil;
2473
2474 CKKSSelves* selves = [self.sosAdapter fetchSelfPeers:&localError];
2475 if (selves == nil || localError) {
2476 secnotice("octagon", "No SOS selves present: %@, skipping in status", localError);
2477 return nil;
2478 }
2479 NSMutableDictionary* selvesSOSPeers = [NSMutableDictionary dictionary];
2480
2481 selvesSOSPeers[@"currentSelf"] = [self ckksPeerStatus:selves.currentSelf];
2482
2483 /*
2484 * If we have past selves, include them too
2485 */
2486 NSMutableSet* pastSelves = [selves.allSelves mutableCopy];
2487 [pastSelves removeObject:selves.currentSelf];
2488 if (pastSelves.count) {
2489 NSMutableArray<NSDictionary *>* pastSelvesStatus = [NSMutableArray array];
2490
2491 for (id<CKKSPeer> peer in pastSelves) {
2492 NSDictionary *peerStatus = [self ckksPeerStatus:peer];
2493 if (peerStatus) {
2494 [pastSelvesStatus addObject:peerStatus];
2495 }
2496 }
2497 selvesSOSPeers[@"pastSelves"] = pastSelvesStatus;
2498 }
2499 return selvesSOSPeers;
2500 }
2501
2502 - (void)rpcStatus:(void (^)(NSDictionary* _Nullable result, NSError* _Nullable error))reply
2503 {
2504 __block NSMutableDictionary* result = [NSMutableDictionary dictionary];
2505
2506 result[@"containerName"] = self.containerName;
2507 result[@"contextID"] = self.contextID;
2508
2509 if ([self checkForCKAccount:nil] != CKKSAccountStatusAvailable) {
2510 secnotice("octagon", "No cloudkit account present");
2511 reply(NULL, [self errorNoiCloudAccount]);
2512 return;
2513 }
2514
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;
2518 }
2519
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];
2524
2525 NSError* metadataError = nil;
2526 OTAccountMetadataClassC* currentAccountMetadata = [self.accountMetadataStore loadOrCreateAccountMetadata:&metadataError];
2527 if(metadataError) {
2528 secnotice("octagon", "Failed to load account metaada for container (%@) and context (%@): %@", self.containerName, self.contextID, metadataError);
2529 }
2530
2531 result[@"memoizedTrustState"] = @(currentAccountMetadata.trustState);
2532 result[@"memoizedAccountState"] = @(currentAccountMetadata.icloudAccountState);
2533 result[@"memoizedCDPStatus"] = @(currentAccountMetadata.cdpState);
2534 result[@"octagonLaunchSeqence"] = [self.launchSequence eventsByTime];
2535
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];
2541 }
2542
2543 {
2544 NSError *error;
2545 id<SecEscrowRequestable> request = [self.escrowRequestClass request:&error];
2546 result[@"escrowRequest"] = [request fetchStatuses:&error];
2547 }
2548
2549 result[@"CoreFollowUp"] = [self.followupHandler sysdiagnoseStatus];
2550
2551 result[@"lastOctagonPush"] = [[CKKSAnalytics logger] datePropertyForKey:CKKSAnalyticsLastOctagonPush];
2552 result[@"pushEnvironments"] = [self.apsReceiver registeredPushEnvironments];
2553
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");
2558 if(dumpError) {
2559 result[@"contextDumpError"] = dumpError;
2560 } else {
2561 result[@"contextDump"] = dump;
2562 }
2563 reply(result, nil);
2564 }];
2565 }
2566
2567 - (void)rpcFetchEgoPeerID:(void (^)(NSString* peerID, NSError* error))reply
2568 {
2569 // We've memoized this peer ID. Use the memorized version...
2570 NSError* localError = nil;
2571 NSString* peerID = [self.accountMetadataStore getEgoPeerID:&localError];
2572
2573 if(peerID) {
2574 secnotice("octagon", "Returning peer ID: %@", peerID);
2575 } else {
2576 secnotice("octagon", "Unable to fetch peer ID: %@", localError);
2577 }
2578 reply(peerID, localError);
2579 }
2580
2581 - (void)rpcFetchDeviceNamesByPeerID:(void (^)(NSDictionary<NSString*, NSString*>* _Nullable peers, NSError* _Nullable error))reply
2582 {
2583 if ([self checkForCKAccount:nil] != CKKSAccountStatusAvailable) {
2584 secnotice("octagon", "No cloudkit account present");
2585 reply(NULL, [self errorNoiCloudAccount]);
2586 return;
2587 }
2588
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
2594 if(dumpError) {
2595 secnotice("octagon", "Unable to dump info: %@", dumpError);
2596 reply(nil, dumpError);
2597 return;
2598 }
2599
2600 NSDictionary* selfInfo = dump[@"self"];
2601 NSArray* peers = dump[@"peers"];
2602 NSArray* trustedPeerIDs = selfInfo[@"dynamicInfo"][@"included"];
2603
2604 NSMutableDictionary<NSString*, NSString*>* peerMap = [NSMutableDictionary dictionary];
2605
2606 for(NSString* peerID in trustedPeerIDs) {
2607 NSDictionary* peerMatchingID = nil;
2608
2609 for(NSDictionary* peer in peers) {
2610 if([peer[@"peerID"] isEqualToString:peerID]) {
2611 peerMatchingID = peer;
2612 break;
2613 }
2614 }
2615
2616 if(!peerMatchingID) {
2617 secerror("octagon: have a trusted peer ID without peer information: %@", peerID);
2618 continue;
2619 }
2620
2621 peerMap[peerID] = peerMatchingID[@"stableInfo"][@"device_name"];
2622 }
2623
2624 reply(peerMap, nil);
2625 }];
2626 }
2627
2628 - (void)rpcSetRecoveryKey:(NSString*)recoveryKey reply:(void (^)(NSError * _Nullable error))reply
2629 {
2630 OTSetRecoveryKeyOperation *pendingOp = [[OTSetRecoveryKeyOperation alloc] initWithDependencies:self.operationDependencies
2631 recoveryKey:recoveryKey];
2632
2633 CKKSResultOperation* callback = [CKKSResultOperation named:@"setRecoveryKey-callback"
2634 withBlock:^{
2635 secnotice("otrpc", "Returning a set recovery key call: %@", pendingOp.error);
2636 reply(pendingOp.error);
2637 }];
2638
2639 [callback addDependency:pendingOp];
2640 [self.operationQueue addOperation:callback];
2641 [self.operationQueue addOperation:pendingOp];
2642 }
2643
2644 - (void)rpcTrustStatusCachedStatus:(OTAccountMetadataClassC*)account
2645 reply:(void (^)(CliqueStatus status,
2646 NSString* egoPeerID,
2647 NSDictionary<NSString*, NSNumber*>* _Nullable peerCountByModelID,
2648 BOOL isExcluded,
2649 BOOL isLocked,
2650 NSError *error))reply
2651 {
2652 CliqueStatus status = CliqueStatusAbsent;
2653
2654 if (account.trustState == OTAccountMetadataClassC_TrustState_TRUSTED) {
2655 status = CliqueStatusIn;
2656 } else if (account.trustState == OTAccountMetadataClassC_TrustState_UNTRUSTED) {
2657 status = CliqueStatusNotIn;
2658 }
2659
2660 secinfo("octagon", "returning cached clique status: %@", OTCliqueStatusToString(status));
2661 reply(status, account.peerID, nil, NO, NO, NULL);
2662 }
2663
2664
2665 - (void)rpcTrustStatus:(OTOperationConfiguration *)configuration
2666 reply:(void (^)(CliqueStatus status,
2667 NSString* _Nullable peerID,
2668 NSDictionary<NSString*, NSNumber*>* _Nullable peerCountByModelID,
2669 BOOL isExcluded,
2670 BOOL isLocked,
2671 NSError *error))reply
2672 {
2673 __block NSError* localError = nil;
2674
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);
2679 return;
2680 }
2681
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);
2685 return;
2686 }
2687
2688 if (configuration.useCachedAccountStatus) {
2689 [self rpcTrustStatusCachedStatus:account reply:reply];
2690 return;
2691 }
2692
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);
2697 return;
2698 } else if(ckAccountStatus == CKKSAccountStatusUnknown) {
2699 secnotice("octagon", "Unknown cloudkit account status, returning cached trust value");
2700 [self rpcTrustStatusCachedStatus:account reply:reply];
2701 return;
2702 }
2703
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;
2709
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;
2720
2721 if(xpcError) {
2722 secnotice("octagon", "error fetching trust status: %@", xpcError);
2723 } else {
2724 secnotice("octagon", "trust status: %@", TPPeerStatusToString(status));
2725
2726 if((status&TPPeerStatusExcluded) == TPPeerStatusExcluded){
2727 trustStatus = CliqueStatusNotIn;
2728 }
2729 else if((status&TPPeerStatusPartiallyReciprocated) == TPPeerStatusPartiallyReciprocated){
2730 trustStatus = CliqueStatusIn;
2731 }
2732 else if((status&TPPeerStatusAncientEpoch) == TPPeerStatusAncientEpoch){
2733 //FIX ME HANDLE THIS CASE
2734 trustStatus= CliqueStatusIn;
2735 }
2736 else if((status&TPPeerStatusOutdatedEpoch) == TPPeerStatusOutdatedEpoch){
2737 //FIX ME HANDLE THIS CASE
2738 trustStatus = CliqueStatusIn;
2739 }
2740 else if((status&TPPeerStatusFullyReciprocated) == TPPeerStatusFullyReciprocated){
2741 trustStatus = CliqueStatusIn;
2742 }
2743 else if ((status&TPPeerStatusSelfTrust) == TPPeerStatusSelfTrust) {
2744 trustStatus = CliqueStatusIn;
2745 }
2746 else if ((status&TPPeerStatusIgnored) == TPPeerStatusIgnored) {
2747 trustStatus = CliqueStatusNotIn;
2748 }
2749 else if((status&TPPeerStatusUnknown) == TPPeerStatusUnknown){
2750 trustStatus = CliqueStatusAbsent;
2751 }
2752 else {
2753 secnotice("octagon", "TPPeerStatus is empty");
2754 trustStatus = CliqueStatusAbsent;
2755 }
2756 }
2757 }];
2758
2759 reply(trustStatus, peerID, peerModelCounts, excluded, isLocked, localError);
2760 }
2761
2762 - (void)rpcFetchAllViableBottles:(void (^)(NSArray<NSString*>* _Nullable sortedBottleIDs, NSArray<NSString*>* _Nullable sortedPartialEscrowRecordIDs, NSError* _Nullable error))reply
2763 {
2764 if ([self checkForCKAccount:nil] != CKKSAccountStatusAvailable) {
2765 secnotice("octagon", "No cloudkit account present");
2766 reply(NULL, NULL, [self errorNoiCloudAccount]);
2767 return;
2768 }
2769
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) {
2774 if(error){
2775 secerror("octagon: error fetching all viable bottles: %@", error);
2776 reply(nil, nil, error);
2777 }else{
2778 secnotice("octagon", "fetched viable bottles: %@", sortedEscrowRecordIDs);
2779 secnotice("octagon", "fetched partially viable bottles: %@", sortedPartialEscrowRecordIDs);
2780 reply(sortedEscrowRecordIDs, sortedPartialEscrowRecordIDs, error);
2781 }
2782 }];
2783 }
2784
2785 - (void)rpcFetchAllViableEscrowRecords:(BOOL)forceFetch reply:(void (^)(NSArray<NSData*>* _Nullable records,
2786 NSError* _Nullable error))reply
2787 {
2788 if ([self checkForCKAccount:nil] != CKKSAccountStatusAvailable) {
2789 secnotice("octagon", "No cloudkit account present");
2790 reply(NULL, [self errorNoiCloudAccount]);
2791 return;
2792 }
2793
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) {
2799 if(error){
2800 secerror("octagon: error fetching all viable escrow records: %@", error);
2801 reply(nil, error);
2802 }else{
2803 secnotice("octagon", "fetched escrow records: %@", records);
2804
2805 reply(records, error);
2806 }
2807 }];
2808 }
2809
2810 - (void)rpcInvalidateEscrowCache:(void (^)(NSError* _Nullable error))reply
2811 {
2812 if ([self checkForCKAccount:nil] != CKKSAccountStatusAvailable) {
2813 secnotice("octagon", "No cloudkit account present");
2814 reply([self errorNoiCloudAccount]);
2815 return;
2816 }
2817
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) {
2820 if (removeError){
2821 secerror("octagon: failed to remove escrow cache: %@", removeError);
2822 reply(removeError);
2823 } else{
2824 secnotice("octagon", "successfully removed escrow cache");
2825 reply(nil);
2826 }
2827 }];
2828 }
2829
2830 - (void)fetchEscrowContents:(void (^)(NSData* _Nullable entropy,
2831 NSString* _Nullable bottleID,
2832 NSData* _Nullable signingPublicKey,
2833 NSError* _Nullable error))reply
2834 {
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) {
2839 if(error){
2840 secerror("octagon: error fetching escrow contents: %@", error);
2841 reply(nil, nil, nil, error);
2842 }else{
2843 secnotice("octagon", "fetched escrow contents for bottle: %@", bottleID);
2844 reply(entropy, bottleID, signingPublicKey, error);
2845 }
2846 }];
2847 }
2848
2849 - (void)rpcValidatePeers:(void (^)(NSDictionary* _Nullable result, NSError* _Nullable error))reply
2850 {
2851 __block NSMutableDictionary* result = [NSMutableDictionary dictionary];
2852
2853 result[@"containerName"] = self.containerName;
2854 result[@"contextID"] = self.contextID;
2855 result[@"state"] = [self.stateMachine waitForState:OctagonStateReady wait:3*NSEC_PER_SEC];
2856
2857 if ([self checkForCKAccount:nil] != CKKSAccountStatusAvailable) {
2858 secnotice("octagon", "No cloudkit account present");
2859 reply(NULL, [self errorNoiCloudAccount]);
2860 return;
2861 }
2862
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");
2867 if(dumpError) {
2868 result[@"error"] = dumpError;
2869 } else {
2870 result[@"validate"] = validateData;
2871 }
2872 reply(result, nil);
2873 }];
2874 }
2875
2876 - (void)rpcRefetchCKKSPolicy:(void (^)(NSError * _Nullable error))reply
2877 {
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],
2884 },
2885 },
2886 }]
2887 reply:reply];
2888 }
2889
2890 - (void)rpcFetchUserControllableViewsSyncingStatus:(void (^)(BOOL areSyncing, NSError* _Nullable error))reply
2891 {
2892 if(self.viewManager.policy) {
2893 BOOL syncing = self.viewManager.policy.syncUserControllableViewsAsBoolean;
2894
2895 secnotice("octagon-ckks", "Returning user-controllable status as %@ (%@)",
2896 syncing ? @"enabled" : @"disabled",
2897 TPPBPeerStableInfo_UserControllableViewStatusAsString(self.viewManager.policy.syncUserControllableViews));
2898
2899 reply(syncing, nil);
2900 return;
2901 }
2902
2903 // No loaded policy? Let's trigger a fetch.
2904 [self rpcRefetchCKKSPolicy:^(NSError * _Nullable error) {
2905 if(error) {
2906 secnotice("octagon-ckks", "Failed to fetch policy: %@", error);
2907 reply(NO, error);
2908 return;
2909 }
2910
2911 if(self.viewManager.policy) {
2912 BOOL syncing = self.viewManager.policy.syncUserControllableViewsAsBoolean;
2913
2914 secnotice("octagon-ckks", "Returning user-controllable status as %@ (%@)",
2915 syncing ? @"enabled" : @"disabled",
2916 TPPBPeerStableInfo_UserControllableViewStatusAsString(self.viewManager.policy.syncUserControllableViews));
2917
2918 reply(syncing, nil);
2919 return;
2920
2921 } else {
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];
2926 if(metadataError) {
2927 secnotice("octagon-ckks", "Error fetching acount state: %@", metadataError);
2928 }
2929 TPSyncingPolicy* policy = [accountState getTPSyncingPolicy];
2930 if(policy) {
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);
2936 return;
2937 }
2938 }
2939
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"]);
2944 return;
2945 }
2946 }];
2947 }
2948
2949 - (void)rpcSetUserControllableViewsSyncingStatus:(BOOL)status reply:(void (^)(BOOL areSyncing, NSError* _Nullable error))reply
2950 {
2951 #if TARGET_OS_TV
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"]);
2957 #else
2958
2959 OctagonState* firstState = status ? OctagonStateEnableUserControllableViews : OctagonStateDisableUserControllableViews;
2960
2961 secnotice("octagon-ckks", "Settting user-controllable sync status as '%@'", status ? @"enabled" : @"disabled");
2962
2963 [self.stateMachine doWatchedStateMachineRPC:@"octagon-set-policy"
2964 sourceStates:[NSMutableSet setWithArray: @[OctagonStateReady]]
2965 path:[OctagonStateTransitionPath pathFromDictionary:@{
2966 firstState: @{
2967 OctagonStateBecomeReady: @{
2968 OctagonStateReady: [OctagonStateTransitionPathStep success],
2969 },
2970 },
2971 }]
2972 reply:^(NSError * _Nullable error) {
2973 if(error) {
2974 secnotice("octagon-ckks", "Failed to set sync policy to '%@': %@", status ? @"enabled" : @"disabled", error);
2975 reply(NO, error);
2976 return;
2977 }
2978
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);
2983 return;
2984 }
2985
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];
2990 if(metadataError) {
2991 secnotice("octagon-ckks", "Error fetching acount state: %@", metadataError);
2992 }
2993 TPSyncingPolicy* policy = [accountState getTPSyncingPolicy];
2994 if(policy) {
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);
3000 return;
3001 }
3002 }
3003
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"]);
3008 return;
3009 }];
3010 #endif
3011 }
3012
3013 #pragma mark --- Health Checker
3014
3015 - (BOOL)postRepairCFU:(NSError**)error
3016 {
3017 NSError* localError = nil;
3018 BOOL postSuccess = NO;
3019 [self.followupHandler postFollowUp:OTFollowupContextTypeStateRepair error:&localError];
3020 if(localError){
3021 secerror("octagon-health: CoreCDP repair failed: %@", localError);
3022 if(error){
3023 *error = localError;
3024 }
3025 }
3026 else{
3027 secnotice("octagon-health", "CoreCDP post repair success");
3028 postSuccess = YES;
3029 }
3030 return postSuccess;
3031 }
3032
3033 - (BOOL)shouldPostConfirmPasscodeCFU:(NSError**)error
3034 {
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);
3039 if(error){
3040 *error = localError;
3041 }
3042 return YES;
3043 }
3044 BOOL pendingUpload = [request pendingEscrowUpload:&localError];
3045
3046 if(localError) {
3047 secnotice("octagon-health", "Failed to check escrow prerecord status: %@", localError);
3048 if(error) {
3049 *error = localError;
3050 }
3051 return YES;
3052 }
3053
3054 if(pendingUpload == YES) {
3055 secnotice("octagon-health", "prerecord is pending, NOT posting CFU");
3056 return NO;
3057 } else {
3058 secnotice("octagon-health", "no pending prerecords, posting CFU");
3059 return YES;
3060 }
3061 }
3062
3063 - (BOOL)leaveTrust:(NSError**)error
3064 {
3065 if (OctagonPlatformSupportsSOS()) {
3066 CFErrorRef cfError = NULL;
3067 bool left = SOSCCRemoveThisDeviceFromCircle_Server(&cfError);
3068
3069 if(!left || cfError) {
3070 secerror("failed to leave SOS circle: %@", cfError);
3071 if(error) {
3072 *error = (NSError*)CFBridgingRelease(cfError);
3073 } else {
3074 CFReleaseNull(cfError);
3075 }
3076 return NO;
3077 }
3078 }
3079 secnotice("octagon-health", "Successfully left SOS");
3080 return YES;
3081 }
3082
3083 - (BOOL)postConfirmPasscodeCFU:(NSError**)error
3084 {
3085 BOOL ret = NO;
3086 NSError* localError = nil;
3087 ret = [self.followupHandler postFollowUp:OTFollowupContextTypeOfflinePasscodeChange error:&localError];
3088 if(localError){
3089 secerror("octagon-health: CoreCDP offline passcode change failed: %@", localError);
3090 *error = localError;
3091 }
3092 return ret;
3093 }
3094
3095 - (void)checkOctagonHealth:(BOOL)skipRateLimitingCheck reply:(void (^)(NSError * _Nullable error))reply
3096 {
3097 secnotice("octagon-health", "Beginning checking overall Octagon Trust");
3098
3099 _skipRateLimitingCheck = skipRateLimitingCheck;
3100
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],
3113 },
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],
3118 },
3119 OctagonStateWaitForUnlock: [OctagonStateTransitionPathStep success],
3120 },
3121 },
3122 OctagonStateWaitForCDP: [OctagonStateTransitionPathStep success],
3123 },
3124 OctagonStateWaitForHSA2: [OctagonStateTransitionPathStep success],
3125 }
3126 }]
3127 reply:reply];
3128 }
3129
3130 - (void)waitForOctagonUpgrade:(void (^)(NSError* error))reply
3131 {
3132 secnotice("octagon-sos", "waitForOctagonUpgrade");
3133
3134 NSError* localError = nil;
3135
3136 if (!self.sosAdapter.sosEnabled) {
3137 secnotice("octagon-sos", "sos not enabled, nothing to do for waitForOctagonUpgrade");
3138 reply(nil);
3139 return;
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"}];
3144 } else {
3145 secerror("octagon-sos: error retrieving circle status: %@", localError);
3146 }
3147 reply(localError);
3148 return;
3149 } else {
3150 secnotice("octagon-sos", "in sos circle!, attempting upgrade");
3151 }
3152
3153 if ([self.stateMachine isPaused]) {
3154 if ([[self.stateMachine currentState] isEqualToString:OctagonStateReady]) {
3155 secnotice("octagon-sos", "waitForOctagonUpgrade: already ready, returning");
3156 reply(nil);
3157 return;
3158 }
3159 } else {
3160 if ([[self.stateMachine waitForState:OctagonStateReady wait:10*NSEC_PER_SEC] isEqualToString:OctagonStateReady]) {
3161 secnotice("octagon-sos", "waitForOctagonUpgrade: in ready (after waiting), returning");
3162 reply(nil);
3163 return;
3164 } else {
3165 secnotice("octagon-sos", "waitForOctagonUpgrade: fail to get to ready after timeout, attempting upgrade");
3166 }
3167 }
3168
3169 NSSet* sourceStates = [NSSet setWithArray: @[OctagonStateWaitForCDP,
3170 OctagonStateUntrusted]];
3171
3172 OctagonStateTransitionPath* path = [OctagonStateTransitionPath pathFromDictionary:@{
3173 OctagonStateAttemptSOSUpgradeDetermineCDPState: @{
3174 OctagonStateAttemptSOSUpgrade: @{
3175 OctagonStateBecomeReady: @{
3176 OctagonStateReady: [OctagonStateTransitionPathStep success],
3177 },
3178 },
3179 },
3180 }];
3181
3182 [self.stateMachine doWatchedStateMachineRPC:@"sos-upgrade-to-ready"
3183 sourceStates:sourceStates
3184 path:path
3185 reply:reply];
3186 }
3187
3188 // Metrics passthroughs
3189
3190 - (BOOL)machineIDOnMemoizedList:(NSString*)machineID error:(NSError**)error
3191 {
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) {
3197 if(miderror) {
3198 secnotice("octagon-metrics", "Failed to fetch allowed machineIDs: %@", miderror);
3199 reterror = miderror;
3200 } else {
3201 if([machineIDs containsObject:machineID]) {
3202 onList = YES;
3203 }
3204 secnotice("octagon-metrics", "MID (%@) on list: %@", machineID, onList ? @"yes" : @"no");
3205 }
3206 }];
3207
3208 if(reterror && error) {
3209 *error = reterror;
3210 }
3211 return onList;
3212 }
3213
3214 - (NSNumber* _Nullable)numberOfPeersInModelWithMachineID:(NSString*)machineID error:(NSError**)error
3215 {
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) {
3222 if(xpcError) {
3223 secnotice("octagon-metrics", "Unable to fetch trust status: %@", xpcError);
3224 retError = xpcError;
3225 } else {
3226 ret = egoStatus.peerCountsByMachineID[machineID] ?: @(0);
3227 secnotice("octagon-metrics", "Number of peers with machineID (%@): %@", machineID, ret);
3228 }
3229 }];
3230
3231 if(retError && error) {
3232 *error = retError;
3233 }
3234
3235 return ret;
3236 }
3237
3238 @end
3239 #endif