2 * Copyright (c) 2016-2018 Apple Inc. All Rights Reserved.
4 * @APPLE_LICENSE_HEADER_START@
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
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.
21 * @APPLE_LICENSE_HEADER_END@
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>
47 #include <dispatch/dispatch.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>
62 #include <malloc/malloc.h>
63 #include <xpc/activity.h>
64 #include <xpc/private.h>
65 #include <os/transaction_private.h>
66 #include <os/variant_private.h>
68 #include <CFNetwork/CFHTTPMessage.h>
69 #include <CoreFoundation/CFURL.h>
70 #include <CoreFoundation/CFUtilities.h>
72 static CFStringRef kValidUpdateProdServer
= CFSTR("valid.apple.com");
73 static CFStringRef kValidUpdateCarryServer
= CFSTR("valid.apple.com/carry");
75 static CFStringRef kSecPrefsDomain
= CFSTR("com.apple.security");
76 static CFStringRef kUpdateServerKey
= CFSTR("ValidUpdateServer");
77 static CFStringRef kUpdateEnabledKey
= CFSTR("ValidUpdateEnabled");
78 static CFStringRef kUpdateIntervalKey
= CFSTR("ValidUpdateInterval");
79 static CFStringRef kBoolTrueKey
= CFSTR("1");
80 static CFStringRef kBoolFalseKey
= CFSTR("0");
82 /* constant length of boolean string keys */
83 #define BOOL_STRING_KEY_LENGTH 1
85 typedef CF_OPTIONS(CFOptionFlags
, SecValidInfoFlags
) {
86 kSecValidInfoComplete
= 1u << 0,
87 kSecValidInfoCheckOCSP
= 1u << 1,
88 kSecValidInfoKnownOnly
= 1u << 2,
89 kSecValidInfoRequireCT
= 1u << 3,
90 kSecValidInfoAllowlist
= 1u << 4,
91 kSecValidInfoNoCACheck
= 1u << 5,
92 kSecValidInfoOverridable
= 1u << 6,
93 kSecValidInfoDateConstraints
= 1u << 7,
94 kSecValidInfoNameConstraints
= 1u << 8,
95 kSecValidInfoPolicyConstraints
= 1u << 9,
98 /* minimum update interval */
99 #define kSecMinUpdateInterval (60.0 * 5)
101 /* standard update interval */
102 #define kSecStdUpdateInterval (60.0 * 60)
104 /* maximum allowed interval */
105 #define kSecMaxUpdateInterval (60.0 * 60 * 24 * 7)
107 #define kSecRevocationBasePath "/Library/Keychains/crls"
108 #define kSecRevocationCurUpdateFile "update-current"
109 #define kSecRevocationDbFileName "valid.sqlite3"
110 #define kSecRevocationDbReplaceFile ".valid_replace"
112 #define isDbOwner SecOTAPKIIsSystemTrustd
114 /* database schema version
116 v2 = fix for group entry transitions
117 v3 = handle optional entries in update dictionaries
118 v4 = add db_format and db_source entries
119 v5 = add date constraints table, with updated group flags
121 Note: kSecRevocationDbMinSchemaVersion is the lowest version whose
122 results can be used. This allows revocation results to be obtained
123 from an existing db before the next update interval occurs, at which
124 time we'll update to the current version (kSecRevocationDbSchemaVersion).
126 #define kSecRevocationDbSchemaVersion 5 /* current version we support */
127 #define kSecRevocationDbMinSchemaVersion 5 /* minimum version we can use */
129 /* update file format
132 kSecValidUpdateFormatG1
= 1, /* initial version */
133 kSecValidUpdateFormatG2
= 2, /* signed content, single plist */
134 kSecValidUpdateFormatG3
= 3 /* signed content, multiple plists */
137 #define kSecRevocationDbUpdateFormat 3 /* current version we support */
138 #define kSecRevocationDbMinUpdateFormat 2 /* minimum version we can use */
140 bool SecRevocationDbVerifyUpdate(void *update
, CFIndex length
);
141 CFIndex
SecRevocationDbIngestUpdate(CFDictionaryRef update
, CFIndex chunkVersion
);
142 void SecRevocationDbApplyUpdate(CFDictionaryRef update
, CFIndex version
);
143 CFAbsoluteTime
SecRevocationDbComputeNextUpdateTime(CFIndex updateInterval
);
144 bool SecRevocationDbSetVersion(CFIndex version
);
145 bool SecRevocationDbSetSchemaVersion(CFIndex dbversion
);
146 bool SecRevocationDbUpdateSchema(void);
147 CFIndex
SecRevocationDbGetUpdateFormat(void);
148 void SecRevocationDbSetUpdateFormat(CFIndex dbformat
);
149 void SecRevocationDbSetUpdateSource(CFStringRef source
);
150 CFStringRef
SecRevocationDbCopyUpdateSource(void);
151 void SecRevocationDbSetNextUpdateTime(CFAbsoluteTime nextUpdate
);
152 CFAbsoluteTime
SecRevocationDbGetNextUpdateTime(void);
153 dispatch_queue_t
SecRevocationDbGetUpdateQueue(void);
154 void SecRevocationDbRemoveAllEntries(void);
155 void SecRevocationDbReleaseAllConnections(void);
158 static CFDataRef
copyInflatedData(CFDataRef data
) {
163 memset(&zs
, 0, sizeof(zs
));
164 /* 32 is a magic value which enables automatic header detection
165 of gzip or zlib compressed data. */
166 if (inflateInit2(&zs
, 32+MAX_WBITS
) != Z_OK
) {
169 zs
.next_in
= (UInt8
*)(CFDataGetBytePtr(data
));
170 zs
.avail_in
= (uInt
)CFDataGetLength(data
);
172 CFMutableDataRef outData
= CFDataCreateMutable(NULL
, 0);
176 CFIndex buf_sz
= malloc_good_size(zs
.avail_in
? zs
.avail_in
: 1024 * 4);
177 unsigned char *buf
= malloc(buf_sz
);
180 zs
.next_out
= (Bytef
*)buf
;
181 zs
.avail_out
= (uInt
)buf_sz
;
182 rc
= inflate(&zs
, 0);
183 CFIndex outLen
= CFDataGetLength(outData
);
184 if (outLen
< (CFIndex
)zs
.total_out
) {
185 CFDataAppendBytes(outData
, (const UInt8
*)buf
, (CFIndex
)zs
.total_out
- outLen
);
187 } while (rc
== Z_OK
);
194 if (rc
!= Z_STREAM_END
) {
195 CFReleaseSafe(outData
);
198 return (CFDataRef
)outData
;
201 static CFDataRef
copyInflatedDataToFile(CFDataRef data
, char *fileName
) {
206 memset(&zs
, 0, sizeof(zs
));
207 /* 32 is a magic value which enables automatic header detection
208 of gzip or zlib compressed data. */
209 if (inflateInit2(&zs
, 32+MAX_WBITS
) != Z_OK
) {
212 zs
.next_in
= (UInt8
*)(CFDataGetBytePtr(data
));
213 zs
.avail_in
= (uInt
)CFDataGetLength(data
);
215 (void)remove(fileName
); /* We need an empty file to start */
218 fd
= open(fileName
, O_RDWR
| O_CREAT
| O_TRUNC
, 0644);
219 if (fd
< 0 || (off
= lseek(fd
, 0, SEEK_SET
)) < 0) {
220 secerror("unable to open %s (errno %d)", fileName
, errno
);
227 CFIndex buf_sz
= malloc_good_size(zs
.avail_in
? zs
.avail_in
: 1024 * 4);
228 unsigned char *buf
= malloc(buf_sz
);
231 zs
.next_out
= (Bytef
*)buf
;
232 zs
.avail_out
= (uInt
)buf_sz
;
233 rc
= inflate(&zs
, 0);
234 if (off
< (int64_t)zs
.total_out
) {
235 off
= write(fd
, buf
, (size_t)zs
.total_out
- (size_t)off
);
237 } while (rc
== Z_OK
);
245 if (rc
!= Z_STREAM_END
) {
246 (void)remove(fileName
);
250 /* Now return an mmapped version of that data */
251 CFDataRef outData
= NULL
;
252 if ((rc
= readValidFile(fileName
, &outData
)) != 0) {
253 secerror("unable to read and map %s (errno %d)", fileName
, rc
);
254 CFReleaseNull(outData
);
259 static CFDataRef
copyDeflatedData(CFDataRef data
) {
264 memset(&zs
, 0, sizeof(zs
));
265 if (deflateInit(&zs
, Z_BEST_COMPRESSION
) != Z_OK
) {
268 zs
.next_in
= (UInt8
*)(CFDataGetBytePtr(data
));
269 zs
.avail_in
= (uInt
)CFDataGetLength(data
);
271 CFMutableDataRef outData
= CFDataCreateMutable(NULL
, 0);
275 CFIndex buf_sz
= malloc_good_size(zs
.avail_in
? zs
.avail_in
: 1024 * 4);
276 unsigned char *buf
= malloc(buf_sz
);
277 int rc
= Z_BUF_ERROR
;
279 zs
.next_out
= (Bytef
*)buf
;
280 zs
.avail_out
= (uInt
)buf_sz
;
281 rc
= deflate(&zs
, Z_FINISH
);
283 if (rc
== Z_OK
|| rc
== Z_STREAM_END
) {
284 CFIndex buf_used
= buf_sz
- zs
.avail_out
;
285 CFDataAppendBytes(outData
, (const UInt8
*)buf
, buf_used
);
287 else if (rc
== Z_BUF_ERROR
) {
289 buf_sz
= malloc_good_size(buf_sz
* 2);
290 buf
= malloc(buf_sz
);
292 rc
= Z_OK
; /* try again with larger buffer */
295 } while (rc
== Z_OK
&& zs
.avail_in
);
302 if (rc
!= Z_STREAM_END
) {
303 CFReleaseSafe(outData
);
306 return (CFDataRef
)outData
;
309 /* Read file opens the file, mmaps it and then closes the file. */
310 int readValidFile(const char *fileName
,
311 CFDataRef
*bytes
) { // mmapped and returned -- must be munmapped!
313 const uint8_t *buf
= NULL
;
318 fd
= open(fileName
, O_RDONLY
);
319 if (fd
< 0) { return errno
; }
320 rtn
= fstat(fd
, &sb
);
321 if (rtn
) { goto errOut
; }
322 if (sb
.st_size
> (off_t
) ((UINT32_MAX
>> 1)-1)) {
326 size
= (size_t)sb
.st_size
;
328 buf
= mmap(NULL
, size
, PROT_READ
, MAP_PRIVATE
, fd
, 0);
329 if (!buf
|| buf
== MAP_FAILED
) {
331 secerror("unable to map %s (errno %d)", fileName
, rtn
);
335 *bytes
= CFDataCreateWithBytesNoCopy(NULL
, buf
, size
, kCFAllocatorNull
);
340 CFReleaseNull(*bytes
);
342 int unmap_err
= munmap((void *)buf
, size
);
343 if (unmap_err
!= 0) {
344 secerror("unable to unmap %ld bytes at %p (error %d)", (long)size
, buf
, rtn
);
351 static void unmapData(CFDataRef CF_CONSUMED data
) {
353 int rtn
= munmap((void *)CFDataGetBytePtr(data
), CFDataGetLength(data
));
355 secerror("unable to unmap %ld bytes at %p (error %d)", CFDataGetLength(data
), CFDataGetBytePtr(data
), rtn
);
362 static bool removeFileWithSuffix(const char *basepath
, const char *suffix
) {
365 asprintf(&path
, "%s%s", basepath
, suffix
);
367 if (remove(path
) == -1) {
369 if (error
== ENOENT
) {
370 result
= true; // not an error if the file did not exist
372 secnotice("validupdate", "remove (%s): %s", path
, strerror(error
));
383 // MARK: SecValidUpdate
385 /* ======================================================================
387 ======================================================================*/
389 CFAbsoluteTime gUpdateStarted
= 0.0;
390 CFAbsoluteTime gNextUpdate
= 0.0;
391 static CFIndex gUpdateInterval
= 0;
392 static CFIndex gLastVersion
= 0;
395 1. The length of the signed data, as a 4-byte integer in network byte order.
396 2. The signed data, which consists of:
397 a. A 4-byte integer in network byte order, the count of plists to follow; and then for each plist:
398 i. A 4-byte integer, the length of each plist
399 ii. A plist, in binary form
400 b. There may be other data after the plists in the signed data, described by a future version of this specification.
401 3. The length of the following CMS blob, as a 4-byte integer in network byte order.
402 4. A detached CMS signature of the signed data described above.
403 5. There may be additional data after the CMS blob, described by a future version of this specification.
405 Note: the difference between g2 and g3 format is the addition of the 4-byte count in (2a).
407 static bool SecValidUpdateProcessData(CFIndex format
, CFDataRef updateData
) {
408 if (!updateData
|| format
< 2) {
413 CFIndex interval
= 0;
414 const UInt8
* p
= CFDataGetBytePtr(updateData
);
415 size_t bytesRemaining
= (p
) ? (size_t)CFDataGetLength(updateData
) : 0;
416 /* make sure there is enough data to contain length and count */
417 if (bytesRemaining
< ((CFIndex
)sizeof(uint32_t) * 2)) {
418 secinfo("validupdate", "Skipping property list creation (length %ld is too short)", (long)bytesRemaining
);
421 /* get length of signed data */
422 uint32_t dataLength
= OSSwapInt32(*((uint32_t *)p
));
423 bytesRemaining
-= sizeof(uint32_t);
424 p
+= sizeof(uint32_t);
426 /* get plist count (G3 format and later) */
427 uint32_t plistCount
= 1;
428 uint32_t plistTotal
= 1;
429 if (format
> kSecValidUpdateFormatG2
) {
430 plistCount
= OSSwapInt32(*((uint32_t *)p
));
431 plistTotal
= plistCount
;
432 bytesRemaining
-= sizeof(uint32_t);
433 p
+= sizeof(uint32_t);
435 if (dataLength
> bytesRemaining
) {
436 secinfo("validupdate", "Skipping property list creation (dataLength=%ld, bytesRemaining=%ld)",
437 (long)dataLength
, (long)bytesRemaining
);
441 /* process each chunked plist */
442 uint32_t plistProcessed
= 0;
443 while (plistCount
> 0 && bytesRemaining
> 0) {
444 CFPropertyListRef propertyList
= NULL
;
445 uint32_t plistLength
= dataLength
;
446 if (format
> kSecValidUpdateFormatG2
) {
447 plistLength
= OSSwapInt32(*((uint32_t *)p
));
448 bytesRemaining
-= sizeof(uint32_t);
449 p
+= sizeof(uint32_t);
454 /* We're about to use a lot of memory for the plist -- go active so we don't get jetsammed */
455 os_transaction_t transaction
;
456 transaction
= os_transaction_create("com.apple.trustd.valid");
458 if (plistLength
<= bytesRemaining
) {
459 CFDataRef data
= CFDataCreateWithBytesNoCopy(NULL
, p
, plistLength
, kCFAllocatorNull
);
460 propertyList
= CFPropertyListCreateWithData(NULL
, data
, kCFPropertyListImmutable
, NULL
, NULL
);
463 if (isDictionary(propertyList
)) {
464 secdebug("validupdate", "Ingesting plist chunk %u of %u, length: %u",
465 plistProcessed
, plistTotal
, plistLength
);
466 CFIndex curVersion
= SecRevocationDbIngestUpdate((CFDictionaryRef
)propertyList
, version
);
467 if (plistProcessed
== 1) {
468 version
= curVersion
;
469 // get server-provided interval
470 CFTypeRef value
= (CFNumberRef
)CFDictionaryGetValue((CFDictionaryRef
)propertyList
,
471 CFSTR("check-again"));
472 if (isNumber(value
)) {
473 CFNumberGetValue((CFNumberRef
)value
, kCFNumberCFIndexType
, &interval
);
476 if (curVersion
< 0) {
477 plistCount
= 0; // we already had this version; skip remaining plists
481 secinfo("validupdate", "Failed to deserialize update chunk %u of %u",
482 plistProcessed
, plistTotal
);
483 if (plistProcessed
== 1) {
484 gNextUpdate
= SecRevocationDbComputeNextUpdateTime(0);
487 /* All finished with this property list */
488 CFReleaseSafe(propertyList
);
489 os_release(transaction
);
491 bytesRemaining
-= plistLength
;
496 secdebug("validupdate", "Update received: v%ld", (long)version
);
497 gLastVersion
= version
;
498 gNextUpdate
= SecRevocationDbComputeNextUpdateTime(interval
);
499 secdebug("validupdate", "Next update time: %f", gNextUpdate
);
503 // remember next update time in case of restart
504 SecRevocationDbSetNextUpdateTime(gNextUpdate
);
509 void SecValidUpdateVerifyAndIngest(CFDataRef updateData
, CFStringRef updateServer
, bool fullUpdate
) {
511 secnotice("validupdate", "invalid update data");
514 /* Verify CMS signature on signed data */
515 if (SecRevocationDbVerifyUpdate((void *)CFDataGetBytePtr(updateData
), CFDataGetLength(updateData
))) {
516 CFStringRef dbSource
= SecRevocationDbCopyUpdateSource();
517 if (dbSource
&& updateServer
&& (kCFCompareEqualTo
!= CFStringCompare(dbSource
, updateServer
,
518 kCFCompareCaseInsensitive
))) {
519 secnotice("validupdate", "switching db source from \"%@\" to \"%@\"", dbSource
, updateServer
);
521 CFReleaseNull(dbSource
);
523 /* Must completely replace existing database contents */
524 SecRevocationDbRemoveAllEntries();
525 SecRevocationDbSetUpdateSource(updateServer
);
527 bool result
= SecValidUpdateProcessData(kSecValidUpdateFormatG3
, updateData
);
529 // Try g2 update format as a fallback if we failed to read g3
530 result
= SecValidUpdateProcessData(kSecValidUpdateFormatG2
, updateData
);
533 secerror("failed to process valid update");
534 TrustdHealthAnalyticsLogErrorCode(TAEventValidUpdate
, TAFatalError
, errSecDecode
);
536 TrustdHealthAnalyticsLogSuccess(TAEventValidUpdate
);
539 secerror("failed to verify valid update");
540 TrustdHealthAnalyticsLogErrorCode(TAEventValidUpdate
, TAFatalError
, errSecVerifyFailed
);
544 static bool SecValidDatabaseFromCompressed(CFDataRef CF_CONSUMED data
) {
545 if (!data
) { return false; }
547 secdebug("validupdate", "read %ld bytes from file", (long)CFDataGetLength(data
));
549 /* We're about to use a lot of memory for the uncompressed update -- go active */
550 os_transaction_t transaction
;
551 transaction
= os_transaction_create("com.apple.trustd.valid");
553 /* Expand the database */
554 __block CFDataRef inflatedData
= NULL
;
555 WithPathInRevocationInfoDirectory(CFSTR(kSecRevocationDbFileName
), ^(const char *dbPath
) {
556 inflatedData
= copyInflatedDataToFile(data
, (char *)dbPath
);
557 secdebug("validupdate", "data expanded: %ld bytes", (long)CFDataGetLength(inflatedData
));
560 os_release(transaction
);
563 unmapData(inflatedData
);
568 static bool SecValidUpdateSatisfiedLocally(CFStringRef server
, CFIndex version
, bool safeToReplace
) {
569 __block
bool result
= false;
570 CFDataRef data
= NULL
;
571 SecOTAPKIRef otapkiRef
= NULL
;
572 bool relaunching
= false;
574 static int sNumLocalUpdates
= 0;
576 // if we've replaced the database with a local asset twice in a row,
577 // something is wrong with it. Get this update from the server.
578 if (sNumLocalUpdates
> 1) {
579 secdebug("validupdate", "%d consecutive db resets, ignoring local asset", sNumLocalUpdates
);
583 // if a non-production server is specified, we will not be able to use a
584 // local production asset since its update sequence will be different.
585 if (kCFCompareEqualTo
!= CFStringCompare(server
, kValidUpdateProdServer
,
586 kCFCompareCaseInsensitive
)) {
587 secdebug("validupdate", "non-production server specified, ignoring local asset");
591 // check static database asset(s)
592 otapkiRef
= SecOTAPKICopyCurrentOTAPKIRef();
596 CFIndex assetVersion
= SecOTAPKIGetValidSnapshotVersion(otapkiRef
);
597 CFIndex assetFormat
= SecOTAPKIGetValidSnapshotFormat(otapkiRef
);
598 // version <= 0 means the database is invalid or empty.
599 // version > 0 means we have some version, but we need to see if a
600 // newer version is available as a local asset.
601 if (assetVersion
<= version
|| assetFormat
< kSecValidUpdateFormatG3
) {
602 // asset is not newer than ours, or its version is unknown
606 // replace database only if safe to do so (i.e. called at startup)
607 if (!safeToReplace
) {
608 // write semaphore file that we will pick up when we next launch
609 char *semPathBuf
= NULL
;
610 asprintf(&semPathBuf
, "%s/%s", kSecRevocationBasePath
, kSecRevocationDbReplaceFile
);
613 int fd
= open(semPathBuf
, O_WRONLY
| O_CREAT
, DEFFILEMODE
);
614 if (fd
== -1 || fstat(fd
, &sb
)) {
615 secnotice("validupdate", "unable to write %s", semPathBuf
);
625 // exit as gracefully as possible so we can replace the database
626 secnotice("validupdate", "process exiting to replace db file");
627 dispatch_after(dispatch_time(DISPATCH_TIME_NOW
, 3ull*NSEC_PER_SEC
), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT
, 0), ^{
628 xpc_transaction_exit_clean();
634 // try to copy uncompressed database asset, if available
635 const char *validDbPathBuf
= SecOTAPKIGetValidDatabaseSnapshot(otapkiRef
);
636 if (validDbPathBuf
) {
637 WithPathInRevocationInfoDirectory(CFSTR(kSecRevocationDbFileName
), ^(const char *path
) {
638 secdebug("validupdate", "will copy data from \"%s\"", validDbPathBuf
);
639 copyfile_state_t state
= copyfile_state_alloc();
640 int retval
= copyfile(validDbPathBuf
, path
, state
, COPYFILE_DATA
);
641 copyfile_state_free(state
);
643 secnotice("validupdate", "copyfile error %d", retval
);
653 // see if compressed database asset is available
654 if (validDbPathBuf
) {
655 char *validDbCmpPathBuf
= NULL
;
656 asprintf(&validDbCmpPathBuf
, "%s%s", validDbPathBuf
, ".gz");
657 if (validDbCmpPathBuf
) {
658 secdebug("validupdate", "will read data from \"%s\"", validDbCmpPathBuf
);
659 if ((rtn
= readValidFile(validDbCmpPathBuf
, &data
)) != 0) {
662 secnotice("validupdate", "readValidFile error %d", rtn
);
664 free(validDbCmpPathBuf
);
667 result
= SecValidDatabaseFromCompressed(data
);
670 CFReleaseNull(otapkiRef
);
673 gLastVersion
= SecRevocationDbGetVersion();
674 SecRevocationDbSetUpdateSource(server
);
675 SecRevocationDbUpdateSchema();
677 secdebug("validupdate", "local update to g%ld/v%ld complete at %f",
678 (long)SecRevocationDbGetUpdateFormat(), (long)gLastVersion
,
679 (double)CFAbsoluteTimeGetCurrent());
681 sNumLocalUpdates
= 0; // reset counter
684 // request is locally satisfied; don't schedule a network update
690 static bool SecValidUpdateSchedule(bool updateEnabled
, CFStringRef server
, CFIndex version
) {
691 /* Check if we have a later version available locally */
692 if (SecValidUpdateSatisfiedLocally(server
, version
, false)) {
696 /* If update not permitted return */
697 if (!updateEnabled
) {
701 #if !TARGET_OS_BRIDGE
702 /* Schedule as a maintenance task */
703 secdebug("validupdate", "will fetch v%lu from \"%@\"", (unsigned long)version
, server
);
704 return SecValidUpdateRequest(SecRevocationDbGetUpdateQueue(), server
, version
);
710 static CFStringRef
SecRevocationDbGetDefaultServer(void) {
712 return kValidUpdateCarryServer
;
713 #else // !RC_SEED_BUILD
714 CFStringRef defaultServer
= kValidUpdateProdServer
;
715 if (os_variant_has_internal_diagnostics("com.apple.security")) {
716 defaultServer
= kValidUpdateCarryServer
;
718 return defaultServer
;
719 #endif // !RC_SEED_BUILD
722 void SecRevocationDbInitialize() {
723 if (!isDbOwner()) { return; }
724 __block
bool initializeDb
= false;
726 /* create base path if it doesn't exist */
727 (void)mkpath_np(kSecRevocationBasePath
, 0755);
729 /* check semaphore file */
730 WithPathInRevocationInfoDirectory(CFSTR(kSecRevocationDbReplaceFile
), ^(const char *path
) {
732 if (stat(path
, &sb
) == 0) {
733 initializeDb
= true; /* file was found, so we will replace the database */
734 if (remove(path
) == -1) {
736 secnotice("validupdate", "remove (%s): %s", path
, strerror(error
));
742 WithPathInRevocationInfoDirectory(CFSTR(kSecRevocationDbFileName
), ^(const char *path
) {
744 /* remove old database file(s) */
745 (void)removeFileWithSuffix(path
, "");
746 (void)removeFileWithSuffix(path
, "-journal");
747 (void)removeFileWithSuffix(path
, "-shm");
748 (void)removeFileWithSuffix(path
, "-wal");
752 if (stat(path
, &sb
) == -1) {
753 initializeDb
= true; /* file not found, so we will create the database */
759 return; /* database exists and doesn't need replacing */
762 /* initialize database from local asset */
763 CFTypeRef value
= (CFStringRef
)CFPreferencesCopyValue(kUpdateServerKey
, kSecPrefsDomain
, kCFPreferencesAnyUser
, kCFPreferencesCurrentHost
);
764 CFStringRef server
= (isString(value
)) ? (CFStringRef
)value
: (CFStringRef
)SecRevocationDbGetDefaultServer();
766 secnotice("validupdate", "initializing database");
767 if (!SecValidUpdateSatisfiedLocally(server
, version
, true)) {
768 #if !TARGET_OS_BRIDGE
769 /* Schedule full update as a maintenance task */
770 (void)SecValidUpdateRequest(SecRevocationDbGetUpdateQueue(), server
, version
);
773 CFReleaseSafe(value
);
778 // MARK: SecValidInfoRef
780 /* ======================================================================
782 ======================================================================
785 static SecValidInfoRef
SecValidInfoCreate(SecValidInfoFormat format
,
789 CFDataRef issuerHash
,
790 CFDataRef anchorHash
,
791 CFDateRef notBeforeDate
,
792 CFDateRef notAfterDate
,
793 CFDataRef nameConstraints
,
794 CFDataRef policyConstraints
) {
795 SecValidInfoRef validInfo
;
796 validInfo
= (SecValidInfoRef
)calloc(1, sizeof(struct __SecValidInfo
));
797 if (!validInfo
) { return NULL
; }
799 CFRetainSafe(certHash
);
800 CFRetainSafe(issuerHash
);
801 CFRetainSafe(anchorHash
);
802 CFRetainSafe(notBeforeDate
);
803 CFRetainSafe(notAfterDate
);
804 CFRetainSafe(nameConstraints
);
805 CFRetainSafe(policyConstraints
);
807 validInfo
->format
= format
;
808 validInfo
->certHash
= certHash
;
809 validInfo
->issuerHash
= issuerHash
;
810 validInfo
->anchorHash
= anchorHash
;
811 validInfo
->isOnList
= isOnList
;
812 validInfo
->valid
= (flags
& kSecValidInfoAllowlist
);
813 validInfo
->complete
= (flags
& kSecValidInfoComplete
);
814 validInfo
->checkOCSP
= (flags
& kSecValidInfoCheckOCSP
);
815 validInfo
->knownOnly
= (flags
& kSecValidInfoKnownOnly
);
816 validInfo
->requireCT
= (flags
& kSecValidInfoRequireCT
);
817 validInfo
->noCACheck
= (flags
& kSecValidInfoNoCACheck
);
818 validInfo
->overridable
= (flags
& kSecValidInfoOverridable
);
819 validInfo
->hasDateConstraints
= (flags
& kSecValidInfoDateConstraints
);
820 validInfo
->hasNameConstraints
= (flags
& kSecValidInfoNameConstraints
);
821 validInfo
->hasPolicyConstraints
= (flags
& kSecValidInfoPolicyConstraints
);
822 validInfo
->notBeforeDate
= notBeforeDate
;
823 validInfo
->notAfterDate
= notAfterDate
;
824 validInfo
->nameConstraints
= nameConstraints
;
825 validInfo
->policyConstraints
= policyConstraints
;
830 void SecValidInfoRelease(SecValidInfoRef validInfo
) {
832 CFReleaseNull(validInfo
->certHash
);
833 CFReleaseNull(validInfo
->issuerHash
);
834 CFReleaseNull(validInfo
->anchorHash
);
835 CFReleaseNull(validInfo
->notBeforeDate
);
836 CFReleaseNull(validInfo
->notAfterDate
);
837 CFReleaseNull(validInfo
->nameConstraints
);
838 CFReleaseNull(validInfo
->policyConstraints
);
843 void SecValidInfoSetAnchor(SecValidInfoRef validInfo
, SecCertificateRef anchor
) {
847 CFDataRef anchorHash
= NULL
;
849 anchorHash
= SecCertificateCopySHA256Digest(anchor
);
851 /* clear no-ca flag for anchors where we want OCSP checked [32523118] */
852 if (SecIsAppleTrustAnchor(anchor
, 0)) {
853 validInfo
->noCACheck
= false;
856 CFReleaseNull(validInfo
->anchorHash
);
857 validInfo
->anchorHash
= anchorHash
;
862 // MARK: SecRevocationDb
864 /* ======================================================================
866 ======================================================================
869 /* SecRevocationDbCheckNextUpdate returns true if we dispatched an
870 update request, otherwise false.
872 static bool _SecRevocationDbCheckNextUpdate(void) {
873 // are we the db owner instance?
877 CFTypeRef value
= NULL
;
879 // is it time to check?
880 CFAbsoluteTime now
= CFAbsoluteTimeGetCurrent();
881 CFAbsoluteTime minNextUpdate
= now
+ gUpdateInterval
;
882 gUpdateStarted
= now
;
884 if (0 == gNextUpdate
) {
885 // first time we're called, check if we have a saved nextUpdate value
886 gNextUpdate
= SecRevocationDbGetNextUpdateTime();
888 if (gNextUpdate
< minNextUpdate
) {
889 gNextUpdate
= minNextUpdate
;
891 // allow pref to override update interval, if it exists
892 CFIndex interval
= -1;
893 value
= (CFNumberRef
)CFPreferencesCopyValue(kUpdateIntervalKey
, kSecPrefsDomain
, kCFPreferencesAnyUser
, kCFPreferencesCurrentHost
);
894 if (isNumber(value
)) {
895 if (CFNumberGetValue((CFNumberRef
)value
, kCFNumberCFIndexType
, &interval
)) {
896 if (interval
< kSecMinUpdateInterval
) {
897 interval
= kSecMinUpdateInterval
;
898 } else if (interval
> kSecMaxUpdateInterval
) {
899 interval
= kSecMaxUpdateInterval
;
903 CFReleaseNull(value
);
904 gUpdateInterval
= kSecStdUpdateInterval
;
906 gUpdateInterval
= interval
;
908 // pin next update time to the preferred update interval
909 if (gNextUpdate
> (gUpdateStarted
+ gUpdateInterval
)) {
910 gNextUpdate
= gUpdateStarted
+ gUpdateInterval
;
912 secdebug("validupdate", "next update at %f (in %f seconds)",
913 (double)gUpdateStarted
, (double)gNextUpdate
-gUpdateStarted
);
915 if (gNextUpdate
> now
) {
919 secnotice("validupdate", "starting update");
921 // set minimum next update time here in case we can't get an update
922 gNextUpdate
= minNextUpdate
;
924 // determine which server to query
926 value
= (CFStringRef
)CFPreferencesCopyValue(kUpdateServerKey
, kSecPrefsDomain
, kCFPreferencesAnyUser
, kCFPreferencesCurrentHost
);
927 if (isString(value
)) {
928 server
= (CFStringRef
) CFRetain(value
);
930 server
= (CFStringRef
) CFRetain(SecRevocationDbGetDefaultServer());
932 CFReleaseNull(value
);
934 // determine version of our current database
935 CFIndex version
= SecRevocationDbGetVersion();
936 secdebug("validupdate", "got version %ld from db", (long)version
);
938 if (gLastVersion
> 0) {
939 secdebug("validupdate", "error getting version; using last good version: %ld", (long)gLastVersion
);
941 version
= gLastVersion
;
944 // determine source of our current database
945 // (if this ever changes, we will need to reload the db)
946 CFStringRef db_source
= SecRevocationDbCopyUpdateSource();
948 db_source
= (CFStringRef
) CFRetain(kValidUpdateProdServer
);
951 // determine whether we need to recreate the database
952 CFIndex db_version
= SecRevocationDbGetSchemaVersion();
953 CFIndex db_format
= SecRevocationDbGetUpdateFormat();
954 if (db_version
< kSecRevocationDbSchemaVersion
||
955 db_format
< kSecRevocationDbUpdateFormat
||
956 kCFCompareEqualTo
!= CFStringCompare(server
, db_source
, kCFCompareCaseInsensitive
)) {
957 // we need to fully rebuild the db contents, so we set our version to 0.
958 version
= gLastVersion
= 0;
961 // determine whether update fetching is enabled
962 #if (__MAC_OS_X_VERSION_MIN_REQUIRED >= 101300 || __IPHONE_OS_VERSION_MIN_REQUIRED >= 110000)
963 bool updateEnabled
= true; // macOS 10.13 or iOS 11.0
965 bool updateEnabled
= false;
967 value
= (CFBooleanRef
)CFPreferencesCopyValue(kUpdateEnabledKey
, kSecPrefsDomain
, kCFPreferencesAnyUser
, kCFPreferencesCurrentHost
);
968 if (isBoolean(value
)) {
969 updateEnabled
= CFBooleanGetValue((CFBooleanRef
)value
);
971 CFReleaseNull(value
);
973 // Schedule maintenance work
974 bool result
= SecValidUpdateSchedule(updateEnabled
, server
, version
);
975 CFReleaseNull(server
);
976 CFReleaseNull(db_source
);
980 void SecRevocationDbCheckNextUpdate(void) {
981 static dispatch_once_t once
;
982 static sec_action_t action
;
984 dispatch_once(&once
, ^{
985 dispatch_queue_t update_queue
= SecRevocationDbGetUpdateQueue();
986 action
= sec_action_create_with_queue(update_queue
, "update_check", kSecMinUpdateInterval
);
987 sec_action_set_handler(action
, ^{
988 (void)_SecRevocationDbCheckNextUpdate();
991 sec_action_perform(action
);
994 /* This function verifies an update, in this format:
995 1) unsigned 32-bit network-byte-order length of binary plist
997 3) unsigned 32-bit network-byte-order length of CMS message
998 4) CMS message (containing certificates and signature over binary plist)
1000 The length argument is the total size of the packed update data.
1002 bool SecRevocationDbVerifyUpdate(void *update
, CFIndex length
) {
1003 if (!update
|| length
<= (CFIndex
)sizeof(uint32_t)) {
1006 uint32_t plistLength
= OSSwapInt32(*((uint32_t *)update
));
1007 if ((plistLength
+ (CFIndex
)(sizeof(uint32_t)*2)) > (uint64_t) length
) {
1008 secdebug("validupdate", "ERROR: reported plist length (%lu)+%lu exceeds total length (%lu)\n",
1009 (unsigned long)plistLength
, (unsigned long)sizeof(uint32_t)*2, (unsigned long)length
);
1012 uint8_t *plistData
= (uint8_t *)update
+ sizeof(uint32_t);
1013 uint8_t *sigData
= (uint8_t *)plistData
+ plistLength
;
1014 uint32_t sigLength
= OSSwapInt32(*((uint32_t *)sigData
));
1015 sigData
+= sizeof(uint32_t);
1016 if ((plistLength
+ sigLength
+ (CFIndex
)(sizeof(uint32_t) * 2)) != (uint64_t) length
) {
1017 secdebug("validupdate", "ERROR: reported lengths do not add up to total length\n");
1021 OSStatus status
= 0;
1022 CMSSignerStatus signerStatus
;
1023 CMSDecoderRef cms
= NULL
;
1024 SecPolicyRef policy
= NULL
;
1025 SecTrustRef trust
= NULL
;
1026 CFDataRef content
= NULL
;
1028 if ((content
= CFDataCreateWithBytesNoCopy(kCFAllocatorDefault
,
1029 (const UInt8
*)plistData
, (CFIndex
)plistLength
, kCFAllocatorNull
)) == NULL
) {
1030 secdebug("validupdate", "CFDataCreateWithBytesNoCopy failed (%ld bytes)\n", (long)plistLength
);
1034 if ((status
= CMSDecoderCreate(&cms
)) != errSecSuccess
) {
1035 secdebug("validupdate", "CMSDecoderCreate failed with error %d\n", (int)status
);
1038 if ((status
= CMSDecoderUpdateMessage(cms
, sigData
, sigLength
)) != errSecSuccess
) {
1039 secdebug("validupdate", "CMSDecoderUpdateMessage failed with error %d\n", (int)status
);
1042 if ((status
= CMSDecoderSetDetachedContent(cms
, content
)) != errSecSuccess
) {
1043 secdebug("validupdate", "CMSDecoderSetDetachedContent failed with error %d\n", (int)status
);
1046 if ((status
= CMSDecoderFinalizeMessage(cms
)) != errSecSuccess
) {
1047 secdebug("validupdate", "CMSDecoderFinalizeMessage failed with error %d\n", (int)status
);
1051 policy
= SecPolicyCreateApplePinned(CFSTR("ValidUpdate"), // kSecPolicyNameAppleValidUpdate
1052 CFSTR("1.2.840.113635.100.6.2.10"), // System Integration 2 Intermediate Certificate
1053 CFSTR("1.2.840.113635.100.6.51")); // Valid update signing OID
1055 // Check that the first signer actually signed this message.
1056 if ((status
= CMSDecoderCopySignerStatus(cms
, 0, policy
,
1057 false, &signerStatus
, &trust
, NULL
)) != errSecSuccess
) {
1058 secdebug("validupdate", "CMSDecoderCopySignerStatus failed with error %d\n", (int)status
);
1061 // Make sure the signature verifies against the detached content
1062 if (signerStatus
!= kCMSSignerValid
) {
1063 secdebug("validupdate", "ERROR: signature did not verify (signer status %d)\n", (int)signerStatus
);
1064 status
= errSecInvalidSignature
;
1067 // Make sure the signing certificate is valid for the specified policy
1068 SecTrustResultType trustResult
= kSecTrustResultInvalid
;
1069 status
= SecTrustEvaluate(trust
, &trustResult
);
1070 if (status
!= errSecSuccess
) {
1071 secdebug("validupdate", "SecTrustEvaluate failed with error %d (trust=%p)\n", (int)status
, (void *)trust
);
1072 } else if (!(trustResult
== kSecTrustResultUnspecified
|| trustResult
== kSecTrustResultProceed
)) {
1073 secdebug("validupdate", "SecTrustEvaluate failed with trust result %d\n", (int)trustResult
);
1074 status
= errSecVerificationFailure
;
1079 CFReleaseSafe(content
);
1080 CFReleaseSafe(trust
);
1081 CFReleaseSafe(policy
);
1084 return (status
== errSecSuccess
);
1087 CFAbsoluteTime
SecRevocationDbComputeNextUpdateTime(CFIndex updateInterval
) {
1088 CFIndex interval
= updateInterval
;
1089 // try to use interval preference if it exists
1090 CFTypeRef value
= (CFNumberRef
)CFPreferencesCopyValue(kUpdateIntervalKey
, kSecPrefsDomain
, kCFPreferencesAnyUser
, kCFPreferencesCurrentHost
);
1091 if (isNumber(value
)) {
1092 CFNumberGetValue((CFNumberRef
)value
, kCFNumberCFIndexType
, &interval
);
1094 CFReleaseNull(value
);
1096 if (interval
<= 0) {
1097 interval
= kSecStdUpdateInterval
;
1101 if (interval
< kSecMinUpdateInterval
) {
1102 interval
= kSecMinUpdateInterval
;
1103 } else if (interval
> kSecMaxUpdateInterval
) {
1104 interval
= kSecMaxUpdateInterval
;
1107 // compute randomization factor, between 0 and 50% of the interval
1108 CFIndex fuzz
= arc4random() % (long)(interval
/2.0);
1109 CFAbsoluteTime nextUpdate
= CFAbsoluteTimeGetCurrent() + interval
+ fuzz
;
1110 secdebug("validupdate", "next update in %ld seconds", (long)(interval
+ fuzz
));
1114 void SecRevocationDbComputeAndSetNextUpdateTime(void) {
1115 gNextUpdate
= SecRevocationDbComputeNextUpdateTime(0);
1116 SecRevocationDbSetNextUpdateTime(gNextUpdate
);
1117 gUpdateStarted
= 0; /* no update is currently in progress */
1120 CFIndex
SecRevocationDbIngestUpdate(CFDictionaryRef update
, CFIndex chunkVersion
) {
1121 CFIndex version
= 0;
1125 CFTypeRef value
= (CFNumberRef
)CFDictionaryGetValue(update
, CFSTR("version"));
1126 if (isNumber(value
)) {
1127 if (!CFNumberGetValue((CFNumberRef
)value
, kCFNumberCFIndexType
, &version
)) {
1132 // only the first chunk will have a version, so the second and
1133 // subsequent chunks will need to pass it in chunkVersion.
1134 version
= chunkVersion
;
1136 CFIndex curVersion
= SecRevocationDbGetVersion();
1137 if (version
> curVersion
|| chunkVersion
> 0) {
1138 SecRevocationDbApplyUpdate(update
, version
);
1140 secdebug("validupdate", "we have v%ld, skipping update to v%ld",
1141 (long)curVersion
, (long)version
);
1142 version
= -1; // invalid, so we know to skip subsequent chunks
1148 /* Database schema */
1150 /* admin table holds these key-value (or key-ival) pairs:
1151 'version' (integer) // version of database content
1152 'check_again' (double) // CFAbsoluteTime of next check (optional)
1153 'db_version' (integer) // version of database schema
1154 'db_hash' (blob) // SHA-256 database hash
1155 --> entries in admin table are unique by text key
1157 issuers table holds map of issuing CA hashes to group identifiers:
1158 groupid (integer) // associated group identifier in group ID table
1159 issuer_hash (blob) // SHA-256 hash of issuer certificate (primary key)
1160 --> entries in issuers table are unique by issuer_hash;
1161 multiple issuer entries may have the same groupid!
1163 groups table holds records with these attributes:
1164 groupid (integer) // ordinal ID associated with this group entry
1165 flags (integer) // a bitmask of the following values:
1166 kSecValidInfoComplete (0x00000001) set if we have all revocation info for this issuer group
1167 kSecValidInfoCheckOCSP (0x00000002) set if must check ocsp for certs from this issuer group
1168 kSecValidInfoKnownOnly (0x00000004) set if any CA from this issuer group must be in database
1169 kSecValidInfoRequireCT (0x00000008) set if all certs from this issuer group must have SCTs
1170 kSecValidInfoAllowlist (0x00000010) set if this entry describes valid certs (i.e. is allowed)
1171 kSecValidInfoNoCACheck (0x00000020) set if this entry does not require an OCSP check to accept
1172 kSecValidInfoOverridable (0x00000040) set if the trust status is recoverable and can be overridden
1173 kSecValidInfoDateConstraints (0x00000080) set if this group has not-before or not-after constraints
1174 kSecValidInfoNameConstraints (0x00000100) [RESERVED] set if this group has name constraints in database
1175 kSecValidInfoPolicyConstraints (0x00000200) [RESERVED] set if this group has policy constraints in database
1176 format (integer) // an integer describing format of entries:
1177 kSecValidInfoFormatUnknown (0) unknown format
1178 kSecValidInfoFormatSerial (1) serial number, not greater than 20 bytes in length
1179 kSecValidInfoFormatSHA256 (2) SHA-256 hash, 32 bytes in length
1180 kSecValidInfoFormatNto1 (3) filter data blob of arbitrary length
1181 data (blob) // Bloom filter data if format is 'nto1', otherwise NULL
1182 --> entries in groups table are unique by groupid
1184 serials table holds serial number blobs with these attributes:
1185 groupid (integer) // identifier for issuer group in the groups table
1186 serial (blob) // serial number
1187 --> entries in serials table are unique by serial and groupid
1189 hashes table holds SHA-256 hashes of certificates with these attributes:
1190 groupid (integer) // identifier for issuer group in the groups table
1191 sha256 (blob) // SHA-256 hash of subject certificate
1192 --> entries in hashes table are unique by sha256 and groupid
1194 dates table holds notBefore and notAfter dates (as CFAbsoluteTime) with these attributes:
1195 groupid (integer) // identifier for issuer group in the groups table (primary key)
1196 notbefore (real) // issued certs are invalid if their notBefore is prior to this date
1197 notafter (real) // issued certs are invalid after this date (or their notAfter, if earlier)
1198 --> entries in dates table are unique by groupid, and only exist if kSecValidInfoDateConstraints is true
1201 #define createTablesSQL CFSTR("CREATE TABLE IF NOT EXISTS admin(" \
1202 "key TEXT PRIMARY KEY NOT NULL," \
1203 "ival INTEGER NOT NULL," \
1206 "CREATE TABLE IF NOT EXISTS issuers(" \
1207 "groupid INTEGER NOT NULL," \
1208 "issuer_hash BLOB PRIMARY KEY NOT NULL" \
1210 "CREATE INDEX IF NOT EXISTS issuer_idx ON issuers(issuer_hash);" \
1211 "CREATE TABLE IF NOT EXISTS groups(" \
1212 "groupid INTEGER PRIMARY KEY AUTOINCREMENT," \
1217 "CREATE TABLE IF NOT EXISTS serials(" \
1218 "groupid INTEGER NOT NULL," \
1219 "serial BLOB NOT NULL," \
1220 "UNIQUE(groupid,serial)" \
1222 "CREATE TABLE IF NOT EXISTS hashes(" \
1223 "groupid INTEGER NOT NULL," \
1224 "sha256 BLOB NOT NULL," \
1225 "UNIQUE(groupid,sha256)" \
1227 "CREATE TABLE IF NOT EXISTS dates(" \
1228 "groupid INTEGER PRIMARY KEY NOT NULL," \
1232 "CREATE TRIGGER IF NOT EXISTS group_del " \
1233 "BEFORE DELETE ON groups FOR EACH ROW " \
1235 "DELETE FROM serials WHERE groupid=OLD.groupid; " \
1236 "DELETE FROM hashes WHERE groupid=OLD.groupid; " \
1237 "DELETE FROM issuers WHERE groupid=OLD.groupid; " \
1238 "DELETE FROM dates WHERE groupid=OLD.groupid; " \
1241 #define selectGroupIdSQL CFSTR("SELECT DISTINCT groupid " \
1242 "FROM issuers WHERE issuer_hash=?")
1243 #define selectVersionSQL CFSTR("SELECT ival FROM admin " \
1244 "WHERE key='version'")
1245 #define selectDbVersionSQL CFSTR("SELECT ival FROM admin " \
1246 "WHERE key='db_version'")
1247 #define selectDbFormatSQL CFSTR("SELECT ival FROM admin " \
1248 "WHERE key='db_format'")
1249 #define selectDbHashSQL CFSTR("SELECT value FROM admin " \
1250 "WHERE key='db_hash'")
1251 #define selectDbSourceSQL CFSTR("SELECT value FROM admin " \
1252 "WHERE key='db_source'")
1253 #define selectNextUpdateSQL CFSTR("SELECT value FROM admin " \
1254 "WHERE key='check_again'")
1255 #define selectGroupRecordSQL CFSTR("SELECT flags,format,data FROM " \
1256 "groups WHERE groupid=?")
1257 #define selectSerialRecordSQL CFSTR("SELECT rowid FROM serials " \
1258 "WHERE groupid=? AND serial=?")
1259 #define selectDateRecordSQL CFSTR("SELECT notbefore,notafter FROM " \
1260 "dates WHERE groupid=?")
1261 #define selectHashRecordSQL CFSTR("SELECT rowid FROM hashes " \
1262 "WHERE groupid=? AND sha256=?")
1263 #define insertAdminRecordSQL CFSTR("INSERT OR REPLACE INTO admin " \
1264 "(key,ival,value) VALUES (?,?,?)")
1265 #define insertIssuerRecordSQL CFSTR("INSERT OR REPLACE INTO issuers " \
1266 "(groupid,issuer_hash) VALUES (?,?)")
1267 #define insertGroupRecordSQL CFSTR("INSERT OR REPLACE INTO groups " \
1268 "(groupid,flags,format,data) VALUES (?,?,?,?)")
1269 #define insertSerialRecordSQL CFSTR("INSERT OR REPLACE INTO serials " \
1270 "(groupid,serial) VALUES (?,?)")
1271 #define insertDateRecordSQL CFSTR("INSERT OR REPLACE INTO dates " \
1272 "(groupid,notbefore,notafter) VALUES (?,?,?)")
1273 #define insertSha256RecordSQL CFSTR("INSERT OR REPLACE INTO hashes " \
1274 "(groupid,sha256) VALUES (?,?)")
1275 #define deleteGroupRecordSQL CFSTR("DELETE FROM groups WHERE groupid=?")
1277 #define updateConstraintsTablesSQL CFSTR("" \
1278 "CREATE TABLE IF NOT EXISTS dates(" \
1279 "groupid INTEGER PRIMARY KEY NOT NULL," \
1284 #define updateGroupDeleteTriggerSQL CFSTR("" \
1285 "DROP TRIGGER IF EXISTS group_del;" \
1286 "CREATE TRIGGER group_del BEFORE DELETE ON groups FOR EACH ROW " \
1288 "DELETE FROM serials WHERE groupid=OLD.groupid; " \
1289 "DELETE FROM hashes WHERE groupid=OLD.groupid; " \
1290 "DELETE FROM issuers WHERE groupid=OLD.groupid; " \
1291 "DELETE FROM dates WHERE groupid=OLD.groupid; " \
1294 #define deleteAllEntriesSQL CFSTR("" \
1295 "DELETE FROM groups; " \
1296 "DELETE FROM admin WHERE key='version'; " \
1297 "DELETE FROM sqlite_sequence")
1299 /* Database management */
1301 static SecDbRef
SecRevocationDbCreate(CFStringRef path
) {
1302 /* only the db owner should open a read-write connection. */
1303 bool readWrite
= isDbOwner();
1306 SecDbRef result
= SecDbCreateWithOptions(path
, mode
, readWrite
, false, false, ^bool (SecDbRef db
, SecDbConnectionRef dbconn
, bool didCreate
, bool *callMeAgainForNextConnection
, CFErrorRef
*error
) {
1307 __block
bool ok
= true;
1308 CFErrorRef localError
= NULL
;
1309 if (ok
&& !SecDbWithSQL(dbconn
, selectGroupIdSQL
, &localError
, NULL
) && CFErrorGetCode(localError
) == SQLITE_ERROR
) {
1310 /* SecDbWithSQL returns SQLITE_ERROR if the table we are preparing the above statement for doesn't exist. */
1312 /* Create all database tables, indexes, and triggers. */
1313 ok
&= SecDbTransaction(dbconn
, kSecDbExclusiveTransactionType
, error
, ^(bool *commit
) {
1314 ok
= SecDbExec(dbconn
, createTablesSQL
, error
);
1318 if (!ok
|| localError
) {
1319 CFIndex errCode
= errSecInternalComponent
;
1320 if (error
&& *error
) {
1321 errCode
= CFErrorGetCode(*error
);
1323 secerror("%s failed: %@", didCreate
? "Create" : "Open", error
? *error
: NULL
);
1324 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationCreate
, TAFatalError
, errCode
);
1326 CFReleaseSafe(localError
);
1333 typedef struct __SecRevocationDb
*SecRevocationDbRef
;
1334 struct __SecRevocationDb
{
1336 dispatch_queue_t update_queue
;
1337 bool updateInProgress
;
1338 bool unsupportedVersion
;
1341 static dispatch_once_t kSecRevocationDbOnce
;
1342 static SecRevocationDbRef kSecRevocationDb
= NULL
;
1344 static SecRevocationDbRef
SecRevocationDbInit(CFStringRef db_name
) {
1345 SecRevocationDbRef rdb
;
1346 dispatch_queue_attr_t attr
;
1348 require(rdb
= (SecRevocationDbRef
)malloc(sizeof(struct __SecRevocationDb
)), errOut
);
1350 rdb
->update_queue
= NULL
;
1351 rdb
->updateInProgress
= false;
1352 rdb
->unsupportedVersion
= false;
1354 require(rdb
->db
= SecRevocationDbCreate(db_name
), errOut
);
1355 attr
= dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL
, QOS_CLASS_BACKGROUND
, 0);
1356 attr
= dispatch_queue_attr_make_with_autorelease_frequency(attr
, DISPATCH_AUTORELEASE_FREQUENCY_WORK_ITEM
);
1357 require(rdb
->update_queue
= dispatch_queue_create(NULL
, attr
), errOut
);
1362 secdebug("validupdate", "Failed to create db at \"%@\"", db_name
);
1364 if (rdb
->update_queue
) {
1365 dispatch_release(rdb
->update_queue
);
1367 CFReleaseSafe(rdb
->db
);
1373 static CFStringRef
SecRevocationDbCopyPath(void) {
1374 CFURLRef revDbURL
= NULL
;
1375 CFStringRef revInfoRelPath
= NULL
;
1376 if ((revInfoRelPath
= CFStringCreateWithFormat(NULL
, NULL
, CFSTR("%s"), kSecRevocationDbFileName
)) != NULL
) {
1377 revDbURL
= SecCopyURLForFileInRevocationInfoDirectory(revInfoRelPath
);
1379 CFReleaseSafe(revInfoRelPath
);
1381 CFStringRef revDbPath
= NULL
;
1383 revDbPath
= CFURLCopyFileSystemPath(revDbURL
, kCFURLPOSIXPathStyle
);
1384 CFRelease(revDbURL
);
1389 static void SecRevocationDbWith(void(^dbJob
)(SecRevocationDbRef db
)) {
1390 dispatch_once(&kSecRevocationDbOnce
, ^{
1391 CFStringRef dbPath
= SecRevocationDbCopyPath();
1393 kSecRevocationDb
= SecRevocationDbInit(dbPath
);
1397 // Do pre job run work here (cancel idle timers etc.)
1398 if (kSecRevocationDb
->updateInProgress
) {
1399 return; // this would block since SecDb has an exclusive transaction lock
1401 dbJob(kSecRevocationDb
);
1402 // Do post job run work here (gc timer, etc.)
1405 static int64_t _SecRevocationDbGetVersion(SecRevocationDbRef rdb
, CFErrorRef
*error
) {
1406 /* look up version entry in admin table; returns -1 on error */
1407 __block
int64_t version
= -1;
1408 __block
bool ok
= true;
1409 __block CFErrorRef localError
= NULL
;
1411 ok
&= SecDbPerformRead(rdb
->db
, &localError
, ^(SecDbConnectionRef dbconn
) {
1412 ok
&= SecDbWithSQL(dbconn
, selectVersionSQL
, &localError
, ^bool(sqlite3_stmt
*selectVersion
) {
1413 ok
&= SecDbStep(dbconn
, selectVersion
, &localError
, ^void(bool *stop
) {
1414 version
= sqlite3_column_int64(selectVersion
, 0);
1420 if (!ok
|| localError
) {
1421 secerror("_SecRevocationDbGetVersion failed: %@", localError
);
1422 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationRead
, TAFatalError
,
1423 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
1425 (void) CFErrorPropagate(localError
, error
);
1429 static void _SecRevocationDbSetVersion(SecRevocationDbRef rdb
, CFIndex version
) {
1430 secdebug("validupdate", "setting version to %ld", (long)version
);
1432 __block CFErrorRef localError
= NULL
;
1433 __block
bool ok
= true;
1434 ok
&= SecDbPerformWrite(rdb
->db
, &localError
, ^(SecDbConnectionRef dbconn
) {
1435 ok
&= SecDbTransaction(dbconn
, kSecDbExclusiveTransactionType
, &localError
, ^(bool *commit
) {
1436 ok
&= SecDbWithSQL(dbconn
, insertAdminRecordSQL
, &localError
, ^bool(sqlite3_stmt
*insertVersion
) {
1437 const char *versionKey
= "version";
1438 ok
= ok
&& SecDbBindText(insertVersion
, 1, versionKey
, strlen(versionKey
),
1439 SQLITE_TRANSIENT
, &localError
);
1440 ok
= ok
&& SecDbBindInt64(insertVersion
, 2,
1441 (sqlite3_int64
)version
, &localError
);
1442 ok
= ok
&& SecDbStep(dbconn
, insertVersion
, &localError
, NULL
);
1447 if (!ok
|| localError
) {
1448 secerror("_SecRevocationDbSetVersion failed: %@", localError
);
1449 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationWrite
, TAFatalError
,
1450 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
1452 CFReleaseSafe(localError
);
1455 static int64_t _SecRevocationDbGetSchemaVersion(SecRevocationDbRef rdb
, CFErrorRef
*error
) {
1456 /* look up db_version entry in admin table; returns -1 on error */
1457 __block
int64_t db_version
= -1;
1458 __block
bool ok
= true;
1459 __block CFErrorRef localError
= NULL
;
1461 ok
&= SecDbPerformRead(rdb
->db
, &localError
, ^(SecDbConnectionRef dbconn
) {
1462 ok
&= SecDbWithSQL(dbconn
, selectDbVersionSQL
, &localError
, ^bool(sqlite3_stmt
*selectDbVersion
) {
1463 ok
&= SecDbStep(dbconn
, selectDbVersion
, &localError
, ^void(bool *stop
) {
1464 db_version
= sqlite3_column_int64(selectDbVersion
, 0);
1470 if (!ok
|| localError
) {
1471 secerror("_SecRevocationDbGetSchemaVersion failed: %@", localError
);
1472 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationRead
, TAFatalError
,
1473 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
1475 (void) CFErrorPropagate(localError
, error
);
1479 static bool _SecRevocationDbSetSchemaVersion(SecRevocationDbRef rdb
, CFIndex dbversion
) {
1480 if (dbversion
> 0) {
1481 int64_t db_version
= _SecRevocationDbGetSchemaVersion(rdb
, NULL
);
1482 if (db_version
>= dbversion
) {
1483 return true; /* requested schema is earlier than current schema */
1486 secdebug("validupdate", "setting db_version to %ld", (long)dbversion
);
1488 __block CFErrorRef localError
= NULL
;
1489 __block
bool ok
= true;
1490 ok
&= SecDbPerformWrite(rdb
->db
, &localError
, ^(SecDbConnectionRef dbconn
) {
1491 ok
&= SecDbTransaction(dbconn
, kSecDbExclusiveTransactionType
, &localError
, ^(bool *commit
) {
1492 ok
&= SecDbWithSQL(dbconn
, insertAdminRecordSQL
, &localError
, ^bool(sqlite3_stmt
*insertDbVersion
) {
1493 const char *dbVersionKey
= "db_version";
1494 ok
= ok
&& SecDbBindText(insertDbVersion
, 1, dbVersionKey
, strlen(dbVersionKey
),
1495 SQLITE_TRANSIENT
, &localError
);
1496 ok
= ok
&& SecDbBindInt64(insertDbVersion
, 2,
1497 (sqlite3_int64
)dbversion
, &localError
);
1498 ok
= ok
&& SecDbStep(dbconn
, insertDbVersion
, &localError
, NULL
);
1503 if (!ok
|| localError
) {
1504 secerror("_SecRevocationDbSetSchemaVersion failed: %@", localError
);
1505 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationWrite
, TAFatalError
,
1506 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
1508 rdb
->unsupportedVersion
= false;
1510 CFReleaseSafe(localError
);
1514 static bool _SecRevocationDbUpdateSchema(SecRevocationDbRef rdb
) {
1515 secdebug("validupdate", "updating db schema to v%ld", (long)kSecRevocationDbSchemaVersion
);
1517 __block CFErrorRef localError
= NULL
;
1518 __block
bool ok
= true;
1519 ok
&= SecDbPerformWrite(rdb
->db
, &localError
, ^(SecDbConnectionRef dbconn
) {
1520 ok
&= SecDbTransaction(dbconn
, kSecDbExclusiveTransactionType
, &localError
, ^(bool *commit
) {
1521 ok
&= SecDbWithSQL(dbconn
, updateConstraintsTablesSQL
, &localError
, ^bool(sqlite3_stmt
*updateTables
) {
1522 ok
= SecDbStep(dbconn
, updateTables
, &localError
, NULL
);
1526 ok
&= SecDbWithSQL(dbconn
, updateGroupDeleteTriggerSQL
, &localError
, ^bool(sqlite3_stmt
*updateTrigger
) {
1527 ok
= SecDbStep(dbconn
, updateTrigger
, &localError
, NULL
);
1533 secerror("_SecRevocationDbUpdateSchema failed: %@", localError
);
1535 ok
&= _SecRevocationDbSetSchemaVersion(rdb
, kSecRevocationDbSchemaVersion
);
1537 CFReleaseSafe(localError
);
1541 static int64_t _SecRevocationDbGetUpdateFormat(SecRevocationDbRef rdb
, CFErrorRef
*error
) {
1542 /* look up db_format entry in admin table; returns -1 on error */
1543 __block
int64_t db_format
= -1;
1544 __block
bool ok
= true;
1545 __block CFErrorRef localError
= NULL
;
1547 ok
&= SecDbPerformRead(rdb
->db
, &localError
, ^(SecDbConnectionRef dbconn
) {
1548 ok
&= SecDbWithSQL(dbconn
, selectDbFormatSQL
, &localError
, ^bool(sqlite3_stmt
*selectDbFormat
) {
1549 ok
&= SecDbStep(dbconn
, selectDbFormat
, &localError
, ^void(bool *stop
) {
1550 db_format
= sqlite3_column_int64(selectDbFormat
, 0);
1556 if (!ok
|| localError
) {
1557 secerror("_SecRevocationDbGetUpdateFormat failed: %@", localError
);
1558 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationRead
, TAFatalError
,
1559 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
1561 (void) CFErrorPropagate(localError
, error
);
1565 static void _SecRevocationDbSetUpdateFormat(SecRevocationDbRef rdb
, CFIndex dbformat
) {
1566 secdebug("validupdate", "setting db_format to %ld", (long)dbformat
);
1568 __block CFErrorRef localError
= NULL
;
1569 __block
bool ok
= true;
1570 ok
&= SecDbPerformWrite(rdb
->db
, &localError
, ^(SecDbConnectionRef dbconn
) {
1571 ok
&= SecDbTransaction(dbconn
, kSecDbExclusiveTransactionType
, &localError
, ^(bool *commit
) {
1572 ok
&= SecDbWithSQL(dbconn
, insertAdminRecordSQL
, &localError
, ^bool(sqlite3_stmt
*insertDbFormat
) {
1573 const char *dbFormatKey
= "db_format";
1574 ok
= ok
&& SecDbBindText(insertDbFormat
, 1, dbFormatKey
, strlen(dbFormatKey
),
1575 SQLITE_TRANSIENT
, &localError
);
1576 ok
= ok
&& SecDbBindInt64(insertDbFormat
, 2,
1577 (sqlite3_int64
)dbformat
, &localError
);
1578 ok
= ok
&& SecDbStep(dbconn
, insertDbFormat
, &localError
, NULL
);
1583 if (!ok
|| localError
) {
1584 secerror("_SecRevocationDbSetUpdateFormat failed: %@", localError
);
1585 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationWrite
, TAFatalError
,
1586 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
1588 rdb
->unsupportedVersion
= false;
1590 CFReleaseSafe(localError
);
1593 static CFStringRef
_SecRevocationDbCopyUpdateSource(SecRevocationDbRef rdb
, CFErrorRef
*error
) {
1594 /* look up db_source entry in admin table; returns NULL on error */
1595 __block CFStringRef updateSource
= NULL
;
1596 __block
bool ok
= true;
1597 __block CFErrorRef localError
= NULL
;
1599 ok
&= SecDbPerformRead(rdb
->db
, &localError
, ^(SecDbConnectionRef dbconn
) {
1600 ok
&= SecDbWithSQL(dbconn
, selectDbSourceSQL
, &localError
, ^bool(sqlite3_stmt
*selectDbSource
) {
1601 ok
&= SecDbStep(dbconn
, selectDbSource
, &localError
, ^void(bool *stop
) {
1602 const UInt8
*p
= (const UInt8
*)sqlite3_column_blob(selectDbSource
, 0);
1604 CFIndex length
= (CFIndex
)sqlite3_column_bytes(selectDbSource
, 0);
1606 updateSource
= CFStringCreateWithBytes(kCFAllocatorDefault
, p
, length
, kCFStringEncodingUTF8
, false);
1614 if (!ok
|| localError
) {
1615 secerror("_SecRevocationDbCopyUpdateSource failed: %@", localError
);
1616 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationRead
, TAFatalError
,
1617 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
1619 (void) CFErrorPropagate(localError
, error
);
1620 return updateSource
;
1623 static void _SecRevocationDbSetUpdateSource(SecRevocationDbRef rdb
, CFStringRef updateSource
) {
1624 if (!updateSource
) {
1625 secerror("_SecRevocationDbSetUpdateSource failed: %d", errSecParam
);
1628 __block
char buffer
[256];
1629 __block
const char *updateSourceCStr
= CFStringGetCStringPtr(updateSource
, kCFStringEncodingUTF8
);
1630 if (!updateSourceCStr
) {
1631 if (CFStringGetCString(updateSource
, buffer
, 256, kCFStringEncodingUTF8
)) {
1632 updateSourceCStr
= buffer
;
1635 if (!updateSourceCStr
) {
1636 secerror("_SecRevocationDbSetUpdateSource failed: unable to get UTF-8 encoding");
1639 secdebug("validupdate", "setting update source to \"%s\"", updateSourceCStr
);
1641 __block CFErrorRef localError
= NULL
;
1642 __block
bool ok
= true;
1643 ok
&= SecDbPerformWrite(rdb
->db
, &localError
, ^(SecDbConnectionRef dbconn
) {
1644 ok
&= SecDbTransaction(dbconn
, kSecDbExclusiveTransactionType
, &localError
, ^(bool *commit
) {
1645 ok
&= SecDbWithSQL(dbconn
, insertAdminRecordSQL
, &localError
, ^bool(sqlite3_stmt
*insertRecord
) {
1646 const char *dbSourceKey
= "db_source";
1647 ok
= ok
&& SecDbBindText(insertRecord
, 1, dbSourceKey
, strlen(dbSourceKey
),
1648 SQLITE_TRANSIENT
, &localError
);
1649 ok
= ok
&& SecDbBindInt64(insertRecord
, 2,
1650 (sqlite3_int64
)0, &localError
);
1651 ok
= ok
&& SecDbBindBlob(insertRecord
, 3,
1652 updateSourceCStr
, strlen(updateSourceCStr
),
1653 SQLITE_TRANSIENT
, &localError
);
1654 ok
= ok
&& SecDbStep(dbconn
, insertRecord
, &localError
, NULL
);
1659 if (!ok
|| localError
) {
1660 secerror("_SecRevocationDbSetUpdateSource failed: %@", localError
);
1661 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationWrite
, TAFatalError
,
1662 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
1664 CFReleaseSafe(localError
);
1667 static CFAbsoluteTime
_SecRevocationDbGetNextUpdateTime(SecRevocationDbRef rdb
, CFErrorRef
*error
) {
1668 /* look up check_again entry in admin table; returns 0 on error */
1669 __block CFAbsoluteTime nextUpdate
= 0;
1670 __block
bool ok
= true;
1671 __block CFErrorRef localError
= NULL
;
1673 ok
&= SecDbPerformRead(rdb
->db
, &localError
, ^(SecDbConnectionRef dbconn
) {
1674 ok
&= SecDbWithSQL(dbconn
, selectNextUpdateSQL
, &localError
, ^bool(sqlite3_stmt
*selectNextUpdate
) {
1675 ok
&= SecDbStep(dbconn
, selectNextUpdate
, &localError
, ^void(bool *stop
) {
1676 CFAbsoluteTime
*p
= (CFAbsoluteTime
*)sqlite3_column_blob(selectNextUpdate
, 0);
1678 if (sizeof(CFAbsoluteTime
) == sqlite3_column_bytes(selectNextUpdate
, 0)) {
1687 if (!ok
|| localError
) {
1688 secerror("_SecRevocationDbGetNextUpdateTime failed: %@", localError
);
1689 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationRead
, TAFatalError
,
1690 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
1692 (void) CFErrorPropagate(localError
, error
);
1696 static void _SecRevocationDbSetNextUpdateTime(SecRevocationDbRef rdb
, CFAbsoluteTime nextUpdate
){
1697 secdebug("validupdate", "setting next update to %f", (double)nextUpdate
);
1699 __block CFErrorRef localError
= NULL
;
1700 __block
bool ok
= true;
1701 ok
&= SecDbPerformWrite(rdb
->db
, &localError
, ^(SecDbConnectionRef dbconn
) {
1702 ok
&= SecDbTransaction(dbconn
, kSecDbExclusiveTransactionType
, &localError
, ^(bool *commit
) {
1703 ok
&= SecDbWithSQL(dbconn
, insertAdminRecordSQL
, &localError
, ^bool(sqlite3_stmt
*insertRecord
) {
1704 const char *nextUpdateKey
= "check_again";
1705 ok
= ok
&& SecDbBindText(insertRecord
, 1, nextUpdateKey
, strlen(nextUpdateKey
),
1706 SQLITE_TRANSIENT
, &localError
);
1707 ok
= ok
&& SecDbBindInt64(insertRecord
, 2,
1708 (sqlite3_int64
)0, &localError
);
1709 ok
= ok
&& SecDbBindBlob(insertRecord
, 3,
1710 &nextUpdate
, sizeof(CFAbsoluteTime
),
1711 SQLITE_TRANSIENT
, &localError
);
1712 ok
= ok
&& SecDbStep(dbconn
, insertRecord
, &localError
, NULL
);
1717 if (!ok
|| localError
) {
1718 secerror("_SecRevocationDbSetNextUpdate failed: %@", localError
);
1719 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationWrite
, TAFatalError
,
1720 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
1722 CFReleaseSafe(localError
);
1725 static bool _SecRevocationDbRemoveAllEntries(SecRevocationDbRef rdb
) {
1726 /* clear out the contents of the database and start fresh */
1727 __block
bool ok
= true;
1728 __block CFErrorRef localError
= NULL
;
1730 /* update schema first */
1731 _SecRevocationDbUpdateSchema(rdb
);
1733 ok
&= SecDbPerformWrite(rdb
->db
, &localError
, ^(SecDbConnectionRef dbconn
) {
1734 ok
&= SecDbTransaction(dbconn
, kSecDbExclusiveTransactionType
, &localError
, ^(bool *commit
) {
1735 /* delete all entries */
1736 ok
&= SecDbExec(dbconn
, deleteAllEntriesSQL
, &localError
);
1737 secnotice("validupdate", "resetting database, result: %d (expected 1)", (ok
) ? 1 : 0);
1740 /* compact the db (must be done outside transaction scope) */
1741 ok
&= SecDbExec(dbconn
, CFSTR("VACUUM"), &localError
);
1742 secnotice("validupdate", "compacting database, result: %d (expected 1)", (ok
) ? 1 : 0);
1744 /* one more thing: update the schema version and format to current */
1745 _SecRevocationDbSetSchemaVersion(rdb
, kSecRevocationDbSchemaVersion
);
1746 _SecRevocationDbSetUpdateFormat(rdb
, kSecRevocationDbUpdateFormat
);
1748 if (!ok
|| localError
) {
1749 secerror("_SecRevocationDbRemoveAllEntries failed: %@", localError
);
1750 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationWrite
, TAFatalError
,
1751 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
1753 CFReleaseSafe(localError
);
1757 static bool _SecRevocationDbUpdateIssuers(SecRevocationDbRef rdb
, int64_t groupId
, CFArrayRef issuers
, CFErrorRef
*error
) {
1758 /* insert or replace issuer records in issuers table */
1759 if (!issuers
|| groupId
< 0) {
1760 return false; /* must have something to insert, and a group to associate with it */
1762 __block
bool ok
= true;
1763 __block CFErrorRef localError
= NULL
;
1765 ok
&= SecDbPerformWrite(rdb
->db
, &localError
, ^(SecDbConnectionRef dbconn
) {
1766 ok
&= SecDbTransaction(dbconn
, kSecDbExclusiveTransactionType
, &localError
, ^(bool *commit
) {
1767 if (isArray(issuers
)) {
1768 CFIndex issuerIX
, issuerCount
= CFArrayGetCount(issuers
);
1769 for (issuerIX
=0; issuerIX
<issuerCount
&& ok
; issuerIX
++) {
1770 CFDataRef hash
= (CFDataRef
)CFArrayGetValueAtIndex(issuers
, issuerIX
);
1771 if (!hash
) { continue; }
1772 ok
= ok
&& SecDbWithSQL(dbconn
, insertIssuerRecordSQL
, &localError
, ^bool(sqlite3_stmt
*insertIssuer
) {
1773 ok
= ok
&& SecDbBindInt64(insertIssuer
, 1,
1774 groupId
, &localError
);
1775 ok
= ok
&& SecDbBindBlob(insertIssuer
, 2,
1776 CFDataGetBytePtr(hash
),
1777 CFDataGetLength(hash
),
1778 SQLITE_TRANSIENT
, &localError
);
1779 /* Execute the insert statement for this issuer record. */
1780 ok
= ok
&& SecDbStep(dbconn
, insertIssuer
, &localError
, NULL
);
1787 if (!ok
|| localError
) {
1788 secerror("_SecRevocationDbUpdateIssuers failed: %@", localError
);
1789 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationWrite
, TAFatalError
,
1790 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
1792 (void) CFErrorPropagate(localError
, error
);
1796 static bool _SecRevocationDbUpdateIssuerData(SecRevocationDbRef rdb
, int64_t groupId
, CFDictionaryRef dict
, CFErrorRef
*error
) {
1797 /* update/delete records in serials or hashes table. */
1798 if (!dict
|| groupId
< 0) {
1799 return false; /* must have something to insert, and a group to associate with it */
1801 __block
bool ok
= true;
1802 __block CFErrorRef localError
= NULL
;
1804 ok
&= SecDbPerformWrite(rdb
->db
, &localError
, ^(SecDbConnectionRef dbconn
) {
1805 ok
&= SecDbTransaction(dbconn
, kSecDbExclusiveTransactionType
, &localError
, ^(bool *commit
) {
1806 CFArrayRef deleteArray
= (CFArrayRef
)CFDictionaryGetValue(dict
, CFSTR("delete"));
1807 /* process deletions */
1808 if (isArray(deleteArray
)) {
1809 //%%% delete old data here (rdar://31439625)
1811 /* process additions */
1812 CFArrayRef addArray
= (CFArrayRef
)CFDictionaryGetValue(dict
, CFSTR("add"));
1813 if (isArray(addArray
)) {
1814 CFIndex identifierIX
, identifierCount
= CFArrayGetCount(addArray
);
1815 for (identifierIX
=0; identifierIX
<identifierCount
; identifierIX
++) {
1816 CFDataRef identifierData
= (CFDataRef
)CFArrayGetValueAtIndex(addArray
, identifierIX
);
1817 if (!identifierData
) { continue; }
1818 CFIndex length
= CFDataGetLength(identifierData
);
1819 /* we can figure out the format without an extra read to get the format column.
1820 len <= 20 is a serial number. len==32 is a sha256 hash. otherwise: xor. */
1821 CFStringRef sql
= NULL
;
1823 sql
= insertSerialRecordSQL
;
1824 } else if (length
== 32) {
1825 sql
= insertSha256RecordSQL
;
1827 if (!sql
) { continue; }
1829 ok
= ok
&& SecDbWithSQL(dbconn
, sql
, &localError
, ^bool(sqlite3_stmt
*insertIdentifier
) {
1830 /* rowid,(groupid,serial|sha256) */
1831 /* rowid is autoincremented and we never set it directly */
1832 ok
= ok
&& SecDbBindInt64(insertIdentifier
, 1,
1833 groupId
, &localError
);
1834 ok
= ok
&& SecDbBindBlob(insertIdentifier
, 2,
1835 CFDataGetBytePtr(identifierData
),
1836 CFDataGetLength(identifierData
),
1837 SQLITE_TRANSIENT
, &localError
);
1838 /* Execute the insert statement for the identifier record. */
1839 ok
= ok
&& SecDbStep(dbconn
, insertIdentifier
, &localError
, NULL
);
1846 if (!ok
|| localError
) {
1847 secerror("_SecRevocationDbUpdatePerIssuerData failed: %@", localError
);
1848 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationWrite
, TAFatalError
,
1849 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
1851 (void) CFErrorPropagate(localError
, error
);
1855 static bool _SecRevocationDbCopyDateConstraints(SecRevocationDbRef rdb
,
1856 int64_t groupId
, CFDateRef
*notBeforeDate
, CFDateRef
*notAfterDate
, CFErrorRef
*error
) {
1857 /* return true if one or both date constraints exist for a given groupId.
1858 the actual constraints are optionally returned in output CFDateRef parameters.
1859 caller is responsible for releasing date and error parameters, if provided.
1861 __block
bool ok
= true;
1862 __block CFDateRef localNotBefore
= NULL
;
1863 __block CFDateRef localNotAfter
= NULL
;
1864 __block CFErrorRef localError
= NULL
;
1866 ok
&= SecDbPerformRead(rdb
->db
, &localError
, ^(SecDbConnectionRef dbconn
) {
1867 ok
&= SecDbWithSQL(dbconn
, selectDateRecordSQL
, &localError
, ^bool(sqlite3_stmt
*selectDates
) {
1868 /* (groupid,notbefore,notafter) */
1869 ok
&= SecDbBindInt64(selectDates
, 1, groupId
, &localError
);
1870 ok
= ok
&& SecDbStep(dbconn
, selectDates
, &localError
, ^(bool *stop
) {
1871 /* if column has no value, its type will be SQLITE_NULL */
1872 if (SQLITE_NULL
!= sqlite3_column_type(selectDates
, 0)) {
1873 CFAbsoluteTime nb
= (CFAbsoluteTime
)sqlite3_column_double(selectDates
, 0);
1874 localNotBefore
= CFDateCreate(NULL
, nb
);
1876 if (SQLITE_NULL
!= sqlite3_column_type(selectDates
, 1)) {
1877 CFAbsoluteTime na
= (CFAbsoluteTime
)sqlite3_column_double(selectDates
, 1);
1878 localNotAfter
= CFDateCreate(NULL
, na
);
1884 /* must have at least one date constraint */
1885 ok
= ok
&& (localNotBefore
!= NULL
|| localNotAfter
!= NULL
);
1886 if (!ok
|| localError
) {
1887 secerror("_SecRevocationDbCopyDateConstraints failed: %@", localError
);
1888 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationRead
, TAFatalError
,
1889 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
1890 CFReleaseNull(localNotBefore
);
1891 CFReleaseNull(localNotAfter
);
1893 if (notBeforeDate
) {
1894 *notBeforeDate
= localNotBefore
;
1896 CFReleaseSafe(localNotBefore
);
1899 *notAfterDate
= localNotAfter
;
1901 CFReleaseSafe(localNotAfter
);
1904 (void) CFErrorPropagate(localError
, error
);
1908 static bool _SecRevocationDbUpdateIssuerConstraints(SecRevocationDbRef rdb
, int64_t groupId
, CFDictionaryRef dict
, CFErrorRef
*error
) {
1909 /* update optional records in dates, names, or policies tables. */
1910 if (!dict
|| groupId
< 0) {
1911 return false; /* must have something to insert, and a group to associate with it */
1913 __block
bool ok
= true;
1914 __block CFErrorRef localError
= NULL
;
1915 __block CFAbsoluteTime notBefore
= -3155760000.0; /* default: 1901-01-01 00:00:00-0000 */
1916 __block CFAbsoluteTime notAfter
= 31556908800.0; /* default: 3001-01-01 00:00:00-0000 */
1918 CFDateRef notBeforeDate
= (CFDateRef
)CFDictionaryGetValue(dict
, CFSTR("not-before"));
1919 CFDateRef notAfterDate
= (CFDateRef
)CFDictionaryGetValue(dict
, CFSTR("not-after"));
1920 if (isDate(notBeforeDate
)) {
1921 notBefore
= CFDateGetAbsoluteTime(notBeforeDate
);
1923 notBeforeDate
= NULL
;
1925 if (isDate(notAfterDate
)) {
1926 notAfter
= CFDateGetAbsoluteTime(notAfterDate
);
1928 notAfterDate
= NULL
;
1930 if (!(notBeforeDate
|| notAfterDate
)) {
1931 return false; /* no dates supplied, so we have nothing to update for this issuer */
1934 if (!(notBeforeDate
&& notAfterDate
)) {
1935 /* only one date was supplied, so check for existing date constraints */
1936 CFDateRef curNotBeforeDate
= NULL
;
1937 CFDateRef curNotAfterDate
= NULL
;
1938 if (_SecRevocationDbCopyDateConstraints(rdb
, groupId
, &curNotBeforeDate
,
1939 &curNotAfterDate
, &localError
)) {
1940 if (!notBeforeDate
) {
1941 notBeforeDate
= curNotBeforeDate
;
1942 notBefore
= CFDateGetAbsoluteTime(notBeforeDate
);
1944 CFReleaseSafe(curNotBeforeDate
);
1946 if (!notAfterDate
) {
1947 notAfterDate
= curNotAfterDate
;
1948 notAfter
= CFDateGetAbsoluteTime(notAfterDate
);
1950 CFReleaseSafe(curNotAfterDate
);
1955 ok
&= SecDbPerformWrite(rdb
->db
, &localError
, ^(SecDbConnectionRef dbconn
) {
1956 ok
&= SecDbTransaction(dbconn
, kSecDbExclusiveTransactionType
, &localError
, ^(bool *commit
) {
1957 ok
&= SecDbWithSQL(dbconn
, insertDateRecordSQL
, &localError
, ^bool(sqlite3_stmt
*insertDate
) {
1958 /* (groupid,notbefore,notafter) */
1959 ok
= ok
&& SecDbBindInt64(insertDate
, 1, groupId
, &localError
);
1960 ok
= ok
&& SecDbBindDouble(insertDate
, 2, notBefore
, &localError
);
1961 ok
= ok
&& SecDbBindDouble(insertDate
, 3, notAfter
, &localError
);
1962 ok
= ok
&& SecDbStep(dbconn
, insertDate
, &localError
, NULL
);
1966 /* %%% (TBI:9254570,21234699) update name and policy constraint entries here */
1970 if (!ok
|| localError
) {
1971 secinfo("validupdate", "_SecRevocationDbUpdateIssuerConstraints failed (ok=%s, localError=%@)",
1972 (ok
) ? "1" : "0", localError
);
1973 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationWrite
, TAFatalError
,
1974 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
1977 (void) CFErrorPropagate(localError
, error
);
1981 static SecValidInfoFormat
_SecRevocationDbGetGroupFormat(SecRevocationDbRef rdb
,
1982 int64_t groupId
, SecValidInfoFlags
*flags
, CFDataRef
*data
, CFErrorRef
*error
) {
1983 /* return group record fields for a given groupId.
1984 on success, returns a non-zero format type, and other field values in optional output parameters.
1985 caller is responsible for releasing data and error parameters, if provided.
1987 __block
bool ok
= true;
1988 __block SecValidInfoFormat format
= 0;
1989 __block CFErrorRef localError
= NULL
;
1991 /* Select the group record to determine flags and format. */
1992 ok
&= SecDbPerformRead(rdb
->db
, &localError
, ^(SecDbConnectionRef dbconn
) {
1993 ok
&= SecDbWithSQL(dbconn
, selectGroupRecordSQL
, &localError
, ^bool(sqlite3_stmt
*selectGroup
) {
1994 ok
= ok
&& SecDbBindInt64(selectGroup
, 1, groupId
, &localError
);
1995 ok
= ok
&& SecDbStep(dbconn
, selectGroup
, &localError
, ^(bool *stop
) {
1997 *flags
= (SecValidInfoFlags
)sqlite3_column_int(selectGroup
, 0);
1999 format
= (SecValidInfoFormat
)sqlite3_column_int(selectGroup
, 1);
2001 //%%% stream the data from the db into a streamed decompression <rdar://32142637>
2002 uint8_t *p
= (uint8_t *)sqlite3_column_blob(selectGroup
, 2);
2003 if (p
!= NULL
&& format
== kSecValidInfoFormatNto1
) {
2004 CFIndex length
= (CFIndex
)sqlite3_column_bytes(selectGroup
, 2);
2005 *data
= CFDataCreate(kCFAllocatorDefault
, p
, length
);
2012 if (!ok
|| localError
) {
2013 secdebug("validupdate", "GetGroupFormat for groupId %lu failed", (unsigned long)groupId
);
2014 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationRead
, TAFatalError
,
2015 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
2016 format
= kSecValidInfoFormatUnknown
;
2018 (void) CFErrorPropagate(localError
, error
);
2019 if (!(format
> kSecValidInfoFormatUnknown
)) {
2020 secdebug("validupdate", "GetGroupFormat: got format %d for groupId %lld", format
, (long long)groupId
);
2025 static bool _SecRevocationDbUpdateFlags(CFDictionaryRef dict
, CFStringRef key
, SecValidInfoFlags mask
, SecValidInfoFlags
*flags
) {
2026 /* If a boolean value exists in the given dictionary for the given key,
2027 or an explicit "1" or "0" is specified as the key string,
2028 set or clear the corresponding bit(s) defined by the mask argument.
2029 Function returns true if the flags value was changed, false otherwise.
2031 if (!isDictionary(dict
) || !isString(key
) || !flags
) {
2034 bool hasValue
= false, newValue
= false, result
= false;
2035 CFTypeRef value
= (CFBooleanRef
)CFDictionaryGetValue(dict
, key
);
2036 if (isBoolean(value
)) {
2037 newValue
= CFBooleanGetValue((CFBooleanRef
)value
);
2039 } else if (BOOL_STRING_KEY_LENGTH
== CFStringGetLength(key
)) {
2040 if (CFStringCompare(key
, kBoolTrueKey
, 0) == kCFCompareEqualTo
) {
2041 hasValue
= newValue
= true;
2042 } else if (CFStringCompare(key
, kBoolFalseKey
, 0) == kCFCompareEqualTo
) {
2047 SecValidInfoFlags oldFlags
= *flags
;
2053 result
= (*flags
!= oldFlags
);
2058 static bool _SecRevocationDbUpdateFilter(CFDictionaryRef dict
, CFDataRef oldData
, CFDataRef
* __nonnull CF_RETURNS_RETAINED xmlData
) {
2059 /* If xor and/or params values exist in the given dictionary, create a new
2060 property list containing the updated values, and return as a flattened
2061 data blob in the xmlData output parameter (note: caller must release.)
2062 Function returns true if there is new xmlData to save, false otherwise.
2064 bool result
= false;
2065 bool xorProvided
= false;
2066 bool paramsProvided
= false;
2067 bool missingData
= false;
2069 if (!dict
|| !xmlData
) {
2070 return result
; /* no-op if no dictionary is provided, or no way to update the data */
2073 CFDataRef xorCurrent
= NULL
;
2074 CFDataRef xorUpdate
= (CFDataRef
)CFDictionaryGetValue(dict
, CFSTR("xor"));
2075 if (isData(xorUpdate
)) {
2078 CFArrayRef paramsCurrent
= NULL
;
2079 CFArrayRef paramsUpdate
= (CFArrayRef
)CFDictionaryGetValue(dict
, CFSTR("params"));
2080 if (isArray(paramsUpdate
)) {
2081 paramsProvided
= true;
2083 if (!(xorProvided
|| paramsProvided
)) {
2084 return result
; /* nothing to update, so we can bail out here. */
2087 CFPropertyListRef nto1Current
= NULL
;
2088 CFMutableDictionaryRef nto1Update
= CFDictionaryCreateMutable(kCFAllocatorDefault
, 0,
2089 &kCFTypeDictionaryKeyCallBacks
,
2090 &kCFTypeDictionaryValueCallBacks
);
2095 /* turn old data into property list */
2096 CFDataRef data
= (CFDataRef
)CFRetainSafe(oldData
);
2097 CFDataRef inflatedData
= copyInflatedData(data
);
2099 CFReleaseSafe(data
);
2100 data
= inflatedData
;
2103 nto1Current
= CFPropertyListCreateWithData(kCFAllocatorDefault
, data
, 0, NULL
, NULL
);
2104 CFReleaseSafe(data
);
2107 xorCurrent
= (CFDataRef
)CFDictionaryGetValue((CFDictionaryRef
)nto1Current
, CFSTR("xor"));
2108 paramsCurrent
= (CFArrayRef
)CFDictionaryGetValue((CFDictionaryRef
)nto1Current
, CFSTR("params"));
2111 /* set current or updated xor data in new property list */
2113 CFDataRef xorNew
= NULL
;
2115 CFIndex xorUpdateLen
= CFDataGetLength(xorUpdate
);
2116 CFMutableDataRef
xor = CFDataCreateMutableCopy(NULL
, 0, xorCurrent
);
2117 if (xor && xorUpdateLen
> 0) {
2118 /* truncate or zero-extend data to match update size */
2119 CFDataSetLength(xor, xorUpdateLen
);
2120 /* exclusive-or update bytes over the existing data */
2121 UInt8
*xorP
= (UInt8
*)CFDataGetMutableBytePtr(xor);
2122 UInt8
*updP
= (UInt8
*)CFDataGetBytePtr(xorUpdate
);
2124 for (int idx
= 0; idx
< xorUpdateLen
; idx
++) {
2125 xorP
[idx
] = xorP
[idx
] ^ updP
[idx
];
2129 xorNew
= (CFDataRef
)xor;
2131 xorNew
= (CFDataRef
)CFRetainSafe(xorUpdate
);
2134 CFDictionaryAddValue(nto1Update
, CFSTR("xor"), xorNew
);
2135 CFReleaseSafe(xorNew
);
2137 secdebug("validupdate", "Failed to get updated filter data");
2140 } else if (xorCurrent
) {
2141 /* not provided, so use existing xor value */
2142 CFDictionaryAddValue(nto1Update
, CFSTR("xor"), xorCurrent
);
2144 secdebug("validupdate", "Failed to get current filter data");
2148 /* set current or updated params in new property list */
2149 if (paramsProvided
) {
2150 CFDictionaryAddValue(nto1Update
, CFSTR("params"), paramsUpdate
);
2151 } else if (paramsCurrent
) {
2152 /* not provided, so use existing params value */
2153 CFDictionaryAddValue(nto1Update
, CFSTR("params"), paramsCurrent
);
2155 /* missing params: neither provided nor existing */
2156 secdebug("validupdate", "Failed to get current filter params");
2160 CFReleaseSafe(nto1Current
);
2162 *xmlData
= CFPropertyListCreateData(kCFAllocatorDefault
, nto1Update
,
2163 kCFPropertyListXMLFormat_v1_0
,
2165 result
= (*xmlData
!= NULL
);
2167 CFReleaseSafe(nto1Update
);
2169 /* compress the xmlData blob, if possible */
2171 CFDataRef deflatedData
= copyDeflatedData(*xmlData
);
2173 if (CFDataGetLength(deflatedData
) < CFDataGetLength(*xmlData
)) {
2174 CFRelease(*xmlData
);
2175 *xmlData
= deflatedData
;
2177 CFRelease(deflatedData
);
2185 static int64_t _SecRevocationDbUpdateGroup(SecRevocationDbRef rdb
, int64_t groupId
, CFDictionaryRef dict
, CFErrorRef
*error
) {
2186 /* insert group record for a given groupId.
2187 if the specified groupId is < 0, a new group entry is created.
2188 returns the groupId on success, or -1 on failure.
2191 return groupId
; /* no-op if no dictionary is provided */
2194 __block
int64_t result
= -1;
2195 __block
bool ok
= true;
2196 __block
bool isFormatChange
= false;
2197 __block CFErrorRef localError
= NULL
;
2199 __block SecValidInfoFlags flags
= 0;
2200 __block SecValidInfoFormat format
= kSecValidInfoFormatUnknown
;
2201 __block SecValidInfoFormat formatUpdate
= kSecValidInfoFormatUnknown
;
2202 __block CFDataRef data
= NULL
;
2205 /* fetch the flags and data for an existing group record, in case some are being changed. */
2206 format
= _SecRevocationDbGetGroupFormat(rdb
, groupId
, &flags
, &data
, NULL
);
2207 if (format
== kSecValidInfoFormatUnknown
) {
2208 secdebug("validupdate", "existing group %lld has unknown format %d, flags=0x%lx",
2209 (long long)groupId
, format
, flags
);
2210 //%%% clean up by deleting all issuers with this groupId, then the group record,
2211 // or just force a full update? note: we can get here if we fail to bind the
2212 // format value in the prepared SQL statement below.
2216 CFTypeRef value
= (CFStringRef
)CFDictionaryGetValue(dict
, CFSTR("format"));
2217 if (isString(value
)) {
2218 if (CFStringCompare((CFStringRef
)value
, CFSTR("serial"), 0) == kCFCompareEqualTo
) {
2219 formatUpdate
= kSecValidInfoFormatSerial
;
2220 } else if (CFStringCompare((CFStringRef
)value
, CFSTR("sha256"), 0) == kCFCompareEqualTo
) {
2221 formatUpdate
= kSecValidInfoFormatSHA256
;
2222 } else if (CFStringCompare((CFStringRef
)value
, CFSTR("nto1"), 0) == kCFCompareEqualTo
) {
2223 formatUpdate
= kSecValidInfoFormatNto1
;
2226 /* if format value is explicitly supplied, then this is effectively a new group entry. */
2227 isFormatChange
= (formatUpdate
> kSecValidInfoFormatUnknown
&&
2228 formatUpdate
!= format
&&
2231 ok
&= SecDbPerformWrite(rdb
->db
, &localError
, ^(SecDbConnectionRef dbconn
) {
2232 ok
&= SecDbTransaction(dbconn
, kSecDbExclusiveTransactionType
, &localError
, ^(bool *commit
) {
2233 if (isFormatChange
) {
2234 secdebug("validupdate", "group %lld format change from %d to %d",
2235 (long long)groupId
, format
, formatUpdate
);
2236 /* format of an existing group is changing; delete the group first.
2237 this should ensure that all entries referencing the old groupid are deleted.
2239 ok
&= SecDbWithSQL(dbconn
, deleteGroupRecordSQL
, &localError
, ^bool(sqlite3_stmt
*deleteResponse
) {
2240 ok
= SecDbBindInt64(deleteResponse
, 1, groupId
, &localError
);
2241 /* Execute the delete statement. */
2242 ok
= ok
&& SecDbStep(dbconn
, deleteResponse
, &localError
, NULL
);
2246 ok
&= SecDbWithSQL(dbconn
, insertGroupRecordSQL
, &localError
, ^bool(sqlite3_stmt
*insertGroup
) {
2247 /* (groupid,flags,format,data) */
2248 /* groups.groupid */
2249 if (ok
&& (!isFormatChange
) && (groupId
>= 0)) {
2250 /* bind to existing groupId row if known, otherwise will insert and autoincrement */
2251 ok
= SecDbBindInt64(insertGroup
, 1, groupId
, &localError
);
2253 secdebug("validupdate", "failed to set groupId %lld", (long long)groupId
);
2258 (void)_SecRevocationDbUpdateFlags(dict
, CFSTR("complete"), kSecValidInfoComplete
, &flags
);
2259 (void)_SecRevocationDbUpdateFlags(dict
, CFSTR("check-ocsp"), kSecValidInfoCheckOCSP
, &flags
);
2260 (void)_SecRevocationDbUpdateFlags(dict
, CFSTR("known-intermediates-only"), kSecValidInfoKnownOnly
, &flags
);
2261 (void)_SecRevocationDbUpdateFlags(dict
, CFSTR("require-ct"), kSecValidInfoRequireCT
, &flags
);
2262 (void)_SecRevocationDbUpdateFlags(dict
, CFSTR("valid"), kSecValidInfoAllowlist
, &flags
);
2263 (void)_SecRevocationDbUpdateFlags(dict
, CFSTR("no-ca"), kSecValidInfoNoCACheck
, &flags
);
2264 (void)_SecRevocationDbUpdateFlags(dict
, CFSTR("overridable"), kSecValidInfoOverridable
, &flags
);
2266 /* date constraints exist if either "not-before" or "not-after" keys are found */
2267 CFTypeRef notBeforeValue
= (CFDateRef
)CFDictionaryGetValue(dict
, CFSTR("not-before"));
2268 CFTypeRef notAfterValue
= (CFDateRef
)CFDictionaryGetValue(dict
, CFSTR("not-after"));
2269 if (isDate(notBeforeValue
) || isDate(notAfterValue
)) {
2270 (void)_SecRevocationDbUpdateFlags(dict
, kBoolTrueKey
, kSecValidInfoDateConstraints
, &flags
);
2271 /* Note that the spec defines not-before and not-after dates as optional, such that
2272 not providing one does not change the database contents. Therefore, we can never clear
2273 this flag; either a new date entry will be supplied, or a format change will cause
2274 the entire group entry to be deleted. */
2277 /* %%% (TBI:9254570,21234699) name and policy constraints don't exist yet */
2278 (void)_SecRevocationDbUpdateFlags(dict
, kBoolFalseKey
, kSecValidInfoNameConstraints
, &flags
);
2279 (void)_SecRevocationDbUpdateFlags(dict
, kBoolFalseKey
, kSecValidInfoPolicyConstraints
, &flags
);
2281 ok
= SecDbBindInt(insertGroup
, 2, (int)flags
, &localError
);
2283 secdebug("validupdate", "failed to set flags (%lu) for groupId %lld", flags
, (long long)groupId
);
2288 SecValidInfoFormat formatValue
= format
;
2289 if (formatUpdate
> kSecValidInfoFormatUnknown
) {
2290 formatValue
= formatUpdate
;
2292 ok
= SecDbBindInt(insertGroup
, 3, (int)formatValue
, &localError
);
2294 secdebug("validupdate", "failed to set format (%d) for groupId %lld", formatValue
, (long long)groupId
);
2298 CFDataRef xmlData
= NULL
;
2300 bool hasFilter
= ((formatUpdate
== kSecValidInfoFormatNto1
) ||
2301 (formatUpdate
== kSecValidInfoFormatUnknown
&&
2302 format
== kSecValidInfoFormatNto1
));
2304 CFDataRef dataValue
= data
; /* use existing data */
2305 if (_SecRevocationDbUpdateFilter(dict
, data
, &xmlData
)) {
2306 dataValue
= xmlData
; /* use updated data */
2309 ok
= SecDbBindBlob(insertGroup
, 4,
2310 CFDataGetBytePtr(dataValue
),
2311 CFDataGetLength(dataValue
),
2312 SQLITE_TRANSIENT
, &localError
);
2315 secdebug("validupdate", "failed to set data for groupId %lld",
2316 (long long)groupId
);
2319 /* else there is no data, so NULL is implicitly bound to column 4 */
2322 /* Execute the insert statement for the group record. */
2324 ok
= SecDbStep(dbconn
, insertGroup
, &localError
, NULL
);
2326 secdebug("validupdate", "failed to execute insertGroup statement for groupId %lld",
2327 (long long)groupId
);
2329 result
= (int64_t)sqlite3_last_insert_rowid(SecDbHandle(dbconn
));
2332 secdebug("validupdate", "failed to insert group %lld", (long long)result
);
2334 /* Clean up temporary allocation made in this block. */
2335 CFReleaseSafe(xmlData
);
2336 CFReleaseSafe(data
);
2341 if (!ok
|| localError
) {
2342 secerror("_SecRevocationDbUpdateGroup failed: %@", localError
);
2343 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationWrite
, TAFatalError
,
2344 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
2346 (void) CFErrorPropagate(localError
, error
);
2350 static int64_t _SecRevocationDbGroupIdForIssuerHash(SecRevocationDbRef rdb
, CFDataRef hash
, CFErrorRef
*error
) {
2351 /* look up issuer hash in issuers table to get groupid, if it exists */
2352 __block
int64_t groupId
= -1;
2353 __block
bool ok
= true;
2354 __block CFErrorRef localError
= NULL
;
2357 secdebug("validupdate", "failed to get hash (%@)", hash
);
2359 require(hash
, errOut
);
2361 /* This is the starting point for any lookup; find a group id for the given issuer hash.
2362 Before we do that, need to verify the current db_version. We cannot use results from a
2363 database created with a schema version older than the minimum supported version.
2364 However, we may be able to use results from a newer version. At the next database
2365 update interval, if the existing schema is old, we'll be removing and recreating
2366 the database contents with the current schema version.
2368 int64_t db_version
= _SecRevocationDbGetSchemaVersion(rdb
, NULL
);
2369 if (db_version
< kSecRevocationDbMinSchemaVersion
) {
2370 if (!rdb
->unsupportedVersion
) {
2371 secdebug("validupdate", "unsupported db_version: %lld", (long long)db_version
);
2372 rdb
->unsupportedVersion
= true; /* only warn once for a given unsupported version */
2375 require_quiet(db_version
>= kSecRevocationDbMinSchemaVersion
, errOut
);
2377 /* Look up provided issuer_hash in the issuers table.
2379 ok
&= SecDbPerformRead(rdb
->db
, &localError
, ^(SecDbConnectionRef dbconn
) {
2380 ok
&= SecDbWithSQL(dbconn
, selectGroupIdSQL
, &localError
, ^bool(sqlite3_stmt
*selectGroupId
) {
2381 ok
&= SecDbBindBlob(selectGroupId
, 1, CFDataGetBytePtr(hash
), CFDataGetLength(hash
), SQLITE_TRANSIENT
, &localError
);
2382 ok
&= SecDbStep(dbconn
, selectGroupId
, &localError
, ^(bool *stopGroupId
) {
2383 groupId
= sqlite3_column_int64(selectGroupId
, 0);
2390 if (!ok
|| localError
) {
2391 secerror("_SecRevocationDbGroupIdForIssuerHash failed: %@", localError
);
2392 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationRead
, TAFatalError
,
2393 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
2395 (void) CFErrorPropagate(localError
, error
);
2399 static bool _SecRevocationDbApplyGroupDelete(SecRevocationDbRef rdb
, CFDataRef issuerHash
, CFErrorRef
*error
) {
2400 /* delete group associated with the given issuer;
2401 schema trigger will delete associated issuers, serials, and hashes. */
2402 __block
int64_t groupId
= -1;
2403 __block
bool ok
= true;
2404 __block CFErrorRef localError
= NULL
;
2406 groupId
= _SecRevocationDbGroupIdForIssuerHash(rdb
, issuerHash
, &localError
);
2407 require(!(groupId
< 0), errOut
);
2409 ok
&= SecDbPerformWrite(rdb
->db
, &localError
, ^(SecDbConnectionRef dbconn
) {
2410 ok
&= SecDbTransaction(dbconn
, kSecDbExclusiveTransactionType
, &localError
, ^(bool *commit
) {
2411 ok
&= SecDbWithSQL(dbconn
, deleteGroupRecordSQL
, &localError
, ^bool(sqlite3_stmt
*deleteResponse
) {
2412 ok
&= SecDbBindInt64(deleteResponse
, 1, groupId
, &localError
);
2413 /* Execute the delete statement. */
2414 ok
= ok
&& SecDbStep(dbconn
, deleteResponse
, &localError
, NULL
);
2421 if (!ok
|| localError
) {
2422 secerror("_SecRevocationDbApplyGroupDelete failed: %@", localError
);
2423 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationWrite
, TAFatalError
,
2424 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
2426 (void) CFErrorPropagate(localError
, error
);
2427 return (groupId
< 0) ? false : true;
2430 static bool _SecRevocationDbApplyGroupUpdate(SecRevocationDbRef rdb
, CFDictionaryRef dict
, CFErrorRef
*error
) {
2431 /* process one issuer group's update dictionary */
2432 int64_t groupId
= -1;
2433 CFErrorRef localError
= NULL
;
2435 CFArrayRef issuers
= (dict
) ? (CFArrayRef
)CFDictionaryGetValue(dict
, CFSTR("issuer-hash")) : NULL
;
2436 if (isArray(issuers
)) {
2437 CFIndex issuerIX
, issuerCount
= CFArrayGetCount(issuers
);
2438 /* while we have issuers and haven't found a matching group id */
2439 for (issuerIX
=0; issuerIX
<issuerCount
&& groupId
< 0; issuerIX
++) {
2440 CFDataRef hash
= (CFDataRef
)CFArrayGetValueAtIndex(issuers
, issuerIX
);
2441 if (!hash
) { continue; }
2442 groupId
= _SecRevocationDbGroupIdForIssuerHash(rdb
, hash
, &localError
);
2445 /* create or update the group entry */
2446 groupId
= _SecRevocationDbUpdateGroup(rdb
, groupId
, dict
, &localError
);
2448 secdebug("validupdate", "failed to get groupId");
2450 /* create or update issuer entries, now that we know the group id */
2451 _SecRevocationDbUpdateIssuers(rdb
, groupId
, issuers
, &localError
);
2452 /* create or update entries in serials or hashes tables */
2453 _SecRevocationDbUpdateIssuerData(rdb
, groupId
, dict
, &localError
);
2454 /* create or update entries in dates/names/policies tables */
2455 _SecRevocationDbUpdateIssuerConstraints(rdb
, groupId
, dict
, &localError
);
2458 (void) CFErrorPropagate(localError
, error
);
2459 return (groupId
> 0) ? true : false;
2462 static void _SecRevocationDbApplyUpdate(SecRevocationDbRef rdb
, CFDictionaryRef update
, CFIndex version
) {
2463 /* process entire update dictionary */
2464 if (!rdb
|| !update
) {
2465 secerror("_SecRevocationDbApplyUpdate failed: invalid args");
2469 __block CFDictionaryRef localUpdate
= (CFDictionaryRef
)CFRetainSafe(update
);
2470 __block CFErrorRef localError
= NULL
;
2472 CFTypeRef value
= NULL
;
2473 CFIndex deleteCount
= 0;
2474 CFIndex updateCount
= 0;
2476 rdb
->updateInProgress
= true;
2478 /* check whether this is a full update */
2479 value
= (CFBooleanRef
)CFDictionaryGetValue(update
, CFSTR("full"));
2480 if (isBoolean(value
) && CFBooleanGetValue((CFBooleanRef
)value
)) {
2481 /* clear the database before processing a full update */
2482 SecRevocationDbRemoveAllEntries();
2485 /* process 'delete' list */
2486 value
= (CFArrayRef
)CFDictionaryGetValue(localUpdate
, CFSTR("delete"));
2487 if (isArray(value
)) {
2488 deleteCount
= CFArrayGetCount((CFArrayRef
)value
);
2489 secdebug("validupdate", "processing %ld deletes", (long)deleteCount
);
2490 for (CFIndex deleteIX
=0; deleteIX
<deleteCount
; deleteIX
++) {
2491 CFDataRef issuerHash
= (CFDataRef
)CFArrayGetValueAtIndex((CFArrayRef
)value
, deleteIX
);
2492 if (isData(issuerHash
)) {
2493 (void)_SecRevocationDbApplyGroupDelete(rdb
, issuerHash
, &localError
);
2494 CFReleaseNull(localError
);
2496 secdebug("validupdate", "skipping delete %ld (hash is not a data value)", (long)deleteIX
);
2501 /* process 'update' list */
2502 value
= (CFArrayRef
)CFDictionaryGetValue(localUpdate
, CFSTR("update"));
2503 if (isArray(value
)) {
2504 updateCount
= CFArrayGetCount((CFArrayRef
)value
);
2505 secdebug("validupdate", "processing %ld updates", (long)updateCount
);
2506 for (CFIndex updateIX
=0; updateIX
<updateCount
; updateIX
++) {
2507 CFDictionaryRef dict
= (CFDictionaryRef
)CFArrayGetValueAtIndex((CFArrayRef
)value
, updateIX
);
2508 if (isDictionary(dict
)) {
2509 (void)_SecRevocationDbApplyGroupUpdate(rdb
, dict
, &localError
);
2510 CFReleaseNull(localError
);
2512 secdebug("validupdate", "skipping update %ld (not a dictionary)", (long)updateIX
);
2516 CFRelease(localUpdate
);
2519 _SecRevocationDbSetVersion(rdb
, version
);
2521 /* set db_version if not already set */
2522 int64_t db_version
= _SecRevocationDbGetSchemaVersion(rdb
, NULL
);
2523 if (db_version
<= 0) {
2524 _SecRevocationDbSetSchemaVersion(rdb
, kSecRevocationDbSchemaVersion
);
2527 /* set db_format if not already set */
2528 int64_t db_format
= _SecRevocationDbGetUpdateFormat(rdb
, NULL
);
2529 if (db_format
<= 0) {
2530 _SecRevocationDbSetUpdateFormat(rdb
, kSecRevocationDbUpdateFormat
);
2533 /* compact the db (must be done outside transaction scope) */
2534 (void)SecDbPerformWrite(rdb
->db
, &localError
, ^(SecDbConnectionRef dbconn
) {
2535 SecDbExec(dbconn
, CFSTR("VACUUM"), &localError
);
2536 CFReleaseNull(localError
);
2539 rdb
->updateInProgress
= false;
2542 static bool _SecRevocationDbSerialInGroup(SecRevocationDbRef rdb
,
2545 CFErrorRef
*error
) {
2546 __block
bool result
= false;
2547 __block
bool ok
= true;
2548 __block CFErrorRef localError
= NULL
;
2549 require(rdb
&& serial
, errOut
);
2550 ok
&= SecDbPerformRead(rdb
->db
, &localError
, ^(SecDbConnectionRef dbconn
) {
2551 ok
&= SecDbWithSQL(dbconn
, selectSerialRecordSQL
, &localError
, ^bool(sqlite3_stmt
*selectSerial
) {
2552 ok
&= SecDbBindInt64(selectSerial
, 1, groupId
, &localError
);
2553 ok
&= SecDbBindBlob(selectSerial
, 2, CFDataGetBytePtr(serial
),
2554 CFDataGetLength(serial
), SQLITE_TRANSIENT
, &localError
);
2555 ok
&= SecDbStep(dbconn
, selectSerial
, &localError
, ^(bool *stop
) {
2556 int64_t foundRowId
= (int64_t)sqlite3_column_int64(selectSerial
, 0);
2557 result
= (foundRowId
> 0);
2564 if (!ok
|| localError
) {
2565 secerror("_SecRevocationDbSerialInGroup failed: %@", localError
);
2566 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationRead
, TAFatalError
,
2567 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
2569 (void) CFErrorPropagate(localError
, error
);
2573 static bool _SecRevocationDbCertHashInGroup(SecRevocationDbRef rdb
,
2576 CFErrorRef
*error
) {
2577 __block
bool result
= false;
2578 __block
bool ok
= true;
2579 __block CFErrorRef localError
= NULL
;
2580 require(rdb
&& certHash
, errOut
);
2581 ok
&= SecDbPerformRead(rdb
->db
, &localError
, ^(SecDbConnectionRef dbconn
) {
2582 ok
&= SecDbWithSQL(dbconn
, selectHashRecordSQL
, &localError
, ^bool(sqlite3_stmt
*selectHash
) {
2583 ok
&= SecDbBindInt64(selectHash
, 1, groupId
, &localError
);
2584 ok
= SecDbBindBlob(selectHash
, 2, CFDataGetBytePtr(certHash
),
2585 CFDataGetLength(certHash
), SQLITE_TRANSIENT
, &localError
);
2586 ok
&= SecDbStep(dbconn
, selectHash
, &localError
, ^(bool *stop
) {
2587 int64_t foundRowId
= (int64_t)sqlite3_column_int64(selectHash
, 0);
2588 result
= (foundRowId
> 0);
2595 if (!ok
|| localError
) {
2596 secerror("_SecRevocationDbCertHashInGroup failed: %@", localError
);
2597 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationRead
, TAFatalError
,
2598 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
2600 (void) CFErrorPropagate(localError
, error
);
2604 static bool _SecRevocationDbSerialInFilter(SecRevocationDbRef rdb
,
2605 CFDataRef serialData
,
2606 CFDataRef xmlData
) {
2607 /* N-To-1 filter implementation.
2608 The 'xmlData' parameter is a flattened XML dictionary,
2609 containing 'xor' and 'params' keys. First order of
2610 business is to reconstitute the blob into components.
2612 bool result
= false;
2613 CFRetainSafe(xmlData
);
2614 CFDataRef propListData
= xmlData
;
2615 /* Expand data blob if needed */
2616 CFDataRef inflatedData
= copyInflatedData(propListData
);
2618 CFReleaseSafe(propListData
);
2619 propListData
= inflatedData
;
2621 CFDataRef
xor = NULL
;
2622 CFArrayRef params
= NULL
;
2623 CFPropertyListRef nto1
= CFPropertyListCreateWithData(kCFAllocatorDefault
, propListData
, 0, NULL
, NULL
);
2625 xor = (CFDataRef
)CFDictionaryGetValue((CFDictionaryRef
)nto1
, CFSTR("xor"));
2626 params
= (CFArrayRef
)CFDictionaryGetValue((CFDictionaryRef
)nto1
, CFSTR("params"));
2628 uint8_t *hash
= (xor) ? (uint8_t*)CFDataGetBytePtr(xor) : NULL
;
2629 CFIndex hashLen
= (hash
) ? CFDataGetLength(xor) : 0;
2630 uint8_t *serial
= (serialData
) ? (uint8_t*)CFDataGetBytePtr(serialData
) : NULL
;
2631 CFIndex serialLen
= (serial
) ? CFDataGetLength(serialData
) : 0;
2633 require(hash
&& serial
&& params
, errOut
);
2635 const uint32_t FNV_OFFSET_BASIS
= 2166136261;
2636 const uint32_t FNV_PRIME
= 16777619;
2637 bool notInHash
= false;
2638 CFIndex ix
, count
= CFArrayGetCount(params
);
2639 for (ix
= 0; ix
< count
; ix
++) {
2641 CFNumberRef cfnum
= (CFNumberRef
)CFArrayGetValueAtIndex(params
, ix
);
2642 if (!isNumber(cfnum
) ||
2643 !CFNumberGetValue(cfnum
, kCFNumberSInt32Type
, ¶m
)) {
2644 secinfo("validupdate", "error processing filter params at index %ld", (long)ix
);
2647 /* process one param */
2648 uint32_t hval
= FNV_OFFSET_BASIS
^ param
;
2649 CFIndex i
= serialLen
;
2651 hval
= ((hval
^ (serial
[--i
])) * FNV_PRIME
) & 0xFFFFFFFF;
2653 hval
= hval
% (hashLen
* 8);
2654 if ((hash
[hval
/8] & (1 << (hval
% 8))) == 0) {
2655 notInHash
= true; /* definitely not in hash */
2660 /* probabilistically might be in hash if we get here. */
2665 CFReleaseSafe(nto1
);
2666 CFReleaseSafe(propListData
);
2670 static SecValidInfoRef
_SecRevocationDbValidInfoForCertificate(SecRevocationDbRef rdb
,
2671 SecCertificateRef certificate
,
2672 CFDataRef issuerHash
,
2673 CFErrorRef
*error
) {
2674 __block CFErrorRef localError
= NULL
;
2675 __block SecValidInfoFlags flags
= 0;
2676 __block SecValidInfoFormat format
= kSecValidInfoFormatUnknown
;
2677 __block CFDataRef data
= NULL
;
2679 bool matched
= false;
2680 bool isOnList
= false;
2681 int64_t groupId
= 0;
2682 CFDataRef serial
= NULL
;
2683 CFDataRef certHash
= NULL
;
2684 CFDateRef notBeforeDate
= NULL
;
2685 CFDateRef notAfterDate
= NULL
;
2686 CFDataRef nameConstraints
= NULL
;
2687 CFDataRef policyConstraints
= NULL
;
2688 SecValidInfoRef result
= NULL
;
2690 require((serial
= SecCertificateCopySerialNumberData(certificate
, NULL
)) != NULL
, errOut
);
2691 require((certHash
= SecCertificateCopySHA256Digest(certificate
)) != NULL
, errOut
);
2692 require((groupId
= _SecRevocationDbGroupIdForIssuerHash(rdb
, issuerHash
, &localError
)) > 0, errOut
);
2694 /* Look up the group record to determine flags and format. */
2695 format
= _SecRevocationDbGetGroupFormat(rdb
, groupId
, &flags
, &data
, &localError
);
2697 if (format
== kSecValidInfoFormatUnknown
) {
2698 /* No group record found for this issuer. Don't return a SecValidInfoRef */
2701 else if (format
== kSecValidInfoFormatSerial
) {
2702 /* Look up certificate's serial number in the serials table. */
2703 matched
= _SecRevocationDbSerialInGroup(rdb
, serial
, groupId
, &localError
);
2705 else if (format
== kSecValidInfoFormatSHA256
) {
2706 /* Look up certificate's SHA-256 hash in the hashes table. */
2707 matched
= _SecRevocationDbCertHashInGroup(rdb
, certHash
, groupId
, &localError
);
2709 else if (format
== kSecValidInfoFormatNto1
) {
2710 /* Perform a Bloom filter match against the serial. If matched is false,
2711 then the cert is definitely not in the list. But if matched is true,
2712 we don't know for certain, so we would need to check OCSP. */
2713 matched
= _SecRevocationDbSerialInFilter(rdb
, serial
, data
);
2717 /* Found a specific match for this certificate. */
2718 secdebug("validupdate", "Valid db matched certificate: %@, format=%d, flags=0x%lx",
2719 certHash
, format
, flags
);
2723 /* If supplemental constraints are present for this issuer, then we always match. */
2724 if ((flags
& kSecValidInfoDateConstraints
) &&
2725 (_SecRevocationDbCopyDateConstraints(rdb
, groupId
, ¬BeforeDate
, ¬AfterDate
, &localError
))) {
2726 secdebug("validupdate", "Valid db matched supplemental date constraints for groupId %lld: nb=%@, na=%@",
2727 (long long)groupId
, notBeforeDate
, notAfterDate
);
2731 /* Return SecValidInfo for certificates for which an issuer entry is found. */
2732 result
= SecValidInfoCreate(format
, flags
, isOnList
,
2733 certHash
, issuerHash
, /*anchorHash*/ NULL
,
2734 notBeforeDate
, notAfterDate
,
2735 nameConstraints
, policyConstraints
);
2737 if (result
&& SecIsAppleTrustAnchor(certificate
, 0)) {
2738 /* Prevent a catch-22. */
2739 secdebug("validupdate", "Valid db match for Apple trust anchor: %@, format=%d, flags=0x%lx",
2740 certHash
, format
, flags
);
2741 SecValidInfoRelease(result
);
2746 (void) CFErrorPropagate(localError
, error
);
2747 CFReleaseSafe(data
);
2748 CFReleaseSafe(certHash
);
2749 CFReleaseSafe(serial
);
2750 CFReleaseSafe(notBeforeDate
);
2751 CFReleaseSafe(notAfterDate
);
2752 CFReleaseSafe(nameConstraints
);
2753 CFReleaseSafe(policyConstraints
);
2757 static SecValidInfoRef
_SecRevocationDbCopyMatching(SecRevocationDbRef db
,
2758 SecCertificateRef certificate
,
2759 SecCertificateRef issuer
) {
2760 SecValidInfoRef result
= NULL
;
2761 CFErrorRef error
= NULL
;
2762 CFDataRef issuerHash
= NULL
;
2764 require(certificate
&& issuer
, errOut
);
2765 require(issuerHash
= SecCertificateCopySHA256Digest(issuer
), errOut
);
2767 result
= _SecRevocationDbValidInfoForCertificate(db
, certificate
, issuerHash
, &error
);
2770 CFReleaseSafe(issuerHash
);
2771 CFReleaseSafe(error
);
2775 static dispatch_queue_t
_SecRevocationDbGetUpdateQueue(SecRevocationDbRef rdb
) {
2776 return (rdb
) ? rdb
->update_queue
: NULL
;
2780 /* Given a valid update dictionary, insert/replace or delete records
2781 in the revocation database. (This function is expected to be called only
2782 by the database maintainer, normally the system instance of trustd.)
2784 void SecRevocationDbApplyUpdate(CFDictionaryRef update
, CFIndex version
) {
2785 SecRevocationDbWith(^(SecRevocationDbRef db
) {
2786 _SecRevocationDbApplyUpdate(db
, update
, version
);
2790 /* Update the database schema, insert missing tables and replace triggers.
2791 (This function is expected to be called only by the database maintainer,
2792 normally the system instance of trustd.)
2794 bool SecRevocationDbUpdateSchema(void) {
2795 __block
bool result
= false;
2796 SecRevocationDbWith(^(SecRevocationDbRef db
) {
2797 result
= _SecRevocationDbUpdateSchema(db
);
2802 /* Set the schema version for the revocation database.
2803 (This function is expected to be called only by the database maintainer,
2804 normally the system instance of trustd.)
2806 bool SecRevocationDbSetSchemaVersion(CFIndex db_version
) {
2807 __block
bool result
= false;
2808 SecRevocationDbWith(^(SecRevocationDbRef db
) {
2809 result
= _SecRevocationDbSetSchemaVersion(db
, db_version
);
2814 /* Set the current version for the revocation database.
2815 (This function is expected to be called only by the database maintainer,
2816 normally the system instance of trustd.)
2818 bool SecRevocationDbSetVersion(CFIndex version
) {
2819 __block
bool result
= false;
2820 SecRevocationDbWith(^(SecRevocationDbRef db
) {
2821 _SecRevocationDbSetVersion(db
, version
);
2826 /* Set the update format for the revocation database.
2827 (This function is expected to be called only by the database maintainer,
2828 normally the system instance of trustd.)
2830 void SecRevocationDbSetUpdateFormat(CFIndex db_format
) {
2831 SecRevocationDbWith(^(SecRevocationDbRef db
) {
2832 _SecRevocationDbSetUpdateFormat(db
, db_format
);
2836 /* Set the update source for the revocation database.
2837 (This function is expected to be called only by the database
2838 maintainer, normally the system instance of trustd. If the
2839 caller does not have write access, this is a no-op.)
2841 void SecRevocationDbSetUpdateSource(CFStringRef updateSource
) {
2842 SecRevocationDbWith(^(SecRevocationDbRef db
) {
2843 _SecRevocationDbSetUpdateSource(db
, updateSource
);
2847 /* Return the update source as a retained CFStringRef.
2848 If the value cannot be obtained, NULL is returned.
2850 CFStringRef
SecRevocationDbCopyUpdateSource(void) {
2851 __block CFStringRef result
= NULL
;
2852 SecRevocationDbWith(^(SecRevocationDbRef db
) {
2853 result
= _SecRevocationDbCopyUpdateSource(db
, NULL
);
2858 /* Set the next update value for the revocation database.
2859 (This function is expected to be called only by the database
2860 maintainer, normally the system instance of trustd. If the
2861 caller does not have write access, this is a no-op.)
2863 void SecRevocationDbSetNextUpdateTime(CFAbsoluteTime nextUpdate
) {
2864 SecRevocationDbWith(^(SecRevocationDbRef db
) {
2865 _SecRevocationDbSetNextUpdateTime(db
, nextUpdate
);
2869 /* Return the next update value as a CFAbsoluteTime.
2870 If the value cannot be obtained, -1 is returned.
2872 CFAbsoluteTime
SecRevocationDbGetNextUpdateTime(void) {
2873 __block CFAbsoluteTime result
= -1;
2874 SecRevocationDbWith(^(SecRevocationDbRef db
) {
2875 result
= _SecRevocationDbGetNextUpdateTime(db
, NULL
);
2880 /* Return the serial background queue for database updates.
2881 If the queue cannot be obtained, NULL is returned.
2883 dispatch_queue_t
SecRevocationDbGetUpdateQueue(void) {
2884 __block dispatch_queue_t result
= NULL
;
2885 SecRevocationDbWith(^(SecRevocationDbRef db
) {
2886 result
= _SecRevocationDbGetUpdateQueue(db
);
2891 /* Remove all entries in the revocation database and reset its version to 0.
2892 (This function is expected to be called only by the database maintainer,
2893 normally the system instance of trustd.)
2895 void SecRevocationDbRemoveAllEntries(void) {
2896 SecRevocationDbWith(^(SecRevocationDbRef db
) {
2897 _SecRevocationDbRemoveAllEntries(db
);
2901 /* Release all connections to the revocation database.
2903 void SecRevocationDbReleaseAllConnections(void) {
2904 SecRevocationDbWith(^(SecRevocationDbRef db
) {
2905 SecDbReleaseAllConnections((db
) ? db
->db
: NULL
);
2909 /* === SecRevocationDb API === */
2911 /* Given a certificate and its issuer, returns a SecValidInfoRef if the
2912 valid database contains matching info; otherwise returns NULL.
2913 Caller must release the returned SecValidInfoRef by calling
2914 SecValidInfoRelease when finished.
2916 SecValidInfoRef
SecRevocationDbCopyMatching(SecCertificateRef certificate
,
2917 SecCertificateRef issuer
) {
2918 __block SecValidInfoRef result
= NULL
;
2919 SecRevocationDbWith(^(SecRevocationDbRef db
) {
2920 result
= _SecRevocationDbCopyMatching(db
, certificate
, issuer
);
2925 /* Return the current version of the revocation database.
2926 A version of 0 indicates an empty database which must be populated.
2927 If the version cannot be obtained, -1 is returned.
2929 CFIndex
SecRevocationDbGetVersion(void) {
2930 __block CFIndex result
= -1;
2931 SecRevocationDbWith(^(SecRevocationDbRef db
) {
2932 result
= (CFIndex
)_SecRevocationDbGetVersion(db
, NULL
);
2937 /* Return the current schema version of the revocation database.
2938 A version of 0 indicates an empty database which must be populated.
2939 If the schema version cannot be obtained, -1 is returned.
2941 CFIndex
SecRevocationDbGetSchemaVersion(void) {
2942 __block CFIndex result
= -1;
2943 SecRevocationDbWith(^(SecRevocationDbRef db
) {
2944 result
= (CFIndex
)_SecRevocationDbGetSchemaVersion(db
, NULL
);
2949 /* Return the current update format of the revocation database.
2950 A version of 0 indicates the format was unknown.
2951 If the update format cannot be obtained, -1 is returned.
2953 CFIndex
SecRevocationDbGetUpdateFormat(void) {
2954 __block CFIndex result
= -1;
2955 SecRevocationDbWith(^(SecRevocationDbRef db
) {
2956 result
= (CFIndex
)_SecRevocationDbGetUpdateFormat(db
, NULL
);