/ keychain / escrowrequest / operations / EscrowRequestPerformEscrowEnrollOperation.m
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