4 #import <TargetConditionals.h>
5 #import <CloudKit/CloudKit_Private.h>
6 #import <Security/SecKey.h>
7 #import <Security/SecKeyPriv.h>
9 #include "keychain/SecureObjectSync/SOSAccount.h"
10 #import "keychain/escrowrequest/Framework/SecEscrowRequest.h"
11 #import "keychain/ot/ObjCImprovements.h"
12 #import "keychain/ot/OTFetchViewsOperation.h"
13 #import "keychain/ot/OTSOSUpgradeOperation.h"
14 #import "keychain/ot/OTOperationDependencies.h"
15 #import "keychain/ot/OTCuttlefishAccountStateHolder.h"
16 #import "keychain/ot/OTUpdateTrustedDeviceListOperation.h"
17 #import "keychain/TrustedPeersHelper/TrustedPeersHelperProtocol.h"
18 #import "keychain/ot/categories/OTAccountMetadataClassC+KeychainSupport.h"
19 #import "keychain/ot/OTFetchCKKSKeysOperation.h"
20 #import "keychain/ot/OTAuthKitAdapter.h"
21 #import "keychain/ckks/CKKSAnalytics.h"
22 #import "keychain/ckks/CKKSKeychainView.h"
23 #import "keychain/ckks/CloudKitCategories.h"
24 #import <AuthKit/AKError.h>
25 #import <os/feature_private.h>
27 @interface OTSOSUpgradeOperation ()
28 @property OTOperationDependencies* deps;
29 @property OTDeviceInformation* deviceInfo;
31 @property OctagonState* ckksConflictState;
33 // Since we're making callback based async calls, use this operation trick to hold off the ending of this operation
34 @property NSOperation* finishedOp;
36 @property OTUpdateTrustedDeviceListOperation* updateOp;
39 @implementation OTSOSUpgradeOperation
40 @synthesize nextState = _nextState;
41 @synthesize intendedState = _intendedState;
43 - (instancetype)initWithDependencies:(OTOperationDependencies*)dependencies
44 intendedState:(OctagonState*)intendedState
45 ckksConflictState:(OctagonState*)ckksConflictState
46 errorState:(OctagonState*)errorState
47 deviceInfo:(OTDeviceInformation*)deviceInfo
49 if((self = [super init])) {
52 _intendedState = intendedState;
53 _nextState = errorState;
54 _ckksConflictState = ckksConflictState;
56 _deviceInfo = deviceInfo;
61 - (NSData *)persistentKeyRef:(SecKeyRef)secKey error:(NSError **)error
63 CFDataRef cfEncryptionKeyPersistRef = NULL;
66 status = SecKeyCopyPersistentRef(secKey, &cfEncryptionKeyPersistRef);
69 *error = [NSError errorWithDomain:NSOSStatusErrorDomain code:status userInfo:nil];
71 } else if (cfEncryptionKeyPersistRef) {
73 *error = [NSError errorWithDomain:NSOSStatusErrorDomain code:errSecItemNotFound userInfo:nil];
77 return CFBridgingRelease(cfEncryptionKeyPersistRef);
84 if(!self.deps.sosAdapter.sosEnabled) {
85 secnotice("octagon-sos", "SOS not enabled on this platform?");
86 self.nextState = OctagonStateBecomeUntrusted;
90 secnotice("octagon-sos", "Attempting SOS upgrade");
93 SOSCCStatus sosCircleStatus = [self.deps.sosAdapter circleStatus:&error];
94 if(error || sosCircleStatus == kSOSCCError) {
95 secnotice("octagon-sos", "Error fetching circle status: %@", error);
96 self.nextState = OctagonStateBecomeUntrusted;
100 if(sosCircleStatus != kSOSCCInCircle) {
101 secnotice("octagon-sos", "Device is not in SOS circle (state: %@), quitting SOS upgrade", SOSAccountGetSOSCCStatusString(sosCircleStatus));
102 self.nextState = OctagonStateBecomeUntrusted;
106 id<CKKSSelfPeer> sosSelf = [self.deps.sosAdapter currentSOSSelf:&error];
107 if(!sosSelf || error) {
108 secnotice("octagon-sos", "Failed to get the current SOS self: %@", error);
109 [self handlePrepareErrors:error nextExpectedState:OctagonStateBecomeUntrusted];
113 // Fetch the persistent references for our signing and encryption keys
114 NSData* signingKeyPersistRef = [self persistentKeyRef:sosSelf.signingKey.secKey error:&error];
115 if (signingKeyPersistRef == NULL) {
116 secnotice("octagon-sos", "Failed to get the persistent ref for our SOS signing key: %@", error);
117 [self handlePrepareErrors:error nextExpectedState:OctagonStateBecomeUntrusted];
121 NSData* encryptionKeyPersistRef = [self persistentKeyRef:sosSelf.encryptionKey.secKey error:&error];
122 if (encryptionKeyPersistRef == NULL) {
123 secnotice("octagon-sos", "Failed to get the persistent ref for our SOS encryption key: %@", error);
124 [self handlePrepareErrors:error nextExpectedState:OctagonStateBecomeUntrusted];
128 self.finishedOp = [NSBlockOperation blockOperationWithBlock:^{
129 // If we errored in some unknown way, ask to try again!
133 // Is this a very scary error?
136 NSTimeInterval ckdelay = CKRetryAfterSecondsForError(self.error);
137 NSTimeInterval delay = 30;
142 if([self.error isCuttlefishError:CuttlefishErrorResultGraphNotFullyReachable]) {
143 secnotice("octagon-sos", "SOS upgrade error is 'result graph not reachable'; retrying is useless: %@", self.error);
147 if([self.error.domain isEqualToString:TrustedPeersHelperErrorDomain] && self.error.code == TrustedPeersHelperErrorNoPeersPreapprovePreparedIdentity) {
148 secnotice("octagon-sos", "SOS upgrade error is 'no peers preapprove us'; retrying immediately is useless: %@", self.error);
153 secnotice("octagon-sos", "SOS upgrade error is not fatal: requesting retry in %0.2fs: %@", delay, self.error);
154 [self.deps.flagHandler handlePendingFlag:[[OctagonPendingFlag alloc] initWithFlag:OctagonFlagAttemptSOSUpgrade
155 delayInSeconds:delay]];
159 [self dependOnBeforeGroupFinished:self.finishedOp];
161 NSString* bottleSalt = nil;
163 if(self.deps.authKitAdapter.primaryiCloudAccountAltDSID){
164 bottleSalt = self.deps.authKitAdapter.primaryiCloudAccountAltDSID;
167 NSError* accountError = nil;
168 OTAccountMetadataClassC* account = [self.deps.stateHolder loadOrCreateAccountMetadata:&accountError];
170 if(account && !accountError) {
171 secnotice("octagon", "retrieved account, altdsid is: %@", account.altDSID);
172 bottleSalt = account.altDSID;
174 if(accountError || !account){
175 secerror("failed to rerieve account object: %@", accountError);
179 NSError* persistError = nil;
180 BOOL persisted = [self.deps.stateHolder persistOctagonJoinAttempt:OTAccountMetadataClassC_AttemptedAJoinState_ATTEMPTED error:&persistError];
181 if(!persisted || persistError) {
182 secerror("octagon: failed to save 'attempted join' state: %@", persistError);
185 [[self.deps.cuttlefishXPC remoteObjectProxyWithErrorHandler:^(NSError * _Nonnull error) {
187 secerror("octagon: Can't talk with TrustedPeersHelper: %@", error);
189 [self runBeforeGroupFinished:self.finishedOp];
191 }] prepareWithContainer:self.deps.containerName
192 context:self.deps.contextID
193 epoch:self.deviceInfo.epoch
194 machineID:self.deviceInfo.machineID
195 bottleSalt:bottleSalt
196 bottleID:[NSUUID UUID].UUIDString
197 modelID:self.deviceInfo.modelID
198 deviceName:self.deviceInfo.deviceName
199 serialNumber:self.self.deviceInfo.serialNumber
200 osVersion:self.deviceInfo.osVersion
203 signingPrivKeyPersistentRef:signingKeyPersistRef
204 encPrivKeyPersistentRef:encryptionKeyPersistRef
205 reply:^(NSString * _Nullable peerID,
206 NSData * _Nullable permanentInfo,
207 NSData * _Nullable permanentInfoSig,
208 NSData * _Nullable stableInfo,
209 NSData * _Nullable stableInfoSig,
210 NSError * _Nullable error) {
213 [[CKKSAnalytics logger] logResultForEvent:OctagonEventUpgradePrepare hardFailure:true result:error];
216 secerror("octagon-sos: Error preparing identity: %@", error);
218 [self handlePrepareErrors:error nextExpectedState:OctagonStateBecomeUntrusted];
220 [self runBeforeGroupFinished:self.finishedOp];
222 secnotice("octagon-sos", "Prepared: %@ %@ %@", peerID, permanentInfo, permanentInfoSig);
233 [[self.deps.cuttlefishXPC remoteObjectProxyWithErrorHandler:^(NSError * _Nonnull error) {
235 secerror("octagon-sos: Can't talk with TrustedPeersHelper: %@", error);
236 [[CKKSAnalytics logger] logResultForEvent:OctagonEventUpgradePreflightPreapprovedJoin hardFailure:true result:error];
238 [self runBeforeGroupFinished:self.finishedOp];
240 }] preflightPreapprovedJoinWithContainer:self.deps.containerName
241 context:self.deps.contextID
242 reply:^(BOOL launchOkay, NSError * _Nullable error) {
245 [[CKKSAnalytics logger] logResultForEvent:OctagonEventUpgradePreflightPreapprovedJoin hardFailure:true result:error];
247 secerror("octagon-sos: preflightPreapprovedJoin failed: %@", error);
250 self.nextState = OctagonStateBecomeUntrusted;
251 [self runBeforeGroupFinished:self.finishedOp];
256 secnotice("octagon-sos", "TPH believes a preapprovedJoin will fail; aborting.");
257 self.nextState = OctagonStateBecomeUntrusted;
258 [self runBeforeGroupFinished:self.finishedOp];
262 secnotice("octagon-sos", "TPH believes a preapprovedJoin might succeed; continuing.");
263 [self afterPreflight];
267 - (void)afterPreflight
270 self.updateOp = [[OTUpdateTrustedDeviceListOperation alloc] initWithDependencies:self.deps
271 intendedState:OctagonStateReady
272 listUpdatesState:OctagonStateReady
273 errorState:OctagonStateError
275 self.updateOp.logForUpgrade = YES;
276 [self runBeforeGroupFinished:self.updateOp];
278 CKKSResultOperation* afterUpdate = [CKKSResultOperation named:@"after-update"
283 [afterUpdate addDependency:self.updateOp];
284 [self runBeforeGroupFinished:afterUpdate];
287 - (void)handlePrepareErrors:(NSError *)error nextExpectedState:(OctagonState*)nextState
289 secnotice("octagon-sos", "handling prepare error: %@", error);
291 if ([self.deps.lockStateTracker isLockedError:error]) {
292 self.nextState = OctagonStateWaitForUnlock;
294 self.nextState = nextState;
301 if (self.updateOp.error) {
302 [self handlePrepareErrors:self.updateOp.error nextExpectedState:self.nextState];
303 [self runBeforeGroupFinished:self.finishedOp];
306 secnotice("octagon-sos", "Successfully saved machineID allow-list");
307 [self afterSuccessfulAllowList];
310 - (void)requestSilentEscrowUpdate
312 NSError* error = nil;
313 id<SecEscrowRequestable> request = [self.deps.escrowRequestClass request:&error];
314 if(!request || error) {
315 secnotice("octagon-sos", "Unable to acquire a EscrowRequest object: %@", error);
319 [request triggerEscrowUpdate:@"octagon-sos" error:&error];
320 [[CKKSAnalytics logger] logResultForEvent:OctagonEventUpgradeSilentEscrow hardFailure:true result:error];
323 secnotice("octagon-sos", "Unable to request silent escrow update: %@", error);
325 secnotice("octagon-sos", "Requested silent escrow update");
329 - (void)afterSuccessfulAllowList
333 OTFetchViewsOperation *fetchViews = [[OTFetchViewsOperation alloc] initWithDependencies:self.deps];
334 [self runBeforeGroupFinished:fetchViews];
336 OTFetchCKKSKeysOperation* fetchKeysOp = [[OTFetchCKKSKeysOperation alloc] initWithDependencies:self.deps];
337 [fetchKeysOp addDependency:fetchViews];
338 [self runBeforeGroupFinished:fetchKeysOp];
340 secnotice("octagon-sos", "Fetching keys from CKKS");
341 CKKSResultOperation* proceedWithKeys = [CKKSResultOperation named:@"sos-upgrade-with-keys"
344 [self proceedWithKeys:fetchKeysOp.viewKeySets pendingTLKShares:fetchKeysOp.pendingTLKShares];
346 [proceedWithKeys addDependency:fetchKeysOp];
347 [self runBeforeGroupFinished:proceedWithKeys];
350 - (void)proceedWithKeys:(NSArray<CKKSKeychainBackedKeySet*>*)viewKeySets pendingTLKShares:(NSArray<CKKSTLKShare*>*)pendingTLKShares
354 secnotice("octagon-sos", "Fetching trusted peers from SOS");
356 NSError* error = nil;
357 NSSet<id<CKKSRemotePeerProtocol>>* peerSet = [self.deps.sosAdapter fetchTrustedPeers:&error];
359 if(!peerSet || error) {
360 secerror("octagon-sos: Can't fetch trusted peers; stopping upgrade: %@", error);
362 self.nextState = OctagonStateBecomeUntrusted;
363 [self runBeforeGroupFinished:self.finishedOp];
367 NSArray<NSData*>* publicSigningSPKIs = [OTSOSActualAdapter peerPublicSigningKeySPKIs:peerSet];
368 secnotice("octagon-sos", "Creating SOS preapproved keys as %@", publicSigningSPKIs);
370 secnotice("octagon-sos", "Beginning SOS upgrade with %d key sets and %d SOS peers", (int)viewKeySets.count, (int)peerSet.count);
372 [[self.deps.cuttlefishXPC remoteObjectProxyWithErrorHandler:^(NSError * _Nonnull error) {
374 secerror("octagon-sos: Can't talk with TrustedPeersHelper: %@", error);
375 [[CKKSAnalytics logger] logResultForEvent:OctagonEventUpgradeSilentEscrow hardFailure:true result:error];
377 [self runBeforeGroupFinished:self.finishedOp];
379 }] attemptPreapprovedJoinWithContainer:self.deps.containerName
380 context:self.deps.contextID
382 tlkShares:pendingTLKShares
383 preapprovedKeys:publicSigningSPKIs
384 reply:^(NSString * _Nullable peerID, NSArray<CKRecord*>* keyHierarchyRecords, NSError * _Nullable error) {
387 [[CKKSAnalytics logger] logResultForEvent:OctagonEventUpgradePreapprovedJoin hardFailure:true result:error];
389 secerror("octagon-sos: attemptPreapprovedJoin failed: %@", error);
391 if ([error isCuttlefishError:CuttlefishErrorKeyHierarchyAlreadyExists]) {
392 secnotice("octagon-ckks", "A CKKS key hierarchy is out of date; requesting reset");
393 self.nextState = self.ckksConflictState;
396 self.nextState = OctagonStateBecomeUntrusted;
398 [self runBeforeGroupFinished:self.finishedOp];
402 [self requestSilentEscrowUpdate];
404 secerror("octagon-sos: attemptPreapprovedJoin succeded");
406 NSError* localError = nil;
407 BOOL persisted = [self.deps.stateHolder persistAccountChanges:^OTAccountMetadataClassC * _Nonnull(OTAccountMetadataClassC * _Nonnull metadata) {
408 metadata.trustState = OTAccountMetadataClassC_TrustState_TRUSTED;
409 metadata.peerID = peerID;
411 } error:&localError];
413 if(!persisted || localError) {
414 secnotice("octagon-sos", "Couldn't persist results: %@", localError);
415 self.error = localError;
416 self.nextState = OctagonStateError;
417 [self runBeforeGroupFinished:self.finishedOp];
421 self.nextState = self.intendedState;
423 // Tell CKKS about our shiny new records!
424 for (id key in self.deps.viewManager.views) {
425 CKKSKeychainView* view = self.deps.viewManager.views[key];
426 secnotice("octagon-ckks", "Providing ck records (from sos upgrade) to %@", view);
427 [view receiveTLKUploadRecords: keyHierarchyRecords];
430 [self runBeforeGroupFinished:self.finishedOp];