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