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