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