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