]> git.saurik.com Git - apple/security.git/blob - keychain/escrowrequest/EscrowRequestServer.m
Security-59754.60.13.tar.gz
[apple/security.git] / keychain / escrowrequest / 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