]> git.saurik.com Git - apple/security.git/blob - keychain/ot/OTCuttlefishContext.m
Security-59306.61.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 #include <sys/sysctl.h>
26
27 #import <os/feature_private.h>
28
29 #import <Security/Security.h>
30
31 #include <utilities/SecFileLocations.h>
32 #include <Security/SecRandomP.h>
33 #import <SecurityFoundation/SFKey_Private.h>
34
35 #import "keychain/TrustedPeersHelper/TrustedPeersHelperProtocol.h"
36 #import <TrustedPeers/TrustedPeers.h>
37 #import "keychain/ckks/CKKS.h"
38 #import "keychain/ckks/CKKSAnalytics.h"
39 #import "keychain/ckks/CKKSResultOperation.h"
40
41 #import "keychain/ckks/OctagonAPSReceiver.h"
42 #import "keychain/analytics/CKKSLaunchSequence.h"
43 #import "keychain/ot/OTDeviceInformationAdapter.h"
44
45
46 #import "keychain/ot/OctagonStateMachine.h"
47 #import "keychain/ot/OctagonStateMachineHelpers.h"
48 #import "keychain/ot/OctagonCKKSPeerAdapter.h"
49 #import "keychain/ot/OctagonCheckTrustStateOperation.h"
50 #import "keychain/ot/OTStates.h"
51 #import "keychain/ot/OTFollowup.h"
52 #import "keychain/ot/OTAuthKitAdapter.h"
53 #import "keychain/ot/OTConstants.h"
54 #import "keychain/ot/OTOperationDependencies.h"
55 #import "keychain/ot/OTClique.h"
56 #import "keychain/ot/OTCuttlefishContext.h"
57 #import "keychain/ot/OTPrepareOperation.h"
58 #import "keychain/ot/OTSOSAdapter.h"
59 #import "keychain/ot/OTSOSUpgradeOperation.h"
60 #import "keychain/ot/OTUpdateTPHOperation.h"
61 #import "keychain/ot/OTEpochOperation.h"
62 #import "keychain/ot/OTClientVoucherOperation.h"
63 #import "keychain/ot/OTLeaveCliqueOperation.h"
64 #import "keychain/ot/OTRemovePeersOperation.h"
65 #import "keychain/ot/OTJoinWithVoucherOperation.h"
66 #import "keychain/ot/OTVouchWithBottleOperation.h"
67 #import "keychain/ot/OTVouchWithRecoveryKeyOperation.h"
68 #import "keychain/ot/OTEstablishOperation.h"
69 #import "keychain/ot/OTLocalCKKSResetOperation.h"
70 #import "keychain/ot/OTUpdateTrustedDeviceListOperation.h"
71 #import "keychain/ot/OTSOSUpdatePreapprovalsOperation.h"
72 #import "keychain/ot/OTResetOperation.h"
73 #import "keychain/ot/OTLocalCuttlefishReset.h"
74 #import "keychain/ot/OTSetRecoveryKeyOperation.h"
75 #import "keychain/ot/OTResetCKKSZonesLackingTLKsOperation.h"
76 #import "keychain/ot/OTUploadNewCKKSTLKsOperation.h"
77 #import "keychain/ot/OTCuttlefishAccountStateHolder.h"
78 #import "keychain/ot/ObjCImprovements.h"
79 #import "keychain/ot/proto/generated_source/OTAccountMetadataClassC.h"
80 #import "keychain/ot/OTTriggerEscrowUpdateOperation.h"
81 #import "keychain/ot/OTCheckHealthOperation.h"
82 #import "keychain/ot/OTEnsureOctagonKeyConsistency.h"
83 #import "keychain/ot/OTDetermineHSA2AccountStatusOperation.h"
84 #import "keychain/ckks/CKKSAccountStateTracker.h"
85 #import "keychain/ckks/CloudKitCategories.h"
86 #import "keychain/escrowrequest/EscrowRequestServer.h"
87
88 #if TARGET_OS_WATCH
89 #import "keychain/otpaird/OTPairingClient.h"
90 #endif /* TARGET_OS_WATCH */
91
92 #import "keychain/ckks/CKKSViewManager.h"
93 #import "keychain/ckks/CKKSKeychainView.h"
94
95 #import "utilities/SecTapToRadar.h"
96
97 #import "keychain/categories/NSError+UsefulConstructors.h"
98 #import <CoreCDP/CDPAccount.h>
99 #import <notify.h>
100
101 NSString* OTCuttlefishContextErrorDomain = @"otcuttlefish";
102 static dispatch_time_t OctagonStateTransitionDefaultTimeout = 10*NSEC_PER_SEC;
103
104 @class CKKSLockStateTracker;
105
106 @interface OTCuttlefishContext () <OTCuttlefishAccountStateHolderNotifier>
107 {
108 NSData* _vouchData;
109 NSData* _vouchSig;
110 NSString* _bottleID;
111 NSString* _bottleSalt;
112 NSData* _entropy;
113 NSArray<NSData*>* _preapprovedKeys;
114 NSString* _recoveryKey;
115 CuttlefishResetReason _resetReason;
116 BOOL _skipRateLimitingCheck;
117 }
118
119 @property CKKSLaunchSequence* launchSequence;
120 @property NSOperationQueue* operationQueue;
121 @property (nonatomic, strong) OTCuttlefishAccountStateHolder *accountMetadataStore;
122 @property OTFollowup *followupHandler;
123
124 @property (readonly) id<CKKSCloudKitAccountStateTrackingProvider, CKKSOctagonStatusMemoizer> accountStateTracker;
125 @property CKAccountInfo* cloudKitAccountInfo;
126 @property CKKSCondition *cloudKitAccountStateKnown;
127
128 @property BOOL getViewsSuccess;
129
130 @property CKKSNearFutureScheduler* suggestTLKUploadNotifier;
131
132 // Dependencies (for injection)
133 @property id<OTSOSAdapter> sosAdapter;
134 @property id<CKKSPeerProvider> octagonAdapter;
135 @property id<OTDeviceInformationAdapter> deviceAdapter;
136 @property (readonly) Class<OctagonAPSConnection> apsConnectionClass;
137 @property (readonly) Class<SecEscrowRequestable> escrowRequestClass;
138
139 @property (nonatomic) BOOL postedRepairCFU;
140 @property (nonatomic) BOOL postedEscrowRepairCFU;
141 @property (nonatomic) BOOL postedRecoveryKeyCFU;
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 _viewManager = viewManager;
169 _postedRepairCFU = NO;
170 _postedRecoveryKeyCFU = NO;
171 _postedEscrowRepairCFU = NO;
172
173 _initialBecomeUntrustedPosted = NO;
174
175 _apsConnectionClass = apsConnectionClass;
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 return self;
223 }
224
225 - (void)dealloc
226 {
227 // TODO: how to invalidate this?
228 //[self.cuttlefishXPCWrapper invalidate];
229 }
230
231 - (void)notifyTrustChanged:(OTAccountMetadataClassC_TrustState)trustState {
232
233 secnotice("octagon", "Changing trust status to: %@",
234 (trustState == OTAccountMetadataClassC_TrustState_TRUSTED) ? @"Trusted" : @"Untrusted");
235
236 /*
237 * We are posting the legacy SOS notification if we don't use SOS
238 * need to rework clients to use a new signal instead of SOS.
239 */
240 if (!OctagonPlatformSupportsSOS()) {
241 notify_post(kSOSCCCircleChangedNotification);
242 }
243
244 notify_post(OTTrustStatusChangeNotification);
245 }
246
247 - (void)accountStateUpdated:(OTAccountMetadataClassC*)newState from:(OTAccountMetadataClassC *)oldState
248 {
249 if (newState.icloudAccountState == OTAccountMetadataClassC_AccountState_ACCOUNT_AVAILABLE && oldState.icloudAccountState != OTAccountMetadataClassC_AccountState_ACCOUNT_AVAILABLE) {
250 [self.launchSequence addEvent:@"iCloudAccount"];
251 }
252
253 if (newState.trustState == OTAccountMetadataClassC_TrustState_TRUSTED && oldState.trustState != OTAccountMetadataClassC_TrustState_TRUSTED) {
254 [self.launchSequence addEvent:@"Trusted"];
255 }
256 if (newState.trustState != OTAccountMetadataClassC_TrustState_TRUSTED && oldState.trustState == OTAccountMetadataClassC_TrustState_TRUSTED) {
257 [self.launchSequence addEvent:@"Untrusted"];
258 [self notifyTrustChanged:newState.trustState];
259 }
260 }
261
262 - (NSString*)description
263 {
264 return [NSString stringWithFormat:@"<OTCuttlefishContext: %@, %@>", self.containerName, self.contextID];
265 }
266
267 - (void)machinesAdded:(NSArray<NSString*>*)machineIDs altDSID:(NSString*)altDSID
268 {
269 WEAKIFY(self);
270 NSError* metadataError = nil;
271 OTAccountMetadataClassC* accountMetadata = [self.accountMetadataStore loadOrCreateAccountMetadata:&metadataError];
272
273 if(!accountMetadata || metadataError) {
274 // TODO: collect a sysdiagnose here if the error is not "device is in class D"
275 secerror("octagon-authkit: Unable to load account metadata: %@", metadataError);
276 [self requestTrustedDeviceListRefresh];
277 return;
278 }
279
280 if(!altDSID || ![accountMetadata.altDSID isEqualToString:altDSID]) {
281 secnotice("octagon-authkit", "Machines-added push is for wrong altDSID (%@); current altDSID (%@)", altDSID, accountMetadata.altDSID);
282 return;
283 }
284
285 secnotice("octagon-authkit", "adding machines for altDSID(%@): %@", altDSID, machineIDs);
286
287 [self.cuttlefishXPCWrapper addAllowedMachineIDsWithContainer:self.containerName
288 context:self.contextID
289 machineIDs:machineIDs
290 reply:^(NSError* error) {
291 STRONGIFY(self);
292 if (error) {
293 secerror("octagon-authkit: addAllow errored: %@", error);
294 [self requestTrustedDeviceListRefresh];
295 } else {
296 secnotice("octagon-authkit", "addAllow succeeded");
297
298 OctagonPendingFlag *pendingFlag = [[OctagonPendingFlag alloc] initWithFlag:OctagonFlagCuttlefishNotification
299 conditions:OctagonPendingConditionsDeviceUnlocked];
300 [self.stateMachine handlePendingFlag:pendingFlag];
301 }
302 }];
303 }
304
305 - (void)machinesRemoved:(NSArray<NSString*>*)machineIDs altDSID:(NSString*)altDSID
306 {
307 WEAKIFY(self);
308
309 NSError* metadataError = nil;
310 OTAccountMetadataClassC* accountMetadata = [self.accountMetadataStore loadOrCreateAccountMetadata:&metadataError];
311
312 if(!accountMetadata || metadataError) {
313 // TODO: collect a sysdiagnose here if the error is not "device is in class D"
314 secerror("octagon-authkit: Unable to load account metadata: %@", metadataError);
315 [self requestTrustedDeviceListRefresh];
316 return;
317 }
318
319 if(!altDSID || ![accountMetadata.altDSID isEqualToString:altDSID]) {
320 secnotice("octagon-authkit", "Machines-removed push is for wrong altDSID (%@); current altDSID (%@)", altDSID, accountMetadata.altDSID);
321 return;
322 }
323
324 secnotice("octagon-authkit", "removing machines for altDSID(%@): %@", altDSID, machineIDs);
325
326 [self.cuttlefishXPCWrapper removeAllowedMachineIDsWithContainer:self.containerName
327 context:self.contextID
328 machineIDs:machineIDs
329 reply:^(NSError* _Nullable error) {
330 STRONGIFY(self);
331 if (error) {
332 secerror("octagon-authkit: removeAllow errored: %@", error);
333 } else {
334 secnotice("octagon-authkit", "removeAllow succeeded");
335 }
336
337 // We don't necessarily trust remove pushes; they could be delayed past when an add has occurred.
338 // Request that the full list be rechecked.
339 [self requestTrustedDeviceListRefresh];
340 }];
341 }
342
343 - (void)incompleteNotificationOfMachineIDListChange
344 {
345 secnotice("octagon", "incomplete machine ID list notification -- refreshing device list");
346 [self requestTrustedDeviceListRefresh];
347 }
348
349
350 - (void)cloudkitAccountStateChange:(CKAccountInfo* _Nullable)oldAccountInfo
351 to:(CKAccountInfo*)currentAccountInfo
352 {
353 dispatch_sync(self.queue, ^{
354 // We don't persist the CK account state; rather, we fetch it anew on every daemon launch.
355 // But, we also have to integrate it into our asynchronous state machine.
356 // So, record the current CK account value, and trigger state machine reprocessing.
357
358 secnotice("octagon", "Told of a new CK account status: %@", currentAccountInfo);
359 self.cloudKitAccountInfo = currentAccountInfo;
360 [self.stateMachine _onqueuePokeStateMachine];
361
362 // But, having the state machine perform the signout is confusing: it would need to make decisions based
363 // on things other than the current state. So, use the RPC mechanism to give it input.
364 // If we receive a sign-in before the sign-out rpc runs, the state machine will be sufficient to get back into
365 // the in-account state.
366
367 // Also let other clients now that we have CK account status
368 [self.cloudKitAccountStateKnown fulfill];
369 });
370
371 if(!(currentAccountInfo.accountStatus == CKAccountStatusAvailable)) {
372 secnotice("octagon", "Informed that the CK account is now unavailable: %@", currentAccountInfo);
373
374 // Add a state machine request to return to OctagonStateWaitingForCloudKitAccount
375 [self.stateMachine doSimpleStateMachineRPC:@"cloudkit-account-gone"
376 op:[OctagonStateTransitionOperation named:@"cloudkit-account-gone"
377 entering:OctagonStateWaitingForCloudKitAccount]
378 sourceStates:OctagonInAccountStates()
379 reply:^(NSError* error) {}];
380 }
381 }
382
383 - (BOOL)accountAvailable:(NSString*)altDSID error:(NSError**)error
384 {
385 secnotice("octagon", "Account available with altDSID: %@ %@", altDSID, self);
386
387 self.launchSequence.firstLaunch = true;
388
389 NSError* localError = nil;
390 [self.accountMetadataStore persistAccountChanges:^OTAccountMetadataClassC * _Nonnull(OTAccountMetadataClassC * _Nonnull metadata) {
391 // Do not set the account available bit here, since we need to check if it's HSA2. The initializing state should do that for us...
392 metadata.altDSID = altDSID;
393
394 return metadata;
395 } error:&localError];
396
397 if(localError) {
398 secerror("octagon: unable to persist new account availability: %@", localError);
399 }
400
401 [self.stateMachine handleFlag:OctagonFlagAccountIsAvailable];
402
403 if(localError) {
404 if(error) {
405 *error = localError;
406 }
407 return NO;
408 }
409 return YES;
410 }
411 - (void) moveToCheckTrustedState
412 {
413 CKKSResultOperation<OctagonStateTransitionOperationProtocol>* checkTrust
414 = [OctagonStateTransitionOperation named:[NSString stringWithFormat:@"%@", @"check-trust-state"]
415 entering:OctagonStateCheckTrustState];
416
417 NSSet* sourceStates = [NSSet setWithArray: @[OctagonStateUntrusted]];
418
419 OctagonStateTransitionRequest<OctagonStateTransitionOperation*>* request = [[OctagonStateTransitionRequest alloc] init:@"check-trust-state"
420 sourceStates:sourceStates
421 serialQueue:self.queue
422 timeout:OctagonStateTransitionDefaultTimeout
423 transitionOp:checkTrust];
424 [self.stateMachine handleExternalRequest:request];
425 }
426
427
428 - (BOOL)idmsTrustLevelChanged:(NSError**)error
429 {
430 [self.stateMachine handleFlag:OctagonFlagIDMSLevelChanged];
431 return YES;
432 }
433
434 - (BOOL)accountNoLongerAvailable:(NSError**)error
435 {
436 OctagonStateTransitionOperation* attemptOp = [OctagonStateTransitionOperation named:@"octagon-account-gone"
437 intending:OctagonStateNoAccountDoReset
438 errorState:OctagonStateNoAccountDoReset
439 withBlockTakingSelf:^(OctagonStateTransitionOperation * _Nonnull op) {
440 __block NSError* localError = nil;
441
442 secnotice("octagon", "Account now unavailable: %@", self);
443 [self.accountMetadataStore persistAccountChanges:^OTAccountMetadataClassC *(OTAccountMetadataClassC * metadata) {
444 metadata.icloudAccountState = OTAccountMetadataClassC_AccountState_NO_ACCOUNT;
445 metadata.altDSID = nil;
446 metadata.trustState = OTAccountMetadataClassC_TrustState_UNKNOWN;
447
448 return metadata;
449 } error:&localError];
450
451 if(localError) {
452 secerror("octagon: unable to persist new account availability: %@", localError);
453 }
454
455 [self.accountStateTracker setHSA2iCloudAccountStatus:CKKSAccountStatusNoAccount];
456
457 // Bring CKKS down, too
458 for (id key in self.viewManager.views) {
459 CKKSKeychainView* view = self.viewManager.views[key];
460 secnotice("octagon-ckks", "Informing %@ of new untrusted status (due to account disappearance)", view);
461 [view endTrustedOperation];
462 }
463
464 op.error = localError;
465 }];
466
467 // Signout works from literally any state. Goodbye, account!
468 NSSet* sourceStates = [NSSet setWithArray: OctagonStateMap().allKeys];
469 OctagonStateTransitionRequest<OctagonStateTransitionOperation*>* request = [[OctagonStateTransitionRequest alloc] init:@"account-not-available"
470 sourceStates:sourceStates
471 serialQueue:self.queue
472 timeout:OctagonStateTransitionDefaultTimeout
473 transitionOp:attemptOp];
474 [self.stateMachine handleExternalRequest:request];
475
476 return YES;
477 }
478
479 - (void)resetOctagonStateMachine
480 {
481 OctagonStateTransitionOperation* op = [OctagonStateTransitionOperation named:@"resetting-state-machine"
482 entering:OctagonStateInitializing];
483 NSMutableSet* sourceStates = [NSMutableSet setWithArray: OctagonStateMap().allKeys];
484
485 OctagonStateTransitionRequest<OctagonStateTransitionOperation*>* request = [[OctagonStateTransitionRequest alloc] init:@"resetting-state-machine"
486 sourceStates:sourceStates
487 serialQueue:self.queue
488 timeout:OctagonStateTransitionDefaultTimeout
489 transitionOp:op];
490
491 [self.stateMachine handleExternalRequest:request];
492
493 }
494
495 - (void)localReset:(nonnull void (^)(NSError * _Nullable))reply
496 {
497 OTLocalResetOperation* pendingOp = [[OTLocalResetOperation alloc] init:self.containerName
498 contextID:self.contextID
499 intendedState:OctagonStateBecomeUntrusted
500 errorState:OctagonStateError
501 cuttlefishXPCWrapper:self.cuttlefishXPCWrapper];
502
503 NSMutableSet* sourceStates = [NSMutableSet setWithArray: OctagonStateMap().allKeys];
504 [self.stateMachine doSimpleStateMachineRPC:@"local-reset" op:pendingOp sourceStates:sourceStates reply:reply];
505 }
506
507 - (NSDictionary*)establishStatePathDictionary
508 {
509 return @{
510 OctagonStateReEnactDeviceList: @{
511 OctagonStateReEnactPrepare: @{
512 OctagonStateReEnactReadyToEstablish: @{
513 OctagonStateEscrowTriggerUpdate: @{
514 OctagonStateBecomeReady: @{
515 OctagonStateReady: [OctagonStateTransitionPathStep success],
516 },
517 },
518
519 // Error handling extra states:
520 OctagonStateEstablishCKKSReset: @{
521 OctagonStateEstablishAfterCKKSReset: @{
522 OctagonStateEscrowTriggerUpdate: @{
523 OctagonStateBecomeReady: @{
524 OctagonStateReady: [OctagonStateTransitionPathStep success],
525 },
526 },
527 },
528 },
529 },
530 },
531 },
532 };
533 }
534
535 - (void)rpcEstablish:(nonnull NSString *)altDSID
536 reply:(nonnull void (^)(NSError * _Nullable))reply
537 {
538 // The reset flow can split into an error-handling path halfway through; this is okay
539 OctagonStateTransitionPath* path = [OctagonStateTransitionPath pathFromDictionary:[self establishStatePathDictionary]];
540
541 [self.stateMachine doWatchedStateMachineRPC:@"establish"
542 sourceStates:OctagonInAccountStates()
543 path:path
544 reply:reply];
545 }
546
547 - (void)rpcResetAndEstablish:(CuttlefishResetReason)resetReason reply:(nonnull void (^)(NSError * _Nullable))reply
548 {
549 _resetReason = resetReason;
550
551 // The reset flow can split into an error-handling path halfway through; this is okay
552 OctagonStateTransitionPath* path = [OctagonStateTransitionPath pathFromDictionary: @{
553 OctagonStateResetBecomeUntrusted: @{
554 OctagonStateResetAndEstablish: @{
555 OctagonStateResetAnyMissingTLKCKKSViews: [self establishStatePathDictionary]
556 },
557 },
558 }];
559
560 // Now, take the state machine from any in-account state to the beginning of the reset flow.
561 [self.stateMachine doWatchedStateMachineRPC:@"rpc-reset-and-establish"
562 sourceStates:OctagonInAccountStates()
563 path:path
564 reply:reply];
565 }
566
567 - (void)rpcLeaveClique:(nonnull void (^)(NSError * _Nullable))reply
568 {
569 OTLeaveCliqueOperation* op = [[OTLeaveCliqueOperation alloc] initWithDependencies:self.operationDependencies
570 intendedState:OctagonStateBecomeUntrusted
571 errorState:OctagonStateError];
572
573 NSSet* sourceStates = [NSSet setWithObject: OctagonStateReady];
574 [self.stateMachine doSimpleStateMachineRPC:@"leave-clique" op:op sourceStates:sourceStates reply:reply];
575 }
576
577 - (void)rpcRemoveFriendsInClique:(NSArray<NSString*>*)peerIDs
578 reply:(void (^)(NSError * _Nullable))reply
579 {
580 OTRemovePeersOperation* op = [[OTRemovePeersOperation alloc] initWithDependencies:self.operationDependencies
581 intendedState:OctagonStateBecomeReady
582 errorState:OctagonStateBecomeReady
583 peerIDs:peerIDs];
584
585 NSSet* sourceStates = [NSSet setWithObject: OctagonStateReady];
586 [self.stateMachine doSimpleStateMachineRPC:@"remove-friends" op:op sourceStates:sourceStates reply:reply];
587 }
588
589 - (OTDeviceInformation*)prepareInformation
590 {
591 NSError* error = nil;
592 NSString* machineID = [self.authKitAdapter machineID:&error];
593
594 if(!machineID || error) {
595 secerror("octagon: Unable to fetch machine ID; expect signin to fail: %@", error);
596 }
597
598 return [[OTDeviceInformation alloc] initForContainerName:self.containerName
599 contextID:self.contextID
600 epoch:0
601 machineID:machineID
602 modelID:self.deviceAdapter.modelID
603 deviceName:self.deviceAdapter.deviceName
604 serialNumber:self.deviceAdapter.serialNumber
605 osVersion:self.deviceAdapter.osVersion];
606 }
607
608 - (OTOperationDependencies*)operationDependencies
609 {
610 return [[OTOperationDependencies alloc] initForContainer:self.containerName
611 contextID:self.contextID
612 stateHolder:self.accountMetadataStore
613 flagHandler:self.stateMachine
614 sosAdapter:self.sosAdapter
615 octagonAdapter:self.octagonAdapter
616 authKitAdapter:self.authKitAdapter
617 deviceInfoAdapter:self.deviceAdapter
618 viewManager:self.viewManager
619 lockStateTracker:self.lockStateTracker
620 cuttlefishXPCWrapper:self.cuttlefishXPCWrapper
621 escrowRequestClass:self.escrowRequestClass];
622 }
623
624 - (void)startOctagonStateMachine
625 {
626 [self.stateMachine startOperation];
627 }
628
629 - (void)handlePairingRestart:(OTJoiningConfiguration*)config
630 {
631 if(self.pairingUUID == nil){
632 secnotice("octagon-pairing", "received new pairing UUID (%@)", config.pairingUUID);
633 self.pairingUUID = config.pairingUUID;
634 }
635
636 if(![self.pairingUUID isEqualToString:config.pairingUUID]){
637 secnotice("octagon-pairing", "current pairing UUID (%@) does not match config UUID (%@)", self.pairingUUID, config.pairingUUID);
638
639 dispatch_semaphore_t sema = dispatch_semaphore_create(0);
640 [self localReset:^(NSError * _Nullable localResetError) {
641 if(localResetError) {
642 secerror("localReset returned an error: %@", localResetError);
643 }else{
644 secnotice("octagon", "localReset succeeded");
645 self.pairingUUID = config.pairingUUID;
646 }
647 dispatch_semaphore_signal(sema);
648 }];
649 if (0 != dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 10))) {
650 secerror("octagon: Timed out waiting for local reset to complete");
651 }
652 }
653 }
654
655 #pragma mark --- State Machine Transitions
656
657 - (CKKSResultOperation<OctagonStateTransitionOperationProtocol>* _Nullable)_onqueueNextStateMachineTransition:(OctagonState*)currentState
658 flags:(nonnull OctagonFlags *)flags
659 pendingFlags:(nonnull id<OctagonStateOnqueuePendingFlagHandler>)pendingFlagHandler
660 {
661 dispatch_assert_queue(self.queue);
662
663 WEAKIFY(self);
664
665 [self.launchSequence addEvent:currentState];
666
667 // If We're initializing, or there was some recent update to the account state,
668 // attempt to see what state we should enter.
669 if([currentState isEqualToString: OctagonStateInitializing]) {
670 return [self initializingOperation];
671 }
672
673 if([currentState isEqualToString:OctagonStateWaitForHSA2]) {
674 if([flags _onqueueContains:OctagonFlagIDMSLevelChanged]) {
675 [flags _onqueueRemoveFlag:OctagonFlagIDMSLevelChanged];
676 return [OctagonStateTransitionOperation named:@"hsa2-check"
677 entering:OctagonStateDetermineiCloudAccountState];
678 }
679
680 secnotice("octagon", "Waiting for an HSA2 account");
681 return nil;
682 }
683
684 if([currentState isEqualToString:OctagonStateWaitingForCloudKitAccount]) {
685 // Here, integrate the memoized CK account state into our state machine
686 if(self.cloudKitAccountInfo && self.cloudKitAccountInfo.accountStatus == CKAccountStatusAvailable) {
687 secnotice("octagon", "CloudKit reports an account is available!");
688 return [OctagonStateTransitionOperation named:@"ck-available"
689 entering:OctagonStateCloudKitNewlyAvailable];
690 } else {
691 secnotice("octagon", "Waiting for a CloudKit account; current state is %@", self.cloudKitAccountInfo ?: @"uninitialized");
692 return nil;
693 }
694 }
695
696 if([currentState isEqualToString:OctagonStateCloudKitNewlyAvailable]) {
697 return [self cloudKitAccountNewlyAvailableOperation];
698 }
699
700 if([currentState isEqualToString:OctagonStateCheckTrustState]) {
701 return [[OctagonCheckTrustStateOperation alloc] initWithDependencies:self.operationDependencies
702 intendedState:OctagonStateBecomeUntrusted
703 errorState:OctagonStateBecomeUntrusted];
704 }
705 #pragma mark --- Octagon Health Check States
706 if([currentState isEqualToString:OctagonStateHSA2HealthCheck]) {
707 return [[OTDetermineHSA2AccountStatusOperation alloc] initWithDependencies:self.operationDependencies
708 stateIfHSA2:OctagonStateSecurityTrustCheck
709 stateIfNotHSA2:OctagonStateWaitForHSA2
710 stateIfNoAccount:OctagonStateNoAccount
711 errorState:OctagonStateError];
712 }
713
714 if([currentState isEqualToString:OctagonStateSecurityTrustCheck]) {
715 return [self evaluateSecdOctagonTrust];
716 }
717
718 if([currentState isEqualToString:OctagonStateTPHTrustCheck]) {
719 return [self evaluateTPHOctagonTrust];
720 }
721
722 if([currentState isEqualToString:OctagonStateCuttlefishTrustCheck]) {
723 return [self cuttlefishTrustEvaluation];
724 }
725
726 if ([currentState isEqualToString:OctagonStatePostRepairCFU]) {
727 return [self postRepairCFUAndBecomeUntrusted];
728 }
729
730 if ([currentState isEqualToString:OctagonStateHealthCheckReset]) {
731 // A small violation of state machines...
732 _resetReason = CuttlefishResetReasonHealthCheck;
733 return [OctagonStateTransitionOperation named:@"begin-reset"
734 entering:OctagonStateResetBecomeUntrusted];
735 }
736
737 #pragma mark --- Watch Pairing States
738
739 #if TARGET_OS_WATCH
740 if([currentState isEqualToString:OctagonStateStartCompanionPairing]) {
741 return [self startCompanionPairingOperation];
742 }
743 #endif /* TARGET_OS_WATCH */
744
745 if([currentState isEqualToString:OctagonStateBecomeUntrusted]) {
746 return [self becomeUntrustedOperation:OctagonStateUntrusted];
747 }
748
749 if([currentState isEqualToString:OctagonStateBecomeReady]) {
750 return [self becomeReadyOperation];
751 }
752
753 if([currentState isEqualToString:OctagonStateNoAccount]) {
754 // We only want to move out of untrusted if something useful has happened!
755 if([flags _onqueueContains:OctagonFlagAccountIsAvailable]) {
756 [flags _onqueueRemoveFlag:OctagonFlagAccountIsAvailable];
757 secnotice("octagon", "Account is available! Attempting initializing op!");
758 return [OctagonStateTransitionOperation named:@"account-probably-present"
759 entering:OctagonStateInitializing];
760 }
761 }
762
763 if([currentState isEqualToString:OctagonStateUntrusted]) {
764 // We only want to move out of untrusted if something useful has happened!
765 if([flags _onqueueContains:OctagonFlagEgoPeerPreapproved]) {
766 [flags _onqueueRemoveFlag:OctagonFlagEgoPeerPreapproved];
767 if(self.sosAdapter.sosEnabled) {
768 secnotice("octagon", "Preapproved flag is high. Attempt SOS upgrade again!");
769 return [OctagonStateTransitionOperation named:@"ck-available"
770 entering:OctagonStateAttemptSOSUpgrade];
771
772 } else {
773 secnotice("octagon", "We are untrusted, but it seems someone preapproves us now. Unfortunately, this platform doesn't support SOS.");
774 }
775 }
776
777 if([flags _onqueueContains:OctagonFlagAttemptSOSUpgrade]) {
778 [flags _onqueueRemoveFlag:OctagonFlagAttemptSOSUpgrade];
779 if(self.sosAdapter.sosEnabled) {
780 secnotice("octagon", "Attempt SOS upgrade again!");
781 return [OctagonStateTransitionOperation named:@"attempt-sos-upgrade"
782 entering:OctagonStateAttemptSOSUpgrade];
783
784 } else {
785 secnotice("octagon", "We are untrusted, but this platform doesn't support SOS.");
786 }
787 }
788
789 if([flags _onqueueContains:OctagonFlagCuttlefishNotification]) {
790 [flags _onqueueRemoveFlag:OctagonFlagCuttlefishNotification];
791 secnotice("octagon", "Updating TPH (while untrusted) due to push");
792 return [OctagonStateTransitionOperation named:@"untrusted-update"
793 entering:OctagonStateUntrustedUpdated];
794 }
795
796 // We're untrusted; no need for the IDMS level flag anymore
797 if([flags _onqueueContains:OctagonFlagIDMSLevelChanged]) {
798 [flags _onqueueRemoveFlag:OctagonFlagIDMSLevelChanged];
799 }
800 }
801
802 if([currentState isEqualToString:OctagonStateUntrustedUpdated]) {
803 return [[OTUpdateTPHOperation alloc] initWithDependencies:self.operationDependencies
804 intendedState:OctagonStateUntrusted
805 errorState:OctagonStateError
806 retryFlag:OctagonFlagCuttlefishNotification];
807 }
808
809 if([currentState isEqualToString:OctagonStateDetermineiCloudAccountState]) {
810 secnotice("octagon", "Determine iCloud account status");
811
812 // TODO replace with OTDetermineHSA2AccountStatusOperation in <rdar://problem/54094162> Octagon: ensure Octagon operations can't occur on SA accounts
813 return [OctagonStateTransitionOperation named:@"octagon-determine-icloud-state"
814 intending:OctagonStateNoAccount
815 errorState:OctagonStateError
816 withBlockTakingSelf:^(OctagonStateTransitionOperation * _Nonnull op) {
817 STRONGIFY(self);
818
819 NSError *authKitError = nil;
820 NSString* primaryAccountAltDSID = [self.authKitAdapter primaryiCloudAccountAltDSID:&authKitError];
821
822 dispatch_sync(self.queue, ^{
823 NSError* error = nil;
824
825 if(primaryAccountAltDSID != nil) {
826 secnotice("octagon", "iCloud account is present; checking HSA2 status");
827
828 bool hsa2 = [self.authKitAdapter accountIsHSA2ByAltDSID:primaryAccountAltDSID];
829 secnotice("octagon", "HSA2 is %@", hsa2 ? @"enabled" : @"disabled");
830
831 [self.accountMetadataStore _onqueuePersistAccountChanges:^OTAccountMetadataClassC *(OTAccountMetadataClassC * metadata) {
832 if(hsa2) {
833 metadata.icloudAccountState = OTAccountMetadataClassC_AccountState_ACCOUNT_AVAILABLE;
834 } else {
835 metadata.icloudAccountState = OTAccountMetadataClassC_AccountState_NO_ACCOUNT;
836 }
837 metadata.altDSID = primaryAccountAltDSID;
838 return metadata;
839 } error:&error];
840
841 // If there's an HSA2 account, return to 'initializing' here, as we want to centralize decisions on what to do next
842 if(hsa2) {
843 op.nextState = OctagonStateInitializing;
844 } else {
845 [self.accountStateTracker setHSA2iCloudAccountStatus:CKKSAccountStatusNoAccount];
846 op.nextState = OctagonStateWaitForHSA2;
847 }
848
849 } else {
850 secnotice("octagon", "iCloud account is not present: %@", authKitError);
851
852 [self.accountMetadataStore _onqueuePersistAccountChanges:^OTAccountMetadataClassC *(OTAccountMetadataClassC * metadata) {
853 metadata.icloudAccountState = OTAccountMetadataClassC_AccountState_NO_ACCOUNT;
854 metadata.altDSID = nil;
855 return metadata;
856 } error:&error];
857
858 op.nextState = OctagonStateNoAccount;
859 }
860
861 if(error) {
862 secerror("octagon: unable to save new account state: %@", error);
863 }
864 });
865 }];
866 }
867
868 if([currentState isEqualToString:OctagonStateNoAccountDoReset]) {
869 secnotice("octagon", "Attempting local-reset as part of signout");
870 return [[OTLocalResetOperation alloc] init:self.containerName
871 contextID:self.contextID
872 intendedState:OctagonStateNoAccount
873 errorState:OctagonStateNoAccount
874 cuttlefishXPCWrapper:self.cuttlefishXPCWrapper];
875 }
876
877 if([currentState isEqualToString:OctagonStateEnsureConsistency]) {
878 secnotice("octagon", "Ensuring consistency of things that might've changed");
879 if(self.sosAdapter.sosEnabled) {
880 return [[OTEnsureOctagonKeyConsistency alloc] initWithDependencies:self.operationDependencies
881 intendedState:OctagonStateEnsureUpdatePreapprovals
882 errorState:OctagonStateBecomeReady];
883 }
884
885 // Add further consistency checks here.
886 return [OctagonStateTransitionOperation named:@"no-consistency-checks"
887 entering:OctagonStateBecomeReady];
888 }
889
890 if([currentState isEqualToString:OctagonStateEnsureUpdatePreapprovals]) {
891 secnotice("octagon", "SOS is enabled; ensuring preapprovals are correct");
892 return [[OTSOSUpdatePreapprovalsOperation alloc] initWithDependencies:self.operationDependencies
893 intendedState:OctagonStateBecomeReady
894 sosNotPresentState:OctagonStateBecomeReady
895 errorState:OctagonStateBecomeReady];
896 }
897
898 if([currentState isEqualToString:OctagonStateAttemptSOSUpgrade] && OctagonPerformSOSUpgrade()) {
899 secnotice("octagon", "Investigating SOS status");
900 return [[OTSOSUpgradeOperation alloc] initWithDependencies:self.operationDependencies
901 intendedState:OctagonStateBecomeReady
902 ckksConflictState:OctagonStateSOSUpgradeCKKSReset
903 errorState:OctagonStateBecomeUntrusted
904 deviceInfo:self.prepareInformation];
905
906 } else if([currentState isEqualToString:OctagonStateSOSUpgradeCKKSReset]) {
907 return [[OTLocalCKKSResetOperation alloc] initWithDependencies:self.operationDependencies
908 intendedState:OctagonStateSOSUpgradeAfterCKKSReset
909 errorState:OctagonStateBecomeUntrusted];
910
911 } else if([currentState isEqualToString:OctagonStateSOSUpgradeAfterCKKSReset]) {
912 return [[OTSOSUpgradeOperation alloc] initWithDependencies:self.operationDependencies
913 intendedState:OctagonStateBecomeReady
914 ckksConflictState:OctagonStateBecomeUntrusted
915 errorState:OctagonStateBecomeUntrusted
916 deviceInfo:self.prepareInformation];
917
918
919 } else if([currentState isEqualToString:OctagonStateCreateIdentityForRecoveryKey]) {
920 OTPrepareOperation* op = [[OTPrepareOperation alloc] initWithDependencies:self.operationDependencies
921 intendedState:OctagonStateVouchWithRecoveryKey
922 errorState:OctagonStateBecomeUntrusted
923 deviceInfo:[self prepareInformation]
924 epoch:1];
925 return op;
926
927 } else if([currentState isEqualToString:OctagonStateInitiatorCreateIdentity]) {
928 OTPrepareOperation* op = [[OTPrepareOperation alloc] initWithDependencies:self.operationDependencies
929 intendedState:OctagonStateInitiatorVouchWithBottle
930 errorState:OctagonStateBecomeUntrusted
931 deviceInfo:[self prepareInformation]
932 epoch:1];
933 return op;
934
935 } else if([currentState isEqualToString:OctagonStateInitiatorVouchWithBottle]) {
936 OTVouchWithBottleOperation* pendingOp = [[OTVouchWithBottleOperation alloc] initWithDependencies:self.operationDependencies
937 intendedState:OctagonStateInitiatorUpdateDeviceList
938 errorState:OctagonStateBecomeUntrusted
939 bottleID:_bottleID
940 entropy:_entropy
941 bottleSalt:_bottleSalt];
942
943 CKKSResultOperation* callback = [CKKSResultOperation named:@"vouchWithBottle-callback"
944 withBlock:^{
945 secnotice("otrpc", "Returning a vouch with bottle call: %@, %@ %@", pendingOp.voucher, pendingOp.voucherSig, pendingOp.error);
946 self->_vouchSig = pendingOp.voucherSig;
947 self->_vouchData = pendingOp.voucher;
948 }];
949 [callback addDependency:pendingOp];
950 [self.operationQueue addOperation: callback];
951
952 return pendingOp;
953
954 } else if([currentState isEqualToString:OctagonStateVouchWithRecoveryKey]) {
955 OTVouchWithRecoveryKeyOperation* pendingOp = [[OTVouchWithRecoveryKeyOperation alloc] initWithDependencies:self.operationDependencies
956 intendedState:OctagonStateInitiatorUpdateDeviceList
957 errorState:OctagonStateBecomeUntrusted
958 recoveryKey:_recoveryKey];
959
960 CKKSResultOperation* callback = [CKKSResultOperation named:@"vouchWithRecoveryKey-callback"
961 withBlock:^{
962 secnotice("otrpc", "Returning a vouch with recovery key call: %@, %@ %@", pendingOp.voucher, pendingOp.voucherSig, pendingOp.error);
963 self->_vouchSig = pendingOp.voucherSig;
964 self->_vouchData = pendingOp.voucher;
965 }];
966 [callback addDependency:pendingOp];
967 [self.operationQueue addOperation: callback];
968
969 return pendingOp;
970
971 } else if([currentState isEqualToString:OctagonStateInitiatorUpdateDeviceList]) {
972 // As part of the 'initiate' flow, we need to update the trusted device list-you're probably on it already
973 OTUpdateTrustedDeviceListOperation* op = [[OTUpdateTrustedDeviceListOperation alloc] initWithDependencies:self.operationDependencies
974 intendedState:OctagonStateInitiatorJoin
975 listUpdatesState:OctagonStateInitiatorJoin
976 errorState:OctagonStateBecomeUntrusted
977 retryFlag:nil];
978 return op;
979
980 } else if ([currentState isEqualToString:OctagonStateInitiatorJoin]){
981 OTJoinWithVoucherOperation* op = [[OTJoinWithVoucherOperation alloc] initWithDependencies:self.operationDependencies
982 intendedState:OctagonStateBecomeReady
983 ckksConflictState:OctagonStateInitiatorJoinCKKSReset
984 errorState:OctagonStateBecomeUntrusted
985 voucherData:_vouchData
986 voucherSig:_vouchSig
987 preapprovedKeys:_preapprovedKeys];
988 return op;
989
990 } else if([currentState isEqualToString:OctagonStateInitiatorJoinCKKSReset]) {
991 return [[OTLocalCKKSResetOperation alloc] initWithDependencies:self.operationDependencies
992 intendedState:OctagonStateInitiatorJoinAfterCKKSReset
993 errorState:OctagonStateBecomeUntrusted];
994
995 } else if ([currentState isEqualToString:OctagonStateInitiatorJoinAfterCKKSReset]){
996 return [[OTJoinWithVoucherOperation alloc] initWithDependencies:self.operationDependencies
997 intendedState:OctagonStateBecomeReady
998 ckksConflictState:OctagonStateBecomeUntrusted
999 errorState:OctagonStateBecomeUntrusted
1000 voucherData:_vouchData
1001 voucherSig:_vouchSig
1002 preapprovedKeys:_preapprovedKeys];
1003
1004 } else if([currentState isEqualToString:OctagonStateResetBecomeUntrusted]) {
1005 return [self becomeUntrustedOperation:OctagonStateResetAndEstablish];
1006
1007 } else if([currentState isEqualToString:OctagonStateResetAndEstablish]) {
1008 return [[OTResetOperation alloc] init:self.containerName
1009 contextID:self.contextID
1010 reason:_resetReason
1011 intendedState:OctagonStateResetAnyMissingTLKCKKSViews
1012 errorState:OctagonStateError
1013 cuttlefishXPCWrapper:self.cuttlefishXPCWrapper];
1014
1015 } else if([currentState isEqualToString:OctagonStateResetAnyMissingTLKCKKSViews]) {
1016 return [[OTResetCKKSZonesLackingTLKsOperation alloc] initWithDependencies:self.operationDependencies
1017 intendedState:OctagonStateReEnactDeviceList
1018 errorState:OctagonStateError];
1019
1020 } else if([currentState isEqualToString:OctagonStateReEnactDeviceList]) {
1021 return [[OTUpdateTrustedDeviceListOperation alloc] initWithDependencies:self.operationDependencies
1022 intendedState:OctagonStateReEnactPrepare
1023 listUpdatesState:OctagonStateReEnactPrepare
1024 errorState:OctagonStateError
1025 retryFlag:nil];
1026
1027 } else if([currentState isEqualToString:OctagonStateReEnactPrepare]) {
1028 // Note: Resetting the account returns epoch to 0.
1029 return [[OTPrepareOperation alloc] initWithDependencies:self.operationDependencies
1030 intendedState:OctagonStateReEnactReadyToEstablish
1031 errorState:OctagonStateError
1032 deviceInfo:[self prepareInformation]
1033 epoch:0];
1034
1035 } else if([currentState isEqualToString:OctagonStateReEnactReadyToEstablish]) {
1036 return [[OTEstablishOperation alloc] initWithDependencies:self.operationDependencies
1037 intendedState:OctagonStateEscrowTriggerUpdate
1038 ckksConflictState:OctagonStateEstablishCKKSReset
1039 errorState:OctagonStateBecomeUntrusted];
1040
1041 } else if([currentState isEqualToString:OctagonStateEstablishCKKSReset]) {
1042 return [[OTLocalCKKSResetOperation alloc] initWithDependencies:self.operationDependencies
1043 intendedState:OctagonStateEstablishAfterCKKSReset
1044 errorState:OctagonStateBecomeUntrusted];
1045
1046 } else if([currentState isEqualToString:OctagonStateEstablishAfterCKKSReset]) {
1047 // If CKKS fails again, just go to "become untrusted"
1048 return [[OTEstablishOperation alloc] initWithDependencies:self.operationDependencies
1049 intendedState:OctagonStateEscrowTriggerUpdate
1050 ckksConflictState:OctagonStateBecomeUntrusted
1051 errorState:OctagonStateBecomeUntrusted];
1052
1053 } else if ([currentState isEqualToString:OctagonStateEscrowTriggerUpdate]){
1054
1055 return [[OTTriggerEscrowUpdateOperation alloc] initWithDependencies:self.operationDependencies
1056 intendedState:OctagonStateBecomeReady
1057 errorState:OctagonStateError];
1058
1059 } else if([currentState isEqualToString: OctagonStateWaitForUnlock]) {
1060 if([flags _onqueueContains:OctagonFlagUnlocked]) {
1061 [flags _onqueueRemoveFlag:OctagonFlagUnlocked];
1062 return [OctagonStateTransitionOperation named:[NSString stringWithFormat:@"%@", @"initializing-after-unlock"]
1063 entering:OctagonStateInitializing];
1064 }
1065
1066 secnotice("octagon", "Requested to enter wait for unlock");
1067 [pendingFlagHandler _onqueueHandlePendingFlag:[[OctagonPendingFlag alloc] initWithFlag:OctagonFlagUnlocked
1068 conditions:OctagonPendingConditionsDeviceUnlocked]];
1069 return nil;
1070
1071 } else if([currentState isEqualToString: OctagonStateUpdateSOSPreapprovals]) {
1072 secnotice("octagon", "Updating SOS preapprovals");
1073
1074 // TODO: if this update fails, we need to redo it later.
1075 return [[OTSOSUpdatePreapprovalsOperation alloc] initWithDependencies:self.operationDependencies
1076 intendedState:OctagonStateReady
1077 sosNotPresentState:OctagonStateReady
1078 errorState:OctagonStateReady];
1079
1080 } else if([currentState isEqualToString:OctagonStateAssistCKKSTLKUpload]) {
1081 return [[OTUploadNewCKKSTLKsOperation alloc] initWithDependencies:self.operationDependencies
1082 intendedState:OctagonStateReady
1083 ckksConflictState:OctagonStateAssistCKKSTLKUploadCKKSReset
1084 errorState:OctagonStateReady];
1085
1086 } else if([currentState isEqualToString:OctagonStateAssistCKKSTLKUploadCKKSReset]) {
1087 return [[OTLocalCKKSResetOperation alloc] initWithDependencies:self.operationDependencies
1088 intendedState:OctagonStateAssistCKKSTLKUploadAfterCKKSReset
1089 errorState:OctagonStateBecomeReady];
1090
1091 } else if([currentState isEqualToString:OctagonStateAssistCKKSTLKUploadAfterCKKSReset]) {
1092 // If CKKS fails again, just go to 'ready'
1093 return [[OTUploadNewCKKSTLKsOperation alloc] initWithDependencies:self.operationDependencies
1094 intendedState:OctagonStateReady
1095 ckksConflictState:OctagonStateReady
1096 errorState:OctagonStateReady];
1097
1098 } else if([currentState isEqualToString:OctagonStateReady]) {
1099 if([flags _onqueueContains:OctagonFlagCKKSRequestsTLKUpload]) {
1100 [flags _onqueueRemoveFlag:OctagonFlagCKKSRequestsTLKUpload];
1101 return [OctagonStateTransitionOperation named:@"ckks-assist"
1102 entering:OctagonStateAssistCKKSTLKUpload];
1103 }
1104
1105 if([flags _onqueueContains:OctagonFlagCuttlefishNotification]) {
1106 [flags _onqueueRemoveFlag:OctagonFlagCuttlefishNotification];
1107 secnotice("octagon", "Updating TPH (while ready) due to push");
1108 return [OctagonStateTransitionOperation named:@"octagon-update"
1109 entering:OctagonStateReadyUpdated];
1110 }
1111
1112 if([flags _onqueueContains:OctagonFlagFetchAuthKitMachineIDList]) {
1113 [flags _onqueueRemoveFlag:OctagonFlagFetchAuthKitMachineIDList];
1114
1115 secnotice("octagon", "Received an suggestion to update the machine ID list (while ready); updating trusted device list");
1116
1117 // If the cached list changes due to this fetch, go into 'updated'. Otherwise, back into ready with you!
1118 return [[OTUpdateTrustedDeviceListOperation alloc] initWithDependencies:self.operationDependencies
1119 intendedState:OctagonStateReady
1120 listUpdatesState:OctagonStateReadyUpdated
1121 errorState:OctagonStateReady
1122 retryFlag:OctagonFlagFetchAuthKitMachineIDList];
1123 }
1124
1125 if([flags _onqueueContains:OctagonFlagAttemptSOSUpdatePreapprovals]) {
1126 [flags _onqueueRemoveFlag:OctagonFlagAttemptSOSUpdatePreapprovals];
1127 if(self.sosAdapter.sosEnabled) {
1128 secnotice("octagon", "Attempt SOS Update preapprovals again!");
1129 return [OctagonStateTransitionOperation named:@"attempt-sos-update-preapproval"
1130 entering:OctagonStateUpdateSOSPreapprovals];
1131 } else {
1132 secnotice("octagon", "We are untrusted, but this platform doesn't support SOS.");
1133 }
1134 }
1135
1136 if([flags _onqueueContains:OctagonFlagAttemptSOSConsistency]) {
1137 [flags _onqueueRemoveFlag:OctagonFlagAttemptSOSConsistency];
1138 if(self.sosAdapter.sosEnabled) {
1139 secnotice("octagon", "Attempting SOS consistency checks");
1140 return [OctagonStateTransitionOperation named:@"attempt-sos-update-preapproval"
1141 entering:OctagonStateEnsureConsistency];
1142 } else {
1143 secnotice("octagon", "Someone would like us to check SOS consistency, but this platform doesn't support SOS.");
1144 }
1145 }
1146
1147 if([flags _onqueueContains:OctagonFlagAccountIsAvailable]) {
1148 // We're in ready--we already know the account is available
1149 secnotice("octagon", "Removing 'account is available' flag");
1150 [flags _onqueueRemoveFlag:OctagonFlagAccountIsAvailable];
1151 }
1152
1153 // We're ready; no need for the IDMS level flag anymore
1154 if([flags _onqueueContains:OctagonFlagIDMSLevelChanged]) {
1155 secnotice("octagon", "Removing 'IDMS level changed' flag");
1156 [flags _onqueueRemoveFlag:OctagonFlagIDMSLevelChanged];
1157 }
1158
1159 secnotice("octagon", "Entering state ready");
1160 [[CKKSAnalytics logger] setDateProperty:[NSDate date] forKey:OctagonAnalyticsLastKeystateReady];
1161 [self.launchSequence launch];
1162 return nil;
1163 } else if([currentState isEqualToString:OctagonStateReadyUpdated]) {
1164 return [[OTUpdateTPHOperation alloc] initWithDependencies:self.operationDependencies
1165 intendedState:OctagonStateReady
1166 errorState:OctagonStateError
1167 retryFlag:OctagonFlagCuttlefishNotification];
1168
1169 } else if ([currentState isEqualToString:OctagonStateError]) {
1170 }
1171
1172 return nil;
1173 }
1174
1175 - (CKKSResultOperation<OctagonStateTransitionOperationProtocol>* _Nullable)initializingOperation
1176 {
1177 WEAKIFY(self);
1178 return [OctagonStateTransitionOperation named:@"octagon-initializing"
1179 intending:OctagonStateNoAccount
1180 errorState:OctagonStateError
1181 withBlockTakingSelf:^(OctagonStateTransitionOperation * _Nonnull op) {
1182 STRONGIFY(self);
1183 NSError* localError = nil;
1184 OTAccountMetadataClassC* account = [self.accountMetadataStore loadOrCreateAccountMetadata:&localError];
1185 if(localError && [self.lockStateTracker isLockedError:localError]){
1186 secnotice("octagon", "Device is locked! pending initialization on unlock");
1187 op.nextState = OctagonStateWaitForUnlock;
1188 return;
1189 }
1190
1191 if(localError || !account) {
1192 secnotice("octagon", "Error loading account data: %@", localError);
1193 op.nextState = OctagonStateNoAccount;
1194
1195 } else if(account.icloudAccountState == OTAccountMetadataClassC_AccountState_ACCOUNT_AVAILABLE) {
1196 secnotice("octagon", "An HSA2 iCloud account exists; waiting for CloudKit to confirm");
1197
1198 // Inform the account state tracker of our HSA2 account
1199 [self.accountStateTracker setHSA2iCloudAccountStatus:CKKSAccountStatusAvailable];
1200
1201 // This seems an odd place to do this, but CKKS currently also tracks the CloudKit account state.
1202 // Since we think we have an HSA2 account, let CKKS figure out its own CloudKit state
1203 secnotice("octagon-ckks", "Initializing CKKS views");
1204 [self.viewManager createViews];
1205 [self.viewManager beginCloudKitOperationOfAllViews];
1206
1207 op.nextState = OctagonStateWaitingForCloudKitAccount;
1208
1209 } else if(account.icloudAccountState == OTAccountMetadataClassC_AccountState_NO_ACCOUNT && account.altDSID != nil) {
1210 secnotice("octagon", "An iCloud account exists, but doesn't appear to be HSA2. Let's check!");
1211 op.nextState = OctagonStateDetermineiCloudAccountState;
1212
1213 } else if(account.icloudAccountState == OTAccountMetadataClassC_AccountState_NO_ACCOUNT) {
1214 [self.accountStateTracker setHSA2iCloudAccountStatus:CKKSAccountStatusNoAccount];
1215
1216 secnotice("octagon", "No iCloud account available.");
1217 op.nextState = OctagonStateNoAccount;
1218
1219 } else {
1220 secnotice("octagon", "Unknown account state (%@). Determining...", [account icloudAccountStateAsString:account.icloudAccountState]);
1221 op.nextState = OctagonStateDetermineiCloudAccountState;
1222 }
1223 }];
1224 }
1225
1226 - (CKKSResultOperation<OctagonStateTransitionOperationProtocol>* _Nullable)evaluateSecdOctagonTrust
1227 {
1228 return [OctagonStateTransitionOperation named:@"octagon-health-securityd-trust-check"
1229 intending:OctagonStateTPHTrustCheck
1230 errorState:OctagonStatePostRepairCFU
1231 withBlockTakingSelf:^(OctagonStateTransitionOperation * _Nonnull op) {
1232 NSError* localError = nil;
1233 OTAccountMetadataClassC* account = [self.accountMetadataStore loadOrCreateAccountMetadata:&localError];
1234 if(account.peerID && account.trustState == OTAccountMetadataClassC_TrustState_TRUSTED) {
1235 secnotice("octagon-health", "peer is trusted: %@", account.peerID);
1236 op.nextState = OctagonStateTPHTrustCheck;
1237
1238 } else {
1239 secnotice("octagon-health", "trust state (%@). checking in with TPH", [account trustStateAsString:account.trustState]);
1240 op.nextState = [self repairAccountIfTrustedByTPHWithIntededState:OctagonStateTPHTrustCheck errorState:OctagonStatePostRepairCFU];
1241 }
1242 }];
1243 }
1244
1245 - (CKKSResultOperation<OctagonStateTransitionOperationProtocol>* _Nullable)evaluateTPHOctagonTrust
1246 {
1247 return [OctagonStateTransitionOperation named:@"octagon-health-tph-trust-check"
1248 intending:OctagonStateCuttlefishTrustCheck
1249 errorState:OctagonStatePostRepairCFU
1250 withBlockTakingSelf:^(OctagonStateTransitionOperation * _Nonnull op) {
1251 [self checkTrustStatusAndPostRepairCFUIfNecessary:^(CliqueStatus status, BOOL posted, BOOL hasIdentity, NSError *trustFromTPHError) {
1252
1253 [[CKKSAnalytics logger] logResultForEvent:OctagonEventTPHHealthCheckStatus hardFailure:false result:trustFromTPHError];
1254 if(trustFromTPHError) {
1255 secerror("octagon-health: hit an error asking TPH for trust status: %@", trustFromTPHError);
1256 op.error = trustFromTPHError;
1257 op.nextState = OctagonStateError;
1258 } else {
1259 if(hasIdentity == NO) {
1260 op.nextState = OctagonStateUntrusted;
1261 } else if(hasIdentity == YES && status == CliqueStatusIn){
1262 secnotice("octagon-health", "TPH says we're trusted and in");
1263 op.nextState = OctagonStateCuttlefishTrustCheck;
1264 } else if (hasIdentity == YES && status != CliqueStatusIn){
1265 secnotice("octagon-health", "TPH says we have an identity but we are not in Octagon, posted CFU: %d", !!posted);
1266 op.nextState = OctagonStatePostRepairCFU;
1267 } else {
1268 secnotice("octagon-health", "weird shouldn't hit this catch all.. assuming untrusted");
1269 op.nextState = OctagonStateUntrusted;
1270 }
1271 }
1272 }];
1273 }];
1274 }
1275
1276 - (CKKSResultOperation<OctagonStateTransitionOperationProtocol>* _Nullable)cuttlefishTrustEvaluation
1277 {
1278
1279 OTCheckHealthOperation* op = [[OTCheckHealthOperation alloc] initWithDependencies:self.operationDependencies
1280 intendedState:OctagonStateBecomeReady
1281 errorState:OctagonStateBecomeReady
1282 deviceInfo:self.prepareInformation
1283 skipRateLimitedCheck:_skipRateLimitingCheck];
1284
1285 CKKSResultOperation* callback = [CKKSResultOperation named:@"rpcHealthCheck"
1286 withBlock:^{
1287 secnotice("octagon-health", "Returning from cuttlefish trust check call: postRepairCFU(%d), postEscrowCFU(%d), resetOctagon(%d)",
1288 op.postRepairCFU, op.postEscrowCFU, op.resetOctagon);
1289 if(op.postRepairCFU) {
1290 secnotice("octagon-health", "Posting Repair CFU");
1291 NSError* postRepairCFUError = nil;
1292 [self postRepairCFU:&postRepairCFUError];
1293 if(postRepairCFUError) {
1294 op.error = postRepairCFUError;
1295 }
1296 }
1297 if(op.postEscrowCFU) {
1298 //hold up, perhaps we already are pending an upload.
1299 NSError* shouldPostError = nil;
1300 BOOL shouldPost = [self shouldPostConfirmPasscodeCFU:&shouldPostError];
1301 if(shouldPostError) {
1302 secerror("octagon-health, hit an error evaluating prerecord status: %@", shouldPostError);
1303 op.error = shouldPostError;
1304 }
1305 if(shouldPost) {
1306 secnotice("octagon-health", "Posting Escrow CFU");
1307 NSError* postEscrowCFUError = nil;
1308 [self postConfirmPasscodeCFU:&postEscrowCFUError];
1309 if(postEscrowCFUError) {
1310 op.error = postEscrowCFUError;
1311 }
1312 } else {
1313 secnotice("octagon-health", "Not posting confirm passcode CFU, already pending a prerecord upload");
1314 }
1315 }
1316 }];
1317 [callback addDependency:op];
1318 [self.operationQueue addOperation: callback];
1319 return op;
1320 }
1321
1322 - (CKKSResultOperation<OctagonStateTransitionOperationProtocol>* _Nullable)postRepairCFUAndBecomeUntrusted
1323 {
1324 return [OctagonStateTransitionOperation named:@"octagon-health-post-repair-cfu"
1325 intending:OctagonStateUntrusted
1326 errorState:OctagonStateError
1327 withBlockTakingSelf:^(OctagonStateTransitionOperation * _Nonnull op) {
1328 [self checkTrustStatusAndPostRepairCFUIfNecessary:^(CliqueStatus status,
1329 BOOL posted,
1330 BOOL hasIdentity,
1331 NSError * _Nullable postError) {
1332 if(postError) {
1333 secerror("ocagon-health: failed to post repair cfu via state machine: %@", postError);
1334 } else {
1335 secnotice("octagon-health", "posted repair cfu via state machine");
1336 }
1337 }];
1338 op.nextState = OctagonStateUntrusted;
1339 }];
1340 }
1341
1342 - (CKKSResultOperation<OctagonStateTransitionOperationProtocol>* _Nullable)cloudKitAccountNewlyAvailableOperation
1343 {
1344 WEAKIFY(self);
1345 return [OctagonStateTransitionOperation named:@"octagon-icloud-account-available"
1346 intending:OctagonStateCheckTrustState
1347 errorState:OctagonStateError
1348 withBlockTakingSelf:^(OctagonStateTransitionOperation * _Nonnull op) {
1349 STRONGIFY(self);
1350 // Register with APS, but don't bother to wait until it's complete.
1351 secnotice("octagon", "iCloud sign in occurred. Attemping to register with APS...");
1352
1353 CKContainer* ckContainer = [CKContainer containerWithIdentifier:self.containerName];
1354 [ckContainer serverPreferredPushEnvironmentWithCompletionHandler: ^(NSString *apsPushEnvString, NSError *error) {
1355 STRONGIFY(self);
1356
1357 if(!self) {
1358 secerror("octagonpush: received callback for released object");
1359 return;
1360 }
1361
1362 if(error || (apsPushEnvString == nil)) {
1363 secerror("octagonpush: Received error fetching preferred push environment (%@): %@", apsPushEnvString, error);
1364 } else {
1365 secnotice("octagonpush", "Registering for environment '%@'", apsPushEnvString);
1366
1367 OctagonAPSReceiver* aps = [OctagonAPSReceiver receiverForEnvironment:apsPushEnvString
1368 namedDelegatePort:SecCKKSAPSNamedPort
1369 apsConnectionClass:self.apsConnectionClass];
1370 [aps registerCuttlefishReceiver:self forContainerName:self.containerName];
1371 }
1372 }];
1373
1374 op.nextState = op.intendedState;
1375 }];
1376 }
1377
1378 - (OctagonState*) repairAccountIfTrustedByTPHWithIntededState:(OctagonState*)intendedState errorState:(OctagonState*)errorState
1379 {
1380 __block OctagonState* nextState = intendedState;
1381
1382 //let's check in with TPH real quick to make sure it agrees with our local assessment
1383 secnotice("octagon-health", "repairAccountIfTrustedByTPHWithIntededState: calling into TPH for trust status");
1384
1385 OTOperationConfiguration *config = [[OTOperationConfiguration alloc]init];
1386
1387 [self rpcTrustStatus:config reply:^(CliqueStatus status,
1388 NSString* egoPeerID,
1389 NSDictionary<NSString*, NSNumber*>* _Nullable peerCountByModelID,
1390 BOOL isExcluded,
1391 NSError * _Nullable error) {
1392 BOOL hasIdentity = egoPeerID != nil;
1393 secnotice("octagon-health", "repairAccountIfTrustedByTPHWithIntededState status: %ld, peerID: %@, isExcluded: %d error: %@", (long)status, egoPeerID, isExcluded, error);
1394
1395 if (error) {
1396 secnotice("octagon-health", "got an error from tph, returning to become_ready state: %@", error);
1397 nextState = OctagonStateBecomeReady;
1398 return;
1399 }
1400
1401 if(OctagonAuthoritativeTrustIsEnabled() && hasIdentity && status == CliqueStatusIn) {
1402 dispatch_semaphore_t sema = dispatch_semaphore_create(0);
1403 [self rpcStatus:^(NSDictionary *dump, NSError *dumpError) {
1404 if(dumpError) {
1405 secerror("octagon-health: error fetching ego peer id!: %@", dumpError);
1406 nextState = errorState;
1407 } else {
1408 NSDictionary* egoInformation = dump[@"self"];
1409 NSString* peerID = egoInformation[@"peerID"];
1410 NSError* persistError = nil;
1411 BOOL persisted = [self.accountMetadataStore persistAccountChanges:^OTAccountMetadataClassC * _Nonnull(OTAccountMetadataClassC * _Nonnull metadata) {
1412 metadata.trustState = OTAccountMetadataClassC_TrustState_TRUSTED;
1413 metadata.icloudAccountState = OTAccountMetadataClassC_AccountState_ACCOUNT_AVAILABLE;
1414 metadata.peerID = peerID;
1415 return metadata;
1416 } error:&persistError];
1417 if(!persisted || persistError) {
1418 secerror("octagon-health: couldn't persist results: %@", persistError);
1419 nextState = errorState;
1420 } else {
1421 secnotice("octagon-health", "added trusted identity to account metadata");
1422 nextState = intendedState;
1423 }
1424 }
1425 dispatch_semaphore_signal(sema);
1426 }];
1427 if (0 != dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 10))) {
1428 secerror("octagon: Timed out checking trust status");
1429 }
1430 } else if (OctagonAuthoritativeTrustIsEnabled() && (self.postedRepairCFU == NO) && hasIdentity && status != CliqueStatusIn){
1431 nextState = errorState;
1432 }
1433 }];
1434
1435 return nextState;
1436 }
1437
1438 - (BOOL) didDeviceAttemptToJoinOctagon:(NSError**)error
1439 {
1440 NSError* fetchAttemptError = nil;
1441 OTAccountMetadataClassC_AttemptedAJoinState attemptedAJoin = [self.accountMetadataStore fetchPersistedJoinAttempt:&fetchAttemptError];
1442 if(fetchAttemptError) {
1443 secerror("octagon: failed to fetch data indicating device attempted to join octagon, assuming it did: %@", fetchAttemptError);
1444 if(error){
1445 *error = fetchAttemptError;
1446 }
1447 return YES;
1448 }
1449 BOOL attempted = YES;
1450 switch (attemptedAJoin) {
1451 case OTAccountMetadataClassC_AttemptedAJoinState_NOTATTEMPTED:
1452 attempted = NO;
1453 break;
1454 case OTAccountMetadataClassC_AttemptedAJoinState_ATTEMPTED:
1455 case OTAccountMetadataClassC_AttemptedAJoinState_UNKNOWN:
1456 default:
1457 break;
1458 }
1459 return attempted;
1460 }
1461
1462 - (void)checkTrustStatusAndPostRepairCFUIfNecessary:(void (^ _Nullable)(CliqueStatus status, BOOL posted, BOOL hasIdentity, NSError * _Nullable error))reply
1463 {
1464 WEAKIFY(self);
1465 OTOperationConfiguration *configuration = [[OTOperationConfiguration alloc] init];
1466 [self rpcTrustStatus:configuration reply:^(CliqueStatus status,
1467 NSString* _Nullable egoPeerID,
1468 NSDictionary<NSString*, NSNumber*>* _Nullable peerCountByModelID,
1469 BOOL isExcluded,
1470 NSError * _Nullable error) {
1471 STRONGIFY(self);
1472
1473 secnotice("octagon", "clique status: %@, egoPeerID: %@, peerCountByModelID: %@, isExcluded: %d error: %@", OTCliqueStatusToString(status), egoPeerID, peerCountByModelID, isExcluded, error);
1474
1475 BOOL hasIdentity = egoPeerID != nil;
1476 if (error && error.code != errSecInteractionNotAllowed) {
1477 reply(status, NO, hasIdentity, error);
1478 return;
1479 }
1480
1481 #if TARGET_OS_TV
1482 // Are there any iphones or iPads? about? Only iOS devices can repair apple TVs.
1483 bool phonePeerPresent = false;
1484 for(NSString* modelID in peerCountByModelID.allKeys) {
1485 bool iPhone = [modelID hasPrefix:@"iPhone"];
1486 bool iPad = [modelID hasPrefix:@"iPad"];
1487 if(!iPhone && !iPad) {
1488 continue;
1489 }
1490
1491 int count = [peerCountByModelID[modelID] intValue];
1492 if(count > 0) {
1493 secnotice("octagon", "Have %d peers with model %@", count, modelID);
1494 phonePeerPresent = true;
1495 break;
1496 }
1497 }
1498 if(!phonePeerPresent) {
1499 secnotice("octagon", "No iOS peers in account; not posting CFU");
1500 reply(status, NO, hasIdentity, nil);
1501 return;
1502 }
1503 #endif
1504
1505 // On platforms with SOS, we only want to post a CFU if we've attempted to join at least once.
1506 // This prevents us from posting a CFU, then performing an SOS upgrade and succeeding.
1507 if(self.sosAdapter.sosEnabled) {
1508 NSError* fetchAttemptError = nil;
1509 BOOL attemptedToJoin = [self didDeviceAttemptToJoinOctagon:&fetchAttemptError];
1510 if(fetchAttemptError){
1511 secerror("octagon: failed to retrieve joining attempt information: %@", fetchAttemptError);
1512 attemptedToJoin = YES;
1513 }
1514
1515 if(!attemptedToJoin) {
1516 secnotice("octagon", "SOS is enabled and we haven't attempted to join; not posting CFU");
1517 reply(status, NO, hasIdentity, nil);
1518 return;
1519 }
1520 }
1521
1522 if(OctagonAuthoritativeTrustIsEnabled() && (status == CliqueStatusNotIn || status == CliqueStatusAbsent || isExcluded)) {
1523 NSError* localError = nil;
1524 BOOL posted = [self postRepairCFU:&localError];
1525 reply(status, posted, hasIdentity, localError);
1526 return;
1527 }
1528 reply(status, NO, hasIdentity, nil);
1529 return;
1530 }];
1531 }
1532
1533 #if TARGET_OS_WATCH
1534 - (CKKSResultOperation<OctagonStateTransitionOperationProtocol>* _Nullable)startCompanionPairingOperation
1535 {
1536 WEAKIFY(self);
1537 return [OctagonStateTransitionOperation named:@"start-companion-pairing"
1538 intending:OctagonStateBecomeUntrusted
1539 errorState:OctagonStateError
1540 withBlockTakingSelf:^(OctagonStateTransitionOperation * _Nonnull op) {
1541 STRONGIFY(self);
1542 OTPairingInitiateWithCompletion(self.queue, ^(bool success, NSError *error) {
1543 if (success) {
1544 secnotice("octagon", "companion pairing succeeded");
1545 } else {
1546 if (error == nil) {
1547 error = [NSError errorWithDomain:(__bridge NSString *)kSecErrorDomain code:errSecInternalError userInfo:nil];
1548 }
1549 secnotice("octagon", "companion pairing failed: %@", error);
1550 }
1551 [[CKKSAnalytics logger] logResultForEvent:OctagonEventCompanionPairing hardFailure:false result:error];
1552 });
1553 op.nextState = op.intendedState;
1554 }];
1555 }
1556 #endif /* TARGET_OS_WATCH */
1557
1558 - (CKKSResultOperation<OctagonStateTransitionOperationProtocol>* _Nullable)becomeUntrustedOperation:(OctagonState*)intendedState
1559 {
1560 WEAKIFY(self);
1561 return [OctagonStateTransitionOperation named:@"octagon-become-untrusted"
1562 intending:intendedState
1563 errorState:OctagonStateError
1564 withBlockTakingSelf:^(OctagonStateTransitionOperation * _Nonnull op) {
1565 STRONGIFY(self);
1566 NSError* localError = nil;
1567
1568 [self.accountStateTracker triggerOctagonStatusFetch];
1569
1570 [self checkTrustStatusAndPostRepairCFUIfNecessary:^(CliqueStatus status, BOOL posted, BOOL hasIdentity, NSError * _Nullable postError) {
1571
1572 [[CKKSAnalytics logger] logResultForEvent:OctagonEventCheckTrustForCFU hardFailure:false result:postError];
1573 if(postError){
1574 secerror("octagon: cfu failed to post");
1575 } else {
1576 secnotice("octagon", "clique status: %@, posted cfu: %d", OTCliqueStatusToString(status), !!posted);
1577 }
1578 }];
1579
1580 [self.accountMetadataStore persistAccountChanges:^OTAccountMetadataClassC * _Nonnull(OTAccountMetadataClassC * _Nonnull metadata) {
1581 metadata.trustState = OTAccountMetadataClassC_TrustState_UNTRUSTED;
1582 return metadata;
1583 } error:&localError];
1584
1585 if(localError) {
1586 secnotice("octagon", "Unable to set trust state: %@", localError);
1587 op.nextState = OctagonStateError;
1588 } else {
1589 op.nextState = op.intendedState;
1590 }
1591
1592 for (id key in self.viewManager.views) {
1593 CKKSKeychainView* view = self.viewManager.views[key];
1594 secnotice("octagon-ckks", "Informing %@ of new untrusted status", view);
1595 [view endTrustedOperation];
1596 }
1597
1598 /*
1599 * Initial notification that we let the world know that trust is up and doing something
1600 */
1601 if (!self.initialBecomeUntrustedPosted) {
1602 [self notifyTrustChanged:OTAccountMetadataClassC_TrustState_UNTRUSTED];
1603 self.initialBecomeUntrustedPosted = YES;
1604 }
1605
1606 self.octagonAdapter = nil;
1607 }];
1608 }
1609
1610 - (CKKSResultOperation<OctagonStateTransitionOperationProtocol>* _Nullable)becomeReadyOperation
1611 {
1612 WEAKIFY(self);
1613 return [OctagonStateTransitionOperation named:@"octagon-ready"
1614 intending:OctagonStateReady
1615 errorState:OctagonStateError
1616 withBlockTakingSelf:^(OctagonStateTransitionOperation * _Nonnull op) {
1617 STRONGIFY(self);
1618
1619 // Note: we don't modify the account metadata store here; that will have been done
1620 // by a join or upgrade operation, possibly long ago
1621
1622 [self.accountStateTracker triggerOctagonStatusFetch];
1623
1624 NSError* localError = nil;
1625 NSString* peerID = [self.accountMetadataStore getEgoPeerID:&localError];
1626 if(!peerID || localError) {
1627 secerror("octagon-ckks: No peer ID to pass to CKKS. Syncing will be disabled.");
1628 } else {
1629 OctagonCKKSPeerAdapter* octagonAdapter = [[OctagonCKKSPeerAdapter alloc] initWithPeerID:peerID operationDependencies:[self operationDependencies]];
1630
1631 // This octagon adapter must be able to load the self peer keys, or we're in trouble.
1632 NSError* egoPeerKeysError = nil;
1633 CKKSSelves* selves = [octagonAdapter fetchSelfPeers:&egoPeerKeysError];
1634 if(!selves || egoPeerKeysError) {
1635 secerror("octagon-ckks: Unable to fetch self peers for %@: %@", octagonAdapter, egoPeerKeysError);
1636
1637 if([self.lockStateTracker isLockedError:egoPeerKeysError]) {
1638 secnotice("octagon-ckks", "Waiting for device unlock to proceed");
1639 op.nextState = OctagonStateWaitForUnlock;
1640 } else {
1641 secnotice("octagon-ckks", "Error is scary; becoming untrusted");
1642 op.nextState = OctagonStateBecomeUntrusted;
1643 }
1644 return;
1645 }
1646
1647 // stash a reference to the adapter so we can provided updates later
1648 self.octagonAdapter = octagonAdapter;
1649
1650 // Start all our CKKS views!
1651 for (id key in self.viewManager.views) {
1652 CKKSKeychainView* view = self.viewManager.views[key];
1653 secnotice("octagon-ckks", "Informing CKKS view '%@' of trusted operation with self peer %@", view.zoneName, peerID);
1654
1655 NSArray<id<CKKSPeerProvider>>* peerProviders = nil;
1656
1657 if(self.sosAdapter.sosEnabled) {
1658 peerProviders = @[self.octagonAdapter, self.sosAdapter];
1659
1660 } else {
1661 peerProviders = @[self.octagonAdapter];
1662 }
1663
1664 [view beginTrustedOperation:peerProviders
1665 suggestTLKUpload:self.suggestTLKUploadNotifier];
1666 }
1667 }
1668 [self notifyTrustChanged:OTAccountMetadataClassC_TrustState_TRUSTED];
1669
1670 op.nextState = op.intendedState;
1671 }];
1672 }
1673
1674 #pragma mark --- Utilities to run at times
1675
1676 - (NSString * _Nullable)extractStringKey:(NSString * _Nonnull)key fromDictionary:(NSDictionary * _Nonnull)d
1677 {
1678 NSString *value = d[key];
1679 if ([value isKindOfClass:[NSString class]]) {
1680 return value;
1681 }
1682 return NULL;
1683 }
1684
1685 - (void)handleHealthRequest
1686 {
1687 NSString *trustState = OTAccountMetadataClassC_TrustStateAsString(self.currentMemoizedTrustState);
1688 OctagonState* currentState = [self.stateMachine waitForState:OctagonStateReady wait:3*NSEC_PER_SEC];
1689
1690 [self.cuttlefishXPCWrapper reportHealthWithContainer:self.containerName context:self.contextID stateMachineState:currentState trustState:trustState reply:^(NSError * _Nullable error) {
1691 if (error) {
1692 secerror("octagon: health report is lost: %@", error);
1693 }
1694 }];
1695 }
1696
1697 - (void)handleTTRRequest:(NSDictionary *)cfDictionary
1698 {
1699 NSString *serialNumber = [self extractStringKey:@"s" fromDictionary:cfDictionary];
1700 NSString *ckDeviceId = [self extractStringKey:@"D" fromDictionary:cfDictionary];
1701 NSString *alert = [self extractStringKey:@"a" fromDictionary:cfDictionary];
1702 NSString *description = [self extractStringKey:@"d" fromDictionary:cfDictionary];
1703 NSString *radar = [self extractStringKey:@"R" fromDictionary:cfDictionary];
1704 NSString *componentName = [self extractStringKey:@"n" fromDictionary:cfDictionary];
1705 NSString *componentVersion = [self extractStringKey:@"v" fromDictionary:cfDictionary];
1706 NSString *componentID = [self extractStringKey:@"I" fromDictionary:cfDictionary];
1707
1708 if (serialNumber) {
1709 if (![self.deviceAdapter.serialNumber isEqualToString:serialNumber]) {
1710 secnotice("octagon", "TTR request not for me (sn)");
1711 return;
1712 }
1713 }
1714 if (ckDeviceId) {
1715 NSString *selfDeviceID = self.viewManager.accountTracker.ckdeviceID;
1716 if (![selfDeviceID isEqualToString:serialNumber]) {
1717 secnotice("octagon", "TTR request not for me (deviceId)");
1718 return;
1719 }
1720 }
1721
1722 if (alert == NULL || description == NULL || radar == NULL) {
1723 secerror("octagon: invalid type of TTR requeat: %@", cfDictionary);
1724 return;
1725 }
1726
1727 SecTapToRadar *ttr = [[SecTapToRadar alloc] initTapToRadar:alert
1728 description:description
1729 radar:radar];
1730 if (componentName && componentVersion && componentID) {
1731 ttr.componentName = componentName;
1732 ttr.componentVersion = componentVersion;
1733 ttr.componentID = componentID;
1734 }
1735 [ttr trigger];
1736 }
1737
1738 // We can't make a APSIncomingMessage in the tests (no public constructor),
1739 // but we don't really care about anything in it but the userInfo dictionary anyway
1740 - (void)notifyContainerChange:(APSIncomingMessage* _Nullable)notification
1741 {
1742 [self notifyContainerChangeWithUserInfo:notification.userInfo];
1743 }
1744
1745 - (void)notifyContainerChangeWithUserInfo:(NSDictionary*)userInfo
1746 {
1747 secerror("OTCuttlefishContext: received a cuttlefish push notification (%@): %@",
1748 self.containerName, userInfo);
1749
1750 NSDictionary *cfDictionary = userInfo[@"cf"];
1751 if ([cfDictionary isKindOfClass:[NSDictionary class]]) {
1752 NSString *command = [self extractStringKey:@"k" fromDictionary:cfDictionary];
1753 if(command) {
1754 if ([command isEqualToString:@"h"]) {
1755 [self handleHealthRequest];
1756 } else if ([command isEqualToString:@"r"]) {
1757 [self handleTTRRequest:cfDictionary];
1758 } else {
1759 secerror("octagon: unknown command: %@", command);
1760 }
1761 return;
1762 }
1763 }
1764
1765 if (self.apsRateLimiter == nil) {
1766 secnotice("octagon", "creating aps rate limiter");
1767 // If we're testing, for the initial delay, use 0.2 second. Otherwise, 2s.
1768 dispatch_time_t initialDelay = (SecCKKSReduceRateLimiting() ? 200 * NSEC_PER_MSEC : 2 * NSEC_PER_SEC);
1769
1770 // If we're testing, for the initial delay, use 2 second. Otherwise, 30s.
1771 dispatch_time_t continuingDelay = (SecCKKSReduceRateLimiting() ? 2 * NSEC_PER_SEC : 30 * NSEC_PER_SEC);
1772
1773 WEAKIFY(self);
1774 self.apsRateLimiter = [[CKKSNearFutureScheduler alloc] initWithName:@"aps-push-ratelimiter"
1775 initialDelay:initialDelay
1776 continuingDelay:continuingDelay
1777 keepProcessAlive:YES
1778 dependencyDescriptionCode:CKKSResultDescriptionNone
1779 block:^{
1780 STRONGIFY(self);
1781 if (self == nil) {
1782 return;
1783 }
1784 secnotice("octagon-push-ratelimited", "notifying container of change for context: %@", self.contextID);
1785 OctagonPendingFlag *pendingFlag = [[OctagonPendingFlag alloc] initWithFlag:OctagonFlagCuttlefishNotification
1786 conditions:OctagonPendingConditionsDeviceUnlocked];
1787
1788 [self.stateMachine handlePendingFlag:pendingFlag];
1789 }];
1790 }
1791
1792 [self.apsRateLimiter trigger];
1793 }
1794
1795 - (BOOL)waitForReady:(int64_t)timeOffset
1796 {
1797 OctagonState* currentState = [self.stateMachine waitForState:OctagonStateReady wait:timeOffset];
1798 return [currentState isEqualToString:OctagonStateReady];
1799
1800 }
1801
1802 - (OTAccountMetadataClassC_TrustState)currentMemoizedTrustState
1803 {
1804 NSError* localError = nil;
1805 OTAccountMetadataClassC* accountMetadata = [self.accountMetadataStore loadOrCreateAccountMetadata:&localError];
1806
1807 if(!accountMetadata) {
1808 secnotice("octagon", "Unable to fetch account metadata: %@", localError);
1809 return OTAccountMetadataClassC_TrustState_UNKNOWN;
1810 }
1811
1812 return accountMetadata.trustState;
1813 }
1814
1815 - (OTAccountMetadataClassC_AccountState)currentMemoizedAccountState
1816 {
1817 NSError* localError = nil;
1818 OTAccountMetadataClassC* accountMetadata = [self.accountMetadataStore loadOrCreateAccountMetadata:&localError];
1819
1820 if(!accountMetadata) {
1821 secnotice("octagon", "Unable to fetch account metadata: %@", localError);
1822 return OTAccountMetadataClassC_AccountState_UNKNOWN;
1823 }
1824
1825 return accountMetadata.icloudAccountState;
1826 }
1827
1828 - (NSDate* _Nullable) currentMemoizedLastHealthCheck
1829 {
1830 NSError* localError = nil;
1831 OTAccountMetadataClassC* accountMetadata = [self.accountMetadataStore loadOrCreateAccountMetadata:&localError];
1832
1833 if(!accountMetadata) {
1834 secnotice("octagon", "Unable to fetch account metadata: %@", localError);
1835 return nil;
1836 }
1837 if(accountMetadata.lastHealthCheckup == 0) {
1838 return nil;
1839 }
1840 return [[NSDate alloc] initWithTimeIntervalSince1970: accountMetadata.lastHealthCheckup];
1841 }
1842
1843 - (void)requestTrustedDeviceListRefresh
1844 {
1845 [self.stateMachine handleFlag:OctagonFlagFetchAuthKitMachineIDList];
1846 }
1847
1848 #pragma mark --- Device Info update handling
1849
1850 - (void)deviceNameUpdated {
1851 secnotice("octagon-devicename", "device name updated: %@", self.contextID);
1852 OctagonPendingFlag *pendingFlag = [[OctagonPendingFlag alloc] initWithFlag:OctagonFlagCuttlefishNotification
1853 conditions:OctagonPendingConditionsDeviceUnlocked];
1854 [self.stateMachine handlePendingFlag:pendingFlag];
1855 }
1856
1857 #pragma mark --- SOS update handling
1858
1859
1860 - (void)selfPeerChanged:(id<CKKSPeerProvider>)provider
1861 {
1862 // Currently, we register for peer changes with just our SOS peer adapter, so the only reason this is called is to receive SOS updates
1863 // Ignore SOS self peer updates for now.
1864 }
1865
1866 - (void)trustedPeerSetChanged:(id<CKKSPeerProvider>)provider
1867 {
1868 // Currently, we register for peer changes with just our SOS peer adapter, so the only reason this is called is to receive SOS updates
1869 secnotice("octagon-sos", "Received an update of an SOS trust set change");
1870
1871 if(!self.sosAdapter.sosEnabled) {
1872 secnotice("octagon-sos", "This platform doesn't support SOS. This is probably a bug?");
1873 }
1874
1875 if (self.sosConsistencyRateLimiter == nil) {
1876 secnotice("octagon", "creating SOS consistency rate limiter");
1877 dispatch_time_t initialDelay = (SecCKKSReduceRateLimiting() ? 200 * NSEC_PER_MSEC : 2 * NSEC_PER_SEC);
1878 dispatch_time_t maximumDelay = (SecCKKSReduceRateLimiting() ? 10 * NSEC_PER_SEC : 30 * NSEC_PER_SEC);
1879
1880 WEAKIFY(self);
1881
1882 void (^block)(void) = ^{
1883 STRONGIFY(self);
1884 [self.stateMachine handleFlag:OctagonFlagAttemptSOSConsistency];
1885 };
1886
1887 self.sosConsistencyRateLimiter = [[CKKSNearFutureScheduler alloc] initWithName:@"sos-consistency-ratelimiter"
1888 initialDelay:initialDelay
1889 expontialBackoff:2
1890 maximumDelay:maximumDelay
1891 keepProcessAlive:false
1892 dependencyDescriptionCode:CKKSResultDescriptionPendingZoneChangeFetchScheduling
1893 block:block];
1894 }
1895
1896 [self.sosConsistencyRateLimiter trigger];
1897 }
1898
1899 #pragma mark --- External Interfaces
1900
1901 //Check for account
1902 - (CKKSAccountStatus)checkForCKAccount:(OTOperationConfiguration * _Nullable)configuration {
1903
1904 #if TARGET_OS_WATCH
1905 // Watches can be very, very slow getting the CK account state
1906 uint64_t timeout = (90 * NSEC_PER_SEC);
1907 #else
1908 uint64_t timeout = (10 * NSEC_PER_SEC);
1909 #endif
1910 if (configuration.timeoutWaitForCKAccount != 0) {
1911 timeout = configuration.timeoutWaitForCKAccount;
1912 }
1913 if (timeout) {
1914 /* wait if account is not present yet */
1915 if([self.cloudKitAccountStateKnown wait:timeout] != 0) {
1916 secnotice("octagon-ck", "Unable to determine CloudKit account state?");
1917 return CKKSAccountStatusUnknown;
1918 }
1919 }
1920
1921 __block bool haveAccount = true;
1922 dispatch_sync(self.queue, ^{
1923 if (self.cloudKitAccountInfo == NULL || self.cloudKitAccountInfo.accountStatus != CKKSAccountStatusAvailable) {
1924 haveAccount = false;
1925 }
1926 });
1927 return haveAccount ? CKKSAccountStatusAvailable : CKKSAccountStatusNoAccount;
1928 }
1929
1930 - (NSError *)errorNoiCloudAccount
1931 {
1932 return [NSError errorWithDomain:OctagonErrorDomain
1933 code:OTErrorNotSignedIn
1934 description:@"User is not signed into iCloud."];
1935 }
1936
1937 //Initiator interfaces
1938
1939 - (void)rpcPrepareIdentityAsApplicantWithConfiguration:(OTJoiningConfiguration*)config
1940 epoch:(uint64_t)epoch
1941 reply:(void (^)(NSString * _Nullable peerID,
1942 NSData * _Nullable permanentInfo,
1943 NSData * _Nullable permanentInfoSig,
1944 NSData * _Nullable stableInfo,
1945 NSData * _Nullable stableInfoSig,
1946 NSError * _Nullable error))reply
1947 {
1948 if ([self checkForCKAccount:nil] != CKKSAccountStatusAvailable) {
1949 secnotice("octagon", "No cloudkit account present");
1950 reply(NULL, NULL, NULL, NULL, NULL, [self errorNoiCloudAccount]);
1951 return;
1952 }
1953
1954 secnotice("otrpc", "Preparing identity as applicant");
1955 OTPrepareOperation* pendingOp = [[OTPrepareOperation alloc] initWithDependencies:self.operationDependencies
1956 intendedState:OctagonStateInitiatorAwaitingVoucher
1957 errorState:OctagonStateBecomeUntrusted
1958 deviceInfo:[self prepareInformation]
1959 epoch:epoch];
1960
1961
1962 dispatch_time_t timeOut = 0;
1963 if(config.timeout != 0) {
1964 timeOut = config.timeout;
1965 } else if(!OctagonPlatformSupportsSOS()){
1966 // Non-iphone non-mac platforms can be slow; heuristically slow them down
1967 timeOut = 60*NSEC_PER_SEC;
1968 } else {
1969 timeOut = 2*NSEC_PER_SEC;
1970 }
1971
1972 OctagonStateTransitionRequest<OTPrepareOperation*>* request = [[OctagonStateTransitionRequest alloc] init:@"prepareForApplicant"
1973 sourceStates:[NSSet setWithArray:@[OctagonStateUntrusted, OctagonStateNoAccount, OctagonStateMachineNotStarted]]
1974 serialQueue:self.queue
1975 timeout:timeOut
1976 transitionOp:pendingOp];
1977
1978 CKKSResultOperation* callback = [CKKSResultOperation named:@"rpcPrepare-callback"
1979 withBlock:^{
1980 secnotice("otrpc", "Returning a prepare call: %@ %@", pendingOp.peerID, pendingOp.error);
1981 reply(pendingOp.peerID,
1982 pendingOp.permanentInfo,
1983 pendingOp.permanentInfoSig,
1984 pendingOp.stableInfo,
1985 pendingOp.stableInfoSig,
1986 pendingOp.error);
1987 }];
1988 [callback addDependency:pendingOp];
1989 [self.operationQueue addOperation: callback];
1990
1991 [self.stateMachine handleExternalRequest:request];
1992
1993 return;
1994 }
1995
1996 -(void)joinWithBottle:(NSString*)bottleID
1997 entropy:(NSData *)entropy
1998 bottleSalt:(NSString *)bottleSalt
1999 reply:(void (^)(NSError * _Nullable error))reply
2000 {
2001 _bottleID = bottleID;
2002 _entropy = entropy;
2003 _bottleSalt = bottleSalt;
2004 OTOperationConfiguration *configuration = [[OTOperationConfiguration alloc] init];
2005
2006 if ([self checkForCKAccount:configuration] != CKKSAccountStatusAvailable) {
2007 secnotice("octagon", "No cloudkit account present");
2008 reply([self errorNoiCloudAccount]);
2009 return;
2010 }
2011
2012 OctagonStateTransitionPath* path = [OctagonStateTransitionPath pathFromDictionary:@{
2013 OctagonStateInitiatorCreateIdentity: @{
2014 OctagonStateInitiatorVouchWithBottle: [self joinStatePathDictionary],
2015 },
2016 }];
2017
2018 [self.stateMachine doWatchedStateMachineRPC:@"rpc-join-with-bottle"
2019 sourceStates:OctagonInAccountStates()
2020 path:path
2021 reply:reply];
2022 }
2023
2024 -(void)joinWithRecoveryKey:(NSString*)recoveryKey
2025 reply:(void (^)(NSError * _Nullable error))reply
2026 {
2027 _recoveryKey = recoveryKey;
2028 OTOperationConfiguration *configuration = [[OTOperationConfiguration alloc] init];
2029
2030 if ([self checkForCKAccount:configuration] != CKKSAccountStatusAvailable) {
2031 secnotice("octagon", "No cloudkit account present");
2032 reply([self errorNoiCloudAccount]);
2033 return;
2034 }
2035
2036 OctagonStateTransitionPath* path = [OctagonStateTransitionPath pathFromDictionary:@{
2037 OctagonStateCreateIdentityForRecoveryKey: @{
2038 OctagonStateVouchWithRecoveryKey: [self joinStatePathDictionary],
2039 },
2040 }];
2041
2042 [self.stateMachine doWatchedStateMachineRPC:@"rpc-join-with-recovery-key"
2043 sourceStates:OctagonInAccountStates()
2044 path:path
2045 reply:reply];
2046 }
2047
2048 - (NSDictionary*)joinStatePathDictionary
2049 {
2050 return @{
2051 OctagonStateInitiatorUpdateDeviceList: @{
2052 OctagonStateInitiatorJoin: @{
2053 OctagonStateBecomeReady: @{
2054 OctagonStateReady: [OctagonStateTransitionPathStep success],
2055 },
2056
2057 OctagonStateInitiatorJoinCKKSReset: @{
2058 OctagonStateInitiatorJoinAfterCKKSReset: @{
2059 OctagonStateBecomeReady: @{
2060 OctagonStateReady: [OctagonStateTransitionPathStep success]
2061 },
2062 },
2063 },
2064 },
2065 },
2066 };
2067 }
2068
2069 - (void)rpcJoin:(NSData*)vouchData
2070 vouchSig:(NSData*)vouchSig
2071 preapprovedKeys:(NSArray<NSData*>* _Nullable)preapprovedKeys
2072 reply:(void (^)(NSError * _Nullable error))reply
2073 {
2074
2075 _vouchData = vouchData;
2076 _vouchSig = vouchSig;
2077 _preapprovedKeys = preapprovedKeys;
2078
2079 if ([self checkForCKAccount:nil] != CKKSAccountStatusAvailable) {
2080 secnotice("octagon", "No cloudkit account present");
2081 reply([self errorNoiCloudAccount]);
2082 return;
2083 }
2084
2085 NSMutableSet* sourceStates = [NSMutableSet setWithObject:OctagonStateInitiatorAwaitingVoucher];
2086
2087 OctagonStateTransitionPath* path = [OctagonStateTransitionPath pathFromDictionary:[self joinStatePathDictionary]];
2088
2089 [self.stateMachine doWatchedStateMachineRPC:@"rpc-join"
2090 sourceStates:sourceStates
2091 path:path
2092 reply:reply];
2093 }
2094
2095 - (NSDictionary *)ckksPeerStatus:(id<CKKSPeer>)peer
2096 {
2097 NSMutableDictionary *peerStatus = [NSMutableDictionary dictionary];
2098
2099 if (peer.peerID) {
2100 peerStatus[@"peerID"] = peer.peerID;
2101 }
2102 NSData *spki = peer.publicSigningKey.encodeSubjectPublicKeyInfo;
2103 if (spki) {
2104 peerStatus[@"signingSPKI"] = [spki base64EncodedStringWithOptions:0];
2105 peerStatus[@"signingSPKIHash"] = [TPHashBuilder hashWithAlgo:kTPHashAlgoSHA256 ofData:spki];
2106 }
2107 return peerStatus;
2108 }
2109
2110 - (NSArray *)sosTrustedPeersStatus
2111 {
2112 NSError *localError = nil;
2113 NSSet<id<CKKSRemotePeerProtocol>>* _Nullable peers = [self.sosAdapter fetchTrustedPeers:&localError];
2114 if (peers == nil || localError) {
2115 secnotice("octagon", "No SOS peers present: %@, skipping in status", localError);
2116 return nil;
2117 }
2118 NSMutableArray<NSDictionary *>* trustedSOSPeers = [NSMutableArray array];
2119
2120 for (id<CKKSPeer> peer in peers) {
2121 NSDictionary *peerStatus = [self ckksPeerStatus:peer];
2122 if (peerStatus) {
2123 [trustedSOSPeers addObject:peerStatus];
2124 }
2125 }
2126 return trustedSOSPeers;
2127 }
2128
2129 - (NSDictionary *)sosSelvesStatus
2130 {
2131 NSError *localError = nil;
2132
2133 CKKSSelves* selves = [self.sosAdapter fetchSelfPeers:&localError];
2134 if (selves == nil || localError) {
2135 secnotice("octagon", "No SOS selves present: %@, skipping in status", localError);
2136 return nil;
2137 }
2138 NSMutableDictionary* selvesSOSPeers = [NSMutableDictionary dictionary];
2139
2140 selvesSOSPeers[@"currentSelf"] = [self ckksPeerStatus:selves.currentSelf];
2141
2142 /*
2143 * If we have past selves, include them too
2144 */
2145 NSMutableSet* pastSelves = [selves.allSelves mutableCopy];
2146 [pastSelves removeObject:selves.currentSelf];
2147 if (pastSelves.count) {
2148 NSMutableArray<NSDictionary *>* pastSelvesStatus = [NSMutableArray array];
2149
2150 for (id<CKKSPeer> peer in pastSelves) {
2151 NSDictionary *peerStatus = [self ckksPeerStatus:peer];
2152 if (peerStatus) {
2153 [pastSelvesStatus addObject:peerStatus];
2154 }
2155 }
2156 selvesSOSPeers[@"pastSelves"] = pastSelvesStatus;
2157 }
2158 return selvesSOSPeers;
2159 }
2160
2161 - (void)rpcStatus:(void (^)(NSDictionary* _Nullable result, NSError* _Nullable error))reply
2162 {
2163 __block NSMutableDictionary* result = [NSMutableDictionary dictionary];
2164
2165 result[@"containerName"] = self.containerName;
2166 result[@"contextID"] = self.contextID;
2167
2168 if ([self checkForCKAccount:nil] != CKKSAccountStatusAvailable) {
2169 secnotice("octagon", "No cloudkit account present");
2170 reply(NULL, [self errorNoiCloudAccount]);
2171 return;
2172 }
2173
2174 if([self.stateMachine.paused wait:3*NSEC_PER_SEC] != 0) {
2175 secnotice("octagon", "Returning status of unpaused state machine for container (%@) and context (%@)", self.containerName, self.contextID);
2176 result[@"stateUnpaused"] = @1;
2177 }
2178
2179 // This will try to allow the state machine to pause
2180 result[@"state"] = self.stateMachine.currentState;
2181 result[@"statePendingFlags"] = [self.stateMachine dumpPendingFlags];
2182 result[@"stateFlags"] = [self.stateMachine.flags dumpFlags];
2183
2184 result[@"memoizedTrustState"] = @(self.currentMemoizedTrustState);
2185 result[@"memoizedAccountState"] = @(self.currentMemoizedAccountState);
2186 result[@"octagonLaunchSeqence"] = [self.launchSequence eventsByTime];
2187 result[@"memoizedlastHealthCheck"] = self.currentMemoizedLastHealthCheck ? self.currentMemoizedLastHealthCheck : @"Never checked";
2188 if (self.sosAdapter.sosEnabled) {
2189 result[@"sosTrustedPeersStatus"] = [self sosTrustedPeersStatus];
2190 result[@"sosSelvesStatus"] = [self sosSelvesStatus];
2191 }
2192
2193 {
2194 NSError *error;
2195 id<SecEscrowRequestable> request = [self.escrowRequestClass request:&error];
2196 result[@"escrowRequest"] = [request fetchStatuses:&error];
2197 }
2198
2199 result[@"CoreFollowUp"] = [self.followupHandler sysdiagnoseStatus];
2200 result[@"lastOctagonPush"] = [[CKKSAnalytics logger] datePropertyForKey:CKKSAnalyticsLastOctagonPush];
2201
2202
2203 [self.cuttlefishXPCWrapper dumpWithContainer:self.containerName
2204 context:self.contextID
2205 reply:^(NSDictionary * _Nullable dump, NSError * _Nullable dumpError) {
2206 secnotice("octagon", "Finished dump for status RPC");
2207 if(dumpError) {
2208 result[@"contextDumpError"] = dumpError;
2209 } else {
2210 result[@"contextDump"] = dump;
2211 }
2212 reply(result, nil);
2213 }];
2214 }
2215
2216 - (void)rpcFetchEgoPeerID:(void (^)(NSString* peerID, NSError* error))reply
2217 {
2218 // We've memoized this peer ID. Use the memorized version...
2219 NSError* localError = nil;
2220 NSString* peerID = [self.accountMetadataStore getEgoPeerID:&localError];
2221
2222 if(peerID) {
2223 secnotice("octagon", "Returning peer ID: %@", peerID);
2224 } else {
2225 secnotice("octagon", "Unable to fetch peer ID: %@", localError);
2226 }
2227 reply(peerID, localError);
2228 }
2229
2230 - (void)rpcFetchDeviceNamesByPeerID:(void (^)(NSDictionary<NSString*, NSString*>* _Nullable peers, NSError* _Nullable error))reply
2231 {
2232 if ([self checkForCKAccount:nil] != CKKSAccountStatusAvailable) {
2233 secnotice("octagon", "No cloudkit account present");
2234 reply(NULL, [self errorNoiCloudAccount]);
2235 return;
2236 }
2237
2238 // As this isn't a state-modifying operation, we don't need to go through the state machine.
2239 [self.cuttlefishXPCWrapper dumpWithContainer:self.containerName
2240 context:self.contextID
2241 reply:^(NSDictionary * _Nullable dump, NSError * _Nullable dumpError) {
2242 // Pull out our peers
2243 if(dumpError) {
2244 secnotice("octagon", "Unable to dump info: %@", dumpError);
2245 reply(nil, dumpError);
2246 return;
2247 }
2248
2249 NSDictionary* selfInfo = dump[@"self"];
2250 NSArray* peers = dump[@"peers"];
2251 NSArray* trustedPeerIDs = selfInfo[@"dynamicInfo"][@"included"];
2252
2253 NSMutableDictionary<NSString*, NSString*>* peerMap = [NSMutableDictionary dictionary];
2254
2255 for(NSString* peerID in trustedPeerIDs) {
2256 NSDictionary* peerMatchingID = nil;
2257
2258 for(NSDictionary* peer in peers) {
2259 if([peer[@"peerID"] isEqualToString:peerID]) {
2260 peerMatchingID = peer;
2261 break;
2262 }
2263 }
2264
2265 if(!peerMatchingID) {
2266 secerror("octagon: have a trusted peer ID without peer information: %@", peerID);
2267 continue;
2268 }
2269
2270 peerMap[peerID] = peerMatchingID[@"stableInfo"][@"device_name"];
2271 }
2272
2273 reply(peerMap, nil);
2274 }];
2275 }
2276
2277 - (void)rpcSetRecoveryKey:(NSString*)recoveryKey reply:(void (^)(NSError * _Nullable error))reply
2278 {
2279 OTSetRecoveryKeyOperation *pendingOp = [[OTSetRecoveryKeyOperation alloc] initWithDependencies:self.operationDependencies
2280 recoveryKey:recoveryKey];
2281
2282 CKKSResultOperation* callback = [CKKSResultOperation named:@"setRecoveryKey-callback"
2283 withBlock:^{
2284 secnotice("otrpc", "Returning a set recovery key call: %@", pendingOp.error);
2285 reply(pendingOp.error);
2286 }];
2287
2288 [callback addDependency:pendingOp];
2289 [self.operationQueue addOperation:callback];
2290 [self.operationQueue addOperation:pendingOp];
2291 }
2292
2293 - (void)rpcTrustStatusCachedStatus:(OTAccountMetadataClassC*)account
2294 reply:(void (^)(CliqueStatus status,
2295 NSString* egoPeerID,
2296 NSDictionary<NSString*, NSNumber*>* _Nullable peerCountByModelID,
2297 BOOL isExcluded,
2298 NSError *error))reply
2299 {
2300 CliqueStatus status = CliqueStatusAbsent;
2301
2302 if (account.trustState == OTAccountMetadataClassC_TrustState_TRUSTED) {
2303 status = CliqueStatusIn;
2304 } else if (account.trustState == OTAccountMetadataClassC_TrustState_UNTRUSTED) {
2305 status = CliqueStatusNotIn;
2306 }
2307
2308 secnotice("octagon", "returning cached clique status: %@", OTCliqueStatusToString(status));
2309 reply(status, account.peerID, nil, NO, NULL);
2310 }
2311
2312
2313 - (void)rpcTrustStatus:(OTOperationConfiguration *)configuration
2314 reply:(void (^)(CliqueStatus status,
2315 NSString* _Nullable peerID,
2316 NSDictionary<NSString*, NSNumber*>* _Nullable peerCountByModelID,
2317 BOOL isExcluded,
2318 NSError *error))reply
2319 {
2320 __block NSError* localError = nil;
2321
2322 OTAccountMetadataClassC* account = [self.accountMetadataStore loadOrCreateAccountMetadata:&localError];
2323 if(localError && [self.lockStateTracker isLockedError:localError]){
2324 secnotice("octagon", "Device is locked! pending initialization on unlock");
2325 reply(CliqueStatusError, nil, nil, NO, localError);
2326 return;
2327 }
2328
2329 if(account.icloudAccountState == OTAccountMetadataClassC_AccountState_NO_ACCOUNT) {
2330 secnotice("octagon", "no account! returning clique status 'no account'");
2331 reply(CliqueStatusNoCloudKitAccount, nil, nil, NO, NULL);
2332 return;
2333 }
2334
2335 if (configuration.useCachedAccountStatus) {
2336 [self rpcTrustStatusCachedStatus:account reply:reply];
2337 return;
2338 }
2339
2340 CKKSAccountStatus ckAccountStatus = [self checkForCKAccount:configuration];
2341 if(ckAccountStatus == CKKSAccountStatusNoAccount) {
2342 secnotice("octagon", "No cloudkit account present");
2343 reply(CliqueStatusNoCloudKitAccount, nil, nil, NO, NULL);
2344 return;
2345 } else if(ckAccountStatus == CKKSAccountStatusUnknown) {
2346 secnotice("octagon", "Unknown cloudkit account status, returning cached trust value");
2347 [self rpcTrustStatusCachedStatus:account reply:reply];
2348 return;
2349 }
2350
2351 __block NSString* peerID = nil;
2352 __block NSDictionary<NSString*, NSNumber*>* peerModelCounts = nil;
2353 __block BOOL excluded = NO;
2354 __block CliqueStatus trustStatus = CliqueStatusError;
2355
2356 [self.cuttlefishXPCWrapper trustStatusWithContainer:self.containerName
2357 context:self.contextID
2358 reply:^(TrustedPeersHelperEgoPeerStatus *egoStatus,
2359 NSError *xpcError) {
2360 TPPeerStatus status = egoStatus.egoStatus;
2361 peerID = egoStatus.egoPeerID;
2362 excluded = egoStatus.isExcluded;
2363 peerModelCounts = egoStatus.viablePeerCountsByModelID;
2364 localError = xpcError;
2365
2366 if(xpcError) {
2367 secnotice("octagon", "error fetching trust status: %@", xpcError);
2368 } else {
2369 secnotice("octagon", "trust status: %@", TPPeerStatusToString(status));
2370
2371 if((status&TPPeerStatusExcluded) == TPPeerStatusExcluded){
2372 trustStatus = CliqueStatusNotIn;
2373 }
2374 else if((status&TPPeerStatusPartiallyReciprocated) == TPPeerStatusPartiallyReciprocated){
2375 trustStatus = CliqueStatusIn;
2376 }
2377 else if((status&TPPeerStatusAncientEpoch) == TPPeerStatusAncientEpoch){
2378 //FIX ME HANDLE THIS CASE
2379 trustStatus= CliqueStatusIn;
2380 }
2381 else if((status&TPPeerStatusOutdatedEpoch) == TPPeerStatusOutdatedEpoch){
2382 //FIX ME HANDLE THIS CASE
2383 trustStatus = CliqueStatusIn;
2384 }
2385 else if((status&TPPeerStatusFullyReciprocated) == TPPeerStatusFullyReciprocated){
2386 trustStatus = CliqueStatusIn;
2387 }
2388 else if((status&TPPeerStatusUnknown) == TPPeerStatusUnknown){
2389 trustStatus = CliqueStatusAbsent;
2390 }
2391 else if ((status&TPPeerStatusSelfTrust) == TPPeerStatusSelfTrust) {
2392 trustStatus = CliqueStatusIn;
2393 }
2394 else {
2395 secnotice("octagon", "TPPeerStatus is empty");
2396 trustStatus = CliqueStatusAbsent;
2397 }
2398 }
2399 }];
2400
2401 if(trustStatus == CliqueStatusIn && self.postedRepairCFU == YES){
2402 NSError* clearError = nil;
2403 [self.followupHandler clearFollowUp:OTFollowupContextTypeStateRepair error:&clearError];
2404 // TODO(caw): should we clear this flag if `clearFollowUpForContext` fails?
2405 self.postedRepairCFU = NO;
2406 }
2407 reply(trustStatus, peerID, peerModelCounts, excluded, localError);
2408 }
2409
2410 - (void)rpcFetchAllViableBottles:(void (^)(NSArray<NSString*>* _Nullable sortedBottleIDs, NSArray<NSString*>* _Nullable sortedPartialEscrowRecordIDs, NSError* _Nullable error))reply
2411 {
2412 if ([self checkForCKAccount:nil] != CKKSAccountStatusAvailable) {
2413 secnotice("octagon", "No cloudkit account present");
2414 reply(NULL, NULL, [self errorNoiCloudAccount]);
2415 return;
2416 }
2417
2418 // As this isn't a state-modifying operation, we don't need to go through the state machine.
2419 [self.cuttlefishXPCWrapper fetchViableBottlesWithContainer:self.containerName
2420 context:self.contextID
2421 reply:^(NSArray<NSString*>* _Nullable sortedEscrowRecordIDs, NSArray<NSString*>* _Nullable sortedPartialEscrowRecordIDs, NSError * _Nullable error) {
2422 if(error){
2423 secerror("octagon: error fetching all viable bottles: %@", error);
2424 reply(nil, nil, error);
2425 }else{
2426 secnotice("octagon", "fetched viable bottles: %@", sortedEscrowRecordIDs);
2427 secnotice("octagon", "fetched partially viable bottles: %@", sortedPartialEscrowRecordIDs);
2428 reply(sortedEscrowRecordIDs, sortedPartialEscrowRecordIDs, error);
2429 }
2430 }];
2431 }
2432
2433 - (void)fetchEscrowContents:(void (^)(NSData* _Nullable entropy,
2434 NSString* _Nullable bottleID,
2435 NSData* _Nullable signingPublicKey,
2436 NSError* _Nullable error))reply
2437 {
2438 // As this isn't a state-modifying operation, we don't need to go through the state machine.
2439 [self.cuttlefishXPCWrapper fetchEscrowContentsWithContainer:self.containerName
2440 context:self.contextID
2441 reply:^(NSData * _Nullable entropy, NSString * _Nullable bottleID, NSData * _Nullable signingPublicKey, NSError * _Nullable error) {
2442 if(error){
2443 secerror("octagon: error fetching escrow contents: %@", error);
2444 reply(nil, nil, nil, error);
2445 }else{
2446 secnotice("octagon", "fetched escrow contents for bottle: %@", bottleID);
2447 reply(entropy, bottleID, signingPublicKey, error);
2448 }
2449 }];
2450 }
2451
2452 - (void)rpcValidatePeers:(void (^)(NSDictionary* _Nullable result, NSError* _Nullable error))reply
2453 {
2454 __block NSMutableDictionary* result = [NSMutableDictionary dictionary];
2455
2456 result[@"containerName"] = self.containerName;
2457 result[@"contextID"] = self.contextID;
2458 result[@"state"] = [self.stateMachine waitForState:OctagonStateReady wait:3*NSEC_PER_SEC];
2459
2460 if ([self checkForCKAccount:nil] != CKKSAccountStatusAvailable) {
2461 secnotice("octagon", "No cloudkit account present");
2462 reply(NULL, [self errorNoiCloudAccount]);
2463 return;
2464 }
2465
2466 [self.cuttlefishXPCWrapper validatePeersWithContainer:self.containerName
2467 context:self.contextID
2468 reply:^(NSDictionary * _Nullable validateData, NSError * _Nullable dumpError) {
2469 secnotice("octagon", "Finished validatePeers for status RPC");
2470 if(dumpError) {
2471 result[@"error"] = dumpError;
2472 } else {
2473 result[@"validate"] = validateData;
2474 }
2475 reply(result, nil);
2476 }];
2477 }
2478
2479
2480 #pragma mark --- Testing
2481 - (void) setAccountStateHolder:(OTCuttlefishAccountStateHolder*)accountMetadataStore
2482 {
2483 self.accountMetadataStore = accountMetadataStore;
2484 }
2485
2486 - (void)setPostedBool:(BOOL)posted
2487 {
2488 self.postedRepairCFU = posted;
2489 }
2490
2491 #pragma mark --- Health Checker
2492
2493 - (BOOL)postRepairCFU:(NSError**)error
2494 {
2495 NSError* localError = nil;
2496 BOOL postSuccess = NO;
2497 if (self.postedRepairCFU == NO) {
2498 [self.followupHandler postFollowUp:OTFollowupContextTypeStateRepair error:&localError];
2499 if(localError){
2500 secerror("octagon-health: CoreCDP repair failed: %@", localError);
2501 if(error){
2502 *error = localError;
2503 }
2504 }
2505 else{
2506 secnotice("octagon-health", "CoreCDP post repair success");
2507 self.postedRepairCFU = YES;
2508 postSuccess = YES;
2509 }
2510 } else {
2511 secnotice("octagon-health", "already posted a repair CFU!");
2512 }
2513 return postSuccess;
2514 }
2515
2516 - (BOOL)shouldPostConfirmPasscodeCFU:(NSError**)error
2517 {
2518 NSError* localError = nil;
2519 id<SecEscrowRequestable> request = [self.escrowRequestClass request:&localError];
2520 if(!request || localError) {
2521 secnotice("octagon-health", "Unable to acquire a EscrowRequest object: %@", localError);
2522 if(error){
2523 *error = localError;
2524 }
2525 return YES;
2526 }
2527 BOOL pendingUpload = [request pendingEscrowUpload:&localError];
2528
2529 if(localError) {
2530 secnotice("octagon-health", "Failed to check escrow prerecord status: %@", localError);
2531 if(error) {
2532 *error = localError;
2533 }
2534 return YES;
2535 }
2536
2537 if(pendingUpload == YES) {
2538 secnotice("octagon-health", "prerecord is pending, NOT posting CFU");
2539 return NO;
2540 } else {
2541 secnotice("octagon-health", "no pending prerecords, posting CFU");
2542 return YES;
2543 }
2544 }
2545
2546 - (void)postConfirmPasscodeCFU:(NSError**)error
2547 {
2548 NSError* localError = nil;
2549 if (self.postedEscrowRepairCFU == NO) {
2550 [self.followupHandler postFollowUp:OTFollowupContextTypeOfflinePasscodeChange error:&localError];
2551 if(localError){
2552 secerror("octagon-health: CoreCDP offline passcode change failed: %@", localError);
2553 *error = localError;
2554 }
2555 else{
2556 secnotice("octagon-health", "CoreCDP offline passcode change success");
2557 self.postedEscrowRepairCFU = YES;
2558 }
2559 } else {
2560 secnotice("octagon-health", "already posted escrow CFU");
2561 }
2562 }
2563
2564 - (void)postRecoveryKeyCFU:(NSError**)error
2565 {
2566 NSError* localError = nil;
2567 if (self.postedRecoveryKeyCFU == NO) {
2568 [self.followupHandler postFollowUp:OTFollowupContextTypeRecoveryKeyRepair error:&localError];
2569 if(localError){
2570 secerror("octagon-health: CoreCDP recovery key cfu failed: %@", localError);
2571 }
2572 else{
2573 secnotice("octagon-health", "CoreCDP recovery key cfu success");
2574 self.postedRecoveryKeyCFU = YES;
2575 }
2576 } else {
2577 secnotice("octagon-health", "already posted recovery key CFU");
2578 }
2579 }
2580
2581 - (void)checkOctagonHealth:(BOOL)skipRateLimitingCheck reply:(void (^)(NSError * _Nullable error))reply
2582 {
2583 secnotice("octagon-health", "Beginning checking overall Octagon Trust");
2584
2585 _skipRateLimitingCheck = skipRateLimitingCheck;
2586
2587 // Ending in "waitforunlock" is okay for a health check
2588 [self.stateMachine doWatchedStateMachineRPC:@"octagon-trust-health-check"
2589 sourceStates:OctagonHealthSourceStates()
2590 path:[OctagonStateTransitionPath pathFromDictionary:@{
2591 OctagonStateHSA2HealthCheck: @{
2592 OctagonStateSecurityTrustCheck: @{
2593 OctagonStateTPHTrustCheck: @{
2594 OctagonStateCuttlefishTrustCheck: @{
2595 OctagonStateBecomeReady: @{
2596 OctagonStateReady: [OctagonStateTransitionPathStep success],
2597 OctagonStateWaitForUnlock: [OctagonStateTransitionPathStep success],
2598 },
2599 // Cuttlefish can suggest we reset the world. Consider reaching here a success,
2600 // instead of tracking the whole reset.
2601 OctagonStateHealthCheckReset: [OctagonStateTransitionPathStep success],
2602 },
2603 },
2604 },
2605 OctagonStateWaitForHSA2: [OctagonStateTransitionPathStep success],
2606 }
2607 }]
2608 reply:reply];
2609 }
2610
2611 - (void)attemptSOSUpgrade:(void (^)(NSError* _Nullable error))reply
2612 {
2613 secnotice("octagon-sos", "attempting to perform an sos upgrade");
2614
2615 if ([self checkForCKAccount:nil] != CKKSAccountStatusAvailable) {
2616 secnotice("octagon-sos", "No cloudkit account present");
2617 reply([self errorNoiCloudAccount]);
2618 return;
2619 }
2620
2621 NSSet* sourceStates = [NSSet setWithArray: @[OctagonStateUntrusted]];
2622
2623 OTSOSUpgradeOperation *pendingOp = [[OTSOSUpgradeOperation alloc] initWithDependencies:self.operationDependencies
2624 intendedState:OctagonStateBecomeReady
2625 ckksConflictState:OctagonStateBecomeUntrusted
2626 errorState:OctagonStateBecomeUntrusted
2627 deviceInfo:self.prepareInformation];
2628
2629 OctagonStateTransitionRequest<OTSOSUpgradeOperation*>* request = [[OctagonStateTransitionRequest alloc] init:@"attempt-sos-upgrade"
2630 sourceStates:sourceStates
2631 serialQueue:self.queue
2632 timeout:OctagonStateTransitionDefaultTimeout
2633 transitionOp:pendingOp];
2634
2635 CKKSResultOperation* callback = [CKKSResultOperation named:@"sos-upgrade-callback"
2636 withBlock:^{
2637 secnotice("otrpc", "Returning from an sos upgrade attempt: %@", pendingOp.error);
2638 [[CKKSAnalytics logger] logResultForEvent:OctagonEventUpgradePreapprovedJoinAfterPairing hardFailure:false result:pendingOp.error];
2639 reply(pendingOp.error);
2640 }];
2641
2642 [callback addDependency:pendingOp];
2643 [self.operationQueue addOperation: callback];
2644
2645 [self.stateMachine handleExternalRequest:request];
2646 }
2647
2648 - (void)waitForOctagonUpgrade:(void (^)(NSError* error))reply
2649 {
2650 secnotice("octagon-sos", "waitForOctagonUpgrade");
2651
2652 if (!self.sosAdapter.sosEnabled) {
2653 secnotice("octagon-sos", "sos not enabled, nothing to do for waitForOctagonUpgrade");
2654 reply(nil);
2655 return;
2656 }
2657
2658 if ([self.stateMachine isPaused]) {
2659 if ([[self.stateMachine currentState] isEqualToString:OctagonStateReady]) {
2660 secnotice("octagon-sos", "waitForOctagonUpgrade: already ready, returning");
2661 reply(nil);
2662 return;
2663 }
2664 } else {
2665 if ([[self.stateMachine waitForState:OctagonStateReady wait:10*NSEC_PER_SEC] isEqualToString:OctagonStateReady]) {
2666 secnotice("octagon-sos", "waitForOctagonUpgrade: in ready (after waiting), returning");
2667 reply(nil);
2668 return;
2669 } else {
2670 secnotice("octagon-sos", "waitForOctagonUpgrade: fail to get to ready after timeout, attempting upgrade");
2671 }
2672 }
2673
2674 NSSet* sourceStates = [NSSet setWithArray: @[OctagonStateUntrusted]];
2675
2676 OctagonStateTransitionPath* path = [OctagonStateTransitionPath pathFromDictionary:@{
2677 OctagonStateAttemptSOSUpgrade: @{
2678 OctagonStateBecomeReady: @{
2679 OctagonStateReady: [OctagonStateTransitionPathStep success],
2680 },
2681 },
2682 }];
2683
2684 [self.stateMachine doWatchedStateMachineRPC:@"sos-upgrade-to-ready"
2685 sourceStates:sourceStates
2686 path:path
2687 reply:reply];
2688 }
2689
2690 - (void)clearPendingCFUFlags
2691 {
2692 self.postedRecoveryKeyCFU = NO;
2693 self.postedEscrowRepairCFU = NO;
2694 self.postedRepairCFU = NO;
2695 }
2696
2697 // Metrics passthroughs
2698
2699 - (BOOL)machineIDOnMemoizedList:(NSString*)machineID error:(NSError**)error
2700 {
2701 __block BOOL onList = NO;
2702 __block NSError* reterror = nil;
2703 [self.cuttlefishXPCWrapper fetchAllowedMachineIDsWithContainer:self.containerName
2704 context:self.contextID
2705 reply:^(NSSet<NSString *> * _Nonnull machineIDs, NSError * _Nullable miderror) {
2706 if(miderror) {
2707 secnotice("octagon-metrics", "Failed to fetch allowed machineIDs: %@", miderror);
2708 reterror = miderror;
2709 } else {
2710 if([machineIDs containsObject:machineID]) {
2711 onList = YES;
2712 }
2713 secnotice("octagon-metrics", "MID (%@) on list: %@", machineID, onList ? @"yes" : @"no");
2714 }
2715 }];
2716
2717 if(reterror && error) {
2718 *error = reterror;
2719 }
2720 return onList;
2721 }
2722
2723 - (NSNumber* _Nullable)numberOfPeersInModelWithMachineID:(NSString*)machineID error:(NSError**)error
2724 {
2725 __block NSNumber* ret = nil;
2726 __block NSError* retError = nil;
2727 [self.cuttlefishXPCWrapper trustStatusWithContainer:self.containerName
2728 context:self.contextID
2729 reply:^(TrustedPeersHelperEgoPeerStatus *egoStatus,
2730 NSError *xpcError) {
2731 if(xpcError) {
2732 secnotice("octagon-metrics", "Unable to fetch trust status: %@", xpcError);
2733 retError = xpcError;
2734 } else {
2735 ret = egoStatus.peerCountsByMachineID[machineID] ?: @(0);
2736 secnotice("octagon-metrics", "Number of peers with machineID (%@): %@", machineID, ret);
2737 }
2738 }];
2739
2740 if(retError && error) {
2741 *error = retError;
2742 }
2743
2744 return ret;
2745 }
2746
2747 @end
2748 #endif