]> git.saurik.com Git - apple/security.git/blob - OSX/sec/securityd/SecRevocationDb.c
Security-58286.1.32.tar.gz
[apple/security.git] / OSX / sec / securityd / SecRevocationDb.c
1 /*
2 * Copyright (c) 2016-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 /*
26 * SecRevocationDb.c
27 */
28
29 #include <securityd/SecRevocationDb.h>
30 #include <securityd/asynchttp.h>
31 #include <securityd/OTATrustUtilities.h>
32 #include <securityd/SecRevocationNetworking.h>
33 #include <Security/SecCertificateInternal.h>
34 #include <Security/SecCMS.h>
35 #include <Security/CMSDecoder.h>
36 #include <Security/SecFramework.h>
37 #include <Security/SecInternal.h>
38 #include <Security/SecPolicyPriv.h>
39 #include <AssertMacros.h>
40 #include <stdlib.h>
41 #include <limits.h>
42 #include <string.h>
43 #include <fcntl.h>
44 #include <sys/stat.h>
45 #include <errno.h>
46 #include <dispatch/dispatch.h>
47 #include <asl.h>
48 #include <copyfile.h>
49 #include "utilities/debugging.h"
50 #include "utilities/sec_action.h"
51 #include "utilities/sqlutils.h"
52 #include "utilities/SecAppleAnchorPriv.h"
53 #include "utilities/iOSforOSX.h"
54 #include <utilities/SecCFError.h>
55 #include <utilities/SecCFRelease.h>
56 #include <utilities/SecCFWrappers.h>
57 #include <utilities/SecDb.h>
58 #include <utilities/SecFileLocations.h>
59 #include <sqlite3.h>
60 #include <zlib.h>
61 #include <malloc/malloc.h>
62 #include <xpc/activity.h>
63 #include <xpc/private.h>
64 #include <os/transaction_private.h>
65
66 #include <CFNetwork/CFHTTPMessage.h>
67 #include <CoreFoundation/CFURL.h>
68 #include <CoreFoundation/CFUtilities.h>
69
70 static CFStringRef kValidUpdateServer = CFSTR("valid.apple.com");
71
72 static CFStringRef kSecPrefsDomain = CFSTR("com.apple.security");
73 static CFStringRef kUpdateServerKey = CFSTR("ValidUpdateServer");
74 static CFStringRef kUpdateEnabledKey = CFSTR("ValidUpdateEnabled");
75 static CFStringRef kUpdateIntervalKey = CFSTR("ValidUpdateInterval");
76
77 typedef CF_OPTIONS(CFOptionFlags, SecValidInfoFlags) {
78 kSecValidInfoComplete = 1u << 0,
79 kSecValidInfoCheckOCSP = 1u << 1,
80 kSecValidInfoKnownOnly = 1u << 2,
81 kSecValidInfoRequireCT = 1u << 3,
82 kSecValidInfoAllowlist = 1u << 4,
83 kSecValidInfoNoCACheck = 1u << 5
84 };
85
86 /* minimum update interval */
87 #define kSecMinUpdateInterval (60.0 * 5)
88
89 /* standard update interval */
90 #define kSecStdUpdateInterval (60.0 * 60)
91
92 /* maximum allowed interval */
93 #define kSecMaxUpdateInterval (60.0 * 60 * 24 * 7)
94
95 #define kSecRevocationBasePath "/Library/Keychains/crls"
96 #define kSecRevocationCurUpdateFile "update-current"
97 #define kSecRevocationDbFileName "valid.sqlite3"
98 #define kSecRevocationDbReplaceFile ".valid_replace"
99
100 /* database schema version
101 v1 = initial version
102 v2 = fix for group entry transitions
103 v3 = handle optional entries in update dictionaries
104 v4 = add db_format and db_source entries
105
106 Note: kSecRevocationDbMinSchemaVersion is the lowest version whose
107 results can be used. This allows revocation results to be obtained
108 from an existing db before the next update interval occurs, at which
109 time we'll update to the current version (kSecRevocationDbSchemaVersion).
110 */
111 #define kSecRevocationDbSchemaVersion 4 /* current version we support */
112 #define kSecRevocationDbMinSchemaVersion 3 /* minimum version we can use */
113
114 /* update file format
115 */
116 CF_ENUM(CFIndex) {
117 kSecValidUpdateFormatG1 = 1, /* initial version */
118 kSecValidUpdateFormatG2 = 2, /* signed content, single plist */
119 kSecValidUpdateFormatG3 = 3 /* signed content, multiple plists */
120 };
121
122 #define kSecRevocationDbUpdateFormat 3 /* current version we support */
123 #define kSecRevocationDbMinUpdateFormat 2 /* minimum version we can use */
124
125 bool SecRevocationDbVerifyUpdate(void *update, CFIndex length);
126 CFIndex SecRevocationDbIngestUpdate(CFDictionaryRef update, CFIndex chunkVersion);
127 void SecRevocationDbApplyUpdate(CFDictionaryRef update, CFIndex version);
128 CFAbsoluteTime SecRevocationDbComputeNextUpdateTime(CFIndex updateInterval);
129 void SecRevocationDbSetSchemaVersion(CFIndex dbversion);
130 CFIndex SecRevocationDbGetUpdateFormat(void);
131 void SecRevocationDbSetUpdateFormat(CFIndex dbformat);
132 void SecRevocationDbSetUpdateSource(CFStringRef source);
133 CFStringRef SecRevocationDbCopyUpdateSource(void);
134 void SecRevocationDbSetNextUpdateTime(CFAbsoluteTime nextUpdate);
135 CFAbsoluteTime SecRevocationDbGetNextUpdateTime(void);
136 dispatch_queue_t SecRevocationDbGetUpdateQueue(void);
137 void SecRevocationDbRemoveAllEntries(void);
138 void SecRevocationDbReleaseAllConnections(void);
139
140
141 static CFDataRef copyInflatedData(CFDataRef data) {
142 if (!data) {
143 return NULL;
144 }
145 z_stream zs;
146 memset(&zs, 0, sizeof(zs));
147 /* 32 is a magic value which enables automatic header detection
148 of gzip or zlib compressed data. */
149 if (inflateInit2(&zs, 32+MAX_WBITS) != Z_OK) {
150 return NULL;
151 }
152 zs.next_in = (UInt8 *)(CFDataGetBytePtr(data));
153 zs.avail_in = (uInt)CFDataGetLength(data);
154
155 CFMutableDataRef outData = CFDataCreateMutable(NULL, 0);
156 if (!outData) {
157 return NULL;
158 }
159 CFIndex buf_sz = malloc_good_size(zs.avail_in ? zs.avail_in : 1024 * 4);
160 unsigned char *buf = malloc(buf_sz);
161 int rc;
162 do {
163 zs.next_out = (Bytef*)buf;
164 zs.avail_out = (uInt)buf_sz;
165 rc = inflate(&zs, 0);
166 CFIndex outLen = CFDataGetLength(outData);
167 if (outLen < (CFIndex)zs.total_out) {
168 CFDataAppendBytes(outData, (const UInt8*)buf, (CFIndex)zs.total_out - outLen);
169 }
170 } while (rc == Z_OK);
171
172 inflateEnd(&zs);
173
174 if (buf) {
175 free(buf);
176 }
177 if (rc != Z_STREAM_END) {
178 CFReleaseSafe(outData);
179 return NULL;
180 }
181 return (CFDataRef)outData;
182 }
183
184 static CFDataRef copyInflatedDataToFile(CFDataRef data, char *fileName) {
185 if (!data) {
186 return NULL;
187 }
188 z_stream zs;
189 memset(&zs, 0, sizeof(zs));
190 /* 32 is a magic value which enables automatic header detection
191 of gzip or zlib compressed data. */
192 if (inflateInit2(&zs, 32+MAX_WBITS) != Z_OK) {
193 return NULL;
194 }
195 zs.next_in = (UInt8 *)(CFDataGetBytePtr(data));
196 zs.avail_in = (uInt)CFDataGetLength(data);
197
198 (void)remove(fileName); /* We need an empty file to start */
199 int fd;
200 off_t off;
201 fd = open(fileName, O_RDWR | O_CREAT | O_TRUNC, 0644);
202 if (fd < 0 || (off = lseek(fd, 0, SEEK_SET)) < 0) {
203 secerror("unable to open %s (errno %d)", fileName, errno);
204 if (fd >= 0) {
205 close(fd);
206 }
207 return NULL;
208 }
209
210 CFIndex buf_sz = malloc_good_size(zs.avail_in ? zs.avail_in : 1024 * 4);
211 unsigned char *buf = malloc(buf_sz);
212 int rc;
213 do {
214 zs.next_out = (Bytef*)buf;
215 zs.avail_out = (uInt)buf_sz;
216 rc = inflate(&zs, 0);
217 if (off < (int64_t)zs.total_out) {
218 off = write(fd, buf, (int64_t)zs.total_out - off);
219 }
220 } while (rc == Z_OK);
221 close(fd);
222
223 inflateEnd(&zs);
224
225 if (buf) {
226 free(buf);
227 }
228 if (rc != Z_STREAM_END) {
229 (void)remove(fileName);
230 return NULL;
231 }
232
233 /* Now return an mmapped version of that data */
234 CFDataRef outData = NULL;
235 if ((rc = readValidFile(fileName, &outData)) != 0) {
236 secerror("unable to read and map %s (errno %d)", fileName, rc);
237 CFReleaseNull(outData);
238 }
239 return outData;
240 }
241
242 static CFDataRef copyDeflatedData(CFDataRef data) {
243 if (!data) {
244 return NULL;
245 }
246 z_stream zs;
247 memset(&zs, 0, sizeof(zs));
248 if (deflateInit(&zs, Z_BEST_COMPRESSION) != Z_OK) {
249 return NULL;
250 }
251 zs.next_in = (UInt8 *)(CFDataGetBytePtr(data));
252 zs.avail_in = (uInt)CFDataGetLength(data);
253
254 CFMutableDataRef outData = CFDataCreateMutable(NULL, 0);
255 if (!outData) {
256 return NULL;
257 }
258 CFIndex buf_sz = malloc_good_size(zs.avail_in ? zs.avail_in : 1024 * 4);
259 unsigned char *buf = malloc(buf_sz);
260 int rc = Z_BUF_ERROR;
261 do {
262 zs.next_out = (Bytef*)buf;
263 zs.avail_out = (uInt)buf_sz;
264 rc = deflate(&zs, Z_FINISH);
265
266 if (rc == Z_OK || rc == Z_STREAM_END) {
267 CFIndex buf_used = buf_sz - zs.avail_out;
268 CFDataAppendBytes(outData, (const UInt8*)buf, buf_used);
269 }
270 else if (rc == Z_BUF_ERROR) {
271 free(buf);
272 buf_sz = malloc_good_size(buf_sz * 2);
273 buf = malloc(buf_sz);
274 if (buf) {
275 rc = Z_OK; /* try again with larger buffer */
276 }
277 }
278 } while (rc == Z_OK && zs.avail_in);
279
280 deflateEnd(&zs);
281
282 if (buf) {
283 free(buf);
284 }
285 if (rc != Z_STREAM_END) {
286 CFReleaseSafe(outData);
287 return NULL;
288 }
289 return (CFDataRef)outData;
290 }
291
292 /* Read file opens the file, mmaps it and then closes the file. */
293 int readValidFile(const char *fileName,
294 CFDataRef *bytes) { // mmapped and returned -- must be munmapped!
295 int rtn, fd;
296 const uint8_t *buf = NULL;
297 struct stat sb;
298 size_t size = 0;
299
300 *bytes = NULL;
301 fd = open(fileName, O_RDONLY);
302 if (fd < 0) { return errno; }
303 rtn = fstat(fd, &sb);
304 if (rtn) { goto errOut; }
305 if (sb.st_size > (off_t) ((UINT32_MAX >> 1)-1)) {
306 rtn = EFBIG;
307 goto errOut;
308 }
309 size = (size_t)sb.st_size;
310
311 buf = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0);
312 if (!buf || buf == MAP_FAILED) {
313 rtn = errno;
314 secerror("unable to map %s (errno %d)", fileName, rtn);
315 goto errOut;
316 }
317
318 *bytes = CFDataCreateWithBytesNoCopy(NULL, buf, size, kCFAllocatorNull);
319
320 errOut:
321 close(fd);
322 if(rtn) {
323 CFReleaseNull(*bytes);
324 if (buf) {
325 int unmap_err = munmap((void *)buf, size);
326 if (unmap_err != 0) {
327 secerror("unable to unmap %ld bytes at %p (error %d)", (long)size, buf, rtn);
328 }
329 }
330 }
331 return rtn;
332 }
333
334 static void unmapData(CFDataRef CF_CONSUMED data) {
335 if (data) {
336 int rtn = munmap((void *)CFDataGetBytePtr(data), CFDataGetLength(data));
337 if (rtn != 0) {
338 secerror("unable to unmap %ld bytes at %p (error %d)", CFDataGetLength(data), CFDataGetBytePtr(data), rtn);
339 }
340
341 }
342 CFReleaseNull(data);
343 }
344
345 static bool removeFileWithSuffix(const char *basepath, const char *suffix) {
346 bool result = false;
347 char *path = NULL;
348 asprintf(&path, "%s%s", basepath, suffix);
349 if (path) {
350 if (remove(path) == -1) {
351 int error = errno;
352 if (error == ENOENT) {
353 result = true; // not an error if the file did not exist
354 } else {
355 secnotice("validupdate", "remove (%s): %s", path, strerror(error));
356 }
357 } else {
358 result = true;
359 }
360 free(path);
361 }
362 return result;
363 }
364
365 static bool isDbOwner() {
366 #if TARGET_OS_EMBEDDED
367 if (getuid() == 64) // _securityd
368 #else
369 if (getuid() == 0)
370 #endif
371 {
372 return true;
373 }
374 return false;
375 }
376
377
378 // MARK: -
379 // MARK: SecValidUpdate
380
381 /* ======================================================================
382 SecValidUpdate
383 ======================================================================*/
384
385 CFAbsoluteTime gUpdateStarted = 0.0;
386 CFAbsoluteTime gNextUpdate = 0.0;
387 static CFIndex gUpdateInterval = 0;
388 static CFIndex gLastVersion = 0;
389
390 /* Update Format:
391 1. The length of the signed data, as a 4-byte integer in network byte order.
392 2. The signed data, which consists of:
393 a. A 4-byte integer in network byte order, the count of plists to follow; and then for each plist:
394 i. A 4-byte integer, the length of each plist
395 ii. A plist, in binary form
396 b. There may be other data after the plists in the signed data, described by a future version of this specification.
397 3. The length of the following CMS blob, as a 4-byte integer in network byte order.
398 4. A detached CMS signature of the signed data described above.
399 5. There may be additional data after the CMS blob, described by a future version of this specification.
400
401 Note: the difference between g2 and g3 format is the addition of the 4-byte count in (2a).
402 */
403 static bool SecValidUpdateProcessData(CFIndex format, CFDataRef updateData) {
404 if (!updateData || format < 2) {
405 return false;
406 }
407 bool result = false;
408 CFIndex version = 0;
409 CFIndex interval = 0;
410 const UInt8* p = CFDataGetBytePtr(updateData);
411 CFIndex bytesRemaining = (p) ? CFDataGetLength(updateData) : 0;
412 /* make sure there is enough data to contain length and count */
413 if (bytesRemaining < ((CFIndex)sizeof(uint32_t) * 2)) {
414 secinfo("validupdate", "Skipping property list creation (length %ld is too short)", (long)bytesRemaining);
415 return result;
416 }
417 /* get length of signed data */
418 uint32_t dataLength = OSSwapInt32(*((uint32_t *)p));
419 bytesRemaining -= sizeof(uint32_t);
420 p += sizeof(uint32_t);
421
422 /* get plist count (G3 format and later) */
423 uint32_t plistCount = 1;
424 uint32_t plistTotal = 1;
425 if (format > kSecValidUpdateFormatG2) {
426 plistCount = OSSwapInt32(*((uint32_t *)p));
427 plistTotal = plistCount;
428 bytesRemaining -= sizeof(uint32_t);
429 p += sizeof(uint32_t);
430 }
431 if (dataLength > bytesRemaining) {
432 secinfo("validupdate", "Skipping property list creation (dataLength=%ld, bytesRemaining=%ld)",
433 (long)dataLength, (long)bytesRemaining);
434 return result;
435 }
436
437 /* process each chunked plist */
438 uint32_t plistProcessed = 0;
439 while (plistCount > 0 && bytesRemaining > 0) {
440 CFPropertyListRef propertyList = NULL;
441 uint32_t plistLength = dataLength;
442 if (format > kSecValidUpdateFormatG2) {
443 plistLength = OSSwapInt32(*((uint32_t *)p));
444 bytesRemaining -= sizeof(uint32_t);
445 p += sizeof(uint32_t);
446 }
447 --plistCount;
448 ++plistProcessed;
449
450 /* We're about to use a lot of memory for the plist -- go active so we don't get jetsammed */
451 os_transaction_t transaction;
452 transaction = os_transaction_create("com.apple.trustd.valid");
453
454 if (plistLength <= bytesRemaining) {
455 CFDataRef data = CFDataCreateWithBytesNoCopy(NULL, p, plistLength, kCFAllocatorNull);
456 propertyList = CFPropertyListCreateWithData(NULL, data, kCFPropertyListImmutable, NULL, NULL);
457 CFReleaseNull(data);
458 }
459 if (isDictionary(propertyList)) {
460 secdebug("validupdate", "Ingesting plist chunk %u of %u, length: %u",
461 plistProcessed, plistTotal, plistLength);
462 CFIndex curVersion = SecRevocationDbIngestUpdate((CFDictionaryRef)propertyList, version);
463 if (plistProcessed == 1) {
464 version = curVersion;
465 // get server-provided interval
466 CFTypeRef value = (CFNumberRef)CFDictionaryGetValue((CFDictionaryRef)propertyList,
467 CFSTR("check-again"));
468 if (isNumber(value)) {
469 CFNumberGetValue((CFNumberRef)value, kCFNumberCFIndexType, &interval);
470 }
471 }
472 if (curVersion < 0) {
473 plistCount = 0; // we already had this version; skip remaining plists
474 result = true;
475 }
476 } else {
477 secinfo("validupdate", "Failed to deserialize update chunk %u of %u",
478 plistProcessed, plistTotal);
479 if (plistProcessed == 1) {
480 gNextUpdate = SecRevocationDbComputeNextUpdateTime(0);
481 }
482 }
483 /* All finished with this property list */
484 CFReleaseSafe(propertyList);
485 os_release(transaction);
486
487 bytesRemaining -= plistLength;
488 p += plistLength;
489 }
490
491 if (version > 0) {
492 secdebug("validupdate", "Update received: v%lu", (unsigned long)version);
493 gLastVersion = version;
494 gNextUpdate = SecRevocationDbComputeNextUpdateTime(interval);
495 secdebug("validupdate", "Next update time: %f", gNextUpdate);
496 result = true;
497 }
498
499 // remember next update time in case of restart
500 SecRevocationDbSetNextUpdateTime(gNextUpdate);
501
502 return result;
503 }
504
505 void SecValidUpdateVerifyAndIngest(CFDataRef updateData) {
506 if (!updateData) {
507 secnotice("validupdate", "invalid update data");
508 return;
509 }
510 /* Verify CMS signature on signed data */
511 if (SecRevocationDbVerifyUpdate((void *)CFDataGetBytePtr(updateData), CFDataGetLength(updateData))) {
512 bool result = SecValidUpdateProcessData(kSecValidUpdateFormatG3, updateData);
513 if (!result) {
514 // Try g2 update format as a fallback if we failed to read g3
515 result = SecValidUpdateProcessData(kSecValidUpdateFormatG2, updateData);
516 }
517 if (!result) {
518 secerror("failed to process valid update");
519 }
520 } else {
521 secerror("failed to verify valid update");
522 }
523 }
524
525 static bool SecValidUpdateFromCompressed(CFDataRef CF_CONSUMED data) {
526 if (!data) { return false; }
527
528 /* We're about to use a lot of memory for the uncompressed update -- go active */
529 os_transaction_t transaction;
530 transaction = os_transaction_create("com.apple.trustd.valid");
531
532 /* Expand the update */
533 __block CFDataRef inflatedData = NULL;
534 WithPathInRevocationInfoDirectory(CFSTR(kSecRevocationCurUpdateFile), ^(const char *curUpdatePath) {
535 inflatedData = copyInflatedDataToFile(data, (char *)curUpdatePath);
536 secdebug("validupdate", "data expanded: %ld bytes", (long)CFDataGetLength(inflatedData));
537 });
538 unmapData(data);
539 os_release(transaction);
540
541 if (inflatedData) {
542 SecValidUpdateVerifyAndIngest(inflatedData);
543 unmapData(inflatedData);
544 }
545
546 /* All done with the temporary file */
547 WithPathInRevocationInfoDirectory(CFSTR(kSecRevocationCurUpdateFile), ^(const char *curUpdatePath) {
548 (void)removeFileWithSuffix(curUpdatePath, "");
549 });
550
551 return true;
552 }
553
554 static bool SecValidDatabaseFromCompressed(CFDataRef CF_CONSUMED data) {
555 if (!data) { return false; }
556
557 secdebug("validupdate", "read %ld bytes from file", (long)CFDataGetLength(data));
558
559 /* We're about to use a lot of memory for the uncompressed update -- go active */
560 os_transaction_t transaction;
561 transaction = os_transaction_create("com.apple.trustd.valid");
562
563 /* Expand the database */
564 __block CFDataRef inflatedData = NULL;
565 WithPathInRevocationInfoDirectory(CFSTR(kSecRevocationDbFileName), ^(const char *dbPath) {
566 inflatedData = copyInflatedDataToFile(data, (char *)dbPath);
567 secdebug("validupdate", "data expanded: %ld bytes", (long)CFDataGetLength(inflatedData));
568 });
569 unmapData(data);
570 os_release(transaction);
571
572 if (inflatedData) {
573 unmapData(inflatedData);
574 }
575 return true;
576 }
577
578 static bool SecValidUpdateSatisfiedLocally(CFStringRef server, CFIndex version, bool safeToReplace) {
579 __block bool result = false;
580 CFDataRef data = NULL;
581 SecOTAPKIRef otapkiRef = NULL;
582 int rtn = 0;
583 static int sNumLocalUpdates = 0;
584
585 // if we've replaced the database with a local asset twice in a row,
586 // something is wrong with it. Get this update from the server.
587 if (sNumLocalUpdates > 1) {
588 secdebug("validupdate", "%d consecutive db resets, ignoring local asset", sNumLocalUpdates);
589 goto updateExit;
590 }
591
592 // if a non-production server is specified, we will not be able to use a
593 // local production asset since its update sequence will be different.
594 if (kCFCompareEqualTo != CFStringCompare(server, kValidUpdateServer,
595 kCFCompareCaseInsensitive)) {
596 secdebug("validupdate", "non-production server specified, ignoring local asset");
597 goto updateExit;
598 }
599
600 // check static database asset(s)
601 otapkiRef = SecOTAPKICopyCurrentOTAPKIRef();
602 if (!otapkiRef) {
603 goto updateExit;
604 }
605 CFIndex assetVersion = SecOTAPKIGetValidSnapshotVersion(otapkiRef);
606 CFIndex assetFormat = SecOTAPKIGetValidSnapshotFormat(otapkiRef);
607 // version <= 0 means the database is invalid or empty.
608 // version > 0 means we have some version, but we need to see if a
609 // newer version is available as a local asset.
610 if (assetVersion <= version || assetFormat < kSecValidUpdateFormatG3) {
611 // asset is not newer than ours, or its version is unknown
612 goto updateExit;
613 }
614
615 // replace database only if safe to do so (i.e. called at startup)
616 if (!safeToReplace) {
617 // write semaphore file that we will pick up when we next launch
618 char *semPathBuf = NULL;
619 asprintf(&semPathBuf, "%s/%s", kSecRevocationBasePath, kSecRevocationDbReplaceFile);
620 if (semPathBuf) {
621 struct stat sb;
622 int fd = open(semPathBuf, O_WRONLY | O_CREAT, DEFFILEMODE);
623 if (fd == -1 || fstat(fd, &sb) || close(fd)) {
624 secnotice("validupdate", "unable to write %s", semPathBuf);
625 }
626 free(semPathBuf);
627 }
628 // exit as gracefully as possible so we can replace the database
629 secnotice("validupdate", "process exiting to replace db file");
630 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 3ull*NSEC_PER_SEC), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
631 xpc_transaction_exit_clean();
632 });
633 goto updateExit;
634 }
635
636 // try to copy uncompressed database asset, if available
637 const char *validDbPathBuf = SecOTAPKIGetValidDatabaseSnapshot(otapkiRef);
638 if (validDbPathBuf) {
639 WithPathInRevocationInfoDirectory(CFSTR(kSecRevocationDbFileName), ^(const char *path) {
640 secdebug("validupdate", "will copy data from \"%s\"", validDbPathBuf);
641 copyfile_state_t state = copyfile_state_alloc();
642 int retval = copyfile(validDbPathBuf, path, state, COPYFILE_DATA);
643 copyfile_state_free(state);
644 if (retval < 0) {
645 secnotice("validupdate", "copyfile error %d", retval);
646 } else {
647 result = true;
648 }
649 });
650 }
651 if (result) {
652 goto updateExit;
653 }
654
655 // see if compressed database asset is available
656 if (validDbPathBuf) {
657 char *validDbCmpPathBuf = NULL;
658 asprintf(&validDbCmpPathBuf, "%s%s", validDbPathBuf, ".gz");
659 if (validDbCmpPathBuf) {
660 secdebug("validupdate", "will read data from \"%s\"", validDbCmpPathBuf);
661 if ((rtn = readValidFile(validDbCmpPathBuf, &data)) != 0) {
662 unmapData(data);
663 data = NULL;
664 secnotice("validupdate", "readValidFile error %d", rtn);
665 }
666 free(validDbCmpPathBuf);
667 }
668 }
669 result = SecValidDatabaseFromCompressed(data);
670 if (result) {
671 goto updateExit;
672 }
673
674 // unable to use database asset; try update asset
675 const char *validUpdatePathBuf = SecOTAPKIGetValidUpdateSnapshot(otapkiRef);
676 if (validUpdatePathBuf) {
677 secdebug("validupdate", "will read data from \"%s\"", validUpdatePathBuf);
678 if ((rtn = readValidFile(validUpdatePathBuf, &data)) != 0) {
679 unmapData(data);
680 data = NULL;
681 secnotice("validupdate", "readValidFile error %d", rtn);
682 }
683 }
684 result = SecValidUpdateFromCompressed(data);
685
686 updateExit:
687 CFReleaseNull(otapkiRef);
688 if (result) {
689 sNumLocalUpdates++;
690 SecRevocationDbSetUpdateSource(server);
691 gLastVersion = SecRevocationDbGetVersion();
692 gUpdateStarted = 0;
693 secdebug("validupdate", "local update to g%ld/v%ld complete at %f",
694 (long)SecRevocationDbGetUpdateFormat(), (long)gLastVersion,
695 (double)CFAbsoluteTimeGetCurrent());
696 } else {
697 sNumLocalUpdates = 0; // reset counter
698 }
699 return result;
700 }
701
702 static bool SecValidUpdateSchedule(bool updateEnabled, CFStringRef server, CFIndex version) {
703 /* Check if we have a later version available locally */
704 if (SecValidUpdateSatisfiedLocally(server, version, false)) {
705 return true;
706 }
707
708 /* If update not permitted return */
709 if (!updateEnabled) {
710 return false;
711 }
712
713 #if !TARGET_OS_BRIDGE
714 /* Schedule as a maintenance task */
715 return SecValidUpdateRequest(SecRevocationDbGetUpdateQueue(), server, version);
716 #else
717 return false;
718 #endif
719 }
720
721 void SecRevocationDbInitialize() {
722 if (!isDbOwner()) { return; }
723 __block bool initializeDb = false;
724
725 /* create base path if it doesn't exist */
726 (void)mkpath_np(kSecRevocationBasePath, 0755);
727
728 /* check semaphore file */
729 WithPathInRevocationInfoDirectory(CFSTR(kSecRevocationDbReplaceFile), ^(const char *path) {
730 struct stat sb;
731 if (stat(path, &sb) == 0) {
732 initializeDb = true; /* file was found, so we will replace the database */
733 if (remove(path) == -1) {
734 int error = errno;
735 secnotice("validupdate", "remove (%s): %s", path, strerror(error));
736 }
737 }
738 });
739
740 /* check database */
741 WithPathInRevocationInfoDirectory(CFSTR(kSecRevocationDbFileName), ^(const char *path) {
742 if (initializeDb) {
743 /* remove old database file(s) */
744 (void)removeFileWithSuffix(path, "");
745 (void)removeFileWithSuffix(path, "-journal");
746 (void)removeFileWithSuffix(path, "-shm");
747 (void)removeFileWithSuffix(path, "-wal");
748 }
749 else {
750 struct stat sb;
751 if (stat(path, &sb) == -1) {
752 initializeDb = true; /* file not found, so we will create the database */
753 }
754 }
755 });
756
757 if (!initializeDb) {
758 return; /* database exists and doesn't need replacing */
759 }
760
761 /* initialize database from local asset */
762 CFTypeRef value = (CFStringRef)CFPreferencesCopyValue(kUpdateServerKey, kSecPrefsDomain, kCFPreferencesAnyUser, kCFPreferencesCurrentHost);
763 CFStringRef server = (isString(value)) ? (CFStringRef)value : (CFStringRef)kValidUpdateServer;
764 CFIndex version = 0;
765 secnotice("validupdate", "initializing database");
766 if (!SecValidUpdateSatisfiedLocally(server, version, true)) {
767 #if !TARGET_OS_BRIDGE
768 /* Schedule full update as a maintenance task */
769 (void)SecValidUpdateRequest(SecRevocationDbGetUpdateQueue(), server, version);
770 #endif
771 }
772 CFReleaseSafe(value);
773 }
774
775
776 // MARK: -
777 // MARK: SecValidInfoRef
778
779 /* ======================================================================
780 SecValidInfoRef
781 ======================================================================
782 */
783
784 static SecValidInfoRef SecValidInfoCreate(SecValidInfoFormat format,
785 CFOptionFlags flags,
786 bool isOnList,
787 CFDataRef certHash,
788 CFDataRef issuerHash,
789 CFDataRef anchorHash) {
790 SecValidInfoRef validInfo;
791 validInfo = (SecValidInfoRef)calloc(1, sizeof(struct __SecValidInfo));
792 if (!validInfo) { return NULL; }
793
794 CFRetainSafe(certHash);
795 CFRetainSafe(issuerHash);
796 validInfo->format = format;
797 validInfo->certHash = certHash;
798 validInfo->issuerHash = issuerHash;
799 validInfo->anchorHash = anchorHash;
800 validInfo->isOnList = isOnList;
801 validInfo->valid = (flags & kSecValidInfoAllowlist);
802 validInfo->complete = (flags & kSecValidInfoComplete);
803 validInfo->checkOCSP = (flags & kSecValidInfoCheckOCSP);
804 validInfo->knownOnly = (flags & kSecValidInfoKnownOnly);
805 validInfo->requireCT = (flags & kSecValidInfoRequireCT);
806 validInfo->noCACheck = (flags & kSecValidInfoNoCACheck);
807
808 return validInfo;
809 }
810
811 void SecValidInfoRelease(SecValidInfoRef validInfo) {
812 if (validInfo) {
813 CFReleaseSafe(validInfo->certHash);
814 CFReleaseSafe(validInfo->issuerHash);
815 CFReleaseSafe(validInfo->anchorHash);
816 free(validInfo);
817 }
818 }
819
820 void SecValidInfoSetAnchor(SecValidInfoRef validInfo, SecCertificateRef anchor) {
821 if (!validInfo) {
822 return;
823 }
824 CFDataRef anchorHash = NULL;
825 if (anchor) {
826 anchorHash = SecCertificateCopySHA256Digest(anchor);
827
828 /* clear no-ca flag for anchors where we want OCSP checked [32523118] */
829 if (SecIsAppleTrustAnchor(anchor, 0)) {
830 validInfo->noCACheck = false;
831 }
832 }
833 CFReleaseNull(validInfo->anchorHash);
834 validInfo->anchorHash = anchorHash;
835 }
836
837
838 // MARK: -
839 // MARK: SecRevocationDb
840
841 /* ======================================================================
842 SecRevocationDb
843 ======================================================================
844 */
845
846 /* SecRevocationDbCheckNextUpdate returns true if we dispatched an
847 update request, otherwise false.
848 */
849 static bool _SecRevocationDbCheckNextUpdate(void) {
850 // are we the db owner instance?
851 if (!isDbOwner()) {
852 return false;
853 }
854 CFTypeRef value = NULL;
855
856 // is it time to check?
857 CFAbsoluteTime now = CFAbsoluteTimeGetCurrent();
858 CFAbsoluteTime minNextUpdate = now + gUpdateInterval;
859 gUpdateStarted = now;
860
861 if (0 == gNextUpdate) {
862 // first time we're called, check if we have a saved nextUpdate value
863 gNextUpdate = SecRevocationDbGetNextUpdateTime();
864 minNextUpdate = now;
865 if (gNextUpdate < minNextUpdate) {
866 gNextUpdate = minNextUpdate;
867 }
868 // allow pref to override update interval, if it exists
869 CFIndex interval = -1;
870 value = (CFNumberRef)CFPreferencesCopyValue(kUpdateIntervalKey, kSecPrefsDomain, kCFPreferencesAnyUser, kCFPreferencesCurrentHost);
871 if (isNumber(value)) {
872 if (CFNumberGetValue((CFNumberRef)value, kCFNumberCFIndexType, &interval)) {
873 if (interval < kSecMinUpdateInterval) {
874 interval = kSecMinUpdateInterval;
875 } else if (interval > kSecMaxUpdateInterval) {
876 interval = kSecMaxUpdateInterval;
877 }
878 }
879 }
880 CFReleaseNull(value);
881 gUpdateInterval = kSecStdUpdateInterval;
882 if (interval > 0) {
883 gUpdateInterval = interval;
884 }
885 // pin next update time to the preferred update interval
886 if (gNextUpdate > (gUpdateStarted + gUpdateInterval)) {
887 gNextUpdate = gUpdateStarted + gUpdateInterval;
888 }
889 secdebug("validupdate", "next update at %f (in %f seconds)",
890 (double)gUpdateStarted, (double)gNextUpdate-gUpdateStarted);
891 }
892 if (gNextUpdate > now) {
893 gUpdateStarted = 0;
894 return false;
895 }
896 secnotice("validupdate", "starting update");
897
898 // set minimum next update time here in case we can't get an update
899 gNextUpdate = minNextUpdate;
900
901 // determine which server to query
902 CFStringRef server;
903 value = (CFStringRef)CFPreferencesCopyValue(kUpdateServerKey, kSecPrefsDomain, kCFPreferencesAnyUser, kCFPreferencesCurrentHost);
904 if (isString(value)) {
905 server = (CFStringRef) CFRetain(value);
906 } else {
907 server = (CFStringRef) CFRetain(kValidUpdateServer);
908 }
909 CFReleaseNull(value);
910
911 // determine version of our current database
912 CFIndex version = SecRevocationDbGetVersion();
913 secdebug("validupdate", "got version %ld from db", (long)version);
914 if (version <= 0) {
915 if (gLastVersion > 0) {
916 secdebug("validupdate", "error getting version; using last good version: %ld", (long)gLastVersion);
917 }
918 version = gLastVersion;
919 }
920
921 // determine source of our current database
922 // (if this ever changes, we will need to reload the db)
923 CFStringRef db_source = SecRevocationDbCopyUpdateSource();
924 if (!db_source) {
925 db_source = (CFStringRef) CFRetain(kValidUpdateServer);
926 }
927
928 // determine whether we need to recreate the database
929 CFIndex db_version = SecRevocationDbGetSchemaVersion();
930 CFIndex db_format = SecRevocationDbGetUpdateFormat();
931 if (db_version < kSecRevocationDbSchemaVersion ||
932 db_format < kSecRevocationDbUpdateFormat ||
933 kCFCompareEqualTo != CFStringCompare(server, db_source, kCFCompareCaseInsensitive)) {
934 /* we need to fully rebuild the db contents. */
935 SecRevocationDbRemoveAllEntries();
936 version = gLastVersion = 0;
937 }
938
939 // determine whether update fetching is enabled
940 #if (__MAC_OS_X_VERSION_MIN_REQUIRED >= 101300 || __IPHONE_OS_VERSION_MIN_REQUIRED >= 110000)
941 bool updateEnabled = true; // macOS 10.13 or iOS 11.0
942 #else
943 bool updateEnabled = false;
944 #endif
945 value = (CFBooleanRef)CFPreferencesCopyValue(kUpdateEnabledKey, kSecPrefsDomain, kCFPreferencesAnyUser, kCFPreferencesCurrentHost);
946 if (isBoolean(value)) {
947 updateEnabled = CFBooleanGetValue((CFBooleanRef)value);
948 }
949 CFReleaseNull(value);
950
951 // Schedule maintenance work
952 bool result = SecValidUpdateSchedule(updateEnabled, server, version);
953 CFReleaseNull(server);
954 CFReleaseNull(db_source);
955 return result;
956 }
957
958 void SecRevocationDbCheckNextUpdate(void) {
959 static dispatch_once_t once;
960 static sec_action_t action;
961
962 dispatch_once(&once, ^{
963 dispatch_queue_t update_queue = SecRevocationDbGetUpdateQueue();
964 action = sec_action_create_with_queue(update_queue, "update_check", kSecMinUpdateInterval);
965 sec_action_set_handler(action, ^{
966 (void)_SecRevocationDbCheckNextUpdate();
967 });
968 });
969 sec_action_perform(action);
970 }
971
972 /* This function verifies an update, in this format:
973 1) unsigned 32-bit network-byte-order length of binary plist
974 2) binary plist data
975 3) unsigned 32-bit network-byte-order length of CMS message
976 4) CMS message (containing certificates and signature over binary plist)
977
978 The length argument is the total size of the packed update data.
979 */
980 bool SecRevocationDbVerifyUpdate(void *update, CFIndex length) {
981 if (!update || length <= (CFIndex)sizeof(uint32_t)) {
982 return false;
983 }
984 uint32_t plistLength = OSSwapInt32(*((uint32_t *)update));
985 if ((plistLength + (CFIndex)(sizeof(uint32_t)*2)) > (uint64_t) length) {
986 secdebug("validupdate", "ERROR: reported plist length (%lu)+%lu exceeds total length (%lu)\n",
987 (unsigned long)plistLength, (unsigned long)sizeof(uint32_t)*2, (unsigned long)length);
988 return false;
989 }
990 uint8_t *plistData = (uint8_t *)update + sizeof(uint32_t);
991 uint8_t *sigData = (uint8_t *)plistData + plistLength;
992 uint32_t sigLength = OSSwapInt32(*((uint32_t *)sigData));
993 sigData += sizeof(uint32_t);
994 if ((plistLength + sigLength + (CFIndex)(sizeof(uint32_t) * 2)) != (uint64_t) length) {
995 secdebug("validupdate", "ERROR: reported lengths do not add up to total length\n");
996 return false;
997 }
998
999 OSStatus status = 0;
1000 CMSSignerStatus signerStatus;
1001 CMSDecoderRef cms = NULL;
1002 SecPolicyRef policy = NULL;
1003 SecTrustRef trust = NULL;
1004 CFDataRef content = NULL;
1005
1006 if ((content = CFDataCreateWithBytesNoCopy(kCFAllocatorDefault,
1007 (const UInt8 *)plistData, (CFIndex)plistLength, kCFAllocatorNull)) == NULL) {
1008 secdebug("validupdate", "CFDataCreateWithBytesNoCopy failed (%ld bytes)\n", (long)plistLength);
1009 return false;
1010 }
1011
1012 if ((status = CMSDecoderCreate(&cms)) != errSecSuccess) {
1013 secdebug("validupdate", "CMSDecoderCreate failed with error %d\n", (int)status);
1014 goto verifyExit;
1015 }
1016 if ((status = CMSDecoderUpdateMessage(cms, sigData, sigLength)) != errSecSuccess) {
1017 secdebug("validupdate", "CMSDecoderUpdateMessage failed with error %d\n", (int)status);
1018 goto verifyExit;
1019 }
1020 if ((status = CMSDecoderSetDetachedContent(cms, content)) != errSecSuccess) {
1021 secdebug("validupdate", "CMSDecoderSetDetachedContent failed with error %d\n", (int)status);
1022 goto verifyExit;
1023 }
1024 if ((status = CMSDecoderFinalizeMessage(cms)) != errSecSuccess) {
1025 secdebug("validupdate", "CMSDecoderFinalizeMessage failed with error %d\n", (int)status);
1026 goto verifyExit;
1027 }
1028
1029 policy = SecPolicyCreateApplePinned(CFSTR("ValidUpdate"), // kSecPolicyNameAppleValidUpdate
1030 CFSTR("1.2.840.113635.100.6.2.10"), // System Integration 2 Intermediate Certificate
1031 CFSTR("1.2.840.113635.100.6.51")); // Valid update signing OID
1032
1033 // Check that the first signer actually signed this message.
1034 if ((status = CMSDecoderCopySignerStatus(cms, 0, policy,
1035 false, &signerStatus, &trust, NULL)) != errSecSuccess) {
1036 secdebug("validupdate", "CMSDecoderCopySignerStatus failed with error %d\n", (int)status);
1037 goto verifyExit;
1038 }
1039 // Make sure the signature verifies against the detached content
1040 if (signerStatus != kCMSSignerValid) {
1041 secdebug("validupdate", "ERROR: signature did not verify (signer status %d)\n", (int)signerStatus);
1042 status = errSecInvalidSignature;
1043 goto verifyExit;
1044 }
1045 // Make sure the signing certificate is valid for the specified policy
1046 SecTrustResultType trustResult = kSecTrustResultInvalid;
1047 status = SecTrustEvaluate(trust, &trustResult);
1048 if (status != errSecSuccess) {
1049 secdebug("validupdate", "SecTrustEvaluate failed with error %d (trust=%p)\n", (int)status, (void *)trust);
1050 } else if (!(trustResult == kSecTrustResultUnspecified || trustResult == kSecTrustResultProceed)) {
1051 secdebug("validupdate", "SecTrustEvaluate failed with trust result %d\n", (int)trustResult);
1052 status = errSecVerificationFailure;
1053 goto verifyExit;
1054 }
1055
1056 verifyExit:
1057 CFReleaseSafe(content);
1058 CFReleaseSafe(trust);
1059 CFReleaseSafe(policy);
1060 CFReleaseSafe(cms);
1061
1062 return (status == errSecSuccess);
1063 }
1064
1065 CFAbsoluteTime SecRevocationDbComputeNextUpdateTime(CFIndex updateInterval) {
1066 CFIndex interval = updateInterval;
1067 // try to use interval preference if it exists
1068 CFTypeRef value = (CFNumberRef)CFPreferencesCopyValue(kUpdateIntervalKey, kSecPrefsDomain, kCFPreferencesAnyUser, kCFPreferencesCurrentHost);
1069 if (isNumber(value)) {
1070 CFNumberGetValue((CFNumberRef)value, kCFNumberCFIndexType, &interval);
1071 }
1072 CFReleaseNull(value);
1073
1074 if (interval <= 0) {
1075 interval = kSecStdUpdateInterval;
1076 }
1077
1078 // sanity check
1079 if (interval < kSecMinUpdateInterval) {
1080 interval = kSecMinUpdateInterval;
1081 } else if (interval > kSecMaxUpdateInterval) {
1082 interval = kSecMaxUpdateInterval;
1083 }
1084
1085 // compute randomization factor, between 0 and 50% of the interval
1086 CFIndex fuzz = arc4random() % (long)(interval/2.0);
1087 CFAbsoluteTime nextUpdate = CFAbsoluteTimeGetCurrent() + interval + fuzz;
1088 secdebug("validupdate", "next update in %ld seconds", (long)(interval + fuzz));
1089 return nextUpdate;
1090 }
1091
1092 void SecRevocationDbComputeAndSetNextUpdateTime(void) {
1093 gNextUpdate = SecRevocationDbComputeNextUpdateTime(0);
1094 SecRevocationDbSetNextUpdateTime(gNextUpdate);
1095 gUpdateStarted = 0; /* no update is currently in progress */
1096 }
1097
1098 CFIndex SecRevocationDbIngestUpdate(CFDictionaryRef update, CFIndex chunkVersion) {
1099 CFIndex version = 0;
1100 if (!update) {
1101 return version;
1102 }
1103 CFTypeRef value = (CFNumberRef)CFDictionaryGetValue(update, CFSTR("version"));
1104 if (isNumber(value)) {
1105 if (!CFNumberGetValue((CFNumberRef)value, kCFNumberCFIndexType, &version)) {
1106 version = 0;
1107 }
1108 }
1109 if (version == 0) {
1110 // only the first chunk will have a version, so the second and
1111 // subsequent chunks will need to pass it in chunkVersion.
1112 version = chunkVersion;
1113 }
1114 CFIndex curVersion = SecRevocationDbGetVersion();
1115 if (version > curVersion || chunkVersion > 0) {
1116 SecRevocationDbApplyUpdate(update, version);
1117 } else {
1118 secdebug("validupdate", "we have v%ld, skipping update to v%ld",
1119 (long)curVersion, (long)version);
1120 version = -1; // invalid, so we know to skip subsequent chunks
1121 }
1122 return version;
1123 }
1124
1125
1126 /* Database schema */
1127
1128 /* admin table holds these key-value (or key-ival) pairs:
1129 'version' (integer) // version of database content
1130 'check_again' (double) // CFAbsoluteTime of next check (optional; this value is currently stored in prefs)
1131 'db_version' (integer) // version of database schema
1132 'db_hash' (blob) // SHA-256 database hash
1133 --> entries in admin table are unique by text key
1134
1135 issuers table holds map of issuing CA hashes to group identifiers:
1136 groupid (integer) // associated group identifier in group ID table
1137 issuer_hash (blob) // SHA-256 hash of issuer certificate (primary key)
1138 --> entries in issuers table are unique by issuer_hash;
1139 multiple issuer entries may have the same groupid!
1140
1141 groups table holds records with these attributes:
1142 groupid (integer) // ordinal ID associated with this group entry
1143 flags (integer) // a bitmask of the following values:
1144 kSecValidInfoComplete (0x00000001) set if we have all revocation info for this issuer group
1145 kSecValidInfoCheckOCSP (0x00000002) set if must check ocsp for certs from this issuer group
1146 kSecValidInfoKnownOnly (0x00000004) set if any CA from this issuer group must be in database
1147 kSecValidInfoRequireCT (0x00000008) set if all certs from this issuer group must have SCTs
1148 kSecValidInfoAllowlist (0x00000010) set if this entry describes valid certs (i.e. is allowed)
1149 kSecValidInfoNoCACheck (0x00000020) set if this entry does not require an OCSP check to accept
1150 format (integer) // an integer describing format of entries:
1151 kSecValidInfoFormatUnknown (0) unknown format
1152 kSecValidInfoFormatSerial (1) serial number, not greater than 20 bytes in length
1153 kSecValidInfoFormatSHA256 (2) SHA-256 hash, 32 bytes in length
1154 kSecValidInfoFormatNto1 (3) filter data blob of arbitrary length
1155 data (blob) // Bloom filter data if format is 'nto1', otherwise NULL
1156 --> entries in groups table are unique by groupid
1157
1158 serials table holds serial number blobs with these attributes:
1159 rowid (integer) // ordinal ID associated with this serial number entry
1160 groupid (integer) // identifier for issuer group in the groups table
1161 serial (blob) // serial number
1162 --> entries in serials table are unique by serial and groupid
1163
1164 hashes table holds SHA-256 hashes of certificates with these attributes:
1165 rowid (integer) // ordinal ID associated with this sha256 hash entry
1166 groupid (integer) // identifier for issuer group in the groups table
1167 sha256 (blob) // SHA-256 hash of subject certificate
1168 --> entries in hashes table are unique by sha256 and groupid
1169 */
1170 #define createTablesSQL CFSTR("CREATE TABLE admin(" \
1171 "key TEXT PRIMARY KEY NOT NULL," \
1172 "ival INTEGER NOT NULL," \
1173 "value BLOB" \
1174 ");" \
1175 "CREATE TABLE issuers(" \
1176 "groupid INTEGER NOT NULL," \
1177 "issuer_hash BLOB PRIMARY KEY NOT NULL" \
1178 ");" \
1179 "CREATE INDEX issuer_idx ON issuers(issuer_hash);" \
1180 "CREATE TABLE groups(" \
1181 "groupid INTEGER PRIMARY KEY AUTOINCREMENT," \
1182 "flags INTEGER," \
1183 "format INTEGER," \
1184 "data BLOB" \
1185 ");" \
1186 "CREATE TABLE serials(" \
1187 "rowid INTEGER PRIMARY KEY AUTOINCREMENT," \
1188 "groupid INTEGER NOT NULL," \
1189 "serial BLOB NOT NULL," \
1190 "UNIQUE(groupid,serial)" \
1191 ");" \
1192 "CREATE TABLE hashes(" \
1193 "rowid INTEGER PRIMARY KEY AUTOINCREMENT," \
1194 "groupid INTEGER NOT NULL," \
1195 "sha256 BLOB NOT NULL," \
1196 "UNIQUE(groupid,sha256)" \
1197 ");" \
1198 "CREATE TRIGGER group_del BEFORE DELETE ON groups FOR EACH ROW " \
1199 "BEGIN " \
1200 "DELETE FROM serials WHERE groupid=OLD.groupid; " \
1201 "DELETE FROM hashes WHERE groupid=OLD.groupid; " \
1202 "DELETE FROM issuers WHERE groupid=OLD.groupid; " \
1203 "END;")
1204
1205 #define selectGroupIdSQL CFSTR("SELECT DISTINCT groupid " \
1206 "FROM issuers WHERE issuer_hash=?")
1207 #define selectVersionSQL CFSTR("SELECT ival FROM admin " \
1208 "WHERE key='version'")
1209 #define selectDbVersionSQL CFSTR("SELECT ival FROM admin " \
1210 "WHERE key='db_version'")
1211 #define selectDbFormatSQL CFSTR("SELECT ival FROM admin " \
1212 "WHERE key='db_format'")
1213 #define selectDbHashSQL CFSTR("SELECT value FROM admin " \
1214 "WHERE key='db_hash'")
1215 #define selectDbSourceSQL CFSTR("SELECT value FROM admin " \
1216 "WHERE key='db_source'")
1217 #define selectNextUpdateSQL CFSTR("SELECT value FROM admin " \
1218 "WHERE key='check_again'")
1219 #define selectGroupRecordSQL CFSTR("SELECT flags,format,data FROM " \
1220 "groups WHERE groupid=?")
1221 #define selectSerialRecordSQL CFSTR("SELECT rowid FROM serials " \
1222 "WHERE groupid=? AND serial=?")
1223 #define selectHashRecordSQL CFSTR("SELECT rowid FROM hashes " \
1224 "WHERE groupid=? AND sha256=?")
1225 #define insertAdminRecordSQL CFSTR("INSERT OR REPLACE INTO admin " \
1226 "(key,ival,value) VALUES (?,?,?)")
1227 #define insertIssuerRecordSQL CFSTR("INSERT OR REPLACE INTO issuers " \
1228 "(groupid,issuer_hash) VALUES (?,?)")
1229 #define insertGroupRecordSQL CFSTR("INSERT OR REPLACE INTO groups " \
1230 "(groupid,flags,format,data) VALUES (?,?,?,?)")
1231 #define insertSerialRecordSQL CFSTR("INSERT OR REPLACE INTO serials " \
1232 "(groupid,serial) VALUES (?,?)")
1233 #define insertSha256RecordSQL CFSTR("INSERT OR REPLACE INTO hashes " \
1234 "(groupid,sha256) VALUES (?,?)")
1235 #define deleteGroupRecordSQL CFSTR("DELETE FROM groups WHERE groupid=?")
1236
1237 #define deleteAllEntriesSQL CFSTR("DELETE from hashes; " \
1238 "DELETE from serials; DELETE from issuers; DELETE from groups; " \
1239 "DELETE from admin; DELETE from sqlite_sequence")
1240 #define deleteTablesSQL CFSTR("DROP TABLE hashes; " \
1241 "DROP TABLE serials; DROP TABLE issuers; DROP TABLE groups; " \
1242 "DROP TABLE admin; DELETE from sqlite_sequence")
1243
1244 /* Database management */
1245
1246 static SecDbRef SecRevocationDbCreate(CFStringRef path) {
1247 /* only the db owner should open a read-write connection. */
1248 bool readWrite = isDbOwner();
1249 mode_t mode = 0644;
1250
1251 SecDbRef result = SecDbCreateWithOptions(path, mode, readWrite, false, false, ^bool (SecDbRef db, SecDbConnectionRef dbconn, bool didCreate, bool *callMeAgainForNextConnection, CFErrorRef *error) {
1252 __block bool ok = true;
1253 CFErrorRef localError = NULL;
1254 if (ok && !SecDbWithSQL(dbconn, selectGroupIdSQL, &localError, NULL) && CFErrorGetCode(localError) == SQLITE_ERROR) {
1255 /* SecDbWithSQL returns SQLITE_ERROR if the table we are preparing the above statement for doesn't exist. */
1256
1257 /* Create all database tables, indexes, and triggers. */
1258 ok &= SecDbTransaction(dbconn, kSecDbExclusiveTransactionType, error, ^(bool *commit) {
1259 ok = SecDbExec(dbconn, createTablesSQL, error);
1260 *commit = ok;
1261 });
1262 }
1263 CFReleaseSafe(localError);
1264 if (!ok)
1265 secerror("%s failed: %@", didCreate ? "Create" : "Open", error ? *error : NULL);
1266 return ok;
1267 });
1268
1269 return result;
1270 }
1271
1272 typedef struct __SecRevocationDb *SecRevocationDbRef;
1273 struct __SecRevocationDb {
1274 SecDbRef db;
1275 dispatch_queue_t update_queue;
1276 bool updateInProgress;
1277 bool unsupportedVersion;
1278 };
1279
1280 static dispatch_once_t kSecRevocationDbOnce;
1281 static SecRevocationDbRef kSecRevocationDb = NULL;
1282
1283 static SecRevocationDbRef SecRevocationDbInit(CFStringRef db_name) {
1284 SecRevocationDbRef rdb;
1285 dispatch_queue_attr_t attr;
1286
1287 require(rdb = (SecRevocationDbRef)malloc(sizeof(struct __SecRevocationDb)), errOut);
1288 rdb->db = NULL;
1289 rdb->update_queue = NULL;
1290 rdb->updateInProgress = false;
1291 rdb->unsupportedVersion = false;
1292
1293 require(rdb->db = SecRevocationDbCreate(db_name), errOut);
1294 attr = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_BACKGROUND, 0);
1295 require(rdb->update_queue = dispatch_queue_create(NULL, attr), errOut);
1296
1297 return rdb;
1298
1299 errOut:
1300 secdebug("validupdate", "Failed to create db at \"%@\"", db_name);
1301 if (rdb) {
1302 if (rdb->update_queue) {
1303 dispatch_release(rdb->update_queue);
1304 }
1305 CFReleaseSafe(rdb->db);
1306 free(rdb);
1307 }
1308 return NULL;
1309 }
1310
1311 static CFStringRef SecRevocationDbCopyPath(void) {
1312 CFURLRef revDbURL = NULL;
1313 CFStringRef revInfoRelPath = NULL;
1314 if ((revInfoRelPath = CFStringCreateWithFormat(NULL, NULL, CFSTR("%s"), kSecRevocationDbFileName)) != NULL) {
1315 revDbURL = SecCopyURLForFileInRevocationInfoDirectory(revInfoRelPath);
1316 }
1317 CFReleaseSafe(revInfoRelPath);
1318
1319 CFStringRef revDbPath = NULL;
1320 if (revDbURL) {
1321 revDbPath = CFURLCopyFileSystemPath(revDbURL, kCFURLPOSIXPathStyle);
1322 CFRelease(revDbURL);
1323 }
1324 return revDbPath;
1325 }
1326
1327 static void SecRevocationDbWith(void(^dbJob)(SecRevocationDbRef db)) {
1328 dispatch_once(&kSecRevocationDbOnce, ^{
1329 CFStringRef dbPath = SecRevocationDbCopyPath();
1330 if (dbPath) {
1331 kSecRevocationDb = SecRevocationDbInit(dbPath);
1332 CFRelease(dbPath);
1333 }
1334 });
1335 // Do pre job run work here (cancel idle timers etc.)
1336 if (kSecRevocationDb->updateInProgress) {
1337 return; // this would block since SecDb has an exclusive transaction lock
1338 }
1339 dbJob(kSecRevocationDb);
1340 // Do post job run work here (gc timer, etc.)
1341 }
1342
1343 static int64_t _SecRevocationDbGetVersion(SecRevocationDbRef rdb, CFErrorRef *error) {
1344 /* look up version entry in admin table; returns -1 on error */
1345 __block int64_t version = -1;
1346 __block bool ok = true;
1347 __block CFErrorRef localError = NULL;
1348
1349 ok &= SecDbPerformRead(rdb->db, &localError, ^(SecDbConnectionRef dbconn) {
1350 if (ok) ok &= SecDbWithSQL(dbconn, selectVersionSQL, &localError, ^bool(sqlite3_stmt *selectVersion) {
1351 ok = SecDbStep(dbconn, selectVersion, &localError, NULL);
1352 version = sqlite3_column_int64(selectVersion, 0);
1353 return ok;
1354 });
1355 });
1356 (void) CFErrorPropagate(localError, error);
1357 return version;
1358 }
1359
1360 static void _SecRevocationDbSetVersion(SecRevocationDbRef rdb, CFIndex version){
1361 secdebug("validupdate", "setting version to %ld", (long)version);
1362
1363 __block CFErrorRef localError = NULL;
1364 __block bool ok = true;
1365 ok &= SecDbPerformWrite(rdb->db, &localError, ^(SecDbConnectionRef dbconn) {
1366 ok &= SecDbTransaction(dbconn, kSecDbExclusiveTransactionType, &localError, ^(bool *commit) {
1367 if (ok) ok = SecDbWithSQL(dbconn, insertAdminRecordSQL, &localError, ^bool(sqlite3_stmt *insertVersion) {
1368 if (ok) {
1369 const char *versionKey = "version";
1370 ok = SecDbBindText(insertVersion, 1, versionKey, strlen(versionKey),
1371 SQLITE_TRANSIENT, &localError);
1372 }
1373 if (ok) {
1374 ok = SecDbBindInt64(insertVersion, 2,
1375 (sqlite3_int64)version, &localError);
1376 }
1377 if (ok) {
1378 ok = SecDbStep(dbconn, insertVersion, &localError, NULL);
1379 }
1380 return ok;
1381 });
1382 });
1383 });
1384 if (!ok) {
1385 secerror("_SecRevocationDbSetVersion failed: %@", localError);
1386 }
1387 CFReleaseSafe(localError);
1388 }
1389
1390 static int64_t _SecRevocationDbGetSchemaVersion(SecRevocationDbRef rdb, CFErrorRef *error) {
1391 /* look up db_version entry in admin table; returns -1 on error */
1392 __block int64_t db_version = -1;
1393 __block bool ok = true;
1394 __block CFErrorRef localError = NULL;
1395
1396 ok &= SecDbPerformRead(rdb->db, &localError, ^(SecDbConnectionRef dbconn) {
1397 if (ok) ok &= SecDbWithSQL(dbconn, selectDbVersionSQL, &localError, ^bool(sqlite3_stmt *selectDbVersion) {
1398 ok = SecDbStep(dbconn, selectDbVersion, &localError, NULL);
1399 db_version = sqlite3_column_int64(selectDbVersion, 0);
1400 return ok;
1401 });
1402 });
1403 (void) CFErrorPropagate(localError, error);
1404 return db_version;
1405 }
1406
1407 static void _SecRevocationDbSetSchemaVersion(SecRevocationDbRef rdb, CFIndex dbversion) {
1408 secdebug("validupdate", "setting db_version to %ld", (long)dbversion);
1409
1410 __block CFErrorRef localError = NULL;
1411 __block bool ok = true;
1412 ok &= SecDbPerformWrite(rdb->db, &localError, ^(SecDbConnectionRef dbconn) {
1413 ok &= SecDbTransaction(dbconn, kSecDbExclusiveTransactionType, &localError, ^(bool *commit) {
1414 if (ok) ok = SecDbWithSQL(dbconn, insertAdminRecordSQL, &localError, ^bool(sqlite3_stmt *insertDbVersion) {
1415 if (ok) {
1416 const char *dbVersionKey = "db_version";
1417 ok = SecDbBindText(insertDbVersion, 1, dbVersionKey, strlen(dbVersionKey),
1418 SQLITE_TRANSIENT, &localError);
1419 }
1420 if (ok) {
1421 ok = SecDbBindInt64(insertDbVersion, 2,
1422 (sqlite3_int64)dbversion, &localError);
1423 }
1424 if (ok) {
1425 ok = SecDbStep(dbconn, insertDbVersion, &localError, NULL);
1426 }
1427 return ok;
1428 });
1429 });
1430 });
1431 if (!ok) {
1432 secerror("_SecRevocationDbSetSchemaVersion failed: %@", localError);
1433 } else {
1434 rdb->unsupportedVersion = false;
1435 }
1436 CFReleaseSafe(localError);
1437 }
1438
1439 static int64_t _SecRevocationDbGetUpdateFormat(SecRevocationDbRef rdb, CFErrorRef *error) {
1440 /* look up db_format entry in admin table; returns -1 on error */
1441 __block int64_t db_format = -1;
1442 __block bool ok = true;
1443 __block CFErrorRef localError = NULL;
1444
1445 ok &= SecDbPerformRead(rdb->db, &localError, ^(SecDbConnectionRef dbconn) {
1446 if (ok) ok &= SecDbWithSQL(dbconn, selectDbFormatSQL, &localError, ^bool(sqlite3_stmt *selectDbFormat) {
1447 ok = SecDbStep(dbconn, selectDbFormat, &localError, NULL);
1448 db_format = sqlite3_column_int64(selectDbFormat, 0);
1449 return ok;
1450 });
1451 });
1452 (void) CFErrorPropagate(localError, error);
1453 return db_format;
1454 }
1455
1456 static void _SecRevocationDbSetUpdateFormat(SecRevocationDbRef rdb, CFIndex dbformat) {
1457 secdebug("validupdate", "setting db_format to %ld", (long)dbformat);
1458
1459 __block CFErrorRef localError = NULL;
1460 __block bool ok = true;
1461 ok &= SecDbPerformWrite(rdb->db, &localError, ^(SecDbConnectionRef dbconn) {
1462 ok &= SecDbTransaction(dbconn, kSecDbExclusiveTransactionType, &localError, ^(bool *commit) {
1463 if (ok) ok = SecDbWithSQL(dbconn, insertAdminRecordSQL, &localError, ^bool(sqlite3_stmt *insertDbFormat) {
1464 if (ok) {
1465 const char *dbFormatKey = "db_format";
1466 ok = SecDbBindText(insertDbFormat, 1, dbFormatKey, strlen(dbFormatKey),
1467 SQLITE_TRANSIENT, &localError);
1468 }
1469 if (ok) {
1470 ok = SecDbBindInt64(insertDbFormat, 2,
1471 (sqlite3_int64)dbformat, &localError);
1472 }
1473 if (ok) {
1474 ok = SecDbStep(dbconn, insertDbFormat, &localError, NULL);
1475 }
1476 return ok;
1477 });
1478 });
1479 });
1480 if (!ok) {
1481 secerror("_SecRevocationDbSetUpdateFormat failed: %@", localError);
1482 } else {
1483 rdb->unsupportedVersion = false;
1484 }
1485 CFReleaseSafe(localError);
1486 }
1487
1488 static CFStringRef _SecRevocationDbCopyUpdateSource(SecRevocationDbRef rdb, CFErrorRef *error) {
1489 /* look up db_source entry in admin table; returns NULL on error */
1490 __block CFStringRef updateSource = NULL;
1491 __block bool ok = true;
1492 __block CFErrorRef localError = NULL;
1493
1494 ok &= SecDbPerformRead(rdb->db, &localError, ^(SecDbConnectionRef dbconn) {
1495 if (ok) ok &= SecDbWithSQL(dbconn, selectDbSourceSQL, &localError, ^bool(sqlite3_stmt *selectDbSource) {
1496 ok = SecDbStep(dbconn, selectDbSource, &localError, NULL);
1497 const UInt8 *p = (const UInt8 *)sqlite3_column_blob(selectDbSource, 0);
1498 if (p != NULL) {
1499 CFIndex length = (CFIndex)sqlite3_column_bytes(selectDbSource, 0);
1500 if (length > 0) {
1501 updateSource = CFStringCreateWithBytes(kCFAllocatorDefault, p, length, kCFStringEncodingUTF8, false);
1502 }
1503 }
1504 return ok;
1505 });
1506 });
1507
1508 (void) CFErrorPropagate(localError, error);
1509 return updateSource;
1510 }
1511
1512 static void _SecRevocationDbSetUpdateSource(SecRevocationDbRef rdb, CFStringRef updateSource) {
1513 if (!updateSource) {
1514 secerror("_SecRevocationDbSetUpdateSource failed: %d", errSecParam);
1515 return;
1516 }
1517 __block char buffer[256];
1518 __block const char *updateSourceCStr = CFStringGetCStringPtr(updateSource, kCFStringEncodingUTF8);
1519 if (!updateSourceCStr) {
1520 if (CFStringGetCString(updateSource, buffer, 256, kCFStringEncodingUTF8)) {
1521 updateSourceCStr = buffer;
1522 }
1523 }
1524 if (!updateSourceCStr) {
1525 secerror("_SecRevocationDbSetUpdateSource failed: unable to get UTF-8 encoding");
1526 return;
1527 }
1528 secdebug("validupdate", "setting update source to \"%s\"", updateSourceCStr);
1529
1530 __block CFErrorRef localError = NULL;
1531 __block bool ok = true;
1532 ok &= SecDbPerformWrite(rdb->db, &localError, ^(SecDbConnectionRef dbconn) {
1533 ok &= SecDbTransaction(dbconn, kSecDbExclusiveTransactionType, &localError, ^(bool *commit) {
1534 if (ok) ok = SecDbWithSQL(dbconn, insertAdminRecordSQL, &localError, ^bool(sqlite3_stmt *insertRecord) {
1535 if (ok) {
1536 const char *dbSourceKey = "db_source";
1537 ok = SecDbBindText(insertRecord, 1, dbSourceKey, strlen(dbSourceKey),
1538 SQLITE_TRANSIENT, &localError);
1539 }
1540 if (ok) {
1541 ok = SecDbBindInt64(insertRecord, 2,
1542 (sqlite3_int64)0, &localError);
1543 }
1544 if (ok) {
1545 ok = SecDbBindBlob(insertRecord, 3,
1546 updateSourceCStr, strlen(updateSourceCStr),
1547 SQLITE_TRANSIENT, &localError);
1548 }
1549 if (ok) {
1550 ok = SecDbStep(dbconn, insertRecord, &localError, NULL);
1551 }
1552 return ok;
1553 });
1554 });
1555 });
1556 if (!ok) {
1557 secerror("_SecRevocationDbSetUpdateSource failed: %@", localError);
1558 }
1559 CFReleaseSafe(localError);
1560 }
1561
1562 static CFAbsoluteTime _SecRevocationDbGetNextUpdateTime(SecRevocationDbRef rdb, CFErrorRef *error) {
1563 /* look up check_again entry in admin table; returns 0 on error */
1564 __block CFAbsoluteTime nextUpdate = 0;
1565 __block bool ok = true;
1566 __block CFErrorRef localError = NULL;
1567
1568 ok &= SecDbPerformRead(rdb->db, &localError, ^(SecDbConnectionRef dbconn) {
1569 if (ok) ok &= SecDbWithSQL(dbconn, selectNextUpdateSQL, &localError, ^bool(sqlite3_stmt *selectNextUpdate) {
1570 ok = SecDbStep(dbconn, selectNextUpdate, &localError, NULL);
1571 CFAbsoluteTime *p = (CFAbsoluteTime *)sqlite3_column_blob(selectNextUpdate, 0);
1572 if (p != NULL) {
1573 if (sizeof(CFAbsoluteTime) == sqlite3_column_bytes(selectNextUpdate, 0)) {
1574 nextUpdate = *p;
1575 }
1576 }
1577 return ok;
1578 });
1579 });
1580
1581 (void) CFErrorPropagate(localError, error);
1582 return nextUpdate;
1583 }
1584
1585 static void _SecRevocationDbSetNextUpdateTime(SecRevocationDbRef rdb, CFAbsoluteTime nextUpdate){
1586 secdebug("validupdate", "setting next update to %f", (double)nextUpdate);
1587
1588 __block CFErrorRef localError = NULL;
1589 __block bool ok = true;
1590 ok &= SecDbPerformWrite(rdb->db, &localError, ^(SecDbConnectionRef dbconn) {
1591 ok &= SecDbTransaction(dbconn, kSecDbExclusiveTransactionType, &localError, ^(bool *commit) {
1592 if (ok) ok = SecDbWithSQL(dbconn, insertAdminRecordSQL, &localError, ^bool(sqlite3_stmt *insertRecord) {
1593 if (ok) {
1594 const char *nextUpdateKey = "check_again";
1595 ok = SecDbBindText(insertRecord, 1, nextUpdateKey, strlen(nextUpdateKey),
1596 SQLITE_TRANSIENT, &localError);
1597 }
1598 if (ok) {
1599 ok = SecDbBindInt64(insertRecord, 2,
1600 (sqlite3_int64)0, &localError);
1601 }
1602 if (ok) {
1603 ok = SecDbBindBlob(insertRecord, 3,
1604 &nextUpdate, sizeof(CFAbsoluteTime),
1605 SQLITE_TRANSIENT, &localError);
1606 }
1607 if (ok) {
1608 ok = SecDbStep(dbconn, insertRecord, &localError, NULL);
1609 }
1610 return ok;
1611 });
1612 });
1613 });
1614 if (!ok) {
1615 secerror("_SecRevocationDbSetNextUpdate failed: %@", localError);
1616 }
1617 CFReleaseSafe(localError);
1618 }
1619
1620 static bool _SecRevocationDbRemoveAllEntries(SecRevocationDbRef rdb) {
1621 /* clear out the contents of the database and start fresh */
1622 __block bool ok = true;
1623 __block CFErrorRef localError = NULL;
1624
1625 ok &= SecDbPerformWrite(rdb->db, &localError, ^(SecDbConnectionRef dbconn) {
1626 ok &= SecDbTransaction(dbconn, kSecDbExclusiveTransactionType, &localError, ^(bool *commit) {
1627 //ok &= SecDbWithSQL(dbconn, deleteAllEntriesSQL, &localError, ^bool(sqlite3_stmt *deleteAll) {
1628 // ok = SecDbStep(dbconn, deleteAll, &localError, NULL);
1629 // return ok;
1630 //});
1631 /* drop all tables and recreate them, in case of schema changes */
1632 ok &= SecDbExec(dbconn, deleteTablesSQL, &localError);
1633 ok &= SecDbExec(dbconn, createTablesSQL, &localError);
1634 secdebug("validupdate", "resetting database, result: %d", (ok) ? 1 : 0);
1635 *commit = ok;
1636 });
1637 /* compact the db (must be done outside transaction scope) */
1638 SecDbExec(dbconn, CFSTR("VACUUM"), &localError);
1639 });
1640 /* one more thing: update the schema version and format to current */
1641 _SecRevocationDbSetSchemaVersion(rdb, kSecRevocationDbSchemaVersion);
1642 _SecRevocationDbSetUpdateFormat(rdb, kSecRevocationDbUpdateFormat);
1643
1644 CFReleaseSafe(localError);
1645 return ok;
1646 }
1647
1648 static bool _SecRevocationDbUpdateIssuers(SecRevocationDbRef rdb, int64_t groupId, CFArrayRef issuers, CFErrorRef *error) {
1649 /* insert or replace issuer records in issuers table */
1650 if (!issuers || groupId < 0) {
1651 return false; /* must have something to insert, and a group to associate with it */
1652 }
1653 __block bool ok = true;
1654 __block CFErrorRef localError = NULL;
1655
1656 ok &= SecDbPerformWrite(rdb->db, &localError, ^(SecDbConnectionRef dbconn) {
1657 ok &= SecDbTransaction(dbconn, kSecDbExclusiveTransactionType, &localError, ^(bool *commit) {
1658 if (isArray(issuers)) {
1659 CFIndex issuerIX, issuerCount = CFArrayGetCount(issuers);
1660 for (issuerIX=0; issuerIX<issuerCount && ok; issuerIX++) {
1661 CFDataRef hash = (CFDataRef)CFArrayGetValueAtIndex(issuers, issuerIX);
1662 if (!hash) { continue; }
1663 if (ok) ok = SecDbWithSQL(dbconn, insertIssuerRecordSQL, &localError, ^bool(sqlite3_stmt *insertIssuer) {
1664 if (ok) {
1665 ok = SecDbBindInt64(insertIssuer, 1,
1666 groupId, &localError);
1667 }
1668 if (ok) {
1669 ok = SecDbBindBlob(insertIssuer, 2,
1670 CFDataGetBytePtr(hash),
1671 CFDataGetLength(hash),
1672 SQLITE_TRANSIENT, &localError);
1673 }
1674 /* Execute the insert statement for this issuer record. */
1675 if (ok) {
1676 ok = SecDbStep(dbconn, insertIssuer, &localError, NULL);
1677 }
1678 return ok;
1679 });
1680 }
1681 }
1682 });
1683 });
1684
1685 (void) CFErrorPropagate(localError, error);
1686 return ok;
1687 }
1688
1689 static bool _SecRevocationDbUpdatePerIssuerData(SecRevocationDbRef rdb, int64_t groupId, CFDictionaryRef dict, CFErrorRef *error) {
1690 /* update/delete records in serials or hashes table. */
1691 if (!dict || groupId < 0) {
1692 return false; /* must have something to insert, and a group to associate with it */
1693 }
1694 __block bool ok = true;
1695 __block CFErrorRef localError = NULL;
1696
1697 ok &= SecDbPerformWrite(rdb->db, &localError, ^(SecDbConnectionRef dbconn) {
1698 ok &= SecDbTransaction(dbconn, kSecDbExclusiveTransactionType, &localError, ^(bool *commit) {
1699 CFArrayRef deleteArray = (CFArrayRef)CFDictionaryGetValue(dict, CFSTR("delete"));
1700 /* process deletions */
1701 if (isArray(deleteArray)) {
1702 //%%% delete old data here (rdar://31439625)
1703 }
1704 /* process additions */
1705 CFArrayRef addArray = (CFArrayRef)CFDictionaryGetValue(dict, CFSTR("add"));
1706 if (isArray(addArray)) {
1707 CFIndex identifierIX, identifierCount = CFArrayGetCount(addArray);
1708 for (identifierIX=0; identifierIX<identifierCount; identifierIX++) {
1709 CFDataRef identifierData = (CFDataRef)CFArrayGetValueAtIndex(addArray, identifierIX);
1710 if (!identifierData) { continue; }
1711 CFIndex length = CFDataGetLength(identifierData);
1712 /* we can figure out the format without an extra read to get the format column.
1713 len <= 20 is a serial number. len==32 is a sha256 hash. otherwise: xor. */
1714 CFStringRef sql = NULL;
1715 if (length <= 20) {
1716 sql = insertSerialRecordSQL;
1717 } else if (length == 32) {
1718 sql = insertSha256RecordSQL;
1719 }
1720 if (!sql) { continue; }
1721
1722 if (ok) ok = SecDbWithSQL(dbconn, sql, &localError, ^bool(sqlite3_stmt *insertIdentifier) {
1723 /* rowid,(groupid,serial|sha256) */
1724 /* rowid is autoincremented and we never set it directly */
1725 if (ok) {
1726 ok = SecDbBindInt64(insertIdentifier, 1,
1727 groupId, &localError);
1728 }
1729 if (ok) {
1730 ok = SecDbBindBlob(insertIdentifier, 2,
1731 CFDataGetBytePtr(identifierData),
1732 CFDataGetLength(identifierData),
1733 SQLITE_TRANSIENT, &localError);
1734 }
1735 /* Execute the insert statement for the identifier record. */
1736 if (ok) {
1737 ok = SecDbStep(dbconn, insertIdentifier, &localError, NULL);
1738 }
1739 return ok;
1740 });
1741 }
1742 }
1743 });
1744 });
1745
1746 (void) CFErrorPropagate(localError, error);
1747 return ok;
1748 }
1749
1750 static SecValidInfoFormat _SecRevocationDbGetGroupFormat(SecRevocationDbRef rdb,
1751 int64_t groupId, SecValidInfoFlags *flags, CFDataRef *data, CFErrorRef *error) {
1752 /* return group record fields for a given groupId.
1753 on success, returns a non-zero format type, and other field values in optional output parameters.
1754 caller is responsible for releasing data and error parameters, if provided.
1755 */
1756 __block bool ok = true;
1757 __block SecValidInfoFormat format = 0;
1758 __block CFErrorRef localError = NULL;
1759
1760 /* Select the group record to determine flags and format. */
1761 ok &= SecDbPerformRead(rdb->db, &localError, ^(SecDbConnectionRef dbconn) {
1762 ok &= SecDbWithSQL(dbconn, selectGroupRecordSQL, &localError, ^bool(sqlite3_stmt *selectGroup) {
1763 ok = SecDbBindInt64(selectGroup, 1, groupId, &localError);
1764 ok &= SecDbStep(dbconn, selectGroup, &localError, ^(bool *stop) {
1765 if (flags) {
1766 *flags = (SecValidInfoFlags)sqlite3_column_int(selectGroup, 0);
1767 }
1768 format = (SecValidInfoFormat)sqlite3_column_int(selectGroup, 1);
1769 if (data) {
1770 //TODO: stream this from sqlite through the inflation so we return an inflated copy, then remove inflate from others
1771 uint8_t *p = (uint8_t *)sqlite3_column_blob(selectGroup, 2);
1772 if (p != NULL && format == kSecValidInfoFormatNto1) {
1773 CFIndex length = (CFIndex)sqlite3_column_bytes(selectGroup, 2);
1774 *data = CFDataCreate(kCFAllocatorDefault, p, length);
1775 }
1776 }
1777 });
1778 return ok;
1779 });
1780 });
1781 if (!ok) {
1782 secdebug("validupdate", "GetGroupFormat for groupId %lu failed", (unsigned long)groupId);
1783 format = kSecValidInfoFormatUnknown;
1784 }
1785 (void) CFErrorPropagate(localError, error);
1786 if (!(format > kSecValidInfoFormatUnknown)) {
1787 secdebug("validupdate", "GetGroupFormat: got format %d for groupId %lld", format, (long long)groupId);
1788 }
1789 return format;
1790 }
1791
1792 static bool _SecRevocationDbUpdateFlags(CFDictionaryRef dict, CFStringRef key, SecValidInfoFlags mask, SecValidInfoFlags *flags) {
1793 /* If a boolean value exists in the given dictionary for the given key,
1794 set or clear the corresponding bit(s) defined by the mask argument.
1795 Function returns true if the flags value was changed, false otherwise.
1796 */
1797 bool result = false;
1798 CFTypeRef value = (CFBooleanRef)CFDictionaryGetValue(dict, key);
1799 if (isBoolean(value) && flags) {
1800 SecValidInfoFlags oldFlags = *flags;
1801 if (CFBooleanGetValue((CFBooleanRef)value)) {
1802 *flags |= mask;
1803 } else {
1804 *flags &= ~(mask);
1805 }
1806 result = (*flags != oldFlags);
1807 }
1808 return result;
1809 }
1810
1811 static bool _SecRevocationDbUpdateFilter(CFDictionaryRef dict, CFDataRef oldData, CFDataRef * __nonnull CF_RETURNS_RETAINED xmlData) {
1812 /* If xor and/or params values exist in the given dictionary, create a new
1813 property list containing the updated values, and return as a flattened
1814 data blob in the xmlData output parameter (note: caller must release.)
1815 Function returns true if there is new xmlData to save, false otherwise.
1816 */
1817 bool result = false;
1818 bool xorProvided = false;
1819 bool paramsProvided = false;
1820 bool missingData = false;
1821
1822 if (!dict || !xmlData) {
1823 return result; /* no-op if no dictionary is provided, or no way to update the data */
1824 }
1825 *xmlData = NULL;
1826 CFDataRef xorCurrent = NULL;
1827 CFDataRef xorUpdate = (CFDataRef)CFDictionaryGetValue(dict, CFSTR("xor"));
1828 if (isData(xorUpdate)) {
1829 xorProvided = true;
1830 }
1831 CFArrayRef paramsCurrent = NULL;
1832 CFArrayRef paramsUpdate = (CFArrayRef)CFDictionaryGetValue(dict, CFSTR("params"));
1833 if (isArray(paramsUpdate)) {
1834 paramsProvided = true;
1835 }
1836 if (!(xorProvided || paramsProvided)) {
1837 return result; /* nothing to update, so we can bail out here. */
1838 }
1839
1840 CFPropertyListRef nto1Current = NULL;
1841 CFMutableDictionaryRef nto1Update = CFDictionaryCreateMutable(kCFAllocatorDefault, 0,
1842 &kCFTypeDictionaryKeyCallBacks,
1843 &kCFTypeDictionaryValueCallBacks);
1844 if (!nto1Update) {
1845 return result;
1846 }
1847
1848 /* turn old data into property list */
1849 CFDataRef data = (CFDataRef)CFRetainSafe(oldData);
1850 CFDataRef inflatedData = copyInflatedData(data);
1851 if (inflatedData) {
1852 CFReleaseSafe(data);
1853 data = inflatedData;
1854 }
1855 if (data) {
1856 nto1Current = CFPropertyListCreateWithData(kCFAllocatorDefault, data, 0, NULL, NULL);
1857 CFReleaseSafe(data);
1858 }
1859 if (nto1Current) {
1860 xorCurrent = (CFDataRef)CFDictionaryGetValue((CFDictionaryRef)nto1Current, CFSTR("xor"));
1861 paramsCurrent = (CFArrayRef)CFDictionaryGetValue((CFDictionaryRef)nto1Current, CFSTR("params"));
1862 }
1863
1864 /* set current or updated xor data in new property list */
1865 if (xorProvided) {
1866 CFDataRef xorNew = NULL;
1867 if (xorCurrent) {
1868 CFIndex xorUpdateLen = CFDataGetLength(xorUpdate);
1869 CFMutableDataRef xor = CFDataCreateMutableCopy(NULL, 0, xorCurrent);
1870 if (xor && xorUpdateLen > 0) {
1871 /* truncate or zero-extend data to match update size */
1872 CFDataSetLength(xor, xorUpdateLen);
1873 /* exclusive-or update bytes over the existing data */
1874 UInt8 *xorP = (UInt8 *)CFDataGetMutableBytePtr(xor);
1875 UInt8 *updP = (UInt8 *)CFDataGetBytePtr(xorUpdate);
1876 if (xorP && updP) {
1877 for (int idx = 0; idx < xorUpdateLen; idx++) {
1878 xorP[idx] = xorP[idx] ^ updP[idx];
1879 }
1880 }
1881 }
1882 xorNew = (CFDataRef)xor;
1883 } else {
1884 xorNew = (CFDataRef)CFRetainSafe(xorUpdate);
1885 }
1886 if (xorNew) {
1887 CFDictionaryAddValue(nto1Update, CFSTR("xor"), xorNew);
1888 CFReleaseSafe(xorNew);
1889 } else {
1890 secdebug("validupdate", "Failed to get updated filter data");
1891 missingData = true;
1892 }
1893 } else if (xorCurrent) {
1894 /* not provided, so use existing xor value */
1895 CFDictionaryAddValue(nto1Update, CFSTR("xor"), xorCurrent);
1896 } else {
1897 secdebug("validupdate", "Failed to get current filter data");
1898 missingData = true;
1899 }
1900
1901 /* set current or updated params in new property list */
1902 if (paramsProvided) {
1903 CFDictionaryAddValue(nto1Update, CFSTR("params"), paramsUpdate);
1904 } else if (paramsCurrent) {
1905 /* not provided, so use existing params value */
1906 CFDictionaryAddValue(nto1Update, CFSTR("params"), paramsCurrent);
1907 } else {
1908 /* missing params: neither provided nor existing */
1909 secdebug("validupdate", "Failed to get current filter params");
1910 missingData = true;
1911 }
1912
1913 CFReleaseSafe(nto1Current);
1914 if (!missingData) {
1915 *xmlData = CFPropertyListCreateData(kCFAllocatorDefault, nto1Update,
1916 kCFPropertyListXMLFormat_v1_0,
1917 0, NULL);
1918 result = (*xmlData != NULL);
1919 }
1920 CFReleaseSafe(nto1Update);
1921
1922 /* compress the xmlData blob, if possible */
1923 if (result) {
1924 CFDataRef deflatedData = copyDeflatedData(*xmlData);
1925 if (deflatedData) {
1926 if (CFDataGetLength(deflatedData) < CFDataGetLength(*xmlData)) {
1927 CFRelease(*xmlData);
1928 *xmlData = deflatedData;
1929 } else {
1930 CFRelease(deflatedData);
1931 }
1932 }
1933 }
1934 return result;
1935 }
1936
1937
1938 static int64_t _SecRevocationDbUpdateGroup(SecRevocationDbRef rdb, int64_t groupId, CFDictionaryRef dict, CFErrorRef *error) {
1939 /* insert group record for a given groupId.
1940 if the specified groupId is < 0, a new group entry is created.
1941 returns the groupId on success, or -1 on failure.
1942 */
1943 if (!dict) {
1944 return groupId; /* no-op if no dictionary is provided */
1945 }
1946
1947 __block int64_t result = -1;
1948 __block bool ok = true;
1949 __block bool isFormatChange = false;
1950 __block CFErrorRef localError = NULL;
1951
1952 __block SecValidInfoFlags flags = 0;
1953 __block SecValidInfoFormat format = kSecValidInfoFormatUnknown;
1954 __block SecValidInfoFormat formatUpdate = kSecValidInfoFormatUnknown;
1955 __block CFDataRef data = NULL;
1956
1957 if (groupId >= 0) {
1958 /* fetch the flags and data for an existing group record, in case some are being changed. */
1959 format = _SecRevocationDbGetGroupFormat(rdb, groupId, &flags, &data, NULL);
1960 if (format == kSecValidInfoFormatUnknown) {
1961 secdebug("validupdate", "existing group %lld has unknown format %d, flags=%lu",
1962 (long long)groupId, format, flags);
1963 //%%% clean up by deleting all issuers with this groupId, then the group record,
1964 // or just force a full update? note: we can get here if we fail to bind the
1965 // format value in the prepared SQL statement below.
1966 return -1;
1967 }
1968 }
1969 CFTypeRef value = (CFStringRef)CFDictionaryGetValue(dict, CFSTR("format"));
1970 if (isString(value)) {
1971 if (CFStringCompare((CFStringRef)value, CFSTR("serial"), 0) == kCFCompareEqualTo) {
1972 formatUpdate = kSecValidInfoFormatSerial;
1973 } else if (CFStringCompare((CFStringRef)value, CFSTR("sha256"), 0) == kCFCompareEqualTo) {
1974 formatUpdate = kSecValidInfoFormatSHA256;
1975 } else if (CFStringCompare((CFStringRef)value, CFSTR("nto1"), 0) == kCFCompareEqualTo) {
1976 formatUpdate = kSecValidInfoFormatNto1;
1977 }
1978 }
1979 /* if format value is explicitly supplied, then this is effectively a new group entry. */
1980 isFormatChange = (formatUpdate > kSecValidInfoFormatUnknown &&
1981 formatUpdate != format &&
1982 groupId >= 0);
1983
1984 ok &= SecDbPerformWrite(rdb->db, &localError, ^(SecDbConnectionRef dbconn) {
1985 ok &= SecDbTransaction(dbconn, kSecDbExclusiveTransactionType, &localError, ^(bool *commit) {
1986 if (isFormatChange) {
1987 secdebug("validupdate", "group %lld format change from %d to %d",
1988 (long long)groupId, format, formatUpdate);
1989 /* format of an existing group is changing; delete the group first.
1990 this should ensure that all entries referencing the old groupid are deleted.
1991 */
1992 ok &= SecDbWithSQL(dbconn, deleteGroupRecordSQL, &localError, ^bool(sqlite3_stmt *deleteResponse) {
1993 ok = SecDbBindInt64(deleteResponse, 1, groupId, &localError);
1994 /* Execute the delete statement. */
1995 if (ok) {
1996 ok = SecDbStep(dbconn, deleteResponse, &localError, NULL);
1997 }
1998 return ok;
1999 });
2000 }
2001 ok &= SecDbWithSQL(dbconn, insertGroupRecordSQL, &localError, ^bool(sqlite3_stmt *insertGroup) {
2002 /* (groupid,flags,format,data) */
2003 /* groups.groupid */
2004 if (ok && (!isFormatChange) && (groupId >= 0)) {
2005 /* bind to existing groupId row if known, otherwise will insert and autoincrement */
2006 ok = SecDbBindInt64(insertGroup, 1, groupId, &localError);
2007 if (!ok) {
2008 secdebug("validupdate", "failed to set groupId %lld", (long long)groupId);
2009 }
2010 }
2011 /* groups.flags */
2012 if (ok) {
2013 (void)_SecRevocationDbUpdateFlags(dict, CFSTR("complete"), kSecValidInfoComplete, &flags);
2014 (void)_SecRevocationDbUpdateFlags(dict, CFSTR("check-ocsp"), kSecValidInfoCheckOCSP, &flags);
2015 (void)_SecRevocationDbUpdateFlags(dict, CFSTR("known-intermediates-only"), kSecValidInfoKnownOnly, &flags);
2016 (void)_SecRevocationDbUpdateFlags(dict, CFSTR("require-ct"), kSecValidInfoRequireCT, &flags);
2017 (void)_SecRevocationDbUpdateFlags(dict, CFSTR("valid"), kSecValidInfoAllowlist, &flags);
2018 (void)_SecRevocationDbUpdateFlags(dict, CFSTR("no-ca"), kSecValidInfoNoCACheck, &flags);
2019
2020 ok = SecDbBindInt(insertGroup, 2, (int)flags, &localError);
2021 if (!ok) {
2022 secdebug("validupdate", "failed to set flags (%lu) for groupId %lld", flags, (long long)groupId);
2023 }
2024 }
2025 /* groups.format */
2026 if (ok) {
2027 SecValidInfoFormat formatValue = format;
2028 if (formatUpdate > kSecValidInfoFormatUnknown) {
2029 formatValue = formatUpdate;
2030 }
2031 ok = SecDbBindInt(insertGroup, 3, (int)formatValue, &localError);
2032 if (!ok) {
2033 secdebug("validupdate", "failed to set format (%d) for groupId %lld", formatValue, (long long)groupId);
2034 }
2035 }
2036 /* groups.data */
2037 CFDataRef xmlData = NULL;
2038 if (ok) {
2039 bool hasFilter = ((formatUpdate == kSecValidInfoFormatNto1) ||
2040 (formatUpdate == kSecValidInfoFormatUnknown &&
2041 format == kSecValidInfoFormatNto1));
2042 if (hasFilter) {
2043 CFDataRef dataValue = data; /* use existing data */
2044 if (_SecRevocationDbUpdateFilter(dict, data, &xmlData)) {
2045 dataValue = xmlData; /* use updated data */
2046 }
2047 if (dataValue) {
2048 ok = SecDbBindBlob(insertGroup, 4,
2049 CFDataGetBytePtr(dataValue),
2050 CFDataGetLength(dataValue),
2051 SQLITE_TRANSIENT, &localError);
2052 }
2053 if (!ok) {
2054 secdebug("validupdate", "failed to set data for groupId %lld",
2055 (long long)groupId);
2056 }
2057 }
2058 /* else there is no data, so NULL is implicitly bound to column 4 */
2059 }
2060
2061 /* Execute the insert statement for the group record. */
2062 if (ok) {
2063 ok = SecDbStep(dbconn, insertGroup, &localError, NULL);
2064 if (!ok) {
2065 secdebug("validupdate", "failed to execute insertGroup statement for groupId %lld",
2066 (long long)groupId);
2067 }
2068 result = (int64_t)sqlite3_last_insert_rowid(SecDbHandle(dbconn));
2069 }
2070 if (!ok) {
2071 secdebug("validupdate", "failed to insert group %lld", (long long)result);
2072 }
2073 /* Clean up temporary allocation made in this block. */
2074 CFReleaseSafe(xmlData);
2075 CFReleaseSafe(data);
2076 return ok;
2077 });
2078 });
2079 });
2080
2081 (void) CFErrorPropagate(localError, error);
2082 return result;
2083 }
2084
2085 static int64_t _SecRevocationDbGroupIdForIssuerHash(SecRevocationDbRef rdb, CFDataRef hash, CFErrorRef *error) {
2086 /* look up issuer hash in issuers table to get groupid, if it exists */
2087 __block int64_t groupId = -1;
2088 __block bool ok = true;
2089 __block CFErrorRef localError = NULL;
2090
2091 if (!hash) {
2092 secdebug("validupdate", "failed to get hash (%@)", hash);
2093 }
2094 require(hash, errOut);
2095
2096 /* This is the starting point for any lookup; find a group id for the given issuer hash.
2097 Before we do that, need to verify the current db_version. We cannot use results from a
2098 database created with a schema version older than the minimum supported version.
2099 However, we may be able to use results from a newer version. At the next database
2100 update interval, if the existing schema is old, we'll be removing and recreating
2101 the database contents with the current schema version.
2102 */
2103 int64_t db_version = _SecRevocationDbGetSchemaVersion(rdb, NULL);
2104 if (db_version < kSecRevocationDbMinSchemaVersion) {
2105 if (!rdb->unsupportedVersion) {
2106 secdebug("validupdate", "unsupported db_version: %lld", (long long)db_version);
2107 rdb->unsupportedVersion = true; /* only warn once for a given unsupported version */
2108 }
2109 }
2110 require_quiet(db_version >= kSecRevocationDbMinSchemaVersion, errOut);
2111
2112 /* Look up provided issuer_hash in the issuers table.
2113 */
2114 ok &= SecDbPerformRead(rdb->db, &localError, ^(SecDbConnectionRef dbconn) {
2115 ok &= SecDbWithSQL(dbconn, selectGroupIdSQL, &localError, ^bool(sqlite3_stmt *selectGroupId) {
2116 ok = SecDbBindBlob(selectGroupId, 1, CFDataGetBytePtr(hash), CFDataGetLength(hash), SQLITE_TRANSIENT, &localError);
2117 ok &= SecDbStep(dbconn, selectGroupId, &localError, ^(bool *stopGroupId) {
2118 groupId = sqlite3_column_int64(selectGroupId, 0);
2119 });
2120 return ok;
2121 });
2122 });
2123
2124 errOut:
2125 (void) CFErrorPropagate(localError, error);
2126 return groupId;
2127 }
2128
2129 static bool _SecRevocationDbApplyGroupDelete(SecRevocationDbRef rdb, CFDataRef issuerHash, CFErrorRef *error) {
2130 /* delete group associated with the given issuer;
2131 schema trigger will delete associated issuers, serials, and hashes. */
2132 __block int64_t groupId = -1;
2133 __block bool ok = true;
2134 __block CFErrorRef localError = NULL;
2135
2136 groupId = _SecRevocationDbGroupIdForIssuerHash(rdb, issuerHash, &localError);
2137 require(!(groupId < 0), errOut);
2138
2139 ok &= SecDbPerformWrite(rdb->db, &localError, ^(SecDbConnectionRef dbconn) {
2140 ok &= SecDbTransaction(dbconn, kSecDbExclusiveTransactionType, &localError, ^(bool *commit) {
2141 ok = SecDbWithSQL(dbconn, deleteGroupRecordSQL, &localError, ^bool(sqlite3_stmt *deleteResponse) {
2142 ok = SecDbBindInt64(deleteResponse, 1, groupId, &localError);
2143 /* Execute the delete statement. */
2144 if (ok) {
2145 ok = SecDbStep(dbconn, deleteResponse, &localError, NULL);
2146 }
2147 return ok;
2148 });
2149 });
2150 });
2151
2152 errOut:
2153 (void) CFErrorPropagate(localError, error);
2154 return (groupId < 0) ? false : true;
2155 }
2156
2157 static bool _SecRevocationDbApplyGroupUpdate(SecRevocationDbRef rdb, CFDictionaryRef dict, CFErrorRef *error) {
2158 /* process one issuer group's update dictionary */
2159 int64_t groupId = -1;
2160 CFErrorRef localError = NULL;
2161
2162 CFArrayRef issuers = (dict) ? (CFArrayRef)CFDictionaryGetValue(dict, CFSTR("issuer-hash")) : NULL;
2163 if (isArray(issuers)) {
2164 CFIndex issuerIX, issuerCount = CFArrayGetCount(issuers);
2165 /* while we have issuers and haven't found a matching group id */
2166 for (issuerIX=0; issuerIX<issuerCount && groupId < 0; issuerIX++) {
2167 CFDataRef hash = (CFDataRef)CFArrayGetValueAtIndex(issuers, issuerIX);
2168 if (!hash) { continue; }
2169 groupId = _SecRevocationDbGroupIdForIssuerHash(rdb, hash, &localError);
2170 }
2171 }
2172 /* create or update the group entry */
2173 groupId = _SecRevocationDbUpdateGroup(rdb, groupId, dict, &localError);
2174 if (groupId < 0) {
2175 secdebug("validupdate", "failed to get groupId");
2176 } else {
2177 /* create or update issuer entries, now that we know the group id */
2178 _SecRevocationDbUpdateIssuers(rdb, groupId, issuers, &localError);
2179 /* create or update entries in serials or hashes tables */
2180 _SecRevocationDbUpdatePerIssuerData(rdb, groupId, dict, &localError);
2181 }
2182
2183 (void) CFErrorPropagate(localError, error);
2184 return (groupId > 0) ? true : false;
2185 }
2186
2187 static void _SecRevocationDbApplyUpdate(SecRevocationDbRef rdb, CFDictionaryRef update, CFIndex version) {
2188 /* process entire update dictionary */
2189 if (!rdb || !update) {
2190 secerror("_SecRevocationDbApplyUpdate failed: invalid args");
2191 return;
2192 }
2193
2194 __block CFDictionaryRef localUpdate = (CFDictionaryRef)CFRetainSafe(update);
2195 __block CFErrorRef localError = NULL;
2196
2197 CFTypeRef value = NULL;
2198 CFIndex deleteCount = 0;
2199 CFIndex updateCount = 0;
2200
2201 rdb->updateInProgress = true;
2202
2203 /* check whether this is a full update */
2204 value = (CFBooleanRef)CFDictionaryGetValue(update, CFSTR("full"));
2205 if (isBoolean(value) && CFBooleanGetValue((CFBooleanRef)value)) {
2206 /* clear the database before processing a full update */
2207 SecRevocationDbRemoveAllEntries();
2208 }
2209
2210 /* process 'delete' list */
2211 value = (CFArrayRef)CFDictionaryGetValue(localUpdate, CFSTR("delete"));
2212 if (isArray(value)) {
2213 deleteCount = CFArrayGetCount((CFArrayRef)value);
2214 secdebug("validupdate", "processing %ld deletes", (long)deleteCount);
2215 for (CFIndex deleteIX=0; deleteIX<deleteCount; deleteIX++) {
2216 CFDataRef issuerHash = (CFDataRef)CFArrayGetValueAtIndex((CFArrayRef)value, deleteIX);
2217 if (isData(issuerHash)) {
2218 (void)_SecRevocationDbApplyGroupDelete(rdb, issuerHash, &localError);
2219 CFReleaseNull(localError);
2220 }
2221 }
2222 }
2223
2224 /* process 'update' list */
2225 value = (CFArrayRef)CFDictionaryGetValue(localUpdate, CFSTR("update"));
2226 if (isArray(value)) {
2227 updateCount = CFArrayGetCount((CFArrayRef)value);
2228 secdebug("validupdate", "processing %ld updates", (long)updateCount);
2229 for (CFIndex updateIX=0; updateIX<updateCount; updateIX++) {
2230 CFDictionaryRef dict = (CFDictionaryRef)CFArrayGetValueAtIndex((CFArrayRef)value, updateIX);
2231 if (isDictionary(dict)) {
2232 (void)_SecRevocationDbApplyGroupUpdate(rdb, dict, &localError);
2233 CFReleaseNull(localError);
2234 }
2235 }
2236 }
2237 CFRelease(localUpdate);
2238
2239 /* set version */
2240 _SecRevocationDbSetVersion(rdb, version);
2241
2242 /* set db_version if not already set */
2243 int64_t db_version = _SecRevocationDbGetSchemaVersion(rdb, NULL);
2244 if (db_version <= 0) {
2245 _SecRevocationDbSetSchemaVersion(rdb, kSecRevocationDbSchemaVersion);
2246 }
2247
2248 /* set db_format if not already set */
2249 int64_t db_format = _SecRevocationDbGetUpdateFormat(rdb, NULL);
2250 if (db_format <= 0) {
2251 _SecRevocationDbSetUpdateFormat(rdb, kSecRevocationDbUpdateFormat);
2252 }
2253
2254 /* compact the db (must be done outside transaction scope) */
2255 (void)SecDbPerformWrite(rdb->db, &localError, ^(SecDbConnectionRef dbconn) {
2256 SecDbExec(dbconn, CFSTR("VACUUM"), &localError);
2257 CFReleaseNull(localError);
2258 });
2259
2260 rdb->updateInProgress = false;
2261 }
2262
2263 static bool _SecRevocationDbSerialInGroup(SecRevocationDbRef rdb,
2264 CFDataRef serial,
2265 int64_t groupId,
2266 CFErrorRef *error) {
2267 __block bool result = false;
2268 __block bool ok = true;
2269 __block CFErrorRef localError = NULL;
2270 require(rdb && serial, errOut);
2271 ok &= SecDbPerformRead(rdb->db, &localError, ^(SecDbConnectionRef dbconn) {
2272 ok &= SecDbWithSQL(dbconn, selectSerialRecordSQL, &localError, ^bool(sqlite3_stmt *selectSerial) {
2273 ok &= SecDbBindInt64(selectSerial, 1, groupId, &localError);
2274 ok &= SecDbBindBlob(selectSerial, 2, CFDataGetBytePtr(serial),
2275 CFDataGetLength(serial), SQLITE_TRANSIENT, &localError);
2276 ok &= SecDbStep(dbconn, selectSerial, &localError, ^(bool *stop) {
2277 int64_t foundRowId = (int64_t)sqlite3_column_int64(selectSerial, 0);
2278 result = (foundRowId > 0);
2279 });
2280 return ok;
2281 });
2282 });
2283
2284 errOut:
2285 (void) CFErrorPropagate(localError, error);
2286 return result;
2287 }
2288
2289 static bool _SecRevocationDbCertHashInGroup(SecRevocationDbRef rdb,
2290 CFDataRef certHash,
2291 int64_t groupId,
2292 CFErrorRef *error) {
2293 __block bool result = false;
2294 __block bool ok = true;
2295 __block CFErrorRef localError = NULL;
2296 require(rdb && certHash, errOut);
2297 ok &= SecDbPerformRead(rdb->db, &localError, ^(SecDbConnectionRef dbconn) {
2298 ok &= SecDbWithSQL(dbconn, selectHashRecordSQL, &localError, ^bool(sqlite3_stmt *selectHash) {
2299 ok &= SecDbBindInt64(selectHash, 1, groupId, &localError);
2300 ok = SecDbBindBlob(selectHash, 2, CFDataGetBytePtr(certHash),
2301 CFDataGetLength(certHash), SQLITE_TRANSIENT, &localError);
2302 ok &= SecDbStep(dbconn, selectHash, &localError, ^(bool *stop) {
2303 int64_t foundRowId = (int64_t)sqlite3_column_int64(selectHash, 0);
2304 result = (foundRowId > 0);
2305 });
2306 return ok;
2307 });
2308 });
2309
2310 errOut:
2311 (void) CFErrorPropagate(localError, error);
2312 return result;
2313 }
2314
2315 static bool _SecRevocationDbSerialInFilter(SecRevocationDbRef rdb,
2316 CFDataRef serialData,
2317 CFDataRef xmlData) {
2318 /* N-To-1 filter implementation.
2319 The 'xmlData' parameter is a flattened XML dictionary,
2320 containing 'xor' and 'params' keys. First order of
2321 business is to reconstitute the blob into components.
2322 */
2323 bool result = false;
2324 CFRetainSafe(xmlData);
2325 CFDataRef propListData = xmlData;
2326 /* Expand data blob if needed */
2327 CFDataRef inflatedData = copyInflatedData(propListData);
2328 if (inflatedData) {
2329 CFReleaseSafe(propListData);
2330 propListData = inflatedData;
2331 }
2332 CFDataRef xor = NULL;
2333 CFArrayRef params = NULL;
2334 CFPropertyListRef nto1 = CFPropertyListCreateWithData(kCFAllocatorDefault, propListData, 0, NULL, NULL);
2335 if (nto1) {
2336 xor = (CFDataRef)CFDictionaryGetValue((CFDictionaryRef)nto1, CFSTR("xor"));
2337 params = (CFArrayRef)CFDictionaryGetValue((CFDictionaryRef)nto1, CFSTR("params"));
2338 }
2339 uint8_t *hash = (xor) ? (uint8_t*)CFDataGetBytePtr(xor) : NULL;
2340 CFIndex hashLen = (hash) ? CFDataGetLength(xor) : 0;
2341 uint8_t *serial = (serialData) ? (uint8_t*)CFDataGetBytePtr(serialData) : NULL;
2342 CFIndex serialLen = (serial) ? CFDataGetLength(serialData) : 0;
2343
2344 require(hash && serial && params, errOut);
2345
2346 const uint32_t FNV_OFFSET_BASIS = 2166136261;
2347 const uint32_t FNV_PRIME = 16777619;
2348 bool notInHash = false;
2349 CFIndex ix, count = CFArrayGetCount(params);
2350 for (ix = 0; ix < count; ix++) {
2351 int32_t param;
2352 CFNumberRef cfnum = (CFNumberRef)CFArrayGetValueAtIndex(params, ix);
2353 if (!isNumber(cfnum) ||
2354 !CFNumberGetValue(cfnum, kCFNumberSInt32Type, &param)) {
2355 secinfo("validupdate", "error processing filter params at index %ld", (long)ix);
2356 continue;
2357 }
2358 /* process one param */
2359 uint32_t hval = FNV_OFFSET_BASIS ^ param;
2360 CFIndex i = serialLen;
2361 while (i > 0) {
2362 hval = ((hval ^ (serial[--i])) * FNV_PRIME) & 0xFFFFFFFF;
2363 }
2364 hval = hval % (hashLen * 8);
2365 if ((hash[hval/8] & (1 << (hval % 8))) == 0) {
2366 notInHash = true; /* definitely not in hash */
2367 break;
2368 }
2369 }
2370 if (!notInHash) {
2371 /* probabilistically might be in hash if we get here. */
2372 result = true;
2373 }
2374
2375 errOut:
2376 CFReleaseSafe(nto1);
2377 CFReleaseSafe(propListData);
2378 return result;
2379 }
2380
2381 static SecValidInfoRef _SecRevocationDbValidInfoForCertificate(SecRevocationDbRef rdb,
2382 SecCertificateRef certificate,
2383 CFDataRef issuerHash,
2384 CFErrorRef *error) {
2385 __block CFErrorRef localError = NULL;
2386 __block SecValidInfoFlags flags = 0;
2387 __block SecValidInfoFormat format = kSecValidInfoFormatUnknown;
2388 __block CFDataRef data = NULL;
2389
2390 bool matched = false;
2391 bool isOnList = false;
2392 int64_t groupId = 0;
2393 CFDataRef serial = NULL;
2394 CFDataRef certHash = NULL;
2395 SecValidInfoRef result = NULL;
2396
2397 require((serial = SecCertificateCopySerialNumberData(certificate, NULL)) != NULL, errOut);
2398 require((certHash = SecCertificateCopySHA256Digest(certificate)) != NULL, errOut);
2399 require((groupId = _SecRevocationDbGroupIdForIssuerHash(rdb, issuerHash, &localError)) > 0, errOut);
2400
2401 /* Look up the group record to determine flags and format. */
2402 format = _SecRevocationDbGetGroupFormat(rdb, groupId, &flags, &data, &localError);
2403
2404 if (format == kSecValidInfoFormatUnknown) {
2405 /* No group record found for this issuer. */
2406 }
2407 else if (format == kSecValidInfoFormatSerial) {
2408 /* Look up certificate's serial number in the serials table. */
2409 matched = _SecRevocationDbSerialInGroup(rdb, serial, groupId, &localError);
2410 }
2411 else if (format == kSecValidInfoFormatSHA256) {
2412 /* Look up certificate's SHA-256 hash in the hashes table. */
2413 matched = _SecRevocationDbCertHashInGroup(rdb, certHash, groupId, &localError);
2414 }
2415 else if (format == kSecValidInfoFormatNto1) {
2416 /* Perform a Bloom filter match against the serial. If matched is false,
2417 then the cert is definitely not in the list. But if matched is true,
2418 we don't know for certain, so we would need to check OCSP. */
2419 matched = _SecRevocationDbSerialInFilter(rdb, serial, data);
2420 }
2421
2422 if (matched) {
2423 /* Found a specific match for this certificate. */
2424 secdebug("validupdate", "Valid db matched certificate: %@, format=%d, flags=%lu",
2425 certHash, format, flags);
2426 isOnList = true;
2427 }
2428 else if ((flags & kSecValidInfoComplete) && (flags & kSecValidInfoAllowlist)) {
2429 /* Not matching against a complete allowlist is equivalent to revocation. */
2430 secdebug("validupdate", "Valid db did NOT match certificate on allowlist: %@, format=%d, flags=%lu",
2431 certHash, format, flags);
2432 matched = true;
2433 }
2434 else if ((!(flags & kSecValidInfoComplete)) && (format > kSecValidInfoFormatUnknown)) {
2435 /* Not matching against an incomplete list implies we need to check OCSP. */
2436 secdebug("validupdate", "Valid db did not find certificate on incomplete list: %@, format=%d, flags=%lu",
2437 certHash, format, flags);
2438 matched = true;
2439 }
2440
2441 if (matched) {
2442 /* Return SecValidInfo for a matched certificate. */
2443 result = SecValidInfoCreate(format, flags, isOnList, certHash, issuerHash, NULL);
2444 }
2445
2446 if (result && SecIsAppleTrustAnchor(certificate, 0)) {
2447 /* Prevent a catch-22. */
2448 secdebug("validupdate", "Valid db match for Apple trust anchor: %@, format=%d, flags=%lu",
2449 certHash, format, flags);
2450 SecValidInfoRelease(result);
2451 result = NULL;
2452 }
2453
2454 errOut:
2455 (void) CFErrorPropagate(localError, error);
2456 CFReleaseSafe(data);
2457 CFReleaseSafe(certHash);
2458 CFReleaseSafe(serial);
2459 return result;
2460 }
2461
2462 static SecValidInfoRef _SecRevocationDbCopyMatching(SecRevocationDbRef db,
2463 SecCertificateRef certificate,
2464 SecCertificateRef issuer) {
2465 SecValidInfoRef result = NULL;
2466 CFErrorRef error = NULL;
2467 CFDataRef issuerHash = NULL;
2468
2469 require(certificate && issuer, errOut);
2470 require(issuerHash = SecCertificateCopySHA256Digest(issuer), errOut);
2471
2472 result = _SecRevocationDbValidInfoForCertificate(db, certificate, issuerHash, &error);
2473
2474 errOut:
2475 CFReleaseSafe(issuerHash);
2476 CFReleaseSafe(error);
2477 return result;
2478 }
2479
2480 static dispatch_queue_t _SecRevocationDbGetUpdateQueue(SecRevocationDbRef rdb) {
2481 return (rdb) ? rdb->update_queue : NULL;
2482 }
2483
2484
2485 /* Given a valid update dictionary, insert/replace or delete records
2486 in the revocation database. (This function is expected to be called only
2487 by the database maintainer, normally the system instance of trustd.)
2488 */
2489 void SecRevocationDbApplyUpdate(CFDictionaryRef update, CFIndex version) {
2490 SecRevocationDbWith(^(SecRevocationDbRef db) {
2491 _SecRevocationDbApplyUpdate(db, update, version);
2492 });
2493 }
2494
2495 /* Set the schema version for the revocation database.
2496 (This function is expected to be called only by the database maintainer,
2497 normally the system instance of trustd.)
2498 */
2499 void SecRevocationDbSetSchemaVersion(CFIndex db_version) {
2500 SecRevocationDbWith(^(SecRevocationDbRef db) {
2501 _SecRevocationDbSetSchemaVersion(db, db_version);
2502 });
2503 }
2504
2505 /* Set the update format for the revocation database.
2506 (This function is expected to be called only by the database maintainer,
2507 normally the system instance of trustd.)
2508 */
2509 void SecRevocationDbSetUpdateFormat(CFIndex db_format) {
2510 SecRevocationDbWith(^(SecRevocationDbRef db) {
2511 _SecRevocationDbSetUpdateFormat(db, db_format);
2512 });
2513 }
2514
2515 /* Set the update source for the revocation database.
2516 (This function is expected to be called only by the database
2517 maintainer, normally the system instance of trustd. If the
2518 caller does not have write access, this is a no-op.)
2519 */
2520 void SecRevocationDbSetUpdateSource(CFStringRef updateSource) {
2521 SecRevocationDbWith(^(SecRevocationDbRef db) {
2522 _SecRevocationDbSetUpdateSource(db, updateSource);
2523 });
2524 }
2525
2526 /* Return the update source as a retained CFStringRef.
2527 If the value cannot be obtained, NULL is returned.
2528 */
2529 CFStringRef SecRevocationDbCopyUpdateSource(void) {
2530 __block CFStringRef result = NULL;
2531 SecRevocationDbWith(^(SecRevocationDbRef db) {
2532 result = _SecRevocationDbCopyUpdateSource(db, NULL);
2533 });
2534 return result;
2535 }
2536
2537 /* Set the next update value for the revocation database.
2538 (This function is expected to be called only by the database
2539 maintainer, normally the system instance of trustd. If the
2540 caller does not have write access, this is a no-op.)
2541 */
2542 void SecRevocationDbSetNextUpdateTime(CFAbsoluteTime nextUpdate) {
2543 SecRevocationDbWith(^(SecRevocationDbRef db) {
2544 _SecRevocationDbSetNextUpdateTime(db, nextUpdate);
2545 });
2546 }
2547
2548 /* Return the next update value as a CFAbsoluteTime.
2549 If the value cannot be obtained, -1 is returned.
2550 */
2551 CFAbsoluteTime SecRevocationDbGetNextUpdateTime(void) {
2552 __block CFAbsoluteTime result = -1;
2553 SecRevocationDbWith(^(SecRevocationDbRef db) {
2554 result = _SecRevocationDbGetNextUpdateTime(db, NULL);
2555 });
2556 return result;
2557 }
2558
2559 /* Return the serial background queue for database updates.
2560 If the queue cannot be obtained, NULL is returned.
2561 */
2562 dispatch_queue_t SecRevocationDbGetUpdateQueue(void) {
2563 __block dispatch_queue_t result = NULL;
2564 SecRevocationDbWith(^(SecRevocationDbRef db) {
2565 result = _SecRevocationDbGetUpdateQueue(db);
2566 });
2567 return result;
2568 }
2569
2570 /* Remove all entries in the revocation database and reset its version to 0.
2571 (This function is expected to be called only by the database maintainer,
2572 normally the system instance of trustd.)
2573 */
2574 void SecRevocationDbRemoveAllEntries(void) {
2575 SecRevocationDbWith(^(SecRevocationDbRef db) {
2576 _SecRevocationDbRemoveAllEntries(db);
2577 });
2578 }
2579
2580 /* Release all connections to the revocation database.
2581 */
2582 void SecRevocationDbReleaseAllConnections(void) {
2583 SecRevocationDbWith(^(SecRevocationDbRef db) {
2584 SecDbReleaseAllConnections((db) ? db->db : NULL);
2585 });
2586 }
2587
2588 /* === SecRevocationDb API === */
2589
2590 /* Given a certificate and its issuer, returns a SecValidInfoRef if the
2591 valid database contains matching info; otherwise returns NULL.
2592 Caller must release the returned SecValidInfoRef by calling
2593 SecValidInfoRelease when finished.
2594 */
2595 SecValidInfoRef SecRevocationDbCopyMatching(SecCertificateRef certificate,
2596 SecCertificateRef issuer) {
2597 __block SecValidInfoRef result = NULL;
2598 SecRevocationDbWith(^(SecRevocationDbRef db) {
2599 result = _SecRevocationDbCopyMatching(db, certificate, issuer);
2600 });
2601 return result;
2602 }
2603
2604 /* Return the current version of the revocation database.
2605 A version of 0 indicates an empty database which must be populated.
2606 If the version cannot be obtained, -1 is returned.
2607 */
2608 CFIndex SecRevocationDbGetVersion(void) {
2609 __block CFIndex result = -1;
2610 SecRevocationDbWith(^(SecRevocationDbRef db) {
2611 result = (CFIndex)_SecRevocationDbGetVersion(db, NULL);
2612 });
2613 return result;
2614 }
2615
2616 /* Return the current schema version of the revocation database.
2617 A version of 0 indicates an empty database which must be populated.
2618 If the schema version cannot be obtained, -1 is returned.
2619 */
2620 CFIndex SecRevocationDbGetSchemaVersion(void) {
2621 __block CFIndex result = -1;
2622 SecRevocationDbWith(^(SecRevocationDbRef db) {
2623 result = (CFIndex)_SecRevocationDbGetSchemaVersion(db, NULL);
2624 });
2625 return result;
2626 }
2627
2628 /* Return the current update format of the revocation database.
2629 A version of 0 indicates the format was unknown.
2630 If the update format cannot be obtained, -1 is returned.
2631 */
2632 CFIndex SecRevocationDbGetUpdateFormat(void) {
2633 __block CFIndex result = -1;
2634 SecRevocationDbWith(^(SecRevocationDbRef db) {
2635 result = (CFIndex)_SecRevocationDbGetUpdateFormat(db, NULL);
2636 });
2637 return result;
2638 }