]> git.saurik.com Git - apple/security.git/blob - OSX/sec/securityd/SecRevocationNetworking.m
Security-58286.60.28.tar.gz
[apple/security.git] / OSX / sec / securityd / SecRevocationNetworking.m
1 /*
2 * Copyright (c) 2017 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 <os/transaction_private.h>
32 #include "utilities/debugging.h"
33 #include "utilities/SecCFWrappers.h"
34 #include "utilities/SecPLWrappers.h"
35 #include "utilities/SecFileLocations.h"
36
37 #include "SecRevocationDb.h"
38 #import "SecTrustLoggingServer.h"
39
40 #import "SecRevocationNetworking.h"
41
42 #define kSecRevocationBasePath "/Library/Keychains/crls"
43
44 static CFStringRef kSecPrefsDomain = CFSTR("com.apple.security");
45 static CFStringRef kUpdateWiFiOnlyKey = CFSTR("ValidUpdateWiFiOnly");
46 static CFStringRef kUpdateBackgroundKey = CFSTR("ValidUpdateBackground");
47
48 extern CFAbsoluteTime gUpdateStarted;
49 extern CFAbsoluteTime gNextUpdate;
50
51 static int checkBasePath(const char *basePath) {
52 return mkpath_np((char*)basePath, 0755);
53 }
54
55 static uint64_t systemUptimeInSeconds() {
56 struct timeval boottime;
57 size_t tv_size = sizeof(boottime);
58 time_t now, uptime = 0;
59 int mib[2];
60 mib[0] = CTL_KERN;
61 mib[1] = KERN_BOOTTIME;
62 (void) time(&now);
63 if (sysctl(mib, 2, &boottime, &tv_size, NULL, 0) != -1 &&
64 boottime.tv_sec != 0) {
65 uptime = now - boottime.tv_sec;
66 }
67 return (uint64_t)uptime;
68 }
69
70 typedef void (^CompletionHandler)(void);
71
72 @interface ValidDelegate : NSObject <NSURLSessionDelegate, NSURLSessionTaskDelegate, NSURLSessionDataDelegate>
73 @property CompletionHandler handler;
74 @property dispatch_queue_t revDbUpdateQueue;
75 @property os_transaction_t transaction;
76 @property NSString *currentUpdateServer;
77 @property NSFileHandle *currentUpdateFile;
78 @property NSURL *currentUpdateFileURL;
79 @property BOOL finishedDownloading;
80 @end
81
82 @implementation ValidDelegate
83
84 - (void)reschedule {
85 /* make sure we release any os transaction, if we went active */
86 if (self->_transaction) {
87 //os_release(self->_transaction); // ARC does this for us and won't let us call release
88 self->_transaction = NULL;
89 }
90 /* POWER LOG EVENT: operation canceled */
91 SecPLLogRegisteredEvent(@"ValidUpdateEvent", @{
92 @"timestamp" : @([[NSDate date] timeIntervalSince1970]),
93 @"event" : (self->_finishedDownloading) ? @"updateCanceled" : @"downloadCanceled"
94 });
95 secnotice("validupdate", "%s canceled at %f",
96 (self->_finishedDownloading) ? "update" : "download",
97 (double)CFAbsoluteTimeGetCurrent());
98
99 self->_handler();
100 SecRevocationDbComputeAndSetNextUpdateTime();
101 }
102
103 - (void)updateDb:(NSUInteger)version {
104 __block NSURL *updateFileURL = self->_currentUpdateFileURL;
105 __block NSString *updateServer = self->_currentUpdateServer;
106 __block NSFileHandle *updateFile = self->_currentUpdateFile;
107 if (!updateFileURL || !updateFile) {
108 [self reschedule];
109 return;
110 }
111
112 dispatch_async(_revDbUpdateQueue, ^{
113 /* POWER LOG EVENT: background update started */
114 SecPLLogRegisteredEvent(@"ValidUpdateEvent", @{
115 @"timestamp" : @([[NSDate date] timeIntervalSince1970]),
116 @"event" : @"updateStarted"
117 });
118 secnotice("validupdate", "update started at %f", (double)CFAbsoluteTimeGetCurrent());
119
120 CFDataRef updateData = NULL;
121 const char *updateFilePath = [updateFileURL fileSystemRepresentation];
122 int rtn;
123 if ((rtn = readValidFile(updateFilePath, &updateData)) != 0) {
124 secerror("failed to read %@ with error %d", updateFileURL, rtn);
125 TrustdHealthAnalyticsLogErrorCode(TAEventValidUpdate, TAFatalError, rtn);
126 [self reschedule];
127 return;
128 }
129
130 secdebug("validupdate", "verifying and ingesting data from %@", updateFileURL);
131 SecValidUpdateVerifyAndIngest(updateData, (__bridge CFStringRef)updateServer, (0 == version));
132 if ((rtn = munmap((void *)CFDataGetBytePtr(updateData), CFDataGetLength(updateData))) != 0) {
133 secerror("unable to unmap current update %ld bytes at %p (error %d)", CFDataGetLength(updateData), CFDataGetBytePtr(updateData), rtn);
134 }
135 CFReleaseNull(updateData);
136
137 /* We're done with this file */
138 [updateFile closeFile];
139 if (updateFilePath) {
140 (void)remove(updateFilePath);
141 }
142 self->_currentUpdateFile = nil;
143 self->_currentUpdateFileURL = nil;
144 self->_currentUpdateServer = nil;
145
146 /* POWER LOG EVENT: background update finished */
147 SecPLLogRegisteredEvent(@"ValidUpdateEvent", @{
148 @"timestamp" : @([[NSDate date] timeIntervalSince1970]),
149 @"event" : @"updateFinished"
150 });
151
152 /* Update is complete */
153 secnotice("validupdate", "update finished at %f", (double)CFAbsoluteTimeGetCurrent());
154 gUpdateStarted = 0;
155
156 self->_handler();
157 });
158 }
159
160 - (NSInteger)versionFromTask:(NSURLSessionTask *)task {
161 return atol([task.taskDescription cStringUsingEncoding:NSUTF8StringEncoding]);
162 }
163
164 - (void)URLSession:(NSURLSession *)session
165 dataTask:(NSURLSessionDataTask *)dataTask
166 didReceiveResponse:(NSURLResponse *)response
167 completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler {
168 secinfo("validupdate", "Session %@ data task %@ returned response %ld, expecting %lld bytes", session, dataTask,
169 (long)[(NSHTTPURLResponse *)response statusCode],[response expectedContentLength]);
170
171 (void)checkBasePath(kSecRevocationBasePath);
172 CFURLRef updateFileURL = SecCopyURLForFileInRevocationInfoDirectory(CFSTR("update-current"));
173 self->_currentUpdateFileURL = (updateFileURL) ? CFBridgingRelease(updateFileURL) : nil;
174 const char *updateFilePath = [self->_currentUpdateFileURL fileSystemRepresentation];
175 if (!updateFilePath) {
176 secnotice("validupdate", "failed to find revocation info directory. canceling task %@", dataTask);
177 completionHandler(NSURLSessionResponseCancel);
178 [self reschedule];
179 return;
180 }
181
182 /* Clean up any old files from previous tasks. */
183 (void)remove(updateFilePath);
184
185 int fd;
186 off_t off;
187 fd = open(updateFilePath, O_RDWR | O_CREAT | O_TRUNC, 0644);
188 if (fd < 0 || (off = lseek(fd, 0, SEEK_SET)) < 0) {
189 secnotice("validupdate","unable to open %@ (errno %d)", self->_currentUpdateFileURL, errno);
190 }
191 if (fd >= 0) {
192 close(fd);
193 }
194
195 /* POWER LOG EVENT: background download actually started */
196 SecPLLogRegisteredEvent(@"ValidUpdateEvent", @{
197 @"timestamp" : @([[NSDate date] timeIntervalSince1970]),
198 @"event" : @"downloadStarted"
199 });
200 secnotice("validupdate", "download started at %f", (double)CFAbsoluteTimeGetCurrent());
201
202 NSError *error = nil;
203 self->_currentUpdateFile = [NSFileHandle fileHandleForWritingToURL:self->_currentUpdateFileURL error:&error];
204 if (!self->_currentUpdateFile) {
205 secnotice("validupdate", "failed to open %@: %@. canceling task %@", self->_currentUpdateFileURL, error, dataTask);
206 #if ENABLE_TRUSTD_ANALYTICS
207 [[TrustdHealthAnalytics logger] logResultForEvent:TrustdHealthAnalyticsEventValidUpdate hardFailure:NO result:error];
208 #endif // ENABLE_TRUSTD_ANALYTICS
209 completionHandler(NSURLSessionResponseCancel);
210 [self reschedule];
211 return;
212 }
213
214 /* We're about to begin downloading -- go active now so we don't get jetsammed */
215 self->_transaction = os_transaction_create("com.apple.trustd.valid.download");
216 completionHandler(NSURLSessionResponseAllow);
217 }
218
219 - (void)URLSession:(NSURLSession *)session
220 dataTask:(NSURLSessionDataTask *)dataTask
221 didReceiveData:(NSData *)data {
222 secdebug("validupdate", "Session %@ data task %@ returned %lu bytes (%lld bytes so far) out of expected %lld bytes",
223 session, dataTask, (unsigned long)[data length], [dataTask countOfBytesReceived], [dataTask countOfBytesExpectedToReceive]);
224
225 if (!self->_currentUpdateFile) {
226 secnotice("validupdate", "received data, but output file is not open");
227 [dataTask cancel];
228 [self reschedule];
229 return;
230 }
231
232 @try {
233 /* Writing can fail and throw an exception, e.g. if we run out of disk space. */
234 [self->_currentUpdateFile writeData:data];
235 }
236 @catch(NSException *exception) {
237 secnotice("validupdate", "%s", exception.description.UTF8String);
238 TrustdHealthAnalyticsLogErrorCode(TAEventValidUpdate, TARecoverableError, errSecDiskFull);
239 [dataTask cancel];
240 [self reschedule];
241 }
242 }
243
244 - (void)URLSession:(NSURLSession *)session
245 task:(NSURLSessionTask *)task
246 didCompleteWithError:(NSError *)error {
247 /* all finished downloading data -- go inactive */
248 if (self->_transaction) {
249 // os_release(self->_transaction); // ARC does this for us and won't let us call release
250 self->_transaction = NULL;
251 }
252 if (error) {
253 secnotice("validupdate", "Session %@ task %@ failed with error %@", session, task, error);
254 #if ENABLE_TRUSTD_ANALYTICS
255 [[TrustdHealthAnalytics logger] logResultForEvent:TrustdHealthAnalyticsEventValidUpdate hardFailure:NO result:error];
256 #endif // ENABLE_TRUSTD_ANALYTICS
257 [self reschedule];
258 /* close file before we leave */
259 [self->_currentUpdateFile closeFile];
260 self->_currentUpdateFile = nil;
261 self->_currentUpdateServer = nil;
262 self->_currentUpdateFileURL = nil;
263 } else {
264 /* POWER LOG EVENT: background download finished */
265 SecPLLogRegisteredEvent(@"ValidUpdateEvent", @{
266 @"timestamp" : @([[NSDate date] timeIntervalSince1970]),
267 @"event" : @"downloadFinished"
268 });
269 secnotice("validupdate", "download finished at %f", (double)CFAbsoluteTimeGetCurrent());
270 secdebug("validupdate", "Session %@ task %@ succeeded", session, task);
271 self->_finishedDownloading = YES;
272 [self updateDb:[self versionFromTask:task]];
273 }
274 }
275
276 @end
277
278 @interface ValidUpdateRequest : NSObject
279 @property NSTimeInterval updateScheduled;
280 @property NSURLSession *backgroundSession;
281 @end
282
283 static ValidUpdateRequest *request = nil;
284
285 @implementation ValidUpdateRequest
286
287 - (NSURLSessionConfiguration *)validUpdateConfiguration {
288 /* preferences to override defaults */
289 CFTypeRef value = NULL;
290 bool updateOnWiFiOnly = true;
291 value = CFPreferencesCopyValue(kUpdateWiFiOnlyKey, kSecPrefsDomain, kCFPreferencesAnyUser, kCFPreferencesCurrentHost);
292 if (isBoolean(value)) {
293 updateOnWiFiOnly = CFBooleanGetValue((CFBooleanRef)value);
294 }
295 CFReleaseNull(value);
296 bool updateInBackground = true;
297 value = CFPreferencesCopyValue(kUpdateBackgroundKey, kSecPrefsDomain, kCFPreferencesAnyUser, kCFPreferencesCurrentHost);
298 if (isBoolean(value)) {
299 updateInBackground = CFBooleanGetValue((CFBooleanRef)value);
300 }
301 CFReleaseNull(value);
302
303 NSURLSessionConfiguration *config = nil;
304 if (updateInBackground) {
305 config = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier: @"com.apple.trustd.networking.background"];
306 config.networkServiceType = NSURLNetworkServiceTypeBackground;
307 config.discretionary = YES;
308 } else {
309 config = [NSURLSessionConfiguration ephemeralSessionConfiguration]; // no cookies or data storage
310 config.networkServiceType = NSURLNetworkServiceTypeDefault;
311 config.discretionary = NO;
312 }
313
314 config.HTTPAdditionalHeaders = @{ @"User-Agent" : @"com.apple.trustd/2.0",
315 @"Accept" : @"*/*",
316 @"Accept-Encoding" : @"gzip,deflate,br"};
317
318 config.TLSMinimumSupportedProtocol = kTLSProtocol12;
319 config.TLSMaximumSupportedProtocol = kTLSProtocol13;
320
321 config._requiresPowerPluggedIn = YES;
322
323 config.allowsCellularAccess = (!updateOnWiFiOnly) ? YES : NO;
324
325 return config;
326 }
327
328 - (void) createSession:(dispatch_queue_t)updateQueue forServer:(NSString *)updateServer {
329 NSURLSessionConfiguration *config = [self validUpdateConfiguration];
330 ValidDelegate *delegate = [[ValidDelegate alloc] init];
331 delegate.handler = ^(void) {
332 request.updateScheduled = 0.0;
333 secdebug("validupdate", "resetting scheduled time");
334 };
335 delegate.transaction = NULL;
336 delegate.revDbUpdateQueue = updateQueue;
337 delegate.finishedDownloading = NO;
338 delegate.currentUpdateServer = [updateServer copy];
339
340 /* Callbacks should be on a separate NSOperationQueue.
341 We'll then dispatch the work on updateQueue and return from the callback. */
342 NSOperationQueue *queue = [[NSOperationQueue alloc] init];
343 _backgroundSession = [NSURLSession sessionWithConfiguration:config delegate:delegate delegateQueue:queue];
344 }
345
346 - (BOOL) scheduleUpdateFromServer:(NSString *)server forVersion:(NSUInteger)version withQueue:(dispatch_queue_t)updateQueue {
347 if (!server) {
348 secnotice("validupdate", "invalid update request");
349 return NO;
350 }
351
352 if (!updateQueue) {
353 secnotice("validupdate", "missing update queue, skipping update");
354 return NO;
355 }
356
357 /* nsurlsessiond waits for unlock to finish launching, so we can't block trust evaluations
358 * on scheduling this background task. Also, we want to wait a sufficient amount of time
359 * after system boot before trying to initiate network activity, to avoid the possibility
360 * of a performance regression in the boot path. */
361 dispatch_async(updateQueue, ^{
362 CFAbsoluteTime now = CFAbsoluteTimeGetCurrent();
363 if (self.updateScheduled != 0.0) {
364 secdebug("validupdate", "update in progress (scheduled %f)", (double)self.updateScheduled);
365 return;
366 } else {
367 uint64_t uptime = systemUptimeInSeconds();
368 const uint64_t minUptime = 180;
369 if (uptime < minUptime) {
370 gNextUpdate = now + (minUptime - uptime);
371 gUpdateStarted = 0;
372 secnotice("validupdate", "postponing update until %f", gNextUpdate);
373 } else {
374 self.updateScheduled = now;
375 secnotice("validupdate", "scheduling update at %f", (double)self.updateScheduled);
376 }
377 }
378
379 NSURL *validUrl = [NSURL URLWithString:[NSString stringWithFormat:@"https://%@/g3/v%ld",
380 server, (unsigned long)version]];
381 if (!validUrl) {
382 secnotice("validupdate", "invalid update url");
383 return;
384 }
385
386 /* clear all old sessions and cleanup disk (for previous download tasks) */
387 static dispatch_once_t onceToken;
388 dispatch_once(&onceToken, ^{
389 [NSURLSession _obliterateAllBackgroundSessionsWithCompletionHandler:^{
390 secnotice("validupdate", "removing all old sessions for trustd");
391 }];
392 });
393
394 if (!self.backgroundSession) {
395 [self createSession:updateQueue forServer:server];
396 }
397
398 /* POWER LOG EVENT: scheduling our background download session now */
399 SecPLLogRegisteredEvent(@"ValidUpdateEvent", @{
400 @"timestamp" : @([[NSDate date] timeIntervalSince1970]),
401 @"event" : @"downloadScheduled",
402 @"version" : @(version)
403 });
404
405 NSURLSessionDataTask *dataTask = [self.backgroundSession dataTaskWithURL:validUrl];
406 dataTask.taskDescription = [NSString stringWithFormat:@"%lu",(unsigned long)version];
407 [dataTask resume];
408 secnotice("validupdate", "scheduled background data task %@ at %f", dataTask, CFAbsoluteTimeGetCurrent());
409 });
410
411 return YES;
412 }
413 @end
414
415 bool SecValidUpdateRequest(dispatch_queue_t queue, CFStringRef server, CFIndex version) {
416 static dispatch_once_t onceToken;
417 dispatch_once(&onceToken, ^{
418 request = [[ValidUpdateRequest alloc] init];
419 });
420 return [request scheduleUpdateFromServer:(__bridge NSString*)server forVersion:version withQueue:queue];
421 }