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