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