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