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