]> git.saurik.com Git - apple/security.git/blob - trust/trustd/SecRevocationDb.c
Security-59306.61.1.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 void SecRevocationDbInitialize() {
942 if (!isDbOwner()) { return; }
943 os_transaction_t transaction = os_transaction_create("com.apple.trustd.valid.initialize");
944 __block bool initializeDb = false;
945
946 /* create base path if it doesn't exist */
947 WithPathInRevocationInfoDirectory(NULL, ^(const char *utf8String) {
948 (void)mkpath_np(utf8String, 0755);
949 });
950
951 /* check semaphore file */
952 WithPathInRevocationInfoDirectory(CFSTR(kSecRevocationDbReplaceFile), ^(const char *path) {
953 struct stat sb;
954 if (stat(path, &sb) == 0) {
955 initializeDb = true; /* file was found, so we will replace the database */
956 if (remove(path) == -1) {
957 int error = errno;
958 secnotice("validupdate", "remove (%s): %s", path, strerror(error));
959 }
960 }
961 });
962
963 /* check database */
964 WithPathInRevocationInfoDirectory(CFSTR(kSecRevocationDbFileName), ^(const char *path) {
965 if (initializeDb) {
966 /* remove old database file(s) */
967 (void)removeFileWithSuffix(path, "");
968 (void)removeFileWithSuffix(path, "-journal");
969 (void)removeFileWithSuffix(path, "-shm");
970 (void)removeFileWithSuffix(path, "-wal");
971 }
972 else {
973 struct stat sb;
974 if (stat(path, &sb) == -1) {
975 initializeDb = true; /* file not found, so we will create the database */
976 }
977 }
978 });
979
980 if (!initializeDb) {
981 os_release(transaction);
982 return; /* database exists and doesn't need replacing */
983 }
984
985 /* initialize database from local asset */
986 CFTypeRef value = (CFStringRef)CFPreferencesCopyValue(kUpdateServerKey, kSecPrefsDomain, kCFPreferencesAnyUser, kCFPreferencesCurrentHost);
987 CFStringRef server = (isString(value)) ? (CFStringRef)value : (CFStringRef)SecRevocationDbGetDefaultServer();
988 CFIndex version = 0;
989 secnotice("validupdate", "initializing database");
990 if (!SecValidUpdateSatisfiedLocally(server, version, true)) {
991 #if !TARGET_OS_BRIDGE
992 /* Schedule full update as a maintenance task */
993 (void)SecValidUpdateRequest(SecRevocationDbGetUpdateQueue(), server, version);
994 #endif
995 }
996 CFReleaseSafe(value);
997 os_release(transaction);
998 }
999
1000
1001 // MARK: -
1002 // MARK: SecValidInfoRef
1003 /*
1004 ==============================================================================
1005 SecValidInfoRef
1006 ==============================================================================
1007 */
1008
1009 CFGiblisWithCompareFor(SecValidInfo);
1010
1011 static SecValidInfoRef SecValidInfoCreate(SecValidInfoFormat format,
1012 CFOptionFlags flags,
1013 bool isOnList,
1014 CFDataRef certHash,
1015 CFDataRef issuerHash,
1016 CFDataRef anchorHash,
1017 CFDateRef notBeforeDate,
1018 CFDateRef notAfterDate,
1019 CFDataRef nameConstraints,
1020 CFDataRef policyConstraints) {
1021 SecValidInfoRef validInfo;
1022 validInfo = CFTypeAllocate(SecValidInfo, struct __SecValidInfo, kCFAllocatorDefault);
1023 if (!validInfo) { return NULL; }
1024
1025 CFRetainSafe(certHash);
1026 CFRetainSafe(issuerHash);
1027 CFRetainSafe(anchorHash);
1028 CFRetainSafe(notBeforeDate);
1029 CFRetainSafe(notAfterDate);
1030 CFRetainSafe(nameConstraints);
1031 CFRetainSafe(policyConstraints);
1032
1033 validInfo->format = format;
1034 validInfo->certHash = certHash;
1035 validInfo->issuerHash = issuerHash;
1036 validInfo->anchorHash = anchorHash;
1037 validInfo->isOnList = isOnList;
1038 validInfo->valid = (flags & kSecValidInfoAllowlist);
1039 validInfo->complete = (flags & kSecValidInfoComplete);
1040 validInfo->checkOCSP = (flags & kSecValidInfoCheckOCSP);
1041 validInfo->knownOnly = (flags & kSecValidInfoKnownOnly);
1042 validInfo->requireCT = (flags & kSecValidInfoRequireCT);
1043 validInfo->noCACheck = (flags & kSecValidInfoNoCAv2Check);
1044 validInfo->overridable = (flags & kSecValidInfoOverridable);
1045 validInfo->hasDateConstraints = (flags & kSecValidInfoDateConstraints);
1046 validInfo->hasNameConstraints = (flags & kSecValidInfoNameConstraints);
1047 validInfo->hasPolicyConstraints = (flags & kSecValidInfoPolicyConstraints);
1048 validInfo->notBeforeDate = notBeforeDate;
1049 validInfo->notAfterDate = notAfterDate;
1050 validInfo->nameConstraints = nameConstraints;
1051 validInfo->policyConstraints = policyConstraints;
1052
1053 return validInfo;
1054 }
1055
1056 static void SecValidInfoDestroy(CFTypeRef cf) {
1057 SecValidInfoRef validInfo = (SecValidInfoRef)cf;
1058 if (validInfo) {
1059 CFReleaseNull(validInfo->certHash);
1060 CFReleaseNull(validInfo->issuerHash);
1061 CFReleaseNull(validInfo->anchorHash);
1062 CFReleaseNull(validInfo->notBeforeDate);
1063 CFReleaseNull(validInfo->notAfterDate);
1064 CFReleaseNull(validInfo->nameConstraints);
1065 CFReleaseNull(validInfo->policyConstraints);
1066 }
1067 }
1068
1069 void SecValidInfoSetAnchor(SecValidInfoRef validInfo, SecCertificateRef anchor) {
1070 if (!validInfo) {
1071 return;
1072 }
1073 CFDataRef anchorHash = NULL;
1074 if (anchor) {
1075 anchorHash = SecCertificateCopySHA256Digest(anchor);
1076
1077 /* clear no-ca flag for anchors where we want OCSP checked [32523118] */
1078 if (SecIsAppleTrustAnchor(anchor, 0)) {
1079 validInfo->noCACheck = false;
1080 }
1081 }
1082 CFReleaseNull(validInfo->anchorHash);
1083 validInfo->anchorHash = anchorHash;
1084 }
1085
1086 static Boolean SecValidInfoCompare(CFTypeRef a, CFTypeRef b) {
1087 SecValidInfoRef validInfoA = (SecValidInfoRef)a;
1088 SecValidInfoRef validInfoB = (SecValidInfoRef)b;
1089 if (validInfoA == validInfoB) {
1090 return true;
1091 }
1092 if (!validInfoA || !validInfoB ||
1093 (CFGetTypeID(a) != SecValidInfoGetTypeID()) ||
1094 (CFGetTypeID(b) != SecValidInfoGetTypeID())) {
1095 return false;
1096 }
1097 return CFEqualSafe(validInfoA->certHash, validInfoB->certHash) && CFEqualSafe(validInfoA->issuerHash, validInfoB->issuerHash);
1098 }
1099
1100 static CFStringRef SecValidInfoCopyFormatDescription(CFTypeRef cf, CFDictionaryRef formatOptions) {
1101 SecValidInfoRef validInfo = (SecValidInfoRef)cf;
1102 CFStringRef certHash = CFDataCopyHexString(validInfo->certHash);
1103 CFStringRef issuerHash = CFDataCopyHexString(validInfo->issuerHash);
1104 CFStringRef desc = CFStringCreateWithFormat(NULL, formatOptions, CFSTR("validInfo certHash: %@ issuerHash: %@"), certHash, issuerHash);
1105 CFReleaseNull(certHash);
1106 CFReleaseNull(issuerHash);
1107 return desc;
1108 }
1109
1110
1111 // MARK: -
1112 // MARK: SecRevocationDb
1113 /*
1114 ==============================================================================
1115 SecRevocationDb
1116 ==============================================================================
1117 */
1118
1119 /* SecRevocationDbCheckNextUpdate returns true if we dispatched an
1120 update request, otherwise false.
1121 */
1122 static bool _SecRevocationDbCheckNextUpdate(void) {
1123 // are we the db owner instance?
1124 if (!isDbOwner()) {
1125 return false;
1126 }
1127 CFTypeRef value = NULL;
1128
1129 // is it time to check?
1130 CFAbsoluteTime now = CFAbsoluteTimeGetCurrent();
1131 CFAbsoluteTime minNextUpdate = now + gUpdateInterval;
1132 gUpdateStarted = now;
1133
1134 if (0 == gNextUpdate) {
1135 // first time we're called, check if we have a saved nextUpdate value
1136 gNextUpdate = SecRevocationDbGetNextUpdateTime();
1137 minNextUpdate = now;
1138 if (gNextUpdate < minNextUpdate) {
1139 gNextUpdate = minNextUpdate;
1140 }
1141 // allow pref to override update interval, if it exists
1142 CFIndex interval = -1;
1143 value = (CFNumberRef)CFPreferencesCopyValue(kUpdateIntervalKey, kSecPrefsDomain, kCFPreferencesAnyUser, kCFPreferencesCurrentHost);
1144 if (isNumber(value)) {
1145 if (CFNumberGetValue((CFNumberRef)value, kCFNumberCFIndexType, &interval)) {
1146 if (interval < kSecMinUpdateInterval) {
1147 interval = kSecMinUpdateInterval;
1148 } else if (interval > kSecMaxUpdateInterval) {
1149 interval = kSecMaxUpdateInterval;
1150 }
1151 }
1152 }
1153 CFReleaseNull(value);
1154 gUpdateInterval = kSecStdUpdateInterval;
1155 if (interval > 0) {
1156 gUpdateInterval = interval;
1157 }
1158 // pin next update time to the preferred update interval
1159 if (gNextUpdate > (gUpdateStarted + gUpdateInterval)) {
1160 gNextUpdate = gUpdateStarted + gUpdateInterval;
1161 }
1162 secdebug("validupdate", "next update at %f (in %f seconds)",
1163 (double)gUpdateStarted, (double)gNextUpdate-gUpdateStarted);
1164 }
1165 if (gNextUpdate > now) {
1166 gUpdateStarted = 0;
1167 return false;
1168 }
1169 secnotice("validupdate", "starting update");
1170
1171 // set minimum next update time here in case we can't get an update
1172 gNextUpdate = minNextUpdate;
1173
1174 // determine which server to query
1175 CFStringRef server;
1176 value = (CFStringRef)CFPreferencesCopyValue(kUpdateServerKey, kSecPrefsDomain, kCFPreferencesAnyUser, kCFPreferencesCurrentHost);
1177 if (isString(value)) {
1178 server = (CFStringRef) CFRetain(value);
1179 } else {
1180 server = (CFStringRef) CFRetain(SecRevocationDbGetDefaultServer());
1181 }
1182 CFReleaseNull(value);
1183
1184 // determine version of our current database
1185 CFIndex version = SecRevocationDbGetVersion();
1186 secdebug("validupdate", "got version %ld from db", (long)version);
1187 if (version <= 0) {
1188 if (gLastVersion > 0) {
1189 secdebug("validupdate", "error getting version; using last good version: %ld", (long)gLastVersion);
1190 }
1191 version = gLastVersion;
1192 }
1193
1194 // determine source of our current database
1195 // (if this ever changes, we will need to reload the db)
1196 CFStringRef db_source = SecRevocationDbCopyUpdateSource();
1197 if (!db_source) {
1198 db_source = (CFStringRef) CFRetain(kValidUpdateProdServer);
1199 }
1200
1201 // determine whether we need to recreate the database
1202 CFIndex db_version = SecRevocationDbGetSchemaVersion();
1203 CFIndex db_format = SecRevocationDbGetUpdateFormat();
1204 if (db_version < kSecRevocationDbSchemaVersion ||
1205 db_format < kSecRevocationDbUpdateFormat ||
1206 kCFCompareEqualTo != CFStringCompare(server, db_source, kCFCompareCaseInsensitive)) {
1207 // we need to fully rebuild the db contents, so we set our version to 0.
1208 version = gLastVersion = 0;
1209 }
1210
1211 // determine whether update fetching is enabled
1212 #if !TARGET_OS_WATCH && !TARGET_OS_BRIDGE
1213 // Valid update fetching was initially enabled on macOS 10.13 and iOS 11.0.
1214 // This conditional has been changed to include every platform and version
1215 // except for those where the db should not be updated over the air.
1216 bool updateEnabled = true;
1217 #else
1218 bool updateEnabled = false;
1219 #endif
1220 value = (CFBooleanRef)CFPreferencesCopyValue(kUpdateEnabledKey, kSecPrefsDomain, kCFPreferencesAnyUser, kCFPreferencesCurrentHost);
1221 if (isBoolean(value)) {
1222 updateEnabled = CFBooleanGetValue((CFBooleanRef)value);
1223 }
1224 CFReleaseNull(value);
1225
1226 // Schedule maintenance work
1227 bool result = SecValidUpdateSchedule(updateEnabled, server, version);
1228 CFReleaseNull(server);
1229 CFReleaseNull(db_source);
1230 return result;
1231 }
1232
1233 void SecRevocationDbCheckNextUpdate(void) {
1234 static dispatch_once_t once;
1235 static sec_action_t action;
1236
1237 dispatch_once(&once, ^{
1238 dispatch_queue_t update_queue = SecRevocationDbGetUpdateQueue();
1239 action = sec_action_create_with_queue(update_queue, "update_check", kSecMinUpdateInterval);
1240 sec_action_set_handler(action, ^{
1241 os_transaction_t transaction = os_transaction_create("com.apple.trustd.valid.checkNextUpdate");
1242 (void)_SecRevocationDbCheckNextUpdate();
1243 os_release(transaction);
1244 });
1245 });
1246 sec_action_perform(action);
1247 }
1248
1249 /* This function verifies an update, in this format:
1250 1) unsigned 32-bit network-byte-order length of binary plist
1251 2) binary plist data
1252 3) unsigned 32-bit network-byte-order length of CMS message
1253 4) CMS message (containing certificates and signature over binary plist)
1254
1255 The length argument is the total size of the packed update data.
1256 */
1257 bool SecRevocationDbVerifyUpdate(void *update, CFIndex length) {
1258 if (!update || length <= (CFIndex)sizeof(uint32_t)) {
1259 return false;
1260 }
1261 uint32_t plistLength = OSSwapInt32(*((uint32_t *)update));
1262 if ((plistLength + (CFIndex)(sizeof(uint32_t)*2)) > (uint64_t) length) {
1263 secdebug("validupdate", "ERROR: reported plist length (%lu)+%lu exceeds total length (%lu)\n",
1264 (unsigned long)plistLength, (unsigned long)sizeof(uint32_t)*2, (unsigned long)length);
1265 return false;
1266 }
1267 uint8_t *plistData = (uint8_t *)update + sizeof(uint32_t);
1268 uint8_t *sigData = (uint8_t *)plistData + plistLength;
1269 uint32_t sigLength = OSSwapInt32(*((uint32_t *)sigData));
1270 sigData += sizeof(uint32_t);
1271 if ((plistLength + sigLength + (CFIndex)(sizeof(uint32_t) * 2)) != (uint64_t) length) {
1272 secdebug("validupdate", "ERROR: reported lengths do not add up to total length\n");
1273 return false;
1274 }
1275
1276 OSStatus status = 0;
1277 CMSSignerStatus signerStatus;
1278 CMSDecoderRef cms = NULL;
1279 SecPolicyRef policy = NULL;
1280 SecTrustRef trust = NULL;
1281 CFDataRef content = NULL;
1282
1283 if ((content = CFDataCreateWithBytesNoCopy(kCFAllocatorDefault,
1284 (const UInt8 *)plistData, (CFIndex)plistLength, kCFAllocatorNull)) == NULL) {
1285 secdebug("validupdate", "CFDataCreateWithBytesNoCopy failed (%ld bytes)\n", (long)plistLength);
1286 return false;
1287 }
1288
1289 if ((status = CMSDecoderCreate(&cms)) != errSecSuccess) {
1290 secdebug("validupdate", "CMSDecoderCreate failed with error %d\n", (int)status);
1291 goto verifyExit;
1292 }
1293 if ((status = CMSDecoderUpdateMessage(cms, sigData, sigLength)) != errSecSuccess) {
1294 secdebug("validupdate", "CMSDecoderUpdateMessage failed with error %d\n", (int)status);
1295 goto verifyExit;
1296 }
1297 if ((status = CMSDecoderSetDetachedContent(cms, content)) != errSecSuccess) {
1298 secdebug("validupdate", "CMSDecoderSetDetachedContent failed with error %d\n", (int)status);
1299 goto verifyExit;
1300 }
1301 if ((status = CMSDecoderFinalizeMessage(cms)) != errSecSuccess) {
1302 secdebug("validupdate", "CMSDecoderFinalizeMessage failed with error %d\n", (int)status);
1303 goto verifyExit;
1304 }
1305
1306 policy = SecPolicyCreateApplePinned(CFSTR("ValidUpdate"), // kSecPolicyNameAppleValidUpdate
1307 CFSTR("1.2.840.113635.100.6.2.10"), // System Integration 2 Intermediate Certificate
1308 CFSTR("1.2.840.113635.100.6.51")); // Valid update signing OID
1309
1310 // Check that the first signer actually signed this message.
1311 if ((status = CMSDecoderCopySignerStatus(cms, 0, policy,
1312 false, &signerStatus, &trust, NULL)) != errSecSuccess) {
1313 secdebug("validupdate", "CMSDecoderCopySignerStatus failed with error %d\n", (int)status);
1314 goto verifyExit;
1315 }
1316 // Make sure the signature verifies against the detached content
1317 if (signerStatus != kCMSSignerValid) {
1318 secdebug("validupdate", "ERROR: signature did not verify (signer status %d)\n", (int)signerStatus);
1319 status = errSecInvalidSignature;
1320 goto verifyExit;
1321 }
1322 // Make sure the signing certificate is valid for the specified policy
1323 SecTrustResultType trustResult = kSecTrustResultInvalid;
1324 status = SecTrustEvaluate(trust, &trustResult);
1325 if (status != errSecSuccess) {
1326 secdebug("validupdate", "SecTrustEvaluate failed with error %d (trust=%p)\n", (int)status, (void *)trust);
1327 } else if (!(trustResult == kSecTrustResultUnspecified || trustResult == kSecTrustResultProceed)) {
1328 secdebug("validupdate", "SecTrustEvaluate failed with trust result %d\n", (int)trustResult);
1329 status = errSecVerificationFailure;
1330 goto verifyExit;
1331 }
1332
1333 verifyExit:
1334 CFReleaseSafe(content);
1335 CFReleaseSafe(trust);
1336 CFReleaseSafe(policy);
1337 CFReleaseSafe(cms);
1338
1339 return (status == errSecSuccess);
1340 }
1341
1342 CFAbsoluteTime SecRevocationDbComputeNextUpdateTime(CFIndex updateInterval) {
1343 CFIndex interval = updateInterval;
1344 // try to use interval preference if it exists
1345 CFTypeRef value = (CFNumberRef)CFPreferencesCopyValue(kUpdateIntervalKey, kSecPrefsDomain, kCFPreferencesAnyUser, kCFPreferencesCurrentHost);
1346 if (isNumber(value)) {
1347 CFNumberGetValue((CFNumberRef)value, kCFNumberCFIndexType, &interval);
1348 }
1349 CFReleaseNull(value);
1350
1351 if (interval <= 0) {
1352 interval = kSecStdUpdateInterval;
1353 }
1354
1355 // sanity check
1356 if (interval < kSecMinUpdateInterval) {
1357 interval = kSecMinUpdateInterval;
1358 } else if (interval > kSecMaxUpdateInterval) {
1359 interval = kSecMaxUpdateInterval;
1360 }
1361
1362 // compute randomization factor, between 0 and 50% of the interval
1363 CFIndex fuzz = arc4random() % (long)(interval/2.0);
1364 CFAbsoluteTime nextUpdate = CFAbsoluteTimeGetCurrent() + interval + fuzz;
1365 secdebug("validupdate", "next update in %ld seconds", (long)(interval + fuzz));
1366 return nextUpdate;
1367 }
1368
1369 void SecRevocationDbComputeAndSetNextUpdateTime(void) {
1370 gNextUpdate = SecRevocationDbComputeNextUpdateTime(0);
1371 (void) SecRevocationDbSetNextUpdateTime(gNextUpdate, NULL);
1372 gUpdateStarted = 0; /* no update is currently in progress */
1373 }
1374
1375 bool SecRevocationDbIngestUpdate(SecRevocationDbConnectionRef dbc, CFDictionaryRef update, CFIndex chunkVersion, CFIndex *outVersion, CFErrorRef *error) {
1376 bool ok = false;
1377 CFIndex version = 0;
1378 CFErrorRef localError = NULL;
1379 if (!update) {
1380 SecError(errSecParam, &localError, CFSTR("SecRevocationDbIngestUpdate: invalid update parameter"));
1381 goto setVersionAndExit;
1382 }
1383 CFTypeRef value = (CFNumberRef)CFDictionaryGetValue(update, CFSTR("version"));
1384 if (isNumber(value)) {
1385 if (!CFNumberGetValue((CFNumberRef)value, kCFNumberCFIndexType, &version)) {
1386 version = 0;
1387 }
1388 }
1389 if (version == 0) {
1390 // only the first chunk will have a version, so the second and
1391 // subsequent chunks will need to pass it in chunkVersion.
1392 version = chunkVersion;
1393 }
1394 // check precommitted version since update hasn't been committed yet
1395 CFIndex curVersion = dbc->precommitVersion;
1396 if (version > curVersion || chunkVersion > 0) {
1397 ok = _SecRevocationDbApplyUpdate(dbc, update, version, &localError);
1398 secdebug("validupdate", "_SecRevocationDbApplyUpdate=%s, v%ld, precommit=%ld, full=%s",
1399 (ok) ? "1" : "0", (long)version, (long)dbc->precommitVersion,
1400 (dbc->fullUpdate) ? "1" : "0");
1401 } else {
1402 secdebug("validupdate", "we have v%ld, skipping update to v%ld",
1403 (long)curVersion, (long)version);
1404 version = -1; // invalid, so we know to skip subsequent chunks
1405 ok = true; // this is not an error condition
1406 }
1407 setVersionAndExit:
1408 if (outVersion) {
1409 *outVersion = version;
1410 }
1411 (void) CFErrorPropagate(localError, error);
1412 return ok;
1413 }
1414
1415
1416 /* Database schema */
1417
1418 /* admin table holds these key-value (or key-ival) pairs:
1419 'version' (integer) // version of database content
1420 'check_again' (double) // CFAbsoluteTime of next check (optional)
1421 'db_version' (integer) // version of database schema
1422 'db_hash' (blob) // SHA-256 database hash
1423 --> entries in admin table are unique by text key
1424
1425 issuers table holds map of issuing CA hashes to group identifiers:
1426 groupid (integer) // associated group identifier in group ID table
1427 issuer_hash (blob) // SHA-256 hash of issuer certificate (primary key)
1428 --> entries in issuers table are unique by issuer_hash;
1429 multiple issuer entries may have the same groupid!
1430
1431 groups table holds records with these attributes:
1432 groupid (integer) // ordinal ID associated with this group entry
1433 flags (integer) // a bitmask of the following values:
1434 kSecValidInfoComplete (0x00000001) set if we have all revocation info for this issuer group
1435 kSecValidInfoCheckOCSP (0x00000002) set if must check ocsp for certs from this issuer group
1436 kSecValidInfoKnownOnly (0x00000004) set if any CA from this issuer group must be in database
1437 kSecValidInfoRequireCT (0x00000008) set if all certs from this issuer group must have SCTs
1438 kSecValidInfoAllowlist (0x00000010) set if this entry describes valid certs (i.e. is allowed)
1439 kSecValidInfoNoCACheck (0x00000020) set if this entry does not require an OCSP check to accept (deprecated)
1440 kSecValidInfoOverridable (0x00000040) set if the trust status is recoverable and can be overridden
1441 kSecValidInfoDateConstraints (0x00000080) set if this group has not-before or not-after constraints
1442 kSecValidInfoNameConstraints (0x00000100) set if this group has name constraints in database
1443 kSecValidInfoPolicyConstraints (0x00000200) set if this group has policy constraints in database
1444 kSecValidInfoNoCAv2Check (0x00000400) set if this entry does not require an OCSP check to accept
1445 format (integer) // an integer describing format of entries:
1446 kSecValidInfoFormatUnknown (0) unknown format
1447 kSecValidInfoFormatSerial (1) serial number, not greater than 20 bytes in length
1448 kSecValidInfoFormatSHA256 (2) SHA-256 hash, 32 bytes in length
1449 kSecValidInfoFormatNto1 (3) filter data blob of arbitrary length
1450 data (blob) // Bloom filter data if format is 'nto1', otherwise NULL
1451 policies (blob) // NULL, or uint8_t count value followed by array of int8_t policy values
1452 --> entries in groups table are unique by groupid
1453
1454 serials table holds serial number blobs with these attributes:
1455 groupid (integer) // identifier for issuer group in the groups table
1456 serial (blob) // serial number
1457 --> entries in serials table are unique by serial and groupid
1458
1459 hashes table holds SHA-256 hashes of certificates with these attributes:
1460 groupid (integer) // identifier for issuer group in the groups table
1461 sha256 (blob) // SHA-256 hash of subject certificate
1462 --> entries in hashes table are unique by sha256 and groupid
1463
1464 dates table holds notBefore and notAfter dates (as CFAbsoluteTime) with these attributes:
1465 groupid (integer) // identifier for issuer group in the groups table (primary key)
1466 notbefore (real) // issued certs are invalid if their notBefore is prior to this date
1467 notafter (real) // issued certs are invalid after this date (or their notAfter, if earlier)
1468 --> entries in dates table are unique by groupid, and only exist if kSecValidInfoDateConstraints is true
1469
1470 */
1471 #define createTablesSQL CFSTR("CREATE TABLE IF NOT EXISTS admin(" \
1472 "key TEXT PRIMARY KEY NOT NULL," \
1473 "ival INTEGER NOT NULL," \
1474 "value BLOB" \
1475 ");" \
1476 "CREATE TABLE IF NOT EXISTS issuers(" \
1477 "groupid INTEGER NOT NULL," \
1478 "issuer_hash BLOB PRIMARY KEY NOT NULL" \
1479 ");" \
1480 "CREATE INDEX IF NOT EXISTS issuer_idx ON issuers(issuer_hash);" \
1481 "CREATE TABLE IF NOT EXISTS groups(" \
1482 "groupid INTEGER PRIMARY KEY AUTOINCREMENT," \
1483 "flags INTEGER," \
1484 "format INTEGER," \
1485 "data BLOB," \
1486 "policies BLOB" \
1487 ");" \
1488 "CREATE TABLE IF NOT EXISTS serials(" \
1489 "groupid INTEGER NOT NULL," \
1490 "serial BLOB NOT NULL," \
1491 "UNIQUE(groupid,serial)" \
1492 ");" \
1493 "CREATE TABLE IF NOT EXISTS hashes(" \
1494 "groupid INTEGER NOT NULL," \
1495 "sha256 BLOB NOT NULL," \
1496 "UNIQUE(groupid,sha256)" \
1497 ");" \
1498 "CREATE TABLE IF NOT EXISTS dates(" \
1499 "groupid INTEGER PRIMARY KEY NOT NULL," \
1500 "notbefore REAL," \
1501 "notafter REAL" \
1502 ");" \
1503 "CREATE TRIGGER IF NOT EXISTS group_del " \
1504 "BEFORE DELETE ON groups FOR EACH ROW " \
1505 "BEGIN " \
1506 "DELETE FROM serials WHERE groupid=OLD.groupid; " \
1507 "DELETE FROM hashes WHERE groupid=OLD.groupid; " \
1508 "DELETE FROM issuers WHERE groupid=OLD.groupid; " \
1509 "DELETE FROM dates WHERE groupid=OLD.groupid; " \
1510 "END;")
1511
1512 #define selectGroupIdSQL CFSTR("SELECT DISTINCT groupid " \
1513 "FROM issuers WHERE issuer_hash=?")
1514 #define selectVersionSQL CFSTR("SELECT ival FROM admin " \
1515 "WHERE key='version'")
1516 #define selectDbVersionSQL CFSTR("SELECT ival FROM admin " \
1517 "WHERE key='db_version'")
1518 #define selectDbFormatSQL CFSTR("SELECT ival FROM admin " \
1519 "WHERE key='db_format'")
1520 #define selectDbHashSQL CFSTR("SELECT value FROM admin " \
1521 "WHERE key='db_hash'")
1522 #define selectDbSourceSQL CFSTR("SELECT value FROM admin " \
1523 "WHERE key='db_source'")
1524 #define selectNextUpdateSQL CFSTR("SELECT value FROM admin " \
1525 "WHERE key='check_again'")
1526 #define selectUpdateIntervalSQL CFSTR("SELECT ival FROM admin " \
1527 "WHERE key='interval'")
1528 #define selectGroupRecordSQL CFSTR("SELECT flags,format,data,policies " \
1529 "FROM groups WHERE groupid=?")
1530 #define selectSerialRecordSQL CFSTR("SELECT rowid FROM serials " \
1531 "WHERE groupid=? AND serial=?")
1532 #define selectDateRecordSQL CFSTR("SELECT notbefore,notafter FROM " \
1533 "dates WHERE groupid=?")
1534 #define selectHashRecordSQL CFSTR("SELECT rowid FROM hashes " \
1535 "WHERE groupid=? AND sha256=?")
1536 #define insertAdminRecordSQL CFSTR("INSERT OR REPLACE INTO admin " \
1537 "(key,ival,value) VALUES (?,?,?)")
1538 #define insertIssuerRecordSQL CFSTR("INSERT OR REPLACE INTO issuers " \
1539 "(groupid,issuer_hash) VALUES (?,?)")
1540 #define insertGroupRecordSQL CFSTR("INSERT OR REPLACE INTO groups " \
1541 "(groupid,flags,format,data,policies) VALUES (?,?,?,?,?)")
1542 #define insertSerialRecordSQL CFSTR("INSERT OR REPLACE INTO serials " \
1543 "(groupid,serial) VALUES (?,?)")
1544 #define deleteSerialRecordSQL CFSTR("DELETE FROM serials " \
1545 "WHERE groupid=? AND hex(serial) LIKE ?")
1546 #define insertSha256RecordSQL CFSTR("INSERT OR REPLACE INTO hashes " \
1547 "(groupid,sha256) VALUES (?,?)")
1548 #define deleteSha256RecordSQL CFSTR("DELETE FROM hashes " \
1549 "WHERE groupid=? AND hex(sha256) LIKE ?")
1550 #define insertDateRecordSQL CFSTR("INSERT OR REPLACE INTO dates " \
1551 "(groupid,notbefore,notafter) VALUES (?,?,?)")
1552 #define deleteGroupRecordSQL CFSTR("DELETE FROM groups " \
1553 "WHERE groupid=?")
1554 #define deleteGroupIssuersSQL CFSTR("DELETE FROM issuers " \
1555 "WHERE groupid=?")
1556 #define addPoliciesColumnSQL CFSTR("ALTER TABLE groups " \
1557 "ADD COLUMN policies BLOB")
1558 #define updateGroupPoliciesSQL CFSTR("UPDATE OR IGNORE groups " \
1559 "SET policies=? WHERE groupid=?")
1560
1561 #define updateConstraintsTablesSQL CFSTR("" \
1562 "CREATE TABLE IF NOT EXISTS dates(" \
1563 "groupid INTEGER PRIMARY KEY NOT NULL," \
1564 "notbefore REAL," \
1565 "notafter REAL" \
1566 ");")
1567
1568 #define updateGroupDeleteTriggerSQL CFSTR("" \
1569 "DROP TRIGGER IF EXISTS group_del;" \
1570 "CREATE TRIGGER group_del BEFORE DELETE ON groups FOR EACH ROW " \
1571 "BEGIN " \
1572 "DELETE FROM serials WHERE groupid=OLD.groupid; " \
1573 "DELETE FROM hashes WHERE groupid=OLD.groupid; " \
1574 "DELETE FROM issuers WHERE groupid=OLD.groupid; " \
1575 "DELETE FROM dates WHERE groupid=OLD.groupid; " \
1576 "END;")
1577
1578 #define deleteAllEntriesSQL CFSTR("" \
1579 "DELETE FROM groups; " \
1580 "DELETE FROM admin WHERE key='version'; " \
1581 "DELETE FROM sqlite_sequence")
1582
1583
1584 /* Database management */
1585
1586 static SecDbRef SecRevocationDbCreate(CFStringRef path) {
1587 /* only the db owner should open a read-write connection. */
1588 __block bool readWrite = isDbOwner();
1589 mode_t mode = 0644;
1590
1591 SecDbRef result = SecDbCreate(path, mode, readWrite, false, true, true, 1, ^bool (SecDbRef db, SecDbConnectionRef dbconn, bool didCreate, bool *callMeAgainForNextConnection, CFErrorRef *error) {
1592 __block bool ok = true;
1593 if (readWrite) {
1594 ok &= SecDbTransaction(dbconn, kSecDbExclusiveTransactionType, error, ^(bool *commit) {
1595 /* Create all database tables, indexes, and triggers.
1596 * SecDbOpen will set auto_vacuum and journal_mode for us before we get called back.*/
1597 ok = ok && SecDbExec(dbconn, createTablesSQL, error);
1598 *commit = ok;
1599 });
1600 }
1601 if (!ok || (error && *error)) {
1602 CFIndex errCode = errSecInternalComponent;
1603 if (error && *error) {
1604 errCode = CFErrorGetCode(*error);
1605 }
1606 secerror("%s failed: %@", didCreate ? "Create" : "Open", error ? *error : NULL);
1607 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb, TAOperationCreate, TAFatalError, errCode);
1608 }
1609 return ok;
1610 });
1611
1612 return result;
1613 }
1614
1615 static dispatch_once_t kSecRevocationDbOnce;
1616 static SecRevocationDbRef kSecRevocationDb = NULL;
1617
1618 static SecRevocationDbRef SecRevocationDbInit(CFStringRef db_name) {
1619 SecRevocationDbRef rdb;
1620 dispatch_queue_attr_t attr;
1621
1622 require(rdb = (SecRevocationDbRef)malloc(sizeof(struct __SecRevocationDb)), errOut);
1623 rdb->db = NULL;
1624 rdb->update_queue = NULL;
1625 rdb->updateInProgress = false;
1626 rdb->unsupportedVersion = false;
1627 rdb->changed = false;
1628
1629 require(rdb->db = SecRevocationDbCreate(db_name), errOut);
1630 attr = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_BACKGROUND, 0);
1631 attr = dispatch_queue_attr_make_with_autorelease_frequency(attr, DISPATCH_AUTORELEASE_FREQUENCY_WORK_ITEM);
1632 require(rdb->update_queue = dispatch_queue_create(NULL, attr), errOut);
1633 require(rdb->info_cache_list = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks), errOut);
1634 require(rdb->info_cache = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks), errOut);
1635 rdb->info_cache_lock = OS_UNFAIR_LOCK_INIT;
1636
1637 if (!isDbOwner()) {
1638 /* register for changes signaled by the db owner instance */
1639 int out_token = 0;
1640 notify_register_dispatch(kSecRevocationDbChanged, &out_token, rdb->update_queue, ^(int __unused token) {
1641 secnotice("validupdate", "Got notification of database change");
1642 SecRevocationDbResetCaches();
1643 });
1644 }
1645 return rdb;
1646
1647 errOut:
1648 secdebug("validupdate", "Failed to create db at \"%@\"", db_name);
1649 if (rdb) {
1650 if (rdb->update_queue) {
1651 dispatch_release(rdb->update_queue);
1652 }
1653 CFReleaseSafe(rdb->db);
1654 free(rdb);
1655 }
1656 return NULL;
1657 }
1658
1659 static CFStringRef SecRevocationDbCopyPath(void) {
1660 CFURLRef revDbURL = NULL;
1661 CFStringRef revInfoRelPath = NULL;
1662 if ((revInfoRelPath = CFStringCreateWithFormat(NULL, NULL, CFSTR("%s"), kSecRevocationDbFileName)) != NULL) {
1663 revDbURL = SecCopyURLForFileInRevocationInfoDirectory(revInfoRelPath);
1664 }
1665 CFReleaseSafe(revInfoRelPath);
1666
1667 CFStringRef revDbPath = NULL;
1668 if (revDbURL) {
1669 revDbPath = CFURLCopyFileSystemPath(revDbURL, kCFURLPOSIXPathStyle);
1670 CFRelease(revDbURL);
1671 }
1672 return revDbPath;
1673 }
1674
1675 static void SecRevocationDbWith(void(^dbJob)(SecRevocationDbRef db)) {
1676 dispatch_once(&kSecRevocationDbOnce, ^{
1677 CFStringRef dbPath = SecRevocationDbCopyPath();
1678 if (dbPath) {
1679 kSecRevocationDb = SecRevocationDbInit(dbPath);
1680 CFRelease(dbPath);
1681 if (kSecRevocationDb && isDbOwner()) {
1682 /* check and update schema immediately after database is opened */
1683 SecRevocationDbUpdateSchema(kSecRevocationDb);
1684 }
1685 }
1686 });
1687 // Do pre job run work here (cancel idle timers etc.)
1688 if (kSecRevocationDb->updateInProgress) {
1689 return; // this would block since SecDb has an exclusive transaction lock
1690 }
1691 dbJob(kSecRevocationDb);
1692 // Do post job run work here (gc timer, etc.)
1693 }
1694
1695 static bool SecRevocationDbPerformWrite(SecRevocationDbRef rdb, CFErrorRef *error,
1696 bool(^writeJob)(SecRevocationDbConnectionRef dbc, CFErrorRef *blockError)) {
1697 __block bool ok = true;
1698 __block CFErrorRef localError = NULL;
1699
1700 ok &= SecDbPerformWrite(rdb->db, &localError, ^(SecDbConnectionRef dbconn) {
1701 ok &= SecDbTransaction(dbconn, kSecDbImmediateTransactionType, &localError, ^(bool *commit) {
1702 SecRevocationDbConnectionRef dbc = SecRevocationDbConnectionInit(rdb, dbconn, &localError);
1703 ok = ok && writeJob(dbc, &localError);
1704 *commit = ok;
1705 free(dbc);
1706 });
1707 });
1708 ok &= CFErrorPropagate(localError, error);
1709 return ok;
1710 }
1711
1712 static bool SecRevocationDbPerformRead(SecRevocationDbRef rdb, CFErrorRef *error,
1713 bool(^readJob)(SecRevocationDbConnectionRef dbc, CFErrorRef *blockError)) {
1714 __block CFErrorRef localError = NULL;
1715 __block bool ok = true;
1716
1717 ok &= SecDbPerformRead(rdb->db, &localError, ^(SecDbConnectionRef dbconn) {
1718 SecRevocationDbConnectionRef dbc = SecRevocationDbConnectionInit(rdb, dbconn, &localError);
1719 ok = ok && readJob(dbc, &localError);
1720 free(dbc);
1721 });
1722 ok &= CFErrorPropagate(localError, error);
1723 return ok;
1724 }
1725
1726 static SecRevocationDbConnectionRef SecRevocationDbConnectionInit(SecRevocationDbRef db, SecDbConnectionRef dbconn, CFErrorRef *error) {
1727 SecRevocationDbConnectionRef dbc = NULL;
1728 CFErrorRef localError = NULL;
1729
1730 dbc = (SecRevocationDbConnectionRef)malloc(sizeof(struct __SecRevocationDbConnection));
1731 if (dbc) {
1732 dbc->db = db;
1733 dbc->dbconn = dbconn;
1734 dbc->precommitVersion = (CFIndex)_SecRevocationDbGetVersion(dbc, &localError);
1735 dbc->precommitDbVersion = (CFIndex)_SecRevocationDbGetSchemaVersion(db, dbc, &localError);
1736 dbc->precommitInterval = 0; /* set only if we are explicitly given a new value */
1737 dbc->fullUpdate = false;
1738 }
1739 (void) CFErrorPropagate(localError, error);
1740 return dbc;
1741 }
1742
1743 static CF_RETURNS_RETAINED CFDataRef createCacheKey(CFDataRef certHash, CFDataRef issuerHash) {
1744 CFMutableDataRef concat = CFDataCreateMutableCopy(NULL, 0, certHash);
1745 CFDataAppend(concat, issuerHash);
1746 CFDataRef result = SecSHA256DigestCreateFromData(NULL, concat);
1747 CFReleaseNull(concat);
1748 return result;
1749 }
1750
1751 static CF_RETURNS_RETAINED SecValidInfoRef SecRevocationDbCacheRead(SecRevocationDbRef db,
1752 SecCertificateRef certificate,
1753 CFDataRef issuerHash) {
1754 if (!db) {
1755 return NULL;
1756 }
1757 SecValidInfoRef result = NULL;
1758 if (!db || !db->info_cache || !db->info_cache_list) {
1759 return result;
1760 }
1761 CFIndex ix = kCFNotFound;
1762 CFDataRef certHash = SecCertificateCopySHA256Digest(certificate);
1763 CFDataRef cacheKey = createCacheKey(certHash, issuerHash);
1764
1765 os_unfair_lock_lock(&db->info_cache_lock); // grab the cache lock before using the cache
1766 if (0 <= (ix = CFArrayGetFirstIndexOfValue(db->info_cache_list,
1767 CFRangeMake(0, CFArrayGetCount(db->info_cache_list)),
1768 cacheKey))) {
1769 result = (SecValidInfoRef)CFDictionaryGetValue(db->info_cache, cacheKey);
1770 // Verify this really is the right result
1771 if (CFEqualSafe(result->certHash, certHash) && CFEqualSafe(result->issuerHash, issuerHash)) {
1772 // Cache hit. Move the entry to the bottom of the list.
1773 CFArrayRemoveValueAtIndex(db->info_cache_list, ix);
1774 CFArrayAppendValue(db->info_cache_list, cacheKey);
1775 secdebug("validcache", "cache hit: %@", cacheKey);
1776 } else {
1777 // Just remove this bad entry
1778 CFArrayRemoveValueAtIndex(db->info_cache_list, ix);
1779 CFDictionaryRemoveValue(db->info_cache, cacheKey);
1780 secdebug("validcache", "cache remove bad: %@", cacheKey);
1781 secnotice("validcache", "found a bad valid info cache entry at %ld", (long)ix);
1782 }
1783 }
1784 CFRetainSafe(result);
1785 os_unfair_lock_unlock(&db->info_cache_lock);
1786 CFReleaseSafe(certHash);
1787 CFReleaseSafe(cacheKey);
1788 return result;
1789 }
1790
1791 static void SecRevocationDbCacheWrite(SecRevocationDbRef db,
1792 SecValidInfoRef validInfo) {
1793 if (!db || !validInfo || !db->info_cache || !db->info_cache_list) {
1794 return;
1795 }
1796
1797 CFDataRef cacheKey = createCacheKey(validInfo->certHash, validInfo->issuerHash);
1798
1799 os_unfair_lock_lock(&db->info_cache_lock); // grab the cache lock before using the cache
1800 // check to make sure another thread didn't add this entry to the cache already
1801 if (0 > CFArrayGetFirstIndexOfValue(db->info_cache_list,
1802 CFRangeMake(0, CFArrayGetCount(db->info_cache_list)),
1803 cacheKey)) {
1804 CFDictionaryAddValue(db->info_cache, cacheKey, validInfo);
1805 if (kSecRevocationDbCacheSize <= CFArrayGetCount(db->info_cache_list)) {
1806 // Remove least recently used cache entry.
1807 secdebug("validcache", "cache remove stale: %@", CFArrayGetValueAtIndex(db->info_cache_list, 0));
1808 CFDictionaryRemoveValue(db->info_cache, CFArrayGetValueAtIndex(db->info_cache_list, 0));
1809 CFArrayRemoveValueAtIndex(db->info_cache_list, 0);
1810 }
1811 CFArrayAppendValue(db->info_cache_list, cacheKey);
1812 secdebug("validcache", "cache add: %@", cacheKey);
1813 }
1814 os_unfair_lock_unlock(&db->info_cache_lock);
1815 CFReleaseNull(cacheKey);
1816 }
1817
1818 static void SecRevocationDbCachePurge(SecRevocationDbRef db) {
1819 if (!db || !db->info_cache || !db->info_cache_list) {
1820 return;
1821 }
1822
1823 /* grab the cache lock and clear all entries */
1824 os_unfair_lock_lock(&db->info_cache_lock);
1825 CFArrayRemoveAllValues(db->info_cache_list);
1826 CFDictionaryRemoveAllValues(db->info_cache);
1827 secdebug("validcache", "cache purge");
1828 os_unfair_lock_unlock(&db->info_cache_lock);
1829 }
1830
1831 static int64_t _SecRevocationDbGetUpdateInterval(SecRevocationDbConnectionRef dbc, CFErrorRef *error) {
1832 /* look up interval entry in admin table; returns -1 on error */
1833 __block int64_t interval = -1;
1834 __block bool ok = true;
1835 __block CFErrorRef localError = NULL;
1836
1837 ok = ok && SecDbWithSQL(dbc->dbconn, selectUpdateIntervalSQL, &localError, ^bool(sqlite3_stmt *selectInterval) {
1838 ok = ok && SecDbStep(dbc->dbconn, selectInterval, &localError, ^void(bool *stop) {
1839 interval = sqlite3_column_int64(selectInterval, 0);
1840 *stop = true;
1841 });
1842 return ok;
1843 });
1844 if (!ok || localError) {
1845 secerror("_SecRevocationDbGetUpdateInterval failed: %@", localError);
1846 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb, TAOperationRead, TAFatalError,
1847 localError ? CFErrorGetCode(localError) : errSecInternalComponent);
1848 }
1849 (void) CFErrorPropagate(localError, error);
1850 return interval;
1851 }
1852
1853 static bool _SecRevocationDbSetUpdateInterval(SecRevocationDbConnectionRef dbc, int64_t interval, CFErrorRef *error) {
1854 secdebug("validupdate", "setting interval to %lld", interval);
1855
1856 __block CFErrorRef localError = NULL;
1857 __block bool ok = (dbc != NULL);
1858 ok = ok && SecDbWithSQL(dbc->dbconn, insertAdminRecordSQL, &localError, ^bool(sqlite3_stmt *insertInterval) {
1859 const char *intervalKey = "interval";
1860 ok = ok && SecDbBindText(insertInterval, 1, intervalKey, strlen(intervalKey),
1861 SQLITE_TRANSIENT, &localError);
1862 ok = ok && SecDbBindInt64(insertInterval, 2,
1863 (sqlite3_int64)interval, &localError);
1864 ok = ok && SecDbStep(dbc->dbconn, insertInterval, &localError, NULL);
1865 return ok;
1866 });
1867 if (!ok || localError) {
1868 secerror("_SecRevocationDbSetUpdateInterval failed: %@", localError);
1869 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb, TAOperationWrite, TAFatalError,
1870 localError ? CFErrorGetCode(localError) : errSecInternalComponent);
1871 }
1872 (void) CFErrorPropagate(localError, error);
1873 return ok;
1874 }
1875
1876 static CFArrayRef _SecRevocationDbCopyHashes(SecRevocationDbConnectionRef dbc, CFErrorRef *error) {
1877 /* return a retained copy of the db_hash array stored in the admin table; or NULL on error */
1878 __block CFMutableArrayRef hashes = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
1879 __block bool ok = (dbc && hashes);
1880 __block CFErrorRef localError = NULL;
1881
1882 ok = ok && SecDbWithSQL(dbc->dbconn, selectDbHashSQL, &localError, ^bool(sqlite3_stmt *selectDbHash) {
1883 ok = ok && SecDbStep(dbc->dbconn, selectDbHash, &localError, ^void(bool *stop) {
1884 uint8_t *p = (uint8_t *)sqlite3_column_blob(selectDbHash, 0);
1885 uint64_t len = sqlite3_column_bytes(selectDbHash, 0);
1886 CFIndex hashLen = CC_SHA256_DIGEST_LENGTH;
1887 while (p && len >= (uint64_t)hashLen) {
1888 CFDataRef hash = CFDataCreate(kCFAllocatorDefault, (const UInt8 *)p, hashLen);
1889 if (hash) {
1890 CFArrayAppendValue(hashes, hash);
1891 CFReleaseNull(hash);
1892 }
1893 len -= hashLen;
1894 p += hashLen;
1895 }
1896 *stop = true;
1897 });
1898 return ok;
1899 });
1900 if (!ok || localError) {
1901 CFReleaseNull(hashes);
1902 }
1903 (void) CFErrorPropagate(localError, error);
1904 return hashes;
1905 }
1906
1907 static bool _SecRevocationDbSetHashes(SecRevocationDbConnectionRef dbc, CFArrayRef hashes, CFErrorRef *error) {
1908 /* flatten and store db_hash array in the admin table */
1909 __block CFErrorRef localError = NULL;
1910 __block bool ok = (dbc && hashes);
1911
1912 ok = ok && SecDbWithSQL(dbc->dbconn, insertAdminRecordSQL, &localError, ^bool(sqlite3_stmt *insertHashes) {
1913 CFIndex count = CFArrayGetCount(hashes);
1914 CFIndex hashLen = CC_SHA256_DIGEST_LENGTH, dataLen = hashLen * count;
1915 uint8_t *dataPtr = (uint8_t *)calloc(dataLen, 1);
1916 uint8_t *p = dataPtr;
1917 for (CFIndex idx = 0; idx < count && p; idx++) {
1918 CFDataRef hash = CFArrayGetValueAtIndex(hashes, idx);
1919 uint8_t *h = (hash) ? (uint8_t *)CFDataGetBytePtr(hash) : NULL;
1920 if (h && CFDataGetLength(hash) == hashLen) { memcpy(p, h, hashLen); }
1921 p += hashLen;
1922 }
1923 const char *hashKey = "db_hash";
1924 ok = ok && SecDbBindText(insertHashes, 1, hashKey, strlen(hashKey),
1925 SQLITE_TRANSIENT, &localError);
1926 ok = ok && SecDbBindInt64(insertHashes, 2,
1927 (sqlite3_int64)0, &localError);
1928 ok = ok && SecDbBindBlob(insertHashes, 3,
1929 dataPtr, dataLen,
1930 SQLITE_TRANSIENT, &localError);
1931 ok = ok && SecDbStep(dbc->dbconn, insertHashes, &localError, NULL);
1932 free(dataPtr);
1933 return ok;
1934 });
1935 if (!ok || localError) {
1936 }
1937 (void) CFErrorPropagate(localError, error);
1938 return ok;
1939 }
1940
1941 static int64_t _SecRevocationDbGetVersion(SecRevocationDbConnectionRef dbc, CFErrorRef *error) {
1942 /* look up version entry in admin table; returns -1 on error */
1943 __block int64_t version = -1;
1944 __block bool ok = (dbc != NULL);
1945 __block CFErrorRef localError = NULL;
1946
1947 ok = ok && SecDbWithSQL(dbc->dbconn, selectVersionSQL, &localError, ^bool(sqlite3_stmt *selectVersion) {
1948 ok = ok && SecDbStep(dbc->dbconn, selectVersion, &localError, ^void(bool *stop) {
1949 version = sqlite3_column_int64(selectVersion, 0);
1950 *stop = true;
1951 });
1952 return ok;
1953 });
1954 if (!ok || localError) {
1955 secerror("_SecRevocationDbGetVersion failed: %@", localError);
1956 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb, TAOperationRead, TAFatalError,
1957 localError ? CFErrorGetCode(localError) : errSecInternalComponent);
1958 }
1959 (void) CFErrorPropagate(localError, error);
1960 return version;
1961 }
1962
1963 static bool _SecRevocationDbSetVersion(SecRevocationDbConnectionRef dbc, CFIndex version, CFErrorRef *error) {
1964 secdebug("validupdate", "setting version to %ld", (long)version);
1965
1966 __block CFErrorRef localError = NULL;
1967 __block bool ok = (dbc != NULL);
1968 ok = ok && SecDbWithSQL(dbc->dbconn, insertAdminRecordSQL, &localError, ^bool(sqlite3_stmt *insertVersion) {
1969 const char *versionKey = "version";
1970 ok = ok && SecDbBindText(insertVersion, 1, versionKey, strlen(versionKey),
1971 SQLITE_TRANSIENT, &localError);
1972 ok = ok && SecDbBindInt64(insertVersion, 2,
1973 (sqlite3_int64)version, &localError);
1974 ok = ok && SecDbStep(dbc->dbconn, insertVersion, &localError, NULL);
1975 return ok;
1976 });
1977 if (!ok || localError) {
1978 secerror("_SecRevocationDbSetVersion failed: %@", localError);
1979 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb, TAOperationWrite, TAFatalError,
1980 localError ? CFErrorGetCode(localError) : errSecInternalComponent);
1981 }
1982 (void) CFErrorPropagate(localError, error);
1983 return ok;
1984 }
1985
1986 static int64_t _SecRevocationDbReadSchemaVersionFromDb(SecRevocationDbConnectionRef dbc, CFErrorRef *error) {
1987 /* look up db_version entry in admin table; returns -1 on error */
1988 __block int64_t db_version = -1;
1989 __block bool ok = (dbc != NULL);
1990 __block CFErrorRef localError = NULL;
1991
1992 ok = ok && SecDbWithSQL(dbc->dbconn, selectDbVersionSQL, &localError, ^bool(sqlite3_stmt *selectDbVersion) {
1993 ok = ok && SecDbStep(dbc->dbconn, selectDbVersion, &localError, ^void(bool *stop) {
1994 db_version = sqlite3_column_int64(selectDbVersion, 0);
1995 *stop = true;
1996 });
1997 return ok;
1998 });
1999 if (!ok || localError) {
2000 secerror("_SecRevocationDbReadSchemaVersionFromDb failed: %@", localError);
2001 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb, TAOperationRead, TAFatalError,
2002 localError ? CFErrorGetCode(localError) : errSecInternalComponent);
2003 }
2004 (void) CFErrorPropagate(localError, error);
2005 return db_version;
2006 }
2007
2008 static _Atomic int64_t gSchemaVersion = -1;
2009 static int64_t _SecRevocationDbGetSchemaVersion(SecRevocationDbRef rdb, SecRevocationDbConnectionRef dbc, CFErrorRef *error) {
2010 static dispatch_once_t onceToken;
2011 dispatch_once(&onceToken, ^{
2012 if (dbc) {
2013 atomic_init(&gSchemaVersion, _SecRevocationDbReadSchemaVersionFromDb(dbc, error));
2014 } else {
2015 (void) SecRevocationDbPerformRead(rdb, error, ^bool(SecRevocationDbConnectionRef dbc, CFErrorRef *blockError) {
2016 atomic_init(&gSchemaVersion, _SecRevocationDbReadSchemaVersionFromDb(dbc, blockError));
2017 return true;
2018 });
2019 }
2020 });
2021 if (atomic_load(&gSchemaVersion) == -1) {
2022 /* Initial read(s) failed. Try to read the schema version again. */
2023 if (dbc) {
2024 atomic_store(&gSchemaVersion, _SecRevocationDbReadSchemaVersionFromDb(dbc, error));
2025 } else {
2026 (void) SecRevocationDbPerformRead(rdb, error, ^bool(SecRevocationDbConnectionRef dbc, CFErrorRef *blockError) {
2027 atomic_store(&gSchemaVersion, _SecRevocationDbReadSchemaVersionFromDb(dbc, blockError));
2028 return true;
2029 });
2030 }
2031 }
2032 return atomic_load(&gSchemaVersion);
2033 }
2034
2035 static void SecRevocationDbResetCaches(void) {
2036 SecRevocationDbWith(^(SecRevocationDbRef db) {
2037 db->unsupportedVersion = false;
2038 db->changed = false;
2039 (void) SecRevocationDbPerformRead(db, NULL, ^bool(SecRevocationDbConnectionRef dbc, CFErrorRef *blockError) {
2040 atomic_store(&gSchemaVersion, _SecRevocationDbReadSchemaVersionFromDb(dbc, blockError));
2041 return true;
2042 });
2043 SecRevocationDbCachePurge(db);
2044 });
2045 }
2046
2047 static bool _SecRevocationDbSetSchemaVersion(SecRevocationDbConnectionRef dbc, CFIndex dbversion, CFErrorRef *error) {
2048 if (dbversion > 0) {
2049 int64_t db_version = (dbc) ? dbc->precommitDbVersion : -1;
2050 if (db_version >= dbversion) {
2051 return true; /* requested schema is earlier than current schema */
2052 }
2053 }
2054 secdebug("validupdate", "setting db_version to %ld", (long)dbversion);
2055
2056 __block CFErrorRef localError = NULL;
2057 __block bool ok = (dbc != NULL);
2058 ok = ok && SecDbWithSQL(dbc->dbconn, insertAdminRecordSQL, &localError, ^bool(sqlite3_stmt *insertDbVersion) {
2059 const char *dbVersionKey = "db_version";
2060 ok = ok && SecDbBindText(insertDbVersion, 1, dbVersionKey, strlen(dbVersionKey),
2061 SQLITE_TRANSIENT, &localError);
2062 ok = ok && SecDbBindInt64(insertDbVersion, 2,
2063 (sqlite3_int64)dbversion, &localError);
2064 ok = ok && SecDbStep(dbc->dbconn, insertDbVersion, &localError, NULL);
2065 return ok;
2066 });
2067 if (!ok || localError) {
2068 secerror("_SecRevocationDbSetSchemaVersion failed: %@", localError);
2069 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb, TAOperationWrite, TAFatalError,
2070 localError ? CFErrorGetCode(localError) : errSecInternalComponent);
2071 } else {
2072 dbc->db->changed = true; /* will notify clients of this change */
2073 dbc->db->unsupportedVersion = false;
2074 dbc->precommitDbVersion = dbversion;
2075 atomic_store(&gSchemaVersion, (int64_t)dbversion);
2076 }
2077 CFReleaseSafe(localError);
2078 return ok;
2079 }
2080
2081 static bool _SecRevocationDbUpdateSchema(SecRevocationDbConnectionRef dbc, CFErrorRef *error) {
2082 __block CFErrorRef localError = NULL;
2083 __block bool ok = (dbc != NULL);
2084 __block int64_t db_version = (dbc) ? dbc->precommitDbVersion : 0;
2085 if (db_version >= kSecRevocationDbSchemaVersion) {
2086 return ok; /* schema version already up to date */
2087 }
2088 secdebug("validupdate", "updating db schema from v%lld to v%lld",
2089 (long long)db_version, (long long)kSecRevocationDbSchemaVersion);
2090
2091 if (ok && db_version < 5) {
2092 /* apply v5 changes (add dates table and replace trigger) */
2093 ok &= SecDbWithSQL(dbc->dbconn, updateConstraintsTablesSQL, &localError, ^bool(sqlite3_stmt *updateTables) {
2094 ok = SecDbStep(dbc->dbconn, updateTables, &localError, NULL);
2095 return ok;
2096 });
2097 ok &= SecDbWithSQL(dbc->dbconn, updateGroupDeleteTriggerSQL, &localError, ^bool(sqlite3_stmt *updateTrigger) {
2098 ok = SecDbStep(dbc->dbconn, updateTrigger, &localError, NULL);
2099 return ok;
2100 });
2101 secdebug("validupdate", "applied schema update to v5 (%s)", (ok) ? "ok" : "failed!");
2102 }
2103 if (ok && db_version < 6) {
2104 /* apply v6 changes (the SecDb layer will update autovacuum mode if needed, so we don't execute
2105 any SQL here, but we do want the database to be replaced in case transaction scope problems
2106 with earlier versions caused missing entries.) */
2107 secdebug("validupdate", "applied schema update to v6 (%s)", (ok) ? "ok" : "failed!");
2108 if (db_version > 0) {
2109 SecValidUpdateForceReplaceDatabase();
2110 }
2111 }
2112 if (ok && db_version < 7) {
2113 /* apply v7 changes (add policies column in groups table) */
2114 ok &= SecDbWithSQL(dbc->dbconn, addPoliciesColumnSQL, &localError, ^bool(sqlite3_stmt *addPoliciesColumn) {
2115 ok = SecDbStep(dbc->dbconn, addPoliciesColumn, &localError, NULL);
2116 return ok;
2117 });
2118 secdebug("validupdate", "applied schema update to v7 (%s)", (ok) ? "ok" : "failed!");
2119 }
2120
2121 if (!ok) {
2122 secerror("_SecRevocationDbUpdateSchema failed: %@", localError);
2123 } else {
2124 ok = ok && _SecRevocationDbSetSchemaVersion(dbc, kSecRevocationDbSchemaVersion, &localError);
2125 }
2126 (void) CFErrorPropagate(localError, error);
2127 return ok;
2128 }
2129
2130 bool SecRevocationDbUpdateSchema(SecRevocationDbRef rdb) {
2131 /* note: this function assumes it is called only by the database owner.
2132 non-owner (read-only) clients will fail if changes to the db are needed. */
2133 if (!rdb || !rdb->db) {
2134 return false;
2135 }
2136 __block bool ok = true;
2137 __block CFErrorRef localError = NULL;
2138 ok &= SecRevocationDbPerformWrite(rdb, &localError, ^bool(SecRevocationDbConnectionRef dbc, CFErrorRef *blockError) {
2139 return _SecRevocationDbUpdateSchema(dbc, blockError);
2140 });
2141 CFReleaseSafe(localError);
2142 return ok;
2143 }
2144
2145 static int64_t _SecRevocationDbGetUpdateFormat(SecRevocationDbConnectionRef dbc, CFErrorRef *error) {
2146 /* look up db_format entry in admin table; returns -1 on error */
2147 __block int64_t db_format = -1;
2148 __block bool ok = (dbc != NULL);
2149 __block CFErrorRef localError = NULL;
2150
2151 ok = ok && SecDbWithSQL(dbc->dbconn, selectDbFormatSQL, &localError, ^bool(sqlite3_stmt *selectDbFormat) {
2152 ok &= SecDbStep(dbc->dbconn, selectDbFormat, &localError, ^void(bool *stop) {
2153 db_format = sqlite3_column_int64(selectDbFormat, 0);
2154 *stop = true;
2155 });
2156 return ok;
2157 });
2158 if (!ok || localError) {
2159 secerror("_SecRevocationDbGetUpdateFormat failed: %@", localError);
2160 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb, TAOperationRead, TAFatalError,
2161 localError ? CFErrorGetCode(localError) : errSecInternalComponent);
2162 }
2163 (void) CFErrorPropagate(localError, error);
2164 return db_format;
2165 }
2166
2167 static bool _SecRevocationDbSetUpdateFormat(SecRevocationDbConnectionRef dbc, CFIndex dbformat, CFErrorRef *error) {
2168 secdebug("validupdate", "setting db_format to %ld", (long)dbformat);
2169
2170 __block CFErrorRef localError = NULL;
2171 __block bool ok = (dbc != NULL);
2172 ok = ok && SecDbWithSQL(dbc->dbconn, insertAdminRecordSQL, &localError, ^bool(sqlite3_stmt *insertDbFormat) {
2173 const char *dbFormatKey = "db_format";
2174 ok = ok && SecDbBindText(insertDbFormat, 1, dbFormatKey, strlen(dbFormatKey),
2175 SQLITE_TRANSIENT, &localError);
2176 ok = ok && SecDbBindInt64(insertDbFormat, 2,
2177 (sqlite3_int64)dbformat, &localError);
2178 ok = ok && SecDbStep(dbc->dbconn, insertDbFormat, &localError, NULL);
2179 return ok;
2180 });
2181 if (!ok || localError) {
2182 secerror("_SecRevocationDbSetUpdateFormat failed: %@", localError);
2183 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb, TAOperationWrite, TAFatalError,
2184 localError ? CFErrorGetCode(localError) : errSecInternalComponent);
2185 } else {
2186 dbc->db->changed = true; /* will notify clients of this change */
2187 dbc->db->unsupportedVersion = false;
2188 }
2189 (void) CFErrorPropagate(localError, error);
2190 return ok;
2191 }
2192
2193 static CFStringRef _SecRevocationDbCopyUpdateSource(SecRevocationDbConnectionRef dbc, CFErrorRef *error) {
2194 /* look up db_source entry in admin table; returns NULL on error */
2195 __block CFStringRef updateSource = NULL;
2196 __block bool ok = (dbc != NULL);
2197 __block CFErrorRef localError = NULL;
2198
2199 ok = ok && SecDbWithSQL(dbc->dbconn, selectDbSourceSQL, &localError, ^bool(sqlite3_stmt *selectDbSource) {
2200 ok &= SecDbStep(dbc->dbconn, selectDbSource, &localError, ^void(bool *stop) {
2201 const UInt8 *p = (const UInt8 *)sqlite3_column_blob(selectDbSource, 0);
2202 if (p != NULL) {
2203 CFIndex length = (CFIndex)sqlite3_column_bytes(selectDbSource, 0);
2204 if (length > 0) {
2205 updateSource = CFStringCreateWithBytes(kCFAllocatorDefault, p, length, kCFStringEncodingUTF8, false);
2206 }
2207 }
2208 *stop = true;
2209 });
2210 return ok;
2211 });
2212 if (!ok || localError) {
2213 secerror("_SecRevocationDbCopyUpdateSource failed: %@", localError);
2214 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb, TAOperationRead, TAFatalError,
2215 localError ? CFErrorGetCode(localError) : errSecInternalComponent);
2216 }
2217 (void) CFErrorPropagate(localError, error);
2218 return updateSource;
2219 }
2220
2221 bool _SecRevocationDbSetUpdateSource(SecRevocationDbConnectionRef dbc, CFStringRef updateSource, CFErrorRef *error) {
2222 if (!updateSource) {
2223 secerror("_SecRevocationDbSetUpdateSource failed: %d", errSecParam);
2224 return false;
2225 }
2226 __block char buffer[256];
2227 __block const char *updateSourceCStr = CFStringGetCStringPtr(updateSource, kCFStringEncodingUTF8);
2228 if (!updateSourceCStr) {
2229 if (CFStringGetCString(updateSource, buffer, 256, kCFStringEncodingUTF8)) {
2230 updateSourceCStr = buffer;
2231 }
2232 }
2233 if (!updateSourceCStr) {
2234 secerror("_SecRevocationDbSetUpdateSource failed: unable to get UTF-8 encoding");
2235 return false;
2236 }
2237 secdebug("validupdate", "setting update source to \"%s\"", updateSourceCStr);
2238
2239 __block CFErrorRef localError = NULL;
2240 __block bool ok = (dbc != NULL);
2241 ok = ok && SecDbWithSQL(dbc->dbconn, insertAdminRecordSQL, &localError, ^bool(sqlite3_stmt *insertRecord) {
2242 const char *dbSourceKey = "db_source";
2243 ok = ok && SecDbBindText(insertRecord, 1, dbSourceKey, strlen(dbSourceKey),
2244 SQLITE_TRANSIENT, &localError);
2245 ok = ok && SecDbBindInt64(insertRecord, 2,
2246 (sqlite3_int64)0, &localError);
2247 ok = ok && SecDbBindBlob(insertRecord, 3,
2248 updateSourceCStr, strlen(updateSourceCStr),
2249 SQLITE_TRANSIENT, &localError);
2250 ok = ok && SecDbStep(dbc->dbconn, insertRecord, &localError, NULL);
2251 return ok;
2252 });
2253 if (!ok || localError) {
2254 secerror("_SecRevocationDbSetUpdateSource failed: %@", localError);
2255 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb, TAOperationWrite, TAFatalError,
2256 localError ? CFErrorGetCode(localError) : errSecInternalComponent);
2257 }
2258 (void) CFErrorPropagate(localError, error);
2259 CFReleaseSafe(localError);
2260 return ok;
2261 }
2262
2263 bool SecRevocationDbSetUpdateSource(SecRevocationDbRef rdb, CFStringRef updateSource) {
2264 /* note: this function assumes it is called only by the database owner.
2265 non-owner (read-only) clients will fail if changes to the db are needed. */
2266 if (!rdb || !rdb->db) {
2267 return false;
2268 }
2269 CFErrorRef localError = NULL;
2270 bool ok = true;
2271 ok &= SecRevocationDbPerformWrite(rdb, &localError, ^bool(SecRevocationDbConnectionRef dbc, CFErrorRef *error) {
2272 return _SecRevocationDbSetUpdateSource(dbc, updateSource, error);
2273 });
2274 CFReleaseSafe(localError);
2275 return ok;
2276 }
2277
2278 static CFAbsoluteTime _SecRevocationDbGetNextUpdateTime(SecRevocationDbConnectionRef dbc, CFErrorRef *error) {
2279 /* look up check_again entry in admin table; returns 0 on error */
2280 __block CFAbsoluteTime nextUpdate = 0;
2281 __block bool ok = (dbc != NULL);
2282 __block CFErrorRef localError = NULL;
2283
2284 ok = ok && SecDbWithSQL(dbc->dbconn, selectNextUpdateSQL, &localError, ^bool(sqlite3_stmt *selectNextUpdate) {
2285 ok &= SecDbStep(dbc->dbconn, selectNextUpdate, &localError, ^void(bool *stop) {
2286 CFAbsoluteTime *p = (CFAbsoluteTime *)sqlite3_column_blob(selectNextUpdate, 0);
2287 if (p != NULL) {
2288 if (sizeof(CFAbsoluteTime) == sqlite3_column_bytes(selectNextUpdate, 0)) {
2289 nextUpdate = *p;
2290 }
2291 }
2292 *stop = true;
2293 });
2294 return ok;
2295 });
2296 if (!ok || localError) {
2297 secerror("_SecRevocationDbGetNextUpdateTime failed: %@", localError);
2298 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb, TAOperationRead, TAFatalError,
2299 localError ? CFErrorGetCode(localError) : errSecInternalComponent);
2300 }
2301 (void) CFErrorPropagate(localError, error);
2302 return nextUpdate;
2303 }
2304
2305 static bool _SecRevocationDbSetNextUpdateTime(SecRevocationDbConnectionRef dbc, CFAbsoluteTime nextUpdate, CFErrorRef *error){
2306 secdebug("validupdate", "setting next update to %f", (double)nextUpdate);
2307
2308 __block CFErrorRef localError = NULL;
2309 __block bool ok = (dbc != NULL);
2310 ok = ok && SecDbWithSQL(dbc->dbconn, insertAdminRecordSQL, &localError, ^bool(sqlite3_stmt *insertRecord) {
2311 const char *nextUpdateKey = "check_again";
2312 ok = ok && SecDbBindText(insertRecord, 1, nextUpdateKey, strlen(nextUpdateKey),
2313 SQLITE_TRANSIENT, &localError);
2314 ok = ok && SecDbBindInt64(insertRecord, 2,
2315 (sqlite3_int64)0, &localError);
2316 ok = ok && SecDbBindBlob(insertRecord, 3,
2317 &nextUpdate, sizeof(CFAbsoluteTime),
2318 SQLITE_TRANSIENT, &localError);
2319 ok = ok && SecDbStep(dbc->dbconn, insertRecord, &localError, NULL);
2320 return ok;
2321 });
2322 if (!ok || localError) {
2323 secerror("_SecRevocationDbSetNextUpdate failed: %@", localError);
2324 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb, TAOperationWrite, TAFatalError,
2325 localError ? CFErrorGetCode(localError) : errSecInternalComponent);
2326 }
2327 (void) CFErrorPropagate(localError, error);
2328 return ok;
2329 }
2330
2331 bool _SecRevocationDbRemoveAllEntries(SecRevocationDbConnectionRef dbc, CFErrorRef *error) {
2332 /* clear out the contents of the database and start fresh */
2333 bool ok = (dbc != NULL);
2334 CFErrorRef localError = NULL;
2335
2336 /* _SecRevocationDbUpdateSchema was called when db was opened, so no need to do it again. */
2337
2338 /* delete all entries */
2339 ok = ok && SecDbExec(dbc->dbconn, deleteAllEntriesSQL, &localError);
2340 secnotice("validupdate", "resetting database, result: %d (expected 1)", (ok) ? 1 : 0);
2341
2342 /* one more thing: update the schema version and format to current */
2343 ok = ok && _SecRevocationDbSetSchemaVersion(dbc, kSecRevocationDbSchemaVersion, &localError);
2344 ok = ok && _SecRevocationDbSetUpdateFormat(dbc, kSecRevocationDbUpdateFormat, &localError);
2345
2346 if (!ok || localError) {
2347 secerror("_SecRevocationDbRemoveAllEntries failed: %@", localError);
2348 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb, TAOperationWrite, TAFatalError,
2349 localError ? CFErrorGetCode(localError) : errSecInternalComponent);
2350 }
2351 (void) CFErrorPropagate(localError, error);
2352 return ok;
2353 }
2354
2355 static bool _SecRevocationDbUpdateIssuers(SecRevocationDbConnectionRef dbc, int64_t groupId, CFArrayRef issuers, CFErrorRef *error) {
2356 /* insert or replace issuer records in issuers table */
2357 if (!issuers || groupId < 0) {
2358 return false; /* must have something to insert, and a group to associate with it */
2359 }
2360 __block bool ok = (dbc != NULL);
2361 __block CFErrorRef localError = NULL;
2362 if (isArray(issuers)) {
2363 CFIndex issuerIX, issuerCount = CFArrayGetCount(issuers);
2364 for (issuerIX=0; issuerIX<issuerCount && ok; issuerIX++) {
2365 CFDataRef hash = (CFDataRef)CFArrayGetValueAtIndex(issuers, issuerIX);
2366 if (!hash) { continue; }
2367 ok = ok && SecDbWithSQL(dbc->dbconn, insertIssuerRecordSQL, &localError, ^bool(sqlite3_stmt *insertIssuer) {
2368 ok = ok && SecDbBindInt64(insertIssuer, 1,
2369 groupId, &localError);
2370 ok = ok && SecDbBindBlob(insertIssuer, 2,
2371 CFDataGetBytePtr(hash),
2372 CFDataGetLength(hash),
2373 SQLITE_TRANSIENT, &localError);
2374 /* Execute the insert statement for this issuer record. */
2375 ok = ok && SecDbStep(dbc->dbconn, insertIssuer, &localError, NULL);
2376 return ok;
2377 });
2378 }
2379 }
2380 if (!ok || localError) {
2381 secerror("_SecRevocationDbUpdateIssuers failed: %@", localError);
2382 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb, TAOperationWrite, TAFatalError,
2383 localError ? CFErrorGetCode(localError) : errSecInternalComponent);
2384 }
2385 (void) CFErrorPropagate(localError, error);
2386 return ok;
2387 }
2388
2389 static SecValidInfoFormat _SecRevocationDbGetGroupFormatForData(SecRevocationDbConnectionRef dbc, int64_t groupId, CFDataRef data) {
2390 /* determine existing format if groupId is supplied and this is a partial update,
2391 otherwise return the expected format for the given data. */
2392 SecValidInfoFormat format = kSecValidInfoFormatUnknown;
2393 if (groupId >= 0 && !dbc->fullUpdate) {
2394 format = _SecRevocationDbGetGroupFormat(dbc, groupId, NULL, NULL, NULL, NULL);
2395 }
2396 if (format == kSecValidInfoFormatUnknown && data != NULL) {
2397 /* group doesn't exist, so determine format based on length of specified data.
2398 len <= 20 is a serial number (actually, <=37, but != 32.)
2399 len==32 is a sha256 hash. otherwise: nto1. */
2400 CFIndex length = CFDataGetLength(data);
2401 if (length == 32) {
2402 format = kSecValidInfoFormatSHA256;
2403 } else if (length <= 37) {
2404 format = kSecValidInfoFormatSerial;
2405 } else if (length > 0) {
2406 format = kSecValidInfoFormatNto1;
2407 }
2408 }
2409 return format;
2410 }
2411
2412 static bool _SecRevocationDbUpdateIssuerData(SecRevocationDbConnectionRef dbc, int64_t groupId, CFDictionaryRef dict, CFErrorRef *error) {
2413 /* update/delete records in serials or hashes table. */
2414 if (!dict || groupId < 0) {
2415 return false; /* must have something to insert, and a group to associate with it */
2416 }
2417 __block bool ok = (dbc != NULL);
2418 __block CFErrorRef localError = NULL;
2419 /* process deletions */
2420 CFArrayRef deleteArray = (CFArrayRef)CFDictionaryGetValue(dict, CFSTR("delete"));
2421 if (isArray(deleteArray)) {
2422 SecValidInfoFormat format = kSecValidInfoFormatUnknown;
2423 CFIndex processed=0, identifierIX, identifierCount = CFArrayGetCount(deleteArray);
2424 for (identifierIX=0; identifierIX<identifierCount; identifierIX++) {
2425 CFDataRef identifierData = (CFDataRef)CFArrayGetValueAtIndex(deleteArray, identifierIX);
2426 if (!identifierData) { continue; }
2427 if (format == kSecValidInfoFormatUnknown) {
2428 format = _SecRevocationDbGetGroupFormatForData(dbc, groupId, identifierData);
2429 }
2430 CFStringRef sql = NULL;
2431 if (format == kSecValidInfoFormatSerial) {
2432 sql = deleteSerialRecordSQL;
2433 } else if (format == kSecValidInfoFormatSHA256) {
2434 sql = deleteSha256RecordSQL;
2435 }
2436 if (!sql) { continue; }
2437
2438 ok = ok && SecDbWithSQL(dbc->dbconn, sql, &localError, ^bool(sqlite3_stmt *deleteIdentifier) {
2439 /* (groupid,serial|sha256) */
2440 CFDataRef hexData = cfToHexData(identifierData, true);
2441 if (!hexData) { return false; }
2442 ok = ok && SecDbBindInt64(deleteIdentifier, 1,
2443 groupId, &localError);
2444 ok = ok && SecDbBindBlob(deleteIdentifier, 2,
2445 CFDataGetBytePtr(hexData),
2446 CFDataGetLength(hexData),
2447 SQLITE_TRANSIENT, &localError);
2448 /* Execute the delete statement for the identifier record. */
2449 ok = ok && SecDbStep(dbc->dbconn, deleteIdentifier, &localError, NULL);
2450 CFReleaseSafe(hexData);
2451 return ok;
2452 });
2453 if (ok) { ++processed; }
2454 }
2455 #if VERBOSE_LOGGING
2456 secdebug("validupdate", "Processed %ld of %ld deletions for group %lld, result=%s",
2457 processed, identifierCount, groupId, (ok) ? "true" : "false");
2458 #endif
2459 }
2460 /* process additions */
2461 CFArrayRef addArray = (CFArrayRef)CFDictionaryGetValue(dict, CFSTR("add"));
2462 if (isArray(addArray)) {
2463 SecValidInfoFormat format = kSecValidInfoFormatUnknown;
2464 CFIndex processed=0, identifierIX, identifierCount = CFArrayGetCount(addArray);
2465 for (identifierIX=0; identifierIX<identifierCount; identifierIX++) {
2466 CFDataRef identifierData = (CFDataRef)CFArrayGetValueAtIndex(addArray, identifierIX);
2467 if (!identifierData) { continue; }
2468 if (format == kSecValidInfoFormatUnknown) {
2469 format = _SecRevocationDbGetGroupFormatForData(dbc, groupId, identifierData);
2470 }
2471 CFStringRef sql = NULL;
2472 if (format == kSecValidInfoFormatSerial) {
2473 sql = insertSerialRecordSQL;
2474 } else if (format == kSecValidInfoFormatSHA256) {
2475 sql = insertSha256RecordSQL;
2476 }
2477 if (!sql) { continue; }
2478
2479 ok = ok && SecDbWithSQL(dbc->dbconn, sql, &localError, ^bool(sqlite3_stmt *insertIdentifier) {
2480 /* rowid,(groupid,serial|sha256) */
2481 /* rowid is autoincremented and we never set it directly */
2482 ok = ok && SecDbBindInt64(insertIdentifier, 1,
2483 groupId, &localError);
2484 ok = ok && SecDbBindBlob(insertIdentifier, 2,
2485 CFDataGetBytePtr(identifierData),
2486 CFDataGetLength(identifierData),
2487 SQLITE_TRANSIENT, &localError);
2488 /* Execute the insert statement for the identifier record. */
2489 ok = ok && SecDbStep(dbc->dbconn, insertIdentifier, &localError, NULL);
2490 return ok;
2491 });
2492 if (ok) { ++processed; }
2493 }
2494 #if VERBOSE_LOGGING
2495 secdebug("validupdate", "Processed %ld of %ld additions for group %lld, result=%s",
2496 processed, identifierCount, groupId, (ok) ? "true" : "false");
2497 #endif
2498 }
2499 if (!ok || localError) {
2500 secerror("_SecRevocationDbUpdatePerIssuerData failed: %@", localError);
2501 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb, TAOperationWrite, TAFatalError,
2502 localError ? CFErrorGetCode(localError) : errSecInternalComponent);
2503 }
2504 (void) CFErrorPropagate(localError, error);
2505 return ok;
2506 }
2507
2508 static bool _SecRevocationDbCopyDateConstraints(SecRevocationDbConnectionRef dbc,
2509 int64_t groupId, CFDateRef *notBeforeDate, CFDateRef *notAfterDate, CFErrorRef *error) {
2510 /* return true if one or both date constraints exist for a given groupId.
2511 the actual constraints are optionally returned in output CFDateRef parameters.
2512 caller is responsible for releasing date and error parameters, if provided.
2513 */
2514 __block bool ok = (dbc != NULL);
2515 __block CFDateRef localNotBefore = NULL;
2516 __block CFDateRef localNotAfter = NULL;
2517 __block CFErrorRef localError = NULL;
2518
2519 ok = ok && SecDbWithSQL(dbc->dbconn, selectDateRecordSQL, &localError, ^bool(sqlite3_stmt *selectDates) {
2520 /* (groupid,notbefore,notafter) */
2521 ok &= SecDbBindInt64(selectDates, 1, groupId, &localError);
2522 ok = ok && SecDbStep(dbc->dbconn, selectDates, &localError, ^(bool *stop) {
2523 /* if column has no value, its type will be SQLITE_NULL */
2524 if (SQLITE_NULL != sqlite3_column_type(selectDates, 0)) {
2525 CFAbsoluteTime nb = (CFAbsoluteTime)sqlite3_column_double(selectDates, 0);
2526 localNotBefore = CFDateCreate(NULL, nb);
2527 }
2528 if (SQLITE_NULL != sqlite3_column_type(selectDates, 1)) {
2529 CFAbsoluteTime na = (CFAbsoluteTime)sqlite3_column_double(selectDates, 1);
2530 localNotAfter = CFDateCreate(NULL, na);
2531 }
2532 });
2533 return ok;
2534 });
2535 /* must have at least one date constraint to return true.
2536 since date constraints are optional, not finding any should not log an error. */
2537 ok = ok && !localError && (localNotBefore != NULL || localNotAfter != NULL);
2538 if (localError) {
2539 secerror("_SecRevocationDbCopyDateConstraints failed: %@", localError);
2540 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb, TAOperationRead, TAFatalError,
2541 localError ? CFErrorGetCode(localError) : errSecInternalComponent);
2542 }
2543 if (!ok) {
2544 CFReleaseNull(localNotBefore);
2545 CFReleaseNull(localNotAfter);
2546 }
2547 if (notBeforeDate) {
2548 *notBeforeDate = localNotBefore;
2549 } else {
2550 CFReleaseSafe(localNotBefore);
2551 }
2552 if (notAfterDate) {
2553 *notAfterDate = localNotAfter;
2554 } else {
2555 CFReleaseSafe(localNotAfter);
2556 }
2557
2558 (void) CFErrorPropagate(localError, error);
2559 return ok;
2560 }
2561
2562 static bool _SecRevocationDbUpdateDateConstraints(SecRevocationDbConnectionRef dbc, int64_t groupId, CFDictionaryRef dict, CFErrorRef *error) {
2563 /* Called only from _SecRevocationDbUpdateIssuerConstraints.
2564 Function assumes that the caller has checked the input arguments.
2565 */
2566 __block bool ok = true;
2567 __block CFErrorRef localError = NULL;
2568 __block CFAbsoluteTime notBefore = -3155760000.0; /* default: 1901-01-01 00:00:00-0000 */
2569 __block CFAbsoluteTime notAfter = 31556908800.0; /* default: 3001-01-01 00:00:00-0000 */
2570
2571 CFDateRef notBeforeDate = (CFDateRef)CFDictionaryGetValue(dict, CFSTR("not-before"));
2572 CFDateRef notAfterDate = (CFDateRef)CFDictionaryGetValue(dict, CFSTR("not-after"));
2573
2574 if (isDate(notBeforeDate)) {
2575 notBefore = CFDateGetAbsoluteTime(notBeforeDate);
2576 } else {
2577 notBeforeDate = NULL;
2578 }
2579 if (isDate(notAfterDate)) {
2580 notAfter = CFDateGetAbsoluteTime(notAfterDate);
2581 } else {
2582 notAfterDate = NULL;
2583 }
2584 if (!(notBeforeDate || notAfterDate)) {
2585 return ok; /* no dates supplied, so we have nothing to update for this issuer */
2586 }
2587
2588 if (!(notBeforeDate && notAfterDate) && !dbc->fullUpdate) {
2589 /* only one date was supplied, so check for existing date constraints */
2590 CFDateRef curNotBeforeDate = NULL;
2591 CFDateRef curNotAfterDate = NULL;
2592 if (_SecRevocationDbCopyDateConstraints(dbc, groupId, &curNotBeforeDate,
2593 &curNotAfterDate, &localError)) {
2594 if (!notBeforeDate) {
2595 notBeforeDate = curNotBeforeDate;
2596 notBefore = CFDateGetAbsoluteTime(notBeforeDate);
2597 } else {
2598 CFReleaseSafe(curNotBeforeDate);
2599 }
2600 if (!notAfterDate) {
2601 notAfterDate = curNotAfterDate;
2602 notAfter = CFDateGetAbsoluteTime(notAfterDate);
2603 } else {
2604 CFReleaseSafe(curNotAfterDate);
2605 }
2606 }
2607 }
2608 ok = ok && SecDbWithSQL(dbc->dbconn, insertDateRecordSQL, &localError, ^bool(sqlite3_stmt *insertDate) {
2609 /* (groupid,notbefore,notafter) */
2610 ok = ok && SecDbBindInt64(insertDate, 1, groupId, &localError);
2611 ok = ok && SecDbBindDouble(insertDate, 2, notBefore, &localError);
2612 ok = ok && SecDbBindDouble(insertDate, 3, notAfter, &localError);
2613 ok = ok && SecDbStep(dbc->dbconn, insertDate, &localError, NULL);
2614 return ok;
2615 });
2616
2617 if (!ok || localError) {
2618 secinfo("validupdate", "_SecRevocationDbUpdateDateConstraints failed (ok=%s, localError=%@)",
2619 (ok) ? "1" : "0", localError);
2620 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb, TAOperationWrite, TAFatalError,
2621 localError ? CFErrorGetCode(localError) : errSecInternalComponent);
2622 }
2623 (void) CFErrorPropagate(localError, error);
2624 return ok;
2625 }
2626
2627 static bool _SecRevocationDbUpdatePolicyConstraints(SecRevocationDbConnectionRef dbc, int64_t groupId, CFDictionaryRef dict, CFErrorRef *error) {
2628 /* Called only from _SecRevocationDbUpdateIssuerConstraints.
2629 Function assumes that the caller has checked the input arguments.
2630 */
2631 __block bool ok = true;
2632 __block CFErrorRef localError = NULL;
2633 CFArrayRef policies = (CFArrayRef)CFDictionaryGetValue(dict, CFSTR("policies"));
2634 if (!isArray(policies)) {
2635 return ok; /* no policies supplied, so nothing to update for this issuer */
2636 }
2637
2638 __block CFDataRef data = createPoliciesData(policies);
2639 ok = data && SecDbWithSQL(dbc->dbconn, updateGroupPoliciesSQL, &localError, ^bool(sqlite3_stmt *updatePolicies) {
2640 /* (policies,groupid) */
2641 ok = ok && SecDbBindBlob(updatePolicies, 1,
2642 CFDataGetBytePtr(data),
2643 CFDataGetLength(data),
2644 SQLITE_TRANSIENT, &localError);
2645 ok = ok && SecDbBindInt64(updatePolicies, 2, groupId, &localError);
2646 ok = ok && SecDbStep(dbc->dbconn, updatePolicies, &localError, NULL);
2647 return ok;
2648 });
2649 CFReleaseSafe(data);
2650
2651 if (!ok || localError) {
2652 secinfo("validupdate", "_SecRevocationDbUpdatePolicyConstraints failed (ok=%s, localError=%@)",
2653 (ok) ? "1" : "0", localError);
2654 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb, TAOperationWrite, TAFatalError,
2655 localError ? CFErrorGetCode(localError) : errSecInternalComponent);
2656 }
2657 (void) CFErrorPropagate(localError, error);
2658 return ok;
2659 }
2660
2661 static bool _SecRevocationDbUpdateNameConstraints(SecRevocationDbConnectionRef dbc, int64_t groupId, CFDictionaryRef dict, CFErrorRef *error) {
2662 /* Called only from _SecRevocationDbUpdateIssuerConstraints.
2663 Function assumes that the caller has checked the input arguments.
2664 */
2665
2666 /* %%% (TBI:9254570) update name constraint entries here */
2667 return true;
2668 }
2669
2670 static bool _SecRevocationDbUpdateIssuerConstraints(SecRevocationDbConnectionRef dbc, int64_t groupId, CFDictionaryRef dict, CFErrorRef *error) {
2671 /* check input arguments */
2672 if (!dbc || !dict || groupId < 0) {
2673 return false;
2674 }
2675 bool ok = true;
2676 ok = ok && _SecRevocationDbUpdateDateConstraints(dbc, groupId, dict, error);
2677 ok = ok && _SecRevocationDbUpdateNameConstraints(dbc, groupId, dict, error);
2678 ok = ok && _SecRevocationDbUpdatePolicyConstraints(dbc, groupId, dict, error);
2679 return ok;
2680 }
2681
2682 static SecValidInfoFormat _SecRevocationDbGetGroupFormat(SecRevocationDbConnectionRef dbc,
2683 int64_t groupId, SecValidInfoFlags *flags, CFDataRef *data, CFDataRef *policies, CFErrorRef *error) {
2684 /* return group record fields for a given groupId.
2685 on success, returns a non-zero format type, and other field values in optional output parameters.
2686 caller is responsible for releasing data, policies, and error parameters, if provided.
2687 */
2688 __block bool ok = (dbc != NULL);
2689 __block SecValidInfoFormat format = 0;
2690 __block CFErrorRef localError = NULL;
2691
2692 /* Select the group record to determine flags and format. */
2693 ok = ok && SecDbWithSQL(dbc->dbconn, selectGroupRecordSQL, &localError, ^bool(sqlite3_stmt *selectGroup) {
2694 ok = ok && SecDbBindInt64(selectGroup, 1, groupId, &localError);
2695 ok = ok && SecDbStep(dbc->dbconn, selectGroup, &localError, ^(bool *stop) {
2696 if (flags) {
2697 *flags = (SecValidInfoFlags)sqlite3_column_int(selectGroup, 0);
2698 }
2699 format = (SecValidInfoFormat)sqlite3_column_int(selectGroup, 1);
2700 if (data) {
2701 //%%% stream the data from the db into a streamed decompression <rdar://32142637>
2702 uint8_t *p = (uint8_t *)sqlite3_column_blob(selectGroup, 2);
2703 if (p != NULL && format == kSecValidInfoFormatNto1) {
2704 CFIndex length = (CFIndex)sqlite3_column_bytes(selectGroup, 2);
2705 *data = CFDataCreate(kCFAllocatorDefault, p, length);
2706 }
2707 }
2708 if (policies) {
2709 uint8_t *p = (uint8_t *)sqlite3_column_blob(selectGroup, 3);
2710 if (p != NULL) {
2711 CFIndex length = (CFIndex)sqlite3_column_bytes(selectGroup, 3);
2712 *policies = CFDataCreate(kCFAllocatorDefault, p, length);
2713 }
2714 }
2715 });
2716 return ok;
2717 });
2718 if (!ok || localError) {
2719 secdebug("validupdate", "GetGroupFormat for groupId %lu failed", (unsigned long)groupId);
2720 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb, TAOperationRead, TAFatalError,
2721 localError ? CFErrorGetCode(localError) : errSecInternalComponent);
2722 format = kSecValidInfoFormatUnknown;
2723 }
2724 (void) CFErrorPropagate(localError, error);
2725 if (!(format > kSecValidInfoFormatUnknown)) {
2726 secdebug("validupdate", "GetGroupFormat: got format %d for groupId %lld", format, (long long)groupId);
2727 }
2728 return format;
2729 }
2730
2731 static bool _SecRevocationDbUpdateFlags(CFDictionaryRef dict, CFStringRef key, SecValidInfoFlags mask, SecValidInfoFlags *flags) {
2732 /* If a boolean value exists in the given dictionary for the given key,
2733 or an explicit "1" or "0" is specified as the key string,
2734 set or clear the corresponding bit(s) defined by the mask argument.
2735 Function returns true if the flags value was changed, false otherwise.
2736 */
2737 if (!isDictionary(dict) || !isString(key) || !flags) {
2738 return false;
2739 }
2740 bool hasValue = false, newValue = false, result = false;
2741 CFTypeRef value = (CFBooleanRef)CFDictionaryGetValue(dict, key);
2742 if (isBoolean(value)) {
2743 newValue = CFBooleanGetValue((CFBooleanRef)value);
2744 hasValue = true;
2745 } else if (BOOL_STRING_KEY_LENGTH == CFStringGetLength(key)) {
2746 if (CFStringCompare(key, kBoolTrueKey, 0) == kCFCompareEqualTo) {
2747 hasValue = newValue = true;
2748 } else if (CFStringCompare(key, kBoolFalseKey, 0) == kCFCompareEqualTo) {
2749 hasValue = true;
2750 }
2751 }
2752 if (hasValue) {
2753 SecValidInfoFlags oldFlags = *flags;
2754 if (newValue) {
2755 *flags |= mask;
2756 } else {
2757 *flags &= ~(mask);
2758 }
2759 result = (*flags != oldFlags);
2760 }
2761 return result;
2762 }
2763
2764 static bool _SecRevocationDbUpdateFilter(CFDictionaryRef dict, CFDataRef oldData, CFDataRef * __nonnull CF_RETURNS_RETAINED xmlData) {
2765 /* If xor and/or params values exist in the given dictionary, create a new
2766 property list containing the updated values, and return as a flattened
2767 data blob in the xmlData output parameter (note: caller must release.)
2768 Function returns true if there is new xmlData to save, false otherwise.
2769 */
2770 bool result = false;
2771 bool xorProvided = false;
2772 bool paramsProvided = false;
2773 bool missingData = false;
2774
2775 if (!dict || !xmlData) {
2776 return result; /* no-op if no dictionary is provided, or no way to update the data */
2777 }
2778 *xmlData = NULL;
2779 CFDataRef xorCurrent = NULL;
2780 CFDataRef xorUpdate = (CFDataRef)CFDictionaryGetValue(dict, CFSTR("xor"));
2781 if (isData(xorUpdate)) {
2782 xorProvided = true;
2783 }
2784 CFArrayRef paramsCurrent = NULL;
2785 CFArrayRef paramsUpdate = (CFArrayRef)CFDictionaryGetValue(dict, CFSTR("params"));
2786 if (isArray(paramsUpdate)) {
2787 paramsProvided = true;
2788 }
2789 if (!(xorProvided || paramsProvided)) {
2790 return result; /* nothing to update, so we can bail out here. */
2791 }
2792
2793 CFPropertyListRef nto1Current = NULL;
2794 CFMutableDictionaryRef nto1Update = CFDictionaryCreateMutable(kCFAllocatorDefault, 0,
2795 &kCFTypeDictionaryKeyCallBacks,
2796 &kCFTypeDictionaryValueCallBacks);
2797 if (!nto1Update) {
2798 return result;
2799 }
2800
2801 /* turn old data into property list */
2802 CFDataRef data = (CFDataRef)CFRetainSafe(oldData);
2803 CFDataRef inflatedData = copyInflatedData(data);
2804 if (inflatedData) {
2805 CFReleaseSafe(data);
2806 data = inflatedData;
2807 }
2808 if (data) {
2809 nto1Current = CFPropertyListCreateWithData(kCFAllocatorDefault, data, 0, NULL, NULL);
2810 CFReleaseSafe(data);
2811 }
2812 if (nto1Current) {
2813 xorCurrent = (CFDataRef)CFDictionaryGetValue((CFDictionaryRef)nto1Current, CFSTR("xor"));
2814 paramsCurrent = (CFArrayRef)CFDictionaryGetValue((CFDictionaryRef)nto1Current, CFSTR("params"));
2815 }
2816
2817 /* set current or updated xor data in new property list */
2818 if (xorProvided) {
2819 CFDataRef xorNew = NULL;
2820 if (xorCurrent) {
2821 CFIndex xorUpdateLen = CFDataGetLength(xorUpdate);
2822 CFMutableDataRef xor = CFDataCreateMutableCopy(NULL, 0, xorCurrent);
2823 if (xor && xorUpdateLen > 0) {
2824 /* truncate or zero-extend data to match update size */
2825 CFDataSetLength(xor, xorUpdateLen);
2826 /* exclusive-or update bytes over the existing data */
2827 UInt8 *xorP = (UInt8 *)CFDataGetMutableBytePtr(xor);
2828 UInt8 *updP = (UInt8 *)CFDataGetBytePtr(xorUpdate);
2829 if (xorP && updP) {
2830 for (int idx = 0; idx < xorUpdateLen; idx++) {
2831 xorP[idx] = xorP[idx] ^ updP[idx];
2832 }
2833 }
2834 }
2835 xorNew = (CFDataRef)xor;
2836 } else {
2837 xorNew = (CFDataRef)CFRetainSafe(xorUpdate);
2838 }
2839 if (xorNew) {
2840 CFDictionaryAddValue(nto1Update, CFSTR("xor"), xorNew);
2841 CFReleaseSafe(xorNew);
2842 } else {
2843 secdebug("validupdate", "Failed to get updated filter data");
2844 missingData = true;
2845 }
2846 } else if (xorCurrent) {
2847 /* not provided, so use existing xor value */
2848 CFDictionaryAddValue(nto1Update, CFSTR("xor"), xorCurrent);
2849 } else {
2850 secdebug("validupdate", "Failed to get current filter data");
2851 missingData = true;
2852 }
2853
2854 /* set current or updated params in new property list */
2855 if (paramsProvided) {
2856 CFDictionaryAddValue(nto1Update, CFSTR("params"), paramsUpdate);
2857 } else if (paramsCurrent) {
2858 /* not provided, so use existing params value */
2859 CFDictionaryAddValue(nto1Update, CFSTR("params"), paramsCurrent);
2860 } else {
2861 /* missing params: neither provided nor existing */
2862 secdebug("validupdate", "Failed to get current filter params");
2863 missingData = true;
2864 }
2865
2866 CFReleaseSafe(nto1Current);
2867 if (!missingData) {
2868 *xmlData = CFPropertyListCreateData(kCFAllocatorDefault, nto1Update,
2869 kCFPropertyListXMLFormat_v1_0,
2870 0, NULL);
2871 result = (*xmlData != NULL);
2872 }
2873 CFReleaseSafe(nto1Update);
2874
2875 /* compress the xmlData blob, if possible */
2876 if (result) {
2877 CFDataRef deflatedData = copyDeflatedData(*xmlData);
2878 if (deflatedData) {
2879 if (CFDataGetLength(deflatedData) < CFDataGetLength(*xmlData)) {
2880 CFRelease(*xmlData);
2881 *xmlData = deflatedData;
2882 } else {
2883 CFRelease(deflatedData);
2884 }
2885 }
2886 }
2887 return result;
2888 }
2889
2890
2891 static int64_t _SecRevocationDbUpdateGroup(SecRevocationDbConnectionRef dbc, int64_t groupId, CFDictionaryRef dict, CFErrorRef *error) {
2892 /* insert group record for a given groupId.
2893 if the specified groupId is < 0, a new group entry is created.
2894 returns the groupId on success, or -1 on failure.
2895 */
2896 if (!dict) {
2897 return groupId; /* no-op if no dictionary is provided */
2898 }
2899
2900 __block int64_t result = -1;
2901 __block bool ok = (dbc != NULL);
2902 __block bool isFormatChange = false;
2903 __block CFErrorRef localError = NULL;
2904
2905 __block SecValidInfoFlags flags = 0;
2906 __block SecValidInfoFormat format = kSecValidInfoFormatUnknown;
2907 __block SecValidInfoFormat formatUpdate = kSecValidInfoFormatUnknown;
2908 __block CFDataRef data = NULL;
2909 __block CFDataRef policies = NULL;
2910
2911 if (groupId >= 0) {
2912 /* fetch the flags and data for an existing group record, in case some are being changed. */
2913 if (ok) {
2914 format = _SecRevocationDbGetGroupFormat(dbc, groupId, &flags, &data, &policies, NULL);
2915 }
2916 if (format == kSecValidInfoFormatUnknown) {
2917 secdebug("validupdate", "existing group %lld has unknown format %d, flags=0x%lx",
2918 (long long)groupId, format, flags);
2919 //%%% clean up by deleting all issuers with this groupId, then the group record,
2920 // or just force a full update? note: we can get here if we fail to bind the
2921 // format value in the prepared SQL statement below.
2922 return -1;
2923 }
2924 }
2925 CFTypeRef value = (CFStringRef)CFDictionaryGetValue(dict, CFSTR("format"));
2926 if (isString(value)) {
2927 if (CFStringCompare((CFStringRef)value, CFSTR("serial"), 0) == kCFCompareEqualTo) {
2928 formatUpdate = kSecValidInfoFormatSerial;
2929 } else if (CFStringCompare((CFStringRef)value, CFSTR("sha256"), 0) == kCFCompareEqualTo) {
2930 formatUpdate = kSecValidInfoFormatSHA256;
2931 } else if (CFStringCompare((CFStringRef)value, CFSTR("nto1"), 0) == kCFCompareEqualTo) {
2932 formatUpdate = kSecValidInfoFormatNto1;
2933 }
2934 }
2935 /* if format value is explicitly supplied, then this is effectively a new group entry. */
2936 isFormatChange = (formatUpdate > kSecValidInfoFormatUnknown &&
2937 formatUpdate != format &&
2938 groupId >= 0);
2939
2940 if (isFormatChange) {
2941 secdebug("validupdate", "group %lld format change from %d to %d",
2942 (long long)groupId, format, formatUpdate);
2943 /* format of an existing group is changing; delete the group first.
2944 this should ensure that all entries referencing the old groupid are deleted.
2945 */
2946 ok = ok && SecDbWithSQL(dbc->dbconn, deleteGroupRecordSQL, &localError, ^bool(sqlite3_stmt *deleteResponse) {
2947 ok = ok && SecDbBindInt64(deleteResponse, 1, groupId, &localError);
2948 /* Execute the delete statement. */
2949 ok = ok && SecDbStep(dbc->dbconn, deleteResponse, &localError, NULL);
2950 return ok;
2951 });
2952 }
2953 ok = ok && SecDbWithSQL(dbc->dbconn, insertGroupRecordSQL, &localError, ^bool(sqlite3_stmt *insertGroup) {
2954 /* (groupid,flags,format,data,policies) */
2955 /* groups.groupid */
2956 if (ok && (!isFormatChange) && (groupId >= 0)) {
2957 /* bind to existing groupId row if known, otherwise will insert and autoincrement */
2958 ok = SecDbBindInt64(insertGroup, 1, groupId, &localError);
2959 if (!ok) {
2960 secdebug("validupdate", "failed to set groupId %lld", (long long)groupId);
2961 }
2962 }
2963 /* groups.flags */
2964 if (ok) {
2965 (void)_SecRevocationDbUpdateFlags(dict, CFSTR("complete"), kSecValidInfoComplete, &flags);
2966 (void)_SecRevocationDbUpdateFlags(dict, CFSTR("check-ocsp"), kSecValidInfoCheckOCSP, &flags);
2967 (void)_SecRevocationDbUpdateFlags(dict, CFSTR("known-intermediates-only"), kSecValidInfoKnownOnly, &flags);
2968 (void)_SecRevocationDbUpdateFlags(dict, CFSTR("require-ct"), kSecValidInfoRequireCT, &flags);
2969 (void)_SecRevocationDbUpdateFlags(dict, CFSTR("valid"), kSecValidInfoAllowlist, &flags);
2970 (void)_SecRevocationDbUpdateFlags(dict, CFSTR("no-ca"), kSecValidInfoNoCACheck, &flags);
2971 (void)_SecRevocationDbUpdateFlags(dict, CFSTR("no-ca-v2"), kSecValidInfoNoCAv2Check, &flags);
2972 (void)_SecRevocationDbUpdateFlags(dict, CFSTR("overridable"), kSecValidInfoOverridable, &flags);
2973
2974 /* date constraints exist if either "not-before" or "not-after" keys are found */
2975 CFTypeRef notBeforeValue = (CFDateRef)CFDictionaryGetValue(dict, CFSTR("not-before"));
2976 CFTypeRef notAfterValue = (CFDateRef)CFDictionaryGetValue(dict, CFSTR("not-after"));
2977 if (isDate(notBeforeValue) || isDate(notAfterValue)) {
2978 (void)_SecRevocationDbUpdateFlags(dict, kBoolTrueKey, kSecValidInfoDateConstraints, &flags);
2979 /* Note that the spec defines not-before and not-after dates as optional, such that
2980 not providing one does not change the database contents. Therefore, we can never clear
2981 this flag; either a new date entry will be supplied, or a format change will cause
2982 the entire group entry to be deleted. */
2983 }
2984 /* policy constraints exist if "policies" key is found */
2985 CFTypeRef policiesValue = (CFArrayRef)CFDictionaryGetValue(dict, CFSTR("policies"));
2986 if (isArray(policiesValue)) {
2987 (void)_SecRevocationDbUpdateFlags(dict, kBoolTrueKey, kSecValidInfoPolicyConstraints, &flags);
2988 /* As above, not providing this value in an update does not change the existing state,
2989 so we never need to clear this flag once it is set. */
2990 }
2991
2992 /* %%% (TBI:9254570) name constraints don't exist yet */
2993 (void)_SecRevocationDbUpdateFlags(dict, kBoolFalseKey, kSecValidInfoNameConstraints, &flags);
2994
2995 ok = SecDbBindInt(insertGroup, 2, (int)flags, &localError);
2996 if (!ok) {
2997 secdebug("validupdate", "failed to set flags (%lu) for groupId %lld", flags, (long long)groupId);
2998 }
2999 }
3000 /* groups.format */
3001 if (ok) {
3002 SecValidInfoFormat formatValue = format;
3003 if (formatUpdate > kSecValidInfoFormatUnknown) {
3004 formatValue = formatUpdate;
3005 }
3006 ok = SecDbBindInt(insertGroup, 3, (int)formatValue, &localError);
3007 if (!ok) {
3008 secdebug("validupdate", "failed to set format (%d) for groupId %lld", formatValue, (long long)groupId);
3009 }
3010 }
3011 /* groups.data */
3012 CFDataRef xmlData = NULL;
3013 if (ok) {
3014 bool hasFilter = ((formatUpdate == kSecValidInfoFormatNto1) ||
3015 (formatUpdate == kSecValidInfoFormatUnknown &&
3016 format == kSecValidInfoFormatNto1));
3017 if (hasFilter) {
3018 CFDataRef dataValue = data; /* use existing data */
3019 if (_SecRevocationDbUpdateFilter(dict, data, &xmlData)) {
3020 dataValue = xmlData; /* use updated data */
3021 }
3022 if (dataValue) {
3023 ok = SecDbBindBlob(insertGroup, 4,
3024 CFDataGetBytePtr(dataValue),
3025 CFDataGetLength(dataValue),
3026 SQLITE_TRANSIENT, &localError);
3027 }
3028 if (!ok) {
3029 secdebug("validupdate", "failed to set data for groupId %lld",
3030 (long long)groupId);
3031 }
3032 }
3033 /* else there is no data, so NULL is implicitly bound to column 4 */
3034 }
3035 /* groups.policies */
3036 CFDataRef newPoliciesData = NULL;
3037 if (ok) {
3038 CFDataRef policiesValue = policies; /* use existing policies */
3039 newPoliciesData = createPoliciesData((CFArrayRef)CFDictionaryGetValue(dict, CFSTR("policies")));
3040 if (newPoliciesData) {
3041 policiesValue = newPoliciesData; /* use updated policies */
3042 }
3043 if (policiesValue) {
3044 ok = SecDbBindBlob(insertGroup, 5,
3045 CFDataGetBytePtr(policiesValue),
3046 CFDataGetLength(policiesValue),
3047 SQLITE_TRANSIENT, &localError);
3048 }
3049 /* else there is no policy data, so NULL is implicitly bound to column 5 */
3050 if (!ok) {
3051 secdebug("validupdate", "failed to set policies for groupId %lld",
3052 (long long)groupId);
3053 }
3054 }
3055
3056 /* Execute the insert statement for the group record. */
3057 if (ok) {
3058 ok = SecDbStep(dbc->dbconn, insertGroup, &localError, NULL);
3059 if (!ok) {
3060 secdebug("validupdate", "failed to execute insertGroup statement for groupId %lld",
3061 (long long)groupId);
3062 }
3063 result = (int64_t)sqlite3_last_insert_rowid(SecDbHandle(dbc->dbconn));
3064 }
3065 if (!ok) {
3066 secdebug("validupdate", "failed to insert group %lld", (long long)result);
3067 }
3068 /* Clean up temporary allocations made in this block. */
3069 CFReleaseSafe(xmlData);
3070 CFReleaseSafe(newPoliciesData);
3071 return ok;
3072 });
3073
3074 CFReleaseSafe(data);
3075 CFReleaseSafe(policies);
3076
3077 if (!ok || localError) {
3078 secerror("_SecRevocationDbUpdateGroup failed: %@", localError);
3079 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb, TAOperationWrite, TAFatalError,
3080 localError ? CFErrorGetCode(localError) : errSecInternalComponent);
3081 }
3082 (void) CFErrorPropagate(localError, error);
3083 return result;
3084 }
3085
3086 static int64_t _SecRevocationDbGroupIdForIssuerHash(SecRevocationDbConnectionRef dbc, CFDataRef hash, CFErrorRef *error) {
3087 /* look up issuer hash in issuers table to get groupid, if it exists */
3088 __block int64_t groupId = -1;
3089 __block bool ok = (dbc != NULL);
3090 __block CFErrorRef localError = NULL;
3091
3092 if (!hash) {
3093 secdebug("validupdate", "failed to get hash (%@)", hash);
3094 }
3095 require(hash && dbc, errOut);
3096
3097 /* This is the starting point for any lookup; find a group id for the given issuer hash.
3098 Before we do that, need to verify the current db_version. We cannot use results from a
3099 database created with a schema version older than the minimum supported version.
3100 However, we may be able to use results from a newer version. At the next database
3101 update interval, if the existing schema is old, we'll be removing and recreating
3102 the database contents with the current schema version.
3103 */
3104 int64_t db_version = _SecRevocationDbGetSchemaVersion(dbc->db, dbc, NULL);
3105 if (db_version < kSecRevocationDbMinSchemaVersion) {
3106 if (!dbc->db->unsupportedVersion) {
3107 secdebug("validupdate", "unsupported db_version: %lld", (long long)db_version);
3108 dbc->db->unsupportedVersion = true; /* only warn once for a given unsupported version */
3109 }
3110 }
3111 require_quiet(db_version >= kSecRevocationDbMinSchemaVersion, errOut);
3112
3113 /* Look up provided issuer_hash in the issuers table.
3114 */
3115 ok = ok && SecDbWithSQL(dbc->dbconn, selectGroupIdSQL, &localError, ^bool(sqlite3_stmt *selectGroupId) {
3116 ok &= SecDbBindBlob(selectGroupId, 1, CFDataGetBytePtr(hash), CFDataGetLength(hash), SQLITE_TRANSIENT, &localError);
3117 ok &= SecDbStep(dbc->dbconn, selectGroupId, &localError, ^(bool *stopGroupId) {
3118 groupId = sqlite3_column_int64(selectGroupId, 0);
3119 });
3120 return ok;
3121 });
3122
3123 errOut:
3124 if (!ok || localError) {
3125 secerror("_SecRevocationDbGroupIdForIssuerHash failed: %@", localError);
3126 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb, TAOperationRead, TAFatalError,
3127 localError ? CFErrorGetCode(localError) : errSecInternalComponent);
3128 }
3129 (void) CFErrorPropagate(localError, error);
3130 return groupId;
3131 }
3132
3133 static bool _SecRevocationDbApplyGroupDelete(SecRevocationDbConnectionRef dbc, CFDataRef issuerHash, CFErrorRef *error) {
3134 /* delete group associated with the given issuer;
3135 schema trigger will delete associated issuers, serials, and hashes. */
3136 __block int64_t groupId = -1;
3137 __block bool ok = (dbc != NULL);
3138 __block CFErrorRef localError = NULL;
3139
3140 if (ok) {
3141 groupId = _SecRevocationDbGroupIdForIssuerHash(dbc, issuerHash, &localError);
3142 }
3143 if (groupId < 0) {
3144 if (!localError) {
3145 SecError(errSecParam, &localError, CFSTR("group not found for issuer"));
3146 }
3147 ok = false;
3148 }
3149 ok = ok && SecDbWithSQL(dbc->dbconn, deleteGroupRecordSQL, &localError, ^bool(sqlite3_stmt *deleteResponse) {
3150 ok &= SecDbBindInt64(deleteResponse, 1, groupId, &localError);
3151 /* Execute the delete statement. */
3152 ok = ok && SecDbStep(dbc->dbconn, deleteResponse, &localError, NULL);
3153 return ok;
3154 });
3155 if (!ok || localError) {
3156 secerror("_SecRevocationDbApplyGroupDelete failed: %@", localError);
3157 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb, TAOperationWrite, TAFatalError,
3158 localError ? CFErrorGetCode(localError) : errSecInternalComponent);
3159 }
3160 (void) CFErrorPropagate(localError, error);
3161 return ok;
3162 }
3163
3164 static bool _SecRevocationDbApplyGroupUpdate(SecRevocationDbConnectionRef dbc, CFDictionaryRef dict, CFErrorRef *error) {
3165 /* process one issuer group's update dictionary */
3166 __block int64_t groupId = -1;
3167 __block bool ok = (dbc != NULL);
3168 __block CFErrorRef localError = NULL;
3169
3170 CFArrayRef issuers = (dict) ? (CFArrayRef)CFDictionaryGetValue(dict, CFSTR("issuer-hash")) : NULL;
3171 /* if this is not a full update, then look for existing group id */
3172 if (ok && isArray(issuers) && !dbc->fullUpdate) {
3173 CFIndex issuerIX, issuerCount = CFArrayGetCount(issuers);
3174 /* while we have issuers and haven't found a matching group id */
3175 for (issuerIX=0; issuerIX<issuerCount && groupId < 0; issuerIX++) {
3176 CFDataRef hash = (CFDataRef)CFArrayGetValueAtIndex(issuers, issuerIX);
3177 if (!hash) { continue; }
3178 groupId = _SecRevocationDbGroupIdForIssuerHash(dbc, hash, &localError);
3179 }
3180 if (groupId >= 0) {
3181 /* according to the spec, we must replace all existing issuers with
3182 the new issuers list, so delete all issuers in the group first. */
3183 ok = ok && SecDbWithSQL(dbc->dbconn, deleteGroupIssuersSQL, &localError, ^bool(sqlite3_stmt *deleteIssuers) {
3184 ok = ok && SecDbBindInt64(deleteIssuers, 1, groupId, &localError);
3185 ok = ok && SecDbStep(dbc->dbconn, deleteIssuers, &localError, NULL);
3186 return ok;
3187 });
3188 }
3189 }
3190 /* create or update the group entry */
3191 if (ok) {
3192 groupId = _SecRevocationDbUpdateGroup(dbc, groupId, dict, &localError);
3193 }
3194 if (groupId < 0) {
3195 secdebug("validupdate", "failed to get groupId");
3196 ok = false;
3197 } else {
3198 /* create or update issuer entries, now that we know the group id */
3199 ok = ok && _SecRevocationDbUpdateIssuers(dbc, groupId, issuers, &localError);
3200 /* create or update entries in serials or hashes tables */
3201 ok = ok && _SecRevocationDbUpdateIssuerData(dbc, groupId, dict, &localError);
3202 /* create or update entries in dates/names/policies tables */
3203 ok = ok && _SecRevocationDbUpdateIssuerConstraints(dbc, groupId, dict, &localError);
3204 }
3205
3206 (void) CFErrorPropagate(localError, error);
3207 return ok;
3208 }
3209
3210 bool _SecRevocationDbApplyUpdate(SecRevocationDbConnectionRef dbc, CFDictionaryRef update, CFIndex version, CFErrorRef *error) {
3211 /* process entire update dictionary */
3212 if (!dbc || !dbc->db || !update) {
3213 secerror("_SecRevocationDbApplyUpdate failed: invalid args");
3214 SecError(errSecParam, error, CFSTR("_SecRevocationDbApplyUpdate: invalid db or update parameter"));
3215 return false;
3216 }
3217
3218 CFDictionaryRef localUpdate = (CFDictionaryRef)CFRetainSafe(update);
3219 CFErrorRef localError = NULL;
3220 bool ok = true;
3221
3222 CFTypeRef value = NULL;
3223 CFIndex deleteCount = 0;
3224 CFIndex updateCount = 0;
3225
3226 dbc->db->updateInProgress = true;
3227
3228 /* check whether this is a full update */
3229 value = (CFBooleanRef)CFDictionaryGetValue(update, CFSTR("full"));
3230 if (isBoolean(value) && CFBooleanGetValue((CFBooleanRef)value)) {
3231 /* clear the database before processing a full update */
3232 dbc->fullUpdate = true;
3233 secdebug("validupdate", "update has \"full\" attribute; clearing database");
3234 ok = ok && _SecRevocationDbRemoveAllEntries(dbc, &localError);
3235 }
3236
3237 /* process 'delete' list */
3238 value = (CFArrayRef)CFDictionaryGetValue(localUpdate, CFSTR("delete"));
3239 if (isArray(value)) {
3240 deleteCount = CFArrayGetCount((CFArrayRef)value);
3241 secdebug("validupdate", "processing %ld deletes", (long)deleteCount);
3242 for (CFIndex deleteIX=0; deleteIX<deleteCount; deleteIX++) {
3243 CFDataRef issuerHash = (CFDataRef)CFArrayGetValueAtIndex((CFArrayRef)value, deleteIX);
3244 if (isData(issuerHash)) {
3245 ok = ok && _SecRevocationDbApplyGroupDelete(dbc, issuerHash, &localError);
3246 } else {
3247 secdebug("validupdate", "skipping delete %ld (hash is not a data value)", (long)deleteIX);
3248 }
3249 }
3250 }
3251
3252 /* process 'update' list */
3253 value = (CFArrayRef)CFDictionaryGetValue(localUpdate, CFSTR("update"));
3254 if (isArray(value)) {
3255 updateCount = CFArrayGetCount((CFArrayRef)value);
3256 secdebug("validupdate", "processing %ld updates", (long)updateCount);
3257 for (CFIndex updateIX=0; updateIX<updateCount; updateIX++) {
3258 CFDictionaryRef dict = (CFDictionaryRef)CFArrayGetValueAtIndex((CFArrayRef)value, updateIX);
3259 if (isDictionary(dict)) {
3260 ok = ok && _SecRevocationDbApplyGroupUpdate(dbc, dict, &localError);
3261 } else {
3262 secdebug("validupdate", "skipping update %ld (not a dictionary)", (long)updateIX);
3263 }
3264 }
3265 }
3266 CFReleaseSafe(localUpdate);
3267
3268 /* set version */
3269 ok = ok && _SecRevocationDbSetVersion(dbc, version, &localError);
3270
3271 /* set interval if not already set, or changed */
3272 int64_t interval = _SecRevocationDbGetUpdateInterval(dbc, NULL);
3273 if (interval != dbc->precommitInterval) {
3274 interval = (dbc->precommitInterval > 0) ? dbc->precommitInterval : kSecStdUpdateInterval;
3275 ok = ok && _SecRevocationDbSetUpdateInterval(dbc, interval, &localError);
3276 }
3277
3278 /* set db_version if not already set */
3279 int64_t db_version = _SecRevocationDbGetSchemaVersion(dbc->db, dbc, NULL);
3280 if (db_version <= 0) {
3281 ok = ok && _SecRevocationDbSetSchemaVersion(dbc, kSecRevocationDbSchemaVersion, &localError);
3282 }
3283
3284 /* set db_format if not already set */
3285 int64_t db_format = _SecRevocationDbGetUpdateFormat(dbc, NULL);
3286 if (db_format <= 0) {
3287 ok = ok && _SecRevocationDbSetUpdateFormat(dbc, kSecRevocationDbUpdateFormat, &localError);
3288 }
3289
3290 /* purge the in-memory cache */
3291 SecRevocationDbCachePurge(dbc->db);
3292
3293 dbc->db->updateInProgress = false;
3294
3295 (void) CFErrorPropagate(localError, error);
3296 return ok;
3297 }
3298
3299 static bool _SecRevocationDbSerialInGroup(SecRevocationDbConnectionRef dbc,
3300 CFDataRef serial,
3301 int64_t groupId,
3302 CFErrorRef *error) {
3303 __block bool result = false;
3304 __block bool ok = true;
3305 __block CFErrorRef localError = NULL;
3306 require(dbc && serial, errOut);
3307 ok &= SecDbWithSQL(dbc->dbconn, selectSerialRecordSQL, &localError, ^bool(sqlite3_stmt *selectSerial) {
3308 ok &= SecDbBindInt64(selectSerial, 1, groupId, &localError);
3309 ok &= SecDbBindBlob(selectSerial, 2, CFDataGetBytePtr(serial),
3310 CFDataGetLength(serial), SQLITE_TRANSIENT, &localError);
3311 ok &= SecDbStep(dbc->dbconn, selectSerial, &localError, ^(bool *stop) {
3312 int64_t foundRowId = (int64_t)sqlite3_column_int64(selectSerial, 0);
3313 result = (foundRowId > 0);
3314 });
3315 return ok;
3316 });
3317
3318 errOut:
3319 if (!ok || localError) {
3320 secerror("_SecRevocationDbSerialInGroup failed: %@", localError);
3321 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb, TAOperationRead, TAFatalError,
3322 localError ? CFErrorGetCode(localError) : errSecInternalComponent);
3323 }
3324 (void) CFErrorPropagate(localError, error);
3325 return result;
3326 }
3327
3328 static bool _SecRevocationDbCertHashInGroup(SecRevocationDbConnectionRef dbc,
3329 CFDataRef certHash,
3330 int64_t groupId,
3331 CFErrorRef *error) {
3332 __block bool result = false;
3333 __block bool ok = true;
3334 __block CFErrorRef localError = NULL;
3335 require(dbc && certHash, errOut);
3336 ok &= SecDbWithSQL(dbc->dbconn, selectHashRecordSQL, &localError, ^bool(sqlite3_stmt *selectHash) {
3337 ok &= SecDbBindInt64(selectHash, 1, groupId, &localError);
3338 ok = SecDbBindBlob(selectHash, 2, CFDataGetBytePtr(certHash),
3339 CFDataGetLength(certHash), SQLITE_TRANSIENT, &localError);
3340 ok &= SecDbStep(dbc->dbconn, selectHash, &localError, ^(bool *stop) {
3341 int64_t foundRowId = (int64_t)sqlite3_column_int64(selectHash, 0);
3342 result = (foundRowId > 0);
3343 });
3344 return ok;
3345 });
3346
3347 errOut:
3348 if (!ok || localError) {
3349 secerror("_SecRevocationDbCertHashInGroup failed: %@", localError);
3350 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb, TAOperationRead, TAFatalError,
3351 localError ? CFErrorGetCode(localError) : errSecInternalComponent);
3352 }
3353 (void) CFErrorPropagate(localError, error);
3354 return result;
3355 }
3356
3357 static bool _SecRevocationDbSerialInFilter(SecRevocationDbConnectionRef dbc,
3358 CFDataRef serialData,
3359 CFDataRef xmlData) {
3360 /* N-To-1 filter implementation.
3361 The 'xmlData' parameter is a flattened XML dictionary,
3362 containing 'xor' and 'params' keys. First order of
3363 business is to reconstitute the blob into components.
3364 */
3365 bool result = false;
3366 CFRetainSafe(xmlData);
3367 CFDataRef propListData = xmlData;
3368 /* Expand data blob if needed */
3369 CFDataRef inflatedData = copyInflatedData(propListData);
3370 if (inflatedData) {
3371 CFReleaseSafe(propListData);
3372 propListData = inflatedData;
3373 }
3374 CFDataRef xor = NULL;
3375 CFArrayRef params = NULL;
3376 CFPropertyListRef nto1 = CFPropertyListCreateWithData(kCFAllocatorDefault, propListData, 0, NULL, NULL);
3377 if (nto1) {
3378 xor = (CFDataRef)CFDictionaryGetValue((CFDictionaryRef)nto1, CFSTR("xor"));
3379 params = (CFArrayRef)CFDictionaryGetValue((CFDictionaryRef)nto1, CFSTR("params"));
3380 }
3381 uint8_t *hash = (xor) ? (uint8_t*)CFDataGetBytePtr(xor) : NULL;
3382 CFIndex hashLen = (hash) ? CFDataGetLength(xor) : 0;
3383 uint8_t *serial = (serialData) ? (uint8_t*)CFDataGetBytePtr(serialData) : NULL;
3384 CFIndex serialLen = (serial) ? CFDataGetLength(serialData) : 0;
3385
3386 require(hash && serial && params, errOut);
3387
3388 const uint32_t FNV_OFFSET_BASIS = 2166136261;
3389 const uint32_t FNV_PRIME = 16777619;
3390 bool notInHash = false;
3391 CFIndex ix, count = CFArrayGetCount(params);
3392 for (ix = 0; ix < count; ix++) {
3393 int32_t param;
3394 CFNumberRef cfnum = (CFNumberRef)CFArrayGetValueAtIndex(params, ix);
3395 if (!isNumber(cfnum) ||
3396 !CFNumberGetValue(cfnum, kCFNumberSInt32Type, &param)) {
3397 secinfo("validupdate", "error processing filter params at index %ld", (long)ix);
3398 continue;
3399 }
3400 /* process one param */
3401 uint32_t hval = FNV_OFFSET_BASIS ^ param;
3402 CFIndex i = serialLen;
3403 while (i > 0) {
3404 hval = ((hval ^ (serial[--i])) * FNV_PRIME) & 0xFFFFFFFF;
3405 }
3406 hval = hval % (hashLen * 8);
3407 if ((hash[hval/8] & (1 << (hval % 8))) == 0) {
3408 notInHash = true; /* definitely not in hash */
3409 break;
3410 }
3411 }
3412 if (!notInHash) {
3413 /* probabilistically might be in hash if we get here. */
3414 result = true;
3415 }
3416
3417 errOut:
3418 CFReleaseSafe(nto1);
3419 CFReleaseSafe(propListData);
3420 return result;
3421 }
3422
3423 static SecValidInfoRef _SecRevocationDbValidInfoForCertificate(SecRevocationDbConnectionRef dbc,
3424 SecCertificateRef certificate,
3425 CFDataRef issuerHash,
3426 CFErrorRef *error) {
3427 __block CFErrorRef localError = NULL;
3428 __block SecValidInfoFlags flags = 0;
3429 __block SecValidInfoFormat format = kSecValidInfoFormatUnknown;
3430 __block CFDataRef data = NULL;
3431
3432 bool matched = false;
3433 bool isOnList = false;
3434 int64_t groupId = 0;
3435 CFDataRef serial = NULL;
3436 CFDataRef certHash = NULL;
3437 CFDateRef notBeforeDate = NULL;
3438 CFDateRef notAfterDate = NULL;
3439 CFDataRef names = NULL;
3440 CFDataRef policies = NULL;
3441 SecValidInfoRef result = NULL;
3442
3443 require((serial = SecCertificateCopySerialNumberData(certificate, NULL)) != NULL, errOut);
3444 require((certHash = SecCertificateCopySHA256Digest(certificate)) != NULL, errOut);
3445 require_quiet((groupId = _SecRevocationDbGroupIdForIssuerHash(dbc, issuerHash, &localError)) > 0, errOut);
3446
3447 /* Look up the group record to determine flags and format. */
3448 format = _SecRevocationDbGetGroupFormat(dbc, groupId, &flags, &data, &policies, &localError);
3449
3450 if (format == kSecValidInfoFormatUnknown) {
3451 /* No group record found for this issuer. Don't return a SecValidInfoRef */
3452 goto errOut;
3453 }
3454 else if (format == kSecValidInfoFormatSerial) {
3455 /* Look up certificate's serial number in the serials table. */
3456 matched = _SecRevocationDbSerialInGroup(dbc, serial, groupId, &localError);
3457 }
3458 else if (format == kSecValidInfoFormatSHA256) {
3459 /* Look up certificate's SHA-256 hash in the hashes table. */
3460 matched = _SecRevocationDbCertHashInGroup(dbc, certHash, groupId, &localError);
3461 }
3462 else if (format == kSecValidInfoFormatNto1) {
3463 /* Perform a Bloom filter match against the serial. If matched is false,
3464 then the cert is definitely not in the list. But if matched is true,
3465 we don't know for certain, so we would need to check OCSP. */
3466 matched = _SecRevocationDbSerialInFilter(dbc, serial, data);
3467 }
3468
3469 if (matched) {
3470 /* Found a specific match for this certificate. */
3471 secdebug("validupdate", "Valid db matched certificate: %@, format=%d, flags=0x%lx",
3472 certHash, format, flags);
3473 isOnList = true;
3474 }
3475
3476 /* If supplemental constraints are present for this issuer, then we always match. */
3477 if ((flags & kSecValidInfoDateConstraints) &&
3478 (_SecRevocationDbCopyDateConstraints(dbc, groupId, &notBeforeDate, &notAfterDate, &localError))) {
3479 secdebug("validupdate", "Valid db matched supplemental date constraints for groupId %lld: nb=%@, na=%@",
3480 (long long)groupId, notBeforeDate, notAfterDate);
3481 }
3482
3483
3484 /* Return SecValidInfo for certificates for which an issuer entry is found. */
3485 result = SecValidInfoCreate(format, flags, isOnList,
3486 certHash, issuerHash, /*anchorHash*/ NULL,
3487 notBeforeDate, notAfterDate,
3488 names, policies);
3489
3490 if (result && SecIsAppleTrustAnchor(certificate, 0)) {
3491 /* Prevent a catch-22. */
3492 secdebug("validupdate", "Valid db match for Apple trust anchor: %@, format=%d, flags=0x%lx",
3493 certHash, format, flags);
3494 CFReleaseNull(result);
3495 }
3496
3497 errOut:
3498 (void) CFErrorPropagate(localError, error);
3499 CFReleaseSafe(data);
3500 CFReleaseSafe(certHash);
3501 CFReleaseSafe(serial);
3502 CFReleaseSafe(notBeforeDate);
3503 CFReleaseSafe(notAfterDate);
3504 CFReleaseSafe(names);
3505 CFReleaseSafe(policies);
3506 return result;
3507 }
3508
3509 static SecValidInfoRef _SecRevocationDbCopyMatching(SecRevocationDbConnectionRef dbc,
3510 SecCertificateRef certificate,
3511 SecCertificateRef issuer) {
3512 SecValidInfoRef result = NULL;
3513 CFErrorRef error = NULL;
3514 CFDataRef issuerHash = NULL;
3515
3516 require(dbc && certificate && issuer, errOut);
3517 require(issuerHash = SecCertificateCopySHA256Digest(issuer), errOut);
3518
3519 /* Check for the result in the cache. */
3520 result = SecRevocationDbCacheRead(dbc->db, certificate, issuerHash);
3521
3522 /* Upon cache miss, get the result from the database and add it to the cache. */
3523 if (!result) {
3524 result = _SecRevocationDbValidInfoForCertificate(dbc, certificate, issuerHash, &error);
3525 SecRevocationDbCacheWrite(dbc->db, result);
3526 }
3527
3528 errOut:
3529 CFReleaseSafe(issuerHash);
3530 CFReleaseSafe(error);
3531 return result;
3532 }
3533
3534 /* Return the update source as a retained CFStringRef.
3535 If the value cannot be obtained, NULL is returned.
3536 */
3537 CFStringRef SecRevocationDbCopyUpdateSource(void) {
3538 __block CFStringRef result = NULL;
3539 SecRevocationDbWith(^(SecRevocationDbRef db) {
3540 (void) SecRevocationDbPerformRead(db, NULL, ^bool(SecRevocationDbConnectionRef dbc, CFErrorRef *blockError) {
3541 result = _SecRevocationDbCopyUpdateSource(dbc, blockError);
3542 return (bool)result;
3543 });
3544 });
3545 return result;
3546 }
3547
3548 /* Set the next update value for the revocation database.
3549 (This function is expected to be called only by the database
3550 maintainer, normally the system instance of trustd. If the
3551 caller does not have write access, this is a no-op.)
3552 */
3553 bool SecRevocationDbSetNextUpdateTime(CFAbsoluteTime nextUpdate, CFErrorRef *error) {
3554 __block bool ok = true;
3555 __block CFErrorRef localError = NULL;
3556 SecRevocationDbWith(^(SecRevocationDbRef rdb) {
3557 ok &= SecRevocationDbPerformWrite(rdb, &localError, ^bool(SecRevocationDbConnectionRef dbc, CFErrorRef *blockError) {
3558 return _SecRevocationDbSetNextUpdateTime(dbc, nextUpdate, blockError);
3559 });
3560 });
3561 (void) CFErrorPropagate(localError, error);
3562 return ok;
3563 }
3564
3565 /* Return the next update value as a CFAbsoluteTime.
3566 If the value cannot be obtained, -1 is returned.
3567 */
3568 CFAbsoluteTime SecRevocationDbGetNextUpdateTime(void) {
3569 __block CFAbsoluteTime result = -1;
3570 SecRevocationDbWith(^(SecRevocationDbRef db) {
3571 (void) SecRevocationDbPerformRead(db, NULL, ^bool(SecRevocationDbConnectionRef dbc, CFErrorRef *blockError) {
3572 result = _SecRevocationDbGetNextUpdateTime(dbc, blockError);
3573 return true;
3574 });
3575 });
3576 return result;
3577 }
3578
3579 /* Return the serial background queue for database updates.
3580 If the queue cannot be obtained, NULL is returned.
3581 */
3582 dispatch_queue_t SecRevocationDbGetUpdateQueue(void) {
3583 __block dispatch_queue_t result = NULL;
3584 SecRevocationDbWith(^(SecRevocationDbRef db) {
3585 result = (db) ? db->update_queue : NULL;
3586 });
3587 return result;
3588 }
3589
3590 /* Release all connections to the revocation database.
3591 */
3592 void SecRevocationDbReleaseAllConnections(void) {
3593 SecRevocationDbWith(^(SecRevocationDbRef db) {
3594 SecDbReleaseAllConnections((db) ? db->db : NULL);
3595 });
3596 }
3597
3598 /* === SecRevocationDb API === */
3599
3600 /* Given a certificate and its issuer, returns a SecValidInfoRef if the
3601 valid database contains matching info; otherwise returns NULL.
3602 Caller must release the returned SecValidInfoRef when finished.
3603 */
3604 SecValidInfoRef SecRevocationDbCopyMatching(SecCertificateRef certificate,
3605 SecCertificateRef issuer) {
3606 __block SecValidInfoRef result = NULL;
3607 SecRevocationDbWith(^(SecRevocationDbRef db) {
3608 (void) SecRevocationDbPerformRead(db, NULL, ^bool(SecRevocationDbConnectionRef dbc, CFErrorRef *blockError) {
3609 result = _SecRevocationDbCopyMatching(dbc, certificate, issuer);
3610 return (bool)result;
3611 });
3612 });
3613 return result;
3614 }
3615
3616 /* Given an issuer, returns true if an entry for this issuer exists in
3617 the database (i.e. a known CA). If the provided certificate is NULL,
3618 or its entry is not found, the function returns false.
3619 */
3620 bool SecRevocationDbContainsIssuer(SecCertificateRef issuer) {
3621 if (!issuer) {
3622 return false;
3623 }
3624 __block bool result = false;
3625 SecRevocationDbWith(^(SecRevocationDbRef db) {
3626 (void) SecRevocationDbPerformRead(db, NULL, ^bool(SecRevocationDbConnectionRef dbc, CFErrorRef *blockError) {
3627 CFDataRef issuerHash = SecCertificateCopySHA256Digest(issuer);
3628 int64_t groupId = _SecRevocationDbGroupIdForIssuerHash(dbc, issuerHash, blockError);
3629 CFReleaseSafe(issuerHash);
3630 result = (groupId > 0);
3631 return result;
3632 });
3633 });
3634 return result;
3635 }
3636
3637 /* Return the current version of the revocation database.
3638 A version of 0 indicates an empty database which must be populated.
3639 If the version cannot be obtained, -1 is returned.
3640 */
3641 CFIndex SecRevocationDbGetVersion(void) {
3642 __block CFIndex result = -1;
3643 SecRevocationDbWith(^(SecRevocationDbRef db) {
3644 (void) SecRevocationDbPerformRead(db, NULL, ^bool(SecRevocationDbConnectionRef dbc, CFErrorRef *blockError) {
3645 result = (CFIndex)_SecRevocationDbGetVersion(dbc, blockError);
3646 return (result >= 0);
3647 });
3648 });
3649 return result;
3650 }
3651
3652 /* Return the current schema version of the revocation database.
3653 A version of 0 indicates an empty database which must be populated.
3654 If the schema version cannot be obtained, -1 is returned.
3655 */
3656 CFIndex SecRevocationDbGetSchemaVersion(void) {
3657 __block CFIndex result = -1;
3658 SecRevocationDbWith(^(SecRevocationDbRef db) {
3659 result = (CFIndex)_SecRevocationDbGetSchemaVersion(db, NULL, NULL);
3660 });
3661 return result;
3662 }
3663
3664 /* Return the current update format of the revocation database.
3665 A version of 0 indicates the format was unknown.
3666 If the update format cannot be obtained, -1 is returned.
3667 */
3668 CFIndex SecRevocationDbGetUpdateFormat(void) {
3669 __block CFIndex result = -1;
3670 SecRevocationDbWith(^(SecRevocationDbRef db) {
3671 (void) SecRevocationDbPerformRead(db, NULL, ^bool(SecRevocationDbConnectionRef dbc, CFErrorRef *blockError) {
3672 result = (CFIndex)_SecRevocationDbGetUpdateFormat(dbc, blockError);
3673 return (result >= 0);
3674 });
3675 });
3676 return result;
3677 }
3678
3679 // MARK: -
3680 // MARK: Digests
3681 /*
3682 ==============================================================================
3683 Digest computation
3684 ==============================================================================
3685 */
3686
3687 /* Returns array of SHA-256 hashes computed over the contents of a valid.sqlite3
3688 database, in the order specified by the valid-server-api documentation. The
3689 resulting hashes can be compared against those in the update's 'hash' array.
3690
3691 Hash 0: full database (all fields in initial Valid specification)
3692 Hash 1: all issuer_hash arrays, plus not-after and not-before dates for each
3693 Hash 2: subset of issuer_hash arrays where the no-ca-v2 flag is set
3694 */
3695 static CF_RETURNS_RETAINED CFArrayRef SecRevocationDbComputeFullContentDigests(SecRevocationDbConnectionRef dbc, CFErrorRef *error) {
3696 if (!dbc) { return NULL; }
3697 __block bool ok = true;
3698 __block CFErrorRef localError = NULL;
3699 __block uint32_t N[4]={0,0,0,0};
3700 __block CC_SHA256_CTX hash0_ctx, hash1_ctx, hash2_ctx;
3701 CC_SHA256_Init(&hash0_ctx);
3702 CC_SHA256_Init(&hash1_ctx);
3703 CC_SHA256_Init(&hash2_ctx);
3704
3705 // Add version, check-again, and update (array count) fields as array of N.
3706 // (Note: 'N' is defined as "unsigned 32-bit integer in network byte order")
3707 int64_t version = _SecRevocationDbGetVersion(dbc, NULL);
3708 N[0] = OSSwapInt32(version & 0xffffffff);
3709 int64_t interval = _SecRevocationDbGetUpdateInterval(dbc, NULL);
3710 if (interval < 0) {
3711 interval = kSecStdUpdateInterval; // if we didn't store it, assume default
3712 }
3713 N[1] = OSSwapInt32(interval & 0xffffffff);
3714 __block int64_t count = 0;
3715 ok = ok && SecDbWithSQL(dbc->dbconn, CFSTR("SELECT count(*) FROM groups"), &localError, ^bool(sqlite3_stmt *selectGroupsCount) {
3716 ok = ok && SecDbStep(dbc->dbconn, selectGroupsCount, &localError, ^void(bool *stop) {
3717 count = sqlite3_column_int64(selectGroupsCount, 0);
3718 *stop = true;
3719 });
3720 return ok;
3721 });
3722 N[2] = OSSwapInt32(count & 0xffffffff);
3723 CC_SHA256_Update(&hash0_ctx, N, sizeof(uint32_t) * 3);
3724
3725 // Sort the update array in order of minimum 'issuer-hash' entry.
3726 // The issuer-hash array is first sorted to determine the lowest issuer-hash,
3727 // and that value is used to sort the update entries.
3728 //
3729 // For our sqlite database, recreating the update array order means fetching
3730 // the groupid column from the issuers table after sorting on issuer_hash,
3731 // using DISTINCT to remove duplicates. Then, for each returned groupid, we
3732 // obtain its list of issuers, its list of serials or hashes, and other data.
3733
3734 ok = ok && SecDbWithSQL(dbc->dbconn, CFSTR("SELECT DISTINCT groupid FROM issuers ORDER BY issuer_hash ASC"), &localError, ^bool(sqlite3_stmt *selectGroups) {
3735 ok = ok && SecDbForEach(dbc->dbconn, selectGroups, &localError, ^bool(int row_index) {
3736 __block int64_t groupId = sqlite3_column_int64(selectGroups, 0);
3737 ok = ok && SecDbWithSQL(dbc->dbconn, CFSTR("SELECT flags,format,data FROM groups WHERE groupid=?"), &localError, ^bool(sqlite3_stmt *selectGroup) {
3738 ok = ok && SecDbBindInt64(selectGroup, 1, groupId, &localError);
3739 ok = ok && SecDbStep(dbc->dbconn, selectGroup, &localError, ^(bool *stop) {
3740 // per-group info is hashed in the following order:
3741 // - issuer_hash array data (sorted)
3742 // - flag bytes, in order listed below
3743 // - format string [serial|sha256|nto1]
3744 // - add array data (sorted), if [serial|sha256]
3745 // - params (if present)
3746 // - xor data (if present)
3747
3748 int64_t flags = sqlite3_column_int64(selectGroup, 0);
3749 bool noCAv2 = (flags & kSecValidInfoNoCAv2Check);
3750
3751 // instead of recreating the issuer_hash array in memory,
3752 // hash its length (item count) followed by the data of each issuer_hash.
3753 ok = ok && SecDbWithSQL(dbc->dbconn, CFSTR("SELECT count(*) FROM issuers WHERE groupid=?"), &localError, ^bool(sqlite3_stmt *selectIssuersCount) {
3754 ok = ok && SecDbBindInt64(selectIssuersCount, 1, groupId, &localError);
3755 ok = ok && SecDbStep(dbc->dbconn, selectIssuersCount, &localError, ^void(bool *stop) {
3756 count = sqlite3_column_int64(selectIssuersCount, 0);
3757 *stop = true;
3758 });
3759 return ok;
3760 });
3761 uint32_t n = OSSwapInt32(count & 0xffffffff);
3762 CC_SHA256_Update(&hash0_ctx, &n, sizeof(uint32_t));
3763 CC_SHA256_Update(&hash1_ctx, &n, sizeof(uint32_t));
3764 if (noCAv2) {
3765 CC_SHA256_Update(&hash2_ctx, &n, sizeof(uint32_t));
3766 }
3767
3768 // process issuer_hash entries for this group
3769 ok = ok && SecDbWithSQL(dbc->dbconn, CFSTR("SELECT issuer_hash FROM issuers WHERE groupid=? ORDER BY issuer_hash ASC"), &localError, ^bool(sqlite3_stmt *selectIssuerHash) {
3770 ok = ok && SecDbBindInt64(selectIssuerHash, 1, groupId, &localError);
3771 ok = ok && SecDbForEach(dbc->dbconn, selectIssuerHash, &localError, ^bool(int row_index) {
3772 uint8_t *p = (uint8_t *)sqlite3_column_blob(selectIssuerHash, 0);
3773 CFDataRef data = NULL;
3774 if (p != NULL) {
3775 CFIndex length = (CFIndex)sqlite3_column_bytes(selectIssuerHash, 0);
3776 data = CFDataCreate(kCFAllocatorDefault, p, length);
3777 }
3778 if (data != NULL) {
3779 hashData(data, &hash0_ctx);
3780 hashData(data, &hash1_ctx);
3781 if (noCAv2) {
3782 hashData(data, &hash2_ctx);
3783 }
3784 CFRelease(data);
3785 } else {
3786 ok = false;
3787 }
3788 return ok;
3789 });
3790 return ok;
3791 });
3792
3793 // process flags, converting to array of unsigned 8-bit values, either 0 or 1:
3794 // [ complete, check-ocsp, known-intermediates-only, no-ca, overridable, require-ct, valid ]
3795 uint8_t C[8]={0,0,0,0,0,0,0,0};
3796 C[0] = (flags & kSecValidInfoComplete) ? 1 : 0;
3797 C[1] = (flags & kSecValidInfoCheckOCSP) ? 1 : 0;
3798 C[2] = (flags & kSecValidInfoKnownOnly) ? 1 : 0;
3799 C[3] = (flags & kSecValidInfoNoCACheck) ? 1 : 0;
3800 C[4] = (flags & kSecValidInfoOverridable) ? 1 : 0;
3801 C[5] = (flags & kSecValidInfoRequireCT) ? 1 : 0;
3802 C[6] = (flags & kSecValidInfoAllowlist) ? 1 : 0;
3803 CC_SHA256_Update(&hash0_ctx, C, sizeof(uint8_t) * 7);
3804
3805 // process format, converting integer to string value [serial|sha256|nto1]
3806 SecValidInfoFormat format = (SecValidInfoFormat)sqlite3_column_int(selectGroup, 1);
3807 switch (format) {
3808 case kSecValidInfoFormatSerial:
3809 hashString(CFSTR("serial"), &hash0_ctx);
3810 break;
3811 case kSecValidInfoFormatSHA256:
3812 hashString(CFSTR("sha256"), &hash0_ctx);
3813 break;
3814 case kSecValidInfoFormatNto1:
3815 hashString(CFSTR("nto1"), &hash0_ctx);
3816 break;
3817 case kSecValidInfoFormatUnknown:
3818 default:
3819 ok = false; // unexpected format values are not allowed
3820 break;
3821 }
3822 // process 'add' array (serial or sha256 format).
3823 // instead of recreating the 'add' array in memory,
3824 // hash its length (item count) followed by the data of each entry.
3825 CFStringRef arrayCountSql = NULL;
3826 if (format == kSecValidInfoFormatSerial) {
3827 arrayCountSql = CFSTR("SELECT count(*) FROM serials WHERE groupid=?");
3828 } else if (format == kSecValidInfoFormatSHA256) {
3829 arrayCountSql = CFSTR("SELECT count(*) FROM hashes WHERE groupid=?");
3830 }
3831 if (arrayCountSql) {
3832 ok = ok && SecDbWithSQL(dbc->dbconn, arrayCountSql, &localError, ^bool(sqlite3_stmt *selectAddCount) {
3833 ok = ok && SecDbBindInt64(selectAddCount, 1, groupId, &localError);
3834 ok = ok && SecDbStep(dbc->dbconn, selectAddCount, &localError, ^void(bool *stop) {
3835 count = sqlite3_column_int64(selectAddCount, 0);
3836 *stop = true;
3837 });
3838 return ok;
3839 });
3840 n = OSSwapInt32(count & 0xffffffff);
3841 CC_SHA256_Update(&hash0_ctx, &n, sizeof(uint32_t));
3842 }
3843 // process data entries for this group
3844 CFStringRef arrayDataSql = NULL;
3845 if (format == kSecValidInfoFormatSerial) {
3846 arrayDataSql = CFSTR("SELECT serial FROM serials WHERE groupid=? ORDER BY serial ASC");
3847 } else if (format == kSecValidInfoFormatSHA256) {
3848 arrayDataSql = CFSTR("SELECT sha256 FROM hashes WHERE groupid=? ORDER by sha256 ASC");
3849 }
3850 if (arrayDataSql) {
3851 ok = ok && SecDbWithSQL(dbc->dbconn, arrayDataSql, &localError, ^bool(sqlite3_stmt *selectAddData) {
3852 ok = ok && SecDbBindInt64(selectAddData, 1, groupId, &localError);
3853 ok = ok && SecDbForEach(dbc->dbconn, selectAddData, &localError, ^bool(int row_index) {
3854 uint8_t *p = (uint8_t *)sqlite3_column_blob(selectAddData, 0);
3855 CFDataRef data = NULL;
3856 if (p != NULL) {
3857 CFIndex length = (CFIndex)sqlite3_column_bytes(selectAddData, 0);
3858 data = CFDataCreate(kCFAllocatorDefault, p, length);
3859 }
3860 if (data != NULL) {
3861 hashData(data, &hash0_ctx);
3862 CFRelease(data);
3863 } else {
3864 ok = false;
3865 }
3866 return ok;
3867 });
3868 return ok;
3869 });
3870 }
3871
3872 // process params and xor data, if format is nto1
3873 if (format == kSecValidInfoFormatNto1) {
3874 uint8_t *p = (uint8_t *)sqlite3_column_blob(selectGroup, 2);
3875 CFDataRef data = NULL;
3876 if (p != NULL) {
3877 CFIndex length = (CFIndex)sqlite3_column_bytes(selectGroup, 2);
3878 data = CFDataCreate(kCFAllocatorDefault, p, length);
3879 }
3880 if (data != NULL) {
3881 // unpack params and xor data
3882 CFDataRef xor = NULL;
3883 CFArrayRef params = NULL;
3884 if (copyFilterComponents(data, &xor, &params)) {
3885 hashArray(params, &hash0_ctx);
3886 hashData(xor, &hash0_ctx);
3887 } else {
3888 ok = false;
3889 }
3890 CFReleaseSafe(xor);
3891 CFReleaseSafe(params);
3892 }
3893 CFReleaseSafe(data);
3894 }
3895
3896 // process date constraints [not-after, not-before]
3897 CFAbsoluteTime notBefore = -3155760000.0; /* default: 1901-01-01 00:00:00-0000 */
3898 CFAbsoluteTime notAfter = 31556908800.0; /* default: 3001-01-01 00:00:00-0000 */
3899 CFDateRef notBeforeDate = NULL;
3900 CFDateRef notAfterDate = NULL;
3901 if (_SecRevocationDbCopyDateConstraints(dbc, groupId, &notBeforeDate, &notAfterDate, &localError)) {
3902 if (notBeforeDate) {
3903 notBefore = CFDateGetAbsoluteTime(notBeforeDate);
3904 CFReleaseNull(notBeforeDate);
3905 }
3906 if (notAfterDate) {
3907 notAfter = CFDateGetAbsoluteTime(notAfterDate);
3908 CFReleaseNull(notAfterDate);
3909 }
3910 }
3911 double nb = htond(notBefore);
3912 double na = htond(notAfter);
3913 CC_SHA256_Update(&hash1_ctx, &na, sizeof(double));
3914 CC_SHA256_Update(&hash1_ctx, &nb, sizeof(double));
3915
3916 *stop = true;
3917 }); // per-group step
3918 return ok;
3919 }); // per-group select
3920 return ok;
3921 }); // for each group in list
3922 return ok;
3923 }); // select full group list
3924
3925 CFMutableArrayRef result = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
3926 if (result) {
3927 uint8_t digest[CC_SHA256_DIGEST_LENGTH];
3928 CFDataRef data = NULL;
3929 CC_SHA256_Final(digest, &hash0_ctx);
3930 if ((data = CFDataCreate(NULL, (const UInt8 *)digest, CC_SHA256_DIGEST_LENGTH)) != NULL) {
3931 CFArrayAppendValue(result, data);
3932 CFReleaseNull(data);
3933 }
3934 CC_SHA256_Final(digest, &hash1_ctx);
3935 if ((data = CFDataCreate(NULL, (const UInt8 *)digest, CC_SHA256_DIGEST_LENGTH)) != NULL) {
3936 CFArrayAppendValue(result, data);
3937 CFReleaseNull(data);
3938 }
3939 CC_SHA256_Final(digest, &hash2_ctx);
3940 if ((data = CFDataCreate(NULL, (const UInt8 *)digest, CC_SHA256_DIGEST_LENGTH)) != NULL) {
3941 CFArrayAppendValue(result, data);
3942 CFReleaseNull(data);
3943 }
3944 }
3945 (void) CFErrorPropagate(localError, error);
3946 return result;
3947 }
3948
3949 static bool SecRevocationDbComputeDigests(SecRevocationDbConnectionRef dbc, CFErrorRef *error) {
3950 secinfo("validupdate", "Started verifying db content");
3951 bool result = true;
3952 CFArrayRef expectedList = _SecRevocationDbCopyHashes(dbc, error);
3953 CFIndex expectedCount = (expectedList) ? CFArrayGetCount(expectedList) : 0;
3954 if (expectedCount < 1) {
3955 secinfo("validupdate", "Unable to read db_hash values");
3956 CFReleaseNull(expectedList);
3957 return result; // %%%% this will happen on first update, when db_hash isn't there
3958 }
3959 CFArrayRef computedList = SecRevocationDbComputeFullContentDigests(dbc, error);
3960 CFIndex computedCount = (computedList) ? CFArrayGetCount(computedList) : 0;
3961 for (CFIndex idx = 0; idx < expectedCount; idx++) {
3962 if (idx >= computedCount) {
3963 continue; // server provided additional hash value that we don't yet compute
3964 }
3965 CFDataRef expectedHash = (CFDataRef)CFArrayGetValueAtIndex(expectedList, idx);
3966 CFDataRef computedHash = (CFDataRef)CFArrayGetValueAtIndex(computedList, idx);
3967 if (!CFEqualSafe(expectedHash, computedHash)) {
3968 result = false;
3969 break;
3970 }
3971 }
3972 if (!result) {
3973 secinfo("validupdate", "Expected: %@", expectedList);
3974 secinfo("validupdate", "Computed: %@", computedList);
3975 }
3976 secinfo("validupdate", "Finished verifying db content; result=%s",
3977 (result) ? "SUCCESS" : "FAIL");
3978 CFReleaseSafe(expectedList);
3979 CFReleaseSafe(computedList);
3980 return result;
3981 }
3982