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