]> git.saurik.com Git - apple/security.git/blob - keychain/escrowrequest/operations/EscrowRequestPerformEscrowEnrollOperation.m
Security-59306.11.20.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(self.enforceRateLimiting && [existingRecord escrowAttemptedWithinLastSeconds:5*60]) {
87 secnotice("escrowrequest", "Skipping pending escrow request (%@); it's rate limited", existingRecord);
88 continue;
89 }
90
91 if(existingRecord.hasSerializedPrerecord) {
92 record = existingRecord;
93 break;
94 }
95 }
96
97 if(record == nil && record.uuid == nil) {
98 secnotice("escrowrequest", "No pending escrow request has a prerecord");
99 self.nextState = EscrowRequestStateNothingToDo;
100 return;
101 }
102
103 secnotice("escrowrequest", "escrow request have pre-record uploading: %@", record.uuid);
104
105 // Ask CDP to escrow the escrow-record. Use the "finish operation" trick to wait
106 CKKSResultOperation* finishOp = [CKKSResultOperation named:@"cdp-finish" withBlock:^{}];
107 [self dependOnBeforeGroupFinished: finishOp];
108
109 /*
110 * Update and save the preRecord an extra time (before we crash)
111 */
112
113 record.lastEscrowAttemptTime = (uint64_t) ([[NSDate date] timeIntervalSince1970] * 1000);
114 record.uploadRetries += 1;
115
116 // Save the last escrow attempt time to keychain
117 NSError* saveError = nil;
118 [record saveToKeychain:&saveError];
119 if(saveError) {
120 secerror("escrowrequest: unable to save last escrow time: %@", error);
121 }
122
123 WEAKIFY(self);
124
125 [EscrowRequestPerformEscrowEnrollOperation cdpUploadPrerecord:record
126 secretType:CDPComplexDeviceSecretType
127 reply:^(BOOL didUpdate, NSError * _Nullable error) {
128 STRONGIFY(self);
129
130 //* check for fatal errors that definatly should make us give up
131 if ([self checkFatalError:error]) {
132 secerror("escrowrequest: fatal error for record: %@, dropping: %@", record.uuid, error);
133 NSError* deleteError = nil;
134 [record deleteFromKeychain:&deleteError];
135 if(saveError) {
136 secerror("escrowrequest: unable to delete last escrow time: %@", deleteError);
137 }
138
139 self.error = error;
140 [self.operationQueue addOperation:finishOp];
141 return;
142 }
143
144 if(error || !didUpdate) {
145 secerror("escrowrequest: prerecord %@ upload failed: %@", record.uuid, error);
146
147 self.error = error;
148 [self.operationQueue addOperation:finishOp];
149 return;
150 }
151
152 self.numberOfRecordsUploaded = 1;
153 secerror("escrowrequest: prerecord %@ upload succeeded", record.uuid);
154
155 record.uploadCompleted = true;
156 NSError* saveError = nil;
157 [record saveToKeychain:&saveError];
158 if(saveError) {
159 secerror("escrowrequest: unable to save last escrow time: %@", error);
160 }
161
162 if(saveError) {
163 secerror("escrowrequest: unable to save completion of prerecord %@ in keychain", record.uuid);
164 }
165
166 self.nextState = EscrowRequestStateNothingToDo;
167 [self.operationQueue addOperation:finishOp];
168 }];
169 }
170
171 + (void)cdpUploadPrerecord:(SecEscrowPendingRecord*)recordToSend
172 secretType:(CDPDeviceSecretType)secretType
173 reply:(void (^)(BOOL didUpdate, NSError* _Nullable error))reply
174 {
175 CDPStateController *controller = [[CDPStateController alloc] initWithContext:nil];
176 [controller attemptToEscrowPreRecord:@"unknown-local-passcode"
177 preRecordUUID:recordToSend.uuid
178 secretType:secretType
179 completion:^(BOOL didUpdate, NSError *error) {
180 reply(didUpdate, error);
181 }];
182 }
183
184 @end