]> git.saurik.com Git - apple/security.git/blob - keychain/escrowrequest/operations/EscrowRequestPerformEscrowEnrollOperation.m
Security-59754.60.13.tar.gz
[apple/security.git] / keychain / escrowrequest / operations / EscrowRequestPerformEscrowEnrollOperation.m
1
2 #import <CoreCDP/CDPError.h>
3 #import <CoreCDP/CDPStateController.h>
4 #import <CloudServices/CloudServices.h>
5
6 #import "utilities/debugging.h"
7
8 #import "keychain/ot/ObjCImprovements.h"
9
10 #import "keychain/escrowrequest/EscrowRequestController.h"
11 #import "keychain/escrowrequest/operations/EscrowRequestPerformEscrowEnrollOperation.h"
12 #import "keychain/escrowrequest/generated_source/SecEscrowPendingRecord.h"
13 #import "keychain/escrowrequest/SecEscrowPendingRecord+KeychainSupport.h"
14
15 #import "keychain/ckks/CKKSLockStateTracker.h"
16
17 @interface EscrowRequestPerformEscrowEnrollOperation ()
18 @property bool enforceRateLimiting;
19 @property CKKSLockStateTracker* lockStateTracker;
20 @end
21
22 @implementation EscrowRequestPerformEscrowEnrollOperation
23 @synthesize nextState = _nextState;
24 @synthesize intendedState = _intendedState;
25
26 - (instancetype)initWithIntendedState:(OctagonState*)intendedState
27 errorState:(OctagonState*)errorState
28 enforceRateLimiting:(bool)enforceRateLimiting
29 lockStateTracker:(CKKSLockStateTracker*)lockStateTracker
30 {
31 if((self = [super init])) {
32 _intendedState = intendedState;
33 _nextState = errorState;
34 _enforceRateLimiting = enforceRateLimiting;
35 _lockStateTracker = lockStateTracker;
36 }
37 return self;
38 }
39
40 - (BOOL)checkFatalError:(NSError *)error
41 {
42 if (error == nil) {
43 return NO;
44 }
45
46 if (error.code == kSecureBackupInternalError && [error.domain isEqualToString:kSecureBackupErrorDomain]) { // SOS peer ID mismatch!!!, the error code is wrong though
47 return YES;
48 }
49
50 if ([error.domain isEqualToString:kSecureBackupErrorDomain] && error.code == kSecureBackupNotInSyncCircleError) {
51 // One or more peers is missing (likely the SOS peer)
52 return YES;
53 }
54
55 if( [error.domain isEqualToString:CDPStateErrorDomain] && error.code == CDPStateErrorNoPeerIdFound) {
56 // CDP is unhappy about the self peer. I don't understand why we get both this and kSecureBackupNotInSyncCircleError
57 return YES;
58 }
59
60 return NO;
61 }
62
63 - (void)groupStart
64 {
65 secnotice("escrowrequest", "Attempting to escrow any pending prerecords");
66
67 NSError* error = nil;
68 NSArray<SecEscrowPendingRecord*>* records = [SecEscrowPendingRecord loadAllFromKeychain:&error];
69 if(error && !([error.domain isEqualToString:NSOSStatusErrorDomain] && error.code == errSecItemNotFound)) {
70 secnotice("escrowrequest", "failed to fetch records from keychain: %@", error);
71 self.error = error;
72
73 if([self.lockStateTracker isLockedError: error]) {
74 secnotice("escrowrequest", "Will retry after unlock");
75 self.nextState = EscrowRequestStateWaitForUnlock;
76 } else {
77 self.nextState = EscrowRequestStateNothingToDo;
78 }
79 return;
80 }
81 error = nil;
82
83 SecEscrowPendingRecord* record = nil;
84
85 for(SecEscrowPendingRecord* existingRecord in records) {
86 if(existingRecord.uploadCompleted) {
87 secnotice("escrowrequest", "Skipping completed escrow request (%@)", existingRecord);
88 continue;
89 }
90
91 if(self.enforceRateLimiting && [existingRecord escrowAttemptedWithinLastSeconds:5*60]) {
92 secnotice("escrowrequest", "Skipping pending escrow request (%@); it's rate limited", existingRecord);
93 continue;
94 }
95
96 if(existingRecord.hasSerializedPrerecord) {
97 record = existingRecord;
98 break;
99 }
100 }
101
102 if(record == nil && record.uuid == nil) {
103 secnotice("escrowrequest", "No pending escrow request has a prerecord");
104 self.nextState = EscrowRequestStateNothingToDo;
105 return;
106 }
107
108 secnotice("escrowrequest", "escrow request have pre-record uploading: %@", record.uuid);
109
110 // Ask CDP to escrow the escrow-record. Use the "finish operation" trick to wait
111 CKKSResultOperation* finishOp = [CKKSResultOperation named:@"cdp-finish" withBlock:^{}];
112 [self dependOnBeforeGroupFinished: finishOp];
113
114 /*
115 * Update and save the preRecord an extra time (before we crash)
116 */
117
118 record.lastEscrowAttemptTime = (uint64_t) ([[NSDate date] timeIntervalSince1970] * 1000);
119 record.uploadRetries += 1;
120
121 // Save the last escrow attempt time to keychain
122 NSError* saveError = nil;
123 [record saveToKeychain:&saveError];
124 if(saveError) {
125 secerror("escrowrequest: unable to save last escrow time: %@", error);
126 }
127
128 WEAKIFY(self);
129
130 [EscrowRequestPerformEscrowEnrollOperation cdpUploadPrerecord:record
131 secretType:CDPComplexDeviceSecretType
132 reply:^(BOOL didUpdate, NSError * _Nullable error) {
133 STRONGIFY(self);
134
135 //* check for fatal errors that definatly should make us give up
136 if ([self checkFatalError:error]) {
137 secerror("escrowrequest: fatal error for record: %@, dropping: %@", record.uuid, error);
138 NSError* deleteError = nil;
139 [record deleteFromKeychain:&deleteError];
140 if(saveError) {
141 secerror("escrowrequest: unable to delete last escrow time: %@", deleteError);
142 }
143
144 self.error = error;
145 [self.operationQueue addOperation:finishOp];
146 return;
147 }
148
149 if(error || !didUpdate) {
150 secerror("escrowrequest: prerecord %@ upload failed: %@", record.uuid, error);
151
152 self.error = error;
153 [self.operationQueue addOperation:finishOp];
154 return;
155 }
156
157 self.numberOfRecordsUploaded = 1;
158 secerror("escrowrequest: prerecord %@ upload succeeded", record.uuid);
159
160 record.uploadCompleted = true;
161 NSError* saveError = nil;
162 [record saveToKeychain:&saveError];
163 if(saveError) {
164 secerror("escrowrequest: unable to save last escrow time: %@", error);
165 }
166
167 if(saveError) {
168 secerror("escrowrequest: unable to save completion of prerecord %@ in keychain", record.uuid);
169 }
170
171 self.nextState = EscrowRequestStateNothingToDo;
172 [self.operationQueue addOperation:finishOp];
173 }];
174 }
175
176 + (void)cdpUploadPrerecord:(SecEscrowPendingRecord*)recordToSend
177 secretType:(CDPDeviceSecretType)secretType
178 reply:(void (^)(BOOL didUpdate, NSError* _Nullable error))reply
179 {
180 CDPStateController *controller = [[CDPStateController alloc] initWithContext:nil];
181 [controller attemptToEscrowPreRecord:@"unknown-local-passcode"
182 preRecordUUID:recordToSend.uuid
183 secretType:secretType
184 completion:^(BOOL didUpdate, NSError *error) {
185 reply(didUpdate, error);
186 }];
187 }
188
189 @end