]> git.saurik.com Git - apple/security.git/blob - OSX/sec/securityd/SecRevocationNetworking.m
Security-58286.220.15.tar.gz
[apple/security.git] / OSX / sec / securityd / SecRevocationNetworking.m
1 /*
2 * Copyright (c) 2017-2018 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", session, dataTask,
182 (long)[(NSHTTPURLResponse *)response statusCode],[response expectedContentLength]);
183
184 (void)checkBasePath(kSecRevocationBasePath);
185 CFURLRef updateFileURL = SecCopyURLForFileInRevocationInfoDirectory(CFSTR("update-current"));
186 self->_currentUpdateFileURL = (updateFileURL) ? CFBridgingRelease(updateFileURL) : nil;
187 const char *updateFilePath = [self->_currentUpdateFileURL fileSystemRepresentation];
188 if (!updateFilePath) {
189 secnotice("validupdate", "failed to find revocation info directory. canceling task %@", dataTask);
190 completionHandler(NSURLSessionResponseCancel);
191 [self reschedule];
192 return;
193 }
194
195 /* Clean up any old files from previous tasks. */
196 (void)remove(updateFilePath);
197
198 int fd;
199 off_t off;
200 fd = open(updateFilePath, O_RDWR | O_CREAT | O_TRUNC, 0644);
201 if (fd < 0 || (off = lseek(fd, 0, SEEK_SET)) < 0) {
202 secnotice("validupdate","unable to open %@ (errno %d)", self->_currentUpdateFileURL, errno);
203 }
204 if (fd >= 0) {
205 close(fd);
206 }
207
208 /* POWER LOG EVENT: background download actually started */
209 SecPLLogRegisteredEvent(@"ValidUpdateEvent", @{
210 @"timestamp" : @([[NSDate date] timeIntervalSince1970]),
211 @"event" : @"downloadStarted"
212 });
213 secnotice("validupdate", "download started at %f", (double)CFAbsoluteTimeGetCurrent());
214
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 [[TrustdHealthAnalytics logger] logResultForEvent:TrustdHealthAnalyticsEventValidUpdate hardFailure:NO result:error];
221 #endif // ENABLE_TRUSTD_ANALYTICS
222 completionHandler(NSURLSessionResponseCancel);
223 [self reschedule];
224 return;
225 }
226
227 completionHandler(NSURLSessionResponseAllow);
228 }
229
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]);
235
236 if (!self->_currentUpdateFile) {
237 secnotice("validupdate", "received data, but output file is not open");
238 [dataTask cancel];
239 [self reschedule];
240 return;
241 }
242
243 @try {
244 /* Writing can fail and throw an exception, e.g. if we run out of disk space. */
245 [self->_currentUpdateFile writeData:data];
246 }
247 @catch(NSException *exception) {
248 secnotice("validupdate", "%s", exception.description.UTF8String);
249 TrustdHealthAnalyticsLogErrorCode(TAEventValidUpdate, TARecoverableError, errSecDiskFull);
250 [dataTask cancel];
251 [self reschedule];
252 }
253 }
254
255 - (void)URLSession:(NSURLSession *)session
256 task:(NSURLSessionTask *)task
257 didCompleteWithError:(NSError *)error {
258 if (error) {
259 secnotice("validupdate", "Session %@ task %@ failed with error %@", session, task, error);
260 #if ENABLE_TRUSTD_ANALYTICS
261 [[TrustdHealthAnalytics logger] logResultForEvent:TrustdHealthAnalyticsEventValidUpdate hardFailure:NO result:error];
262 #endif // ENABLE_TRUSTD_ANALYTICS
263 [self reschedule];
264 /* close file before we leave */
265 [self->_currentUpdateFile closeFile];
266 self->_currentUpdateFile = nil;
267 self->_currentUpdateServer = nil;
268 self->_currentUpdateFileURL = nil;
269 } else {
270 /* POWER LOG EVENT: background download finished */
271 SecPLLogRegisteredEvent(@"ValidUpdateEvent", @{
272 @"timestamp" : @([[NSDate date] timeIntervalSince1970]),
273 @"event" : @"downloadFinished"
274 });
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]];
279 }
280 if (self->_transaction) {
281 self->_transaction = nil;
282 }
283 }
284
285 @end
286
287 @interface ValidUpdateRequest : NSObject
288 @property NSTimeInterval updateScheduled;
289 @property NSURLSession *backgroundSession;
290 @end
291
292 static ValidUpdateRequest *request = nil;
293
294 @implementation ValidUpdateRequest
295
296 - (NSURLSessionConfiguration *)validUpdateConfiguration {
297 /* preferences to override defaults */
298 CFTypeRef value = NULL;
299 bool updateOnWiFiOnly = true;
300 value = CFPreferencesCopyValue(kUpdateWiFiOnlyKey, kSecPrefsDomain, kCFPreferencesAnyUser, kCFPreferencesCurrentHost);
301 if (isBoolean(value)) {
302 updateOnWiFiOnly = CFBooleanGetValue((CFBooleanRef)value);
303 }
304 CFReleaseNull(value);
305 bool updateInBackground = true;
306 value = CFPreferencesCopyValue(kUpdateBackgroundKey, kSecPrefsDomain, kCFPreferencesAnyUser, kCFPreferencesCurrentHost);
307 if (isBoolean(value)) {
308 updateInBackground = CFBooleanGetValue((CFBooleanRef)value);
309 }
310 CFReleaseNull(value);
311
312 NSURLSessionConfiguration *config = nil;
313 if (updateInBackground) {
314 config = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier: @"com.apple.trustd.networking.background"];
315 config.networkServiceType = NSURLNetworkServiceTypeBackground;
316 config.discretionary = YES;
317 } else {
318 config = [NSURLSessionConfiguration ephemeralSessionConfiguration]; // no cookies or data storage
319 config.networkServiceType = NSURLNetworkServiceTypeDefault;
320 config.discretionary = NO;
321 }
322
323 config.HTTPAdditionalHeaders = @{ @"User-Agent" : @"com.apple.trustd/2.0",
324 @"Accept" : @"*/*",
325 @"Accept-Encoding" : @"gzip,deflate,br"};
326
327 config.TLSMinimumSupportedProtocol = kTLSProtocol12;
328 config.TLSMaximumSupportedProtocol = kTLSProtocol13;
329
330 config._requiresPowerPluggedIn = YES;
331
332 config.allowsCellularAccess = (!updateOnWiFiOnly) ? YES : NO;
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 _backgroundSession = [NSURLSession sessionWithConfiguration:config delegate:delegate delegateQueue:queue];
353 }
354
355 - (BOOL) scheduleUpdateFromServer:(NSString *)server forVersion:(NSUInteger)version withQueue:(dispatch_queue_t)updateQueue {
356 if (!server) {
357 secnotice("validupdate", "invalid update request");
358 return NO;
359 }
360
361 if (!updateQueue) {
362 secnotice("validupdate", "missing update queue, skipping update");
363 return NO;
364 }
365
366 /* nsurlsessiond waits for unlock to finish launching, so we can't block trust evaluations
367 * on scheduling this background task. Also, we want to wait a sufficient amount of time
368 * after system boot before trying to initiate network activity, to avoid the possibility
369 * of a performance regression in the boot path. */
370 dispatch_async(updateQueue, ^{
371 /* Take a transaction while we work */
372 os_transaction_t transaction = os_transaction_create("com.apple.trustd.valid.scheduleUpdate");
373 CFAbsoluteTime now = CFAbsoluteTimeGetCurrent();
374 if (self.updateScheduled != 0.0) {
375 secdebug("validupdate", "update in progress (scheduled %f)", (double)self.updateScheduled);
376 return;
377 } else {
378 uint64_t uptime = systemUptimeInSeconds();
379 const uint64_t minUptime = 180;
380 if (uptime < minUptime) {
381 gNextUpdate = now + (minUptime - uptime);
382 gUpdateStarted = 0;
383 secnotice("validupdate", "postponing update until %f", gNextUpdate);
384 } else {
385 self.updateScheduled = now;
386 secnotice("validupdate", "scheduling update at %f", (double)self.updateScheduled);
387 }
388 }
389
390 NSURL *validUrl = [NSURL URLWithString:[NSString stringWithFormat:@"https://%@/g3/v%ld",
391 server, (unsigned long)version]];
392 if (!validUrl) {
393 secnotice("validupdate", "invalid update url");
394 return;
395 }
396
397 /* clear all old sessions and cleanup disk (for previous download tasks) */
398 static dispatch_once_t onceToken;
399 dispatch_once(&onceToken, ^{
400 @autoreleasepool {
401 [NSURLSession _obliterateAllBackgroundSessionsWithCompletionHandler:^{
402 secnotice("validupdate", "removing all old sessions for trustd");
403 }];
404 }
405 });
406
407 if (!self.backgroundSession) {
408 [self createSession:updateQueue forServer:server];
409 } else {
410 ValidDelegate *delegate = (ValidDelegate *)[self.backgroundSession delegate];
411 delegate.currentUpdateServer = [server copy];
412 }
413
414 /* POWER LOG EVENT: scheduling our background download session now */
415 SecPLLogRegisteredEvent(@"ValidUpdateEvent", @{
416 @"timestamp" : @([[NSDate date] timeIntervalSince1970]),
417 @"event" : @"downloadScheduled",
418 @"version" : @(version)
419 });
420
421 NSURLSessionDataTask *dataTask = [self.backgroundSession dataTaskWithURL:validUrl];
422 dataTask.taskDescription = [NSString stringWithFormat:@"%lu",(unsigned long)version];
423 [dataTask resume];
424 secnotice("validupdate", "scheduled background data task %@ at %f", dataTask, CFAbsoluteTimeGetCurrent());
425 (void) transaction; // dead store
426 transaction = nil;
427 });
428
429 return YES;
430 }
431 @end
432
433 bool SecValidUpdateRequest(dispatch_queue_t queue, CFStringRef server, CFIndex version) {
434 static dispatch_once_t onceToken;
435 dispatch_once(&onceToken, ^{
436 @autoreleasepool {
437 request = [[ValidUpdateRequest alloc] init];
438 }
439 });
440 @autoreleasepool {
441 return [request scheduleUpdateFromServer:(__bridge NSString*)server forVersion:version withQueue:queue];
442 }
443 }
444
445 /* MARK: - */
446 /* MARK: OCSP Fetch Networking */
447 #define OCSP_REQUEST_THRESHOLD 10
448
449 @interface OCSPFetchDelegate : TrustURLSessionDelegate
450 @end
451
452 @implementation OCSPFetchDelegate
453 - (BOOL)fetchNext:(NSURLSession *)session {
454 SecORVCRef orvc = (SecORVCRef)self.context;
455 TrustAnalyticsBuilder *analytics = SecPathBuilderGetAnalyticsData(orvc->builder);
456
457 BOOL result = true;
458 if ((result = [super fetchNext:session])) {
459 /* no fetch scheduled */
460 orvc->done = true;
461 } else {
462 if (self.URIix > 0) {
463 orvc->responder = (__bridge CFURLRef)self.URIs[self.URIix - 1];
464 } else {
465 orvc->responder = (__bridge CFURLRef)self.URIs[0];
466 }
467 if (analytics) {
468 analytics->ocsp_fetches++;
469 }
470 }
471 return result;
472 }
473
474 - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
475 /* call the superclass's method to set expiration */
476 [super URLSession:session task:task didCompleteWithError:error];
477
478 __block SecORVCRef orvc = (SecORVCRef)self.context;
479 if (!orvc || !orvc->builder) {
480 /* We already returned to the PathBuilder state machine. */
481 return;
482 }
483
484 TrustAnalyticsBuilder *analytics = SecPathBuilderGetAnalyticsData(orvc->builder);
485 if (error) {
486 /* Log the error */
487 secnotice("rvc", "Failed to download ocsp response %@, with error %@", task.originalRequest.URL, error);
488 if (analytics) {
489 analytics->ocsp_fetch_failed++;
490 }
491 } else {
492 SecOCSPResponseRef ocspResponse = SecOCSPResponseCreate((__bridge CFDataRef)self.response);
493 if (ocspResponse) {
494 SecORVCConsumeOCSPResponse(orvc, ocspResponse, self.expiration, true, false);
495 if (analytics && !orvc->done) {
496 /* We got an OCSP response that didn't pass validation */
497 analytics-> ocsp_validation_failed = true;
498 }
499 } else if (analytics) {
500 /* We got something that wasn't an OCSP response (e.g. captive portal) --
501 * we consider that a fetch failure */
502 analytics->ocsp_fetch_failed++;
503 }
504 }
505
506 /* If we didn't get a valid OCSP response, try the next URI */
507 if (!orvc->done) {
508 (void)[self fetchNext:session];
509 }
510
511 /* We got a valid OCSP response or couldn't schedule any more fetches.
512 * Close the session, update the PVCs, decrement the async count, and callback if we're all done. */
513 if (orvc->done) {
514 secdebug("rvc", "builder %p, done with OCSP fetches for cert: %ld", orvc->builder, orvc->certIX);
515 self.context = nil;
516 [session invalidateAndCancel];
517 SecORVCUpdatePVC(orvc);
518 if (0 == SecPathBuilderDecrementAsyncJobCount(orvc->builder)) {
519 /* We're the last async job to finish, jump back into the state machine */
520 secdebug("rvc", "builder %p, done with all async jobs", orvc->builder);
521 dispatch_async(SecPathBuilderGetQueue(orvc->builder), ^{
522 SecPathBuilderStep(orvc->builder);
523 });
524 }
525 }
526 }
527
528 - (NSURLRequest *)createNextRequest:(NSURL *)uri {
529 SecORVCRef orvc = (SecORVCRef)self.context;
530 CFDataRef ocspDER = CFRetainSafe(SecOCSPRequestGetDER(orvc->ocspRequest));
531 NSData *nsOcspDER = CFBridgingRelease(ocspDER);
532 NSString *ocspBase64 = [nsOcspDER base64EncodedStringWithOptions:0];
533
534 /* Ensure that we percent-encode specific characters in the base64 path
535 which are defined as delimiters in RFC 3986 [2.2].
536 */
537 static NSMutableCharacterSet *allowedSet = nil;
538 static dispatch_once_t onceToken;
539 dispatch_once(&onceToken, ^{
540 allowedSet = [[NSCharacterSet URLPathAllowedCharacterSet] mutableCopy];
541 [allowedSet removeCharactersInString:@":/?#[]@!$&'()*+,;="];
542 });
543 NSString *escapedRequest = [ocspBase64 stringByAddingPercentEncodingWithAllowedCharacters:allowedSet];
544 NSURLRequest *request = nil;
545
546 /* Interesting tidbit from rfc5019
547 When sending requests that are less than or equal to 255 bytes in
548 total (after encoding) including the scheme and delimiters (http://),
549 server name and base64-encoded OCSPRequest structure, clients MUST
550 use the GET method (to enable OCSP response caching). OCSP requests
551 larger than 255 bytes SHOULD be submitted using the POST method.
552 */
553 if (([[uri absoluteString] length] + 1 + [escapedRequest length]) < 256) {
554 /* Use a GET */
555 NSString *requestString = [NSString stringWithFormat:@"%@/%@", [uri absoluteString], escapedRequest];
556 NSURL *requestURL = [NSURL URLWithString:requestString];
557 request = [NSURLRequest requestWithURL:requestURL];
558 } else {
559 /* Use a POST */
560 NSMutableURLRequest *mutableRequest = [NSMutableURLRequest requestWithURL:uri];
561 mutableRequest.HTTPMethod = @"POST";
562 mutableRequest.HTTPBody = nsOcspDER;
563 request = mutableRequest;
564 }
565
566 return request;
567 }
568
569 - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didFinishCollectingMetrics:(NSURLSessionTaskMetrics *)taskMetrics {
570 secdebug("rvc", "got metrics with task interval %f", taskMetrics.taskInterval.duration);
571 SecORVCRef orvc = (SecORVCRef)self.context;
572 if (orvc && orvc->builder) {
573 TrustAnalyticsBuilder *analytics = SecPathBuilderGetAnalyticsData(orvc->builder);
574 if (analytics) {
575 analytics->ocsp_fetch_time += (uint64_t)(taskMetrics.taskInterval.duration * NSEC_PER_SEC);
576 }
577 }
578 }
579 @end
580
581 bool SecORVCBeginFetches(SecORVCRef orvc, SecCertificateRef cert) {
582 @autoreleasepool {
583 CFArrayRef ocspResponders = CFRetainSafe(SecCertificateGetOCSPResponders(cert));
584 NSArray *nsResponders = CFBridgingRelease(ocspResponders);
585
586 NSInteger count = [nsResponders count];
587 if (count > OCSP_REQUEST_THRESHOLD) {
588 secnotice("rvc", "too may OCSP responder entries (%ld)", (long)count);
589 orvc->done = true;
590 return true;
591 }
592
593 NSURLSessionConfiguration *config = [NSURLSessionConfiguration ephemeralSessionConfiguration];
594 config.timeoutIntervalForResource = TrustURLSessionGetResourceTimeout();
595 config.HTTPAdditionalHeaders = @{@"User-Agent" : @"com.apple.trustd/2.0"};
596
597 NSData *auditToken = CFBridgingRelease(SecPathBuilderCopyClientAuditToken(orvc->builder));
598 if (auditToken) {
599 config._sourceApplicationAuditTokenData = auditToken;
600 }
601
602 OCSPFetchDelegate *delegate = [[OCSPFetchDelegate alloc] init];
603 delegate.context = orvc;
604 delegate.URIs = nsResponders;
605 delegate.URIix = 0;
606
607 NSOperationQueue *queue = [[NSOperationQueue alloc] init];
608
609 NSURLSession *session = [NSURLSession sessionWithConfiguration:config delegate:delegate delegateQueue:queue];
610 secdebug("rvc", "created URLSession for %@", cert);
611
612 bool result = false;
613 if ((result = [delegate fetchNext:session])) {
614 /* no fetch scheduled, close the session */
615 [session invalidateAndCancel];
616 }
617 return result;
618 }
619 }