2 #import "utilities/debugging.h"
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"
9 #import "keychain/ckks/CKKSLockStateTracker.h"
10 #import "keychain/ckks/CKKSAnalytics.h"
12 NSString* ESRPendingSince = @"ERSPending";
14 @implementation EscrowRequestServer
16 - (instancetype)initWithLockStateTracker:(CKKSLockStateTracker*)lockStateTracker
18 if((self = [super init])) {
19 _controller = [[EscrowRequestController alloc] initWithLockStateTracker:lockStateTracker];
24 + (EscrowRequestServer*)server
26 static EscrowRequestServer* server;
27 static dispatch_once_t onceToken;
28 dispatch_once(&onceToken, ^{
29 server = [[EscrowRequestServer alloc] initWithLockStateTracker:[CKKSLockStateTracker globalTracker]];
30 [server setupAnalytics];
36 + (id<SecEscrowRequestable> _Nullable)request:(NSError *__autoreleasing _Nullable * _Nullable)error {
37 return [EscrowRequestServer server];
40 - (BOOL)triggerEscrowUpdate:(nonnull NSString *)reason
41 error:(NSError * _Nullable __autoreleasing * _Nullable)error
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);
47 __block NSError* updateError = nil;
48 [self triggerEscrowUpdate:reason reply:^(NSError * _Nullable operationError) {
49 updateError = operationError;
50 dispatch_semaphore_signal(sema);
52 dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
54 if(error && updateError) {
57 return updateError == nil ? YES : NO;
60 - (NSDictionary *)fetchStatuses:(NSError **)error
62 __block NSDictionary *status = nil;
64 __block NSError* updateError = nil;
65 [self fetchRequestStatuses:^(NSDictionary<NSString *,NSString *> * _Nullable requestUUID, NSError * _Nullable blockError) {
67 updateError = blockError;
70 if(error && updateError) {
76 - (bool)pendingEscrowUpload:(NSError *__autoreleasing _Nullable * _Nullable)error {
77 __block NSDictionary *result = nil;
78 __block NSError* updateError = nil;
80 [self fetchRequestStatuses:^(NSDictionary<NSString *,NSString *> * requestUUID, NSError *blockError) {
82 updateError = blockError;
86 secnotice("escrow", "failed to fetch escrow statuses: %@", updateError);
92 if(result == nil || (result && [result count] == 0)) {
97 for(NSString* status in result.allValues) {
98 if([status isEqualToString:SecEscrowRequestHavePrecord] ||
99 [status isEqualToString:SecEscrowRequestPendingPasscode] ||
100 [status isEqualToString:SecEscrowRequestPendingCertificate]) {
108 - (void)cachePrerecord:(NSString*)uuid
109 serializedPrerecord:(nonnull NSData *)prerecord
110 reply:(nonnull void (^)(NSError * _Nullable))reply
112 NSError* error = nil;
113 SecEscrowPendingRecord* record = [SecEscrowPendingRecord loadFromKeychain:uuid error:&error];
116 secerror("escrowrequest: unable to load uuid %@: %@", uuid, error);
121 record.serializedPrerecord = prerecord;
123 [record saveToKeychain:&error];
125 secerror("escrowrequest: unable to save new prerecord for uuid %@: %@", uuid, error);
130 secnotice("escrowrequest", "saved new prerecord for uuid %@", uuid);
132 // Poke the EscrowRequestController, so it will upload the record
133 [self.controller.stateMachine pokeStateMachine];
138 - (void)fetchPrerecord:(nonnull NSString *)prerecordUUID
139 reply:(nonnull void (^)(NSData * _Nullable, NSError * _Nullable))reply
141 NSError* error = nil;
142 SecEscrowPendingRecord* record = [SecEscrowPendingRecord loadFromKeychain:prerecordUUID error:&error];
145 secerror("escrowrequest: unable to load prerecord with uuid %@: %@", prerecordUUID, error);
150 if(record.hasSerializedPrerecord) {
151 secnotice("escrowrequest", "fetched prerecord for uuid %@", prerecordUUID);
152 reply(record.serializedPrerecord, nil);
154 secerror("escrowrequest: no prerecord for uuid %@: %@", prerecordUUID, error);
155 // TODO: fill in error
160 - (void)fetchRequestWaitingOnPasscode:(nonnull void (^)(NSString * _Nullable, NSError * _Nullable))reply
162 NSError* error = nil;
164 NSArray<SecEscrowPendingRecord*>* records = [SecEscrowPendingRecord loadAllFromKeychain:&error];
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];
173 if(!records || error) {
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);
185 if(record.hasSerializedPrerecord) {
186 secnotice("escrowrequest", "Escrow request %@ already has a prerecord; no passcode needed", record.uuid);
190 secnotice("escrowrequest", "Escrow request %@ is pending a passcode", record.uuid);
191 reply(record.uuid, nil);
195 secnotice("escrowrequest", "No escrow requests need a passcode");
199 - (void)triggerEscrowUpdate:(nonnull NSString *)reason
200 reply:(nonnull void (^)(NSError * _Nullable))reply
202 secnotice("escrowrequest", "Triggering an escrow update request due to '%@'", reason);
204 [self.controller triggerEscrowUpdateRPC:reason
208 - (void)fetchRequestStatuses:(void (^)(NSDictionary<NSString*, NSString*>* _Nullable requestUUID, NSError* _Nullable error))reply
210 NSError* error = nil;
211 NSArray<SecEscrowPendingRecord*>* records = [SecEscrowPendingRecord loadAllFromKeychain:&error];
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");
221 secerror("escrowrequest: failed to load requests: %@", error);
225 secnotice("escrowrequest", "found requests: %@", records);
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;
236 d[record.uuid] = SecEscrowRequestPendingCertificate;
243 - (void)resetAllRequests:(void (^)(NSError* _Nullable error))reply
245 secnotice("escrowrequest", "deleting all requests");
247 NSError* error = nil;
248 NSArray<SecEscrowPendingRecord*>* records = [SecEscrowPendingRecord loadAllFromKeychain:&error];
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");
258 secnotice("escrowrequest", "error fetching records: %@", error);
263 for(SecEscrowPendingRecord* record in records) {
264 [record deleteFromKeychain:&error];
266 secnotice("escrowrequest", "Unable to delete %@: %@", record, error);
270 // Report the last error, if any.
274 - (void)storePrerecordsInEscrow:(void (^)(uint64_t count, NSError* _Nullable error))reply
276 secnotice("escrowrequest", "attempting to store a prerecord in escrow");
278 [self.controller storePrerecordsInEscrowRPC:reply];
281 - (void)setupAnalytics
283 [[CKKSAnalytics logger] AddMultiSamplerForName:@"escorwrequest-healthSummary" withTimeInterval:SFAnalyticsSamplerIntervalOncePerReport block:^NSDictionary<NSString *,NSNumber *> *{
284 NSMutableDictionary<NSString *,NSNumber*>* values = [NSMutableDictionary dictionary];
286 NSDate *date = [[CKKSAnalytics logger] datePropertyForKey:ESRPendingSince];
288 values[ESRPendingSince] = @([CKKSAnalytics fuzzyDaysSinceDate:date]);