]> git.saurik.com Git - apple/security.git/blob - OSX/sec/securityd/SecRevocationDb.c
Security-57740.60.18.tar.gz
[apple/security.git] / OSX / sec / securityd / SecRevocationDb.c
1 /*
2 * Copyright (c) 2016 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 /*
26 * SecRevocationDb.c
27 */
28
29 #include <securityd/SecRevocationDb.h>
30 #include <securityd/asynchttp.h>
31 #include <Security/SecCertificateInternal.h>
32 #include <Security/SecCMS.h>
33 #include <Security/SecFramework.h>
34 #include <Security/SecInternal.h>
35 #include <AssertMacros.h>
36 #include <stdlib.h>
37 #include <limits.h>
38 #include <string.h>
39 #include <fcntl.h>
40 #include <sys/stat.h>
41 #include <errno.h>
42 #include <dispatch/dispatch.h>
43 #include <asl.h>
44 #include "utilities/debugging.h"
45 #include "utilities/sqlutils.h"
46 #include "utilities/SecAppleAnchorPriv.h"
47 #include "utilities/iOSforOSX.h"
48 #include <utilities/SecCFError.h>
49 #include <utilities/SecCFRelease.h>
50 #include <utilities/SecCFWrappers.h>
51 #include <utilities/SecDb.h>
52 #include <utilities/SecFileLocations.h>
53 #include <sqlite3.h>
54 #include <zlib.h>
55 #include <malloc/malloc.h>
56 #include <xpc/activity.h>
57 #include <xpc/private.h>
58
59 #include <CFNetwork/CFHTTPMessage.h>
60 #include <CoreFoundation/CFURL.h>
61 #include <CoreFoundation/CFUtilities.h>
62
63
64 static CFStringRef kAcceptEncoding = CFSTR("Accept-Encoding");
65 static CFStringRef kAppEncoding = CFSTR("deflate");
66 static CFStringRef kUserAgent = CFSTR("User-Agent");
67 static CFStringRef kAppUserAgent = CFSTR("com.apple.trustd/1.0");
68 static CFStringRef kValidUpdateServer = CFSTR("valid.apple.com");
69
70 static CFStringRef kSecPrefsDomain = CFSTR("com.apple.security");
71 static CFStringRef kUpdateServerKey = CFSTR("ValidUpdateServer");
72 static CFStringRef kUpdateEnabledKey = CFSTR("ValidUpdateEnabled");
73 static CFStringRef kUpdateIntervalKey = CFSTR("ValidUpdateInterval");
74 static CFStringRef kUpdateWiFiOnlyKey = CFSTR("ValidUpdateWiFiOnly");
75
76 typedef CF_OPTIONS(CFOptionFlags, SecValidInfoFlags) {
77 kSecValidInfoComplete = 1u << 0,
78 kSecValidInfoCheckOCSP = 1u << 1,
79 kSecValidInfoKnownOnly = 1u << 2,
80 kSecValidInfoRequireCT = 1u << 3,
81 kSecValidInfoAllowlist = 1u << 4
82 };
83
84 /* minimum initial interval after process startup */
85 #define kSecMinUpdateInterval (60.0 * 5)
86
87 /* second and subsequent intervals */
88 #define kSecStdUpdateInterval (60.0 * 60)
89
90 /* maximum allowed interval */
91 #define kSecMaxUpdateInterval (60.0 * 60 * 24 * 7)
92
93 /* background download timeout */
94 #define kSecMaxDownloadSeconds (60.0 * 10)
95
96 #define kSecRevocationBasePath "/Library/Keychains/crls"
97 #define kSecRevocationDbFileName "valid.sqlite3"
98
99 bool SecRevocationDbVerifyUpdate(CFDictionaryRef update);
100 CFIndex SecRevocationDbIngestUpdate(CFDictionaryRef update);
101 void SecRevocationDbApplyUpdate(CFDictionaryRef update, CFIndex version);
102 CFAbsoluteTime SecRevocationDbComputeNextUpdateTime(CFDictionaryRef update);
103 void SecRevocationDbSetSchemaVersion(CFIndex dbversion);
104 void SecRevocationDbSetNextUpdateTime(CFAbsoluteTime nextUpdate);
105 CFAbsoluteTime SecRevocationDbGetNextUpdateTime(void);
106 dispatch_queue_t SecRevocationDbGetUpdateQueue(void);
107 void SecRevocationDbRemoveAllEntries(void);
108
109
110 static CFDataRef copyInflatedData(CFDataRef data) {
111 if (!data) {
112 return NULL;
113 }
114 z_stream zs;
115 memset(&zs, 0, sizeof(zs));
116 /* 32 is a magic value which enables automatic header detection
117 of gzip or zlib compressed data. */
118 if (inflateInit2(&zs, 32+MAX_WBITS) != Z_OK) {
119 return NULL;
120 }
121 zs.next_in = (UInt8 *)(CFDataGetBytePtr(data));
122 zs.avail_in = (uInt)CFDataGetLength(data);
123
124 CFMutableDataRef outData = CFDataCreateMutable(NULL, 0);
125 if (!outData) {
126 return NULL;
127 }
128 CFIndex buf_sz = malloc_good_size(zs.avail_in ? zs.avail_in : 1024 * 4);
129 unsigned char *buf = malloc(buf_sz);
130 int rc;
131 do {
132 zs.next_out = (Bytef*)buf;
133 zs.avail_out = (uInt)buf_sz;
134 rc = inflate(&zs, 0);
135 CFIndex outLen = CFDataGetLength(outData);
136 if (outLen < (CFIndex)zs.total_out) {
137 CFDataAppendBytes(outData, (const UInt8*)buf, (CFIndex)zs.total_out - outLen);
138 }
139 } while (rc == Z_OK);
140
141 inflateEnd(&zs);
142
143 if (buf) {
144 free(buf);
145 }
146 if (rc != Z_STREAM_END) {
147 CFReleaseSafe(outData);
148 return NULL;
149 }
150 return (CFDataRef)outData;
151 }
152
153 static CFDataRef copyDeflatedData(CFDataRef data) {
154 if (!data) {
155 return NULL;
156 }
157 z_stream zs;
158 memset(&zs, 0, sizeof(zs));
159 if (deflateInit(&zs, Z_BEST_COMPRESSION) != Z_OK) {
160 return NULL;
161 }
162 zs.next_in = (UInt8 *)(CFDataGetBytePtr(data));
163 zs.avail_in = (uInt)CFDataGetLength(data);
164
165 CFMutableDataRef outData = CFDataCreateMutable(NULL, 0);
166 if (!outData) {
167 return NULL;
168 }
169 CFIndex buf_sz = malloc_good_size(zs.avail_in ? zs.avail_in : 1024 * 4);
170 unsigned char *buf = malloc(buf_sz);
171 int rc = Z_BUF_ERROR;
172 do {
173 zs.next_out = (Bytef*)buf;
174 zs.avail_out = (uInt)buf_sz;
175 rc = deflate(&zs, Z_FINISH);
176
177 if (rc == Z_OK || rc == Z_STREAM_END) {
178 CFIndex buf_used = buf_sz - zs.avail_out;
179 CFDataAppendBytes(outData, (const UInt8*)buf, buf_used);
180 }
181 else if (rc == Z_BUF_ERROR) {
182 free(buf);
183 buf_sz = malloc_good_size(buf_sz * 2);
184 buf = malloc(buf_sz);
185 if (buf) {
186 rc = Z_OK; /* try again with larger buffer */
187 }
188 }
189 } while (rc == Z_OK && zs.avail_in);
190
191 deflateEnd(&zs);
192
193 if (buf) {
194 free(buf);
195 }
196 if (rc != Z_STREAM_END) {
197 CFReleaseSafe(outData);
198 return NULL;
199 }
200 return (CFDataRef)outData;
201 }
202
203 static uint32_t calculateCrc32(CFDataRef data) {
204 if (!data) { return 0; }
205 uint32_t crc = (uint32_t)crc32(0L, Z_NULL, 0);
206 uint32_t len = (uint32_t)CFDataGetLength(data);
207 const unsigned char *bytes = CFDataGetBytePtr(data);
208 return (uint32_t)crc32(crc, bytes, len);
209 }
210
211 static int checkBasePath(const char *basePath) {
212 return mkpath_np((char*)basePath, 0755);
213 }
214
215 static int writeFile(const char *fileName,
216 const unsigned char *bytes, // compressed data, if crc != 0
217 size_t numBytes, // length of content to write
218 uint32_t crc, // crc32 over uncompressed content
219 uint32_t length) { // uncompressed content length
220 int rtn, fd;
221 off_t off;
222 size_t numToWrite=numBytes;
223 const unsigned char *p=bytes;
224
225 fd = open(fileName, O_RDWR | O_CREAT | O_TRUNC, 0644);
226 if(fd < 0) { return errno; }
227 off = lseek(fd, 0, SEEK_SET);
228 if(off < 0) { return errno; }
229 if(crc) {
230 /* add gzip header per RFC1952 2.2 */
231 uint8_t hdr[10] = { 31, 139, 8, 0, 0, 0, 0, 0, 2, 3 };
232 write(fd, hdr, sizeof(hdr));
233 /* skip 2-byte stream header and 4-byte trailing CRC */
234 if (numToWrite > 6) {
235 numToWrite -= 6;
236 p += 2;
237 }
238 }
239 off = write(fd, p, numToWrite);
240 if((size_t)off != numToWrite) {
241 rtn = EIO;
242 } else {
243 rtn = 0;
244 }
245 if(crc) {
246 /* add gzip trailer per RFC1952 2.2 */
247 /* note: gzip seems to want these values in host byte order. */
248 write(fd, &crc, sizeof(crc));
249 write(fd, &length, sizeof(length));
250 }
251 close(fd);
252 return rtn;
253 }
254
255 static int readFile(const char *fileName,
256 CFDataRef *bytes) { // allocated and returned
257 int rtn, fd;
258 char *buf;
259 struct stat sb;
260 size_t size;
261 ssize_t rrc;
262
263 *bytes = NULL;
264 fd = open(fileName, O_RDONLY);
265 if(fd < 0) { return errno; }
266 rtn = fstat(fd, &sb);
267 if(rtn) { goto errOut; }
268 if (sb.st_size > (off_t) ((UINT32_MAX >> 1)-1)) {
269 rtn = EFBIG;
270 goto errOut;
271 }
272 size = (size_t)sb.st_size;
273
274 *bytes = (CFDataRef)CFDataCreateMutable(NULL, (CFIndex)size);
275 if(!*bytes) {
276 rtn = ENOMEM;
277 goto errOut;
278 }
279
280 CFDataSetLength((CFMutableDataRef)*bytes, (CFIndex)size);
281 buf = (char*)CFDataGetBytePtr(*bytes);
282 rrc = read(fd, buf, size);
283 if(rrc != (ssize_t) size) {
284 rtn = EIO;
285 }
286 else {
287 rtn = 0;
288 }
289
290 errOut:
291 close(fd);
292 if(rtn) {
293 CFReleaseNull(*bytes);
294 }
295 return rtn;
296 }
297
298 static bool isDbOwner() {
299 #if TARGET_OS_EMBEDDED
300 if (getuid() == 64) // _securityd
301 #else
302 if (getuid() == 0)
303 #endif
304 {
305 return true;
306 }
307 return false;
308 }
309
310
311 // MARK: -
312 // MARK: SecValidUpdateRequest
313
314 /* ======================================================================
315 SecValidUpdateRequest
316 ======================================================================*/
317
318 static CFAbsoluteTime gUpdateRequestScheduled = 0.0;
319 static CFAbsoluteTime gNextUpdate = 0.0;
320 static CFIndex gUpdateInterval = 0;
321 static CFIndex gLastVersion = 0;
322
323 typedef struct SecValidUpdateRequest *SecValidUpdateRequestRef;
324 struct SecValidUpdateRequest {
325 asynchttp_t http; /* Must be first field. */
326 CFStringRef server; /* Server name. (e.g. "valid.apple.com") */
327 CFIndex version; /* Our current version. */
328 xpc_object_t criteria; /* Constraints dictionary for request. */
329 };
330
331 static void SecValidUpdateRequestRelease(SecValidUpdateRequestRef request) {
332 if (!request) {
333 return;
334 }
335 CFReleaseSafe(request->server);
336 asynchttp_free(&request->http);
337 if (request->criteria) {
338 xpc_release(request->criteria);
339 }
340 free(request);
341 }
342
343 static void SecValidUpdateRequestIssue(SecValidUpdateRequestRef request) {
344 // issue the async http request now
345 CFStringRef urlStr = CFStringCreateWithFormat(kCFAllocatorDefault, NULL,
346 CFSTR("https://%@/get/v%ld"),
347 request->server, (long)request->version);
348
349 CFURLRef url = (urlStr) ? CFURLCreateWithString(kCFAllocatorDefault, urlStr, NULL) : NULL;
350 CFReleaseSafe(urlStr);
351 if (!url) {
352 secnotice("validupdate", "invalid update url");
353 SecValidUpdateRequestRelease(request);
354 return;
355 }
356 CFHTTPMessageRef msg = CFHTTPMessageCreateRequest(kCFAllocatorDefault,
357 CFSTR("GET"), url, kCFHTTPVersion1_1);
358 CFReleaseSafe(url);
359 if (msg) {
360 secdebug("validupdate", "%@", msg);
361 CFHTTPMessageSetHeaderFieldValue(msg, CFSTR("Accept"), CFSTR("*/*"));
362 CFHTTPMessageSetHeaderFieldValue(msg, kAcceptEncoding, kAppEncoding);
363 CFHTTPMessageSetHeaderFieldValue(msg, kUserAgent, kAppUserAgent);
364 bool done = asynchttp_request(msg, kSecMaxDownloadSeconds*NSEC_PER_SEC, &request->http);
365 CFReleaseSafe(msg);
366 if (done == false) {
367 return;
368 }
369 }
370 secdebug("validupdate", "no request issued");
371 SecValidUpdateRequestRelease(request);
372 }
373
374 static bool SecValidUpdateRequestSchedule(SecValidUpdateRequestRef request) {
375 if (!request || !request->server) {
376 secnotice("validupdate", "invalid update request");
377 SecValidUpdateRequestRelease(request);
378 return false;
379 } else if (gUpdateRequestScheduled != 0.0) {
380 // TBD: may need a separate scheduled activity which can perform a request with
381 // fewer constraints if our request has not been satisfied for a week or so
382 secdebug("validupdate", "update request already scheduled at %f, will not reissue",
383 (double)gUpdateRequestScheduled);
384 SecValidUpdateRequestRelease(request);
385 return true; // request is still in the queue
386 } else {
387 gUpdateRequestScheduled = CFAbsoluteTimeGetCurrent();
388 secdebug("validupdate", "scheduling update at %f", (double)gUpdateRequestScheduled);
389 }
390
391 // determine whether to issue request without waiting for activity criteria to be satisfied
392 bool updateOnWiFiOnly = true;
393 CFTypeRef value = (CFBooleanRef)CFPreferencesCopyValue(kUpdateWiFiOnlyKey, kSecPrefsDomain, kCFPreferencesAnyUser, kCFPreferencesCurrentHost);
394 if (isBoolean(value)) {
395 updateOnWiFiOnly = CFBooleanGetValue((CFBooleanRef)value);
396 }
397 CFReleaseNull(value);
398 if (!updateOnWiFiOnly) {
399 SecValidUpdateRequestIssue(request);
400 gUpdateRequestScheduled = 0.0;
401 return true;
402 }
403
404 xpc_object_t criteria = xpc_dictionary_create(NULL, NULL, 0);
405 xpc_dictionary_set_bool(criteria, XPC_ACTIVITY_REPEATING, false);
406 xpc_dictionary_set_string(criteria, XPC_ACTIVITY_PRIORITY, XPC_ACTIVITY_PRIORITY_MAINTENANCE);
407 // we want to start as soon as possible
408 xpc_dictionary_set_int64(criteria, XPC_ACTIVITY_DELAY, 0);
409 xpc_dictionary_set_int64(criteria, XPC_ACTIVITY_GRACE_PERIOD, 5);
410 // we are downloading data and want to use WiFi instead of cellular
411 xpc_dictionary_set_bool(criteria, XPC_ACTIVITY_REQUIRE_NETWORK_CONNECTIVITY, true);
412 xpc_dictionary_set_bool(criteria, XPC_ACTIVITY_REQUIRE_INEXPENSIVE_NETWORK_CONNECTIVITY, true);
413 xpc_dictionary_set_string(criteria, XPC_ACTIVITY_NETWORK_TRANSFER_DIRECTION, XPC_ACTIVITY_NETWORK_TRANSFER_DIRECTION_DOWNLOAD);
414
415 if (request->criteria) {
416 xpc_release(request->criteria);
417 }
418 request->criteria = criteria;
419
420 xpc_activity_register("com.apple.trustd.validupdate", criteria, ^(xpc_activity_t activity) {
421 xpc_activity_state_t activityState = xpc_activity_get_state(activity);
422 switch (activityState) {
423 case XPC_ACTIVITY_STATE_CHECK_IN: {
424 secdebug("validupdate", "xpc activity state: XPC_ACTIVITY_STATE_CHECK_IN");
425 break;
426 }
427 case XPC_ACTIVITY_STATE_RUN: {
428 secdebug("validupdate", "xpc activity state: XPC_ACTIVITY_STATE_RUN");
429 if (!xpc_activity_set_state(activity, XPC_ACTIVITY_STATE_CONTINUE)) {
430 secnotice("validupdate", "unable to set activity state to XPC_ACTIVITY_STATE_CONTINUE");
431 }
432 // criteria for this activity have been met; issue the network request
433 SecValidUpdateRequestIssue(request);
434 gUpdateRequestScheduled = 0.0;
435 if (!xpc_activity_set_state(activity, XPC_ACTIVITY_STATE_DONE)) {
436 secnotice("validupdate", "unable to set activity state to XPC_ACTIVITY_STATE_DONE");
437 }
438 break;
439 }
440 default: {
441 secdebug("validupdate", "unhandled activity state (%ld)", (long)activityState);
442 break;
443 }
444 }
445 });
446
447 return true;
448 }
449
450 static bool SecValidUpdateRequestConsumeReply(CF_CONSUMED CFDataRef data, CFIndex version, bool save) {
451 if (!data) {
452 secnotice("validupdate", "invalid data");
453 return false;
454 }
455 CFIndex length = CFDataGetLength(data);
456 secdebug("validupdate", "data received: %ld bytes", (long)length);
457
458 char *curPathBuf = NULL;
459 if (save) {
460 checkBasePath(kSecRevocationBasePath);
461 asprintf(&curPathBuf, "%s/%s.plist.gz", kSecRevocationBasePath, "update-current");
462 }
463 // expand compressed data
464 CFDataRef inflatedData = copyInflatedData(data);
465 if (inflatedData) {
466 CFIndex cmplength = length;
467 length = CFDataGetLength(inflatedData);
468 if (curPathBuf) {
469 uint32_t crc = calculateCrc32(inflatedData);
470 writeFile(curPathBuf, CFDataGetBytePtr(data), cmplength, crc, (uint32_t)length);
471 }
472 CFReleaseSafe(data);
473 data = inflatedData;
474 }
475 secdebug("validupdate", "data expanded: %ld bytes", (long)length);
476
477 // mmap the expanded data while property list object is created
478 CFPropertyListRef propertyList = NULL;
479 char *expPathBuf = NULL;
480 asprintf(&expPathBuf, "%s/%s.plist", kSecRevocationBasePath, "update-current");
481 if (expPathBuf) {
482 writeFile(expPathBuf, CFDataGetBytePtr(data), length, 0, (uint32_t)length);
483 CFReleaseNull(data);
484 // no copies of data should exist in memory at this point
485 int fd = open(expPathBuf, O_RDONLY);
486 if (fd < 0) {
487 secerror("unable to open %s (errno %d)", expPathBuf, errno);
488 }
489 else {
490 void *p = mmap(NULL, length, PROT_READ, MAP_PRIVATE, fd, 0);
491 if (!p || p == MAP_FAILED) {
492 secerror("unable to map %s (errno %d)", expPathBuf, errno);
493 }
494 else {
495 data = CFDataCreateWithBytesNoCopy(NULL, (const UInt8 *)p, length, kCFAllocatorNull);
496 if (data) {
497 propertyList = CFPropertyListCreateWithData(kCFAllocatorDefault, data,
498 kCFPropertyListImmutable, NULL, NULL);
499 }
500 int rtn = munmap(p, length);
501 if (rtn != 0) {
502 secerror("unable to unmap %ld bytes at %p (error %d)", (long)length, p, rtn);
503 }
504 }
505 (void)close(fd);
506 }
507 // all done with this file
508 (void)remove(expPathBuf);
509 free(expPathBuf);
510 }
511 CFReleaseSafe(data);
512
513 CFIndex curVersion = version;
514 Boolean fullUpdate = false;
515 if (isDictionary(propertyList)) {
516 if (SecRevocationDbVerifyUpdate((CFDictionaryRef)propertyList)) {
517 CFTypeRef value = (CFBooleanRef)CFDictionaryGetValue((CFDictionaryRef)propertyList, CFSTR("full"));
518 if (isBoolean(value)) {
519 fullUpdate = CFBooleanGetValue((CFBooleanRef)value);
520 }
521 curVersion = SecRevocationDbIngestUpdate((CFDictionaryRef)propertyList);
522 gNextUpdate = SecRevocationDbComputeNextUpdateTime((CFDictionaryRef)propertyList);
523 }
524 } else {
525 secerror("update failed: could not create property list");
526 }
527 CFReleaseSafe(propertyList);
528
529 if (curVersion > version) {
530 secdebug("validupdate", "update received: v%ld", (unsigned long)curVersion);
531 // save this update and make it current
532 char *newPathBuf = NULL;
533 if (fullUpdate) {
534 asprintf(&newPathBuf, "%s/update-full.plist.gz", kSecRevocationBasePath);
535 //%%% glob and remove all "update-v*.plist.gz" files here
536 }
537 else {
538 asprintf(&newPathBuf, "%s/update-v%ld.plist.gz", kSecRevocationBasePath, (unsigned long)curVersion);
539 }
540 if (newPathBuf) {
541 if (curPathBuf) {
542 if (fullUpdate) {
543 // try to save the latest full update
544 (void)rename(curPathBuf, newPathBuf);
545 }
546 else {
547 // try to remove delta updates
548 (void)remove(curPathBuf);
549 }
550 }
551 free(newPathBuf);
552 }
553 gLastVersion = curVersion;
554 }
555 if (curPathBuf) {
556 free(curPathBuf);
557 }
558
559 // remember next update time in case of restart
560 SecRevocationDbSetNextUpdateTime(gNextUpdate);
561
562 return true;
563 }
564
565 static bool SecValidUpdateRequestSatisfiedLocally(SecValidUpdateRequestRef request) {
566 // if we can read the requested data locally, we don't need a network request.
567
568 // note: only need this if we don't have any version and are starting from scratch.
569 // otherwise we don't know what the current version actually is; only the server
570 // can tell us that at any given time, so we have to ask it for any version >0.
571 // we cannot reuse a saved delta without being on the exact version from which
572 // it was generated.
573 // TBD:
574 // - if requested version N is 0, and no 'update-full' in kSecRevocationBasePath,
575 // call a OTATrustUtilities SPI to obtain static 'update-full' asset data.
576
577 CFDataRef data = NULL;
578 char *curPathBuf = NULL;
579 if (0 == request->version) {
580 asprintf(&curPathBuf, "%s/update-full.plist.gz", kSecRevocationBasePath);
581 }
582 else {
583 return false;
584 //asprintf(&curPathBuf, "%s/update-v%ld.plist.gz", kSecRevocationBasePath, (unsigned long)request->version);
585 }
586 if (curPathBuf) {
587 secdebug("validupdate", "will read data from \"%s\"", curPathBuf);
588 int rtn = readFile(curPathBuf, &data);
589 free(curPathBuf);
590 if (rtn) { CFReleaseNull(data); }
591 }
592 if (data) {
593 secdebug("validupdate", "read %ld bytes from file", (long)CFDataGetLength(data));
594 //%%% TBD dispatch this work on the request's queue and return true immediately
595 return SecValidUpdateRequestConsumeReply(data, request->version, false);
596 }
597 return false;
598 }
599
600 static void SecValidUpdateRequestCompleted(asynchttp_t *http, CFTimeInterval maxAge) {
601 // cast depends on http being first field in struct SecValidUpdateRequest.
602 SecValidUpdateRequestRef request = (SecValidUpdateRequestRef)http;
603 if (!request) {
604 secnotice("validupdate", "no request to complete!");
605 return;
606 }
607 CFDataRef data = (request->http.response) ? CFHTTPMessageCopyBody(request->http.response) : NULL;
608 CFIndex version = request->version;
609 SecValidUpdateRequestRelease(request);
610 if (!data) {
611 secdebug("validupdate", "no data received");
612 return;
613 }
614 SecValidUpdateRequestConsumeReply(data, version, true);
615 }
616
617
618 // MARK: -
619 // MARK: SecValidInfoRef
620
621 /* ======================================================================
622 SecValidInfoRef
623 ======================================================================
624 */
625
626 static SecValidInfoRef SecValidInfoCreate(SecValidInfoFormat format,
627 CFOptionFlags flags,
628 CFDataRef certHash,
629 CFDataRef issuerHash) {
630 SecValidInfoRef validInfo;
631 validInfo = (SecValidInfoRef)calloc(1, sizeof(struct __SecValidInfo));
632 if (!validInfo) { return NULL; }
633
634 CFRetainSafe(certHash);
635 CFRetainSafe(issuerHash);
636 validInfo->format = format;
637 validInfo->certHash = certHash;
638 validInfo->issuerHash = issuerHash;
639 validInfo->valid = (flags & kSecValidInfoAllowlist);
640 validInfo->complete = (flags & kSecValidInfoComplete);
641 validInfo->checkOCSP = (flags & kSecValidInfoCheckOCSP);
642 validInfo->knownOnly = (flags & kSecValidInfoKnownOnly);
643 validInfo->requireCT = (flags & kSecValidInfoRequireCT);
644
645 return validInfo;
646 }
647
648 void SecValidInfoRelease(SecValidInfoRef validInfo) {
649 if (validInfo) {
650 CFReleaseSafe(validInfo->certHash);
651 CFReleaseSafe(validInfo->issuerHash);
652 free(validInfo);
653 }
654 }
655
656
657 // MARK: -
658 // MARK: SecRevocationDb
659
660 /* ======================================================================
661 SecRevocationDb
662 ======================================================================
663 */
664
665 /* SecRevocationDbCheckNextUpdate returns true if we dispatched an
666 update request, otherwise false.
667 */
668 bool SecRevocationDbCheckNextUpdate(void) {
669 // are we the db owner instance?
670 if (!isDbOwner()) {
671 return false;
672 }
673 CFTypeRef value = NULL;
674
675 // is it time to check?
676 CFAbsoluteTime now = CFAbsoluteTimeGetCurrent();
677 CFAbsoluteTime minNextUpdate = now + gUpdateInterval;
678 if (0 == gNextUpdate) {
679 // first time we're called, check if we have a saved nextUpdate value
680 gNextUpdate = SecRevocationDbGetNextUpdateTime();
681 // pin to minimum first-time interval, so we don't perturb startup
682 minNextUpdate = now + kSecMinUpdateInterval;
683 if (gNextUpdate < minNextUpdate) {
684 gNextUpdate = minNextUpdate;
685 }
686 // allow pref to override update interval, if it exists
687 CFIndex interval = -1;
688 value = (CFNumberRef)CFPreferencesCopyValue(kUpdateIntervalKey, kSecPrefsDomain, kCFPreferencesAnyUser, kCFPreferencesCurrentHost);
689 if (isNumber(value)) {
690 if (CFNumberGetValue((CFNumberRef)value, kCFNumberCFIndexType, &interval)) {
691 if (interval < kSecMinUpdateInterval) {
692 interval = kSecMinUpdateInterval;
693 } else if (interval > kSecMaxUpdateInterval) {
694 interval = kSecMaxUpdateInterval;
695 }
696 }
697 }
698 CFReleaseNull(value);
699 gUpdateInterval = kSecStdUpdateInterval;
700 if (interval > 0) {
701 gUpdateInterval = interval;
702 }
703 }
704 if (gNextUpdate > now) {
705 return false;
706 }
707 // set minimum next update time here in case we can't get an update
708 gNextUpdate = minNextUpdate;
709
710 // determine which server to query
711 CFStringRef server;
712 value = (CFStringRef)CFPreferencesCopyValue(kUpdateServerKey, kSecPrefsDomain, kCFPreferencesAnyUser, kCFPreferencesCurrentHost);
713 if (isString(value)) {
714 server = (CFStringRef) CFRetain(value);
715 } else {
716 server = (CFStringRef) CFRetain(kValidUpdateServer);
717 }
718 CFReleaseNull(value);
719
720 // determine what version we currently have
721 CFIndex version = SecRevocationDbGetVersion();
722 secdebug("validupdate", "got version %ld from db", (long)version);
723 if (version <= 0) {
724 if (gLastVersion > 0) {
725 secdebug("validupdate", "error getting version; using last good version: %ld", (long)gLastVersion);
726 }
727 version = gLastVersion;
728 }
729
730 // determine whether we need to recreate the database
731 CFIndex db_version = SecRevocationDbGetSchemaVersion();
732 if (db_version == 1) {
733 /* code which created this db failed to update changed flags,
734 so we need to fully rebuild its contents. */
735 SecRevocationDbRemoveAllEntries();
736 version = gLastVersion = 0;
737 }
738
739 // determine whether update fetching is enabled
740 #if (__MAC_OS_X_VERSION_MIN_REQUIRED >= 101300 || __IPHONE_OS_VERSION_MIN_REQUIRED >= 110000)
741 bool updateEnabled = true; // macOS 10.13 or iOS 11.0, not tvOS, not watchOS
742 #else
743 bool updateEnabled = false;
744 #endif
745 value = (CFBooleanRef)CFPreferencesCopyValue(kUpdateEnabledKey, kSecPrefsDomain, kCFPreferencesAnyUser, kCFPreferencesCurrentHost);
746 if (isBoolean(value)) {
747 updateEnabled = CFBooleanGetValue((CFBooleanRef)value);
748 }
749 CFReleaseNull(value);
750
751 // set up a network request
752 SecValidUpdateRequestRef request = (SecValidUpdateRequestRef)calloc(1, sizeof(*request));
753 request->http.queue = SecRevocationDbGetUpdateQueue();
754 request->http.completed = SecValidUpdateRequestCompleted;
755 request->server = server;
756 request->version = version;
757 request->criteria = NULL;
758
759 if (SecValidUpdateRequestSatisfiedLocally(request)) {
760 SecValidUpdateRequestRelease(request);
761 return true;
762 }
763 if (!updateEnabled) {
764 SecValidUpdateRequestRelease(request);
765 return false;
766 }
767 return SecValidUpdateRequestSchedule(request);
768 }
769
770 bool SecRevocationDbVerifyUpdate(CFDictionaryRef update) {
771
772 //%%% TBD: check signature with new SecPolicyRef; rdar://28619456
773 return true;
774 }
775
776 CFAbsoluteTime SecRevocationDbComputeNextUpdateTime(CFDictionaryRef update) {
777 CFIndex interval = 0;
778 // get server-provided interval
779 if (update) {
780 CFTypeRef value = (CFNumberRef)CFDictionaryGetValue(update, CFSTR("check-again"));
781 if (isNumber(value)) {
782 CFNumberGetValue((CFNumberRef)value, kCFNumberCFIndexType, &interval);
783 }
784 }
785 // try to use interval preference if it exists
786 CFTypeRef value = (CFNumberRef)CFPreferencesCopyValue(kUpdateIntervalKey, kSecPrefsDomain, kCFPreferencesAnyUser, kCFPreferencesCurrentHost);
787 if (isNumber(value)) {
788 CFNumberGetValue((CFNumberRef)value, kCFNumberCFIndexType, &interval);
789 }
790 CFReleaseNull(value);
791
792 // sanity check
793 if (interval < kSecMinUpdateInterval) {
794 interval = kSecMinUpdateInterval;
795 } else if (interval > kSecMaxUpdateInterval) {
796 interval = kSecMaxUpdateInterval;
797 }
798
799 // compute randomization factor, between 0 and 50% of the interval
800 CFIndex fuzz = arc4random() % (long)(interval/2.0);
801 CFAbsoluteTime nextUpdate = CFAbsoluteTimeGetCurrent() + interval + fuzz;
802 secdebug("validupdate", "next update in %ld seconds", (long)(interval + fuzz));
803 return nextUpdate;
804 }
805
806 CFIndex SecRevocationDbIngestUpdate(CFDictionaryRef update) {
807 CFIndex version = 0;
808 if (!update) {
809 return version;
810 }
811 CFTypeRef value = (CFNumberRef)CFDictionaryGetValue(update, CFSTR("version"));
812 if (isNumber(value)) {
813 if (!CFNumberGetValue((CFNumberRef)value, kCFNumberCFIndexType, &version)) {
814 version = 0;
815 }
816 }
817 SecRevocationDbApplyUpdate(update, version);
818
819 return version;
820 }
821
822 /* Database management */
823
824 /* v1 = initial version */
825 /* v2 = fix for group entry transitions */
826
827 #define kSecRevocationDbSchemaVersion 2
828
829 #define selectGroupIdSQL CFSTR("SELECT DISTINCT groupid " \
830 "FROM issuers WHERE issuer_hash=?")
831
832 static SecDbRef SecRevocationDbCreate(CFStringRef path) {
833 /* only the db owner should open a read-write connection. */
834 bool readWrite = isDbOwner();
835 mode_t mode = 0644;
836
837 SecDbRef result = SecDbCreateWithOptions(path, mode, readWrite, false, false, ^bool (SecDbConnectionRef dbconn, bool didCreate, bool *callMeAgainForNextConnection, CFErrorRef *error) {
838 __block bool ok = true;
839 CFErrorRef localError = NULL;
840 if (ok && !SecDbWithSQL(dbconn, selectGroupIdSQL, &localError, NULL) && CFErrorGetCode(localError) == SQLITE_ERROR) {
841 /* SecDbWithSQL returns SQLITE_ERROR if the table we are preparing the above statement for doesn't exist. */
842
843 /* admin table holds these key-value (or key-ival) pairs:
844 'version' (integer) // version of database content
845 'check_again' (double) // CFAbsoluteTime of next check (optional; this value is currently stored in prefs)
846 'db_version' (integer) // version of database schema
847 'db_hash' (blob) // SHA-256 database hash
848 --> entries in admin table are unique by text key
849
850 issuers table holds map of issuing CA hashes to group identifiers:
851 issuer_hash (blob) // SHA-256 hash of issuer certificate (primary key)
852 groupid (integer) // associated group identifier in group ID table
853 --> entries in issuers table are unique by issuer_hash;
854 multiple issuer entries may have the same groupid!
855
856 groups table holds records with these attributes:
857 groupid (integer) // ordinal ID associated with this group entry
858 flags (integer) // a bitmask of the following values:
859 kSecValidInfoComplete (0x00000001) set if we have all revocation info for this issuer group
860 kSecValidInfoCheckOCSP (0x00000002) set if must check ocsp for certs from this issuer group
861 kSecValidInfoKnownOnly (0x00000004) set if any CA from this issuer group must be in database
862 kSecValidInfoRequireCT (0x00000008) set if all certs from this issuer group must have SCTs
863 kSecValidInfoAllowlist (0x00000010) set if this entry describes valid certs (i.e. is allowed)
864 format (integer) // an integer describing format of entries:
865 kSecValidInfoFormatUnknown (0) unknown format
866 kSecValidInfoFormatSerial (1) serial number, not greater than 20 bytes in length
867 kSecValidInfoFormatSHA256 (2) SHA-256 hash, 32 bytes in length
868 kSecValidInfoFormatNto1 (3) filter data blob of arbitrary length
869 data (blob) // Bloom filter data if format is 'nto1', otherwise NULL
870 --> entries in groups table are unique by groupid
871
872 serials table holds serial number blobs with these attributes:
873 rowid (integer) // ordinal ID associated with this serial number entry
874 serial (blob) // serial number
875 groupid (integer) // identifier for issuer group in the groups table
876 --> entries in serials table are unique by serial and groupid
877
878 hashes table holds SHA-256 hashes of certificates with these attributes:
879 rowid (integer) // ordinal ID associated with this sha256 hash entry
880 sha256 (blob) // SHA-256 hash of subject certificate
881 groupid (integer) // identifier for issuer group in the groups table
882 --> entries in hashes table are unique by sha256 and groupid
883 */
884 ok &= SecDbTransaction(dbconn, kSecDbExclusiveTransactionType, error, ^(bool *commit) {
885 ok = SecDbExec(dbconn,
886 CFSTR("CREATE TABLE admin("
887 "key TEXT PRIMARY KEY NOT NULL,"
888 "ival INTEGER NOT NULL,"
889 "value BLOB"
890 ");"
891 "CREATE TABLE issuers("
892 "issuer_hash BLOB PRIMARY KEY NOT NULL,"
893 "groupid INTEGER NOT NULL"
894 ");"
895 "CREATE INDEX issuer_idx ON issuers(issuer_hash);"
896 "CREATE TABLE groups("
897 "groupid INTEGER PRIMARY KEY AUTOINCREMENT,"
898 "flags INTEGER NOT NULL,"
899 "format INTEGER NOT NULL,"
900 "data BLOB"
901 ");"
902 "CREATE TABLE serials("
903 "rowid INTEGER PRIMARY KEY AUTOINCREMENT,"
904 "serial BLOB NOT NULL,"
905 "groupid INTEGER NOT NULL,"
906 "UNIQUE(serial,groupid)"
907 ");"
908 "CREATE TABLE hashes("
909 "rowid INTEGER PRIMARY KEY AUTOINCREMENT,"
910 "sha256 BLOB NOT NULL,"
911 "groupid INTEGER NOT NULL,"
912 "UNIQUE(sha256,groupid)"
913 ");"
914 "CREATE TRIGGER group_del BEFORE DELETE ON groups FOR EACH ROW "
915 "BEGIN "
916 "DELETE FROM serials WHERE groupid=OLD.groupid; "
917 "DELETE FROM hashes WHERE groupid=OLD.groupid; "
918 "DELETE FROM issuers WHERE groupid=OLD.groupid; "
919 "END;"), error);
920 *commit = ok;
921 });
922 }
923 CFReleaseSafe(localError);
924 if (!ok)
925 secerror("%s failed: %@", didCreate ? "Create" : "Open", error ? *error : NULL);
926 return ok;
927 });
928
929 return result;
930 }
931
932 typedef struct __SecRevocationDb *SecRevocationDbRef;
933 struct __SecRevocationDb {
934 SecDbRef db;
935 dispatch_queue_t update_queue;
936 bool fullUpdateInProgress;
937 };
938
939 static dispatch_once_t kSecRevocationDbOnce;
940 static SecRevocationDbRef kSecRevocationDb = NULL;
941
942 static SecRevocationDbRef SecRevocationDbInit(CFStringRef db_name) {
943 SecRevocationDbRef this;
944 dispatch_queue_attr_t attr;
945
946 require(this = (SecRevocationDbRef)malloc(sizeof(struct __SecRevocationDb)), errOut);
947 this->db = NULL;
948 this->update_queue = NULL;
949 this->fullUpdateInProgress = false;
950
951 require(this->db = SecRevocationDbCreate(db_name), errOut);
952 attr = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_BACKGROUND, 0);
953 require(this->update_queue = dispatch_queue_create(NULL, attr), errOut);
954
955 return this;
956
957 errOut:
958 secdebug("validupdate", "Failed to create db at \"%@\"", db_name);
959 if (this) {
960 if (this->update_queue) {
961 dispatch_release(this->update_queue);
962 }
963 CFReleaseSafe(this->db);
964 free(this);
965 }
966 return NULL;
967 }
968
969 static CFStringRef SecRevocationDbCopyPath(void) {
970 CFURLRef revDbURL = NULL;
971 CFStringRef revInfoRelPath = NULL;
972 if ((revInfoRelPath = CFStringCreateWithFormat(NULL, NULL, CFSTR("%s"), kSecRevocationDbFileName)) != NULL) {
973 revDbURL = SecCopyURLForFileInRevocationInfoDirectory(revInfoRelPath);
974 }
975 CFReleaseSafe(revInfoRelPath);
976
977 CFStringRef revDbPath = NULL;
978 if (revDbURL) {
979 revDbPath = CFURLCopyFileSystemPath(revDbURL, kCFURLPOSIXPathStyle);
980 CFRelease(revDbURL);
981 }
982 return revDbPath;
983 }
984
985 static void SecRevocationDbWith(void(^dbJob)(SecRevocationDbRef db)) {
986 dispatch_once(&kSecRevocationDbOnce, ^{
987 CFStringRef dbPath = SecRevocationDbCopyPath();
988 if (dbPath) {
989 kSecRevocationDb = SecRevocationDbInit(dbPath);
990 CFRelease(dbPath);
991 }
992 });
993 // Do pre job run work here (cancel idle timers etc.)
994 if (kSecRevocationDb->fullUpdateInProgress) {
995 return; // this would block since SecDb has an exclusive transaction lock
996 }
997 dbJob(kSecRevocationDb);
998 // Do post job run work here (gc timer, etc.)
999 }
1000
1001 /* Instance implementation. */
1002
1003 #define selectVersionSQL CFSTR("SELECT ival FROM admin " \
1004 "WHERE key='version'")
1005 #define selectDbVersionSQL CFSTR("SELECT ival FROM admin " \
1006 "WHERE key='db_version'")
1007 #define selectDbHashSQL CFSTR("SELECT value FROM admin " \
1008 "WHERE key='db_hash'")
1009 #define selectNextUpdateSQL CFSTR("SELECT value FROM admin " \
1010 "WHERE key='check_again'")
1011 #define selectGroupRecordSQL CFSTR("SELECT flags,format,data FROM " \
1012 "groups WHERE groupid=?")
1013 #define selectSerialRecordSQL CFSTR("SELECT rowid FROM serials " \
1014 "WHERE serial=? AND groupid=?")
1015 #define selectHashRecordSQL CFSTR("SELECT rowid FROM hashes " \
1016 "WHERE sha256=? AND groupid=?")
1017 #define insertAdminRecordSQL CFSTR("INSERT OR REPLACE INTO admin " \
1018 "(key,ival,value) VALUES (?,?,?)")
1019 #define insertIssuerRecordSQL CFSTR("INSERT OR REPLACE INTO issuers " \
1020 "(issuer_hash,groupid) VALUES (?,?)")
1021 #define insertGroupRecordSQL CFSTR("INSERT OR REPLACE INTO groups " \
1022 "(groupid,flags,format,data) VALUES (?,?,?,?)")
1023 #define insertSerialRecordSQL CFSTR("INSERT OR REPLACE INTO serials " \
1024 "(rowid,serial,groupid) VALUES (?,?,?)")
1025 #define insertSha256RecordSQL CFSTR("INSERT OR REPLACE INTO hashes " \
1026 "(rowid,sha256,groupid) VALUES (?,?,?)")
1027 #define deleteGroupRecordSQL CFSTR("DELETE FROM groups WHERE groupid=?")
1028
1029 #define deleteAllEntriesSQL CFSTR("DELETE from hashes; " \
1030 "DELETE from serials; DELETE from issuers; DELETE from groups; " \
1031 "DELETE from admin; DELETE from sqlite_sequence; VACUUM")
1032
1033 static int64_t _SecRevocationDbGetVersion(SecRevocationDbRef this, CFErrorRef *error) {
1034 /* look up version entry in admin table; returns -1 on error */
1035 __block int64_t version = -1;
1036 __block bool ok = true;
1037 __block CFErrorRef localError = NULL;
1038
1039 ok &= SecDbPerformRead(this->db, &localError, ^(SecDbConnectionRef dbconn) {
1040 if (ok) ok &= SecDbWithSQL(dbconn, selectVersionSQL, &localError, ^bool(sqlite3_stmt *selectVersion) {
1041 ok = SecDbStep(dbconn, selectVersion, &localError, NULL);
1042 version = sqlite3_column_int64(selectVersion, 0);
1043 return ok;
1044 });
1045 });
1046 (void) CFErrorPropagate(localError, error);
1047 return version;
1048 }
1049
1050 static void _SecRevocationDbSetVersion(SecRevocationDbRef this, CFIndex version){
1051 secdebug("validupdate", "setting version to %ld", (long)version);
1052
1053 __block CFErrorRef localError = NULL;
1054 __block bool ok = true;
1055 ok &= SecDbPerformWrite(this->db, &localError, ^(SecDbConnectionRef dbconn) {
1056 ok &= SecDbTransaction(dbconn, kSecDbExclusiveTransactionType, &localError, ^(bool *commit) {
1057 if (ok) ok = SecDbWithSQL(dbconn, insertAdminRecordSQL, &localError, ^bool(sqlite3_stmt *insertVersion) {
1058 if (ok) {
1059 const char *versionKey = "version";
1060 ok = SecDbBindText(insertVersion, 1, versionKey, strlen(versionKey),
1061 SQLITE_TRANSIENT, &localError);
1062 }
1063 if (ok) {
1064 ok = SecDbBindInt64(insertVersion, 2,
1065 (sqlite3_int64)version, &localError);
1066 }
1067 if (ok) {
1068 ok = SecDbStep(dbconn, insertVersion, &localError, NULL);
1069 }
1070 return ok;
1071 });
1072 });
1073 });
1074 if (!ok) {
1075 secerror("_SecRevocationDbSetVersion failed: %@", localError);
1076 }
1077 CFReleaseSafe(localError);
1078 }
1079
1080 static int64_t _SecRevocationDbGetSchemaVersion(SecRevocationDbRef this, CFErrorRef *error) {
1081 /* look up db_version entry in admin table; returns -1 on error */
1082 __block int64_t db_version = -1;
1083 __block bool ok = true;
1084 __block CFErrorRef localError = NULL;
1085
1086 ok &= SecDbPerformRead(this->db, &localError, ^(SecDbConnectionRef dbconn) {
1087 if (ok) ok &= SecDbWithSQL(dbconn, selectDbVersionSQL, &localError, ^bool(sqlite3_stmt *selectDbVersion) {
1088 ok = SecDbStep(dbconn, selectDbVersion, &localError, NULL);
1089 db_version = sqlite3_column_int64(selectDbVersion, 0);
1090 return ok;
1091 });
1092 });
1093 (void) CFErrorPropagate(localError, error);
1094 return db_version;
1095 }
1096
1097 static void _SecRevocationDbSetSchemaVersion(SecRevocationDbRef this, CFIndex dbversion){
1098 secdebug("validupdate", "setting db_version to %ld", (long)dbversion);
1099
1100 __block CFErrorRef localError = NULL;
1101 __block bool ok = true;
1102 ok &= SecDbPerformWrite(this->db, &localError, ^(SecDbConnectionRef dbconn) {
1103 ok &= SecDbTransaction(dbconn, kSecDbExclusiveTransactionType, &localError, ^(bool *commit) {
1104 if (ok) ok = SecDbWithSQL(dbconn, insertAdminRecordSQL, &localError, ^bool(sqlite3_stmt *insertDbVersion) {
1105 if (ok) {
1106 const char *dbVersionKey = "db_version";
1107 ok = SecDbBindText(insertDbVersion, 1, dbVersionKey, strlen(dbVersionKey),
1108 SQLITE_TRANSIENT, &localError);
1109 }
1110 if (ok) {
1111 ok = SecDbBindInt64(insertDbVersion, 2,
1112 (sqlite3_int64)dbversion, &localError);
1113 }
1114 if (ok) {
1115 ok = SecDbStep(dbconn, insertDbVersion, &localError, NULL);
1116 }
1117 return ok;
1118 });
1119 });
1120 });
1121 if (!ok) {
1122 secerror("_SecRevocationDbSetSchemaVersion failed: %@", localError);
1123 }
1124 CFReleaseSafe(localError);
1125 }
1126
1127 static CFAbsoluteTime _SecRevocationDbGetNextUpdateTime(SecRevocationDbRef this, CFErrorRef *error) {
1128 /* look up check_again entry in admin table; returns 0 on error */
1129 __block CFAbsoluteTime nextUpdate = 0;
1130 __block bool ok = true;
1131 __block CFErrorRef localError = NULL;
1132
1133 ok &= SecDbPerformRead(this->db, &localError, ^(SecDbConnectionRef dbconn) {
1134 if (ok) ok &= SecDbWithSQL(dbconn, selectNextUpdateSQL, &localError, ^bool(sqlite3_stmt *selectNextUpdate) {
1135 ok = SecDbStep(dbconn, selectNextUpdate, &localError, NULL);
1136 CFAbsoluteTime *p = (CFAbsoluteTime *)sqlite3_column_blob(selectNextUpdate, 0);
1137 if (p != NULL) {
1138 if (sizeof(CFAbsoluteTime) == sqlite3_column_bytes(selectNextUpdate, 0)) {
1139 nextUpdate = *p;
1140 }
1141 }
1142 return ok;
1143 });
1144 });
1145
1146 (void) CFErrorPropagate(localError, error);
1147 return nextUpdate;
1148 }
1149
1150 static void _SecRevocationDbSetNextUpdateTime(SecRevocationDbRef this, CFAbsoluteTime nextUpdate){
1151 secdebug("validupdate", "setting next update to %f", (double)nextUpdate);
1152
1153 __block CFErrorRef localError = NULL;
1154 __block bool ok = true;
1155 ok &= SecDbPerformWrite(this->db, &localError, ^(SecDbConnectionRef dbconn) {
1156 ok &= SecDbTransaction(dbconn, kSecDbExclusiveTransactionType, &localError, ^(bool *commit) {
1157 if (ok) ok = SecDbWithSQL(dbconn, insertAdminRecordSQL, &localError, ^bool(sqlite3_stmt *insertRecord) {
1158 if (ok) {
1159 const char *nextUpdateKey = "check_again";
1160 ok = SecDbBindText(insertRecord, 1, nextUpdateKey, strlen(nextUpdateKey),
1161 SQLITE_TRANSIENT, &localError);
1162 }
1163 if (ok) {
1164 ok = SecDbBindInt64(insertRecord, 2,
1165 (sqlite3_int64)0, &localError);
1166 }
1167 if (ok) {
1168 ok = SecDbBindBlob(insertRecord, 3,
1169 &nextUpdate, sizeof(CFAbsoluteTime),
1170 SQLITE_TRANSIENT, &localError);
1171 }
1172 if (ok) {
1173 ok = SecDbStep(dbconn, insertRecord, &localError, NULL);
1174 }
1175 return ok;
1176 });
1177 });
1178 });
1179 if (!ok) {
1180 secerror("_SecRevocationDbSetNextUpdate failed: %@", localError);
1181 }
1182 CFReleaseSafe(localError);
1183 }
1184
1185 static bool _SecRevocationDbRemoveAllEntries(SecRevocationDbRef this) {
1186 /* remove all entries from all tables in the database */
1187 __block bool ok = true;
1188 __block CFErrorRef localError = NULL;
1189
1190 ok &= SecDbPerformWrite(this->db, &localError, ^(SecDbConnectionRef dbconn) {
1191 ok &= SecDbTransaction(dbconn, kSecDbExclusiveTransactionType, &localError, ^(bool *commit) {
1192 ok &= SecDbWithSQL(dbconn, deleteAllEntriesSQL, &localError, ^bool(sqlite3_stmt *deleteAll) {
1193 ok = SecDbStep(dbconn, deleteAll, &localError, NULL);
1194 return ok;
1195 });
1196 });
1197 });
1198 /* one more thing: update the schema version */
1199 _SecRevocationDbSetSchemaVersion(this, kSecRevocationDbSchemaVersion);
1200
1201 CFReleaseSafe(localError);
1202 return ok;
1203 }
1204
1205 static bool _SecRevocationDbUpdateIssuers(SecRevocationDbRef this, int64_t groupId, CFArrayRef issuers, CFErrorRef *error) {
1206 /* insert or replace issuer records in issuers table */
1207 if (!issuers || groupId < 0) {
1208 return false; /* must have something to insert, and a group to associate with it */
1209 }
1210 __block bool ok = true;
1211 __block CFErrorRef localError = NULL;
1212
1213 ok &= SecDbPerformWrite(this->db, &localError, ^(SecDbConnectionRef dbconn) {
1214 ok &= SecDbTransaction(dbconn, kSecDbExclusiveTransactionType, &localError, ^(bool *commit) {
1215 if (isArray(issuers)) {
1216 CFIndex issuerIX, issuerCount = CFArrayGetCount(issuers);
1217 for (issuerIX=0; issuerIX<issuerCount && ok; issuerIX++) {
1218 CFDataRef hash = (CFDataRef)CFArrayGetValueAtIndex(issuers, issuerIX);
1219 if (!hash) { continue; }
1220 if (ok) ok = SecDbWithSQL(dbconn, insertIssuerRecordSQL, &localError, ^bool(sqlite3_stmt *insertIssuer) {
1221 if (ok) {
1222 ok = SecDbBindBlob(insertIssuer, 1,
1223 CFDataGetBytePtr(hash),
1224 CFDataGetLength(hash),
1225 SQLITE_TRANSIENT, &localError);
1226 }
1227 if (ok) {
1228 ok = SecDbBindInt64(insertIssuer, 2,
1229 groupId, &localError);
1230 }
1231 /* Execute the insert statement for this issuer record. */
1232 if (ok) {
1233 ok = SecDbStep(dbconn, insertIssuer, &localError, NULL);
1234 }
1235 return ok;
1236 });
1237 }
1238 }
1239 });
1240 });
1241
1242 (void) CFErrorPropagate(localError, error);
1243 return ok;
1244 }
1245
1246 static bool _SecRevocationDbUpdatePerIssuerData(SecRevocationDbRef this, int64_t groupId, CFDictionaryRef dict, CFErrorRef *error) {
1247 /* insert records in serials or hashes table. */
1248 if (!dict || groupId < 0) {
1249 return false; /* must have something to insert, and a group to associate with it */
1250 }
1251 __block bool ok = true;
1252 __block CFErrorRef localError = NULL;
1253
1254 ok &= SecDbPerformWrite(this->db, &localError, ^(SecDbConnectionRef dbconn) {
1255 ok &= SecDbTransaction(dbconn, kSecDbExclusiveTransactionType, &localError, ^(bool *commit) {
1256 CFArrayRef addArray = (CFArrayRef)CFDictionaryGetValue(dict, CFSTR("add"));
1257 if (isArray(addArray)) {
1258 CFIndex identifierIX, identifierCount = CFArrayGetCount(addArray);
1259 for (identifierIX=0; identifierIX<identifierCount; identifierIX++) {
1260 CFDataRef identifierData = (CFDataRef)CFArrayGetValueAtIndex(addArray, identifierIX);
1261 if (!identifierData) { continue; }
1262 CFIndex length = CFDataGetLength(identifierData);
1263 /* we can figure out the format without an extra read to get the format column.
1264 len <= 20 is a serial number. len==32 is a sha256 hash. otherwise: xor. */
1265 CFStringRef sql = NULL;
1266 if (length <= 20) {
1267 sql = insertSerialRecordSQL;
1268 } else if (length == 32) {
1269 sql = insertSha256RecordSQL;
1270 }
1271 if (!sql) { continue; }
1272
1273 if (ok) ok = SecDbWithSQL(dbconn, sql, &localError, ^bool(sqlite3_stmt *insertIdentifier) {
1274 /* (rowid,serial|sha256,groupid) */
1275 /* rowid == column 1, autoincrement so we don't set directly */
1276 if (ok) {
1277 ok = SecDbBindBlob(insertIdentifier, 2,
1278 CFDataGetBytePtr(identifierData),
1279 CFDataGetLength(identifierData),
1280 SQLITE_TRANSIENT, &localError);
1281 }
1282 if (ok) {
1283 ok = SecDbBindInt64(insertIdentifier, 3,
1284 groupId, &localError);
1285 }
1286 /* Execute the insert statement for the identifier record. */
1287 if (ok) {
1288 ok = SecDbStep(dbconn, insertIdentifier, &localError, NULL);
1289 }
1290 return ok;
1291 });
1292 }
1293 }
1294 });
1295 });
1296
1297 (void) CFErrorPropagate(localError, error);
1298 return ok;
1299 }
1300
1301 static int64_t _SecRevocationDbUpdateGroup(SecRevocationDbRef this, int64_t groupId, CFDictionaryRef dict, CFErrorRef *error) {
1302 /* insert group record for a given groupId.
1303 if the specified groupId is < 0, a new group entry is created.
1304 returns the groupId on success, or -1 on failure.
1305 */
1306 __block int64_t result = -1;
1307 __block bool ok = true;
1308 __block CFErrorRef localError = NULL;
1309
1310 if (!dict) {
1311 return groupId; /* no-op if no dictionary is provided */
1312 }
1313
1314 ok &= SecDbPerformWrite(this->db, &localError, ^(SecDbConnectionRef dbconn) {
1315 ok &= SecDbTransaction(dbconn, kSecDbExclusiveTransactionType, &localError, ^(bool *commit) {
1316 ok &= SecDbWithSQL(dbconn, insertGroupRecordSQL, &localError, ^bool(sqlite3_stmt *insertGroup) {
1317 CFTypeRef value;
1318 SecValidInfoFormat format = kSecValidInfoFormatUnknown;
1319 /* (groupid,flags,format,data) */
1320 /* groups.groupid */
1321 if (ok && !(groupId < 0)) {
1322 /* bind to existing groupId row if known, otherwise will insert and autoincrement */
1323 ok = SecDbBindInt64(insertGroup, 1, groupId, &localError);
1324 }
1325 /* groups.flags */
1326 if (ok) {
1327 int flags = 0;
1328 value = (CFBooleanRef)CFDictionaryGetValue(dict, CFSTR("complete"));
1329 if (isBoolean(value)) {
1330 if (CFBooleanGetValue((CFBooleanRef)value)) {
1331 flags |= kSecValidInfoComplete;
1332 }
1333 }
1334 value = (CFBooleanRef)CFDictionaryGetValue(dict, CFSTR("check-ocsp"));
1335 if (isBoolean(value)) {
1336 if (CFBooleanGetValue((CFBooleanRef)value)) {
1337 flags |= kSecValidInfoCheckOCSP;
1338 }
1339 }
1340 value = (CFBooleanRef)CFDictionaryGetValue(dict, CFSTR("known-intermediates-only"));
1341 if (isBoolean(value)) {
1342 if (CFBooleanGetValue((CFBooleanRef)value)) {
1343 flags |= kSecValidInfoKnownOnly;
1344 }
1345 }
1346 value = (CFBooleanRef)CFDictionaryGetValue(dict, CFSTR("require-ct"));
1347 if (isBoolean(value)) {
1348 if (CFBooleanGetValue((CFBooleanRef)value)) {
1349 flags |= kSecValidInfoRequireCT;
1350 }
1351 }
1352 value = (CFBooleanRef)CFDictionaryGetValue(dict, CFSTR("valid"));
1353 if (isBoolean(value)) {
1354 if (CFBooleanGetValue((CFBooleanRef)value)) {
1355 flags |= kSecValidInfoAllowlist;
1356 }
1357 }
1358 ok = SecDbBindInt(insertGroup, 2, flags, &localError);
1359 }
1360 /* groups.format */
1361 if (ok) {
1362 value = (CFBooleanRef)CFDictionaryGetValue(dict, CFSTR("format"));
1363 if (isString(value)) {
1364 if (CFStringCompare((CFStringRef)value, CFSTR("serial"), 0) == kCFCompareEqualTo) {
1365 format = kSecValidInfoFormatSerial;
1366 } else if (CFStringCompare((CFStringRef)value, CFSTR("sha256"), 0) == kCFCompareEqualTo) {
1367 format = kSecValidInfoFormatSHA256;
1368 } else if (CFStringCompare((CFStringRef)value, CFSTR("nto1"), 0) == kCFCompareEqualTo) {
1369 format = kSecValidInfoFormatNto1;
1370 }
1371 }
1372 ok = SecDbBindInt(insertGroup, 3, (int)format, &localError);
1373 }
1374 /* groups.data */
1375 CFDataRef xmlData = NULL;
1376 if (ok && format == kSecValidInfoFormatNto1) {
1377 CFMutableDictionaryRef nto1 = CFDictionaryCreateMutable(kCFAllocatorDefault, 0,
1378 &kCFTypeDictionaryKeyCallBacks,
1379 &kCFTypeDictionaryValueCallBacks);
1380 value = (CFDataRef)CFDictionaryGetValue(dict, CFSTR("xor"));
1381 if (isData(value)) {
1382 CFDictionaryAddValue(nto1, CFSTR("xor"), value);
1383 }
1384 value = (CFArrayRef)CFDictionaryGetValue(dict, CFSTR("params"));
1385 if (isArray(value)) {
1386 CFDictionaryAddValue(nto1, CFSTR("params"), value);
1387 }
1388 xmlData = CFPropertyListCreateData(kCFAllocatorDefault, nto1,
1389 kCFPropertyListXMLFormat_v1_0, 0, &localError);
1390 CFReleaseSafe(nto1);
1391
1392 if (xmlData) {
1393 // compress the xmlData blob, if possible
1394 CFDataRef deflatedData = copyDeflatedData(xmlData);
1395 if (deflatedData) {
1396 if (CFDataGetLength(deflatedData) < CFDataGetLength(xmlData)) {
1397 CFRelease(xmlData);
1398 xmlData = deflatedData;
1399 }
1400 else {
1401 CFRelease(deflatedData);
1402 }
1403 }
1404 ok = SecDbBindBlob(insertGroup, 4,
1405 CFDataGetBytePtr(xmlData),
1406 CFDataGetLength(xmlData),
1407 SQLITE_TRANSIENT, &localError);
1408 }
1409 }
1410
1411 /* Execute the insert statement for the group record. */
1412 if (ok) {
1413 ok = SecDbStep(dbconn, insertGroup, &localError, NULL);
1414 result = (int64_t)sqlite3_last_insert_rowid(SecDbHandle(dbconn));
1415 }
1416 if (!ok) {
1417 secdebug("validupdate", "Failed to insert group %ld", (long)result);
1418 }
1419 /* Clean up temporary allocation made in this block. */
1420 CFReleaseSafe(xmlData);
1421 return ok;
1422 });
1423 });
1424 });
1425
1426 (void) CFErrorPropagate(localError, error);
1427 return result;
1428 }
1429
1430 static int64_t _SecRevocationDbGroupIdForIssuerHash(SecRevocationDbRef this, CFDataRef hash, CFErrorRef *error) {
1431 /* look up issuer hash in issuers table to get groupid, if it exists */
1432 __block int64_t groupId = -1;
1433 __block bool ok = true;
1434 __block CFErrorRef localError = NULL;
1435
1436 require(hash, errOut);
1437
1438 /* This is the starting point for any lookup; find a group id for the given issuer hash.
1439 Before we do that, need to verify the current db_version. We cannot use results from a
1440 database created with schema version 1. At the next database update interval,
1441 we'll be removing and recreating the database contents with the current schema version.
1442 */
1443 int64_t db_version = _SecRevocationDbGetSchemaVersion(this, NULL);
1444 require(db_version > 1, errOut);
1445
1446 /* Look up provided issuer_hash in the issuers table.
1447 */
1448 ok &= SecDbPerformRead(this->db, &localError, ^(SecDbConnectionRef dbconn) {
1449 ok &= SecDbWithSQL(dbconn, selectGroupIdSQL, &localError, ^bool(sqlite3_stmt *selectGroupId) {
1450 ok = SecDbBindBlob(selectGroupId, 1, CFDataGetBytePtr(hash), CFDataGetLength(hash), SQLITE_TRANSIENT, &localError);
1451 ok &= SecDbStep(dbconn, selectGroupId, &localError, ^(bool *stopGroupId) {
1452 groupId = sqlite3_column_int64(selectGroupId, 0);
1453 });
1454 return ok;
1455 });
1456 });
1457
1458 errOut:
1459 (void) CFErrorPropagate(localError, error);
1460 return groupId;
1461 }
1462
1463 static bool _SecRevocationDbApplyGroupDelete(SecRevocationDbRef this, CFDataRef issuerHash, CFErrorRef *error) {
1464 /* delete group associated with the given issuer;
1465 schema trigger will delete associated issuers, serials, and hashes. */
1466 __block int64_t groupId = -1;
1467 __block bool ok = true;
1468 __block CFErrorRef localError = NULL;
1469
1470 groupId = _SecRevocationDbGroupIdForIssuerHash(this, issuerHash, &localError);
1471 require(!(groupId < 0), errOut);
1472
1473 ok &= SecDbPerformWrite(this->db, &localError, ^(SecDbConnectionRef dbconn) {
1474 ok &= SecDbTransaction(dbconn, kSecDbExclusiveTransactionType, &localError, ^(bool *commit) {
1475 ok = SecDbWithSQL(dbconn, deleteGroupRecordSQL, &localError, ^bool(sqlite3_stmt *deleteResponse) {
1476 ok = SecDbBindInt64(deleteResponse, 1, groupId, &localError);
1477 /* Execute the delete statement. */
1478 if (ok) {
1479 ok = SecDbStep(dbconn, deleteResponse, &localError, NULL);
1480 }
1481 return ok;
1482 });
1483 });
1484 });
1485
1486 errOut:
1487 (void) CFErrorPropagate(localError, error);
1488 return (groupId < 0) ? false : true;
1489 }
1490
1491 static bool _SecRevocationDbApplyGroupUpdate(SecRevocationDbRef this, CFDictionaryRef dict, CFErrorRef *error) {
1492 /* process one issuer group's update dictionary */
1493 int64_t groupId = -1;
1494 CFErrorRef localError = NULL;
1495
1496 CFArrayRef issuers = (dict) ? (CFArrayRef)CFDictionaryGetValue(dict, CFSTR("issuer-hash")) : NULL;
1497 if (isArray(issuers)) {
1498 CFIndex issuerIX, issuerCount = CFArrayGetCount(issuers);
1499 /* while we have issuers and haven't found a matching group id */
1500 for (issuerIX=0; issuerIX<issuerCount && groupId < 0; issuerIX++) {
1501 CFDataRef hash = (CFDataRef)CFArrayGetValueAtIndex(issuers, issuerIX);
1502 if (!hash) { continue; }
1503 groupId = _SecRevocationDbGroupIdForIssuerHash(this, hash, &localError);
1504 }
1505 }
1506 /* create or update the group entry */
1507 groupId = _SecRevocationDbUpdateGroup(this, groupId, dict, &localError);
1508 if (groupId < 0) {
1509 secdebug("validupdate", "failed to get groupId");
1510 } else {
1511 //secdebug("validupdate", "got groupId %ld", (long)groupId);
1512 /* create or update issuer entries, now that we know the group id */
1513 _SecRevocationDbUpdateIssuers(this, groupId, issuers, &localError);
1514 /* create or update entries in serials or hashes tables */
1515 _SecRevocationDbUpdatePerIssuerData(this, groupId, dict, &localError);
1516 }
1517
1518 (void) CFErrorPropagate(localError, error);
1519 return (groupId < 0) ? false : true;
1520 }
1521
1522 static void _SecRevocationDbApplyUpdate(SecRevocationDbRef this, CFDictionaryRef update, CFIndex version) {
1523 /* process entire update dictionary */
1524 if (!this || !update) {
1525 secerror("_SecRevocationDbApplyUpdate failed: invalid args");
1526 return;
1527 }
1528 CFRetain(update);
1529
1530 __block CFDictionaryRef localUpdate = update;
1531 __block CFErrorRef localError = NULL;
1532
1533 // This may take a while; do the work on our update queue with background priority.
1534
1535 dispatch_async(this->update_queue, ^{
1536
1537 CFTypeRef value;
1538 CFIndex deleteCount = 0;
1539 CFIndex updateCount = 0;
1540
1541 /* check whether this is a full update */
1542 this->fullUpdateInProgress = false;
1543 value = (CFBooleanRef)CFDictionaryGetValue(update, CFSTR("full"));
1544 if (isBoolean(value)) {
1545 this->fullUpdateInProgress = CFBooleanGetValue((CFBooleanRef)value);
1546 }
1547
1548 /* process 'delete' list */
1549 value = (CFArrayRef)CFDictionaryGetValue(localUpdate, CFSTR("delete"));
1550 if (isArray(value)) {
1551 deleteCount = CFArrayGetCount((CFArrayRef)value);
1552 secdebug("validupdate", "processing %ld deletes", (long)deleteCount);
1553 for (CFIndex deleteIX=0; deleteIX<deleteCount; deleteIX++) {
1554 CFDataRef issuerHash = (CFDataRef)CFArrayGetValueAtIndex((CFArrayRef)value, deleteIX);
1555 if (isData(issuerHash)) {
1556 (void)_SecRevocationDbApplyGroupDelete(this, issuerHash, &localError);
1557 CFReleaseNull(localError);
1558 }
1559 }
1560 }
1561
1562 /* process 'update' list */
1563 value = (CFArrayRef)CFDictionaryGetValue(localUpdate, CFSTR("update"));
1564 if (isArray(value)) {
1565 updateCount = CFArrayGetCount((CFArrayRef)value);
1566 secdebug("validupdate", "processing %ld updates", (long)updateCount);
1567 for (CFIndex updateIX=0; updateIX<updateCount; updateIX++) {
1568 CFDictionaryRef dict = (CFDictionaryRef)CFArrayGetValueAtIndex((CFArrayRef)value, updateIX);
1569 if (isDictionary(dict)) {
1570 (void)_SecRevocationDbApplyGroupUpdate(this, dict, &localError);
1571 CFReleaseNull(localError);
1572 }
1573 }
1574 }
1575 CFRelease(localUpdate);
1576
1577 /* set version */
1578 _SecRevocationDbSetVersion(this, version);
1579
1580 /* set db_version if not already set */
1581 int64_t db_version = _SecRevocationDbGetSchemaVersion(this, NULL);
1582 if (db_version < 0) {
1583 _SecRevocationDbSetSchemaVersion(this, kSecRevocationDbSchemaVersion);
1584 }
1585
1586 /* compact the db */
1587 (void)SecDbPerformWrite(this->db, &localError, ^(SecDbConnectionRef dbconn) {
1588 SecDbTransaction(dbconn, kSecDbExclusiveTransactionType, &localError, ^(bool *commit) {
1589 SecDbExec(dbconn, CFSTR("VACUUM;"), &localError);
1590 CFReleaseNull(localError);
1591 });
1592 });
1593 this->fullUpdateInProgress = false;
1594
1595 });
1596 }
1597
1598 static bool _SecRevocationDbSerialInGroup(SecRevocationDbRef this,
1599 CFDataRef serial,
1600 int64_t groupId,
1601 CFErrorRef *error) {
1602 __block bool result = false;
1603 __block bool ok = true;
1604 __block CFErrorRef localError = NULL;
1605 require(this && serial, errOut);
1606 ok &= SecDbPerformRead(this->db, &localError, ^(SecDbConnectionRef dbconn) {
1607 ok &= SecDbWithSQL(dbconn, selectSerialRecordSQL, &localError, ^bool(sqlite3_stmt *selectSerial) {
1608 ok &= SecDbBindBlob(selectSerial, 1, CFDataGetBytePtr(serial),
1609 CFDataGetLength(serial), SQLITE_TRANSIENT, &localError);
1610 ok &= SecDbBindInt64(selectSerial, 2, groupId, &localError);
1611 ok &= SecDbStep(dbconn, selectSerial, &localError, ^(bool *stop) {
1612 int64_t foundRowId = (int64_t)sqlite3_column_int64(selectSerial, 0);
1613 result = (foundRowId > 0);
1614 });
1615 return ok;
1616 });
1617 });
1618
1619 errOut:
1620 (void) CFErrorPropagate(localError, error);
1621 return result;
1622 }
1623
1624 static bool _SecRevocationDbCertHashInGroup(SecRevocationDbRef this,
1625 CFDataRef certHash,
1626 int64_t groupId,
1627 CFErrorRef *error) {
1628 __block bool result = false;
1629 __block bool ok = true;
1630 __block CFErrorRef localError = NULL;
1631 require(this && certHash, errOut);
1632 ok &= SecDbPerformRead(this->db, &localError, ^(SecDbConnectionRef dbconn) {
1633 ok &= SecDbWithSQL(dbconn, selectHashRecordSQL, &localError, ^bool(sqlite3_stmt *selectHash) {
1634 ok = SecDbBindBlob(selectHash, 1, CFDataGetBytePtr(certHash),
1635 CFDataGetLength(certHash), SQLITE_TRANSIENT, &localError);
1636 ok &= SecDbBindInt64(selectHash, 2, groupId, &localError);
1637 ok &= SecDbStep(dbconn, selectHash, &localError, ^(bool *stop) {
1638 int64_t foundRowId = (int64_t)sqlite3_column_int64(selectHash, 0);
1639 result = (foundRowId > 0);
1640 });
1641 return ok;
1642 });
1643 });
1644
1645 errOut:
1646 (void) CFErrorPropagate(localError, error);
1647 return result;
1648 }
1649
1650 static bool _SecRevocationDbSerialInFilter(SecRevocationDbRef this,
1651 CFDataRef serialData,
1652 CFDataRef xmlData) {
1653 /* N-To-1 filter implementation.
1654 The 'xmlData' parameter is a flattened XML dictionary,
1655 containing 'xor' and 'params' keys. First order of
1656 business is to reconstitute the blob into components.
1657 */
1658 bool result = false;
1659 CFRetainSafe(xmlData);
1660 CFDataRef propListData = xmlData;
1661 /* Expand data blob if needed */
1662 CFDataRef inflatedData = copyInflatedData(propListData);
1663 if (inflatedData) {
1664 CFReleaseSafe(propListData);
1665 propListData = inflatedData;
1666 }
1667 CFDataRef xor = NULL;
1668 CFArrayRef params = NULL;
1669 CFPropertyListRef nto1 = CFPropertyListCreateWithData(kCFAllocatorDefault, propListData, 0, NULL, NULL);
1670 if (nto1) {
1671 xor = (CFDataRef)CFDictionaryGetValue((CFDictionaryRef)nto1, CFSTR("xor"));
1672 params = (CFArrayRef)CFDictionaryGetValue((CFDictionaryRef)nto1, CFSTR("params"));
1673 }
1674 uint8_t *hash = (xor) ? (uint8_t*)CFDataGetBytePtr(xor) : NULL;
1675 CFIndex hashLen = (hash) ? CFDataGetLength(xor) : 0;
1676 uint8_t *serial = (serialData) ? (uint8_t*)CFDataGetBytePtr(serialData) : NULL;
1677 CFIndex serialLen = (serial) ? CFDataGetLength(serialData) : 0;
1678
1679 require(hash && serial && params, errOut);
1680
1681 const uint32_t FNV_OFFSET_BASIS = 2166136261;
1682 const uint32_t FNV_PRIME = 16777619;
1683 bool notInHash = false;
1684 CFIndex ix, count = CFArrayGetCount(params);
1685 for (ix = 0; ix < count; ix++) {
1686 int32_t param;
1687 CFNumberRef cfnum = (CFNumberRef)CFArrayGetValueAtIndex(params, ix);
1688 if (!isNumber(cfnum) ||
1689 !CFNumberGetValue(cfnum, kCFNumberSInt32Type, &param)) {
1690 secinfo("validupdate", "error processing filter params at index %ld", (long)ix);
1691 continue;
1692 }
1693 /* process one param */
1694 uint32_t hval = FNV_OFFSET_BASIS ^ param;
1695 CFIndex i = serialLen;
1696 while (i > 0) {
1697 hval = ((hval ^ (serial[--i])) * FNV_PRIME) & 0xFFFFFFFF;
1698 }
1699 hval = hval % (hashLen * 8);
1700 if ((hash[hval/8] & (1 << (hval % 8))) == 0) {
1701 notInHash = true; /* definitely not in hash */
1702 break;
1703 }
1704 }
1705 if (!notInHash) {
1706 /* probabilistically might be in hash if we get here. */
1707 result = true;
1708 }
1709
1710 errOut:
1711 CFReleaseSafe(nto1);
1712 CFReleaseSafe(propListData);
1713 return result;
1714 }
1715
1716 static SecValidInfoRef _SecRevocationDbValidInfoForCertificate(SecRevocationDbRef this,
1717 SecCertificateRef certificate,
1718 CFDataRef issuerHash,
1719 CFErrorRef *error) {
1720 __block CFErrorRef localError = NULL;
1721 __block bool ok = true;
1722 __block int flags = 0;
1723 __block SecValidInfoFormat format = kSecValidInfoFormatUnknown;
1724 __block CFDataRef data = NULL;
1725
1726 bool matched = false;
1727 int64_t groupId = 0;
1728 CFDataRef serial = NULL;
1729 CFDataRef certHash = NULL;
1730 SecValidInfoRef result = NULL;
1731
1732 #if TARGET_OS_OSX
1733 require(serial = SecCertificateCopySerialNumber(certificate, NULL), errOut);
1734 #else
1735 require(serial = SecCertificateCopySerialNumber(certificate), errOut);
1736 #endif
1737 require(certHash = SecCertificateCopySHA256Digest(certificate), errOut);
1738
1739 require(groupId = _SecRevocationDbGroupIdForIssuerHash(this, issuerHash, &localError), errOut);
1740
1741 /* Select the group record to determine flags and format. */
1742 ok &= SecDbPerformRead(this->db, &localError, ^(SecDbConnectionRef dbconn) {
1743 ok &= SecDbWithSQL(dbconn, selectGroupRecordSQL, &localError, ^bool(sqlite3_stmt *selectGroup) {
1744 ok = SecDbBindInt64(selectGroup, 1, groupId, &localError);
1745 ok &= SecDbStep(dbconn, selectGroup, &localError, ^(bool *stop) {
1746 flags = (int)sqlite3_column_int(selectGroup, 0);
1747 format = (SecValidInfoFormat)sqlite3_column_int(selectGroup, 1);
1748 uint8_t *p = (uint8_t *)sqlite3_column_blob(selectGroup, 2);
1749 if (p != NULL && format == kSecValidInfoFormatNto1) {
1750 CFIndex length = (CFIndex)sqlite3_column_bytes(selectGroup, 2);
1751 data = CFDataCreate(kCFAllocatorDefault, p, length);
1752 }
1753 });
1754 return ok;
1755 });
1756 });
1757
1758 if (format == kSecValidInfoFormatUnknown) {
1759 /* No group record found for this issuer. */
1760 }
1761 else if (format == kSecValidInfoFormatSerial) {
1762 /* Look up certificate's serial number in the serials table. */
1763 matched = _SecRevocationDbSerialInGroup(this, serial, groupId, &localError);
1764 }
1765 else if (format == kSecValidInfoFormatSHA256) {
1766 /* Look up certificate's SHA-256 hash in the hashes table. */
1767 matched = _SecRevocationDbCertHashInGroup(this, certHash, groupId, &localError);
1768 }
1769 else if (format == kSecValidInfoFormatNto1) {
1770 /* Perform a Bloom filter match against the serial. If matched is false,
1771 then the cert is definitely not in the list. But if matched is true,
1772 we don't know for certain, so we would need to check OCSP. */
1773 matched = _SecRevocationDbSerialInFilter(this, serial, data);
1774 }
1775
1776 if (matched) {
1777 /* Always return SecValidInfo for a matched certificate. */
1778 secdebug("validupdate", "Valid db matched cert: %@, format=%d, flags=%d",
1779 certHash, format, flags);
1780 result = SecValidInfoCreate(format, flags, certHash, issuerHash);
1781 }
1782 else if ((flags & kSecValidInfoComplete) && (flags & kSecValidInfoAllowlist)) {
1783 /* Not matching against a complete whitelist is equivalent to revocation. */
1784 secdebug("validupdate", "Valid db did NOT match cert on allowlist: %@, format=%d, flags=%d",
1785 certHash, format, flags);
1786 result = SecValidInfoCreate(format, flags, certHash, issuerHash);
1787 }
1788
1789 if (result && SecIsAppleTrustAnchor(certificate, 0)) {
1790 /* Prevent a catch-22. */
1791 secdebug("validupdate", "Valid db match for Apple trust anchor: %@, format=%d, flags=%d",
1792 certHash, format, flags);
1793 SecValidInfoRelease(result);
1794 result = NULL;
1795 }
1796
1797 errOut:
1798 (void) CFErrorPropagate(localError, error);
1799 CFReleaseSafe(data);
1800 CFReleaseSafe(certHash);
1801 CFReleaseSafe(serial);
1802 return result;
1803 }
1804
1805 static SecValidInfoRef _SecRevocationDbCopyMatching(SecRevocationDbRef db,
1806 SecCertificateRef certificate,
1807 SecCertificateRef issuer) {
1808 SecValidInfoRef result = NULL;
1809 CFErrorRef error = NULL;
1810 CFDataRef issuerHash = NULL;
1811
1812 require(certificate && issuer, errOut);
1813 require(issuerHash = SecCertificateCopySHA256Digest(issuer), errOut);
1814
1815 result = _SecRevocationDbValidInfoForCertificate(db, certificate, issuerHash, &error);
1816
1817 errOut:
1818 CFReleaseSafe(issuerHash);
1819 CFReleaseSafe(error);
1820 return result;
1821 }
1822
1823 static dispatch_queue_t _SecRevocationDbGetUpdateQueue(SecRevocationDbRef this) {
1824 return (this) ? this->update_queue : NULL;
1825 }
1826
1827
1828 /* Given a valid update dictionary, insert/replace or delete records
1829 in the revocation database. (This function is expected to be called only
1830 by the database maintainer, normally the system instance of trustd.)
1831 */
1832 void SecRevocationDbApplyUpdate(CFDictionaryRef update, CFIndex version) {
1833 SecRevocationDbWith(^(SecRevocationDbRef db) {
1834 _SecRevocationDbApplyUpdate(db, update, version);
1835 });
1836 }
1837
1838 /* Set the schema version for the revocation database.
1839 (This function is expected to be called only by the database maintainer,
1840 normally the system instance of trustd.)
1841 */
1842 void SecRevocationDbSetSchemaVersion(CFIndex db_version) {
1843 SecRevocationDbWith(^(SecRevocationDbRef db) {
1844 _SecRevocationDbSetSchemaVersion(db, db_version);
1845 });
1846 }
1847
1848 /* Set the next update value for the revocation database.
1849 (This function is expected to be called only by the database
1850 maintainer, normally the system instance of trustd. If the
1851 caller does not have write access, this is a no-op.)
1852 */
1853 void SecRevocationDbSetNextUpdateTime(CFAbsoluteTime nextUpdate) {
1854 SecRevocationDbWith(^(SecRevocationDbRef db) {
1855 _SecRevocationDbSetNextUpdateTime(db, nextUpdate);
1856 });
1857 }
1858
1859 /* Return the next update value as a CFAbsoluteTime.
1860 If the value cannot be obtained, -1 is returned.
1861 */
1862 CFAbsoluteTime SecRevocationDbGetNextUpdateTime(void) {
1863 __block CFAbsoluteTime result = -1;
1864 SecRevocationDbWith(^(SecRevocationDbRef db) {
1865 result = _SecRevocationDbGetNextUpdateTime(db, NULL);
1866 });
1867 return result;
1868 }
1869
1870 /* Return the serial background queue for database updates.
1871 If the queue cannot be obtained, NULL is returned.
1872 */
1873 dispatch_queue_t SecRevocationDbGetUpdateQueue(void) {
1874 __block dispatch_queue_t result = NULL;
1875 SecRevocationDbWith(^(SecRevocationDbRef db) {
1876 result = _SecRevocationDbGetUpdateQueue(db);
1877 });
1878 return result;
1879 }
1880
1881 /* Remove all entries in the revocation database and reset its version to 0.
1882 (This function is expected to be called only by the database maintainer,
1883 normally the system instance of trustd.)
1884 */
1885 void SecRevocationDbRemoveAllEntries(void) {
1886 SecRevocationDbWith(^(SecRevocationDbRef db) {
1887 _SecRevocationDbRemoveAllEntries(db);
1888 });
1889 }
1890
1891 /* === Public API === */
1892
1893 /* Given a certificate and its issuer, returns a SecValidInfoRef if the
1894 valid database contains matching info; otherwise returns NULL.
1895 Caller must release the returned SecValidInfoRef by calling
1896 SecValidInfoRelease when finished.
1897 */
1898 SecValidInfoRef SecRevocationDbCopyMatching(SecCertificateRef certificate,
1899 SecCertificateRef issuer) {
1900 __block SecValidInfoRef result = NULL;
1901 SecRevocationDbWith(^(SecRevocationDbRef db) {
1902 result = _SecRevocationDbCopyMatching(db, certificate, issuer);
1903 });
1904 return result;
1905 }
1906
1907 /* Return the current version of the revocation database.
1908 A version of 0 indicates an empty database which must be populated.
1909 If the version cannot be obtained, -1 is returned.
1910 */
1911 CFIndex SecRevocationDbGetVersion(void) {
1912 __block CFIndex result = -1;
1913 SecRevocationDbWith(^(SecRevocationDbRef db) {
1914 result = (CFIndex)_SecRevocationDbGetVersion(db, NULL);
1915 });
1916 return result;
1917 }
1918
1919 /* Return the current schema version of the revocation database.
1920 A version of 0 indicates an empty database which must be populated.
1921 If the schema version cannot be obtained, -1 is returned.
1922 */
1923 CFIndex SecRevocationDbGetSchemaVersion(void) {
1924 __block CFIndex result = -1;
1925 SecRevocationDbWith(^(SecRevocationDbRef db) {
1926 result = (CFIndex)_SecRevocationDbGetSchemaVersion(db, NULL);
1927 });
1928 return result;
1929 }