]> git.saurik.com Git - apple/security.git/blob - keychain/ot/OTSOSUpgradeOperation.m
Security-59754.80.3.tar.gz
[apple/security.git] / keychain / ot / OTSOSUpgradeOperation.m
1
2 #if OCTAGON
3
4 #import <TargetConditionals.h>
5 #import <CloudKit/CloudKit_Private.h>
6 #import <Security/SecKey.h>
7 #import <Security/SecKeyPriv.h>
8
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>
26
27 @interface OTSOSUpgradeOperation ()
28 @property OTOperationDependencies* deps;
29 @property OTDeviceInformation* deviceInfo;
30
31 @property OctagonState* ckksConflictState;
32
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;
35
36 @property OTUpdateTrustedDeviceListOperation* updateOp;
37
38 @property (nullable) NSArray<NSData*>* peerPreapprovedSPKIs;
39 @end
40
41 @implementation OTSOSUpgradeOperation
42 @synthesize nextState = _nextState;
43 @synthesize intendedState = _intendedState;
44
45 - (instancetype)initWithDependencies:(OTOperationDependencies*)dependencies
46 intendedState:(OctagonState*)intendedState
47 ckksConflictState:(OctagonState*)ckksConflictState
48 errorState:(OctagonState*)errorState
49 deviceInfo:(OTDeviceInformation*)deviceInfo
50 policyOverride:(TPPolicyVersion* _Nullable)policyOverride
51 {
52 if((self = [super init])) {
53 _deps = dependencies;
54
55 _intendedState = intendedState;
56 _nextState = errorState;
57 _ckksConflictState = ckksConflictState;
58
59 _deviceInfo = deviceInfo;
60 _policyOverride = policyOverride;
61 }
62 return self;
63 }
64
65 - (NSData *)persistentKeyRef:(SecKeyRef)secKey error:(NSError **)error
66 {
67 CFDataRef cfEncryptionKeyPersistRef = NULL;
68 OSStatus status;
69
70 status = SecKeyCopyPersistentRef(secKey, &cfEncryptionKeyPersistRef);
71 if(status) {
72 if (error) {
73 *error = [NSError errorWithDomain:NSOSStatusErrorDomain code:status userInfo:nil];
74 }
75 } else if (cfEncryptionKeyPersistRef) {
76 if (error) {
77 *error = [NSError errorWithDomain:NSOSStatusErrorDomain code:errSecItemNotFound userInfo:nil];
78 }
79 }
80
81 return CFBridgingRelease(cfEncryptionKeyPersistRef);
82 }
83
84 - (void)groupStart
85 {
86 WEAKIFY(self);
87
88 if(!self.deps.sosAdapter.sosEnabled) {
89 secnotice("octagon-sos", "SOS not enabled on this platform?");
90 self.nextState = OctagonStateBecomeUntrusted;
91 return;
92 }
93
94 secnotice("octagon-sos", "Attempting SOS upgrade");
95
96 NSError* error = nil;
97 SOSCCStatus sosCircleStatus = [self.deps.sosAdapter circleStatus:&error];
98 if(error || sosCircleStatus == kSOSCCError) {
99 secnotice("octagon-sos", "Error fetching circle status: %@", error);
100 self.nextState = OctagonStateBecomeUntrusted;
101 return;
102 }
103
104 // Now that we have some non-error SOS status, write down that we attempted an SOS Upgrade (and make sure the CDP bit is on)
105 NSError* persistError = nil;
106 BOOL persisted = [self.deps.stateHolder persistAccountChanges:^OTAccountMetadataClassC * _Nonnull(OTAccountMetadataClassC * _Nonnull metadata) {
107 metadata.attemptedJoin = OTAccountMetadataClassC_AttemptedAJoinState_ATTEMPTED;
108 metadata.cdpState = OTAccountMetadataClassC_CDPState_ENABLED;
109 return metadata;
110 } error:&persistError];
111 if(!persisted || persistError) {
112 secerror("octagon: failed to save 'attempted join' state: %@", persistError);
113 }
114
115 if(sosCircleStatus != kSOSCCInCircle) {
116 secnotice("octagon-sos", "Device is not in SOS circle (state: %@), quitting SOS upgrade", SOSAccountGetSOSCCStatusString(sosCircleStatus));
117 self.nextState = OctagonStateBecomeUntrusted;
118 return;
119 }
120
121 id<CKKSSelfPeer> sosSelf = [self.deps.sosAdapter currentSOSSelf:&error];
122 if(!sosSelf || error) {
123 secnotice("octagon-sos", "Failed to get the current SOS self: %@", error);
124 [self handlePrepareErrors:error nextExpectedState:OctagonStateBecomeUntrusted];
125 return;
126 }
127
128 // Fetch the persistent references for our signing and encryption keys
129 NSData* signingKeyPersistRef = [self persistentKeyRef:sosSelf.signingKey.secKey error:&error];
130 if (signingKeyPersistRef == NULL) {
131 secnotice("octagon-sos", "Failed to get the persistent ref for our SOS signing key: %@", error);
132 [self handlePrepareErrors:error nextExpectedState:OctagonStateBecomeUntrusted];
133 return;
134 }
135
136 NSData* encryptionKeyPersistRef = [self persistentKeyRef:sosSelf.encryptionKey.secKey error:&error];
137 if (encryptionKeyPersistRef == NULL) {
138 secnotice("octagon-sos", "Failed to get the persistent ref for our SOS encryption key: %@", error);
139 [self handlePrepareErrors:error nextExpectedState:OctagonStateBecomeUntrusted];
140 return;
141 }
142
143 self.finishedOp = [NSBlockOperation blockOperationWithBlock:^{
144 // If we errored in some unknown way, ask to try again!
145 STRONGIFY(self);
146
147 if(self.error) {
148 // Is this a very scary error?
149 bool fatal = false;
150
151 NSTimeInterval ckDelay = CKRetryAfterSecondsForError(self.error);
152 NSTimeInterval cuttlefishDelay = [self.error cuttlefishRetryAfter];
153 NSTimeInterval delay = MAX(ckDelay, cuttlefishDelay);
154 if (delay == 0) {
155 delay = 30;
156 }
157
158 if([self.error isCuttlefishError:CuttlefishErrorResultGraphNotFullyReachable]) {
159 secnotice("octagon-sos", "SOS upgrade error is 'result graph not reachable'; retrying is useless: %@", self.error);
160 fatal = true;
161 }
162
163 if([self.error.domain isEqualToString:TrustedPeersHelperErrorDomain] && self.error.code == TrustedPeersHelperErrorNoPeersPreapprovePreparedIdentity) {
164 secnotice("octagon-sos", "SOS upgrade error is 'no peers preapprove us'; retrying immediately is useless: %@", self.error);
165 fatal = true;
166 }
167
168 if([self.error.domain isEqualToString:TrustedPeersHelperErrorDomain] && self.error.code == TrustedPeersHelperErrorNoPeersPreapprovedBySelf) {
169 secnotice("octagon-sos", "SOS upgrade error is 'we don't preapprove anyone'; retrying immediately is useless: %@", self.error);
170 fatal = true;
171 }
172
173 if(!fatal) {
174 secnotice("octagon-sos", "SOS upgrade error is not fatal: requesting retry in %0.2fs: %@", delay, self.error);
175 [self.deps.flagHandler handlePendingFlag:[[OctagonPendingFlag alloc] initWithFlag:OctagonFlagAttemptSOSUpgrade
176 delayInSeconds:delay]];
177 }
178 }
179 }];
180 [self dependOnBeforeGroupFinished:self.finishedOp];
181
182 secnotice("octagon-sos", "Fetching trusted peers from SOS");
183
184 NSError* sosPreapprovalError = nil;
185 self.peerPreapprovedSPKIs = [OTSOSAdapterHelpers peerPublicSigningKeySPKIsForCircle:self.deps.sosAdapter error:&sosPreapprovalError];
186
187 if(self.peerPreapprovedSPKIs) {
188 secnotice("octagon-sos", "SOS preapproved keys are %@", self.peerPreapprovedSPKIs);
189 } else {
190 secnotice("octagon-sos", "Unable to fetch SOS preapproved keys: %@", sosPreapprovalError);
191 self.error = sosPreapprovalError;
192 [self runBeforeGroupFinished:self.finishedOp];
193 return;
194 }
195
196 NSString* bottleSalt = nil;
197 NSError *authKitError = nil;
198
199 NSString *altDSID = [self.deps.authKitAdapter primaryiCloudAccountAltDSID:&authKitError];
200 if(altDSID){
201 bottleSalt = altDSID;
202 }
203 else {
204 NSError* accountError = nil;
205 OTAccountMetadataClassC* account = [self.deps.stateHolder loadOrCreateAccountMetadata:&accountError];
206
207 if(account && !accountError) {
208 secnotice("octagon", "retrieved account, altdsid is: %@", account.altDSID);
209 bottleSalt = account.altDSID;
210 }
211 if(accountError || !account){
212 secerror("failed to rerieve account object: %@", accountError);
213 }
214 }
215
216 NSError* sosViewError = nil;
217 BOOL safariViewEnabled = [self.deps.sosAdapter safariViewSyncingEnabled:&sosViewError];
218 if(sosViewError) {
219 secnotice("octagon-sos", "Unable to check safari view status: %@", sosViewError);
220 }
221
222 secnotice("octagon-sos", "Safari view is: %@", safariViewEnabled ? @"enabled" : @"disabled");
223
224 [self.deps.cuttlefishXPCWrapper prepareWithContainer:self.deps.containerName
225 context:self.deps.contextID
226 epoch:self.deviceInfo.epoch
227 machineID:self.deviceInfo.machineID
228 bottleSalt:bottleSalt
229 bottleID:[NSUUID UUID].UUIDString
230 modelID:self.deviceInfo.modelID
231 deviceName:self.deviceInfo.deviceName
232 serialNumber:self.self.deviceInfo.serialNumber
233 osVersion:self.deviceInfo.osVersion
234 policyVersion:self.policyOverride
235 policySecrets:nil
236 syncUserControllableViews:safariViewEnabled ?
237 TPPBPeerStableInfo_UserControllableViewStatus_ENABLED :
238 TPPBPeerStableInfo_UserControllableViewStatus_DISABLED
239 signingPrivKeyPersistentRef:signingKeyPersistRef
240 encPrivKeyPersistentRef:encryptionKeyPersistRef
241 reply:^(NSString * _Nullable peerID,
242 NSData * _Nullable permanentInfo,
243 NSData * _Nullable permanentInfoSig,
244 NSData * _Nullable stableInfo,
245 NSData * _Nullable stableInfoSig,
246 TPSyncingPolicy* _Nullable syncingPolicy,
247 NSError * _Nullable error) {
248 STRONGIFY(self);
249
250 [[CKKSAnalytics logger] logResultForEvent:OctagonEventUpgradePrepare hardFailure:true result:error];
251
252 if(error) {
253 secerror("octagon-sos: Error preparing identity: %@", error);
254 self.error = error;
255 [self handlePrepareErrors:error nextExpectedState:OctagonStateBecomeUntrusted];
256
257 [self runBeforeGroupFinished:self.finishedOp];
258 return;
259 }
260
261 secnotice("octagon-sos", "Prepared: %@ %@ %@", peerID, permanentInfo, permanentInfoSig);
262
263 NSError* localError = nil;
264 BOOL persisted = [self.deps.stateHolder persistAccountChanges:^OTAccountMetadataClassC * _Nullable(OTAccountMetadataClassC * _Nonnull metadata) {
265 [metadata setTPSyncingPolicy:syncingPolicy];
266 return metadata;
267 } error:&localError];
268
269 if(!persisted || localError) {
270 secerror("octagon-ckks: Error persisting new views and policy: %@", localError);
271 self.error = localError;
272 [self handlePrepareErrors:error nextExpectedState:OctagonStateBecomeUntrusted];
273 [self runBeforeGroupFinished:self.finishedOp];
274 return;
275 }
276
277 [self.deps.viewManager setCurrentSyncingPolicy:syncingPolicy];
278
279 [self afterPrepare];
280 }];
281 }
282
283 - (void)afterPrepare
284 {
285 WEAKIFY(self);
286 [self.deps.cuttlefishXPCWrapper preflightPreapprovedJoinWithContainer:self.deps.containerName
287 context:self.deps.contextID
288 preapprovedKeys:self.peerPreapprovedSPKIs
289 reply:^(BOOL launchOkay, NSError * _Nullable error) {
290 STRONGIFY(self);
291
292 [[CKKSAnalytics logger] logResultForEvent:OctagonEventUpgradePreflightPreapprovedJoin hardFailure:true result:error];
293 if(error) {
294 secerror("octagon-sos: preflightPreapprovedJoin failed: %@", error);
295
296 self.error = error;
297 self.nextState = OctagonStateBecomeUntrusted;
298 [self runBeforeGroupFinished:self.finishedOp];
299 return;
300 }
301
302 if(!launchOkay) {
303 secnotice("octagon-sos", "TPH believes a preapprovedJoin will fail; aborting.");
304 self.nextState = OctagonStateBecomeUntrusted;
305 [self runBeforeGroupFinished:self.finishedOp];
306 return;
307 }
308
309 secnotice("octagon-sos", "TPH believes a preapprovedJoin might succeed; continuing.");
310 [self afterPreflight];
311 }];
312 }
313
314 - (void)afterPreflight
315 {
316 WEAKIFY(self);
317 self.updateOp = [[OTUpdateTrustedDeviceListOperation alloc] initWithDependencies:self.deps
318 intendedState:OctagonStateReady
319 listUpdatesState:OctagonStateReady
320 errorState:OctagonStateError
321 retryFlag:nil];
322 self.updateOp.logForUpgrade = YES;
323 [self runBeforeGroupFinished:self.updateOp];
324
325 CKKSResultOperation* afterUpdate = [CKKSResultOperation named:@"after-update"
326 withBlock:^{
327 STRONGIFY(self);
328 [self afterUpdate];
329 }];
330 [afterUpdate addDependency:self.updateOp];
331 [self runBeforeGroupFinished:afterUpdate];
332 }
333
334 - (void)handlePrepareErrors:(NSError *)error nextExpectedState:(OctagonState*)nextState
335 {
336 secnotice("octagon-sos", "handling prepare error: %@", error);
337
338 if ([self.deps.lockStateTracker isLockedError:error]) {
339 self.nextState = OctagonStateWaitForUnlock;
340 } else {
341 self.nextState = nextState;
342 }
343 self.error = error;
344 }
345
346 - (void)afterUpdate
347 {
348 if (self.updateOp.error) {
349 [self handlePrepareErrors:self.updateOp.error nextExpectedState:self.nextState];
350 [self runBeforeGroupFinished:self.finishedOp];
351 return;
352 }
353 secnotice("octagon-sos", "Successfully saved machineID allow-list");
354 [self afterSuccessfulAllowList];
355 }
356
357 - (void)requestSilentEscrowUpdate
358 {
359 NSError* error = nil;
360 id<SecEscrowRequestable> request = [self.deps.escrowRequestClass request:&error];
361 if(!request || error) {
362 secnotice("octagon-sos", "Unable to acquire a EscrowRequest object: %@", error);
363 return;
364 }
365
366 [request triggerEscrowUpdate:@"octagon-sos" error:&error];
367 [[CKKSAnalytics logger] logResultForEvent:OctagonEventUpgradeSilentEscrow hardFailure:true result:error];
368
369 if(error) {
370 secnotice("octagon-sos", "Unable to request silent escrow update: %@", error);
371 } else{
372 secnotice("octagon-sos", "Requested silent escrow update");
373 }
374 }
375
376 - (void)afterSuccessfulAllowList
377 {
378 WEAKIFY(self);
379
380 OTFetchCKKSKeysOperation* fetchKeysOp = [[OTFetchCKKSKeysOperation alloc] initWithDependencies:self.deps
381 refetchNeeded:NO];
382 [self runBeforeGroupFinished:fetchKeysOp];
383
384 secnotice("octagon-sos", "Fetching keys from CKKS");
385 CKKSResultOperation* proceedWithKeys = [CKKSResultOperation named:@"sos-upgrade-with-keys"
386 withBlock:^{
387 STRONGIFY(self);
388 [self proceedWithKeys:fetchKeysOp.viewKeySets pendingTLKShares:fetchKeysOp.pendingTLKShares];
389 }];
390 [proceedWithKeys addDependency:fetchKeysOp];
391 [self runBeforeGroupFinished:proceedWithKeys];
392 }
393
394 - (void)proceedWithKeys:(NSArray<CKKSKeychainBackedKeySet*>*)viewKeySets pendingTLKShares:(NSArray<CKKSTLKShare*>*)pendingTLKShares
395 {
396 WEAKIFY(self);
397
398 secnotice("octagon-sos", "Beginning SOS upgrade with %d key sets and %d SOS peers", (int)viewKeySets.count, (int)self.peerPreapprovedSPKIs.count);
399
400 [self.deps.cuttlefishXPCWrapper attemptPreapprovedJoinWithContainer:self.deps.containerName
401 context:self.deps.contextID
402 ckksKeys:viewKeySets
403 tlkShares:pendingTLKShares
404 preapprovedKeys:self.peerPreapprovedSPKIs
405 reply:^(NSString * _Nullable peerID,
406 NSArray<CKRecord*>* keyHierarchyRecords,
407 TPSyncingPolicy* _Nullable syncingPolicy,
408 NSError * _Nullable error) {
409 STRONGIFY(self);
410
411 [[CKKSAnalytics logger] logResultForEvent:OctagonEventUpgradePreapprovedJoin hardFailure:true result:error];
412 if(error) {
413 secerror("octagon-sos: attemptPreapprovedJoin failed: %@", error);
414
415 if ([error isCuttlefishError:CuttlefishErrorKeyHierarchyAlreadyExists]) {
416 secnotice("octagon-ckks", "A CKKS key hierarchy is out of date; requesting reset");
417 self.nextState = self.ckksConflictState;
418 } else {
419 self.error = error;
420 self.nextState = OctagonStateBecomeUntrusted;
421 }
422 [self runBeforeGroupFinished:self.finishedOp];
423 return;
424 }
425
426 [self requestSilentEscrowUpdate];
427
428 secerror("octagon-sos: attemptPreapprovedJoin succeded");
429 [self.deps.viewManager setCurrentSyncingPolicy:syncingPolicy];
430
431 NSError* localError = nil;
432 BOOL persisted = [self.deps.stateHolder persistAccountChanges:^OTAccountMetadataClassC * _Nonnull(OTAccountMetadataClassC * _Nonnull metadata) {
433 metadata.trustState = OTAccountMetadataClassC_TrustState_TRUSTED;
434 metadata.peerID = peerID;
435
436 [metadata setTPSyncingPolicy:syncingPolicy];
437 return metadata;
438 } error:&localError];
439
440 if(!persisted || localError) {
441 secnotice("octagon-sos", "Couldn't persist results: %@", localError);
442 self.error = localError;
443 self.nextState = OctagonStateError;
444 [self runBeforeGroupFinished:self.finishedOp];
445 return;
446 }
447
448 self.nextState = self.intendedState;
449
450 // Tell CKKS about our shiny new records!
451 for (id key in self.deps.viewManager.views) {
452 CKKSKeychainView* view = self.deps.viewManager.views[key];
453 secnotice("octagon-ckks", "Providing ck records (from sos upgrade) to %@", view);
454 [view receiveTLKUploadRecords: keyHierarchyRecords];
455 }
456
457 [self runBeforeGroupFinished:self.finishedOp];
458 }];
459 }
460
461 @end
462
463 #endif // OCTAGON