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