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