]> git.saurik.com Git - apple/security.git/blob - OSX/sec/securityd/SecRevocationNetworking.m
Security-58286.251.4.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 config._requiresPowerPluggedIn = YES;
318 config.allowsCellularAccess = (!updateOnWiFiOnly) ? YES : NO;
319 } else {
320 config = [NSURLSessionConfiguration ephemeralSessionConfiguration]; // no cookies or data storage
321 config.networkServiceType = NSURLNetworkServiceTypeDefault;
322 config.discretionary = NO;
323 }
324
325 config.HTTPAdditionalHeaders = @{ @"User-Agent" : @"com.apple.trustd/2.0",
326 @"Accept" : @"*/*",
327 @"Accept-Encoding" : @"gzip,deflate,br"};
328
329 config.TLSMinimumSupportedProtocol = kTLSProtocol12;
330 config.TLSMaximumSupportedProtocol = kTLSProtocol13;
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 _backgroundSession = [NSURLSession sessionWithConfiguration:config delegate:delegate delegateQueue:queue];
351 }
352
353 - (BOOL) scheduleUpdateFromServer:(NSString *)server forVersion:(NSUInteger)version withQueue:(dispatch_queue_t)updateQueue {
354 if (!server) {
355 secnotice("validupdate", "invalid update request");
356 return NO;
357 }
358
359 if (!updateQueue) {
360 secnotice("validupdate", "missing update queue, skipping update");
361 return NO;
362 }
363
364 /* nsurlsessiond waits for unlock to finish launching, so we can't block trust evaluations
365 * on scheduling this background task. Also, we want to wait a sufficient amount of time
366 * after system boot before trying to initiate network activity, to avoid the possibility
367 * of a performance regression in the boot path. */
368 dispatch_async(updateQueue, ^{
369 /* Take a transaction while we work */
370 os_transaction_t transaction = os_transaction_create("com.apple.trustd.valid.scheduleUpdate");
371 CFAbsoluteTime now = CFAbsoluteTimeGetCurrent();
372 if (self.updateScheduled != 0.0) {
373 secdebug("validupdate", "update in progress (scheduled %f)", (double)self.updateScheduled);
374 return;
375 } else {
376 uint64_t uptime = systemUptimeInSeconds();
377 const uint64_t minUptime = 180;
378 if (uptime < minUptime) {
379 gNextUpdate = now + (minUptime - uptime);
380 gUpdateStarted = 0;
381 secnotice("validupdate", "postponing update until %f", gNextUpdate);
382 } else {
383 self.updateScheduled = now;
384 secnotice("validupdate", "scheduling update at %f", (double)self.updateScheduled);
385 }
386 }
387
388 NSURL *validUrl = [NSURL URLWithString:[NSString stringWithFormat:@"https://%@/g3/v%ld",
389 server, (unsigned long)version]];
390 if (!validUrl) {
391 secnotice("validupdate", "invalid update url");
392 return;
393 }
394
395 /* clear all old sessions and cleanup disk (for previous download tasks) */
396 static dispatch_once_t onceToken;
397 dispatch_once(&onceToken, ^{
398 @autoreleasepool {
399 [NSURLSession _obliterateAllBackgroundSessionsWithCompletionHandler:^{
400 secnotice("validupdate", "removing all old sessions for trustd");
401 }];
402 }
403 });
404
405 if (!self.backgroundSession) {
406 [self createSession:updateQueue forServer:server];
407 } else {
408 ValidDelegate *delegate = (ValidDelegate *)[self.backgroundSession delegate];
409 delegate.currentUpdateServer = [server copy];
410 }
411
412 /* POWER LOG EVENT: scheduling our background download session now */
413 SecPLLogRegisteredEvent(@"ValidUpdateEvent", @{
414 @"timestamp" : @([[NSDate date] timeIntervalSince1970]),
415 @"event" : @"downloadScheduled",
416 @"version" : @(version)
417 });
418
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;
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 }