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