]> git.saurik.com Git - apple/security.git/blob - OSX/sec/securityd/SecRevocationDb.c
Security-58286.31.2.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, (size_t)zs.total_out - (size_t)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 size_t bytesRemaining = (p) ? (size_t)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)) {
624 secnotice("validupdate", "unable to write %s", semPathBuf);
625 }
626 if (fd >= 0) {
627 close(fd);
628 }
629 free(semPathBuf);
630 }
631 // exit as gracefully as possible so we can replace the database
632 secnotice("validupdate", "process exiting to replace db file");
633 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 3ull*NSEC_PER_SEC), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
634 xpc_transaction_exit_clean();
635 });
636 goto updateExit;
637 }
638
639 // try to copy uncompressed database asset, if available
640 const char *validDbPathBuf = SecOTAPKIGetValidDatabaseSnapshot(otapkiRef);
641 if (validDbPathBuf) {
642 WithPathInRevocationInfoDirectory(CFSTR(kSecRevocationDbFileName), ^(const char *path) {
643 secdebug("validupdate", "will copy data from \"%s\"", validDbPathBuf);
644 copyfile_state_t state = copyfile_state_alloc();
645 int retval = copyfile(validDbPathBuf, path, state, COPYFILE_DATA);
646 copyfile_state_free(state);
647 if (retval < 0) {
648 secnotice("validupdate", "copyfile error %d", retval);
649 } else {
650 result = true;
651 }
652 });
653 }
654 if (result) {
655 goto updateExit;
656 }
657
658 // see if compressed database asset is available
659 if (validDbPathBuf) {
660 char *validDbCmpPathBuf = NULL;
661 asprintf(&validDbCmpPathBuf, "%s%s", validDbPathBuf, ".gz");
662 if (validDbCmpPathBuf) {
663 secdebug("validupdate", "will read data from \"%s\"", validDbCmpPathBuf);
664 if ((rtn = readValidFile(validDbCmpPathBuf, &data)) != 0) {
665 unmapData(data);
666 data = NULL;
667 secnotice("validupdate", "readValidFile error %d", rtn);
668 }
669 free(validDbCmpPathBuf);
670 }
671 }
672 result = SecValidDatabaseFromCompressed(data);
673 if (result) {
674 goto updateExit;
675 }
676
677 // unable to use database asset; try update asset
678 const char *validUpdatePathBuf = SecOTAPKIGetValidUpdateSnapshot(otapkiRef);
679 if (validUpdatePathBuf) {
680 secdebug("validupdate", "will read data from \"%s\"", validUpdatePathBuf);
681 if ((rtn = readValidFile(validUpdatePathBuf, &data)) != 0) {
682 unmapData(data);
683 data = NULL;
684 secnotice("validupdate", "readValidFile error %d", rtn);
685 }
686 }
687 result = SecValidUpdateFromCompressed(data);
688
689 updateExit:
690 CFReleaseNull(otapkiRef);
691 if (result) {
692 sNumLocalUpdates++;
693 SecRevocationDbSetUpdateSource(server);
694 gLastVersion = SecRevocationDbGetVersion();
695 gUpdateStarted = 0;
696 secdebug("validupdate", "local update to g%ld/v%ld complete at %f",
697 (long)SecRevocationDbGetUpdateFormat(), (long)gLastVersion,
698 (double)CFAbsoluteTimeGetCurrent());
699 } else {
700 sNumLocalUpdates = 0; // reset counter
701 }
702 return result;
703 }
704
705 static bool SecValidUpdateSchedule(bool updateEnabled, CFStringRef server, CFIndex version) {
706 /* Check if we have a later version available locally */
707 if (SecValidUpdateSatisfiedLocally(server, version, false)) {
708 return true;
709 }
710
711 /* If update not permitted return */
712 if (!updateEnabled) {
713 return false;
714 }
715
716 #if !TARGET_OS_BRIDGE
717 /* Schedule as a maintenance task */
718 return SecValidUpdateRequest(SecRevocationDbGetUpdateQueue(), server, version);
719 #else
720 return false;
721 #endif
722 }
723
724 void SecRevocationDbInitialize() {
725 if (!isDbOwner()) { return; }
726 __block bool initializeDb = false;
727
728 /* create base path if it doesn't exist */
729 (void)mkpath_np(kSecRevocationBasePath, 0755);
730
731 /* check semaphore file */
732 WithPathInRevocationInfoDirectory(CFSTR(kSecRevocationDbReplaceFile), ^(const char *path) {
733 struct stat sb;
734 if (stat(path, &sb) == 0) {
735 initializeDb = true; /* file was found, so we will replace the database */
736 if (remove(path) == -1) {
737 int error = errno;
738 secnotice("validupdate", "remove (%s): %s", path, strerror(error));
739 }
740 }
741 });
742
743 /* check database */
744 WithPathInRevocationInfoDirectory(CFSTR(kSecRevocationDbFileName), ^(const char *path) {
745 if (initializeDb) {
746 /* remove old database file(s) */
747 (void)removeFileWithSuffix(path, "");
748 (void)removeFileWithSuffix(path, "-journal");
749 (void)removeFileWithSuffix(path, "-shm");
750 (void)removeFileWithSuffix(path, "-wal");
751 }
752 else {
753 struct stat sb;
754 if (stat(path, &sb) == -1) {
755 initializeDb = true; /* file not found, so we will create the database */
756 }
757 }
758 });
759
760 if (!initializeDb) {
761 return; /* database exists and doesn't need replacing */
762 }
763
764 /* initialize database from local asset */
765 CFTypeRef value = (CFStringRef)CFPreferencesCopyValue(kUpdateServerKey, kSecPrefsDomain, kCFPreferencesAnyUser, kCFPreferencesCurrentHost);
766 CFStringRef server = (isString(value)) ? (CFStringRef)value : (CFStringRef)kValidUpdateServer;
767 CFIndex version = 0;
768 secnotice("validupdate", "initializing database");
769 if (!SecValidUpdateSatisfiedLocally(server, version, true)) {
770 #if !TARGET_OS_BRIDGE
771 /* Schedule full update as a maintenance task */
772 (void)SecValidUpdateRequest(SecRevocationDbGetUpdateQueue(), server, version);
773 #endif
774 }
775 CFReleaseSafe(value);
776 }
777
778
779 // MARK: -
780 // MARK: SecValidInfoRef
781
782 /* ======================================================================
783 SecValidInfoRef
784 ======================================================================
785 */
786
787 static SecValidInfoRef SecValidInfoCreate(SecValidInfoFormat format,
788 CFOptionFlags flags,
789 bool isOnList,
790 CFDataRef certHash,
791 CFDataRef issuerHash,
792 CFDataRef anchorHash) {
793 SecValidInfoRef validInfo;
794 validInfo = (SecValidInfoRef)calloc(1, sizeof(struct __SecValidInfo));
795 if (!validInfo) { return NULL; }
796
797 CFRetainSafe(certHash);
798 CFRetainSafe(issuerHash);
799 validInfo->format = format;
800 validInfo->certHash = certHash;
801 validInfo->issuerHash = issuerHash;
802 validInfo->anchorHash = anchorHash;
803 validInfo->isOnList = isOnList;
804 validInfo->valid = (flags & kSecValidInfoAllowlist);
805 validInfo->complete = (flags & kSecValidInfoComplete);
806 validInfo->checkOCSP = (flags & kSecValidInfoCheckOCSP);
807 validInfo->knownOnly = (flags & kSecValidInfoKnownOnly);
808 validInfo->requireCT = (flags & kSecValidInfoRequireCT);
809 validInfo->noCACheck = (flags & kSecValidInfoNoCACheck);
810
811 return validInfo;
812 }
813
814 void SecValidInfoRelease(SecValidInfoRef validInfo) {
815 if (validInfo) {
816 CFReleaseSafe(validInfo->certHash);
817 CFReleaseSafe(validInfo->issuerHash);
818 CFReleaseSafe(validInfo->anchorHash);
819 free(validInfo);
820 }
821 }
822
823 void SecValidInfoSetAnchor(SecValidInfoRef validInfo, SecCertificateRef anchor) {
824 if (!validInfo) {
825 return;
826 }
827 CFDataRef anchorHash = NULL;
828 if (anchor) {
829 anchorHash = SecCertificateCopySHA256Digest(anchor);
830
831 /* clear no-ca flag for anchors where we want OCSP checked [32523118] */
832 if (SecIsAppleTrustAnchor(anchor, 0)) {
833 validInfo->noCACheck = false;
834 }
835 }
836 CFReleaseNull(validInfo->anchorHash);
837 validInfo->anchorHash = anchorHash;
838 }
839
840
841 // MARK: -
842 // MARK: SecRevocationDb
843
844 /* ======================================================================
845 SecRevocationDb
846 ======================================================================
847 */
848
849 /* SecRevocationDbCheckNextUpdate returns true if we dispatched an
850 update request, otherwise false.
851 */
852 static bool _SecRevocationDbCheckNextUpdate(void) {
853 // are we the db owner instance?
854 if (!isDbOwner()) {
855 return false;
856 }
857 CFTypeRef value = NULL;
858
859 // is it time to check?
860 CFAbsoluteTime now = CFAbsoluteTimeGetCurrent();
861 CFAbsoluteTime minNextUpdate = now + gUpdateInterval;
862 gUpdateStarted = now;
863
864 if (0 == gNextUpdate) {
865 // first time we're called, check if we have a saved nextUpdate value
866 gNextUpdate = SecRevocationDbGetNextUpdateTime();
867 minNextUpdate = now;
868 if (gNextUpdate < minNextUpdate) {
869 gNextUpdate = minNextUpdate;
870 }
871 // allow pref to override update interval, if it exists
872 CFIndex interval = -1;
873 value = (CFNumberRef)CFPreferencesCopyValue(kUpdateIntervalKey, kSecPrefsDomain, kCFPreferencesAnyUser, kCFPreferencesCurrentHost);
874 if (isNumber(value)) {
875 if (CFNumberGetValue((CFNumberRef)value, kCFNumberCFIndexType, &interval)) {
876 if (interval < kSecMinUpdateInterval) {
877 interval = kSecMinUpdateInterval;
878 } else if (interval > kSecMaxUpdateInterval) {
879 interval = kSecMaxUpdateInterval;
880 }
881 }
882 }
883 CFReleaseNull(value);
884 gUpdateInterval = kSecStdUpdateInterval;
885 if (interval > 0) {
886 gUpdateInterval = interval;
887 }
888 // pin next update time to the preferred update interval
889 if (gNextUpdate > (gUpdateStarted + gUpdateInterval)) {
890 gNextUpdate = gUpdateStarted + gUpdateInterval;
891 }
892 secdebug("validupdate", "next update at %f (in %f seconds)",
893 (double)gUpdateStarted, (double)gNextUpdate-gUpdateStarted);
894 }
895 if (gNextUpdate > now) {
896 gUpdateStarted = 0;
897 return false;
898 }
899 secnotice("validupdate", "starting update");
900
901 // set minimum next update time here in case we can't get an update
902 gNextUpdate = minNextUpdate;
903
904 // determine which server to query
905 CFStringRef server;
906 value = (CFStringRef)CFPreferencesCopyValue(kUpdateServerKey, kSecPrefsDomain, kCFPreferencesAnyUser, kCFPreferencesCurrentHost);
907 if (isString(value)) {
908 server = (CFStringRef) CFRetain(value);
909 } else {
910 server = (CFStringRef) CFRetain(kValidUpdateServer);
911 }
912 CFReleaseNull(value);
913
914 // determine version of our current database
915 CFIndex version = SecRevocationDbGetVersion();
916 secdebug("validupdate", "got version %ld from db", (long)version);
917 if (version <= 0) {
918 if (gLastVersion > 0) {
919 secdebug("validupdate", "error getting version; using last good version: %ld", (long)gLastVersion);
920 }
921 version = gLastVersion;
922 }
923
924 // determine source of our current database
925 // (if this ever changes, we will need to reload the db)
926 CFStringRef db_source = SecRevocationDbCopyUpdateSource();
927 if (!db_source) {
928 db_source = (CFStringRef) CFRetain(kValidUpdateServer);
929 }
930
931 // determine whether we need to recreate the database
932 CFIndex db_version = SecRevocationDbGetSchemaVersion();
933 CFIndex db_format = SecRevocationDbGetUpdateFormat();
934 if (db_version < kSecRevocationDbSchemaVersion ||
935 db_format < kSecRevocationDbUpdateFormat ||
936 kCFCompareEqualTo != CFStringCompare(server, db_source, kCFCompareCaseInsensitive)) {
937 /* we need to fully rebuild the db contents. */
938 SecRevocationDbRemoveAllEntries();
939 version = gLastVersion = 0;
940 }
941
942 // determine whether update fetching is enabled
943 #if (__MAC_OS_X_VERSION_MIN_REQUIRED >= 101300 || __IPHONE_OS_VERSION_MIN_REQUIRED >= 110000)
944 bool updateEnabled = true; // macOS 10.13 or iOS 11.0
945 #else
946 bool updateEnabled = false;
947 #endif
948 value = (CFBooleanRef)CFPreferencesCopyValue(kUpdateEnabledKey, kSecPrefsDomain, kCFPreferencesAnyUser, kCFPreferencesCurrentHost);
949 if (isBoolean(value)) {
950 updateEnabled = CFBooleanGetValue((CFBooleanRef)value);
951 }
952 CFReleaseNull(value);
953
954 // Schedule maintenance work
955 bool result = SecValidUpdateSchedule(updateEnabled, server, version);
956 CFReleaseNull(server);
957 CFReleaseNull(db_source);
958 return result;
959 }
960
961 void SecRevocationDbCheckNextUpdate(void) {
962 static dispatch_once_t once;
963 static sec_action_t action;
964
965 dispatch_once(&once, ^{
966 dispatch_queue_t update_queue = SecRevocationDbGetUpdateQueue();
967 action = sec_action_create_with_queue(update_queue, "update_check", kSecMinUpdateInterval);
968 sec_action_set_handler(action, ^{
969 (void)_SecRevocationDbCheckNextUpdate();
970 });
971 });
972 sec_action_perform(action);
973 }
974
975 /* This function verifies an update, in this format:
976 1) unsigned 32-bit network-byte-order length of binary plist
977 2) binary plist data
978 3) unsigned 32-bit network-byte-order length of CMS message
979 4) CMS message (containing certificates and signature over binary plist)
980
981 The length argument is the total size of the packed update data.
982 */
983 bool SecRevocationDbVerifyUpdate(void *update, CFIndex length) {
984 if (!update || length <= (CFIndex)sizeof(uint32_t)) {
985 return false;
986 }
987 uint32_t plistLength = OSSwapInt32(*((uint32_t *)update));
988 if ((plistLength + (CFIndex)(sizeof(uint32_t)*2)) > (uint64_t) length) {
989 secdebug("validupdate", "ERROR: reported plist length (%lu)+%lu exceeds total length (%lu)\n",
990 (unsigned long)plistLength, (unsigned long)sizeof(uint32_t)*2, (unsigned long)length);
991 return false;
992 }
993 uint8_t *plistData = (uint8_t *)update + sizeof(uint32_t);
994 uint8_t *sigData = (uint8_t *)plistData + plistLength;
995 uint32_t sigLength = OSSwapInt32(*((uint32_t *)sigData));
996 sigData += sizeof(uint32_t);
997 if ((plistLength + sigLength + (CFIndex)(sizeof(uint32_t) * 2)) != (uint64_t) length) {
998 secdebug("validupdate", "ERROR: reported lengths do not add up to total length\n");
999 return false;
1000 }
1001
1002 OSStatus status = 0;
1003 CMSSignerStatus signerStatus;
1004 CMSDecoderRef cms = NULL;
1005 SecPolicyRef policy = NULL;
1006 SecTrustRef trust = NULL;
1007 CFDataRef content = NULL;
1008
1009 if ((content = CFDataCreateWithBytesNoCopy(kCFAllocatorDefault,
1010 (const UInt8 *)plistData, (CFIndex)plistLength, kCFAllocatorNull)) == NULL) {
1011 secdebug("validupdate", "CFDataCreateWithBytesNoCopy failed (%ld bytes)\n", (long)plistLength);
1012 return false;
1013 }
1014
1015 if ((status = CMSDecoderCreate(&cms)) != errSecSuccess) {
1016 secdebug("validupdate", "CMSDecoderCreate failed with error %d\n", (int)status);
1017 goto verifyExit;
1018 }
1019 if ((status = CMSDecoderUpdateMessage(cms, sigData, sigLength)) != errSecSuccess) {
1020 secdebug("validupdate", "CMSDecoderUpdateMessage failed with error %d\n", (int)status);
1021 goto verifyExit;
1022 }
1023 if ((status = CMSDecoderSetDetachedContent(cms, content)) != errSecSuccess) {
1024 secdebug("validupdate", "CMSDecoderSetDetachedContent failed with error %d\n", (int)status);
1025 goto verifyExit;
1026 }
1027 if ((status = CMSDecoderFinalizeMessage(cms)) != errSecSuccess) {
1028 secdebug("validupdate", "CMSDecoderFinalizeMessage failed with error %d\n", (int)status);
1029 goto verifyExit;
1030 }
1031
1032 policy = SecPolicyCreateApplePinned(CFSTR("ValidUpdate"), // kSecPolicyNameAppleValidUpdate
1033 CFSTR("1.2.840.113635.100.6.2.10"), // System Integration 2 Intermediate Certificate
1034 CFSTR("1.2.840.113635.100.6.51")); // Valid update signing OID
1035
1036 // Check that the first signer actually signed this message.
1037 if ((status = CMSDecoderCopySignerStatus(cms, 0, policy,
1038 false, &signerStatus, &trust, NULL)) != errSecSuccess) {
1039 secdebug("validupdate", "CMSDecoderCopySignerStatus failed with error %d\n", (int)status);
1040 goto verifyExit;
1041 }
1042 // Make sure the signature verifies against the detached content
1043 if (signerStatus != kCMSSignerValid) {
1044 secdebug("validupdate", "ERROR: signature did not verify (signer status %d)\n", (int)signerStatus);
1045 status = errSecInvalidSignature;
1046 goto verifyExit;
1047 }
1048 // Make sure the signing certificate is valid for the specified policy
1049 SecTrustResultType trustResult = kSecTrustResultInvalid;
1050 status = SecTrustEvaluate(trust, &trustResult);
1051 if (status != errSecSuccess) {
1052 secdebug("validupdate", "SecTrustEvaluate failed with error %d (trust=%p)\n", (int)status, (void *)trust);
1053 } else if (!(trustResult == kSecTrustResultUnspecified || trustResult == kSecTrustResultProceed)) {
1054 secdebug("validupdate", "SecTrustEvaluate failed with trust result %d\n", (int)trustResult);
1055 status = errSecVerificationFailure;
1056 goto verifyExit;
1057 }
1058
1059 verifyExit:
1060 CFReleaseSafe(content);
1061 CFReleaseSafe(trust);
1062 CFReleaseSafe(policy);
1063 CFReleaseSafe(cms);
1064
1065 return (status == errSecSuccess);
1066 }
1067
1068 CFAbsoluteTime SecRevocationDbComputeNextUpdateTime(CFIndex updateInterval) {
1069 CFIndex interval = updateInterval;
1070 // try to use interval preference if it exists
1071 CFTypeRef value = (CFNumberRef)CFPreferencesCopyValue(kUpdateIntervalKey, kSecPrefsDomain, kCFPreferencesAnyUser, kCFPreferencesCurrentHost);
1072 if (isNumber(value)) {
1073 CFNumberGetValue((CFNumberRef)value, kCFNumberCFIndexType, &interval);
1074 }
1075 CFReleaseNull(value);
1076
1077 if (interval <= 0) {
1078 interval = kSecStdUpdateInterval;
1079 }
1080
1081 // sanity check
1082 if (interval < kSecMinUpdateInterval) {
1083 interval = kSecMinUpdateInterval;
1084 } else if (interval > kSecMaxUpdateInterval) {
1085 interval = kSecMaxUpdateInterval;
1086 }
1087
1088 // compute randomization factor, between 0 and 50% of the interval
1089 CFIndex fuzz = arc4random() % (long)(interval/2.0);
1090 CFAbsoluteTime nextUpdate = CFAbsoluteTimeGetCurrent() + interval + fuzz;
1091 secdebug("validupdate", "next update in %ld seconds", (long)(interval + fuzz));
1092 return nextUpdate;
1093 }
1094
1095 void SecRevocationDbComputeAndSetNextUpdateTime(void) {
1096 gNextUpdate = SecRevocationDbComputeNextUpdateTime(0);
1097 SecRevocationDbSetNextUpdateTime(gNextUpdate);
1098 gUpdateStarted = 0; /* no update is currently in progress */
1099 }
1100
1101 CFIndex SecRevocationDbIngestUpdate(CFDictionaryRef update, CFIndex chunkVersion) {
1102 CFIndex version = 0;
1103 if (!update) {
1104 return version;
1105 }
1106 CFTypeRef value = (CFNumberRef)CFDictionaryGetValue(update, CFSTR("version"));
1107 if (isNumber(value)) {
1108 if (!CFNumberGetValue((CFNumberRef)value, kCFNumberCFIndexType, &version)) {
1109 version = 0;
1110 }
1111 }
1112 if (version == 0) {
1113 // only the first chunk will have a version, so the second and
1114 // subsequent chunks will need to pass it in chunkVersion.
1115 version = chunkVersion;
1116 }
1117 CFIndex curVersion = SecRevocationDbGetVersion();
1118 if (version > curVersion || chunkVersion > 0) {
1119 SecRevocationDbApplyUpdate(update, version);
1120 } else {
1121 secdebug("validupdate", "we have v%ld, skipping update to v%ld",
1122 (long)curVersion, (long)version);
1123 version = -1; // invalid, so we know to skip subsequent chunks
1124 }
1125 return version;
1126 }
1127
1128
1129 /* Database schema */
1130
1131 /* admin table holds these key-value (or key-ival) pairs:
1132 'version' (integer) // version of database content
1133 'check_again' (double) // CFAbsoluteTime of next check (optional; this value is currently stored in prefs)
1134 'db_version' (integer) // version of database schema
1135 'db_hash' (blob) // SHA-256 database hash
1136 --> entries in admin table are unique by text key
1137
1138 issuers table holds map of issuing CA hashes to group identifiers:
1139 groupid (integer) // associated group identifier in group ID table
1140 issuer_hash (blob) // SHA-256 hash of issuer certificate (primary key)
1141 --> entries in issuers table are unique by issuer_hash;
1142 multiple issuer entries may have the same groupid!
1143
1144 groups table holds records with these attributes:
1145 groupid (integer) // ordinal ID associated with this group entry
1146 flags (integer) // a bitmask of the following values:
1147 kSecValidInfoComplete (0x00000001) set if we have all revocation info for this issuer group
1148 kSecValidInfoCheckOCSP (0x00000002) set if must check ocsp for certs from this issuer group
1149 kSecValidInfoKnownOnly (0x00000004) set if any CA from this issuer group must be in database
1150 kSecValidInfoRequireCT (0x00000008) set if all certs from this issuer group must have SCTs
1151 kSecValidInfoAllowlist (0x00000010) set if this entry describes valid certs (i.e. is allowed)
1152 kSecValidInfoNoCACheck (0x00000020) set if this entry does not require an OCSP check to accept
1153 format (integer) // an integer describing format of entries:
1154 kSecValidInfoFormatUnknown (0) unknown format
1155 kSecValidInfoFormatSerial (1) serial number, not greater than 20 bytes in length
1156 kSecValidInfoFormatSHA256 (2) SHA-256 hash, 32 bytes in length
1157 kSecValidInfoFormatNto1 (3) filter data blob of arbitrary length
1158 data (blob) // Bloom filter data if format is 'nto1', otherwise NULL
1159 --> entries in groups table are unique by groupid
1160
1161 serials table holds serial number blobs with these attributes:
1162 rowid (integer) // ordinal ID associated with this serial number entry
1163 groupid (integer) // identifier for issuer group in the groups table
1164 serial (blob) // serial number
1165 --> entries in serials table are unique by serial and groupid
1166
1167 hashes table holds SHA-256 hashes of certificates with these attributes:
1168 rowid (integer) // ordinal ID associated with this sha256 hash entry
1169 groupid (integer) // identifier for issuer group in the groups table
1170 sha256 (blob) // SHA-256 hash of subject certificate
1171 --> entries in hashes table are unique by sha256 and groupid
1172 */
1173 #define createTablesSQL CFSTR("CREATE TABLE admin(" \
1174 "key TEXT PRIMARY KEY NOT NULL," \
1175 "ival INTEGER NOT NULL," \
1176 "value BLOB" \
1177 ");" \
1178 "CREATE TABLE issuers(" \
1179 "groupid INTEGER NOT NULL," \
1180 "issuer_hash BLOB PRIMARY KEY NOT NULL" \
1181 ");" \
1182 "CREATE INDEX issuer_idx ON issuers(issuer_hash);" \
1183 "CREATE TABLE groups(" \
1184 "groupid INTEGER PRIMARY KEY AUTOINCREMENT," \
1185 "flags INTEGER," \
1186 "format INTEGER," \
1187 "data BLOB" \
1188 ");" \
1189 "CREATE TABLE serials(" \
1190 "rowid INTEGER PRIMARY KEY AUTOINCREMENT," \
1191 "groupid INTEGER NOT NULL," \
1192 "serial BLOB NOT NULL," \
1193 "UNIQUE(groupid,serial)" \
1194 ");" \
1195 "CREATE TABLE hashes(" \
1196 "rowid INTEGER PRIMARY KEY AUTOINCREMENT," \
1197 "groupid INTEGER NOT NULL," \
1198 "sha256 BLOB NOT NULL," \
1199 "UNIQUE(groupid,sha256)" \
1200 ");" \
1201 "CREATE TRIGGER group_del BEFORE DELETE ON groups FOR EACH ROW " \
1202 "BEGIN " \
1203 "DELETE FROM serials WHERE groupid=OLD.groupid; " \
1204 "DELETE FROM hashes WHERE groupid=OLD.groupid; " \
1205 "DELETE FROM issuers WHERE groupid=OLD.groupid; " \
1206 "END;")
1207
1208 #define selectGroupIdSQL CFSTR("SELECT DISTINCT groupid " \
1209 "FROM issuers WHERE issuer_hash=?")
1210 #define selectVersionSQL CFSTR("SELECT ival FROM admin " \
1211 "WHERE key='version'")
1212 #define selectDbVersionSQL CFSTR("SELECT ival FROM admin " \
1213 "WHERE key='db_version'")
1214 #define selectDbFormatSQL CFSTR("SELECT ival FROM admin " \
1215 "WHERE key='db_format'")
1216 #define selectDbHashSQL CFSTR("SELECT value FROM admin " \
1217 "WHERE key='db_hash'")
1218 #define selectDbSourceSQL CFSTR("SELECT value FROM admin " \
1219 "WHERE key='db_source'")
1220 #define selectNextUpdateSQL CFSTR("SELECT value FROM admin " \
1221 "WHERE key='check_again'")
1222 #define selectGroupRecordSQL CFSTR("SELECT flags,format,data FROM " \
1223 "groups WHERE groupid=?")
1224 #define selectSerialRecordSQL CFSTR("SELECT rowid FROM serials " \
1225 "WHERE groupid=? AND serial=?")
1226 #define selectHashRecordSQL CFSTR("SELECT rowid FROM hashes " \
1227 "WHERE groupid=? AND sha256=?")
1228 #define insertAdminRecordSQL CFSTR("INSERT OR REPLACE INTO admin " \
1229 "(key,ival,value) VALUES (?,?,?)")
1230 #define insertIssuerRecordSQL CFSTR("INSERT OR REPLACE INTO issuers " \
1231 "(groupid,issuer_hash) VALUES (?,?)")
1232 #define insertGroupRecordSQL CFSTR("INSERT OR REPLACE INTO groups " \
1233 "(groupid,flags,format,data) VALUES (?,?,?,?)")
1234 #define insertSerialRecordSQL CFSTR("INSERT OR REPLACE INTO serials " \
1235 "(groupid,serial) VALUES (?,?)")
1236 #define insertSha256RecordSQL CFSTR("INSERT OR REPLACE INTO hashes " \
1237 "(groupid,sha256) VALUES (?,?)")
1238 #define deleteGroupRecordSQL CFSTR("DELETE FROM groups WHERE groupid=?")
1239
1240 #define deleteAllEntriesSQL CFSTR("DELETE from hashes; " \
1241 "DELETE from serials; DELETE from issuers; DELETE from groups; " \
1242 "DELETE from admin; DELETE from sqlite_sequence")
1243 #define deleteTablesSQL CFSTR("DROP TABLE hashes; " \
1244 "DROP TABLE serials; DROP TABLE issuers; DROP TABLE groups; " \
1245 "DROP TABLE admin; DELETE from sqlite_sequence")
1246
1247 /* Database management */
1248
1249 static SecDbRef SecRevocationDbCreate(CFStringRef path) {
1250 /* only the db owner should open a read-write connection. */
1251 bool readWrite = isDbOwner();
1252 mode_t mode = 0644;
1253
1254 SecDbRef result = SecDbCreateWithOptions(path, mode, readWrite, false, false, ^bool (SecDbRef db, SecDbConnectionRef dbconn, bool didCreate, bool *callMeAgainForNextConnection, CFErrorRef *error) {
1255 __block bool ok = true;
1256 CFErrorRef localError = NULL;
1257 if (ok && !SecDbWithSQL(dbconn, selectGroupIdSQL, &localError, NULL) && CFErrorGetCode(localError) == SQLITE_ERROR) {
1258 /* SecDbWithSQL returns SQLITE_ERROR if the table we are preparing the above statement for doesn't exist. */
1259
1260 /* Create all database tables, indexes, and triggers. */
1261 ok &= SecDbTransaction(dbconn, kSecDbExclusiveTransactionType, error, ^(bool *commit) {
1262 ok = SecDbExec(dbconn, createTablesSQL, error);
1263 *commit = ok;
1264 });
1265 }
1266 CFReleaseSafe(localError);
1267 if (!ok)
1268 secerror("%s failed: %@", didCreate ? "Create" : "Open", error ? *error : NULL);
1269 return ok;
1270 });
1271
1272 return result;
1273 }
1274
1275 typedef struct __SecRevocationDb *SecRevocationDbRef;
1276 struct __SecRevocationDb {
1277 SecDbRef db;
1278 dispatch_queue_t update_queue;
1279 bool updateInProgress;
1280 bool unsupportedVersion;
1281 };
1282
1283 static dispatch_once_t kSecRevocationDbOnce;
1284 static SecRevocationDbRef kSecRevocationDb = NULL;
1285
1286 static SecRevocationDbRef SecRevocationDbInit(CFStringRef db_name) {
1287 SecRevocationDbRef rdb;
1288 dispatch_queue_attr_t attr;
1289
1290 require(rdb = (SecRevocationDbRef)malloc(sizeof(struct __SecRevocationDb)), errOut);
1291 rdb->db = NULL;
1292 rdb->update_queue = NULL;
1293 rdb->updateInProgress = false;
1294 rdb->unsupportedVersion = false;
1295
1296 require(rdb->db = SecRevocationDbCreate(db_name), errOut);
1297 attr = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_BACKGROUND, 0);
1298 require(rdb->update_queue = dispatch_queue_create(NULL, attr), errOut);
1299
1300 return rdb;
1301
1302 errOut:
1303 secdebug("validupdate", "Failed to create db at \"%@\"", db_name);
1304 if (rdb) {
1305 if (rdb->update_queue) {
1306 dispatch_release(rdb->update_queue);
1307 }
1308 CFReleaseSafe(rdb->db);
1309 free(rdb);
1310 }
1311 return NULL;
1312 }
1313
1314 static CFStringRef SecRevocationDbCopyPath(void) {
1315 CFURLRef revDbURL = NULL;
1316 CFStringRef revInfoRelPath = NULL;
1317 if ((revInfoRelPath = CFStringCreateWithFormat(NULL, NULL, CFSTR("%s"), kSecRevocationDbFileName)) != NULL) {
1318 revDbURL = SecCopyURLForFileInRevocationInfoDirectory(revInfoRelPath);
1319 }
1320 CFReleaseSafe(revInfoRelPath);
1321
1322 CFStringRef revDbPath = NULL;
1323 if (revDbURL) {
1324 revDbPath = CFURLCopyFileSystemPath(revDbURL, kCFURLPOSIXPathStyle);
1325 CFRelease(revDbURL);
1326 }
1327 return revDbPath;
1328 }
1329
1330 static void SecRevocationDbWith(void(^dbJob)(SecRevocationDbRef db)) {
1331 dispatch_once(&kSecRevocationDbOnce, ^{
1332 CFStringRef dbPath = SecRevocationDbCopyPath();
1333 if (dbPath) {
1334 kSecRevocationDb = SecRevocationDbInit(dbPath);
1335 CFRelease(dbPath);
1336 }
1337 });
1338 // Do pre job run work here (cancel idle timers etc.)
1339 if (kSecRevocationDb->updateInProgress) {
1340 return; // this would block since SecDb has an exclusive transaction lock
1341 }
1342 dbJob(kSecRevocationDb);
1343 // Do post job run work here (gc timer, etc.)
1344 }
1345
1346 static int64_t _SecRevocationDbGetVersion(SecRevocationDbRef rdb, CFErrorRef *error) {
1347 /* look up version entry in admin table; returns -1 on error */
1348 __block int64_t version = -1;
1349 __block bool ok = true;
1350 __block CFErrorRef localError = NULL;
1351
1352 ok &= SecDbPerformRead(rdb->db, &localError, ^(SecDbConnectionRef dbconn) {
1353 if (ok) ok &= SecDbWithSQL(dbconn, selectVersionSQL, &localError, ^bool(sqlite3_stmt *selectVersion) {
1354 ok = SecDbStep(dbconn, selectVersion, &localError, NULL);
1355 version = sqlite3_column_int64(selectVersion, 0);
1356 return ok;
1357 });
1358 });
1359 (void) CFErrorPropagate(localError, error);
1360 return version;
1361 }
1362
1363 static void _SecRevocationDbSetVersion(SecRevocationDbRef rdb, CFIndex version){
1364 secdebug("validupdate", "setting version to %ld", (long)version);
1365
1366 __block CFErrorRef localError = NULL;
1367 __block bool ok = true;
1368 ok &= SecDbPerformWrite(rdb->db, &localError, ^(SecDbConnectionRef dbconn) {
1369 ok &= SecDbTransaction(dbconn, kSecDbExclusiveTransactionType, &localError, ^(bool *commit) {
1370 if (ok) ok = SecDbWithSQL(dbconn, insertAdminRecordSQL, &localError, ^bool(sqlite3_stmt *insertVersion) {
1371 if (ok) {
1372 const char *versionKey = "version";
1373 ok = SecDbBindText(insertVersion, 1, versionKey, strlen(versionKey),
1374 SQLITE_TRANSIENT, &localError);
1375 }
1376 if (ok) {
1377 ok = SecDbBindInt64(insertVersion, 2,
1378 (sqlite3_int64)version, &localError);
1379 }
1380 if (ok) {
1381 ok = SecDbStep(dbconn, insertVersion, &localError, NULL);
1382 }
1383 return ok;
1384 });
1385 });
1386 });
1387 if (!ok) {
1388 secerror("_SecRevocationDbSetVersion failed: %@", localError);
1389 }
1390 CFReleaseSafe(localError);
1391 }
1392
1393 static int64_t _SecRevocationDbGetSchemaVersion(SecRevocationDbRef rdb, CFErrorRef *error) {
1394 /* look up db_version entry in admin table; returns -1 on error */
1395 __block int64_t db_version = -1;
1396 __block bool ok = true;
1397 __block CFErrorRef localError = NULL;
1398
1399 ok &= SecDbPerformRead(rdb->db, &localError, ^(SecDbConnectionRef dbconn) {
1400 if (ok) ok &= SecDbWithSQL(dbconn, selectDbVersionSQL, &localError, ^bool(sqlite3_stmt *selectDbVersion) {
1401 ok = SecDbStep(dbconn, selectDbVersion, &localError, NULL);
1402 db_version = sqlite3_column_int64(selectDbVersion, 0);
1403 return ok;
1404 });
1405 });
1406 (void) CFErrorPropagate(localError, error);
1407 return db_version;
1408 }
1409
1410 static void _SecRevocationDbSetSchemaVersion(SecRevocationDbRef rdb, CFIndex dbversion) {
1411 secdebug("validupdate", "setting db_version to %ld", (long)dbversion);
1412
1413 __block CFErrorRef localError = NULL;
1414 __block bool ok = true;
1415 ok &= SecDbPerformWrite(rdb->db, &localError, ^(SecDbConnectionRef dbconn) {
1416 ok &= SecDbTransaction(dbconn, kSecDbExclusiveTransactionType, &localError, ^(bool *commit) {
1417 if (ok) ok = SecDbWithSQL(dbconn, insertAdminRecordSQL, &localError, ^bool(sqlite3_stmt *insertDbVersion) {
1418 if (ok) {
1419 const char *dbVersionKey = "db_version";
1420 ok = SecDbBindText(insertDbVersion, 1, dbVersionKey, strlen(dbVersionKey),
1421 SQLITE_TRANSIENT, &localError);
1422 }
1423 if (ok) {
1424 ok = SecDbBindInt64(insertDbVersion, 2,
1425 (sqlite3_int64)dbversion, &localError);
1426 }
1427 if (ok) {
1428 ok = SecDbStep(dbconn, insertDbVersion, &localError, NULL);
1429 }
1430 return ok;
1431 });
1432 });
1433 });
1434 if (!ok) {
1435 secerror("_SecRevocationDbSetSchemaVersion failed: %@", localError);
1436 } else {
1437 rdb->unsupportedVersion = false;
1438 }
1439 CFReleaseSafe(localError);
1440 }
1441
1442 static int64_t _SecRevocationDbGetUpdateFormat(SecRevocationDbRef rdb, CFErrorRef *error) {
1443 /* look up db_format entry in admin table; returns -1 on error */
1444 __block int64_t db_format = -1;
1445 __block bool ok = true;
1446 __block CFErrorRef localError = NULL;
1447
1448 ok &= SecDbPerformRead(rdb->db, &localError, ^(SecDbConnectionRef dbconn) {
1449 if (ok) ok &= SecDbWithSQL(dbconn, selectDbFormatSQL, &localError, ^bool(sqlite3_stmt *selectDbFormat) {
1450 ok = SecDbStep(dbconn, selectDbFormat, &localError, NULL);
1451 db_format = sqlite3_column_int64(selectDbFormat, 0);
1452 return ok;
1453 });
1454 });
1455 (void) CFErrorPropagate(localError, error);
1456 return db_format;
1457 }
1458
1459 static void _SecRevocationDbSetUpdateFormat(SecRevocationDbRef rdb, CFIndex dbformat) {
1460 secdebug("validupdate", "setting db_format to %ld", (long)dbformat);
1461
1462 __block CFErrorRef localError = NULL;
1463 __block bool ok = true;
1464 ok &= SecDbPerformWrite(rdb->db, &localError, ^(SecDbConnectionRef dbconn) {
1465 ok &= SecDbTransaction(dbconn, kSecDbExclusiveTransactionType, &localError, ^(bool *commit) {
1466 if (ok) ok = SecDbWithSQL(dbconn, insertAdminRecordSQL, &localError, ^bool(sqlite3_stmt *insertDbFormat) {
1467 if (ok) {
1468 const char *dbFormatKey = "db_format";
1469 ok = SecDbBindText(insertDbFormat, 1, dbFormatKey, strlen(dbFormatKey),
1470 SQLITE_TRANSIENT, &localError);
1471 }
1472 if (ok) {
1473 ok = SecDbBindInt64(insertDbFormat, 2,
1474 (sqlite3_int64)dbformat, &localError);
1475 }
1476 if (ok) {
1477 ok = SecDbStep(dbconn, insertDbFormat, &localError, NULL);
1478 }
1479 return ok;
1480 });
1481 });
1482 });
1483 if (!ok) {
1484 secerror("_SecRevocationDbSetUpdateFormat failed: %@", localError);
1485 } else {
1486 rdb->unsupportedVersion = false;
1487 }
1488 CFReleaseSafe(localError);
1489 }
1490
1491 static CFStringRef _SecRevocationDbCopyUpdateSource(SecRevocationDbRef rdb, CFErrorRef *error) {
1492 /* look up db_source entry in admin table; returns NULL on error */
1493 __block CFStringRef updateSource = NULL;
1494 __block bool ok = true;
1495 __block CFErrorRef localError = NULL;
1496
1497 ok &= SecDbPerformRead(rdb->db, &localError, ^(SecDbConnectionRef dbconn) {
1498 if (ok) ok &= SecDbWithSQL(dbconn, selectDbSourceSQL, &localError, ^bool(sqlite3_stmt *selectDbSource) {
1499 ok = SecDbStep(dbconn, selectDbSource, &localError, NULL);
1500 const UInt8 *p = (const UInt8 *)sqlite3_column_blob(selectDbSource, 0);
1501 if (p != NULL) {
1502 CFIndex length = (CFIndex)sqlite3_column_bytes(selectDbSource, 0);
1503 if (length > 0) {
1504 updateSource = CFStringCreateWithBytes(kCFAllocatorDefault, p, length, kCFStringEncodingUTF8, false);
1505 }
1506 }
1507 return ok;
1508 });
1509 });
1510
1511 (void) CFErrorPropagate(localError, error);
1512 return updateSource;
1513 }
1514
1515 static void _SecRevocationDbSetUpdateSource(SecRevocationDbRef rdb, CFStringRef updateSource) {
1516 if (!updateSource) {
1517 secerror("_SecRevocationDbSetUpdateSource failed: %d", errSecParam);
1518 return;
1519 }
1520 __block char buffer[256];
1521 __block const char *updateSourceCStr = CFStringGetCStringPtr(updateSource, kCFStringEncodingUTF8);
1522 if (!updateSourceCStr) {
1523 if (CFStringGetCString(updateSource, buffer, 256, kCFStringEncodingUTF8)) {
1524 updateSourceCStr = buffer;
1525 }
1526 }
1527 if (!updateSourceCStr) {
1528 secerror("_SecRevocationDbSetUpdateSource failed: unable to get UTF-8 encoding");
1529 return;
1530 }
1531 secdebug("validupdate", "setting update source to \"%s\"", updateSourceCStr);
1532
1533 __block CFErrorRef localError = NULL;
1534 __block bool ok = true;
1535 ok &= SecDbPerformWrite(rdb->db, &localError, ^(SecDbConnectionRef dbconn) {
1536 ok &= SecDbTransaction(dbconn, kSecDbExclusiveTransactionType, &localError, ^(bool *commit) {
1537 if (ok) ok = SecDbWithSQL(dbconn, insertAdminRecordSQL, &localError, ^bool(sqlite3_stmt *insertRecord) {
1538 if (ok) {
1539 const char *dbSourceKey = "db_source";
1540 ok = SecDbBindText(insertRecord, 1, dbSourceKey, strlen(dbSourceKey),
1541 SQLITE_TRANSIENT, &localError);
1542 }
1543 if (ok) {
1544 ok = SecDbBindInt64(insertRecord, 2,
1545 (sqlite3_int64)0, &localError);
1546 }
1547 if (ok) {
1548 ok = SecDbBindBlob(insertRecord, 3,
1549 updateSourceCStr, strlen(updateSourceCStr),
1550 SQLITE_TRANSIENT, &localError);
1551 }
1552 if (ok) {
1553 ok = SecDbStep(dbconn, insertRecord, &localError, NULL);
1554 }
1555 return ok;
1556 });
1557 });
1558 });
1559 if (!ok) {
1560 secerror("_SecRevocationDbSetUpdateSource failed: %@", localError);
1561 }
1562 CFReleaseSafe(localError);
1563 }
1564
1565 static CFAbsoluteTime _SecRevocationDbGetNextUpdateTime(SecRevocationDbRef rdb, CFErrorRef *error) {
1566 /* look up check_again entry in admin table; returns 0 on error */
1567 __block CFAbsoluteTime nextUpdate = 0;
1568 __block bool ok = true;
1569 __block CFErrorRef localError = NULL;
1570
1571 ok &= SecDbPerformRead(rdb->db, &localError, ^(SecDbConnectionRef dbconn) {
1572 if (ok) ok &= SecDbWithSQL(dbconn, selectNextUpdateSQL, &localError, ^bool(sqlite3_stmt *selectNextUpdate) {
1573 ok = SecDbStep(dbconn, selectNextUpdate, &localError, NULL);
1574 CFAbsoluteTime *p = (CFAbsoluteTime *)sqlite3_column_blob(selectNextUpdate, 0);
1575 if (p != NULL) {
1576 if (sizeof(CFAbsoluteTime) == sqlite3_column_bytes(selectNextUpdate, 0)) {
1577 nextUpdate = *p;
1578 }
1579 }
1580 return ok;
1581 });
1582 });
1583
1584 (void) CFErrorPropagate(localError, error);
1585 return nextUpdate;
1586 }
1587
1588 static void _SecRevocationDbSetNextUpdateTime(SecRevocationDbRef rdb, CFAbsoluteTime nextUpdate){
1589 secdebug("validupdate", "setting next update to %f", (double)nextUpdate);
1590
1591 __block CFErrorRef localError = NULL;
1592 __block bool ok = true;
1593 ok &= SecDbPerformWrite(rdb->db, &localError, ^(SecDbConnectionRef dbconn) {
1594 ok &= SecDbTransaction(dbconn, kSecDbExclusiveTransactionType, &localError, ^(bool *commit) {
1595 if (ok) ok = SecDbWithSQL(dbconn, insertAdminRecordSQL, &localError, ^bool(sqlite3_stmt *insertRecord) {
1596 if (ok) {
1597 const char *nextUpdateKey = "check_again";
1598 ok = SecDbBindText(insertRecord, 1, nextUpdateKey, strlen(nextUpdateKey),
1599 SQLITE_TRANSIENT, &localError);
1600 }
1601 if (ok) {
1602 ok = SecDbBindInt64(insertRecord, 2,
1603 (sqlite3_int64)0, &localError);
1604 }
1605 if (ok) {
1606 ok = SecDbBindBlob(insertRecord, 3,
1607 &nextUpdate, sizeof(CFAbsoluteTime),
1608 SQLITE_TRANSIENT, &localError);
1609 }
1610 if (ok) {
1611 ok = SecDbStep(dbconn, insertRecord, &localError, NULL);
1612 }
1613 return ok;
1614 });
1615 });
1616 });
1617 if (!ok) {
1618 secerror("_SecRevocationDbSetNextUpdate failed: %@", localError);
1619 }
1620 CFReleaseSafe(localError);
1621 }
1622
1623 static bool _SecRevocationDbRemoveAllEntries(SecRevocationDbRef rdb) {
1624 /* clear out the contents of the database and start fresh */
1625 __block bool ok = true;
1626 __block CFErrorRef localError = NULL;
1627
1628 ok &= SecDbPerformWrite(rdb->db, &localError, ^(SecDbConnectionRef dbconn) {
1629 ok &= SecDbTransaction(dbconn, kSecDbExclusiveTransactionType, &localError, ^(bool *commit) {
1630 //ok &= SecDbWithSQL(dbconn, deleteAllEntriesSQL, &localError, ^bool(sqlite3_stmt *deleteAll) {
1631 // ok = SecDbStep(dbconn, deleteAll, &localError, NULL);
1632 // return ok;
1633 //});
1634 /* drop all tables and recreate them, in case of schema changes */
1635 ok &= SecDbExec(dbconn, deleteTablesSQL, &localError);
1636 ok &= SecDbExec(dbconn, createTablesSQL, &localError);
1637 secdebug("validupdate", "resetting database, result: %d", (ok) ? 1 : 0);
1638 *commit = ok;
1639 });
1640 /* compact the db (must be done outside transaction scope) */
1641 SecDbExec(dbconn, CFSTR("VACUUM"), &localError);
1642 });
1643 /* one more thing: update the schema version and format to current */
1644 _SecRevocationDbSetSchemaVersion(rdb, kSecRevocationDbSchemaVersion);
1645 _SecRevocationDbSetUpdateFormat(rdb, kSecRevocationDbUpdateFormat);
1646
1647 CFReleaseSafe(localError);
1648 return ok;
1649 }
1650
1651 static bool _SecRevocationDbUpdateIssuers(SecRevocationDbRef rdb, int64_t groupId, CFArrayRef issuers, CFErrorRef *error) {
1652 /* insert or replace issuer records in issuers table */
1653 if (!issuers || groupId < 0) {
1654 return false; /* must have something to insert, and a group to associate with it */
1655 }
1656 __block bool ok = true;
1657 __block CFErrorRef localError = NULL;
1658
1659 ok &= SecDbPerformWrite(rdb->db, &localError, ^(SecDbConnectionRef dbconn) {
1660 ok &= SecDbTransaction(dbconn, kSecDbExclusiveTransactionType, &localError, ^(bool *commit) {
1661 if (isArray(issuers)) {
1662 CFIndex issuerIX, issuerCount = CFArrayGetCount(issuers);
1663 for (issuerIX=0; issuerIX<issuerCount && ok; issuerIX++) {
1664 CFDataRef hash = (CFDataRef)CFArrayGetValueAtIndex(issuers, issuerIX);
1665 if (!hash) { continue; }
1666 if (ok) ok = SecDbWithSQL(dbconn, insertIssuerRecordSQL, &localError, ^bool(sqlite3_stmt *insertIssuer) {
1667 if (ok) {
1668 ok = SecDbBindInt64(insertIssuer, 1,
1669 groupId, &localError);
1670 }
1671 if (ok) {
1672 ok = SecDbBindBlob(insertIssuer, 2,
1673 CFDataGetBytePtr(hash),
1674 CFDataGetLength(hash),
1675 SQLITE_TRANSIENT, &localError);
1676 }
1677 /* Execute the insert statement for this issuer record. */
1678 if (ok) {
1679 ok = SecDbStep(dbconn, insertIssuer, &localError, NULL);
1680 }
1681 return ok;
1682 });
1683 }
1684 }
1685 });
1686 });
1687
1688 (void) CFErrorPropagate(localError, error);
1689 return ok;
1690 }
1691
1692 static bool _SecRevocationDbUpdatePerIssuerData(SecRevocationDbRef rdb, int64_t groupId, CFDictionaryRef dict, CFErrorRef *error) {
1693 /* update/delete records in serials or hashes table. */
1694 if (!dict || groupId < 0) {
1695 return false; /* must have something to insert, and a group to associate with it */
1696 }
1697 __block bool ok = true;
1698 __block CFErrorRef localError = NULL;
1699
1700 ok &= SecDbPerformWrite(rdb->db, &localError, ^(SecDbConnectionRef dbconn) {
1701 ok &= SecDbTransaction(dbconn, kSecDbExclusiveTransactionType, &localError, ^(bool *commit) {
1702 CFArrayRef deleteArray = (CFArrayRef)CFDictionaryGetValue(dict, CFSTR("delete"));
1703 /* process deletions */
1704 if (isArray(deleteArray)) {
1705 //%%% delete old data here (rdar://31439625)
1706 }
1707 /* process additions */
1708 CFArrayRef addArray = (CFArrayRef)CFDictionaryGetValue(dict, CFSTR("add"));
1709 if (isArray(addArray)) {
1710 CFIndex identifierIX, identifierCount = CFArrayGetCount(addArray);
1711 for (identifierIX=0; identifierIX<identifierCount; identifierIX++) {
1712 CFDataRef identifierData = (CFDataRef)CFArrayGetValueAtIndex(addArray, identifierIX);
1713 if (!identifierData) { continue; }
1714 CFIndex length = CFDataGetLength(identifierData);
1715 /* we can figure out the format without an extra read to get the format column.
1716 len <= 20 is a serial number. len==32 is a sha256 hash. otherwise: xor. */
1717 CFStringRef sql = NULL;
1718 if (length <= 20) {
1719 sql = insertSerialRecordSQL;
1720 } else if (length == 32) {
1721 sql = insertSha256RecordSQL;
1722 }
1723 if (!sql) { continue; }
1724
1725 if (ok) ok = SecDbWithSQL(dbconn, sql, &localError, ^bool(sqlite3_stmt *insertIdentifier) {
1726 /* rowid,(groupid,serial|sha256) */
1727 /* rowid is autoincremented and we never set it directly */
1728 if (ok) {
1729 ok = SecDbBindInt64(insertIdentifier, 1,
1730 groupId, &localError);
1731 }
1732 if (ok) {
1733 ok = SecDbBindBlob(insertIdentifier, 2,
1734 CFDataGetBytePtr(identifierData),
1735 CFDataGetLength(identifierData),
1736 SQLITE_TRANSIENT, &localError);
1737 }
1738 /* Execute the insert statement for the identifier record. */
1739 if (ok) {
1740 ok = SecDbStep(dbconn, insertIdentifier, &localError, NULL);
1741 }
1742 return ok;
1743 });
1744 }
1745 }
1746 });
1747 });
1748
1749 (void) CFErrorPropagate(localError, error);
1750 return ok;
1751 }
1752
1753 static SecValidInfoFormat _SecRevocationDbGetGroupFormat(SecRevocationDbRef rdb,
1754 int64_t groupId, SecValidInfoFlags *flags, CFDataRef *data, CFErrorRef *error) {
1755 /* return group record fields for a given groupId.
1756 on success, returns a non-zero format type, and other field values in optional output parameters.
1757 caller is responsible for releasing data and error parameters, if provided.
1758 */
1759 __block bool ok = true;
1760 __block SecValidInfoFormat format = 0;
1761 __block CFErrorRef localError = NULL;
1762
1763 /* Select the group record to determine flags and format. */
1764 ok &= SecDbPerformRead(rdb->db, &localError, ^(SecDbConnectionRef dbconn) {
1765 ok &= SecDbWithSQL(dbconn, selectGroupRecordSQL, &localError, ^bool(sqlite3_stmt *selectGroup) {
1766 ok = SecDbBindInt64(selectGroup, 1, groupId, &localError);
1767 ok &= SecDbStep(dbconn, selectGroup, &localError, ^(bool *stop) {
1768 if (flags) {
1769 *flags = (SecValidInfoFlags)sqlite3_column_int(selectGroup, 0);
1770 }
1771 format = (SecValidInfoFormat)sqlite3_column_int(selectGroup, 1);
1772 if (data) {
1773 //TODO: stream this from sqlite through the inflation so we return an inflated copy, then remove inflate from others
1774 uint8_t *p = (uint8_t *)sqlite3_column_blob(selectGroup, 2);
1775 if (p != NULL && format == kSecValidInfoFormatNto1) {
1776 CFIndex length = (CFIndex)sqlite3_column_bytes(selectGroup, 2);
1777 *data = CFDataCreate(kCFAllocatorDefault, p, length);
1778 }
1779 }
1780 });
1781 return ok;
1782 });
1783 });
1784 if (!ok) {
1785 secdebug("validupdate", "GetGroupFormat for groupId %lu failed", (unsigned long)groupId);
1786 format = kSecValidInfoFormatUnknown;
1787 }
1788 (void) CFErrorPropagate(localError, error);
1789 if (!(format > kSecValidInfoFormatUnknown)) {
1790 secdebug("validupdate", "GetGroupFormat: got format %d for groupId %lld", format, (long long)groupId);
1791 }
1792 return format;
1793 }
1794
1795 static bool _SecRevocationDbUpdateFlags(CFDictionaryRef dict, CFStringRef key, SecValidInfoFlags mask, SecValidInfoFlags *flags) {
1796 /* If a boolean value exists in the given dictionary for the given key,
1797 set or clear the corresponding bit(s) defined by the mask argument.
1798 Function returns true if the flags value was changed, false otherwise.
1799 */
1800 bool result = false;
1801 CFTypeRef value = (CFBooleanRef)CFDictionaryGetValue(dict, key);
1802 if (isBoolean(value) && flags) {
1803 SecValidInfoFlags oldFlags = *flags;
1804 if (CFBooleanGetValue((CFBooleanRef)value)) {
1805 *flags |= mask;
1806 } else {
1807 *flags &= ~(mask);
1808 }
1809 result = (*flags != oldFlags);
1810 }
1811 return result;
1812 }
1813
1814 static bool _SecRevocationDbUpdateFilter(CFDictionaryRef dict, CFDataRef oldData, CFDataRef * __nonnull CF_RETURNS_RETAINED xmlData) {
1815 /* If xor and/or params values exist in the given dictionary, create a new
1816 property list containing the updated values, and return as a flattened
1817 data blob in the xmlData output parameter (note: caller must release.)
1818 Function returns true if there is new xmlData to save, false otherwise.
1819 */
1820 bool result = false;
1821 bool xorProvided = false;
1822 bool paramsProvided = false;
1823 bool missingData = false;
1824
1825 if (!dict || !xmlData) {
1826 return result; /* no-op if no dictionary is provided, or no way to update the data */
1827 }
1828 *xmlData = NULL;
1829 CFDataRef xorCurrent = NULL;
1830 CFDataRef xorUpdate = (CFDataRef)CFDictionaryGetValue(dict, CFSTR("xor"));
1831 if (isData(xorUpdate)) {
1832 xorProvided = true;
1833 }
1834 CFArrayRef paramsCurrent = NULL;
1835 CFArrayRef paramsUpdate = (CFArrayRef)CFDictionaryGetValue(dict, CFSTR("params"));
1836 if (isArray(paramsUpdate)) {
1837 paramsProvided = true;
1838 }
1839 if (!(xorProvided || paramsProvided)) {
1840 return result; /* nothing to update, so we can bail out here. */
1841 }
1842
1843 CFPropertyListRef nto1Current = NULL;
1844 CFMutableDictionaryRef nto1Update = CFDictionaryCreateMutable(kCFAllocatorDefault, 0,
1845 &kCFTypeDictionaryKeyCallBacks,
1846 &kCFTypeDictionaryValueCallBacks);
1847 if (!nto1Update) {
1848 return result;
1849 }
1850
1851 /* turn old data into property list */
1852 CFDataRef data = (CFDataRef)CFRetainSafe(oldData);
1853 CFDataRef inflatedData = copyInflatedData(data);
1854 if (inflatedData) {
1855 CFReleaseSafe(data);
1856 data = inflatedData;
1857 }
1858 if (data) {
1859 nto1Current = CFPropertyListCreateWithData(kCFAllocatorDefault, data, 0, NULL, NULL);
1860 CFReleaseSafe(data);
1861 }
1862 if (nto1Current) {
1863 xorCurrent = (CFDataRef)CFDictionaryGetValue((CFDictionaryRef)nto1Current, CFSTR("xor"));
1864 paramsCurrent = (CFArrayRef)CFDictionaryGetValue((CFDictionaryRef)nto1Current, CFSTR("params"));
1865 }
1866
1867 /* set current or updated xor data in new property list */
1868 if (xorProvided) {
1869 CFDataRef xorNew = NULL;
1870 if (xorCurrent) {
1871 CFIndex xorUpdateLen = CFDataGetLength(xorUpdate);
1872 CFMutableDataRef xor = CFDataCreateMutableCopy(NULL, 0, xorCurrent);
1873 if (xor && xorUpdateLen > 0) {
1874 /* truncate or zero-extend data to match update size */
1875 CFDataSetLength(xor, xorUpdateLen);
1876 /* exclusive-or update bytes over the existing data */
1877 UInt8 *xorP = (UInt8 *)CFDataGetMutableBytePtr(xor);
1878 UInt8 *updP = (UInt8 *)CFDataGetBytePtr(xorUpdate);
1879 if (xorP && updP) {
1880 for (int idx = 0; idx < xorUpdateLen; idx++) {
1881 xorP[idx] = xorP[idx] ^ updP[idx];
1882 }
1883 }
1884 }
1885 xorNew = (CFDataRef)xor;
1886 } else {
1887 xorNew = (CFDataRef)CFRetainSafe(xorUpdate);
1888 }
1889 if (xorNew) {
1890 CFDictionaryAddValue(nto1Update, CFSTR("xor"), xorNew);
1891 CFReleaseSafe(xorNew);
1892 } else {
1893 secdebug("validupdate", "Failed to get updated filter data");
1894 missingData = true;
1895 }
1896 } else if (xorCurrent) {
1897 /* not provided, so use existing xor value */
1898 CFDictionaryAddValue(nto1Update, CFSTR("xor"), xorCurrent);
1899 } else {
1900 secdebug("validupdate", "Failed to get current filter data");
1901 missingData = true;
1902 }
1903
1904 /* set current or updated params in new property list */
1905 if (paramsProvided) {
1906 CFDictionaryAddValue(nto1Update, CFSTR("params"), paramsUpdate);
1907 } else if (paramsCurrent) {
1908 /* not provided, so use existing params value */
1909 CFDictionaryAddValue(nto1Update, CFSTR("params"), paramsCurrent);
1910 } else {
1911 /* missing params: neither provided nor existing */
1912 secdebug("validupdate", "Failed to get current filter params");
1913 missingData = true;
1914 }
1915
1916 CFReleaseSafe(nto1Current);
1917 if (!missingData) {
1918 *xmlData = CFPropertyListCreateData(kCFAllocatorDefault, nto1Update,
1919 kCFPropertyListXMLFormat_v1_0,
1920 0, NULL);
1921 result = (*xmlData != NULL);
1922 }
1923 CFReleaseSafe(nto1Update);
1924
1925 /* compress the xmlData blob, if possible */
1926 if (result) {
1927 CFDataRef deflatedData = copyDeflatedData(*xmlData);
1928 if (deflatedData) {
1929 if (CFDataGetLength(deflatedData) < CFDataGetLength(*xmlData)) {
1930 CFRelease(*xmlData);
1931 *xmlData = deflatedData;
1932 } else {
1933 CFRelease(deflatedData);
1934 }
1935 }
1936 }
1937 return result;
1938 }
1939
1940
1941 static int64_t _SecRevocationDbUpdateGroup(SecRevocationDbRef rdb, int64_t groupId, CFDictionaryRef dict, CFErrorRef *error) {
1942 /* insert group record for a given groupId.
1943 if the specified groupId is < 0, a new group entry is created.
1944 returns the groupId on success, or -1 on failure.
1945 */
1946 if (!dict) {
1947 return groupId; /* no-op if no dictionary is provided */
1948 }
1949
1950 __block int64_t result = -1;
1951 __block bool ok = true;
1952 __block bool isFormatChange = false;
1953 __block CFErrorRef localError = NULL;
1954
1955 __block SecValidInfoFlags flags = 0;
1956 __block SecValidInfoFormat format = kSecValidInfoFormatUnknown;
1957 __block SecValidInfoFormat formatUpdate = kSecValidInfoFormatUnknown;
1958 __block CFDataRef data = NULL;
1959
1960 if (groupId >= 0) {
1961 /* fetch the flags and data for an existing group record, in case some are being changed. */
1962 format = _SecRevocationDbGetGroupFormat(rdb, groupId, &flags, &data, NULL);
1963 if (format == kSecValidInfoFormatUnknown) {
1964 secdebug("validupdate", "existing group %lld has unknown format %d, flags=%lu",
1965 (long long)groupId, format, flags);
1966 //%%% clean up by deleting all issuers with this groupId, then the group record,
1967 // or just force a full update? note: we can get here if we fail to bind the
1968 // format value in the prepared SQL statement below.
1969 return -1;
1970 }
1971 }
1972 CFTypeRef value = (CFStringRef)CFDictionaryGetValue(dict, CFSTR("format"));
1973 if (isString(value)) {
1974 if (CFStringCompare((CFStringRef)value, CFSTR("serial"), 0) == kCFCompareEqualTo) {
1975 formatUpdate = kSecValidInfoFormatSerial;
1976 } else if (CFStringCompare((CFStringRef)value, CFSTR("sha256"), 0) == kCFCompareEqualTo) {
1977 formatUpdate = kSecValidInfoFormatSHA256;
1978 } else if (CFStringCompare((CFStringRef)value, CFSTR("nto1"), 0) == kCFCompareEqualTo) {
1979 formatUpdate = kSecValidInfoFormatNto1;
1980 }
1981 }
1982 /* if format value is explicitly supplied, then this is effectively a new group entry. */
1983 isFormatChange = (formatUpdate > kSecValidInfoFormatUnknown &&
1984 formatUpdate != format &&
1985 groupId >= 0);
1986
1987 ok &= SecDbPerformWrite(rdb->db, &localError, ^(SecDbConnectionRef dbconn) {
1988 ok &= SecDbTransaction(dbconn, kSecDbExclusiveTransactionType, &localError, ^(bool *commit) {
1989 if (isFormatChange) {
1990 secdebug("validupdate", "group %lld format change from %d to %d",
1991 (long long)groupId, format, formatUpdate);
1992 /* format of an existing group is changing; delete the group first.
1993 this should ensure that all entries referencing the old groupid are deleted.
1994 */
1995 ok &= SecDbWithSQL(dbconn, deleteGroupRecordSQL, &localError, ^bool(sqlite3_stmt *deleteResponse) {
1996 ok = SecDbBindInt64(deleteResponse, 1, groupId, &localError);
1997 /* Execute the delete statement. */
1998 if (ok) {
1999 ok = SecDbStep(dbconn, deleteResponse, &localError, NULL);
2000 }
2001 return ok;
2002 });
2003 }
2004 ok &= SecDbWithSQL(dbconn, insertGroupRecordSQL, &localError, ^bool(sqlite3_stmt *insertGroup) {
2005 /* (groupid,flags,format,data) */
2006 /* groups.groupid */
2007 if (ok && (!isFormatChange) && (groupId >= 0)) {
2008 /* bind to existing groupId row if known, otherwise will insert and autoincrement */
2009 ok = SecDbBindInt64(insertGroup, 1, groupId, &localError);
2010 if (!ok) {
2011 secdebug("validupdate", "failed to set groupId %lld", (long long)groupId);
2012 }
2013 }
2014 /* groups.flags */
2015 if (ok) {
2016 (void)_SecRevocationDbUpdateFlags(dict, CFSTR("complete"), kSecValidInfoComplete, &flags);
2017 (void)_SecRevocationDbUpdateFlags(dict, CFSTR("check-ocsp"), kSecValidInfoCheckOCSP, &flags);
2018 (void)_SecRevocationDbUpdateFlags(dict, CFSTR("known-intermediates-only"), kSecValidInfoKnownOnly, &flags);
2019 (void)_SecRevocationDbUpdateFlags(dict, CFSTR("require-ct"), kSecValidInfoRequireCT, &flags);
2020 (void)_SecRevocationDbUpdateFlags(dict, CFSTR("valid"), kSecValidInfoAllowlist, &flags);
2021 (void)_SecRevocationDbUpdateFlags(dict, CFSTR("no-ca"), kSecValidInfoNoCACheck, &flags);
2022
2023 ok = SecDbBindInt(insertGroup, 2, (int)flags, &localError);
2024 if (!ok) {
2025 secdebug("validupdate", "failed to set flags (%lu) for groupId %lld", flags, (long long)groupId);
2026 }
2027 }
2028 /* groups.format */
2029 if (ok) {
2030 SecValidInfoFormat formatValue = format;
2031 if (formatUpdate > kSecValidInfoFormatUnknown) {
2032 formatValue = formatUpdate;
2033 }
2034 ok = SecDbBindInt(insertGroup, 3, (int)formatValue, &localError);
2035 if (!ok) {
2036 secdebug("validupdate", "failed to set format (%d) for groupId %lld", formatValue, (long long)groupId);
2037 }
2038 }
2039 /* groups.data */
2040 CFDataRef xmlData = NULL;
2041 if (ok) {
2042 bool hasFilter = ((formatUpdate == kSecValidInfoFormatNto1) ||
2043 (formatUpdate == kSecValidInfoFormatUnknown &&
2044 format == kSecValidInfoFormatNto1));
2045 if (hasFilter) {
2046 CFDataRef dataValue = data; /* use existing data */
2047 if (_SecRevocationDbUpdateFilter(dict, data, &xmlData)) {
2048 dataValue = xmlData; /* use updated data */
2049 }
2050 if (dataValue) {
2051 ok = SecDbBindBlob(insertGroup, 4,
2052 CFDataGetBytePtr(dataValue),
2053 CFDataGetLength(dataValue),
2054 SQLITE_TRANSIENT, &localError);
2055 }
2056 if (!ok) {
2057 secdebug("validupdate", "failed to set data for groupId %lld",
2058 (long long)groupId);
2059 }
2060 }
2061 /* else there is no data, so NULL is implicitly bound to column 4 */
2062 }
2063
2064 /* Execute the insert statement for the group record. */
2065 if (ok) {
2066 ok = SecDbStep(dbconn, insertGroup, &localError, NULL);
2067 if (!ok) {
2068 secdebug("validupdate", "failed to execute insertGroup statement for groupId %lld",
2069 (long long)groupId);
2070 }
2071 result = (int64_t)sqlite3_last_insert_rowid(SecDbHandle(dbconn));
2072 }
2073 if (!ok) {
2074 secdebug("validupdate", "failed to insert group %lld", (long long)result);
2075 }
2076 /* Clean up temporary allocation made in this block. */
2077 CFReleaseSafe(xmlData);
2078 CFReleaseSafe(data);
2079 return ok;
2080 });
2081 });
2082 });
2083
2084 (void) CFErrorPropagate(localError, error);
2085 return result;
2086 }
2087
2088 static int64_t _SecRevocationDbGroupIdForIssuerHash(SecRevocationDbRef rdb, CFDataRef hash, CFErrorRef *error) {
2089 /* look up issuer hash in issuers table to get groupid, if it exists */
2090 __block int64_t groupId = -1;
2091 __block bool ok = true;
2092 __block CFErrorRef localError = NULL;
2093
2094 if (!hash) {
2095 secdebug("validupdate", "failed to get hash (%@)", hash);
2096 }
2097 require(hash, errOut);
2098
2099 /* This is the starting point for any lookup; find a group id for the given issuer hash.
2100 Before we do that, need to verify the current db_version. We cannot use results from a
2101 database created with a schema version older than the minimum supported version.
2102 However, we may be able to use results from a newer version. At the next database
2103 update interval, if the existing schema is old, we'll be removing and recreating
2104 the database contents with the current schema version.
2105 */
2106 int64_t db_version = _SecRevocationDbGetSchemaVersion(rdb, NULL);
2107 if (db_version < kSecRevocationDbMinSchemaVersion) {
2108 if (!rdb->unsupportedVersion) {
2109 secdebug("validupdate", "unsupported db_version: %lld", (long long)db_version);
2110 rdb->unsupportedVersion = true; /* only warn once for a given unsupported version */
2111 }
2112 }
2113 require_quiet(db_version >= kSecRevocationDbMinSchemaVersion, errOut);
2114
2115 /* Look up provided issuer_hash in the issuers table.
2116 */
2117 ok &= SecDbPerformRead(rdb->db, &localError, ^(SecDbConnectionRef dbconn) {
2118 ok &= SecDbWithSQL(dbconn, selectGroupIdSQL, &localError, ^bool(sqlite3_stmt *selectGroupId) {
2119 ok = SecDbBindBlob(selectGroupId, 1, CFDataGetBytePtr(hash), CFDataGetLength(hash), SQLITE_TRANSIENT, &localError);
2120 ok &= SecDbStep(dbconn, selectGroupId, &localError, ^(bool *stopGroupId) {
2121 groupId = sqlite3_column_int64(selectGroupId, 0);
2122 });
2123 return ok;
2124 });
2125 });
2126
2127 errOut:
2128 (void) CFErrorPropagate(localError, error);
2129 return groupId;
2130 }
2131
2132 static bool _SecRevocationDbApplyGroupDelete(SecRevocationDbRef rdb, CFDataRef issuerHash, CFErrorRef *error) {
2133 /* delete group associated with the given issuer;
2134 schema trigger will delete associated issuers, serials, and hashes. */
2135 __block int64_t groupId = -1;
2136 __block bool ok = true;
2137 __block CFErrorRef localError = NULL;
2138
2139 groupId = _SecRevocationDbGroupIdForIssuerHash(rdb, issuerHash, &localError);
2140 require(!(groupId < 0), errOut);
2141
2142 ok &= SecDbPerformWrite(rdb->db, &localError, ^(SecDbConnectionRef dbconn) {
2143 ok &= SecDbTransaction(dbconn, kSecDbExclusiveTransactionType, &localError, ^(bool *commit) {
2144 ok = SecDbWithSQL(dbconn, deleteGroupRecordSQL, &localError, ^bool(sqlite3_stmt *deleteResponse) {
2145 ok = SecDbBindInt64(deleteResponse, 1, groupId, &localError);
2146 /* Execute the delete statement. */
2147 if (ok) {
2148 ok = SecDbStep(dbconn, deleteResponse, &localError, NULL);
2149 }
2150 return ok;
2151 });
2152 });
2153 });
2154
2155 errOut:
2156 (void) CFErrorPropagate(localError, error);
2157 return (groupId < 0) ? false : true;
2158 }
2159
2160 static bool _SecRevocationDbApplyGroupUpdate(SecRevocationDbRef rdb, CFDictionaryRef dict, CFErrorRef *error) {
2161 /* process one issuer group's update dictionary */
2162 int64_t groupId = -1;
2163 CFErrorRef localError = NULL;
2164
2165 CFArrayRef issuers = (dict) ? (CFArrayRef)CFDictionaryGetValue(dict, CFSTR("issuer-hash")) : NULL;
2166 if (isArray(issuers)) {
2167 CFIndex issuerIX, issuerCount = CFArrayGetCount(issuers);
2168 /* while we have issuers and haven't found a matching group id */
2169 for (issuerIX=0; issuerIX<issuerCount && groupId < 0; issuerIX++) {
2170 CFDataRef hash = (CFDataRef)CFArrayGetValueAtIndex(issuers, issuerIX);
2171 if (!hash) { continue; }
2172 groupId = _SecRevocationDbGroupIdForIssuerHash(rdb, hash, &localError);
2173 }
2174 }
2175 /* create or update the group entry */
2176 groupId = _SecRevocationDbUpdateGroup(rdb, groupId, dict, &localError);
2177 if (groupId < 0) {
2178 secdebug("validupdate", "failed to get groupId");
2179 } else {
2180 /* create or update issuer entries, now that we know the group id */
2181 _SecRevocationDbUpdateIssuers(rdb, groupId, issuers, &localError);
2182 /* create or update entries in serials or hashes tables */
2183 _SecRevocationDbUpdatePerIssuerData(rdb, groupId, dict, &localError);
2184 }
2185
2186 (void) CFErrorPropagate(localError, error);
2187 return (groupId > 0) ? true : false;
2188 }
2189
2190 static void _SecRevocationDbApplyUpdate(SecRevocationDbRef rdb, CFDictionaryRef update, CFIndex version) {
2191 /* process entire update dictionary */
2192 if (!rdb || !update) {
2193 secerror("_SecRevocationDbApplyUpdate failed: invalid args");
2194 return;
2195 }
2196
2197 __block CFDictionaryRef localUpdate = (CFDictionaryRef)CFRetainSafe(update);
2198 __block CFErrorRef localError = NULL;
2199
2200 CFTypeRef value = NULL;
2201 CFIndex deleteCount = 0;
2202 CFIndex updateCount = 0;
2203
2204 rdb->updateInProgress = true;
2205
2206 /* check whether this is a full update */
2207 value = (CFBooleanRef)CFDictionaryGetValue(update, CFSTR("full"));
2208 if (isBoolean(value) && CFBooleanGetValue((CFBooleanRef)value)) {
2209 /* clear the database before processing a full update */
2210 SecRevocationDbRemoveAllEntries();
2211 }
2212
2213 /* process 'delete' list */
2214 value = (CFArrayRef)CFDictionaryGetValue(localUpdate, CFSTR("delete"));
2215 if (isArray(value)) {
2216 deleteCount = CFArrayGetCount((CFArrayRef)value);
2217 secdebug("validupdate", "processing %ld deletes", (long)deleteCount);
2218 for (CFIndex deleteIX=0; deleteIX<deleteCount; deleteIX++) {
2219 CFDataRef issuerHash = (CFDataRef)CFArrayGetValueAtIndex((CFArrayRef)value, deleteIX);
2220 if (isData(issuerHash)) {
2221 (void)_SecRevocationDbApplyGroupDelete(rdb, issuerHash, &localError);
2222 CFReleaseNull(localError);
2223 }
2224 }
2225 }
2226
2227 /* process 'update' list */
2228 value = (CFArrayRef)CFDictionaryGetValue(localUpdate, CFSTR("update"));
2229 if (isArray(value)) {
2230 updateCount = CFArrayGetCount((CFArrayRef)value);
2231 secdebug("validupdate", "processing %ld updates", (long)updateCount);
2232 for (CFIndex updateIX=0; updateIX<updateCount; updateIX++) {
2233 CFDictionaryRef dict = (CFDictionaryRef)CFArrayGetValueAtIndex((CFArrayRef)value, updateIX);
2234 if (isDictionary(dict)) {
2235 (void)_SecRevocationDbApplyGroupUpdate(rdb, dict, &localError);
2236 CFReleaseNull(localError);
2237 }
2238 }
2239 }
2240 CFRelease(localUpdate);
2241
2242 /* set version */
2243 _SecRevocationDbSetVersion(rdb, version);
2244
2245 /* set db_version if not already set */
2246 int64_t db_version = _SecRevocationDbGetSchemaVersion(rdb, NULL);
2247 if (db_version <= 0) {
2248 _SecRevocationDbSetSchemaVersion(rdb, kSecRevocationDbSchemaVersion);
2249 }
2250
2251 /* set db_format if not already set */
2252 int64_t db_format = _SecRevocationDbGetUpdateFormat(rdb, NULL);
2253 if (db_format <= 0) {
2254 _SecRevocationDbSetUpdateFormat(rdb, kSecRevocationDbUpdateFormat);
2255 }
2256
2257 /* compact the db (must be done outside transaction scope) */
2258 (void)SecDbPerformWrite(rdb->db, &localError, ^(SecDbConnectionRef dbconn) {
2259 SecDbExec(dbconn, CFSTR("VACUUM"), &localError);
2260 CFReleaseNull(localError);
2261 });
2262
2263 rdb->updateInProgress = false;
2264 }
2265
2266 static bool _SecRevocationDbSerialInGroup(SecRevocationDbRef rdb,
2267 CFDataRef serial,
2268 int64_t groupId,
2269 CFErrorRef *error) {
2270 __block bool result = false;
2271 __block bool ok = true;
2272 __block CFErrorRef localError = NULL;
2273 require(rdb && serial, errOut);
2274 ok &= SecDbPerformRead(rdb->db, &localError, ^(SecDbConnectionRef dbconn) {
2275 ok &= SecDbWithSQL(dbconn, selectSerialRecordSQL, &localError, ^bool(sqlite3_stmt *selectSerial) {
2276 ok &= SecDbBindInt64(selectSerial, 1, groupId, &localError);
2277 ok &= SecDbBindBlob(selectSerial, 2, CFDataGetBytePtr(serial),
2278 CFDataGetLength(serial), SQLITE_TRANSIENT, &localError);
2279 ok &= SecDbStep(dbconn, selectSerial, &localError, ^(bool *stop) {
2280 int64_t foundRowId = (int64_t)sqlite3_column_int64(selectSerial, 0);
2281 result = (foundRowId > 0);
2282 });
2283 return ok;
2284 });
2285 });
2286
2287 errOut:
2288 (void) CFErrorPropagate(localError, error);
2289 return result;
2290 }
2291
2292 static bool _SecRevocationDbCertHashInGroup(SecRevocationDbRef rdb,
2293 CFDataRef certHash,
2294 int64_t groupId,
2295 CFErrorRef *error) {
2296 __block bool result = false;
2297 __block bool ok = true;
2298 __block CFErrorRef localError = NULL;
2299 require(rdb && certHash, errOut);
2300 ok &= SecDbPerformRead(rdb->db, &localError, ^(SecDbConnectionRef dbconn) {
2301 ok &= SecDbWithSQL(dbconn, selectHashRecordSQL, &localError, ^bool(sqlite3_stmt *selectHash) {
2302 ok &= SecDbBindInt64(selectHash, 1, groupId, &localError);
2303 ok = SecDbBindBlob(selectHash, 2, CFDataGetBytePtr(certHash),
2304 CFDataGetLength(certHash), SQLITE_TRANSIENT, &localError);
2305 ok &= SecDbStep(dbconn, selectHash, &localError, ^(bool *stop) {
2306 int64_t foundRowId = (int64_t)sqlite3_column_int64(selectHash, 0);
2307 result = (foundRowId > 0);
2308 });
2309 return ok;
2310 });
2311 });
2312
2313 errOut:
2314 (void) CFErrorPropagate(localError, error);
2315 return result;
2316 }
2317
2318 static bool _SecRevocationDbSerialInFilter(SecRevocationDbRef rdb,
2319 CFDataRef serialData,
2320 CFDataRef xmlData) {
2321 /* N-To-1 filter implementation.
2322 The 'xmlData' parameter is a flattened XML dictionary,
2323 containing 'xor' and 'params' keys. First order of
2324 business is to reconstitute the blob into components.
2325 */
2326 bool result = false;
2327 CFRetainSafe(xmlData);
2328 CFDataRef propListData = xmlData;
2329 /* Expand data blob if needed */
2330 CFDataRef inflatedData = copyInflatedData(propListData);
2331 if (inflatedData) {
2332 CFReleaseSafe(propListData);
2333 propListData = inflatedData;
2334 }
2335 CFDataRef xor = NULL;
2336 CFArrayRef params = NULL;
2337 CFPropertyListRef nto1 = CFPropertyListCreateWithData(kCFAllocatorDefault, propListData, 0, NULL, NULL);
2338 if (nto1) {
2339 xor = (CFDataRef)CFDictionaryGetValue((CFDictionaryRef)nto1, CFSTR("xor"));
2340 params = (CFArrayRef)CFDictionaryGetValue((CFDictionaryRef)nto1, CFSTR("params"));
2341 }
2342 uint8_t *hash = (xor) ? (uint8_t*)CFDataGetBytePtr(xor) : NULL;
2343 CFIndex hashLen = (hash) ? CFDataGetLength(xor) : 0;
2344 uint8_t *serial = (serialData) ? (uint8_t*)CFDataGetBytePtr(serialData) : NULL;
2345 CFIndex serialLen = (serial) ? CFDataGetLength(serialData) : 0;
2346
2347 require(hash && serial && params, errOut);
2348
2349 const uint32_t FNV_OFFSET_BASIS = 2166136261;
2350 const uint32_t FNV_PRIME = 16777619;
2351 bool notInHash = false;
2352 CFIndex ix, count = CFArrayGetCount(params);
2353 for (ix = 0; ix < count; ix++) {
2354 int32_t param;
2355 CFNumberRef cfnum = (CFNumberRef)CFArrayGetValueAtIndex(params, ix);
2356 if (!isNumber(cfnum) ||
2357 !CFNumberGetValue(cfnum, kCFNumberSInt32Type, &param)) {
2358 secinfo("validupdate", "error processing filter params at index %ld", (long)ix);
2359 continue;
2360 }
2361 /* process one param */
2362 uint32_t hval = FNV_OFFSET_BASIS ^ param;
2363 CFIndex i = serialLen;
2364 while (i > 0) {
2365 hval = ((hval ^ (serial[--i])) * FNV_PRIME) & 0xFFFFFFFF;
2366 }
2367 hval = hval % (hashLen * 8);
2368 if ((hash[hval/8] & (1 << (hval % 8))) == 0) {
2369 notInHash = true; /* definitely not in hash */
2370 break;
2371 }
2372 }
2373 if (!notInHash) {
2374 /* probabilistically might be in hash if we get here. */
2375 result = true;
2376 }
2377
2378 errOut:
2379 CFReleaseSafe(nto1);
2380 CFReleaseSafe(propListData);
2381 return result;
2382 }
2383
2384 static SecValidInfoRef _SecRevocationDbValidInfoForCertificate(SecRevocationDbRef rdb,
2385 SecCertificateRef certificate,
2386 CFDataRef issuerHash,
2387 CFErrorRef *error) {
2388 __block CFErrorRef localError = NULL;
2389 __block SecValidInfoFlags flags = 0;
2390 __block SecValidInfoFormat format = kSecValidInfoFormatUnknown;
2391 __block CFDataRef data = NULL;
2392
2393 bool matched = false;
2394 bool isOnList = false;
2395 int64_t groupId = 0;
2396 CFDataRef serial = NULL;
2397 CFDataRef certHash = NULL;
2398 SecValidInfoRef result = NULL;
2399
2400 require((serial = SecCertificateCopySerialNumberData(certificate, NULL)) != NULL, errOut);
2401 require((certHash = SecCertificateCopySHA256Digest(certificate)) != NULL, errOut);
2402 require((groupId = _SecRevocationDbGroupIdForIssuerHash(rdb, issuerHash, &localError)) > 0, errOut);
2403
2404 /* Look up the group record to determine flags and format. */
2405 format = _SecRevocationDbGetGroupFormat(rdb, groupId, &flags, &data, &localError);
2406
2407 if (format == kSecValidInfoFormatUnknown) {
2408 /* No group record found for this issuer. */
2409 }
2410 else if (format == kSecValidInfoFormatSerial) {
2411 /* Look up certificate's serial number in the serials table. */
2412 matched = _SecRevocationDbSerialInGroup(rdb, serial, groupId, &localError);
2413 }
2414 else if (format == kSecValidInfoFormatSHA256) {
2415 /* Look up certificate's SHA-256 hash in the hashes table. */
2416 matched = _SecRevocationDbCertHashInGroup(rdb, certHash, groupId, &localError);
2417 }
2418 else if (format == kSecValidInfoFormatNto1) {
2419 /* Perform a Bloom filter match against the serial. If matched is false,
2420 then the cert is definitely not in the list. But if matched is true,
2421 we don't know for certain, so we would need to check OCSP. */
2422 matched = _SecRevocationDbSerialInFilter(rdb, serial, data);
2423 }
2424
2425 if (matched) {
2426 /* Found a specific match for this certificate. */
2427 secdebug("validupdate", "Valid db matched certificate: %@, format=%d, flags=%lu",
2428 certHash, format, flags);
2429 isOnList = true;
2430 }
2431 else if ((flags & kSecValidInfoComplete) && (flags & kSecValidInfoAllowlist)) {
2432 /* Not matching against a complete allowlist is equivalent to revocation. */
2433 secdebug("validupdate", "Valid db did NOT match certificate on allowlist: %@, format=%d, flags=%lu",
2434 certHash, format, flags);
2435 matched = true;
2436 }
2437 else if ((!(flags & kSecValidInfoComplete)) && (format > kSecValidInfoFormatUnknown)) {
2438 /* Not matching against an incomplete list implies we need to check OCSP. */
2439 secdebug("validupdate", "Valid db did not find certificate on incomplete list: %@, format=%d, flags=%lu",
2440 certHash, format, flags);
2441 matched = true;
2442 }
2443
2444 if (matched) {
2445 /* Return SecValidInfo for a matched certificate. */
2446 result = SecValidInfoCreate(format, flags, isOnList, certHash, issuerHash, NULL);
2447 }
2448
2449 if (result && SecIsAppleTrustAnchor(certificate, 0)) {
2450 /* Prevent a catch-22. */
2451 secdebug("validupdate", "Valid db match for Apple trust anchor: %@, format=%d, flags=%lu",
2452 certHash, format, flags);
2453 SecValidInfoRelease(result);
2454 result = NULL;
2455 }
2456
2457 errOut:
2458 (void) CFErrorPropagate(localError, error);
2459 CFReleaseSafe(data);
2460 CFReleaseSafe(certHash);
2461 CFReleaseSafe(serial);
2462 return result;
2463 }
2464
2465 static SecValidInfoRef _SecRevocationDbCopyMatching(SecRevocationDbRef db,
2466 SecCertificateRef certificate,
2467 SecCertificateRef issuer) {
2468 SecValidInfoRef result = NULL;
2469 CFErrorRef error = NULL;
2470 CFDataRef issuerHash = NULL;
2471
2472 require(certificate && issuer, errOut);
2473 require(issuerHash = SecCertificateCopySHA256Digest(issuer), errOut);
2474
2475 result = _SecRevocationDbValidInfoForCertificate(db, certificate, issuerHash, &error);
2476
2477 errOut:
2478 CFReleaseSafe(issuerHash);
2479 CFReleaseSafe(error);
2480 return result;
2481 }
2482
2483 static dispatch_queue_t _SecRevocationDbGetUpdateQueue(SecRevocationDbRef rdb) {
2484 return (rdb) ? rdb->update_queue : NULL;
2485 }
2486
2487
2488 /* Given a valid update dictionary, insert/replace or delete records
2489 in the revocation database. (This function is expected to be called only
2490 by the database maintainer, normally the system instance of trustd.)
2491 */
2492 void SecRevocationDbApplyUpdate(CFDictionaryRef update, CFIndex version) {
2493 SecRevocationDbWith(^(SecRevocationDbRef db) {
2494 _SecRevocationDbApplyUpdate(db, update, version);
2495 });
2496 }
2497
2498 /* Set the schema version for the revocation database.
2499 (This function is expected to be called only by the database maintainer,
2500 normally the system instance of trustd.)
2501 */
2502 void SecRevocationDbSetSchemaVersion(CFIndex db_version) {
2503 SecRevocationDbWith(^(SecRevocationDbRef db) {
2504 _SecRevocationDbSetSchemaVersion(db, db_version);
2505 });
2506 }
2507
2508 /* Set the update format for the revocation database.
2509 (This function is expected to be called only by the database maintainer,
2510 normally the system instance of trustd.)
2511 */
2512 void SecRevocationDbSetUpdateFormat(CFIndex db_format) {
2513 SecRevocationDbWith(^(SecRevocationDbRef db) {
2514 _SecRevocationDbSetUpdateFormat(db, db_format);
2515 });
2516 }
2517
2518 /* Set the update source for the revocation database.
2519 (This function is expected to be called only by the database
2520 maintainer, normally the system instance of trustd. If the
2521 caller does not have write access, this is a no-op.)
2522 */
2523 void SecRevocationDbSetUpdateSource(CFStringRef updateSource) {
2524 SecRevocationDbWith(^(SecRevocationDbRef db) {
2525 _SecRevocationDbSetUpdateSource(db, updateSource);
2526 });
2527 }
2528
2529 /* Return the update source as a retained CFStringRef.
2530 If the value cannot be obtained, NULL is returned.
2531 */
2532 CFStringRef SecRevocationDbCopyUpdateSource(void) {
2533 __block CFStringRef result = NULL;
2534 SecRevocationDbWith(^(SecRevocationDbRef db) {
2535 result = _SecRevocationDbCopyUpdateSource(db, NULL);
2536 });
2537 return result;
2538 }
2539
2540 /* Set the next update value for the revocation database.
2541 (This function is expected to be called only by the database
2542 maintainer, normally the system instance of trustd. If the
2543 caller does not have write access, this is a no-op.)
2544 */
2545 void SecRevocationDbSetNextUpdateTime(CFAbsoluteTime nextUpdate) {
2546 SecRevocationDbWith(^(SecRevocationDbRef db) {
2547 _SecRevocationDbSetNextUpdateTime(db, nextUpdate);
2548 });
2549 }
2550
2551 /* Return the next update value as a CFAbsoluteTime.
2552 If the value cannot be obtained, -1 is returned.
2553 */
2554 CFAbsoluteTime SecRevocationDbGetNextUpdateTime(void) {
2555 __block CFAbsoluteTime result = -1;
2556 SecRevocationDbWith(^(SecRevocationDbRef db) {
2557 result = _SecRevocationDbGetNextUpdateTime(db, NULL);
2558 });
2559 return result;
2560 }
2561
2562 /* Return the serial background queue for database updates.
2563 If the queue cannot be obtained, NULL is returned.
2564 */
2565 dispatch_queue_t SecRevocationDbGetUpdateQueue(void) {
2566 __block dispatch_queue_t result = NULL;
2567 SecRevocationDbWith(^(SecRevocationDbRef db) {
2568 result = _SecRevocationDbGetUpdateQueue(db);
2569 });
2570 return result;
2571 }
2572
2573 /* Remove all entries in the revocation database and reset its version to 0.
2574 (This function is expected to be called only by the database maintainer,
2575 normally the system instance of trustd.)
2576 */
2577 void SecRevocationDbRemoveAllEntries(void) {
2578 SecRevocationDbWith(^(SecRevocationDbRef db) {
2579 _SecRevocationDbRemoveAllEntries(db);
2580 });
2581 }
2582
2583 /* Release all connections to the revocation database.
2584 */
2585 void SecRevocationDbReleaseAllConnections(void) {
2586 SecRevocationDbWith(^(SecRevocationDbRef db) {
2587 SecDbReleaseAllConnections((db) ? db->db : NULL);
2588 });
2589 }
2590
2591 /* === SecRevocationDb API === */
2592
2593 /* Given a certificate and its issuer, returns a SecValidInfoRef if the
2594 valid database contains matching info; otherwise returns NULL.
2595 Caller must release the returned SecValidInfoRef by calling
2596 SecValidInfoRelease when finished.
2597 */
2598 SecValidInfoRef SecRevocationDbCopyMatching(SecCertificateRef certificate,
2599 SecCertificateRef issuer) {
2600 __block SecValidInfoRef result = NULL;
2601 SecRevocationDbWith(^(SecRevocationDbRef db) {
2602 result = _SecRevocationDbCopyMatching(db, certificate, issuer);
2603 });
2604 return result;
2605 }
2606
2607 /* Return the current version of the revocation database.
2608 A version of 0 indicates an empty database which must be populated.
2609 If the version cannot be obtained, -1 is returned.
2610 */
2611 CFIndex SecRevocationDbGetVersion(void) {
2612 __block CFIndex result = -1;
2613 SecRevocationDbWith(^(SecRevocationDbRef db) {
2614 result = (CFIndex)_SecRevocationDbGetVersion(db, NULL);
2615 });
2616 return result;
2617 }
2618
2619 /* Return the current schema version of the revocation database.
2620 A version of 0 indicates an empty database which must be populated.
2621 If the schema version cannot be obtained, -1 is returned.
2622 */
2623 CFIndex SecRevocationDbGetSchemaVersion(void) {
2624 __block CFIndex result = -1;
2625 SecRevocationDbWith(^(SecRevocationDbRef db) {
2626 result = (CFIndex)_SecRevocationDbGetSchemaVersion(db, NULL);
2627 });
2628 return result;
2629 }
2630
2631 /* Return the current update format of the revocation database.
2632 A version of 0 indicates the format was unknown.
2633 If the update format cannot be obtained, -1 is returned.
2634 */
2635 CFIndex SecRevocationDbGetUpdateFormat(void) {
2636 __block CFIndex result = -1;
2637 SecRevocationDbWith(^(SecRevocationDbRef db) {
2638 result = (CFIndex)_SecRevocationDbGetUpdateFormat(db, NULL);
2639 });
2640 return result;
2641 }