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