EscrowRequestServer.m
1 2 #import "utilities/debugging.h" 3 4 #import "keychain/escrowrequest/EscrowRequestController.h" 5 #import "keychain/escrowrequest/EscrowRequestServer.h" 6 #import "keychain/escrowrequest/generated_source/SecEscrowPendingRecord.h" 7 #import "keychain/escrowrequest/SecEscrowPendingRecord+KeychainSupport.h" 8 9 #import "keychain/ckks/CKKSLockStateTracker.h" 10 #import "keychain/ckks/CKKSAnalytics.h" 11 12 NSString* ESRPendingSince = @"ERSPending"; 13 14 @implementation EscrowRequestServer 15 16 - (instancetype)initWithLockStateTracker:(CKKSLockStateTracker*)lockStateTracker 17 { 18 if((self = [super init])) { 19 _controller = [[EscrowRequestController alloc] initWithLockStateTracker:lockStateTracker]; 20 } 21 return self; 22 } 23 24 + (EscrowRequestServer*)server 25 { 26 static EscrowRequestServer* server; 27 static dispatch_once_t onceToken; 28 dispatch_once(&onceToken, ^{ 29 server = [[EscrowRequestServer alloc] initWithLockStateTracker:[CKKSLockStateTracker globalTracker]]; 30 [server setupAnalytics]; 31 }); 32 return server; 33 } 34 35 36 + (id<SecEscrowRequestable> _Nullable)request:(NSError *__autoreleasing _Nullable * _Nullable)error { 37 return [EscrowRequestServer server]; 38 } 39 40 - (BOOL)triggerEscrowUpdate:(nonnull NSString *)reason 41 error:(NSError * _Nullable __autoreleasing * _Nullable)error 42 { 43 // Magical async code style to sync conversion only happens with NSXPC. 44 // Use a semaphore here, since we don't have any other option. 45 dispatch_semaphore_t sema = dispatch_semaphore_create(0); 46 47 __block NSError* updateError = nil; 48 [self triggerEscrowUpdate:reason reply:^(NSError * _Nullable operationError) { 49 updateError = operationError; 50 dispatch_semaphore_signal(sema); 51 }]; 52 dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER); 53 54 if(error && updateError) { 55 *error = updateError; 56 } 57 return updateError == nil ? YES : NO; 58 } 59 60 - (NSDictionary *)fetchStatuses:(NSError **)error 61 { 62 __block NSDictionary *status = nil; 63 64 __block NSError* updateError = nil; 65 [self fetchRequestStatuses:^(NSDictionary<NSString *,NSString *> * _Nullable requestUUID, NSError * _Nullable blockError) { 66 status = requestUUID; 67 updateError = blockError; 68 }]; 69 70 if(error && updateError) { 71 *error = updateError; 72 } 73 return status; 74 } 75 76 - (bool)pendingEscrowUpload:(NSError *__autoreleasing _Nullable * _Nullable)error { 77 __block NSDictionary *result = nil; 78 __block NSError* updateError = nil; 79 80 [self fetchRequestStatuses:^(NSDictionary<NSString *,NSString *> * requestUUID, NSError *blockError) { 81 result = requestUUID; 82 updateError = blockError; 83 }]; 84 85 if(updateError) { 86 secnotice("escrow", "failed to fetch escrow statuses: %@", updateError); 87 if(error) { 88 *error = updateError; 89 } 90 return NO; 91 } 92 if(result == nil || (result && [result count] == 0)) { 93 return NO; 94 } 95 96 BOOL inProgress = NO; 97 for(NSString* status in result.allValues) { 98 if([status isEqualToString:SecEscrowRequestHavePrecord] || 99 [status isEqualToString:SecEscrowRequestPendingPasscode] || 100 [status isEqualToString:SecEscrowRequestPendingCertificate]) { 101 inProgress = YES; 102 } 103 } 104 105 return inProgress; 106 } 107 108 - (void)cachePrerecord:(NSString*)uuid 109 serializedPrerecord:(nonnull NSData *)prerecord 110 reply:(nonnull void (^)(NSError * _Nullable))reply 111 { 112 NSError* error = nil; 113 SecEscrowPendingRecord* record = [SecEscrowPendingRecord loadFromKeychain:uuid error:&error]; 114 115 if(error) { 116 secerror("escrowrequest: unable to load uuid %@: %@", uuid, error); 117 reply(error); 118 return; 119 } 120 121 record.serializedPrerecord = prerecord; 122 123 [record saveToKeychain:&error]; 124 if(error) { 125 secerror("escrowrequest: unable to save new prerecord for uuid %@: %@", uuid, error); 126 reply(error); 127 return; 128 } 129 130 secnotice("escrowrequest", "saved new prerecord for uuid %@", uuid); 131 132 // Poke the EscrowRequestController, so it will upload the record 133 [self.controller.stateMachine pokeStateMachine]; 134 135 reply(nil); 136 } 137 138 - (void)fetchPrerecord:(nonnull NSString *)prerecordUUID 139 reply:(nonnull void (^)(NSData * _Nullable, NSError * _Nullable))reply 140 { 141 NSError* error = nil; 142 SecEscrowPendingRecord* record = [SecEscrowPendingRecord loadFromKeychain:prerecordUUID error:&error]; 143 144 if(error) { 145 secerror("escrowrequest: unable to load prerecord with uuid %@: %@", prerecordUUID, error); 146 reply(nil, error); 147 return; 148 } 149 150 if(record.hasSerializedPrerecord) { 151 secnotice("escrowrequest", "fetched prerecord for uuid %@", prerecordUUID); 152 reply(record.serializedPrerecord, nil); 153 } else { 154 secerror("escrowrequest: no prerecord for uuid %@: %@", prerecordUUID, error); 155 // TODO: fill in error 156 reply(nil, error); 157 } 158 } 159 160 - (void)fetchRequestWaitingOnPasscode:(nonnull void (^)(NSString * _Nullable, NSError * _Nullable))reply 161 { 162 NSError* error = nil; 163 164 NSArray<SecEscrowPendingRecord*>* records = [SecEscrowPendingRecord loadAllFromKeychain:&error]; 165 166 if(error && [error.domain isEqualToString:NSOSStatusErrorDomain] && error.code == errSecItemNotFound) { 167 // Fair enough! There are no requests waiting for a passcode. 168 [[CKKSAnalytics logger] setDateProperty:nil forKey:ESRPendingSince]; 169 reply(nil, nil); 170 return; 171 } 172 173 if(!records || error) { 174 reply(nil, error); 175 return; 176 } 177 178 // Are any of these requests pending? 179 for(SecEscrowPendingRecord* record in records) { 180 if(!record.certCached) { 181 secnotice("escrowrequest", "Escrow request %@ doesn't yet have a certificate cached", record.uuid); 182 continue; 183 } 184 185 if(record.hasSerializedPrerecord) { 186 secnotice("escrowrequest", "Escrow request %@ already has a prerecord; no passcode needed", record.uuid); 187 continue; 188 } 189 190 secnotice("escrowrequest", "Escrow request %@ is pending a passcode", record.uuid); 191 reply(record.uuid, nil); 192 return; 193 } 194 195 secnotice("escrowrequest", "No escrow requests need a passcode"); 196 reply(nil, nil); 197 } 198 199 - (void)triggerEscrowUpdate:(nonnull NSString *)reason 200 reply:(nonnull void (^)(NSError * _Nullable))reply 201 { 202 secnotice("escrowrequest", "Triggering an escrow update request due to '%@'", reason); 203 204 [self.controller triggerEscrowUpdateRPC:reason 205 reply:reply]; 206 } 207 208 - (void)fetchRequestStatuses:(void (^)(NSDictionary<NSString*, NSString*>* _Nullable requestUUID, NSError* _Nullable error))reply 209 { 210 NSError* error = nil; 211 NSArray<SecEscrowPendingRecord*>* records = [SecEscrowPendingRecord loadAllFromKeychain:&error]; 212 213 if(error && [error.domain isEqualToString:NSOSStatusErrorDomain] && error.code == errSecItemNotFound) { 214 // Fair enough! There are no requests waiting for a passcode. 215 secnotice("escrowrequest", "no extant requests"); 216 reply(@{}, nil); 217 return; 218 } 219 220 if(error) { 221 secerror("escrowrequest: failed to load requests: %@", error); 222 reply(nil, error); 223 } 224 225 secnotice("escrowrequest", "found requests: %@", records); 226 227 NSMutableDictionary<NSString*, NSString*>* d = [NSMutableDictionary dictionary]; 228 for(SecEscrowPendingRecord* record in records) { 229 if(record.uploadCompleted) { 230 d[record.uuid] = @"complete"; 231 } else if(record.hasSerializedPrerecord) { 232 d[record.uuid] = SecEscrowRequestHavePrecord; 233 } else if(record.certCached) { 234 d[record.uuid] = SecEscrowRequestPendingPasscode; 235 } else { 236 d[record.uuid] = SecEscrowRequestPendingCertificate; 237 } 238 } 239 240 reply(d, nil); 241 } 242 243 - (void)resetAllRequests:(void (^)(NSError* _Nullable error))reply 244 { 245 secnotice("escrowrequest", "deleting all requests"); 246 247 NSError* error = nil; 248 NSArray<SecEscrowPendingRecord*>* records = [SecEscrowPendingRecord loadAllFromKeychain:&error]; 249 250 if(error && [error.domain isEqualToString:NSOSStatusErrorDomain] && error.code == errSecItemNotFound) { 251 // Fair enough! There are no requests waiting for a passcode. 252 secnotice("escrowrequest", "no extant requests; nothing to delete"); 253 reply(nil); 254 return; 255 } 256 257 if(error) { 258 secnotice("escrowrequest", "error fetching records: %@", error); 259 reply(error); 260 return; 261 } 262 263 for(SecEscrowPendingRecord* record in records) { 264 [record deleteFromKeychain:&error]; 265 if(error) { 266 secnotice("escrowrequest", "Unable to delete %@: %@", record, error); 267 } 268 } 269 270 // Report the last error, if any. 271 reply(error); 272 } 273 274 - (void)storePrerecordsInEscrow:(void (^)(uint64_t count, NSError* _Nullable error))reply 275 { 276 secnotice("escrowrequest", "attempting to store a prerecord in escrow"); 277 278 [self.controller storePrerecordsInEscrowRPC:reply]; 279 } 280 281 - (void)setupAnalytics 282 { 283 [[CKKSAnalytics logger] AddMultiSamplerForName:@"escorwrequest-healthSummary" withTimeInterval:SFAnalyticsSamplerIntervalOncePerReport block:^NSDictionary<NSString *,NSNumber *> *{ 284 NSMutableDictionary<NSString *,NSNumber*>* values = [NSMutableDictionary dictionary]; 285 286 NSDate *date = [[CKKSAnalytics logger] datePropertyForKey:ESRPendingSince]; 287 if (date) { 288 values[ESRPendingSince] = @([CKKSAnalytics fuzzyDaysSinceDate:date]); 289 } 290 291 return values; 292 }]; 293 } 294 295 @end