2 * Copyright (c) 2017-2019 Apple Inc. All Rights Reserved.
4 * @APPLE_LICENSE_HEADER_START@
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
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.
21 * @APPLE_LICENSE_HEADER_END@
25 #include <AssertMacros.h>
26 #import <Foundation/Foundation.h>
27 #import <CFNetwork/CFNSURLConnection.h>
29 #include <sys/sysctl.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"
39 #include "SecRevocationDb.h"
40 #include "SecRevocationServer.h"
41 #include "SecTrustServer.h"
42 #include "SecOCSPRequest.h"
43 #include "SecOCSPResponse.h"
45 #import "SecTrustLoggingServer.h"
46 #import "TrustURLSessionDelegate.h"
48 #import "SecRevocationNetworking.h"
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");
55 extern CFAbsoluteTime gUpdateStarted;
56 extern CFAbsoluteTime gNextUpdate;
58 static int checkBasePath(const char *basePath) {
59 return mkpath_np((char*)basePath, 0755);
62 static uint64_t systemUptimeInSeconds() {
63 struct timeval boottime;
64 size_t tv_size = sizeof(boottime);
65 time_t now, uptime = 0;
68 mib[1] = KERN_BOOTTIME;
70 if (sysctl(mib, 2, &boottime, &tv_size, NULL, 0) != -1 &&
71 boottime.tv_sec != 0) {
72 uptime = now - boottime.tv_sec;
74 return (uint64_t)uptime;
77 typedef void (^CompletionHandler)(void);
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;
89 @implementation ValidDelegate
92 /* POWER LOG EVENT: operation canceled */
93 SecPLLogRegisteredEvent(@"ValidUpdateEvent", @{
94 @"timestamp" : @([[NSDate date] timeIntervalSince1970]),
95 @"event" : (self->_finishedDownloading) ? @"updateCanceled" : @"downloadCanceled"
97 secnotice("validupdate", "%s canceled at %f",
98 (self->_finishedDownloading) ? "update" : "download",
99 (double)CFAbsoluteTimeGetCurrent());
102 SecRevocationDbComputeAndSetNextUpdateTime();
103 if (self->_transaction) {
104 self->_transaction = nil;
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) {
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"
125 secnotice("validupdate", "update started at %f", (double)CFAbsoluteTimeGetCurrent());
127 CFDataRef updateData = NULL;
128 const char *updateFilePath = [updateFileURL fileSystemRepresentation];
130 if ((rtn = readValidFile(updateFilePath, &updateData)) != 0) {
131 secerror("failed to read %@ with error %d", updateFileURL, rtn);
132 TrustdHealthAnalyticsLogErrorCode(TAEventValidUpdate, TAFatalError, rtn);
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);
143 CFReleaseNull(updateData);
145 /* We're done with this file */
146 [updateFile closeFile];
147 if (updateFilePath) {
148 (void)remove(updateFilePath);
150 self->_currentUpdateFile = nil;
151 self->_currentUpdateFileURL = nil;
152 self->_currentUpdateServer = nil;
154 /* POWER LOG EVENT: background update finished */
155 SecPLLogRegisteredEvent(@"ValidUpdateEvent", @{
156 @"timestamp" : @([[NSDate date] timeIntervalSince1970]),
157 @"event" : @"updateFinished"
160 /* Update is complete */
161 secnotice("validupdate", "update finished at %f", (double)CFAbsoluteTimeGetCurrent());
165 transaction = nil; // we're all done now
169 - (NSInteger)versionFromTask:(NSURLSessionTask *)task {
170 return atol([task.taskDescription cStringUsingEncoding:NSUTF8StringEncoding]);
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]);
183 WithPathInRevocationInfoDirectory(NULL, ^(const char *utf8String) {
184 (void)checkBasePath(utf8String);
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);
196 /* Clean up any old files from previous tasks. */
197 (void)remove(updateFilePath);
200 fd = open(updateFilePath, O_RDWR | O_CREAT | O_TRUNC, 0644);
202 secnotice("validupdate","unable to open %@ (errno %d)", self->_currentUpdateFileURL, errno);
208 /* POWER LOG EVENT: background download actually started */
209 SecPLLogRegisteredEvent(@"ValidUpdateEvent", @{
210 @"timestamp" : @([[NSDate date] timeIntervalSince1970]),
211 @"event" : @"downloadStarted"
213 secnotice("validupdate", "download started at %f", (double)CFAbsoluteTimeGetCurrent());
215 NSError *error = nil;
216 self->_currentUpdateFile = [NSFileHandle fileHandleForWritingToURL:self->_currentUpdateFileURL error:&error];
217 if (!self->_currentUpdateFile) {
218 secnotice("validupdate", "failed to open %@: %@. canceling task %@", self->_currentUpdateFileURL, error, dataTask);
219 #if ENABLE_TRUSTD_ANALYTICS
220 [[TrustAnalytics logger] logResultForEvent:TrustdHealthAnalyticsEventValidUpdate hardFailure:NO result:error];
221 #endif // ENABLE_TRUSTD_ANALYTICS
222 completionHandler(NSURLSessionResponseCancel);
227 completionHandler(NSURLSessionResponseAllow);
230 - (void)URLSession:(NSURLSession *)session
231 dataTask:(NSURLSessionDataTask *)dataTask
232 didReceiveData:(NSData *)data {
233 secdebug("validupdate", "Session %@ data task %@ returned %lu bytes (%lld bytes so far) out of expected %lld bytes",
234 session, dataTask, (unsigned long)[data length], [dataTask countOfBytesReceived], [dataTask countOfBytesExpectedToReceive]);
236 if (!self->_currentUpdateFile) {
237 secnotice("validupdate", "received data, but output file is not open");
244 /* Writing can fail and throw an exception, e.g. if we run out of disk space. */
245 [self->_currentUpdateFile writeData:data];
247 @catch(NSException *exception) {
248 secnotice("validupdate", "%s", exception.description.UTF8String);
249 TrustdHealthAnalyticsLogErrorCode(TAEventValidUpdate, TARecoverableError, errSecDiskFull);
255 - (void)URLSession:(NSURLSession *)session
256 task:(NSURLSessionTask *)task
257 didCompleteWithError:(NSError *)error {
259 secnotice("validupdate", "Session %@ task %@ failed with error %@", session, task, error);
260 #if ENABLE_TRUSTD_ANALYTICS
261 [[TrustAnalytics logger] logResultForEvent:TrustdHealthAnalyticsEventValidUpdate hardFailure:NO result:error];
262 #endif // ENABLE_TRUSTD_ANALYTICS
264 /* close file before we leave */
265 [self->_currentUpdateFile closeFile];
266 self->_currentUpdateFile = nil;
267 self->_currentUpdateServer = nil;
268 self->_currentUpdateFileURL = nil;
270 /* POWER LOG EVENT: background download finished */
271 SecPLLogRegisteredEvent(@"ValidUpdateEvent", @{
272 @"timestamp" : @([[NSDate date] timeIntervalSince1970]),
273 @"event" : @"downloadFinished"
275 secnotice("validupdate", "download finished at %f", (double)CFAbsoluteTimeGetCurrent());
276 secdebug("validupdate", "Session %@ task %@ succeeded", session, task);
277 self->_finishedDownloading = YES;
278 [self updateDb:[self versionFromTask:task]];
280 if (self->_transaction) {
281 self->_transaction = nil;
287 @interface ValidUpdateRequest : NSObject
288 @property NSTimeInterval updateScheduled;
289 @property NSURLSession *backgroundSession;
290 @property NSURLSession *ephemeralSession;
293 static ValidUpdateRequest *request = nil;
295 @implementation ValidUpdateRequest
297 - (NSURLSessionConfiguration *)validUpdateConfiguration:(BOOL)background {
298 NSURLSessionConfiguration *config = nil;
300 /* preferences to override defaults */
301 CFTypeRef value = NULL;
302 bool updateOnWiFiOnly = true;
303 value = CFPreferencesCopyValue(kUpdateWiFiOnlyKey, kSecPrefsDomain, kCFPreferencesAnyUser, kCFPreferencesCurrentHost);
304 if (isBoolean(value)) {
305 updateOnWiFiOnly = CFBooleanGetValue((CFBooleanRef)value);
307 CFReleaseNull(value);
309 config = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier: @"com.apple.trustd.networking.background"];
310 config.networkServiceType = NSURLNetworkServiceTypeBackground;
311 config.discretionary = YES;
312 config._requiresPowerPluggedIn = YES;
313 config.allowsCellularAccess = (!updateOnWiFiOnly) ? YES : NO;
315 config = [NSURLSessionConfiguration ephemeralSessionConfiguration]; // no cookies or data storage
316 config.networkServiceType = NSURLNetworkServiceTypeDefault;
317 config.discretionary = NO;
320 config.HTTPAdditionalHeaders = @{ @"User-Agent" : @"com.apple.trustd/2.0",
322 @"Accept-Encoding" : @"gzip,deflate,br"};
324 config.TLSMinimumSupportedProtocol = kTLSProtocol12;
329 - (NSURLSession *)createSession:(BOOL)background queue:(dispatch_queue_t)updateQueue forServer:(NSString *)updateServer
331 NSURLSessionConfiguration *config = [self validUpdateConfiguration:background];
332 ValidDelegate *delegate = [[ValidDelegate alloc] init];
333 delegate.handler = ^(void) {
334 request.updateScheduled = 0.0;
335 secdebug("validupdate", "resetting scheduled time");
337 delegate.transaction = NULL;
338 delegate.revDbUpdateQueue = updateQueue;
339 delegate.finishedDownloading = NO;
340 delegate.currentUpdateServer = [updateServer copy];
342 /* Callbacks should be on a separate NSOperationQueue.
343 We'll then dispatch the work on updateQueue and return from the callback. */
344 NSOperationQueue *queue = [[NSOperationQueue alloc] init];
345 queue.maxConcurrentOperationCount = 1;
346 return [NSURLSession sessionWithConfiguration:config delegate:delegate delegateQueue:queue];
349 - (void) createSessions:(dispatch_queue_t)updateQueue forServer:(NSString *)updateServer {
350 self.ephemeralSession = [self createSession:NO queue:updateQueue forServer:updateServer];
352 bool updateInBackground = true;
353 CFTypeRef value = CFPreferencesCopyValue(kUpdateBackgroundKey, kSecPrefsDomain, kCFPreferencesAnyUser, kCFPreferencesCurrentHost);
354 if (isBoolean(value)) {
355 updateInBackground = CFBooleanGetValue((CFBooleanRef)value);
357 CFReleaseNull(value);
358 if (updateInBackground) {
359 self.backgroundSession = [self createSession:YES queue:updateQueue forServer:updateServer];
361 self.backgroundSession = self.ephemeralSession;
365 - (BOOL) scheduleUpdateFromServer:(NSString *)server forVersion:(NSUInteger)version withQueue:(dispatch_queue_t)updateQueue {
367 secnotice("validupdate", "invalid update request");
372 secnotice("validupdate", "missing update queue, skipping update");
376 /* nsurlsessiond waits for unlock to finish launching, so we can't block trust evaluations
377 * on scheduling this background task. Also, we want to wait a sufficient amount of time
378 * after system boot before trying to initiate network activity, to avoid the possibility
379 * of a performance regression in the boot path. */
380 dispatch_async(updateQueue, ^{
381 CFAbsoluteTime now = CFAbsoluteTimeGetCurrent();
382 if (self.updateScheduled != 0.0) {
383 secdebug("validupdate", "update in progress (scheduled %f)", (double)self.updateScheduled);
386 uint64_t uptime = systemUptimeInSeconds();
387 const uint64_t minUptime = 180;
388 if (uptime < minUptime) {
389 gNextUpdate = now + (minUptime - uptime);
391 secnotice("validupdate", "postponing update until %f", gNextUpdate);
394 self.updateScheduled = now;
395 secnotice("validupdate", "scheduling update at %f", (double)self.updateScheduled);
399 /* we have an update to schedule, so take a transaction while we work */
400 os_transaction_t transaction = os_transaction_create("com.apple.trustd.valid.scheduleUpdate");
402 /* clear all old sessions and cleanup disk (for previous download tasks) */
403 static dispatch_once_t onceToken;
404 dispatch_once(&onceToken, ^{
406 [NSURLSession _obliterateAllBackgroundSessionsWithCompletionHandler:^{
407 secnotice("validupdate", "removing all old sessions for trustd");
412 if (!self.backgroundSession) {
413 [self createSessions:updateQueue forServer:server];
415 ValidDelegate *delegate = (ValidDelegate *)[self.backgroundSession delegate];
416 delegate.currentUpdateServer = [server copy];
419 /* POWER LOG EVENT: scheduling our background download session now */
420 SecPLLogRegisteredEvent(@"ValidUpdateEvent", @{
421 @"timestamp" : @([[NSDate date] timeIntervalSince1970]),
422 @"event" : @"downloadScheduled",
423 @"version" : @(version)
426 NSURL *validUrl = [NSURL URLWithString:[NSString stringWithFormat:@"https://%@/g3/v%ld",
427 server, (unsigned long)version]];
428 NSURLSessionDataTask *dataTask = [self.backgroundSession dataTaskWithURL:validUrl];
429 dataTask.taskDescription = [NSString stringWithFormat:@"%lu",(unsigned long)version];
431 secnotice("validupdate", "scheduled background data task %@ at %f", dataTask, CFAbsoluteTimeGetCurrent());
432 (void) transaction; // dead store
433 transaction = nil; // ARC releases the transaction
439 - (BOOL)updateNowFromServer:(NSString *)server version:(NSUInteger)version queue:(dispatch_queue_t)updateQueue
442 secnotice("validupdate", "invalid update request");
447 secnotice("validupdate", "missing update queue, skipping update");
451 if (!self.ephemeralSession) {
452 [self createSessions:updateQueue forServer:server];
454 ValidDelegate *delegate = (ValidDelegate *)[self.ephemeralSession delegate];
455 delegate.currentUpdateServer = [server copy];
458 /* POWER LOG EVENT: scheduling our background download session now */
459 SecPLLogRegisteredEvent(@"ValidUpdateEvent", @{
460 @"timestamp" : @([[NSDate date] timeIntervalSince1970]),
461 @"event" : @"downloadScheduled",
462 @"version" : @(version)
465 NSURL *validUrl = [NSURL URLWithString:[NSString stringWithFormat:@"https://%@/g3/v%ld",
466 server, (unsigned long)version]];
467 NSURLSessionDataTask *dataTask = [self.ephemeralSession dataTaskWithURL:validUrl];
468 dataTask.taskDescription = [NSString stringWithFormat:@"%lu",(unsigned long)version];
470 secnotice("validupdate", "running foreground data task %@ at %f", dataTask, CFAbsoluteTimeGetCurrent());
476 static void SecValidUpdateCreateValidUpdateRequest()
478 static dispatch_once_t onceToken;
479 dispatch_once(&onceToken, ^{
481 request = [[ValidUpdateRequest alloc] init];
486 bool SecValidUpdateRequest(dispatch_queue_t queue, CFStringRef server, CFIndex version) {
487 SecValidUpdateCreateValidUpdateRequest();
489 return [request scheduleUpdateFromServer:(__bridge NSString*)server forVersion:version withQueue:queue];
493 bool SecValidUpdateUpdateNow(dispatch_queue_t queue, CFStringRef server, CFIndex version) {
494 SecValidUpdateCreateValidUpdateRequest();
496 return [request updateNowFromServer:(__bridge NSString*)server version:version queue:queue];
501 /* MARK: OCSP Fetch Networking */
502 #define OCSP_REQUEST_THRESHOLD 10
504 @interface OCSPFetchDelegate : TrustURLSessionDelegate
507 @implementation OCSPFetchDelegate
508 - (BOOL)fetchNext:(NSURLSession *)session {
509 SecORVCRef orvc = (SecORVCRef)self.context;
510 TrustAnalyticsBuilder *analytics = SecPathBuilderGetAnalyticsData(orvc->builder);
513 if ((result = [super fetchNext:session])) {
514 /* no fetch scheduled */
517 if (self.URIix > 0) {
518 orvc->responder = (__bridge CFURLRef)self.URIs[self.URIix - 1];
520 orvc->responder = (__bridge CFURLRef)self.URIs[0];
523 analytics->ocsp_fetches++;
529 - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
530 /* call the superclass's method to set expiration */
531 [super URLSession:session task:task didCompleteWithError:error];
533 __block SecORVCRef orvc = (SecORVCRef)self.context;
534 if (!orvc || !orvc->builder) {
535 /* We already returned to the PathBuilder state machine. */
539 TrustAnalyticsBuilder *analytics = SecPathBuilderGetAnalyticsData(orvc->builder);
542 secnotice("rvc", "Failed to download ocsp response %@, with error %@", task.originalRequest.URL, error);
544 analytics->ocsp_fetch_failed++;
547 SecOCSPResponseRef ocspResponse = SecOCSPResponseCreate((__bridge CFDataRef)self.response);
549 SecORVCConsumeOCSPResponse(orvc, ocspResponse, self.expiration, true, false);
550 if (analytics && !orvc->done) {
551 /* We got an OCSP response that didn't pass validation */
552 analytics->ocsp_validation_failed = true;
554 } else if (analytics) {
555 /* We got something that wasn't an OCSP response (e.g. captive portal) --
556 * we consider that a fetch failure */
557 analytics->ocsp_fetch_failed++;
561 /* If we didn't get a valid OCSP response, try the next URI */
563 (void)[self fetchNext:session];
566 /* We got a valid OCSP response or couldn't schedule any more fetches.
567 * Close the session, update the PVCs, decrement the async count, and callback if we're all done. */
569 secdebug("rvc", "builder %p, done with OCSP fetches for cert: %ld", orvc->builder, orvc->certIX);
571 [session invalidateAndCancel];
572 SecORVCUpdatePVC(orvc);
573 if (0 == SecPathBuilderDecrementAsyncJobCount(orvc->builder)) {
574 /* We're the last async job to finish, jump back into the state machine */
575 secdebug("rvc", "builder %p, done with all async jobs", orvc->builder);
576 dispatch_async(SecPathBuilderGetQueue(orvc->builder), ^{
577 SecPathBuilderStep(orvc->builder);
583 - (NSURLRequest *)createNextRequest:(NSURL *)uri {
584 SecORVCRef orvc = (SecORVCRef)self.context;
585 CFDataRef ocspDER = CFRetainSafe(SecOCSPRequestGetDER(orvc->ocspRequest));
586 NSData *nsOcspDER = CFBridgingRelease(ocspDER);
587 NSString *ocspBase64 = [nsOcspDER base64EncodedStringWithOptions:0];
589 /* Ensure that we percent-encode specific characters in the base64 path
590 which are defined as delimiters in RFC 3986 [2.2].
592 static NSMutableCharacterSet *allowedSet = nil;
593 static dispatch_once_t onceToken;
594 dispatch_once(&onceToken, ^{
595 allowedSet = [[NSCharacterSet URLPathAllowedCharacterSet] mutableCopy];
596 [allowedSet removeCharactersInString:@":/?#[]@!$&'()*+,;="];
598 NSString *escapedRequest = [ocspBase64 stringByAddingPercentEncodingWithAllowedCharacters:allowedSet];
599 NSURLRequest *request = nil;
601 /* Interesting tidbit from rfc5019
602 When sending requests that are less than or equal to 255 bytes in
603 total (after encoding) including the scheme and delimiters (http://),
604 server name and base64-encoded OCSPRequest structure, clients MUST
605 use the GET method (to enable OCSP response caching). OCSP requests
606 larger than 255 bytes SHOULD be submitted using the POST method.
608 if (([[uri absoluteString] length] + 1 + [escapedRequest length]) < 256) {
610 NSString *requestString = [NSString stringWithFormat:@"%@/%@", [uri absoluteString], escapedRequest];
611 NSURL *requestURL = [NSURL URLWithString:requestString];
612 request = [NSURLRequest requestWithURL:requestURL];
615 NSMutableURLRequest *mutableRequest = [NSMutableURLRequest requestWithURL:uri];
616 mutableRequest.HTTPMethod = @"POST";
617 mutableRequest.HTTPBody = nsOcspDER;
618 request = mutableRequest;
624 - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didFinishCollectingMetrics:(NSURLSessionTaskMetrics *)taskMetrics {
625 secdebug("rvc", "got metrics with task interval %f", taskMetrics.taskInterval.duration);
626 SecORVCRef orvc = (SecORVCRef)self.context;
627 if (orvc && orvc->builder) {
628 TrustAnalyticsBuilder *analytics = SecPathBuilderGetAnalyticsData(orvc->builder);
630 analytics->ocsp_fetch_time += (uint64_t)(taskMetrics.taskInterval.duration * NSEC_PER_SEC);
636 bool SecORVCBeginFetches(SecORVCRef orvc, SecCertificateRef cert) {
638 CFArrayRef ocspResponders = CFRetainSafe(SecCertificateGetOCSPResponders(cert));
639 NSArray *nsResponders = CFBridgingRelease(ocspResponders);
641 NSInteger count = [nsResponders count];
642 if (count > OCSP_REQUEST_THRESHOLD) {
643 secnotice("rvc", "too may OCSP responder entries (%ld)", (long)count);
648 NSURLSessionConfiguration *config = [NSURLSessionConfiguration ephemeralSessionConfiguration];
649 config.timeoutIntervalForResource = TrustURLSessionGetResourceTimeout();
650 config.HTTPAdditionalHeaders = @{@"User-Agent" : @"com.apple.trustd/2.0"};
652 NSData *auditToken = CFBridgingRelease(SecPathBuilderCopyClientAuditToken(orvc->builder));
654 config._sourceApplicationAuditTokenData = auditToken;
657 OCSPFetchDelegate *delegate = [[OCSPFetchDelegate alloc] init];
658 delegate.context = orvc;
659 delegate.URIs = nsResponders;
662 NSOperationQueue *queue = [[NSOperationQueue alloc] init];
664 NSURLSession *session = [NSURLSession sessionWithConfiguration:config delegate:delegate delegateQueue:queue];
665 secdebug("rvc", "created URLSession for %@", cert);
668 if ((result = [delegate fetchNext:session])) {
669 /* no fetch scheduled, close the session */
670 [session invalidateAndCancel];