EscrowRequestController.m
1 2 #import "utilities/debugging.h" 3 4 #import "keychain/ot/OctagonStateMachine.h" 5 #import "keychain/ot/OTStates.h" 6 #import "keychain/escrowrequest/EscrowRequestController.h" 7 #import "keychain/escrowrequest/EscrowRequestServer.h" 8 9 #import "keychain/ckks/CKKSLockStateTracker.h" 10 11 #import "keychain/ot/ObjCImprovements.h" 12 13 #import "keychain/escrowrequest/operations/EscrowRequestInformCloudServicesOperation.h" 14 #import "keychain/escrowrequest/operations/EscrowRequestPerformEscrowEnrollOperation.h" 15 16 #import "keychain/escrowrequest/generated_source/SecEscrowPendingRecord.h" 17 #import "keychain/escrowrequest/SecEscrowPendingRecord+KeychainSupport.h" 18 19 OctagonState* const EscrowRequestStateNothingToDo = (OctagonState*)@"nothing_to_do"; 20 OctagonState* const EscrowRequestStateTriggerCloudServices = (OctagonState*)@"trigger_cloudservices"; 21 22 OctagonState* const EscrowRequestStateAttemptEscrowUpload = (OctagonState*)@"trigger_escrow_upload"; 23 OctagonState* const EscrowRequestStateWaitForUnlock = (OctagonState*)@"wait_for_unlock"; 24 25 @interface EscrowRequestController () 26 @property dispatch_queue_t queue; 27 @property CKKSLockStateTracker* lockStateTracker; 28 @property bool haveRecordedDate; 29 @end 30 31 @implementation EscrowRequestController 32 33 - (instancetype)initWithLockStateTracker:(CKKSLockStateTracker*)lockStateTracker 34 { 35 if((self = [super init])) { 36 _queue = dispatch_queue_create("EscrowRequestControllerQueue", DISPATCH_QUEUE_SERIAL_WITH_AUTORELEASE_POOL); 37 _lockStateTracker = lockStateTracker; 38 39 _stateMachine = [[OctagonStateMachine alloc] initWithName:@"escrowrequest" 40 states:[NSSet setWithArray:@[EscrowRequestStateNothingToDo, 41 EscrowRequestStateTriggerCloudServices, 42 EscrowRequestStateAttemptEscrowUpload, 43 EscrowRequestStateWaitForUnlock]] 44 flags: [NSSet setWithArray:@[OctagonFlagEscrowRequestInformCloudServicesOperation]] 45 initialState:EscrowRequestStateNothingToDo 46 queue:_queue 47 stateEngine:self 48 lockStateTracker:lockStateTracker 49 reachabilityTracker:nil]; 50 51 _forceIgnoreCloudServicesRateLimiting = false; 52 } 53 54 return self; 55 } 56 57 - (CKKSResultOperation<OctagonStateTransitionOperationProtocol> * _Nullable)_onqueueNextStateMachineTransition:(nonnull OctagonState *)currentState 58 flags:(nonnull OctagonFlags *)flags 59 pendingFlags:(nonnull id<OctagonStateOnqueuePendingFlagHandler>)pendingFlagHandler 60 { 61 dispatch_assert_queue(self.queue); 62 if([flags _onqueueContains:OctagonFlagEscrowRequestInformCloudServicesOperation]) { 63 [flags _onqueueRemoveFlag:OctagonFlagEscrowRequestInformCloudServicesOperation]; 64 return [[EscrowRequestInformCloudServicesOperation alloc] initWithIntendedState:EscrowRequestStateNothingToDo 65 errorState:EscrowRequestStateNothingToDo 66 lockStateTracker:self.lockStateTracker]; 67 } 68 69 if([currentState isEqualToString:EscrowRequestStateTriggerCloudServices]) { 70 return [[EscrowRequestInformCloudServicesOperation alloc] initWithIntendedState:EscrowRequestStateNothingToDo 71 errorState:EscrowRequestStateNothingToDo 72 lockStateTracker:self.lockStateTracker]; 73 } 74 75 if([currentState isEqualToString:EscrowRequestStateAttemptEscrowUpload]) { 76 return [[EscrowRequestPerformEscrowEnrollOperation alloc] initWithIntendedState:EscrowRequestStateNothingToDo 77 errorState:EscrowRequestStateNothingToDo 78 enforceRateLimiting:true 79 lockStateTracker:self.lockStateTracker]; 80 } 81 82 if([currentState isEqualToString:EscrowRequestStateWaitForUnlock]) { 83 secnotice("escrowrequest", "waiting for unlock before continuing with state machine"); 84 OctagonStateTransitionOperation* op = [OctagonStateTransitionOperation named:@"wait-for-unlock" 85 entering:EscrowRequestStateNothingToDo]; 86 [op addNullableDependency:self.lockStateTracker.unlockDependency]; 87 return op; 88 } 89 90 NSError* error = nil; 91 NSArray<SecEscrowPendingRecord*>* records = [SecEscrowPendingRecord loadAllFromKeychain:&error]; 92 if(error) { 93 if([self.lockStateTracker isLockedError:error]) { 94 return [OctagonStateTransitionOperation named:@"wait-for-unlock" 95 entering:EscrowRequestStateWaitForUnlock]; 96 } 97 secnotice("escrowrequest", "failed to fetch records from keychain, nothing to do: %@", error); 98 return nil; 99 } 100 101 // First, do we need to poke CloudServices? 102 for(SecEscrowPendingRecord* record in records) { 103 // Completed records don't need anything. 104 if(record.hasUploadCompleted && record.uploadCompleted) { 105 continue; 106 } 107 108 if (!self.haveRecordedDate) { 109 NSDate *date = [[CKKSAnalytics logger] datePropertyForKey:ESRPendingSince]; 110 if (date == NULL) { 111 [[CKKSAnalytics logger] setDateProperty:[NSDate date] forKey:ESRPendingSince]; 112 } 113 self.haveRecordedDate = true; 114 } 115 116 uint64_t fiveMinutesAgo = ((uint64_t)[[NSDate date] timeIntervalSince1970] * 1000) - (1000*60*5); 117 118 if(!record.certCached) { 119 if(!self.forceIgnoreCloudServicesRateLimiting && (record.hasLastCloudServicesTriggerTime && record.lastCloudServicesTriggerTime >= fiveMinutesAgo)) { 120 secnotice("escrowrequest", "Request %@ needs to cache a certificate, but that has been attempted recently. Holding off...", record.uuid); 121 continue; 122 } 123 124 secnotice("escrowrequest", "Request %@ needs a cached certififcate", record.uuid); 125 126 return [OctagonStateTransitionOperation named:@"escrow-request-cache-cert" 127 entering:EscrowRequestStateTriggerCloudServices]; 128 } 129 130 if(record.hasSerializedPrerecord) { 131 if([record escrowAttemptedWithinLastSeconds:5*60]) { 132 secnotice("escrowrequest", "Request %@ needs to be stored, but has been attempted recently. Holding off...", record.uuid); 133 continue; 134 } 135 136 secnotice("escrowrequest", "Request %@ needs to be stored!", record.uuid); 137 138 return [OctagonStateTransitionOperation named:@"escrow-request-attempt-escrow-upload" 139 entering:EscrowRequestStateAttemptEscrowUpload]; 140 } 141 } 142 143 144 return nil; 145 } 146 147 - (void)triggerEscrowUpdateRPC:(nonnull NSString *)reason 148 reply:(nonnull void (^)(NSError * _Nullable))reply 149 { 150 [self.stateMachine startOperation]; 151 152 NSError* error = nil; 153 NSArray<SecEscrowPendingRecord*>* records = [SecEscrowPendingRecord loadAllFromKeychain:&error]; 154 if(error && !([error.domain isEqualToString:NSOSStatusErrorDomain] && error.code == errSecItemNotFound)) { 155 secnotice("escrowrequest", "failed to fetch records from keychain: %@", error); 156 reply(error); 157 return; 158 } 159 error = nil; 160 161 secnotice("escrowrequest", "Investigating a new escrow request"); 162 163 BOOL escrowRequestExists = NO; 164 for(SecEscrowPendingRecord* existingRecord in records) { 165 if(existingRecord.uploadCompleted) { 166 continue; 167 } 168 169 if (existingRecord.hasAltDSID) { 170 continue; 171 } 172 173 secnotice("escrowrequest", "Retriggering an existing escrow request: %@", existingRecord); 174 existingRecord.hasCertCached = false; 175 existingRecord.serializedPrerecord = nil; 176 177 [existingRecord saveToKeychain:&error]; 178 if(error) { 179 secerror("escrowrequest: Unable to save modified request to keychain: %@", error); 180 reply(error); 181 return; 182 } 183 184 secnotice("escrowrequest", "Retriggering an existing escrow request complete"); 185 escrowRequestExists = YES; 186 } 187 188 if(escrowRequestExists == NO){ 189 secnotice("escrowrequest", "Creating a new escrow request"); 190 191 SecEscrowPendingRecord* record = [[SecEscrowPendingRecord alloc] init]; 192 record.uuid = [[NSUUID UUID] UUIDString]; 193 record.altDSID = nil; 194 record.triggerRequestTime = ((uint64_t)[[NSDate date] timeIntervalSince1970] * 1000); 195 secnotice("escrowrequest", "beginning a new escrow request (%@)", record.uuid); 196 197 [record saveToKeychain:&error]; 198 199 if(error) { 200 secerror("escrowrequest: unable to save escrow update request: %@", error); 201 reply(error); 202 return; 203 } 204 } 205 206 [[CKKSAnalytics logger] setDateProperty:[NSDate date] forKey:ESRPendingSince]; 207 self.haveRecordedDate = true; 208 209 [self.stateMachine handleFlag:OctagonFlagEscrowRequestInformCloudServicesOperation]; 210 211 reply(nil); 212 } 213 214 - (void)storePrerecordsInEscrowRPC:(void (^)(uint64_t count, NSError* _Nullable error))reply 215 { 216 EscrowRequestPerformEscrowEnrollOperation* op = [[EscrowRequestPerformEscrowEnrollOperation alloc] initWithIntendedState:EscrowRequestStateNothingToDo 217 errorState:EscrowRequestStateNothingToDo 218 enforceRateLimiting:false 219 lockStateTracker:self.lockStateTracker]; 220 [self.stateMachine startOperation]; 221 [self.stateMachine doSimpleStateMachineRPC:@"trigger-escrow-store" 222 op:op 223 sourceStates:[NSSet setWithObject:EscrowRequestStateNothingToDo] 224 reply:^(NSError * _Nullable error) { 225 secnotice("escrowrequest", "Uploaded %d records with error %@", (int)op.numberOfRecordsUploaded, error); 226 reply(op.numberOfRecordsUploaded, error); 227 }]; 228 } 229 230 @end