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