/ keychain / escrowrequest / operations / EscrowRequestInformCloudServicesOperation.m
EscrowRequestInformCloudServicesOperation.m
  1  
  2  #import <CloudServices/SecureBackup.h>
  3  
  4  #import "utilities/debugging.h"
  5  #import "keychain/ckks/CKKSLockStateTracker.h"
  6  
  7  #import "keychain/escrowrequest/EscrowRequestController.h"
  8  #import "keychain/escrowrequest/operations/EscrowRequestInformCloudServicesOperation.h"
  9  #import "keychain/escrowrequest/generated_source/SecEscrowPendingRecord.h"
 10  #import "keychain/escrowrequest/SecEscrowPendingRecord+KeychainSupport.h"
 11  
 12  @interface EscrowRequestInformCloudServicesOperation()
 13  @property CKKSLockStateTracker* lockStateTracker;
 14  @end
 15  
 16  @implementation EscrowRequestInformCloudServicesOperation
 17  @synthesize nextState = _nextState;
 18  @synthesize intendedState = _intendedState;
 19  
 20  - (instancetype)initWithIntendedState:(OctagonState*)intendedState
 21                             errorState:(OctagonState*)errorState
 22                       lockStateTracker:(CKKSLockStateTracker*)lockStateTracker
 23  {
 24      if((self = [super init])) {
 25          _intendedState = intendedState;
 26          _nextState = errorState;
 27          _lockStateTracker = lockStateTracker;
 28      }
 29      return self;
 30  }
 31  
 32  - (void)main
 33  {
 34      secnotice("escrowrequest", "Telling CloudServices about any pending requests");
 35  
 36      NSError* error = nil;
 37      NSArray<SecEscrowPendingRecord*>* records = [SecEscrowPendingRecord loadAllFromKeychain:&error];
 38      if(error && !([error.domain isEqualToString:NSOSStatusErrorDomain] && error.code == errSecItemNotFound)) {
 39          secnotice("escrowrequest", "failed to fetch records from keychain: %@", error);
 40          if([self.lockStateTracker isLockedError:error]) {
 41              secnotice("escrowrequest", "Trying again after unlock");
 42              self.nextState = EscrowRequestStateWaitForUnlock;
 43          } else {
 44              self.nextState = EscrowRequestStateNothingToDo;
 45          }
 46          self.error = error;
 47          return;
 48      }
 49      error = nil;
 50  
 51      SecEscrowPendingRecord* record = nil;
 52  
 53      for(SecEscrowPendingRecord* existingRecord in records) {
 54          if(!existingRecord.hasCertCached) {
 55              record = existingRecord;
 56              break;
 57          }
 58      }
 59  
 60      if(!record) {
 61          secnotice("escrowrequest", "No pending escrow request needs a certificate");
 62          self.nextState = EscrowRequestStateNothingToDo;
 63          return;
 64      }
 65  
 66      // Next, see if CloudServices can cache a certificate
 67      NSData* cachedCert = [EscrowRequestInformCloudServicesOperation triggerCloudServicesPasscodeRequest:record.uuid error:&error];
 68      record.lastCloudServicesTriggerTime = (uint64_t) ([[NSDate date] timeIntervalSince1970] * 1000);
 69  
 70      if(!cachedCert || error) {
 71          secerror("escrowrequest: cloudservices reports an issue caching the certificate, so we'll have to try again later: %@", error);
 72          self.error = error;
 73          // TODO: wait for network?
 74          self.nextState = EscrowRequestStateNothingToDo;
 75  
 76          NSError* saveCacheTimeError = nil;
 77          [record saveToKeychain:&saveCacheTimeError];
 78          if(saveCacheTimeError) {
 79              secerror("escrowrequest: unable to save the last attempt time: %@", saveCacheTimeError);
 80          }
 81  
 82          return;
 83      }
 84  
 85      record.certCached = true;
 86      [record saveToKeychain:&error];
 87  
 88      if(error) {
 89          // Ignore this error, since we've successfully triggered the update. We'll probably re-cache the certificate later, but that's okay.
 90          secerror("escrowrequest: unable to save escrow update request certificate status, so we'll have to try again later: %@", error);
 91          self.error = error;
 92  
 93          if([self.lockStateTracker isLockedError:error]) {
 94              secnotice("escrowrequest", "Trying again after unlock");
 95              self.nextState = EscrowRequestStateWaitForUnlock;
 96          } else {
 97              self.nextState = EscrowRequestStateNothingToDo;
 98          }
 99  
100          return;
101      }
102  
103      secnotice("escrowrequest", "CloudService successfully cached a certificate; request is ready for passcode");
104      self.nextState = EscrowRequestStateNothingToDo;
105  }
106  
107  // Separated into a class method for mocking
108  // Returns any cert that CS has cached
109  + (NSData* _Nullable)triggerCloudServicesPasscodeRequest:(NSString*)uuid error:(NSError**)error
110  {
111      SecureBackup* sb = [[SecureBackup alloc] init];
112  
113      NSError* localError = nil;
114      SecureBackupBeginPasscodeRequestResults* results = [sb beginHSA2PasscodeRequest:true
115                                                                                 uuid:uuid
116                                                                                error:error];
117  
118      if(!results || localError) {
119          secerror("escrowrequest: unable to begin passcode request: %@", localError);
120          if(error) {
121              *error = localError;
122          }
123          return nil;
124      }
125  
126      if(!results.cert) {
127          secerror("escrowrequest: sbd failed to cache a certificate");
128          // TODO fill in error
129          return nil;
130      }
131  
132      return results.cert;
133  }
134  
135  @end