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