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