]> git.saurik.com Git - apple/security.git/blob - trust/trustd/SecRevocationNetworking.m
Security-59306.41.2.tar.gz
[apple/security.git] / trust / trustd / SecRevocationNetworking.m
1 /*
2 * Copyright (c) 2017-2019 Apple Inc. All Rights Reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
11 * file.
12 *
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 *
23 */
24
25 #include <AssertMacros.h>
26 #import <Foundation/Foundation.h>
27 #import <CFNetwork/CFNSURLConnection.h>
28
29 #include <sys/sysctl.h>
30 #include <sys/time.h>
31 #include <mach/mach_time.h>
32 #include <os/transaction_private.h>
33 #include <Security/SecCertificateInternal.h>
34 #include "utilities/debugging.h"
35 #include "utilities/SecCFWrappers.h"
36 #include "utilities/SecPLWrappers.h"
37 #include "utilities/SecFileLocations.h"
38
39 #include "SecRevocationDb.h"
40 #include "SecRevocationServer.h"
41 #include "SecTrustServer.h"
42 #include "SecOCSPRequest.h"
43 #include "SecOCSPResponse.h"
44
45 #import "SecTrustLoggingServer.h"
46 #import "TrustURLSessionDelegate.h"
47
48 #import "SecRevocationNetworking.h"
49
50 /* MARK: Valid Update Networking */
51 static CFStringRef kSecPrefsDomain = CFSTR("com.apple.security");
52 static CFStringRef kUpdateWiFiOnlyKey = CFSTR("ValidUpdateWiFiOnly");
53 static CFStringRef kUpdateBackgroundKey = CFSTR("ValidUpdateBackground");
54
55 extern CFAbsoluteTime gUpdateStarted;
56 extern CFAbsoluteTime gNextUpdate;
57
58 static int checkBasePath(const char *basePath) {
59 return mkpath_np((char*)basePath, 0755);
60 }
61
62 static uint64_t systemUptimeInSeconds() {
63 struct timeval boottime;
64 size_t tv_size = sizeof(boottime);
65 time_t now, uptime = 0;
66 int mib[2];
67 mib[0] = CTL_KERN;
68 mib[1] = KERN_BOOTTIME;
69 (void) time(&now);
70 if (sysctl(mib, 2, &boottime, &tv_size, NULL, 0) != -1 &&
71 boottime.tv_sec != 0) {
72 uptime = now - boottime.tv_sec;
73 }
74 return (uint64_t)uptime;
75 }
76
77 typedef void (^CompletionHandler)(void);
78
79 @interface ValidDelegate : NSObject <NSURLSessionDelegate, NSURLSessionTaskDelegate, NSURLSessionDataDelegate>
80 @property CompletionHandler handler;
81 @property dispatch_queue_t revDbUpdateQueue;
82 @property os_transaction_t transaction;
83 @property NSString *currentUpdateServer;
84 @property NSFileHandle *currentUpdateFile;
85 @property NSURL *currentUpdateFileURL;
86 @property BOOL finishedDownloading;
87 @end
88
89 @implementation ValidDelegate
90
91 - (void)reschedule {
92 /* POWER LOG EVENT: operation canceled */
93 SecPLLogRegisteredEvent(@"ValidUpdateEvent", @{
94 @"timestamp" : @([[NSDate date] timeIntervalSince1970]),
95 @"event" : (self->_finishedDownloading) ? @"updateCanceled" : @"downloadCanceled"
96 });
97 secnotice("validupdate", "%s canceled at %f",
98 (self->_finishedDownloading) ? "update" : "download",
99 (double)CFAbsoluteTimeGetCurrent());
100
101 self->_handler();
102 SecRevocationDbComputeAndSetNextUpdateTime();
103 if (self->_transaction) {
104 self->_transaction = nil;
105 }
106 }
107
108 - (void)updateDb:(NSUInteger)version {
109 __block NSURL *updateFileURL = self->_currentUpdateFileURL;
110 __block NSString *updateServer = self->_currentUpdateServer;
111 __block NSFileHandle *updateFile = self->_currentUpdateFile;
112 if (!updateFileURL || !updateFile) {
113 [self reschedule];
114 return;
115 }
116
117 /* Hold a transaction until we finish the update */
118 __block os_transaction_t transaction = os_transaction_create("com.apple.trustd.valid.updateDb");
119 dispatch_async(_revDbUpdateQueue, ^{
120 /* POWER LOG EVENT: background update started */
121 SecPLLogRegisteredEvent(@"ValidUpdateEvent", @{
122 @"timestamp" : @([[NSDate date] timeIntervalSince1970]),
123 @"event" : @"updateStarted"
124 });
125 secnotice("validupdate", "update started at %f", (double)CFAbsoluteTimeGetCurrent());
126
127 CFDataRef updateData = NULL;
128 const char *updateFilePath = [updateFileURL fileSystemRepresentation];
129 int rtn;
130 if ((rtn = readValidFile(updateFilePath, &updateData)) != 0) {
131 secerror("failed to read %@ with error %d", updateFileURL, rtn);
132 TrustdHealthAnalyticsLogErrorCode(TAEventValidUpdate, TAFatalError, rtn);
133 [self reschedule];
134 transaction = nil;
135 return;
136 }
137
138 secdebug("validupdate", "verifying and ingesting data from %@", updateFileURL);
139 SecValidUpdateVerifyAndIngest(updateData, (__bridge CFStringRef)updateServer, (0 == version));
140 if ((rtn = munmap((void *)CFDataGetBytePtr(updateData), CFDataGetLength(updateData))) != 0) {
141 secerror("unable to unmap current update %ld bytes at %p (error %d)", CFDataGetLength(updateData), CFDataGetBytePtr(updateData), rtn);
142 }
143 CFReleaseNull(updateData);
144
145 /* We're done with this file */
146 [updateFile closeFile];
147 if (updateFilePath) {
148 (void)remove(updateFilePath);
149 }
150 self->_currentUpdateFile = nil;
151 self->_currentUpdateFileURL = nil;
152 self->_currentUpdateServer = nil;
153
154 /* POWER LOG EVENT: background update finished */
155 SecPLLogRegisteredEvent(@"ValidUpdateEvent", @{
156 @"timestamp" : @([[NSDate date] timeIntervalSince1970]),
157 @"event" : @"updateFinished"
158 });
159
160 /* Update is complete */
161 secnotice("validupdate", "update finished at %f", (double)CFAbsoluteTimeGetCurrent());
162 gUpdateStarted = 0;
163
164 self->_handler();
165 transaction = nil; // we're all done now
166 });
167 }
168
169 - (NSInteger)versionFromTask:(NSURLSessionTask *)task {
170 return atol([task.taskDescription cStringUsingEncoding:NSUTF8StringEncoding]);
171 }
172
173 - (void)URLSession:(NSURLSession *)session
174 dataTask:(NSURLSessionDataTask *)dataTask
175 didReceiveResponse:(NSURLResponse *)response
176 completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler {
177 /* nsurlsessiond started our download. Create a transaction since we're going to be working for a little bit */
178 self->_transaction = os_transaction_create("com.apple.trustd.valid.download");
179 secinfo("validupdate", "Session %@ data task %@ returned response %ld (%@), expecting %lld bytes",
180 session, dataTask, (long)[(NSHTTPURLResponse *)response statusCode],
181 [response MIMEType], [response expectedContentLength]);
182
183 WithPathInRevocationInfoDirectory(NULL, ^(const char *utf8String) {
184 (void)checkBasePath(utf8String);
185 });
186 CFURLRef updateFileURL = SecCopyURLForFileInRevocationInfoDirectory(CFSTR("update-current"));
187 self->_currentUpdateFileURL = (updateFileURL) ? CFBridgingRelease(updateFileURL) : nil;
188 const char *updateFilePath = [self->_currentUpdateFileURL fileSystemRepresentation];
189 if (!updateFilePath) {
190 secnotice("validupdate", "failed to find revocation info directory. canceling task %@", dataTask);
191 completionHandler(NSURLSessionResponseCancel);
192 [self reschedule];
193 return;
194 }
195
196 /* Clean up any old files from previous tasks. */
197 (void)remove(updateFilePath);
198
199 int fd;
200 off_t off;
201 fd = open(updateFilePath, O_RDWR | O_CREAT | O_TRUNC, 0644);
202 if (fd < 0 || (off = lseek(fd, 0, SEEK_SET)) < 0) {
203 secnotice("validupdate","unable to open %@ (errno %d)", self->_currentUpdateFileURL, errno);
204 }
205 if (fd >= 0) {
206 close(fd);
207 }
208
209 /* POWER LOG EVENT: background download actually started */
210 SecPLLogRegisteredEvent(@"ValidUpdateEvent", @{
211 @"timestamp" : @([[NSDate date] timeIntervalSince1970]),
212 @"event" : @"downloadStarted"
213 });
214 secnotice("validupdate", "download started at %f", (double)CFAbsoluteTimeGetCurrent());
215
216 NSError *error = nil;
217 self->_currentUpdateFile = [NSFileHandle fileHandleForWritingToURL:self->_currentUpdateFileURL error:&error];
218 if (!self->_currentUpdateFile) {
219 secnotice("validupdate", "failed to open %@: %@. canceling task %@", self->_currentUpdateFileURL, error, dataTask);
220 #if ENABLE_TRUSTD_ANALYTICS
221 [[TrustdHealthAnalytics logger] logResultForEvent:TrustdHealthAnalyticsEventValidUpdate hardFailure:NO result:error];
222 #endif // ENABLE_TRUSTD_ANALYTICS
223 completionHandler(NSURLSessionResponseCancel);
224 [self reschedule];
225 return;
226 }
227
228 completionHandler(NSURLSessionResponseAllow);
229 }
230
231 - (void)URLSession:(NSURLSession *)session
232 dataTask:(NSURLSessionDataTask *)dataTask
233 didReceiveData:(NSData *)data {
234 secdebug("validupdate", "Session %@ data task %@ returned %lu bytes (%lld bytes so far) out of expected %lld bytes",
235 session, dataTask, (unsigned long)[data length], [dataTask countOfBytesReceived], [dataTask countOfBytesExpectedToReceive]);
236
237 if (!self->_currentUpdateFile) {
238 secnotice("validupdate", "received data, but output file is not open");
239 [dataTask cancel];
240 [self reschedule];
241 return;
242 }
243
244 @try {
245 /* Writing can fail and throw an exception, e.g. if we run out of disk space. */
246 [self->_currentUpdateFile writeData:data];
247 }
248 @catch(NSException *exception) {
249 secnotice("validupdate", "%s", exception.description.UTF8String);
250 TrustdHealthAnalyticsLogErrorCode(TAEventValidUpdate, TARecoverableError, errSecDiskFull);
251 [dataTask cancel];
252 [self reschedule];
253 }
254 }
255
256 - (void)URLSession:(NSURLSession *)session
257 task:(NSURLSessionTask *)task
258 didCompleteWithError:(NSError *)error {
259 if (error) {
260 secnotice("validupdate", "Session %@ task %@ failed with error %@", session, task, error);
261 #if ENABLE_TRUSTD_ANALYTICS
262 [[TrustdHealthAnalytics logger] logResultForEvent:TrustdHealthAnalyticsEventValidUpdate hardFailure:NO result:error];
263 #endif // ENABLE_TRUSTD_ANALYTICS
264 [self reschedule];
265 /* close file before we leave */
266 [self->_currentUpdateFile closeFile];
267 self->_currentUpdateFile = nil;
268 self->_currentUpdateServer = nil;
269 self->_currentUpdateFileURL = nil;
270 } else {
271 /* POWER LOG EVENT: background download finished */
272 SecPLLogRegisteredEvent(@"ValidUpdateEvent", @{
273 @"timestamp" : @([[NSDate date] timeIntervalSince1970]),
274 @"event" : @"downloadFinished"
275 });
276 secnotice("validupdate", "download finished at %f", (double)CFAbsoluteTimeGetCurrent());
277 secdebug("validupdate", "Session %@ task %@ succeeded", session, task);
278 self->_finishedDownloading = YES;
279 [self updateDb:[self versionFromTask:task]];
280 }
281 if (self->_transaction) {
282 self->_transaction = nil;
283 }
284 }
285
286 @end
287
288 @interface ValidUpdateRequest : NSObject
289 @property NSTimeInterval updateScheduled;
290 @property NSURLSession *backgroundSession;
291 @end
292
293 static ValidUpdateRequest *request = nil;
294
295 @implementation ValidUpdateRequest
296
297 - (NSURLSessionConfiguration *)validUpdateConfiguration {
298 /* preferences to override defaults */
299 CFTypeRef value = NULL;
300 bool updateOnWiFiOnly = true;
301 value = CFPreferencesCopyValue(kUpdateWiFiOnlyKey, kSecPrefsDomain, kCFPreferencesAnyUser, kCFPreferencesCurrentHost);
302 if (isBoolean(value)) {
303 updateOnWiFiOnly = CFBooleanGetValue((CFBooleanRef)value);
304 }
305 CFReleaseNull(value);
306 bool updateInBackground = true;
307 value = CFPreferencesCopyValue(kUpdateBackgroundKey, kSecPrefsDomain, kCFPreferencesAnyUser, kCFPreferencesCurrentHost);
308 if (isBoolean(value)) {
309 updateInBackground = CFBooleanGetValue((CFBooleanRef)value);
310 }
311 CFReleaseNull(value);
312
313 NSURLSessionConfiguration *config = nil;
314 if (updateInBackground) {
315 config = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier: @"com.apple.trustd.networking.background"];
316 config.networkServiceType = NSURLNetworkServiceTypeBackground;
317 config.discretionary = YES;
318 config._requiresPowerPluggedIn = YES;
319 config.allowsCellularAccess = (!updateOnWiFiOnly) ? YES : NO;
320 } else {
321 config = [NSURLSessionConfiguration ephemeralSessionConfiguration]; // no cookies or data storage
322 config.networkServiceType = NSURLNetworkServiceTypeDefault;
323 config.discretionary = NO;
324 }
325
326 config.HTTPAdditionalHeaders = @{ @"User-Agent" : @"com.apple.trustd/2.0",
327 @"Accept" : @"*/*",
328 @"Accept-Encoding" : @"gzip,deflate,br"};
329
330 config.TLSMinimumSupportedProtocol = kTLSProtocol12;
331
332 return config;
333 }
334
335 - (void) createSession:(dispatch_queue_t)updateQueue forServer:(NSString *)updateServer {
336 NSURLSessionConfiguration *config = [self validUpdateConfiguration];
337 ValidDelegate *delegate = [[ValidDelegate alloc] init];
338 delegate.handler = ^(void) {
339 request.updateScheduled = 0.0;
340 secdebug("validupdate", "resetting scheduled time");
341 };
342 delegate.transaction = NULL;
343 delegate.revDbUpdateQueue = updateQueue;
344 delegate.finishedDownloading = NO;
345 delegate.currentUpdateServer = [updateServer copy];
346
347 /* Callbacks should be on a separate NSOperationQueue.
348 We'll then dispatch the work on updateQueue and return from the callback. */
349 NSOperationQueue *queue = [[NSOperationQueue alloc] init];
350 queue.maxConcurrentOperationCount = 1;
351 _backgroundSession = [NSURLSession sessionWithConfiguration:config delegate:delegate delegateQueue:queue];
352 }
353
354 - (BOOL) scheduleUpdateFromServer:(NSString *)server forVersion:(NSUInteger)version withQueue:(dispatch_queue_t)updateQueue {
355 if (!server) {
356 secnotice("validupdate", "invalid update request");
357 return NO;
358 }
359
360 if (!updateQueue) {
361 secnotice("validupdate", "missing update queue, skipping update");
362 return NO;
363 }
364
365 /* nsurlsessiond waits for unlock to finish launching, so we can't block trust evaluations
366 * on scheduling this background task. Also, we want to wait a sufficient amount of time
367 * after system boot before trying to initiate network activity, to avoid the possibility
368 * of a performance regression in the boot path. */
369 dispatch_async(updateQueue, ^{
370 CFAbsoluteTime now = CFAbsoluteTimeGetCurrent();
371 if (self.updateScheduled != 0.0) {
372 secdebug("validupdate", "update in progress (scheduled %f)", (double)self.updateScheduled);
373 return;
374 } else {
375 uint64_t uptime = systemUptimeInSeconds();
376 const uint64_t minUptime = 180;
377 if (uptime < minUptime) {
378 gNextUpdate = now + (minUptime - uptime);
379 gUpdateStarted = 0;
380 secnotice("validupdate", "postponing update until %f", gNextUpdate);
381 return;
382 } else {
383 self.updateScheduled = now;
384 secnotice("validupdate", "scheduling update at %f", (double)self.updateScheduled);
385 }
386 }
387
388 /* we have an update to schedule, so take a transaction while we work */
389 os_transaction_t transaction = os_transaction_create("com.apple.trustd.valid.scheduleUpdate");
390
391 /* clear all old sessions and cleanup disk (for previous download tasks) */
392 static dispatch_once_t onceToken;
393 dispatch_once(&onceToken, ^{
394 @autoreleasepool {
395 [NSURLSession _obliterateAllBackgroundSessionsWithCompletionHandler:^{
396 secnotice("validupdate", "removing all old sessions for trustd");
397 }];
398 }
399 });
400
401 if (!self.backgroundSession) {
402 [self createSession:updateQueue forServer:server];
403 } else {
404 ValidDelegate *delegate = (ValidDelegate *)[self.backgroundSession delegate];
405 delegate.currentUpdateServer = [server copy];
406 }
407
408 /* POWER LOG EVENT: scheduling our background download session now */
409 SecPLLogRegisteredEvent(@"ValidUpdateEvent", @{
410 @"timestamp" : @([[NSDate date] timeIntervalSince1970]),
411 @"event" : @"downloadScheduled",
412 @"version" : @(version)
413 });
414
415 NSURL *validUrl = [NSURL URLWithString:[NSString stringWithFormat:@"https://%@/g3/v%ld",
416 server, (unsigned long)version]];
417 NSURLSessionDataTask *dataTask = [self.backgroundSession dataTaskWithURL:validUrl];
418 dataTask.taskDescription = [NSString stringWithFormat:@"%lu",(unsigned long)version];
419 [dataTask resume];
420 secnotice("validupdate", "scheduled background data task %@ at %f", dataTask, CFAbsoluteTimeGetCurrent());
421 (void) transaction; // dead store
422 transaction = nil; // ARC releases the transaction
423 });
424
425 return YES;
426 }
427 @end
428
429 bool SecValidUpdateRequest(dispatch_queue_t queue, CFStringRef server, CFIndex version) {
430 static dispatch_once_t onceToken;
431 dispatch_once(&onceToken, ^{
432 @autoreleasepool {
433 request = [[ValidUpdateRequest alloc] init];
434 }
435 });
436 @autoreleasepool {
437 return [request scheduleUpdateFromServer:(__bridge NSString*)server forVersion:version withQueue:queue];
438 }
439 }
440
441 /* MARK: - */
442 /* MARK: OCSP Fetch Networking */
443 #define OCSP_REQUEST_THRESHOLD 10
444
445 @interface OCSPFetchDelegate : TrustURLSessionDelegate
446 @end
447
448 @implementation OCSPFetchDelegate
449 - (BOOL)fetchNext:(NSURLSession *)session {
450 SecORVCRef orvc = (SecORVCRef)self.context;
451 TrustAnalyticsBuilder *analytics = SecPathBuilderGetAnalyticsData(orvc->builder);
452
453 BOOL result = true;
454 if ((result = [super fetchNext:session])) {
455 /* no fetch scheduled */
456 orvc->done = true;
457 } else {
458 if (self.URIix > 0) {
459 orvc->responder = (__bridge CFURLRef)self.URIs[self.URIix - 1];
460 } else {
461 orvc->responder = (__bridge CFURLRef)self.URIs[0];
462 }
463 if (analytics) {
464 analytics->ocsp_fetches++;
465 }
466 }
467 return result;
468 }
469
470 - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
471 /* call the superclass's method to set expiration */
472 [super URLSession:session task:task didCompleteWithError:error];
473
474 __block SecORVCRef orvc = (SecORVCRef)self.context;
475 if (!orvc || !orvc->builder) {
476 /* We already returned to the PathBuilder state machine. */
477 return;
478 }
479
480 TrustAnalyticsBuilder *analytics = SecPathBuilderGetAnalyticsData(orvc->builder);
481 if (error) {
482 /* Log the error */
483 secnotice("rvc", "Failed to download ocsp response %@, with error %@", task.originalRequest.URL, error);
484 if (analytics) {
485 analytics->ocsp_fetch_failed++;
486 }
487 } else {
488 SecOCSPResponseRef ocspResponse = SecOCSPResponseCreate((__bridge CFDataRef)self.response);
489 if (ocspResponse) {
490 SecORVCConsumeOCSPResponse(orvc, ocspResponse, self.expiration, true, false);
491 if (analytics && !orvc->done) {
492 /* We got an OCSP response that didn't pass validation */
493 analytics-> ocsp_validation_failed = true;
494 }
495 } else if (analytics) {
496 /* We got something that wasn't an OCSP response (e.g. captive portal) --
497 * we consider that a fetch failure */
498 analytics->ocsp_fetch_failed++;
499 }
500 }
501
502 /* If we didn't get a valid OCSP response, try the next URI */
503 if (!orvc->done) {
504 (void)[self fetchNext:session];
505 }
506
507 /* We got a valid OCSP response or couldn't schedule any more fetches.
508 * Close the session, update the PVCs, decrement the async count, and callback if we're all done. */
509 if (orvc->done) {
510 secdebug("rvc", "builder %p, done with OCSP fetches for cert: %ld", orvc->builder, orvc->certIX);
511 self.context = nil;
512 [session invalidateAndCancel];
513 SecORVCUpdatePVC(orvc);
514 if (0 == SecPathBuilderDecrementAsyncJobCount(orvc->builder)) {
515 /* We're the last async job to finish, jump back into the state machine */
516 secdebug("rvc", "builder %p, done with all async jobs", orvc->builder);
517 dispatch_async(SecPathBuilderGetQueue(orvc->builder), ^{
518 SecPathBuilderStep(orvc->builder);
519 });
520 }
521 }
522 }
523
524 - (NSURLRequest *)createNextRequest:(NSURL *)uri {
525 SecORVCRef orvc = (SecORVCRef)self.context;
526 CFDataRef ocspDER = CFRetainSafe(SecOCSPRequestGetDER(orvc->ocspRequest));
527 NSData *nsOcspDER = CFBridgingRelease(ocspDER);
528 NSString *ocspBase64 = [nsOcspDER base64EncodedStringWithOptions:0];
529
530 /* Ensure that we percent-encode specific characters in the base64 path
531 which are defined as delimiters in RFC 3986 [2.2].
532 */
533 static NSMutableCharacterSet *allowedSet = nil;
534 static dispatch_once_t onceToken;
535 dispatch_once(&onceToken, ^{
536 allowedSet = [[NSCharacterSet URLPathAllowedCharacterSet] mutableCopy];
537 [allowedSet removeCharactersInString:@":/?#[]@!$&'()*+,;="];
538 });
539 NSString *escapedRequest = [ocspBase64 stringByAddingPercentEncodingWithAllowedCharacters:allowedSet];
540 NSURLRequest *request = nil;
541
542 /* Interesting tidbit from rfc5019
543 When sending requests that are less than or equal to 255 bytes in
544 total (after encoding) including the scheme and delimiters (http://),
545 server name and base64-encoded OCSPRequest structure, clients MUST
546 use the GET method (to enable OCSP response caching). OCSP requests
547 larger than 255 bytes SHOULD be submitted using the POST method.
548 */
549 if (([[uri absoluteString] length] + 1 + [escapedRequest length]) < 256) {
550 /* Use a GET */
551 NSString *requestString = [NSString stringWithFormat:@"%@/%@", [uri absoluteString], escapedRequest];
552 NSURL *requestURL = [NSURL URLWithString:requestString];
553 request = [NSURLRequest requestWithURL:requestURL];
554 } else {
555 /* Use a POST */
556 NSMutableURLRequest *mutableRequest = [NSMutableURLRequest requestWithURL:uri];
557 mutableRequest.HTTPMethod = @"POST";
558 mutableRequest.HTTPBody = nsOcspDER;
559 request = mutableRequest;
560 }
561
562 return request;
563 }
564
565 - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didFinishCollectingMetrics:(NSURLSessionTaskMetrics *)taskMetrics {
566 secdebug("rvc", "got metrics with task interval %f", taskMetrics.taskInterval.duration);
567 SecORVCRef orvc = (SecORVCRef)self.context;
568 if (orvc && orvc->builder) {
569 TrustAnalyticsBuilder *analytics = SecPathBuilderGetAnalyticsData(orvc->builder);
570 if (analytics) {
571 analytics->ocsp_fetch_time += (uint64_t)(taskMetrics.taskInterval.duration * NSEC_PER_SEC);
572 }
573 }
574 }
575 @end
576
577 bool SecORVCBeginFetches(SecORVCRef orvc, SecCertificateRef cert) {
578 @autoreleasepool {
579 CFArrayRef ocspResponders = CFRetainSafe(SecCertificateGetOCSPResponders(cert));
580 NSArray *nsResponders = CFBridgingRelease(ocspResponders);
581
582 NSInteger count = [nsResponders count];
583 if (count > OCSP_REQUEST_THRESHOLD) {
584 secnotice("rvc", "too may OCSP responder entries (%ld)", (long)count);
585 orvc->done = true;
586 return true;
587 }
588
589 NSURLSessionConfiguration *config = [NSURLSessionConfiguration ephemeralSessionConfiguration];
590 config.timeoutIntervalForResource = TrustURLSessionGetResourceTimeout();
591 config.HTTPAdditionalHeaders = @{@"User-Agent" : @"com.apple.trustd/2.0"};
592
593 NSData *auditToken = CFBridgingRelease(SecPathBuilderCopyClientAuditToken(orvc->builder));
594 if (auditToken) {
595 config._sourceApplicationAuditTokenData = auditToken;
596 }
597
598 OCSPFetchDelegate *delegate = [[OCSPFetchDelegate alloc] init];
599 delegate.context = orvc;
600 delegate.URIs = nsResponders;
601 delegate.URIix = 0;
602
603 NSOperationQueue *queue = [[NSOperationQueue alloc] init];
604
605 NSURLSession *session = [NSURLSession sessionWithConfiguration:config delegate:delegate delegateQueue:queue];
606 secdebug("rvc", "created URLSession for %@", cert);
607
608 bool result = false;
609 if ((result = [delegate fetchNext:session])) {
610 /* no fetch scheduled, close the session */
611 [session invalidateAndCancel];
612 }
613 return result;
614 }
615 }