2 #import <CoreCDP/CDPError.h>
3 #import <CoreCDP/CDPStateController.h>
4 #import <CloudServices/CloudServices.h>
6 #import "utilities/debugging.h"
8 #import "keychain/ot/ObjCImprovements.h"
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"
15 #import "keychain/ckks/CKKSLockStateTracker.h"
17 @interface EscrowRequestPerformEscrowEnrollOperation ()
18 @property bool enforceRateLimiting;
19 @property CKKSLockStateTracker* lockStateTracker;
22 @implementation EscrowRequestPerformEscrowEnrollOperation
23 @synthesize nextState = _nextState;
24 @synthesize intendedState = _intendedState;
26 - (instancetype)initWithIntendedState:(OctagonState*)intendedState
27 errorState:(OctagonState*)errorState
28 enforceRateLimiting:(bool)enforceRateLimiting
29 lockStateTracker:(CKKSLockStateTracker*)lockStateTracker
31 if((self = [super init])) {
32 _intendedState = intendedState;
33 _nextState = errorState;
34 _enforceRateLimiting = enforceRateLimiting;
35 _lockStateTracker = lockStateTracker;
40 - (BOOL)checkFatalError:(NSError *)error
46 if (error.code == kSecureBackupInternalError && [error.domain isEqualToString:kSecureBackupErrorDomain]) { // SOS peer ID mismatch!!!, the error code is wrong though
50 if ([error.domain isEqualToString:kSecureBackupErrorDomain] && error.code == kSecureBackupNotInSyncCircleError) {
51 // One or more peers is missing (likely the SOS peer)
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
65 secnotice("escrowrequest", "Attempting to escrow any pending prerecords");
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);
73 if([self.lockStateTracker isLockedError: error]) {
74 secnotice("escrowrequest", "Will retry after unlock");
75 self.nextState = EscrowRequestStateWaitForUnlock;
77 self.nextState = EscrowRequestStateNothingToDo;
83 SecEscrowPendingRecord* record = nil;
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);
91 if(existingRecord.hasSerializedPrerecord) {
92 record = existingRecord;
97 if(record == nil && record.uuid == nil) {
98 secnotice("escrowrequest", "No pending escrow request has a prerecord");
99 self.nextState = EscrowRequestStateNothingToDo;
103 secnotice("escrowrequest", "escrow request have pre-record uploading: %@", record.uuid);
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];
110 * Update and save the preRecord an extra time (before we crash)
113 record.lastEscrowAttemptTime = (uint64_t) ([[NSDate date] timeIntervalSince1970] * 1000);
114 record.uploadRetries += 1;
116 // Save the last escrow attempt time to keychain
117 NSError* saveError = nil;
118 [record saveToKeychain:&saveError];
120 secerror("escrowrequest: unable to save last escrow time: %@", error);
125 [EscrowRequestPerformEscrowEnrollOperation cdpUploadPrerecord:record
126 secretType:CDPComplexDeviceSecretType
127 reply:^(BOOL didUpdate, NSError * _Nullable error) {
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];
136 secerror("escrowrequest: unable to delete last escrow time: %@", deleteError);
140 [self.operationQueue addOperation:finishOp];
144 if(error || !didUpdate) {
145 secerror("escrowrequest: prerecord %@ upload failed: %@", record.uuid, error);
148 [self.operationQueue addOperation:finishOp];
152 self.numberOfRecordsUploaded = 1;
153 secerror("escrowrequest: prerecord %@ upload succeeded", record.uuid);
155 record.uploadCompleted = true;
156 NSError* saveError = nil;
157 [record saveToKeychain:&saveError];
159 secerror("escrowrequest: unable to save last escrow time: %@", error);
163 secerror("escrowrequest: unable to save completion of prerecord %@ in keychain", record.uuid);
166 self.nextState = EscrowRequestStateNothingToDo;
167 [self.operationQueue addOperation:finishOp];
171 + (void)cdpUploadPrerecord:(SecEscrowPendingRecord*)recordToSend
172 secretType:(CDPDeviceSecretType)secretType
173 reply:(void (^)(BOOL didUpdate, NSError* _Nullable error))reply
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);