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/OTATrustUtilities.h>
31 #include <securityd/SecRevocationNetworking.h>
32 #include <securityd/SecTrustLoggingServer.h>
33 #include <Security/SecCertificateInternal.h>
34 #include <Security/SecCMS.h>
35 #include <Security/CMSDecoder.h>
36 #include <Security/SecFramework.h>
37 #include <Security/SecInternal.h>
38 #include <Security/SecPolicyPriv.h>
39 #include <AssertMacros.h>
41 #include <stdatomic.h>
47 #include <dispatch/dispatch.h>
51 #include "utilities/debugging.h"
52 #include "utilities/sec_action.h"
53 #include "utilities/sqlutils.h"
54 #include "utilities/SecAppleAnchorPriv.h"
55 #include "utilities/iOSforOSX.h"
56 #include <utilities/SecCFError.h>
57 #include <utilities/SecCFRelease.h>
58 #include <utilities/SecCFWrappers.h>
59 #include <utilities/SecDb.h>
60 #include <utilities/SecFileLocations.h>
63 #include <malloc/malloc.h>
64 #include <xpc/activity.h>
65 #include <xpc/private.h>
66 #include <os/transaction_private.h>
67 #include <os/variant_private.h>
70 #include <CFNetwork/CFHTTPMessage.h>
71 #include <CoreFoundation/CFURL.h>
72 #include <CoreFoundation/CFUtilities.h>
74 const CFStringRef kValidUpdateProdServer
= CFSTR("valid.apple.com");
75 const CFStringRef kValidUpdateCarryServer
= CFSTR("valid.apple.com/carry");
77 static CFStringRef kSecPrefsDomain
= CFSTR("com.apple.security");
78 static CFStringRef kUpdateServerKey
= CFSTR("ValidUpdateServer");
79 static CFStringRef kUpdateEnabledKey
= CFSTR("ValidUpdateEnabled");
80 static CFStringRef kUpdateIntervalKey
= CFSTR("ValidUpdateInterval");
81 static CFStringRef kBoolTrueKey
= CFSTR("1");
82 static CFStringRef kBoolFalseKey
= CFSTR("0");
84 /* constant length of boolean string keys */
85 #define BOOL_STRING_KEY_LENGTH 1
87 typedef CF_OPTIONS(CFOptionFlags
, SecValidInfoFlags
) {
88 kSecValidInfoComplete
= 1u << 0,
89 kSecValidInfoCheckOCSP
= 1u << 1,
90 kSecValidInfoKnownOnly
= 1u << 2,
91 kSecValidInfoRequireCT
= 1u << 3,
92 kSecValidInfoAllowlist
= 1u << 4,
93 kSecValidInfoNoCACheck
= 1u << 5,
94 kSecValidInfoOverridable
= 1u << 6,
95 kSecValidInfoDateConstraints
= 1u << 7,
96 kSecValidInfoNameConstraints
= 1u << 8,
97 kSecValidInfoPolicyConstraints
= 1u << 9,
98 kSecValidInfoNoCAv2Check
= 1u << 10,
101 /* minimum update interval */
102 #define kSecMinUpdateInterval (60.0 * 5)
104 /* standard update interval */
105 #define kSecStdUpdateInterval (60.0 * 60)
107 /* maximum allowed interval */
108 #define kSecMaxUpdateInterval (60.0 * 60 * 24 * 7)
110 #define kSecRevocationBasePath "/Library/Keychains/crls"
111 #define kSecRevocationCurUpdateFile "update-current"
112 #define kSecRevocationDbFileName "valid.sqlite3"
113 #define kSecRevocationDbReplaceFile ".valid_replace"
115 #define isDbOwner SecOTAPKIIsSystemTrustd
117 #define kSecRevocationDbChanged "com.apple.trustd.valid.db-changed"
119 /* database schema version
121 v2 = fix for group entry transitions
122 v3 = handle optional entries in update dictionaries
123 v4 = add db_format and db_source entries
124 v5 = add date constraints table, with updated group flags
125 v6 = explicitly set autovacuum and journal modes at db creation
127 Note: kSecRevocationDbMinSchemaVersion is the lowest version whose
128 results can be used. This allows revocation results to be obtained
129 from an existing db before the next update interval occurs, at which
130 time we'll update to the current version (kSecRevocationDbSchemaVersion).
132 #define kSecRevocationDbSchemaVersion 6 /* current version we support */
133 #define kSecRevocationDbMinSchemaVersion 6 /* minimum version we can use */
135 /* update file format
138 kSecValidUpdateFormatG1
= 1, /* initial version */
139 kSecValidUpdateFormatG2
= 2, /* signed content, single plist */
140 kSecValidUpdateFormatG3
= 3 /* signed content, multiple plists */
143 #define kSecRevocationDbUpdateFormat 3 /* current version we support */
144 #define kSecRevocationDbMinUpdateFormat 2 /* minimum version we can use */
146 #define kSecRevocationDbCacheSize 100
148 typedef struct __SecRevocationDb
*SecRevocationDbRef
;
149 struct __SecRevocationDb
{
151 dispatch_queue_t update_queue
;
152 bool updateInProgress
;
153 bool unsupportedVersion
;
155 CFMutableArrayRef info_cache_list
;
156 CFMutableDictionaryRef info_cache
;
157 os_unfair_lock info_cache_lock
;
160 typedef struct __SecRevocationDbConnection
*SecRevocationDbConnectionRef
;
161 struct __SecRevocationDbConnection
{
162 SecRevocationDbRef db
;
163 SecDbConnectionRef dbconn
;
164 CFIndex precommitVersion
;
165 CFIndex precommitDbVersion
;
169 bool SecRevocationDbVerifyUpdate(void *update
, CFIndex length
);
170 bool SecRevocationDbIngestUpdate(SecRevocationDbConnectionRef dbc
, CFDictionaryRef update
, CFIndex chunkVersion
, CFIndex
*outVersion
, CFErrorRef
*error
);
171 bool _SecRevocationDbApplyUpdate(SecRevocationDbConnectionRef dbc
, CFDictionaryRef update
, CFIndex version
, CFErrorRef
*error
);
172 CFAbsoluteTime
SecRevocationDbComputeNextUpdateTime(CFIndex updateInterval
);
173 bool SecRevocationDbUpdateSchema(SecRevocationDbRef rdb
);
174 CFIndex
SecRevocationDbGetUpdateFormat(void);
175 bool _SecRevocationDbSetUpdateSource(SecRevocationDbConnectionRef dbc
, CFStringRef source
, CFErrorRef
*error
);
176 bool SecRevocationDbSetUpdateSource(SecRevocationDbRef rdb
, CFStringRef source
);
177 CFStringRef
SecRevocationDbCopyUpdateSource(void);
178 bool SecRevocationDbSetNextUpdateTime(CFAbsoluteTime nextUpdate
, CFErrorRef
*error
);
179 CFAbsoluteTime
SecRevocationDbGetNextUpdateTime(void);
180 dispatch_queue_t
SecRevocationDbGetUpdateQueue(void);
181 bool _SecRevocationDbRemoveAllEntries(SecRevocationDbConnectionRef dbc
, CFErrorRef
*error
);
182 void SecRevocationDbReleaseAllConnections(void);
183 static void SecRevocationDbWith(void(^dbJob
)(SecRevocationDbRef db
));
184 static bool SecRevocationDbPerformWrite(SecRevocationDbRef rdb
, CFErrorRef
*error
, bool(^writeJob
)(SecRevocationDbConnectionRef dbc
, CFErrorRef
*blockError
));
185 static SecValidInfoFormat
_SecRevocationDbGetGroupFormat(SecRevocationDbConnectionRef dbc
, int64_t groupId
, SecValidInfoFlags
*flags
, CFDataRef
*data
, CFErrorRef
*error
);
186 static bool _SecRevocationDbSetVersion(SecRevocationDbConnectionRef dbc
, CFIndex version
, CFErrorRef
*error
);
187 static int64_t _SecRevocationDbGetVersion(SecRevocationDbConnectionRef dbc
, CFErrorRef
*error
);
188 static int64_t _SecRevocationDbGetSchemaVersion(SecRevocationDbRef rdb
, SecRevocationDbConnectionRef dbc
, CFErrorRef
*error
);
189 static void SecRevocationDbResetCaches(void);
190 static SecRevocationDbConnectionRef
SecRevocationDbConnectionInit(SecRevocationDbRef db
, SecDbConnectionRef dbconn
, CFErrorRef
*error
);
193 static CFDataRef
copyInflatedData(CFDataRef data
) {
198 memset(&zs
, 0, sizeof(zs
));
199 /* 32 is a magic value which enables automatic header detection
200 of gzip or zlib compressed data. */
201 if (inflateInit2(&zs
, 32+MAX_WBITS
) != Z_OK
) {
204 zs
.next_in
= (UInt8
*)(CFDataGetBytePtr(data
));
205 zs
.avail_in
= (uInt
)CFDataGetLength(data
);
207 CFMutableDataRef outData
= CFDataCreateMutable(NULL
, 0);
211 CFIndex buf_sz
= malloc_good_size(zs
.avail_in
? zs
.avail_in
: 1024 * 4);
212 unsigned char *buf
= malloc(buf_sz
);
215 zs
.next_out
= (Bytef
*)buf
;
216 zs
.avail_out
= (uInt
)buf_sz
;
217 rc
= inflate(&zs
, 0);
218 CFIndex outLen
= CFDataGetLength(outData
);
219 if (outLen
< (CFIndex
)zs
.total_out
) {
220 CFDataAppendBytes(outData
, (const UInt8
*)buf
, (CFIndex
)zs
.total_out
- outLen
);
222 } while (rc
== Z_OK
);
229 if (rc
!= Z_STREAM_END
) {
230 CFReleaseSafe(outData
);
233 return (CFDataRef
)outData
;
236 static CFDataRef
copyDeflatedData(CFDataRef data
) {
241 memset(&zs
, 0, sizeof(zs
));
242 if (deflateInit(&zs
, Z_BEST_COMPRESSION
) != Z_OK
) {
245 zs
.next_in
= (UInt8
*)(CFDataGetBytePtr(data
));
246 zs
.avail_in
= (uInt
)CFDataGetLength(data
);
248 CFMutableDataRef outData
= CFDataCreateMutable(NULL
, 0);
252 CFIndex buf_sz
= malloc_good_size(zs
.avail_in
? zs
.avail_in
: 1024 * 4);
253 unsigned char *buf
= malloc(buf_sz
);
254 int rc
= Z_BUF_ERROR
;
256 zs
.next_out
= (Bytef
*)buf
;
257 zs
.avail_out
= (uInt
)buf_sz
;
258 rc
= deflate(&zs
, Z_FINISH
);
260 if (rc
== Z_OK
|| rc
== Z_STREAM_END
) {
261 CFIndex buf_used
= buf_sz
- zs
.avail_out
;
262 CFDataAppendBytes(outData
, (const UInt8
*)buf
, buf_used
);
264 else if (rc
== Z_BUF_ERROR
) {
266 buf_sz
= malloc_good_size(buf_sz
* 2);
267 buf
= malloc(buf_sz
);
269 rc
= Z_OK
; /* try again with larger buffer */
272 } while (rc
== Z_OK
&& zs
.avail_in
);
279 if (rc
!= Z_STREAM_END
) {
280 CFReleaseSafe(outData
);
283 return (CFDataRef
)outData
;
286 /* Read file opens the file, mmaps it and then closes the file. */
287 int readValidFile(const char *fileName
,
288 CFDataRef
*bytes
) { // mmapped and returned -- must be munmapped!
290 const uint8_t *buf
= NULL
;
295 fd
= open(fileName
, O_RDONLY
);
296 if (fd
< 0) { return errno
; }
297 rtn
= fstat(fd
, &sb
);
298 if (rtn
) { goto errOut
; }
299 if (sb
.st_size
> (off_t
) ((UINT32_MAX
>> 1)-1)) {
303 size
= (size_t)sb
.st_size
;
305 buf
= mmap(NULL
, size
, PROT_READ
, MAP_PRIVATE
, fd
, 0);
306 if (!buf
|| buf
== MAP_FAILED
) {
308 secerror("unable to map %s (errno %d)", fileName
, rtn
);
312 *bytes
= CFDataCreateWithBytesNoCopy(NULL
, buf
, size
, kCFAllocatorNull
);
317 CFReleaseNull(*bytes
);
319 int unmap_err
= munmap((void *)buf
, size
);
320 if (unmap_err
!= 0) {
321 secerror("unable to unmap %ld bytes at %p (error %d)", (long)size
, buf
, rtn
);
328 static bool removeFileWithSuffix(const char *basepath
, const char *suffix
) {
331 asprintf(&path
, "%s%s", basepath
, suffix
);
333 if (remove(path
) == -1) {
335 if (error
== ENOENT
) {
336 result
= true; // not an error if the file did not exist
338 secnotice("validupdate", "remove (%s): %s", path
, strerror(error
));
348 static CFDataRef CF_RETURNS_RETAINED
cfToHexData(CFDataRef data
, bool prependWildcard
) {
349 if (!isData(data
)) { return NULL
; }
350 CFIndex len
= CFDataGetLength(data
) * 2;
351 CFMutableStringRef hex
= CFStringCreateMutable(NULL
, len
+1);
352 static const char* digits
[]={
353 "0","1","2","3","4","5","6","7","8","9","A","B","C","D","E","F"};
354 if (prependWildcard
) {
355 CFStringAppendCString(hex
, "%", 1);
357 const uint8_t* p
= CFDataGetBytePtr(data
);
358 for (CFIndex i
= 0; i
< CFDataGetLength(data
); i
++) {
359 CFStringAppendCString(hex
, digits
[p
[i
] >> 4], 1);
360 CFStringAppendCString(hex
, digits
[p
[i
] & 0xf], 1);
362 CFDataRef result
= CFStringCreateExternalRepresentation(NULL
, hex
, kCFStringEncodingUTF8
, 0);
368 // MARK: SecValidUpdate
370 /* ======================================================================
372 ======================================================================*/
374 CFAbsoluteTime gUpdateStarted
= 0.0;
375 CFAbsoluteTime gNextUpdate
= 0.0;
376 static CFIndex gUpdateInterval
= 0;
377 static CFIndex gLastVersion
= 0;
380 1. The length of the signed data, as a 4-byte integer in network byte order.
381 2. The signed data, which consists of:
382 a. A 4-byte integer in network byte order, the count of plists to follow; and then for each plist:
383 i. A 4-byte integer, the length of each plist
384 ii. A plist, in binary form
385 b. There may be other data after the plists in the signed data, described by a future version of this specification.
386 3. The length of the following CMS blob, as a 4-byte integer in network byte order.
387 4. A detached CMS signature of the signed data described above.
388 5. There may be additional data after the CMS blob, described by a future version of this specification.
390 Note: the difference between g2 and g3 format is the addition of the 4-byte count in (2a).
392 static bool SecValidUpdateProcessData(SecRevocationDbConnectionRef dbc
, CFIndex format
, CFDataRef updateData
, CFErrorRef
*error
) {
394 if (!updateData
|| format
< 2) {
395 SecError(errSecParam
, error
, CFSTR("SecValidUpdateProcessData: invalid update format"));
399 CFIndex interval
= 0;
400 const UInt8
* p
= CFDataGetBytePtr(updateData
);
401 size_t bytesRemaining
= (p
) ? (size_t)CFDataGetLength(updateData
) : 0;
402 /* make sure there is enough data to contain length and count */
403 if (bytesRemaining
< ((CFIndex
)sizeof(uint32_t) * 2)) {
404 secinfo("validupdate", "Skipping property list creation (length %ld is too short)", (long)bytesRemaining
);
405 SecError(errSecParam
, error
, CFSTR("SecValidUpdateProcessData: data length is too short"));
408 /* get length of signed data */
409 uint32_t dataLength
= OSSwapInt32(*((uint32_t *)p
));
410 bytesRemaining
-= sizeof(uint32_t);
411 p
+= sizeof(uint32_t);
413 /* get plist count (G3 format and later) */
414 uint32_t plistCount
= 1;
415 uint32_t plistTotal
= 1;
416 if (format
> kSecValidUpdateFormatG2
) {
417 plistCount
= OSSwapInt32(*((uint32_t *)p
));
418 plistTotal
= plistCount
;
419 bytesRemaining
-= sizeof(uint32_t);
420 p
+= sizeof(uint32_t);
422 if (dataLength
> bytesRemaining
) {
423 secinfo("validupdate", "Skipping property list creation (dataLength=%ld, bytesRemaining=%ld)",
424 (long)dataLength
, (long)bytesRemaining
);
425 SecError(errSecParam
, error
, CFSTR("SecValidUpdateProcessData: data longer than expected"));
429 /* process each chunked plist */
431 CFErrorRef localError
= NULL
;
432 uint32_t plistProcessed
= 0;
433 while (plistCount
> 0 && bytesRemaining
> 0) {
434 CFPropertyListRef propertyList
= NULL
;
435 uint32_t plistLength
= dataLength
;
436 if (format
> kSecValidUpdateFormatG2
) {
437 plistLength
= OSSwapInt32(*((uint32_t *)p
));
438 bytesRemaining
-= sizeof(uint32_t);
439 p
+= sizeof(uint32_t);
444 if (plistLength
<= bytesRemaining
) {
445 CFDataRef data
= CFDataCreateWithBytesNoCopy(NULL
, p
, plistLength
, kCFAllocatorNull
);
446 propertyList
= CFPropertyListCreateWithData(NULL
, data
, kCFPropertyListImmutable
, NULL
, NULL
);
449 if (isDictionary(propertyList
)) {
450 secdebug("validupdate", "Ingesting plist chunk %u of %u, length: %u",
451 plistProcessed
, plistTotal
, plistLength
);
452 CFIndex curVersion
= -1;
453 ok
= ok
&& SecRevocationDbIngestUpdate(dbc
, (CFDictionaryRef
)propertyList
, version
, &curVersion
, &localError
);
454 if (plistProcessed
== 1) {
455 dbc
->precommitVersion
= version
= curVersion
;
456 // get server-provided interval
457 CFTypeRef value
= (CFNumberRef
)CFDictionaryGetValue((CFDictionaryRef
)propertyList
,
458 CFSTR("check-again"));
459 if (isNumber(value
)) {
460 CFNumberGetValue((CFNumberRef
)value
, kCFNumberCFIndexType
, &interval
);
463 if (ok
&& curVersion
< 0) {
464 plistCount
= 0; // we already had this version; skip remaining plists
468 secinfo("validupdate", "Failed to deserialize update chunk %u of %u",
469 plistProcessed
, plistTotal
);
470 SecError(errSecParam
, error
, CFSTR("SecValidUpdateProcessData: failed to get update chunk"));
471 if (plistProcessed
== 1) {
472 gNextUpdate
= SecRevocationDbComputeNextUpdateTime(0);
475 /* All finished with this property list */
476 CFReleaseSafe(propertyList
);
478 bytesRemaining
-= plistLength
;
482 if (ok
&& version
> 0) {
483 secdebug("validupdate", "Update received: v%ld", (long)version
);
484 gLastVersion
= version
;
485 gNextUpdate
= SecRevocationDbComputeNextUpdateTime(interval
);
486 secdebug("validupdate", "Next update time: %f", gNextUpdate
);
490 (void) CFErrorPropagate(localError
, error
);
494 void SecValidUpdateVerifyAndIngest(CFDataRef updateData
, CFStringRef updateServer
, bool fullUpdate
) {
496 secnotice("validupdate", "invalid update data");
499 /* Verify CMS signature on signed data */
500 if (!SecRevocationDbVerifyUpdate((void *)CFDataGetBytePtr(updateData
), CFDataGetLength(updateData
))) {
501 secerror("failed to verify valid update");
502 TrustdHealthAnalyticsLogErrorCode(TAEventValidUpdate
, TAFatalError
, errSecVerifyFailed
);
505 /* Read current update source from database. */
506 CFStringRef dbSource
= SecRevocationDbCopyUpdateSource();
507 if (dbSource
&& updateServer
&& (kCFCompareEqualTo
!= CFStringCompare(dbSource
, updateServer
,
508 kCFCompareCaseInsensitive
))) {
509 secnotice("validupdate", "switching db source from \"%@\" to \"%@\"", dbSource
, updateServer
);
511 CFReleaseNull(dbSource
);
513 /* Ingest the update. This is now performed under a single immediate write transaction,
514 so other writers are blocked (but not other readers), and the changes can be rolled back
515 in their entirety if any error occurs. */
516 __block
bool ok
= true;
517 __block CFErrorRef localError
= NULL
;
518 __block SecRevocationDbConnectionRef dbc
= NULL
;
519 SecRevocationDbWith(^(SecRevocationDbRef rdb
) {
520 ok
&= SecRevocationDbPerformWrite(rdb
, &localError
, ^bool(SecRevocationDbConnectionRef dbc
, CFErrorRef
*blockError
) {
522 /* Must completely replace existing database contents */
523 secdebug("validupdate", "starting to process full update; clearing database");
524 ok
= ok
&& _SecRevocationDbRemoveAllEntries(dbc
,blockError
);
525 ok
= ok
&& _SecRevocationDbSetUpdateSource(dbc
, updateServer
, blockError
);
527 dbc
->precommitVersion
= 0;
528 dbc
->fullUpdate
= true;
531 ok
= ok
&& SecValidUpdateProcessData(dbc
, kSecValidUpdateFormatG3
, updateData
, blockError
);
533 secerror("failed to process valid update: %@", blockError
? *blockError
: NULL
);
534 TrustdHealthAnalyticsLogErrorCode(TAEventValidUpdate
, TAFatalError
, errSecDecode
);
536 TrustdHealthAnalyticsLogSuccess(TAEventValidUpdate
);
541 rdb
->changed
= false;
542 /* signal other trustd instances that the database has been updated */
543 notify_post(kSecRevocationDbChanged
);
547 /* remember next update time in case of restart (separate write transaction) */
548 (void) SecRevocationDbSetNextUpdateTime(gNextUpdate
, NULL
);
553 CFReleaseSafe(localError
);
556 static bool SecValidUpdateForceReplaceDatabase(void) {
559 // write semaphore file that we will pick up when we next launch
560 char *semPathBuf
= NULL
;
561 asprintf(&semPathBuf
, "%s/%s", kSecRevocationBasePath
, kSecRevocationDbReplaceFile
);
564 int fd
= open(semPathBuf
, O_WRONLY
| O_CREAT
, DEFFILEMODE
);
565 if (fd
== -1 || fstat(fd
, &sb
)) {
566 secnotice("validupdate", "unable to write %s", semPathBuf
);
576 // exit as gracefully as possible so we can replace the database
577 secnotice("validupdate", "process exiting to replace db file");
578 dispatch_after(dispatch_time(DISPATCH_TIME_NOW
, 3ull*NSEC_PER_SEC
), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT
, 0), ^{
579 xpc_transaction_exit_clean();
585 static bool SecValidUpdateSatisfiedLocally(CFStringRef server
, CFIndex version
, bool safeToReplace
) {
586 __block
bool result
= false;
587 SecOTAPKIRef otapkiRef
= NULL
;
588 bool relaunching
= false;
589 static int sNumLocalUpdates
= 0;
591 // if we've replaced the database with a local asset twice in a row,
592 // something is wrong with it. Get this update from the server.
593 if (sNumLocalUpdates
> 1) {
594 secdebug("validupdate", "%d consecutive db resets, ignoring local asset", sNumLocalUpdates
);
598 // if a non-production server is specified, we will not be able to use a
599 // local production asset since its update sequence will be different.
600 if (kCFCompareEqualTo
!= CFStringCompare(server
, kValidUpdateProdServer
,
601 kCFCompareCaseInsensitive
)) {
602 secdebug("validupdate", "non-production server specified, ignoring local asset");
606 // check static database asset(s)
607 otapkiRef
= SecOTAPKICopyCurrentOTAPKIRef();
611 CFIndex assetVersion
= SecOTAPKIGetValidSnapshotVersion(otapkiRef
);
612 CFIndex assetFormat
= SecOTAPKIGetValidSnapshotFormat(otapkiRef
);
613 // version <= 0 means the database is invalid or empty.
614 // version > 0 means we have some version, but we need to see if a
615 // newer version is available as a local asset.
616 if (assetVersion
<= version
|| assetFormat
< kSecValidUpdateFormatG3
) {
617 // asset is not newer than ours, or its version is unknown
621 // replace database only if safe to do so (i.e. called at startup)
622 if (!safeToReplace
) {
623 relaunching
= SecValidUpdateForceReplaceDatabase();
627 // try to copy uncompressed database asset, if available
628 const char *validDbPathBuf
= SecOTAPKIGetValidDatabaseSnapshot(otapkiRef
);
629 if (validDbPathBuf
) {
630 WithPathInRevocationInfoDirectory(CFSTR(kSecRevocationDbFileName
), ^(const char *path
) {
631 secdebug("validupdate", "will copy data from \"%s\"", validDbPathBuf
);
632 copyfile_state_t state
= copyfile_state_alloc();
633 int retval
= copyfile(validDbPathBuf
, path
, state
, COPYFILE_DATA
);
634 copyfile_state_free(state
);
636 secnotice("validupdate", "copyfile error %d", retval
);
644 CFReleaseNull(otapkiRef
);
647 gLastVersion
= SecRevocationDbGetVersion();
648 // note: snapshot should already have latest schema and production source,
649 // but set it here anyway so we don't keep trying to replace the db.
650 SecRevocationDbWith(^(SecRevocationDbRef db
) {
651 (void)SecRevocationDbSetUpdateSource(db
, server
);
652 (void)SecRevocationDbUpdateSchema(db
);
655 secdebug("validupdate", "local update to g%ld/v%ld complete at %f",
656 (long)SecRevocationDbGetUpdateFormat(), (long)gLastVersion
,
657 (double)CFAbsoluteTimeGetCurrent());
659 sNumLocalUpdates
= 0; // reset counter
662 // request is locally satisfied; don't schedule a network update
668 static bool SecValidUpdateSchedule(bool updateEnabled
, CFStringRef server
, CFIndex version
) {
669 /* Check if we have a later version available locally */
670 if (SecValidUpdateSatisfiedLocally(server
, version
, false)) {
674 /* If update not permitted return */
675 if (!updateEnabled
) {
679 #if !TARGET_OS_BRIDGE
680 /* Schedule as a maintenance task */
681 secdebug("validupdate", "will fetch v%lu from \"%@\"", (unsigned long)version
, server
);
682 return SecValidUpdateRequest(SecRevocationDbGetUpdateQueue(), server
, version
);
688 static CFStringRef
SecRevocationDbGetDefaultServer(void) {
690 return kValidUpdateCarryServer
;
691 #else // !RC_SEED_BUILD
692 CFStringRef defaultServer
= kValidUpdateProdServer
;
693 if (os_variant_has_internal_diagnostics("com.apple.security")) {
694 defaultServer
= kValidUpdateCarryServer
;
696 return defaultServer
;
697 #endif // !RC_SEED_BUILD
700 void SecRevocationDbInitialize() {
701 if (!isDbOwner()) { return; }
702 os_transaction_t transaction
= os_transaction_create("com.apple.trustd.valid.initialize");
703 __block
bool initializeDb
= false;
705 /* create base path if it doesn't exist */
706 (void)mkpath_np(kSecRevocationBasePath
, 0755);
708 /* check semaphore file */
709 WithPathInRevocationInfoDirectory(CFSTR(kSecRevocationDbReplaceFile
), ^(const char *path
) {
711 if (stat(path
, &sb
) == 0) {
712 initializeDb
= true; /* file was found, so we will replace the database */
713 if (remove(path
) == -1) {
715 secnotice("validupdate", "remove (%s): %s", path
, strerror(error
));
721 WithPathInRevocationInfoDirectory(CFSTR(kSecRevocationDbFileName
), ^(const char *path
) {
723 /* remove old database file(s) */
724 (void)removeFileWithSuffix(path
, "");
725 (void)removeFileWithSuffix(path
, "-journal");
726 (void)removeFileWithSuffix(path
, "-shm");
727 (void)removeFileWithSuffix(path
, "-wal");
731 if (stat(path
, &sb
) == -1) {
732 initializeDb
= true; /* file not found, so we will create the database */
738 os_release(transaction
);
739 return; /* database exists and doesn't need replacing */
742 /* initialize database from local asset */
743 CFTypeRef value
= (CFStringRef
)CFPreferencesCopyValue(kUpdateServerKey
, kSecPrefsDomain
, kCFPreferencesAnyUser
, kCFPreferencesCurrentHost
);
744 CFStringRef server
= (isString(value
)) ? (CFStringRef
)value
: (CFStringRef
)SecRevocationDbGetDefaultServer();
746 secnotice("validupdate", "initializing database");
747 if (!SecValidUpdateSatisfiedLocally(server
, version
, true)) {
748 #if !TARGET_OS_BRIDGE
749 /* Schedule full update as a maintenance task */
750 (void)SecValidUpdateRequest(SecRevocationDbGetUpdateQueue(), server
, version
);
753 CFReleaseSafe(value
);
754 os_release(transaction
);
759 // MARK: SecValidInfoRef
761 /* ======================================================================
763 ======================================================================
766 CFGiblisWithCompareFor(SecValidInfo
);
768 static SecValidInfoRef
SecValidInfoCreate(SecValidInfoFormat format
,
772 CFDataRef issuerHash
,
773 CFDataRef anchorHash
,
774 CFDateRef notBeforeDate
,
775 CFDateRef notAfterDate
,
776 CFDataRef nameConstraints
,
777 CFDataRef policyConstraints
) {
778 SecValidInfoRef validInfo
;
779 validInfo
= CFTypeAllocate(SecValidInfo
, struct __SecValidInfo
, kCFAllocatorDefault
);
780 if (!validInfo
) { return NULL
; }
782 CFRetainSafe(certHash
);
783 CFRetainSafe(issuerHash
);
784 CFRetainSafe(anchorHash
);
785 CFRetainSafe(notBeforeDate
);
786 CFRetainSafe(notAfterDate
);
787 CFRetainSafe(nameConstraints
);
788 CFRetainSafe(policyConstraints
);
790 validInfo
->format
= format
;
791 validInfo
->certHash
= certHash
;
792 validInfo
->issuerHash
= issuerHash
;
793 validInfo
->anchorHash
= anchorHash
;
794 validInfo
->isOnList
= isOnList
;
795 validInfo
->valid
= (flags
& kSecValidInfoAllowlist
);
796 validInfo
->complete
= (flags
& kSecValidInfoComplete
);
797 validInfo
->checkOCSP
= (flags
& kSecValidInfoCheckOCSP
);
798 validInfo
->knownOnly
= (flags
& kSecValidInfoKnownOnly
);
799 validInfo
->requireCT
= (flags
& kSecValidInfoRequireCT
);
800 validInfo
->noCACheck
= (flags
& kSecValidInfoNoCAv2Check
);
801 validInfo
->overridable
= (flags
& kSecValidInfoOverridable
);
802 validInfo
->hasDateConstraints
= (flags
& kSecValidInfoDateConstraints
);
803 validInfo
->hasNameConstraints
= (flags
& kSecValidInfoNameConstraints
);
804 validInfo
->hasPolicyConstraints
= (flags
& kSecValidInfoPolicyConstraints
);
805 validInfo
->notBeforeDate
= notBeforeDate
;
806 validInfo
->notAfterDate
= notAfterDate
;
807 validInfo
->nameConstraints
= nameConstraints
;
808 validInfo
->policyConstraints
= policyConstraints
;
813 static void SecValidInfoDestroy(CFTypeRef cf
) {
814 SecValidInfoRef validInfo
= (SecValidInfoRef
)cf
;
816 CFReleaseNull(validInfo
->certHash
);
817 CFReleaseNull(validInfo
->issuerHash
);
818 CFReleaseNull(validInfo
->anchorHash
);
819 CFReleaseNull(validInfo
->notBeforeDate
);
820 CFReleaseNull(validInfo
->notAfterDate
);
821 CFReleaseNull(validInfo
->nameConstraints
);
822 CFReleaseNull(validInfo
->policyConstraints
);
826 void SecValidInfoSetAnchor(SecValidInfoRef validInfo
, SecCertificateRef anchor
) {
830 CFDataRef anchorHash
= NULL
;
832 anchorHash
= SecCertificateCopySHA256Digest(anchor
);
834 /* clear no-ca flag for anchors where we want OCSP checked [32523118] */
835 if (SecIsAppleTrustAnchor(anchor
, 0)) {
836 validInfo
->noCACheck
= false;
839 CFReleaseNull(validInfo
->anchorHash
);
840 validInfo
->anchorHash
= anchorHash
;
843 static Boolean
SecValidInfoCompare(CFTypeRef a
, CFTypeRef b
) {
844 SecValidInfoRef validInfoA
= (SecValidInfoRef
)a
;
845 SecValidInfoRef validInfoB
= (SecValidInfoRef
)b
;
846 if (validInfoA
== validInfoB
) {
849 if (!validInfoA
|| !validInfoB
||
850 (CFGetTypeID(a
) != SecValidInfoGetTypeID()) ||
851 (CFGetTypeID(b
) != SecValidInfoGetTypeID())) {
854 return CFEqualSafe(validInfoA
->certHash
, validInfoB
->certHash
) && CFEqualSafe(validInfoA
->issuerHash
, validInfoB
->issuerHash
);
857 static CFStringRef
SecValidInfoCopyFormatDescription(CFTypeRef cf
, CFDictionaryRef formatOptions
) {
858 SecValidInfoRef validInfo
= (SecValidInfoRef
)cf
;
859 CFStringRef certHash
= CFDataCopyHexString(validInfo
->certHash
);
860 CFStringRef issuerHash
= CFDataCopyHexString(validInfo
->issuerHash
);
861 CFStringRef desc
= CFStringCreateWithFormat(NULL
, formatOptions
, CFSTR("validInfo certHash: %@ issuerHash: %@"), certHash
, issuerHash
);
862 CFReleaseNull(certHash
);
863 CFReleaseNull(issuerHash
);
869 // MARK: SecRevocationDb
871 /* ======================================================================
873 ======================================================================
876 /* SecRevocationDbCheckNextUpdate returns true if we dispatched an
877 update request, otherwise false.
879 static bool _SecRevocationDbCheckNextUpdate(void) {
880 // are we the db owner instance?
884 CFTypeRef value
= NULL
;
886 // is it time to check?
887 CFAbsoluteTime now
= CFAbsoluteTimeGetCurrent();
888 CFAbsoluteTime minNextUpdate
= now
+ gUpdateInterval
;
889 gUpdateStarted
= now
;
891 if (0 == gNextUpdate
) {
892 // first time we're called, check if we have a saved nextUpdate value
893 gNextUpdate
= SecRevocationDbGetNextUpdateTime();
895 if (gNextUpdate
< minNextUpdate
) {
896 gNextUpdate
= minNextUpdate
;
898 // allow pref to override update interval, if it exists
899 CFIndex interval
= -1;
900 value
= (CFNumberRef
)CFPreferencesCopyValue(kUpdateIntervalKey
, kSecPrefsDomain
, kCFPreferencesAnyUser
, kCFPreferencesCurrentHost
);
901 if (isNumber(value
)) {
902 if (CFNumberGetValue((CFNumberRef
)value
, kCFNumberCFIndexType
, &interval
)) {
903 if (interval
< kSecMinUpdateInterval
) {
904 interval
= kSecMinUpdateInterval
;
905 } else if (interval
> kSecMaxUpdateInterval
) {
906 interval
= kSecMaxUpdateInterval
;
910 CFReleaseNull(value
);
911 gUpdateInterval
= kSecStdUpdateInterval
;
913 gUpdateInterval
= interval
;
915 // pin next update time to the preferred update interval
916 if (gNextUpdate
> (gUpdateStarted
+ gUpdateInterval
)) {
917 gNextUpdate
= gUpdateStarted
+ gUpdateInterval
;
919 secdebug("validupdate", "next update at %f (in %f seconds)",
920 (double)gUpdateStarted
, (double)gNextUpdate
-gUpdateStarted
);
922 if (gNextUpdate
> now
) {
926 secnotice("validupdate", "starting update");
928 // set minimum next update time here in case we can't get an update
929 gNextUpdate
= minNextUpdate
;
931 // determine which server to query
933 value
= (CFStringRef
)CFPreferencesCopyValue(kUpdateServerKey
, kSecPrefsDomain
, kCFPreferencesAnyUser
, kCFPreferencesCurrentHost
);
934 if (isString(value
)) {
935 server
= (CFStringRef
) CFRetain(value
);
937 server
= (CFStringRef
) CFRetain(SecRevocationDbGetDefaultServer());
939 CFReleaseNull(value
);
941 // determine version of our current database
942 CFIndex version
= SecRevocationDbGetVersion();
943 secdebug("validupdate", "got version %ld from db", (long)version
);
945 if (gLastVersion
> 0) {
946 secdebug("validupdate", "error getting version; using last good version: %ld", (long)gLastVersion
);
948 version
= gLastVersion
;
951 // determine source of our current database
952 // (if this ever changes, we will need to reload the db)
953 CFStringRef db_source
= SecRevocationDbCopyUpdateSource();
955 db_source
= (CFStringRef
) CFRetain(kValidUpdateProdServer
);
958 // determine whether we need to recreate the database
959 CFIndex db_version
= SecRevocationDbGetSchemaVersion();
960 CFIndex db_format
= SecRevocationDbGetUpdateFormat();
961 if (db_version
< kSecRevocationDbSchemaVersion
||
962 db_format
< kSecRevocationDbUpdateFormat
||
963 kCFCompareEqualTo
!= CFStringCompare(server
, db_source
, kCFCompareCaseInsensitive
)) {
964 // we need to fully rebuild the db contents, so we set our version to 0.
965 version
= gLastVersion
= 0;
968 // determine whether update fetching is enabled
969 #if (__MAC_OS_X_VERSION_MIN_REQUIRED >= 101300 || __IPHONE_OS_VERSION_MIN_REQUIRED >= 110000)
970 bool updateEnabled
= true; // macOS 10.13 or iOS 11.0
972 bool updateEnabled
= false;
974 value
= (CFBooleanRef
)CFPreferencesCopyValue(kUpdateEnabledKey
, kSecPrefsDomain
, kCFPreferencesAnyUser
, kCFPreferencesCurrentHost
);
975 if (isBoolean(value
)) {
976 updateEnabled
= CFBooleanGetValue((CFBooleanRef
)value
);
978 CFReleaseNull(value
);
980 // Schedule maintenance work
981 bool result
= SecValidUpdateSchedule(updateEnabled
, server
, version
);
982 CFReleaseNull(server
);
983 CFReleaseNull(db_source
);
987 void SecRevocationDbCheckNextUpdate(void) {
988 static dispatch_once_t once
;
989 static sec_action_t action
;
991 dispatch_once(&once
, ^{
992 dispatch_queue_t update_queue
= SecRevocationDbGetUpdateQueue();
993 action
= sec_action_create_with_queue(update_queue
, "update_check", kSecMinUpdateInterval
);
994 sec_action_set_handler(action
, ^{
995 os_transaction_t transaction
= os_transaction_create("com.apple.trustd.valid.checkNextUpdate");
996 (void)_SecRevocationDbCheckNextUpdate();
997 os_release(transaction
);
1000 sec_action_perform(action
);
1003 /* This function verifies an update, in this format:
1004 1) unsigned 32-bit network-byte-order length of binary plist
1005 2) binary plist data
1006 3) unsigned 32-bit network-byte-order length of CMS message
1007 4) CMS message (containing certificates and signature over binary plist)
1009 The length argument is the total size of the packed update data.
1011 bool SecRevocationDbVerifyUpdate(void *update
, CFIndex length
) {
1012 if (!update
|| length
<= (CFIndex
)sizeof(uint32_t)) {
1015 uint32_t plistLength
= OSSwapInt32(*((uint32_t *)update
));
1016 if ((plistLength
+ (CFIndex
)(sizeof(uint32_t)*2)) > (uint64_t) length
) {
1017 secdebug("validupdate", "ERROR: reported plist length (%lu)+%lu exceeds total length (%lu)\n",
1018 (unsigned long)plistLength
, (unsigned long)sizeof(uint32_t)*2, (unsigned long)length
);
1021 uint8_t *plistData
= (uint8_t *)update
+ sizeof(uint32_t);
1022 uint8_t *sigData
= (uint8_t *)plistData
+ plistLength
;
1023 uint32_t sigLength
= OSSwapInt32(*((uint32_t *)sigData
));
1024 sigData
+= sizeof(uint32_t);
1025 if ((plistLength
+ sigLength
+ (CFIndex
)(sizeof(uint32_t) * 2)) != (uint64_t) length
) {
1026 secdebug("validupdate", "ERROR: reported lengths do not add up to total length\n");
1030 OSStatus status
= 0;
1031 CMSSignerStatus signerStatus
;
1032 CMSDecoderRef cms
= NULL
;
1033 SecPolicyRef policy
= NULL
;
1034 SecTrustRef trust
= NULL
;
1035 CFDataRef content
= NULL
;
1037 if ((content
= CFDataCreateWithBytesNoCopy(kCFAllocatorDefault
,
1038 (const UInt8
*)plistData
, (CFIndex
)plistLength
, kCFAllocatorNull
)) == NULL
) {
1039 secdebug("validupdate", "CFDataCreateWithBytesNoCopy failed (%ld bytes)\n", (long)plistLength
);
1043 if ((status
= CMSDecoderCreate(&cms
)) != errSecSuccess
) {
1044 secdebug("validupdate", "CMSDecoderCreate failed with error %d\n", (int)status
);
1047 if ((status
= CMSDecoderUpdateMessage(cms
, sigData
, sigLength
)) != errSecSuccess
) {
1048 secdebug("validupdate", "CMSDecoderUpdateMessage failed with error %d\n", (int)status
);
1051 if ((status
= CMSDecoderSetDetachedContent(cms
, content
)) != errSecSuccess
) {
1052 secdebug("validupdate", "CMSDecoderSetDetachedContent failed with error %d\n", (int)status
);
1055 if ((status
= CMSDecoderFinalizeMessage(cms
)) != errSecSuccess
) {
1056 secdebug("validupdate", "CMSDecoderFinalizeMessage failed with error %d\n", (int)status
);
1060 policy
= SecPolicyCreateApplePinned(CFSTR("ValidUpdate"), // kSecPolicyNameAppleValidUpdate
1061 CFSTR("1.2.840.113635.100.6.2.10"), // System Integration 2 Intermediate Certificate
1062 CFSTR("1.2.840.113635.100.6.51")); // Valid update signing OID
1064 // Check that the first signer actually signed this message.
1065 if ((status
= CMSDecoderCopySignerStatus(cms
, 0, policy
,
1066 false, &signerStatus
, &trust
, NULL
)) != errSecSuccess
) {
1067 secdebug("validupdate", "CMSDecoderCopySignerStatus failed with error %d\n", (int)status
);
1070 // Make sure the signature verifies against the detached content
1071 if (signerStatus
!= kCMSSignerValid
) {
1072 secdebug("validupdate", "ERROR: signature did not verify (signer status %d)\n", (int)signerStatus
);
1073 status
= errSecInvalidSignature
;
1076 // Make sure the signing certificate is valid for the specified policy
1077 SecTrustResultType trustResult
= kSecTrustResultInvalid
;
1078 status
= SecTrustEvaluate(trust
, &trustResult
);
1079 if (status
!= errSecSuccess
) {
1080 secdebug("validupdate", "SecTrustEvaluate failed with error %d (trust=%p)\n", (int)status
, (void *)trust
);
1081 } else if (!(trustResult
== kSecTrustResultUnspecified
|| trustResult
== kSecTrustResultProceed
)) {
1082 secdebug("validupdate", "SecTrustEvaluate failed with trust result %d\n", (int)trustResult
);
1083 status
= errSecVerificationFailure
;
1088 CFReleaseSafe(content
);
1089 CFReleaseSafe(trust
);
1090 CFReleaseSafe(policy
);
1093 return (status
== errSecSuccess
);
1096 CFAbsoluteTime
SecRevocationDbComputeNextUpdateTime(CFIndex updateInterval
) {
1097 CFIndex interval
= updateInterval
;
1098 // try to use interval preference if it exists
1099 CFTypeRef value
= (CFNumberRef
)CFPreferencesCopyValue(kUpdateIntervalKey
, kSecPrefsDomain
, kCFPreferencesAnyUser
, kCFPreferencesCurrentHost
);
1100 if (isNumber(value
)) {
1101 CFNumberGetValue((CFNumberRef
)value
, kCFNumberCFIndexType
, &interval
);
1103 CFReleaseNull(value
);
1105 if (interval
<= 0) {
1106 interval
= kSecStdUpdateInterval
;
1110 if (interval
< kSecMinUpdateInterval
) {
1111 interval
= kSecMinUpdateInterval
;
1112 } else if (interval
> kSecMaxUpdateInterval
) {
1113 interval
= kSecMaxUpdateInterval
;
1116 // compute randomization factor, between 0 and 50% of the interval
1117 CFIndex fuzz
= arc4random() % (long)(interval
/2.0);
1118 CFAbsoluteTime nextUpdate
= CFAbsoluteTimeGetCurrent() + interval
+ fuzz
;
1119 secdebug("validupdate", "next update in %ld seconds", (long)(interval
+ fuzz
));
1123 void SecRevocationDbComputeAndSetNextUpdateTime(void) {
1124 gNextUpdate
= SecRevocationDbComputeNextUpdateTime(0);
1125 (void) SecRevocationDbSetNextUpdateTime(gNextUpdate
, NULL
);
1126 gUpdateStarted
= 0; /* no update is currently in progress */
1129 bool SecRevocationDbIngestUpdate(SecRevocationDbConnectionRef dbc
, CFDictionaryRef update
, CFIndex chunkVersion
, CFIndex
*outVersion
, CFErrorRef
*error
) {
1131 CFIndex version
= 0;
1132 CFErrorRef localError
= NULL
;
1134 SecError(errSecParam
, &localError
, CFSTR("SecRevocationDbIngestUpdate: invalid update parameter"));
1135 goto setVersionAndExit
;
1137 CFTypeRef value
= (CFNumberRef
)CFDictionaryGetValue(update
, CFSTR("version"));
1138 if (isNumber(value
)) {
1139 if (!CFNumberGetValue((CFNumberRef
)value
, kCFNumberCFIndexType
, &version
)) {
1144 // only the first chunk will have a version, so the second and
1145 // subsequent chunks will need to pass it in chunkVersion.
1146 version
= chunkVersion
;
1148 // check precommitted version since update hasn't been committed yet
1149 CFIndex curVersion
= dbc
->precommitVersion
;
1150 if (version
> curVersion
|| chunkVersion
> 0) {
1151 ok
= _SecRevocationDbApplyUpdate(dbc
, update
, version
, &localError
);
1152 secdebug("validupdate", "_SecRevocationDbApplyUpdate=%s, v%ld, precommit=%ld, full=%s",
1153 (ok
) ? "1" : "0", (long)version
, (long)dbc
->precommitVersion
,
1154 (dbc
->fullUpdate
) ? "1" : "0");
1156 secdebug("validupdate", "we have v%ld, skipping update to v%ld",
1157 (long)curVersion
, (long)version
);
1158 version
= -1; // invalid, so we know to skip subsequent chunks
1159 ok
= true; // this is not an error condition
1163 *outVersion
= version
;
1165 (void) CFErrorPropagate(localError
, error
);
1170 /* Database schema */
1172 /* admin table holds these key-value (or key-ival) pairs:
1173 'version' (integer) // version of database content
1174 'check_again' (double) // CFAbsoluteTime of next check (optional)
1175 'db_version' (integer) // version of database schema
1176 'db_hash' (blob) // SHA-256 database hash
1177 --> entries in admin table are unique by text key
1179 issuers table holds map of issuing CA hashes to group identifiers:
1180 groupid (integer) // associated group identifier in group ID table
1181 issuer_hash (blob) // SHA-256 hash of issuer certificate (primary key)
1182 --> entries in issuers table are unique by issuer_hash;
1183 multiple issuer entries may have the same groupid!
1185 groups table holds records with these attributes:
1186 groupid (integer) // ordinal ID associated with this group entry
1187 flags (integer) // a bitmask of the following values:
1188 kSecValidInfoComplete (0x00000001) set if we have all revocation info for this issuer group
1189 kSecValidInfoCheckOCSP (0x00000002) set if must check ocsp for certs from this issuer group
1190 kSecValidInfoKnownOnly (0x00000004) set if any CA from this issuer group must be in database
1191 kSecValidInfoRequireCT (0x00000008) set if all certs from this issuer group must have SCTs
1192 kSecValidInfoAllowlist (0x00000010) set if this entry describes valid certs (i.e. is allowed)
1193 kSecValidInfoNoCACheck (0x00000020) set if this entry does not require an OCSP check to accept (deprecated)
1194 kSecValidInfoOverridable (0x00000040) set if the trust status is recoverable and can be overridden
1195 kSecValidInfoDateConstraints (0x00000080) set if this group has not-before or not-after constraints
1196 kSecValidInfoNameConstraints (0x00000100) [RESERVED] set if this group has name constraints in database
1197 kSecValidInfoPolicyConstraints (0x00000200) [RESERVED] set if this group has policy constraints in database
1198 kSecValidInfoNoCAv2Check (0x00000400) set if this entry does not require an OCSP check to accept
1199 format (integer) // an integer describing format of entries:
1200 kSecValidInfoFormatUnknown (0) unknown format
1201 kSecValidInfoFormatSerial (1) serial number, not greater than 20 bytes in length
1202 kSecValidInfoFormatSHA256 (2) SHA-256 hash, 32 bytes in length
1203 kSecValidInfoFormatNto1 (3) filter data blob of arbitrary length
1204 data (blob) // Bloom filter data if format is 'nto1', otherwise NULL
1205 --> entries in groups table are unique by groupid
1207 serials table holds serial number blobs with these attributes:
1208 groupid (integer) // identifier for issuer group in the groups table
1209 serial (blob) // serial number
1210 --> entries in serials table are unique by serial and groupid
1212 hashes table holds SHA-256 hashes of certificates with these attributes:
1213 groupid (integer) // identifier for issuer group in the groups table
1214 sha256 (blob) // SHA-256 hash of subject certificate
1215 --> entries in hashes table are unique by sha256 and groupid
1217 dates table holds notBefore and notAfter dates (as CFAbsoluteTime) with these attributes:
1218 groupid (integer) // identifier for issuer group in the groups table (primary key)
1219 notbefore (real) // issued certs are invalid if their notBefore is prior to this date
1220 notafter (real) // issued certs are invalid after this date (or their notAfter, if earlier)
1221 --> entries in dates table are unique by groupid, and only exist if kSecValidInfoDateConstraints is true
1224 #define createTablesSQL CFSTR("CREATE TABLE IF NOT EXISTS admin(" \
1225 "key TEXT PRIMARY KEY NOT NULL," \
1226 "ival INTEGER NOT NULL," \
1229 "CREATE TABLE IF NOT EXISTS issuers(" \
1230 "groupid INTEGER NOT NULL," \
1231 "issuer_hash BLOB PRIMARY KEY NOT NULL" \
1233 "CREATE INDEX IF NOT EXISTS issuer_idx ON issuers(issuer_hash);" \
1234 "CREATE TABLE IF NOT EXISTS groups(" \
1235 "groupid INTEGER PRIMARY KEY AUTOINCREMENT," \
1240 "CREATE TABLE IF NOT EXISTS serials(" \
1241 "groupid INTEGER NOT NULL," \
1242 "serial BLOB NOT NULL," \
1243 "UNIQUE(groupid,serial)" \
1245 "CREATE TABLE IF NOT EXISTS hashes(" \
1246 "groupid INTEGER NOT NULL," \
1247 "sha256 BLOB NOT NULL," \
1248 "UNIQUE(groupid,sha256)" \
1250 "CREATE TABLE IF NOT EXISTS dates(" \
1251 "groupid INTEGER PRIMARY KEY NOT NULL," \
1255 "CREATE TRIGGER IF NOT EXISTS group_del " \
1256 "BEFORE DELETE ON groups FOR EACH ROW " \
1258 "DELETE FROM serials WHERE groupid=OLD.groupid; " \
1259 "DELETE FROM hashes WHERE groupid=OLD.groupid; " \
1260 "DELETE FROM issuers WHERE groupid=OLD.groupid; " \
1261 "DELETE FROM dates WHERE groupid=OLD.groupid; " \
1264 #define selectGroupIdSQL CFSTR("SELECT DISTINCT groupid " \
1265 "FROM issuers WHERE issuer_hash=?")
1266 #define selectVersionSQL CFSTR("SELECT ival FROM admin " \
1267 "WHERE key='version'")
1268 #define selectDbVersionSQL CFSTR("SELECT ival FROM admin " \
1269 "WHERE key='db_version'")
1270 #define selectDbFormatSQL CFSTR("SELECT ival FROM admin " \
1271 "WHERE key='db_format'")
1272 #define selectDbHashSQL CFSTR("SELECT value FROM admin " \
1273 "WHERE key='db_hash'")
1274 #define selectDbSourceSQL CFSTR("SELECT value FROM admin " \
1275 "WHERE key='db_source'")
1276 #define selectNextUpdateSQL CFSTR("SELECT value FROM admin " \
1277 "WHERE key='check_again'")
1278 #define selectGroupRecordSQL CFSTR("SELECT flags,format,data FROM " \
1279 "groups WHERE groupid=?")
1280 #define selectSerialRecordSQL CFSTR("SELECT rowid FROM serials " \
1281 "WHERE groupid=? AND serial=?")
1282 #define selectDateRecordSQL CFSTR("SELECT notbefore,notafter FROM " \
1283 "dates WHERE groupid=?")
1284 #define selectHashRecordSQL CFSTR("SELECT rowid FROM hashes " \
1285 "WHERE groupid=? AND sha256=?")
1286 #define insertAdminRecordSQL CFSTR("INSERT OR REPLACE INTO admin " \
1287 "(key,ival,value) VALUES (?,?,?)")
1288 #define insertIssuerRecordSQL CFSTR("INSERT OR REPLACE INTO issuers " \
1289 "(groupid,issuer_hash) VALUES (?,?)")
1290 #define insertGroupRecordSQL CFSTR("INSERT OR REPLACE INTO groups " \
1291 "(groupid,flags,format,data) VALUES (?,?,?,?)")
1292 #define insertSerialRecordSQL CFSTR("INSERT OR REPLACE INTO serials " \
1293 "(groupid,serial) VALUES (?,?)")
1294 #define deleteSerialRecordSQL CFSTR("DELETE FROM serials " \
1295 "WHERE groupid=? AND hex(serial) LIKE ?")
1296 #define insertSha256RecordSQL CFSTR("INSERT OR REPLACE INTO hashes " \
1297 "(groupid,sha256) VALUES (?,?)")
1298 #define deleteSha256RecordSQL CFSTR("DELETE FROM hashes " \
1299 "WHERE groupid=? AND hex(sha256) LIKE ?")
1300 #define insertDateRecordSQL CFSTR("INSERT OR REPLACE INTO dates " \
1301 "(groupid,notbefore,notafter) VALUES (?,?,?)")
1302 #define deleteGroupRecordSQL CFSTR("DELETE FROM groups " \
1304 #define deleteGroupIssuersSQL CFSTR("DELETE FROM issuers " \
1307 #define updateConstraintsTablesSQL CFSTR("" \
1308 "CREATE TABLE IF NOT EXISTS dates(" \
1309 "groupid INTEGER PRIMARY KEY NOT NULL," \
1314 #define updateGroupDeleteTriggerSQL CFSTR("" \
1315 "DROP TRIGGER IF EXISTS group_del;" \
1316 "CREATE TRIGGER group_del BEFORE DELETE ON groups FOR EACH ROW " \
1318 "DELETE FROM serials WHERE groupid=OLD.groupid; " \
1319 "DELETE FROM hashes WHERE groupid=OLD.groupid; " \
1320 "DELETE FROM issuers WHERE groupid=OLD.groupid; " \
1321 "DELETE FROM dates WHERE groupid=OLD.groupid; " \
1324 #define deleteAllEntriesSQL CFSTR("" \
1325 "DELETE FROM groups; " \
1326 "DELETE FROM admin WHERE key='version'; " \
1327 "DELETE FROM sqlite_sequence")
1330 /* Database management */
1332 static SecDbRef
SecRevocationDbCreate(CFStringRef path
) {
1333 /* only the db owner should open a read-write connection. */
1334 __block
bool readWrite
= isDbOwner();
1337 SecDbRef result
= SecDbCreate(path
, mode
, readWrite
, false, true, true, 1, ^bool (SecDbRef db
, SecDbConnectionRef dbconn
, bool didCreate
, bool *callMeAgainForNextConnection
, CFErrorRef
*error
) {
1338 __block
bool ok
= true;
1340 ok
&= SecDbTransaction(dbconn
, kSecDbExclusiveTransactionType
, error
, ^(bool *commit
) {
1341 /* Create all database tables, indexes, and triggers.
1342 * SecDbOpen will set auto_vacuum and journal_mode for us before we get called back.*/
1343 ok
= ok
&& SecDbExec(dbconn
, createTablesSQL
, error
);
1347 if (!ok
|| (error
&& *error
)) {
1348 CFIndex errCode
= errSecInternalComponent
;
1349 if (error
&& *error
) {
1350 errCode
= CFErrorGetCode(*error
);
1352 secerror("%s failed: %@", didCreate
? "Create" : "Open", error
? *error
: NULL
);
1353 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationCreate
, TAFatalError
, errCode
);
1361 static dispatch_once_t kSecRevocationDbOnce
;
1362 static SecRevocationDbRef kSecRevocationDb
= NULL
;
1364 static SecRevocationDbRef
SecRevocationDbInit(CFStringRef db_name
) {
1365 SecRevocationDbRef rdb
;
1366 dispatch_queue_attr_t attr
;
1368 require(rdb
= (SecRevocationDbRef
)malloc(sizeof(struct __SecRevocationDb
)), errOut
);
1370 rdb
->update_queue
= NULL
;
1371 rdb
->updateInProgress
= false;
1372 rdb
->unsupportedVersion
= false;
1373 rdb
->changed
= false;
1375 require(rdb
->db
= SecRevocationDbCreate(db_name
), errOut
);
1376 attr
= dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL
, QOS_CLASS_BACKGROUND
, 0);
1377 attr
= dispatch_queue_attr_make_with_autorelease_frequency(attr
, DISPATCH_AUTORELEASE_FREQUENCY_WORK_ITEM
);
1378 require(rdb
->update_queue
= dispatch_queue_create(NULL
, attr
), errOut
);
1379 require(rdb
->info_cache_list
= CFArrayCreateMutable(NULL
, 0, &kCFTypeArrayCallBacks
), errOut
);
1380 require(rdb
->info_cache
= CFDictionaryCreateMutable(NULL
, 0, &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
), errOut
);
1381 rdb
->info_cache_lock
= OS_UNFAIR_LOCK_INIT
;
1384 /* register for changes signaled by the db owner instance */
1386 notify_register_dispatch(kSecRevocationDbChanged
, &out_token
, rdb
->update_queue
, ^(int __unused token
) {
1387 secnotice("validupdate", "Got notification of database change");
1388 SecRevocationDbResetCaches();
1394 secdebug("validupdate", "Failed to create db at \"%@\"", db_name
);
1396 if (rdb
->update_queue
) {
1397 dispatch_release(rdb
->update_queue
);
1399 CFReleaseSafe(rdb
->db
);
1405 static CFStringRef
SecRevocationDbCopyPath(void) {
1406 CFURLRef revDbURL
= NULL
;
1407 CFStringRef revInfoRelPath
= NULL
;
1408 if ((revInfoRelPath
= CFStringCreateWithFormat(NULL
, NULL
, CFSTR("%s"), kSecRevocationDbFileName
)) != NULL
) {
1409 revDbURL
= SecCopyURLForFileInRevocationInfoDirectory(revInfoRelPath
);
1411 CFReleaseSafe(revInfoRelPath
);
1413 CFStringRef revDbPath
= NULL
;
1415 revDbPath
= CFURLCopyFileSystemPath(revDbURL
, kCFURLPOSIXPathStyle
);
1416 CFRelease(revDbURL
);
1421 static void SecRevocationDbWith(void(^dbJob
)(SecRevocationDbRef db
)) {
1422 dispatch_once(&kSecRevocationDbOnce
, ^{
1423 CFStringRef dbPath
= SecRevocationDbCopyPath();
1425 kSecRevocationDb
= SecRevocationDbInit(dbPath
);
1427 if (kSecRevocationDb
&& isDbOwner()) {
1428 /* check and update schema immediately after database is opened */
1429 SecRevocationDbUpdateSchema(kSecRevocationDb
);
1433 // Do pre job run work here (cancel idle timers etc.)
1434 if (kSecRevocationDb
->updateInProgress
) {
1435 return; // this would block since SecDb has an exclusive transaction lock
1437 dbJob(kSecRevocationDb
);
1438 // Do post job run work here (gc timer, etc.)
1441 static bool SecRevocationDbPerformWrite(SecRevocationDbRef rdb
, CFErrorRef
*error
,
1442 bool(^writeJob
)(SecRevocationDbConnectionRef dbc
, CFErrorRef
*blockError
)) {
1443 __block
bool ok
= true;
1444 __block CFErrorRef localError
= NULL
;
1446 ok
&= SecDbPerformWrite(rdb
->db
, &localError
, ^(SecDbConnectionRef dbconn
) {
1447 ok
&= SecDbTransaction(dbconn
, kSecDbImmediateTransactionType
, &localError
, ^(bool *commit
) {
1448 SecRevocationDbConnectionRef dbc
= SecRevocationDbConnectionInit(rdb
, dbconn
, &localError
);
1449 ok
= ok
&& writeJob(dbc
, &localError
);
1454 ok
&= CFErrorPropagate(localError
, error
);
1458 static bool SecRevocationDbPerformRead(SecRevocationDbRef rdb
, CFErrorRef
*error
,
1459 bool(^readJob
)(SecRevocationDbConnectionRef dbc
, CFErrorRef
*blockError
)) {
1460 __block CFErrorRef localError
= NULL
;
1461 __block
bool ok
= true;
1463 ok
&= SecDbPerformRead(rdb
->db
, &localError
, ^(SecDbConnectionRef dbconn
) {
1464 SecRevocationDbConnectionRef dbc
= SecRevocationDbConnectionInit(rdb
, dbconn
, &localError
);
1465 ok
= ok
&& readJob(dbc
, &localError
);
1468 ok
&= CFErrorPropagate(localError
, error
);
1472 static SecRevocationDbConnectionRef
SecRevocationDbConnectionInit(SecRevocationDbRef db
, SecDbConnectionRef dbconn
, CFErrorRef
*error
) {
1473 SecRevocationDbConnectionRef dbc
= NULL
;
1474 CFErrorRef localError
= NULL
;
1476 dbc
= (SecRevocationDbConnectionRef
)malloc(sizeof(struct __SecRevocationDbConnection
));
1479 dbc
->dbconn
= dbconn
;
1480 dbc
->precommitVersion
= _SecRevocationDbGetVersion(dbc
, &localError
);
1481 dbc
->precommitDbVersion
= _SecRevocationDbGetSchemaVersion(db
, dbc
, &localError
);
1482 dbc
->fullUpdate
= false;
1484 (void) CFErrorPropagate(localError
, error
);
1488 static CF_RETURNS_RETAINED CFDataRef
createCacheKey(CFDataRef certHash
, CFDataRef issuerHash
) {
1489 CFMutableDataRef concat
= CFDataCreateMutableCopy(NULL
, 0, certHash
);
1490 CFDataAppend(concat
, issuerHash
);
1491 CFDataRef result
= SecSHA256DigestCreateFromData(NULL
, concat
);
1492 CFReleaseNull(concat
);
1496 static CF_RETURNS_RETAINED SecValidInfoRef
SecRevocationDbCacheRead(SecRevocationDbRef db
,
1497 SecCertificateRef certificate
,
1498 CFDataRef issuerHash
) {
1502 SecValidInfoRef result
= NULL
;
1503 if (!db
|| !db
->info_cache
|| !db
->info_cache_list
) {
1506 CFIndex ix
= kCFNotFound
;
1507 CFDataRef certHash
= SecCertificateCopySHA256Digest(certificate
);
1508 CFDataRef cacheKey
= createCacheKey(certHash
, issuerHash
);
1510 os_unfair_lock_lock(&db
->info_cache_lock
); // grab the cache lock before using the cache
1511 if (0 <= (ix
= CFArrayGetFirstIndexOfValue(db
->info_cache_list
,
1512 CFRangeMake(0, CFArrayGetCount(db
->info_cache_list
)),
1514 result
= (SecValidInfoRef
)CFDictionaryGetValue(db
->info_cache
, cacheKey
);
1515 // Verify this really is the right result
1516 if (CFEqualSafe(result
->certHash
, certHash
) && CFEqualSafe(result
->issuerHash
, issuerHash
)) {
1517 // Cache hit. Move the entry to the bottom of the list.
1518 CFArrayRemoveValueAtIndex(db
->info_cache_list
, ix
);
1519 CFArrayAppendValue(db
->info_cache_list
, cacheKey
);
1520 secdebug("validcache", "cache hit: %@", cacheKey
);
1522 // Just remove this bad entry
1523 CFArrayRemoveValueAtIndex(db
->info_cache_list
, ix
);
1524 CFDictionaryRemoveValue(db
->info_cache
, cacheKey
);
1525 secdebug("validcache", "cache remove bad: %@", cacheKey
);
1526 secnotice("validcache", "found a bad valid info cache entry at %ld", (long)ix
);
1529 CFRetainSafe(result
);
1530 os_unfair_lock_unlock(&db
->info_cache_lock
);
1531 CFReleaseSafe(certHash
);
1532 CFReleaseSafe(cacheKey
);
1536 static void SecRevocationDbCacheWrite(SecRevocationDbRef db
,
1537 SecValidInfoRef validInfo
) {
1538 if (!db
|| !validInfo
|| !db
->info_cache
|| !db
->info_cache_list
) {
1542 CFDataRef cacheKey
= createCacheKey(validInfo
->certHash
, validInfo
->issuerHash
);
1544 os_unfair_lock_lock(&db
->info_cache_lock
); // grab the cache lock before using the cache
1545 // check to make sure another thread didn't add this entry to the cache already
1546 if (0 > CFArrayGetFirstIndexOfValue(db
->info_cache_list
,
1547 CFRangeMake(0, CFArrayGetCount(db
->info_cache_list
)),
1549 CFDictionaryAddValue(db
->info_cache
, cacheKey
, validInfo
);
1550 if (kSecRevocationDbCacheSize
<= CFArrayGetCount(db
->info_cache_list
)) {
1551 // Remove least recently used cache entry.
1552 secdebug("validcache", "cache remove stale: %@", CFArrayGetValueAtIndex(db
->info_cache_list
, 0));
1553 CFDictionaryRemoveValue(db
->info_cache
, CFArrayGetValueAtIndex(db
->info_cache_list
, 0));
1554 CFArrayRemoveValueAtIndex(db
->info_cache_list
, 0);
1556 CFArrayAppendValue(db
->info_cache_list
, cacheKey
);
1557 secdebug("validcache", "cache add: %@", cacheKey
);
1559 os_unfair_lock_unlock(&db
->info_cache_lock
);
1560 CFReleaseNull(cacheKey
);
1563 static void SecRevocationDbCachePurge(SecRevocationDbRef db
) {
1564 if (!db
|| !db
->info_cache
|| !db
->info_cache_list
) {
1568 /* grab the cache lock and clear all entries */
1569 os_unfair_lock_lock(&db
->info_cache_lock
);
1570 CFArrayRemoveAllValues(db
->info_cache_list
);
1571 CFDictionaryRemoveAllValues(db
->info_cache
);
1572 secdebug("validcache", "cache purge");
1573 os_unfair_lock_unlock(&db
->info_cache_lock
);
1576 static int64_t _SecRevocationDbGetVersion(SecRevocationDbConnectionRef dbc
, CFErrorRef
*error
) {
1577 /* look up version entry in admin table; returns -1 on error */
1578 __block
int64_t version
= -1;
1579 __block
bool ok
= (dbc
!= NULL
);
1580 __block CFErrorRef localError
= NULL
;
1582 ok
= ok
&& SecDbWithSQL(dbc
->dbconn
, selectVersionSQL
, &localError
, ^bool(sqlite3_stmt
*selectVersion
) {
1583 ok
= ok
&& SecDbStep(dbc
->dbconn
, selectVersion
, &localError
, ^void(bool *stop
) {
1584 version
= sqlite3_column_int64(selectVersion
, 0);
1589 if (!ok
|| localError
) {
1590 secerror("_SecRevocationDbGetVersion failed: %@", localError
);
1591 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationRead
, TAFatalError
,
1592 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
1594 (void) CFErrorPropagate(localError
, error
);
1598 static bool _SecRevocationDbSetVersion(SecRevocationDbConnectionRef dbc
, CFIndex version
, CFErrorRef
*error
) {
1599 secdebug("validupdate", "setting version to %ld", (long)version
);
1601 __block CFErrorRef localError
= NULL
;
1602 __block
bool ok
= (dbc
!= NULL
);
1603 ok
= ok
&& SecDbWithSQL(dbc
->dbconn
, insertAdminRecordSQL
, &localError
, ^bool(sqlite3_stmt
*insertVersion
) {
1604 const char *versionKey
= "version";
1605 ok
= ok
&& SecDbBindText(insertVersion
, 1, versionKey
, strlen(versionKey
),
1606 SQLITE_TRANSIENT
, &localError
);
1607 ok
= ok
&& SecDbBindInt64(insertVersion
, 2,
1608 (sqlite3_int64
)version
, &localError
);
1609 ok
= ok
&& SecDbStep(dbc
->dbconn
, insertVersion
, &localError
, NULL
);
1612 if (!ok
|| localError
) {
1613 secerror("_SecRevocationDbSetVersion failed: %@", localError
);
1614 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationWrite
, TAFatalError
,
1615 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
1617 (void) CFErrorPropagate(localError
, error
);
1621 static int64_t _SecRevocationDbReadSchemaVersionFromDb(SecRevocationDbConnectionRef dbc
, CFErrorRef
*error
) {
1622 /* look up db_version entry in admin table; returns -1 on error */
1623 __block
int64_t db_version
= -1;
1624 __block
bool ok
= (dbc
!= NULL
);
1625 __block CFErrorRef localError
= NULL
;
1627 ok
= ok
&& SecDbWithSQL(dbc
->dbconn
, selectDbVersionSQL
, &localError
, ^bool(sqlite3_stmt
*selectDbVersion
) {
1628 ok
= ok
&& SecDbStep(dbc
->dbconn
, selectDbVersion
, &localError
, ^void(bool *stop
) {
1629 db_version
= sqlite3_column_int64(selectDbVersion
, 0);
1634 if (!ok
|| localError
) {
1635 secerror("_SecRevocationDbReadSchemaVersionFromDb failed: %@", localError
);
1636 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationRead
, TAFatalError
,
1637 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
1639 (void) CFErrorPropagate(localError
, error
);
1643 static _Atomic
int64_t gSchemaVersion
= -1;
1644 static int64_t _SecRevocationDbGetSchemaVersion(SecRevocationDbRef rdb
, SecRevocationDbConnectionRef dbc
, CFErrorRef
*error
) {
1645 static dispatch_once_t onceToken
;
1646 dispatch_once(&onceToken
, ^{
1648 atomic_init(&gSchemaVersion
, _SecRevocationDbReadSchemaVersionFromDb(dbc
, error
));
1650 (void) SecRevocationDbPerformRead(rdb
, error
, ^bool(SecRevocationDbConnectionRef dbc
, CFErrorRef
*blockError
) {
1651 atomic_init(&gSchemaVersion
, _SecRevocationDbReadSchemaVersionFromDb(dbc
, blockError
));
1656 if (atomic_load(&gSchemaVersion
) == -1) {
1657 /* Initial read(s) failed. Try to read the schema version again. */
1659 atomic_store(&gSchemaVersion
, _SecRevocationDbReadSchemaVersionFromDb(dbc
, error
));
1661 (void) SecRevocationDbPerformRead(rdb
, error
, ^bool(SecRevocationDbConnectionRef dbc
, CFErrorRef
*blockError
) {
1662 atomic_store(&gSchemaVersion
, _SecRevocationDbReadSchemaVersionFromDb(dbc
, blockError
));
1667 return atomic_load(&gSchemaVersion
);
1670 static void SecRevocationDbResetCaches(void) {
1671 SecRevocationDbWith(^(SecRevocationDbRef db
) {
1672 db
->unsupportedVersion
= false;
1673 db
->changed
= false;
1674 (void) SecRevocationDbPerformRead(db
, NULL
, ^bool(SecRevocationDbConnectionRef dbc
, CFErrorRef
*blockError
) {
1675 atomic_store(&gSchemaVersion
, _SecRevocationDbReadSchemaVersionFromDb(dbc
, blockError
));
1678 SecRevocationDbCachePurge(db
);
1682 static bool _SecRevocationDbSetSchemaVersion(SecRevocationDbConnectionRef dbc
, CFIndex dbversion
, CFErrorRef
*error
) {
1683 if (dbversion
> 0) {
1684 int64_t db_version
= (dbc
) ? dbc
->precommitDbVersion
: -1;
1685 if (db_version
>= dbversion
) {
1686 return true; /* requested schema is earlier than current schema */
1689 secdebug("validupdate", "setting db_version to %ld", (long)dbversion
);
1691 __block CFErrorRef localError
= NULL
;
1692 __block
bool ok
= (dbc
!= NULL
);
1693 ok
= ok
&& SecDbWithSQL(dbc
->dbconn
, insertAdminRecordSQL
, &localError
, ^bool(sqlite3_stmt
*insertDbVersion
) {
1694 const char *dbVersionKey
= "db_version";
1695 ok
= ok
&& SecDbBindText(insertDbVersion
, 1, dbVersionKey
, strlen(dbVersionKey
),
1696 SQLITE_TRANSIENT
, &localError
);
1697 ok
= ok
&& SecDbBindInt64(insertDbVersion
, 2,
1698 (sqlite3_int64
)dbversion
, &localError
);
1699 ok
= ok
&& SecDbStep(dbc
->dbconn
, insertDbVersion
, &localError
, NULL
);
1702 if (!ok
|| localError
) {
1703 secerror("_SecRevocationDbSetSchemaVersion failed: %@", localError
);
1704 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationWrite
, TAFatalError
,
1705 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
1707 dbc
->db
->changed
= true; /* will notify clients of this change */
1708 dbc
->db
->unsupportedVersion
= false;
1709 dbc
->precommitDbVersion
= dbversion
;
1710 atomic_store(&gSchemaVersion
, (int64_t)dbversion
);
1712 CFReleaseSafe(localError
);
1716 static bool _SecRevocationDbUpdateSchema(SecRevocationDbConnectionRef dbc
, CFErrorRef
*error
) {
1717 __block CFErrorRef localError
= NULL
;
1718 __block
bool ok
= (dbc
!= NULL
);
1719 __block
int64_t db_version
= (dbc
) ? dbc
->precommitDbVersion
: 0;
1720 if (db_version
>= kSecRevocationDbSchemaVersion
) {
1721 return ok
; /* schema version already up to date */
1723 secdebug("validupdate", "updating db schema from v%lld to v%lld",
1724 (long long)db_version
, (long long)kSecRevocationDbSchemaVersion
);
1726 if (ok
&& db_version
< 5) {
1727 /* apply v5 changes (add dates table and replace trigger) */
1728 ok
&= SecDbWithSQL(dbc
->dbconn
, updateConstraintsTablesSQL
, &localError
, ^bool(sqlite3_stmt
*updateTables
) {
1729 ok
= SecDbStep(dbc
->dbconn
, updateTables
, &localError
, NULL
);
1732 ok
&= SecDbWithSQL(dbc
->dbconn
, updateGroupDeleteTriggerSQL
, &localError
, ^bool(sqlite3_stmt
*updateTrigger
) {
1733 ok
= SecDbStep(dbc
->dbconn
, updateTrigger
, &localError
, NULL
);
1736 secdebug("validupdate", "applied schema update to v5 (%s)", (ok
) ? "ok" : "failed!");
1738 if (ok
&& db_version
< 6) {
1739 /* apply v6 changes (the SecDb layer will update autovacuum mode if needed, so we don't execute
1740 any SQL here, but we do want the database to be replaced in case transaction scope problems
1741 with earlier versions caused missing entries.) */
1742 secdebug("validupdate", "applied schema update to v6 (%s)", (ok
) ? "ok" : "failed!");
1743 if (db_version
> 0) {
1744 SecValidUpdateForceReplaceDatabase();
1749 secerror("_SecRevocationDbUpdateSchema failed: %@", localError
);
1751 ok
= ok
&& _SecRevocationDbSetSchemaVersion(dbc
, kSecRevocationDbSchemaVersion
, &localError
);
1753 (void) CFErrorPropagate(localError
, error
);
1757 bool SecRevocationDbUpdateSchema(SecRevocationDbRef rdb
) {
1758 /* note: this function assumes it is called only by the database owner.
1759 non-owner (read-only) clients will fail if changes to the db are needed. */
1760 if (!rdb
|| !rdb
->db
) {
1763 __block
bool ok
= true;
1764 __block CFErrorRef localError
= NULL
;
1765 ok
&= SecRevocationDbPerformWrite(rdb
, &localError
, ^bool(SecRevocationDbConnectionRef dbc
, CFErrorRef
*blockError
) {
1766 return _SecRevocationDbUpdateSchema(dbc
, blockError
);
1768 CFReleaseSafe(localError
);
1772 static int64_t _SecRevocationDbGetUpdateFormat(SecRevocationDbConnectionRef dbc
, CFErrorRef
*error
) {
1773 /* look up db_format entry in admin table; returns -1 on error */
1774 __block
int64_t db_format
= -1;
1775 __block
bool ok
= (dbc
!= NULL
);
1776 __block CFErrorRef localError
= NULL
;
1778 ok
= ok
&& SecDbWithSQL(dbc
->dbconn
, selectDbFormatSQL
, &localError
, ^bool(sqlite3_stmt
*selectDbFormat
) {
1779 ok
&= SecDbStep(dbc
->dbconn
, selectDbFormat
, &localError
, ^void(bool *stop
) {
1780 db_format
= sqlite3_column_int64(selectDbFormat
, 0);
1785 if (!ok
|| localError
) {
1786 secerror("_SecRevocationDbGetUpdateFormat failed: %@", localError
);
1787 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationRead
, TAFatalError
,
1788 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
1790 (void) CFErrorPropagate(localError
, error
);
1794 static bool _SecRevocationDbSetUpdateFormat(SecRevocationDbConnectionRef dbc
, CFIndex dbformat
, CFErrorRef
*error
) {
1795 secdebug("validupdate", "setting db_format to %ld", (long)dbformat
);
1797 __block CFErrorRef localError
= NULL
;
1798 __block
bool ok
= (dbc
!= NULL
);
1799 ok
= ok
&& SecDbWithSQL(dbc
->dbconn
, insertAdminRecordSQL
, &localError
, ^bool(sqlite3_stmt
*insertDbFormat
) {
1800 const char *dbFormatKey
= "db_format";
1801 ok
= ok
&& SecDbBindText(insertDbFormat
, 1, dbFormatKey
, strlen(dbFormatKey
),
1802 SQLITE_TRANSIENT
, &localError
);
1803 ok
= ok
&& SecDbBindInt64(insertDbFormat
, 2,
1804 (sqlite3_int64
)dbformat
, &localError
);
1805 ok
= ok
&& SecDbStep(dbc
->dbconn
, insertDbFormat
, &localError
, NULL
);
1808 if (!ok
|| localError
) {
1809 secerror("_SecRevocationDbSetUpdateFormat failed: %@", localError
);
1810 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationWrite
, TAFatalError
,
1811 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
1813 dbc
->db
->changed
= true; /* will notify clients of this change */
1814 dbc
->db
->unsupportedVersion
= false;
1816 (void) CFErrorPropagate(localError
, error
);
1820 static CFStringRef
_SecRevocationDbCopyUpdateSource(SecRevocationDbConnectionRef dbc
, CFErrorRef
*error
) {
1821 /* look up db_source entry in admin table; returns NULL on error */
1822 __block CFStringRef updateSource
= NULL
;
1823 __block
bool ok
= (dbc
!= NULL
);
1824 __block CFErrorRef localError
= NULL
;
1826 ok
= ok
&& SecDbWithSQL(dbc
->dbconn
, selectDbSourceSQL
, &localError
, ^bool(sqlite3_stmt
*selectDbSource
) {
1827 ok
&= SecDbStep(dbc
->dbconn
, selectDbSource
, &localError
, ^void(bool *stop
) {
1828 const UInt8
*p
= (const UInt8
*)sqlite3_column_blob(selectDbSource
, 0);
1830 CFIndex length
= (CFIndex
)sqlite3_column_bytes(selectDbSource
, 0);
1832 updateSource
= CFStringCreateWithBytes(kCFAllocatorDefault
, p
, length
, kCFStringEncodingUTF8
, false);
1839 if (!ok
|| localError
) {
1840 secerror("_SecRevocationDbCopyUpdateSource failed: %@", localError
);
1841 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationRead
, TAFatalError
,
1842 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
1844 (void) CFErrorPropagate(localError
, error
);
1845 return updateSource
;
1848 bool _SecRevocationDbSetUpdateSource(SecRevocationDbConnectionRef dbc
, CFStringRef updateSource
, CFErrorRef
*error
) {
1849 if (!updateSource
) {
1850 secerror("_SecRevocationDbSetUpdateSource failed: %d", errSecParam
);
1853 __block
char buffer
[256];
1854 __block
const char *updateSourceCStr
= CFStringGetCStringPtr(updateSource
, kCFStringEncodingUTF8
);
1855 if (!updateSourceCStr
) {
1856 if (CFStringGetCString(updateSource
, buffer
, 256, kCFStringEncodingUTF8
)) {
1857 updateSourceCStr
= buffer
;
1860 if (!updateSourceCStr
) {
1861 secerror("_SecRevocationDbSetUpdateSource failed: unable to get UTF-8 encoding");
1864 secdebug("validupdate", "setting update source to \"%s\"", updateSourceCStr
);
1866 __block CFErrorRef localError
= NULL
;
1867 __block
bool ok
= (dbc
!= NULL
);
1868 ok
= ok
&& SecDbWithSQL(dbc
->dbconn
, insertAdminRecordSQL
, &localError
, ^bool(sqlite3_stmt
*insertRecord
) {
1869 const char *dbSourceKey
= "db_source";
1870 ok
= ok
&& SecDbBindText(insertRecord
, 1, dbSourceKey
, strlen(dbSourceKey
),
1871 SQLITE_TRANSIENT
, &localError
);
1872 ok
= ok
&& SecDbBindInt64(insertRecord
, 2,
1873 (sqlite3_int64
)0, &localError
);
1874 ok
= ok
&& SecDbBindBlob(insertRecord
, 3,
1875 updateSourceCStr
, strlen(updateSourceCStr
),
1876 SQLITE_TRANSIENT
, &localError
);
1877 ok
= ok
&& SecDbStep(dbc
->dbconn
, insertRecord
, &localError
, NULL
);
1880 if (!ok
|| localError
) {
1881 secerror("_SecRevocationDbSetUpdateSource failed: %@", localError
);
1882 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationWrite
, TAFatalError
,
1883 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
1885 (void) CFErrorPropagate(localError
, error
);
1886 CFReleaseSafe(localError
);
1890 bool SecRevocationDbSetUpdateSource(SecRevocationDbRef rdb
, CFStringRef updateSource
) {
1891 /* note: this function assumes it is called only by the database owner.
1892 non-owner (read-only) clients will fail if changes to the db are needed. */
1893 if (!rdb
|| !rdb
->db
) {
1896 CFErrorRef localError
= NULL
;
1898 ok
&= SecRevocationDbPerformWrite(rdb
, &localError
, ^bool(SecRevocationDbConnectionRef dbc
, CFErrorRef
*error
) {
1899 return _SecRevocationDbSetUpdateSource(dbc
, updateSource
, error
);
1901 CFReleaseSafe(localError
);
1905 static CFAbsoluteTime
_SecRevocationDbGetNextUpdateTime(SecRevocationDbConnectionRef dbc
, CFErrorRef
*error
) {
1906 /* look up check_again entry in admin table; returns 0 on error */
1907 __block CFAbsoluteTime nextUpdate
= 0;
1908 __block
bool ok
= (dbc
!= NULL
);
1909 __block CFErrorRef localError
= NULL
;
1911 ok
= ok
&& SecDbWithSQL(dbc
->dbconn
, selectNextUpdateSQL
, &localError
, ^bool(sqlite3_stmt
*selectNextUpdate
) {
1912 ok
&= SecDbStep(dbc
->dbconn
, selectNextUpdate
, &localError
, ^void(bool *stop
) {
1913 CFAbsoluteTime
*p
= (CFAbsoluteTime
*)sqlite3_column_blob(selectNextUpdate
, 0);
1915 if (sizeof(CFAbsoluteTime
) == sqlite3_column_bytes(selectNextUpdate
, 0)) {
1923 if (!ok
|| localError
) {
1924 secerror("_SecRevocationDbGetNextUpdateTime failed: %@", localError
);
1925 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationRead
, TAFatalError
,
1926 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
1928 (void) CFErrorPropagate(localError
, error
);
1932 static bool _SecRevocationDbSetNextUpdateTime(SecRevocationDbConnectionRef dbc
, CFAbsoluteTime nextUpdate
, CFErrorRef
*error
){
1933 secdebug("validupdate", "setting next update to %f", (double)nextUpdate
);
1935 __block CFErrorRef localError
= NULL
;
1936 __block
bool ok
= (dbc
!= NULL
);
1937 ok
= ok
&& SecDbWithSQL(dbc
->dbconn
, insertAdminRecordSQL
, &localError
, ^bool(sqlite3_stmt
*insertRecord
) {
1938 const char *nextUpdateKey
= "check_again";
1939 ok
= ok
&& SecDbBindText(insertRecord
, 1, nextUpdateKey
, strlen(nextUpdateKey
),
1940 SQLITE_TRANSIENT
, &localError
);
1941 ok
= ok
&& SecDbBindInt64(insertRecord
, 2,
1942 (sqlite3_int64
)0, &localError
);
1943 ok
= ok
&& SecDbBindBlob(insertRecord
, 3,
1944 &nextUpdate
, sizeof(CFAbsoluteTime
),
1945 SQLITE_TRANSIENT
, &localError
);
1946 ok
= ok
&& SecDbStep(dbc
->dbconn
, insertRecord
, &localError
, NULL
);
1949 if (!ok
|| localError
) {
1950 secerror("_SecRevocationDbSetNextUpdate failed: %@", localError
);
1951 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationWrite
, TAFatalError
,
1952 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
1954 (void) CFErrorPropagate(localError
, error
);
1958 bool _SecRevocationDbRemoveAllEntries(SecRevocationDbConnectionRef dbc
, CFErrorRef
*error
) {
1959 /* clear out the contents of the database and start fresh */
1960 bool ok
= (dbc
!= NULL
);
1961 CFErrorRef localError
= NULL
;
1963 /* _SecRevocationDbUpdateSchema was called when db was opened, so no need to do it again. */
1965 /* delete all entries */
1966 ok
= ok
&& SecDbExec(dbc
->dbconn
, deleteAllEntriesSQL
, &localError
);
1967 secnotice("validupdate", "resetting database, result: %d (expected 1)", (ok
) ? 1 : 0);
1969 /* one more thing: update the schema version and format to current */
1970 ok
= ok
&& _SecRevocationDbSetSchemaVersion(dbc
, kSecRevocationDbSchemaVersion
, &localError
);
1971 ok
= ok
&& _SecRevocationDbSetUpdateFormat(dbc
, kSecRevocationDbUpdateFormat
, &localError
);
1973 if (!ok
|| localError
) {
1974 secerror("_SecRevocationDbRemoveAllEntries failed: %@", localError
);
1975 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationWrite
, TAFatalError
,
1976 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
1978 (void) CFErrorPropagate(localError
, error
);
1982 static bool _SecRevocationDbUpdateIssuers(SecRevocationDbConnectionRef dbc
, int64_t groupId
, CFArrayRef issuers
, CFErrorRef
*error
) {
1983 /* insert or replace issuer records in issuers table */
1984 if (!issuers
|| groupId
< 0) {
1985 return false; /* must have something to insert, and a group to associate with it */
1987 __block
bool ok
= (dbc
!= NULL
);
1988 __block CFErrorRef localError
= NULL
;
1989 if (isArray(issuers
)) {
1990 CFIndex issuerIX
, issuerCount
= CFArrayGetCount(issuers
);
1991 for (issuerIX
=0; issuerIX
<issuerCount
&& ok
; issuerIX
++) {
1992 CFDataRef hash
= (CFDataRef
)CFArrayGetValueAtIndex(issuers
, issuerIX
);
1993 if (!hash
) { continue; }
1994 ok
= ok
&& SecDbWithSQL(dbc
->dbconn
, insertIssuerRecordSQL
, &localError
, ^bool(sqlite3_stmt
*insertIssuer
) {
1995 ok
= ok
&& SecDbBindInt64(insertIssuer
, 1,
1996 groupId
, &localError
);
1997 ok
= ok
&& SecDbBindBlob(insertIssuer
, 2,
1998 CFDataGetBytePtr(hash
),
1999 CFDataGetLength(hash
),
2000 SQLITE_TRANSIENT
, &localError
);
2001 /* Execute the insert statement for this issuer record. */
2002 ok
= ok
&& SecDbStep(dbc
->dbconn
, insertIssuer
, &localError
, NULL
);
2007 if (!ok
|| localError
) {
2008 secerror("_SecRevocationDbUpdateIssuers failed: %@", localError
);
2009 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationWrite
, TAFatalError
,
2010 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
2012 (void) CFErrorPropagate(localError
, error
);
2016 static SecValidInfoFormat
_SecRevocationDbGetGroupFormatForData(SecRevocationDbConnectionRef dbc
, int64_t groupId
, CFDataRef data
) {
2017 /* determine existing format if groupId is supplied and this is a partial update,
2018 otherwise return the expected format for the given data. */
2019 SecValidInfoFormat format
= kSecValidInfoFormatUnknown
;
2020 if (groupId
>= 0 && !dbc
->fullUpdate
) {
2021 format
= _SecRevocationDbGetGroupFormat(dbc
, groupId
, NULL
, NULL
, NULL
);
2023 if (format
== kSecValidInfoFormatUnknown
&& data
!= NULL
) {
2024 /* group doesn't exist, so determine format based on length of specified data.
2025 len <= 20 is a serial number (actually, <=37, but != 32.)
2026 len==32 is a sha256 hash. otherwise: nto1. */
2027 CFIndex length
= CFDataGetLength(data
);
2029 format
= kSecValidInfoFormatSHA256
;
2030 } else if (length
<= 37) {
2031 format
= kSecValidInfoFormatSerial
;
2032 } else if (length
> 0) {
2033 format
= kSecValidInfoFormatNto1
;
2039 static bool _SecRevocationDbUpdateIssuerData(SecRevocationDbConnectionRef dbc
, int64_t groupId
, CFDictionaryRef dict
, CFErrorRef
*error
) {
2040 /* update/delete records in serials or hashes table. */
2041 if (!dict
|| groupId
< 0) {
2042 return false; /* must have something to insert, and a group to associate with it */
2044 __block
bool ok
= (dbc
!= NULL
);
2045 __block CFErrorRef localError
= NULL
;
2046 /* process deletions */
2047 CFArrayRef deleteArray
= (CFArrayRef
)CFDictionaryGetValue(dict
, CFSTR("delete"));
2048 if (isArray(deleteArray
)) {
2049 SecValidInfoFormat format
= kSecValidInfoFormatUnknown
;
2050 CFIndex processed
=0, identifierIX
, identifierCount
= CFArrayGetCount(deleteArray
);
2051 for (identifierIX
=0; identifierIX
<identifierCount
; identifierIX
++) {
2052 CFDataRef identifierData
= (CFDataRef
)CFArrayGetValueAtIndex(deleteArray
, identifierIX
);
2053 if (!identifierData
) { continue; }
2054 if (format
== kSecValidInfoFormatUnknown
) {
2055 format
= _SecRevocationDbGetGroupFormatForData(dbc
, groupId
, identifierData
);
2057 CFStringRef sql
= NULL
;
2058 if (format
== kSecValidInfoFormatSerial
) {
2059 sql
= deleteSerialRecordSQL
;
2060 } else if (format
== kSecValidInfoFormatSHA256
) {
2061 sql
= deleteSha256RecordSQL
;
2063 if (!sql
) { continue; }
2065 ok
= ok
&& SecDbWithSQL(dbc
->dbconn
, sql
, &localError
, ^bool(sqlite3_stmt
*deleteIdentifier
) {
2066 /* (groupid,serial|sha256) */
2067 CFDataRef hexData
= cfToHexData(identifierData
, true);
2068 if (!hexData
) { return false; }
2069 ok
= ok
&& SecDbBindInt64(deleteIdentifier
, 1,
2070 groupId
, &localError
);
2071 ok
= ok
&& SecDbBindBlob(deleteIdentifier
, 2,
2072 CFDataGetBytePtr(hexData
),
2073 CFDataGetLength(hexData
),
2074 SQLITE_TRANSIENT
, &localError
);
2075 /* Execute the delete statement for the identifier record. */
2076 ok
= ok
&& SecDbStep(dbc
->dbconn
, deleteIdentifier
, &localError
, NULL
);
2077 CFReleaseSafe(hexData
);
2080 if (ok
) { ++processed
; }
2083 secdebug("validupdate", "Processed %ld of %ld deletions for group %lld, result=%s",
2084 processed
, identifierCount
, groupId
, (ok
) ? "true" : "false");
2087 /* process additions */
2088 CFArrayRef addArray
= (CFArrayRef
)CFDictionaryGetValue(dict
, CFSTR("add"));
2089 if (isArray(addArray
)) {
2090 SecValidInfoFormat format
= kSecValidInfoFormatUnknown
;
2091 CFIndex processed
=0, identifierIX
, identifierCount
= CFArrayGetCount(addArray
);
2092 for (identifierIX
=0; identifierIX
<identifierCount
; identifierIX
++) {
2093 CFDataRef identifierData
= (CFDataRef
)CFArrayGetValueAtIndex(addArray
, identifierIX
);
2094 if (!identifierData
) { continue; }
2095 if (format
== kSecValidInfoFormatUnknown
) {
2096 format
= _SecRevocationDbGetGroupFormatForData(dbc
, groupId
, identifierData
);
2098 CFStringRef sql
= NULL
;
2099 if (format
== kSecValidInfoFormatSerial
) {
2100 sql
= insertSerialRecordSQL
;
2101 } else if (format
== kSecValidInfoFormatSHA256
) {
2102 sql
= insertSha256RecordSQL
;
2104 if (!sql
) { continue; }
2106 ok
= ok
&& SecDbWithSQL(dbc
->dbconn
, sql
, &localError
, ^bool(sqlite3_stmt
*insertIdentifier
) {
2107 /* rowid,(groupid,serial|sha256) */
2108 /* rowid is autoincremented and we never set it directly */
2109 ok
= ok
&& SecDbBindInt64(insertIdentifier
, 1,
2110 groupId
, &localError
);
2111 ok
= ok
&& SecDbBindBlob(insertIdentifier
, 2,
2112 CFDataGetBytePtr(identifierData
),
2113 CFDataGetLength(identifierData
),
2114 SQLITE_TRANSIENT
, &localError
);
2115 /* Execute the insert statement for the identifier record. */
2116 ok
= ok
&& SecDbStep(dbc
->dbconn
, insertIdentifier
, &localError
, NULL
);
2119 if (ok
) { ++processed
; }
2122 secdebug("validupdate", "Processed %ld of %ld additions for group %lld, result=%s",
2123 processed
, identifierCount
, groupId
, (ok
) ? "true" : "false");
2126 if (!ok
|| localError
) {
2127 secerror("_SecRevocationDbUpdatePerIssuerData failed: %@", localError
);
2128 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationWrite
, TAFatalError
,
2129 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
2131 (void) CFErrorPropagate(localError
, error
);
2135 static bool _SecRevocationDbCopyDateConstraints(SecRevocationDbConnectionRef dbc
,
2136 int64_t groupId
, CFDateRef
*notBeforeDate
, CFDateRef
*notAfterDate
, CFErrorRef
*error
) {
2137 /* return true if one or both date constraints exist for a given groupId.
2138 the actual constraints are optionally returned in output CFDateRef parameters.
2139 caller is responsible for releasing date and error parameters, if provided.
2141 __block
bool ok
= (dbc
!= NULL
);
2142 __block CFDateRef localNotBefore
= NULL
;
2143 __block CFDateRef localNotAfter
= NULL
;
2144 __block CFErrorRef localError
= NULL
;
2146 ok
= ok
&& SecDbWithSQL(dbc
->dbconn
, selectDateRecordSQL
, &localError
, ^bool(sqlite3_stmt
*selectDates
) {
2147 /* (groupid,notbefore,notafter) */
2148 ok
&= SecDbBindInt64(selectDates
, 1, groupId
, &localError
);
2149 ok
= ok
&& SecDbStep(dbc
->dbconn
, selectDates
, &localError
, ^(bool *stop
) {
2150 /* if column has no value, its type will be SQLITE_NULL */
2151 if (SQLITE_NULL
!= sqlite3_column_type(selectDates
, 0)) {
2152 CFAbsoluteTime nb
= (CFAbsoluteTime
)sqlite3_column_double(selectDates
, 0);
2153 localNotBefore
= CFDateCreate(NULL
, nb
);
2155 if (SQLITE_NULL
!= sqlite3_column_type(selectDates
, 1)) {
2156 CFAbsoluteTime na
= (CFAbsoluteTime
)sqlite3_column_double(selectDates
, 1);
2157 localNotAfter
= CFDateCreate(NULL
, na
);
2162 /* must have at least one date constraint to return true.
2163 since date constraints are optional, not finding any should not log an error. */
2164 ok
= ok
&& !localError
&& (localNotBefore
!= NULL
|| localNotAfter
!= NULL
);
2166 secerror("_SecRevocationDbCopyDateConstraints failed: %@", localError
);
2167 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationRead
, TAFatalError
,
2168 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
2171 CFReleaseNull(localNotBefore
);
2172 CFReleaseNull(localNotAfter
);
2174 if (notBeforeDate
) {
2175 *notBeforeDate
= localNotBefore
;
2177 CFReleaseSafe(localNotBefore
);
2180 *notAfterDate
= localNotAfter
;
2182 CFReleaseSafe(localNotAfter
);
2185 (void) CFErrorPropagate(localError
, error
);
2189 static bool _SecRevocationDbUpdateIssuerConstraints(SecRevocationDbConnectionRef dbc
, int64_t groupId
, CFDictionaryRef dict
, CFErrorRef
*error
) {
2190 /* update optional records in dates, names, or policies tables. */
2191 if (!dbc
|| !dict
|| groupId
< 0) {
2192 return false; /* must have something to insert, and a group to associate with it */
2194 __block
bool ok
= true;
2195 __block CFErrorRef localError
= NULL
;
2196 __block CFAbsoluteTime notBefore
= -3155760000.0; /* default: 1901-01-01 00:00:00-0000 */
2197 __block CFAbsoluteTime notAfter
= 31556908800.0; /* default: 3001-01-01 00:00:00-0000 */
2199 CFDateRef notBeforeDate
= (CFDateRef
)CFDictionaryGetValue(dict
, CFSTR("not-before"));
2200 CFDateRef notAfterDate
= (CFDateRef
)CFDictionaryGetValue(dict
, CFSTR("not-after"));
2201 if (isDate(notBeforeDate
)) {
2202 notBefore
= CFDateGetAbsoluteTime(notBeforeDate
);
2204 notBeforeDate
= NULL
;
2206 if (isDate(notAfterDate
)) {
2207 notAfter
= CFDateGetAbsoluteTime(notAfterDate
);
2209 notAfterDate
= NULL
;
2211 if (!(notBeforeDate
|| notAfterDate
)) {
2212 return ok
; /* no dates supplied, so we have nothing to update for this issuer */
2215 if (!(notBeforeDate
&& notAfterDate
) && !dbc
->fullUpdate
) {
2216 /* only one date was supplied, so check for existing date constraints */
2217 CFDateRef curNotBeforeDate
= NULL
;
2218 CFDateRef curNotAfterDate
= NULL
;
2219 if (_SecRevocationDbCopyDateConstraints(dbc
, groupId
, &curNotBeforeDate
,
2220 &curNotAfterDate
, &localError
)) {
2221 if (!notBeforeDate
) {
2222 notBeforeDate
= curNotBeforeDate
;
2223 notBefore
= CFDateGetAbsoluteTime(notBeforeDate
);
2225 CFReleaseSafe(curNotBeforeDate
);
2227 if (!notAfterDate
) {
2228 notAfterDate
= curNotAfterDate
;
2229 notAfter
= CFDateGetAbsoluteTime(notAfterDate
);
2231 CFReleaseSafe(curNotAfterDate
);
2236 ok
= ok
&& SecDbWithSQL(dbc
->dbconn
, insertDateRecordSQL
, &localError
, ^bool(sqlite3_stmt
*insertDate
) {
2237 /* (groupid,notbefore,notafter) */
2238 ok
= ok
&& SecDbBindInt64(insertDate
, 1, groupId
, &localError
);
2239 ok
= ok
&& SecDbBindDouble(insertDate
, 2, notBefore
, &localError
);
2240 ok
= ok
&& SecDbBindDouble(insertDate
, 3, notAfter
, &localError
);
2241 ok
= ok
&& SecDbStep(dbc
->dbconn
, insertDate
, &localError
, NULL
);
2245 /* %%% (TBI:9254570,21234699) update name and policy constraint entries here */
2247 if (!ok
|| localError
) {
2248 secinfo("validupdate", "_SecRevocationDbUpdateIssuerConstraints failed (ok=%s, localError=%@)",
2249 (ok
) ? "1" : "0", localError
);
2250 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationWrite
, TAFatalError
,
2251 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
2254 (void) CFErrorPropagate(localError
, error
);
2258 static SecValidInfoFormat
_SecRevocationDbGetGroupFormat(SecRevocationDbConnectionRef dbc
,
2259 int64_t groupId
, SecValidInfoFlags
*flags
, CFDataRef
*data
, CFErrorRef
*error
) {
2260 /* return group record fields for a given groupId.
2261 on success, returns a non-zero format type, and other field values in optional output parameters.
2262 caller is responsible for releasing data and error parameters, if provided.
2264 __block
bool ok
= (dbc
!= NULL
);
2265 __block SecValidInfoFormat format
= 0;
2266 __block CFErrorRef localError
= NULL
;
2268 /* Select the group record to determine flags and format. */
2269 ok
= ok
&& SecDbWithSQL(dbc
->dbconn
, selectGroupRecordSQL
, &localError
, ^bool(sqlite3_stmt
*selectGroup
) {
2270 ok
= ok
&& SecDbBindInt64(selectGroup
, 1, groupId
, &localError
);
2271 ok
= ok
&& SecDbStep(dbc
->dbconn
, selectGroup
, &localError
, ^(bool *stop
) {
2273 *flags
= (SecValidInfoFlags
)sqlite3_column_int(selectGroup
, 0);
2275 format
= (SecValidInfoFormat
)sqlite3_column_int(selectGroup
, 1);
2277 //%%% stream the data from the db into a streamed decompression <rdar://32142637>
2278 uint8_t *p
= (uint8_t *)sqlite3_column_blob(selectGroup
, 2);
2279 if (p
!= NULL
&& format
== kSecValidInfoFormatNto1
) {
2280 CFIndex length
= (CFIndex
)sqlite3_column_bytes(selectGroup
, 2);
2281 *data
= CFDataCreate(kCFAllocatorDefault
, p
, length
);
2287 if (!ok
|| localError
) {
2288 secdebug("validupdate", "GetGroupFormat for groupId %lu failed", (unsigned long)groupId
);
2289 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationRead
, TAFatalError
,
2290 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
2291 format
= kSecValidInfoFormatUnknown
;
2293 (void) CFErrorPropagate(localError
, error
);
2294 if (!(format
> kSecValidInfoFormatUnknown
)) {
2295 secdebug("validupdate", "GetGroupFormat: got format %d for groupId %lld", format
, (long long)groupId
);
2300 static bool _SecRevocationDbUpdateFlags(CFDictionaryRef dict
, CFStringRef key
, SecValidInfoFlags mask
, SecValidInfoFlags
*flags
) {
2301 /* If a boolean value exists in the given dictionary for the given key,
2302 or an explicit "1" or "0" is specified as the key string,
2303 set or clear the corresponding bit(s) defined by the mask argument.
2304 Function returns true if the flags value was changed, false otherwise.
2306 if (!isDictionary(dict
) || !isString(key
) || !flags
) {
2309 bool hasValue
= false, newValue
= false, result
= false;
2310 CFTypeRef value
= (CFBooleanRef
)CFDictionaryGetValue(dict
, key
);
2311 if (isBoolean(value
)) {
2312 newValue
= CFBooleanGetValue((CFBooleanRef
)value
);
2314 } else if (BOOL_STRING_KEY_LENGTH
== CFStringGetLength(key
)) {
2315 if (CFStringCompare(key
, kBoolTrueKey
, 0) == kCFCompareEqualTo
) {
2316 hasValue
= newValue
= true;
2317 } else if (CFStringCompare(key
, kBoolFalseKey
, 0) == kCFCompareEqualTo
) {
2322 SecValidInfoFlags oldFlags
= *flags
;
2328 result
= (*flags
!= oldFlags
);
2333 static bool _SecRevocationDbUpdateFilter(CFDictionaryRef dict
, CFDataRef oldData
, CFDataRef
* __nonnull CF_RETURNS_RETAINED xmlData
) {
2334 /* If xor and/or params values exist in the given dictionary, create a new
2335 property list containing the updated values, and return as a flattened
2336 data blob in the xmlData output parameter (note: caller must release.)
2337 Function returns true if there is new xmlData to save, false otherwise.
2339 bool result
= false;
2340 bool xorProvided
= false;
2341 bool paramsProvided
= false;
2342 bool missingData
= false;
2344 if (!dict
|| !xmlData
) {
2345 return result
; /* no-op if no dictionary is provided, or no way to update the data */
2348 CFDataRef xorCurrent
= NULL
;
2349 CFDataRef xorUpdate
= (CFDataRef
)CFDictionaryGetValue(dict
, CFSTR("xor"));
2350 if (isData(xorUpdate
)) {
2353 CFArrayRef paramsCurrent
= NULL
;
2354 CFArrayRef paramsUpdate
= (CFArrayRef
)CFDictionaryGetValue(dict
, CFSTR("params"));
2355 if (isArray(paramsUpdate
)) {
2356 paramsProvided
= true;
2358 if (!(xorProvided
|| paramsProvided
)) {
2359 return result
; /* nothing to update, so we can bail out here. */
2362 CFPropertyListRef nto1Current
= NULL
;
2363 CFMutableDictionaryRef nto1Update
= CFDictionaryCreateMutable(kCFAllocatorDefault
, 0,
2364 &kCFTypeDictionaryKeyCallBacks
,
2365 &kCFTypeDictionaryValueCallBacks
);
2370 /* turn old data into property list */
2371 CFDataRef data
= (CFDataRef
)CFRetainSafe(oldData
);
2372 CFDataRef inflatedData
= copyInflatedData(data
);
2374 CFReleaseSafe(data
);
2375 data
= inflatedData
;
2378 nto1Current
= CFPropertyListCreateWithData(kCFAllocatorDefault
, data
, 0, NULL
, NULL
);
2379 CFReleaseSafe(data
);
2382 xorCurrent
= (CFDataRef
)CFDictionaryGetValue((CFDictionaryRef
)nto1Current
, CFSTR("xor"));
2383 paramsCurrent
= (CFArrayRef
)CFDictionaryGetValue((CFDictionaryRef
)nto1Current
, CFSTR("params"));
2386 /* set current or updated xor data in new property list */
2388 CFDataRef xorNew
= NULL
;
2390 CFIndex xorUpdateLen
= CFDataGetLength(xorUpdate
);
2391 CFMutableDataRef
xor = CFDataCreateMutableCopy(NULL
, 0, xorCurrent
);
2392 if (xor && xorUpdateLen
> 0) {
2393 /* truncate or zero-extend data to match update size */
2394 CFDataSetLength(xor, xorUpdateLen
);
2395 /* exclusive-or update bytes over the existing data */
2396 UInt8
*xorP
= (UInt8
*)CFDataGetMutableBytePtr(xor);
2397 UInt8
*updP
= (UInt8
*)CFDataGetBytePtr(xorUpdate
);
2399 for (int idx
= 0; idx
< xorUpdateLen
; idx
++) {
2400 xorP
[idx
] = xorP
[idx
] ^ updP
[idx
];
2404 xorNew
= (CFDataRef
)xor;
2406 xorNew
= (CFDataRef
)CFRetainSafe(xorUpdate
);
2409 CFDictionaryAddValue(nto1Update
, CFSTR("xor"), xorNew
);
2410 CFReleaseSafe(xorNew
);
2412 secdebug("validupdate", "Failed to get updated filter data");
2415 } else if (xorCurrent
) {
2416 /* not provided, so use existing xor value */
2417 CFDictionaryAddValue(nto1Update
, CFSTR("xor"), xorCurrent
);
2419 secdebug("validupdate", "Failed to get current filter data");
2423 /* set current or updated params in new property list */
2424 if (paramsProvided
) {
2425 CFDictionaryAddValue(nto1Update
, CFSTR("params"), paramsUpdate
);
2426 } else if (paramsCurrent
) {
2427 /* not provided, so use existing params value */
2428 CFDictionaryAddValue(nto1Update
, CFSTR("params"), paramsCurrent
);
2430 /* missing params: neither provided nor existing */
2431 secdebug("validupdate", "Failed to get current filter params");
2435 CFReleaseSafe(nto1Current
);
2437 *xmlData
= CFPropertyListCreateData(kCFAllocatorDefault
, nto1Update
,
2438 kCFPropertyListXMLFormat_v1_0
,
2440 result
= (*xmlData
!= NULL
);
2442 CFReleaseSafe(nto1Update
);
2444 /* compress the xmlData blob, if possible */
2446 CFDataRef deflatedData
= copyDeflatedData(*xmlData
);
2448 if (CFDataGetLength(deflatedData
) < CFDataGetLength(*xmlData
)) {
2449 CFRelease(*xmlData
);
2450 *xmlData
= deflatedData
;
2452 CFRelease(deflatedData
);
2460 static int64_t _SecRevocationDbUpdateGroup(SecRevocationDbConnectionRef dbc
, int64_t groupId
, CFDictionaryRef dict
, CFErrorRef
*error
) {
2461 /* insert group record for a given groupId.
2462 if the specified groupId is < 0, a new group entry is created.
2463 returns the groupId on success, or -1 on failure.
2466 return groupId
; /* no-op if no dictionary is provided */
2469 __block
int64_t result
= -1;
2470 __block
bool ok
= (dbc
!= NULL
);
2471 __block
bool isFormatChange
= false;
2472 __block CFErrorRef localError
= NULL
;
2474 __block SecValidInfoFlags flags
= 0;
2475 __block SecValidInfoFormat format
= kSecValidInfoFormatUnknown
;
2476 __block SecValidInfoFormat formatUpdate
= kSecValidInfoFormatUnknown
;
2477 __block CFDataRef data
= NULL
;
2480 /* fetch the flags and data for an existing group record, in case some are being changed. */
2482 format
= _SecRevocationDbGetGroupFormat(dbc
, groupId
, &flags
, &data
, NULL
);
2484 if (format
== kSecValidInfoFormatUnknown
) {
2485 secdebug("validupdate", "existing group %lld has unknown format %d, flags=0x%lx",
2486 (long long)groupId
, format
, flags
);
2487 //%%% clean up by deleting all issuers with this groupId, then the group record,
2488 // or just force a full update? note: we can get here if we fail to bind the
2489 // format value in the prepared SQL statement below.
2493 CFTypeRef value
= (CFStringRef
)CFDictionaryGetValue(dict
, CFSTR("format"));
2494 if (isString(value
)) {
2495 if (CFStringCompare((CFStringRef
)value
, CFSTR("serial"), 0) == kCFCompareEqualTo
) {
2496 formatUpdate
= kSecValidInfoFormatSerial
;
2497 } else if (CFStringCompare((CFStringRef
)value
, CFSTR("sha256"), 0) == kCFCompareEqualTo
) {
2498 formatUpdate
= kSecValidInfoFormatSHA256
;
2499 } else if (CFStringCompare((CFStringRef
)value
, CFSTR("nto1"), 0) == kCFCompareEqualTo
) {
2500 formatUpdate
= kSecValidInfoFormatNto1
;
2503 /* if format value is explicitly supplied, then this is effectively a new group entry. */
2504 isFormatChange
= (formatUpdate
> kSecValidInfoFormatUnknown
&&
2505 formatUpdate
!= format
&&
2508 if (isFormatChange
) {
2509 secdebug("validupdate", "group %lld format change from %d to %d",
2510 (long long)groupId
, format
, formatUpdate
);
2511 /* format of an existing group is changing; delete the group first.
2512 this should ensure that all entries referencing the old groupid are deleted.
2514 ok
= ok
&& SecDbWithSQL(dbc
->dbconn
, deleteGroupRecordSQL
, &localError
, ^bool(sqlite3_stmt
*deleteResponse
) {
2515 ok
= ok
&& SecDbBindInt64(deleteResponse
, 1, groupId
, &localError
);
2516 /* Execute the delete statement. */
2517 ok
= ok
&& SecDbStep(dbc
->dbconn
, deleteResponse
, &localError
, NULL
);
2521 ok
= ok
&& SecDbWithSQL(dbc
->dbconn
, insertGroupRecordSQL
, &localError
, ^bool(sqlite3_stmt
*insertGroup
) {
2522 /* (groupid,flags,format,data) */
2523 /* groups.groupid */
2524 if (ok
&& (!isFormatChange
) && (groupId
>= 0)) {
2525 /* bind to existing groupId row if known, otherwise will insert and autoincrement */
2526 ok
= SecDbBindInt64(insertGroup
, 1, groupId
, &localError
);
2528 secdebug("validupdate", "failed to set groupId %lld", (long long)groupId
);
2533 (void)_SecRevocationDbUpdateFlags(dict
, CFSTR("complete"), kSecValidInfoComplete
, &flags
);
2534 (void)_SecRevocationDbUpdateFlags(dict
, CFSTR("check-ocsp"), kSecValidInfoCheckOCSP
, &flags
);
2535 (void)_SecRevocationDbUpdateFlags(dict
, CFSTR("known-intermediates-only"), kSecValidInfoKnownOnly
, &flags
);
2536 (void)_SecRevocationDbUpdateFlags(dict
, CFSTR("require-ct"), kSecValidInfoRequireCT
, &flags
);
2537 (void)_SecRevocationDbUpdateFlags(dict
, CFSTR("valid"), kSecValidInfoAllowlist
, &flags
);
2538 (void)_SecRevocationDbUpdateFlags(dict
, CFSTR("no-ca"), kSecValidInfoNoCACheck
, &flags
);
2539 (void)_SecRevocationDbUpdateFlags(dict
, CFSTR("no-ca-v2"), kSecValidInfoNoCAv2Check
, &flags
);
2540 (void)_SecRevocationDbUpdateFlags(dict
, CFSTR("overridable"), kSecValidInfoOverridable
, &flags
);
2542 /* date constraints exist if either "not-before" or "not-after" keys are found */
2543 CFTypeRef notBeforeValue
= (CFDateRef
)CFDictionaryGetValue(dict
, CFSTR("not-before"));
2544 CFTypeRef notAfterValue
= (CFDateRef
)CFDictionaryGetValue(dict
, CFSTR("not-after"));
2545 if (isDate(notBeforeValue
) || isDate(notAfterValue
)) {
2546 (void)_SecRevocationDbUpdateFlags(dict
, kBoolTrueKey
, kSecValidInfoDateConstraints
, &flags
);
2547 /* Note that the spec defines not-before and not-after dates as optional, such that
2548 not providing one does not change the database contents. Therefore, we can never clear
2549 this flag; either a new date entry will be supplied, or a format change will cause
2550 the entire group entry to be deleted. */
2553 /* %%% (TBI:9254570,21234699) name and policy constraints don't exist yet */
2554 (void)_SecRevocationDbUpdateFlags(dict
, kBoolFalseKey
, kSecValidInfoNameConstraints
, &flags
);
2555 (void)_SecRevocationDbUpdateFlags(dict
, kBoolFalseKey
, kSecValidInfoPolicyConstraints
, &flags
);
2557 ok
= SecDbBindInt(insertGroup
, 2, (int)flags
, &localError
);
2559 secdebug("validupdate", "failed to set flags (%lu) for groupId %lld", flags
, (long long)groupId
);
2564 SecValidInfoFormat formatValue
= format
;
2565 if (formatUpdate
> kSecValidInfoFormatUnknown
) {
2566 formatValue
= formatUpdate
;
2568 ok
= SecDbBindInt(insertGroup
, 3, (int)formatValue
, &localError
);
2570 secdebug("validupdate", "failed to set format (%d) for groupId %lld", formatValue
, (long long)groupId
);
2574 CFDataRef xmlData
= NULL
;
2576 bool hasFilter
= ((formatUpdate
== kSecValidInfoFormatNto1
) ||
2577 (formatUpdate
== kSecValidInfoFormatUnknown
&&
2578 format
== kSecValidInfoFormatNto1
));
2580 CFDataRef dataValue
= data
; /* use existing data */
2581 if (_SecRevocationDbUpdateFilter(dict
, data
, &xmlData
)) {
2582 dataValue
= xmlData
; /* use updated data */
2585 ok
= SecDbBindBlob(insertGroup
, 4,
2586 CFDataGetBytePtr(dataValue
),
2587 CFDataGetLength(dataValue
),
2588 SQLITE_TRANSIENT
, &localError
);
2591 secdebug("validupdate", "failed to set data for groupId %lld",
2592 (long long)groupId
);
2595 /* else there is no data, so NULL is implicitly bound to column 4 */
2598 /* Execute the insert statement for the group record. */
2600 ok
= SecDbStep(dbc
->dbconn
, insertGroup
, &localError
, NULL
);
2602 secdebug("validupdate", "failed to execute insertGroup statement for groupId %lld",
2603 (long long)groupId
);
2605 result
= (int64_t)sqlite3_last_insert_rowid(SecDbHandle(dbc
->dbconn
));
2608 secdebug("validupdate", "failed to insert group %lld", (long long)result
);
2610 /* Clean up temporary allocation made in this block. */
2611 CFReleaseSafe(xmlData
);
2612 CFReleaseSafe(data
);
2615 if (!ok
|| localError
) {
2616 secerror("_SecRevocationDbUpdateGroup failed: %@", localError
);
2617 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationWrite
, TAFatalError
,
2618 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
2620 (void) CFErrorPropagate(localError
, error
);
2624 static int64_t _SecRevocationDbGroupIdForIssuerHash(SecRevocationDbConnectionRef dbc
, CFDataRef hash
, CFErrorRef
*error
) {
2625 /* look up issuer hash in issuers table to get groupid, if it exists */
2626 __block
int64_t groupId
= -1;
2627 __block
bool ok
= (dbc
!= NULL
);
2628 __block CFErrorRef localError
= NULL
;
2631 secdebug("validupdate", "failed to get hash (%@)", hash
);
2633 require(hash
&& dbc
, errOut
);
2635 /* This is the starting point for any lookup; find a group id for the given issuer hash.
2636 Before we do that, need to verify the current db_version. We cannot use results from a
2637 database created with a schema version older than the minimum supported version.
2638 However, we may be able to use results from a newer version. At the next database
2639 update interval, if the existing schema is old, we'll be removing and recreating
2640 the database contents with the current schema version.
2642 int64_t db_version
= _SecRevocationDbGetSchemaVersion(dbc
->db
, dbc
, NULL
);
2643 if (db_version
< kSecRevocationDbMinSchemaVersion
) {
2644 if (!dbc
->db
->unsupportedVersion
) {
2645 secdebug("validupdate", "unsupported db_version: %lld", (long long)db_version
);
2646 dbc
->db
->unsupportedVersion
= true; /* only warn once for a given unsupported version */
2649 require_quiet(db_version
>= kSecRevocationDbMinSchemaVersion
, errOut
);
2651 /* Look up provided issuer_hash in the issuers table.
2653 ok
= ok
&& SecDbWithSQL(dbc
->dbconn
, selectGroupIdSQL
, &localError
, ^bool(sqlite3_stmt
*selectGroupId
) {
2654 ok
&= SecDbBindBlob(selectGroupId
, 1, CFDataGetBytePtr(hash
), CFDataGetLength(hash
), SQLITE_TRANSIENT
, &localError
);
2655 ok
&= SecDbStep(dbc
->dbconn
, selectGroupId
, &localError
, ^(bool *stopGroupId
) {
2656 groupId
= sqlite3_column_int64(selectGroupId
, 0);
2662 if (!ok
|| localError
) {
2663 secerror("_SecRevocationDbGroupIdForIssuerHash failed: %@", localError
);
2664 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationRead
, TAFatalError
,
2665 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
2667 (void) CFErrorPropagate(localError
, error
);
2671 static bool _SecRevocationDbApplyGroupDelete(SecRevocationDbConnectionRef dbc
, CFDataRef issuerHash
, CFErrorRef
*error
) {
2672 /* delete group associated with the given issuer;
2673 schema trigger will delete associated issuers, serials, and hashes. */
2674 __block
int64_t groupId
= -1;
2675 __block
bool ok
= (dbc
!= NULL
);
2676 __block CFErrorRef localError
= NULL
;
2679 groupId
= _SecRevocationDbGroupIdForIssuerHash(dbc
, issuerHash
, &localError
);
2683 SecError(errSecParam
, &localError
, CFSTR("group not found for issuer"));
2687 ok
= ok
&& SecDbWithSQL(dbc
->dbconn
, deleteGroupRecordSQL
, &localError
, ^bool(sqlite3_stmt
*deleteResponse
) {
2688 ok
&= SecDbBindInt64(deleteResponse
, 1, groupId
, &localError
);
2689 /* Execute the delete statement. */
2690 ok
= ok
&& SecDbStep(dbc
->dbconn
, deleteResponse
, &localError
, NULL
);
2693 if (!ok
|| localError
) {
2694 secerror("_SecRevocationDbApplyGroupDelete failed: %@", localError
);
2695 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationWrite
, TAFatalError
,
2696 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
2698 (void) CFErrorPropagate(localError
, error
);
2702 static bool _SecRevocationDbApplyGroupUpdate(SecRevocationDbConnectionRef dbc
, CFDictionaryRef dict
, CFErrorRef
*error
) {
2703 /* process one issuer group's update dictionary */
2704 __block
int64_t groupId
= -1;
2705 __block
bool ok
= (dbc
!= NULL
);
2706 __block CFErrorRef localError
= NULL
;
2708 CFArrayRef issuers
= (dict
) ? (CFArrayRef
)CFDictionaryGetValue(dict
, CFSTR("issuer-hash")) : NULL
;
2709 /* if this is not a full update, then look for existing group id */
2710 if (ok
&& isArray(issuers
) && !dbc
->fullUpdate
) {
2711 CFIndex issuerIX
, issuerCount
= CFArrayGetCount(issuers
);
2712 /* while we have issuers and haven't found a matching group id */
2713 for (issuerIX
=0; issuerIX
<issuerCount
&& groupId
< 0; issuerIX
++) {
2714 CFDataRef hash
= (CFDataRef
)CFArrayGetValueAtIndex(issuers
, issuerIX
);
2715 if (!hash
) { continue; }
2716 groupId
= _SecRevocationDbGroupIdForIssuerHash(dbc
, hash
, &localError
);
2719 /* according to the spec, we must replace all existing issuers with
2720 the new issuers list, so delete all issuers in the group first. */
2721 ok
= ok
&& SecDbWithSQL(dbc
->dbconn
, deleteGroupIssuersSQL
, &localError
, ^bool(sqlite3_stmt
*deleteIssuers
) {
2722 ok
= ok
&& SecDbBindInt64(deleteIssuers
, 1, groupId
, &localError
);
2723 ok
= ok
&& SecDbStep(dbc
->dbconn
, deleteIssuers
, &localError
, NULL
);
2728 /* create or update the group entry */
2730 groupId
= _SecRevocationDbUpdateGroup(dbc
, groupId
, dict
, &localError
);
2733 secdebug("validupdate", "failed to get groupId");
2736 /* create or update issuer entries, now that we know the group id */
2737 ok
= ok
&& _SecRevocationDbUpdateIssuers(dbc
, groupId
, issuers
, &localError
);
2738 /* create or update entries in serials or hashes tables */
2739 ok
= ok
&& _SecRevocationDbUpdateIssuerData(dbc
, groupId
, dict
, &localError
);
2740 /* create or update entries in dates/names/policies tables */
2741 ok
= ok
&& _SecRevocationDbUpdateIssuerConstraints(dbc
, groupId
, dict
, &localError
);
2744 (void) CFErrorPropagate(localError
, error
);
2748 bool _SecRevocationDbApplyUpdate(SecRevocationDbConnectionRef dbc
, CFDictionaryRef update
, CFIndex version
, CFErrorRef
*error
) {
2749 /* process entire update dictionary */
2750 if (!dbc
|| !dbc
->db
|| !update
) {
2751 secerror("_SecRevocationDbApplyUpdate failed: invalid args");
2752 SecError(errSecParam
, error
, CFSTR("_SecRevocationDbApplyUpdate: invalid db or update parameter"));
2756 CFDictionaryRef localUpdate
= (CFDictionaryRef
)CFRetainSafe(update
);
2757 CFErrorRef localError
= NULL
;
2760 CFTypeRef value
= NULL
;
2761 CFIndex deleteCount
= 0;
2762 CFIndex updateCount
= 0;
2764 dbc
->db
->updateInProgress
= true;
2766 /* check whether this is a full update */
2767 value
= (CFBooleanRef
)CFDictionaryGetValue(update
, CFSTR("full"));
2768 if (isBoolean(value
) && CFBooleanGetValue((CFBooleanRef
)value
)) {
2769 /* clear the database before processing a full update */
2770 dbc
->fullUpdate
= true;
2771 secdebug("validupdate", "update has \"full\" attribute; clearing database");
2772 ok
= ok
&& _SecRevocationDbRemoveAllEntries(dbc
, &localError
);
2775 /* process 'delete' list */
2776 value
= (CFArrayRef
)CFDictionaryGetValue(localUpdate
, CFSTR("delete"));
2777 if (isArray(value
)) {
2778 deleteCount
= CFArrayGetCount((CFArrayRef
)value
);
2779 secdebug("validupdate", "processing %ld deletes", (long)deleteCount
);
2780 for (CFIndex deleteIX
=0; deleteIX
<deleteCount
; deleteIX
++) {
2781 CFDataRef issuerHash
= (CFDataRef
)CFArrayGetValueAtIndex((CFArrayRef
)value
, deleteIX
);
2782 if (isData(issuerHash
)) {
2783 ok
= ok
&& _SecRevocationDbApplyGroupDelete(dbc
, issuerHash
, &localError
);
2785 secdebug("validupdate", "skipping delete %ld (hash is not a data value)", (long)deleteIX
);
2790 /* process 'update' list */
2791 value
= (CFArrayRef
)CFDictionaryGetValue(localUpdate
, CFSTR("update"));
2792 if (isArray(value
)) {
2793 updateCount
= CFArrayGetCount((CFArrayRef
)value
);
2794 secdebug("validupdate", "processing %ld updates", (long)updateCount
);
2795 for (CFIndex updateIX
=0; updateIX
<updateCount
; updateIX
++) {
2796 CFDictionaryRef dict
= (CFDictionaryRef
)CFArrayGetValueAtIndex((CFArrayRef
)value
, updateIX
);
2797 if (isDictionary(dict
)) {
2798 ok
= ok
&& _SecRevocationDbApplyGroupUpdate(dbc
, dict
, &localError
);
2800 secdebug("validupdate", "skipping update %ld (not a dictionary)", (long)updateIX
);
2804 CFReleaseSafe(localUpdate
);
2807 ok
= ok
&& _SecRevocationDbSetVersion(dbc
, version
, &localError
);
2809 /* set db_version if not already set */
2810 int64_t db_version
= _SecRevocationDbGetSchemaVersion(dbc
->db
, dbc
, NULL
);
2811 if (db_version
<= 0) {
2812 ok
= ok
&& _SecRevocationDbSetSchemaVersion(dbc
, kSecRevocationDbSchemaVersion
, &localError
);
2815 /* set db_format if not already set */
2816 int64_t db_format
= _SecRevocationDbGetUpdateFormat(dbc
, NULL
);
2817 if (db_format
<= 0) {
2818 ok
= ok
&& _SecRevocationDbSetUpdateFormat(dbc
, kSecRevocationDbUpdateFormat
, &localError
);
2821 /* purge the in-memory cache */
2822 SecRevocationDbCachePurge(dbc
->db
);
2824 dbc
->db
->updateInProgress
= false;
2826 (void) CFErrorPropagate(localError
, error
);
2830 static bool _SecRevocationDbSerialInGroup(SecRevocationDbConnectionRef dbc
,
2833 CFErrorRef
*error
) {
2834 __block
bool result
= false;
2835 __block
bool ok
= true;
2836 __block CFErrorRef localError
= NULL
;
2837 require(dbc
&& serial
, errOut
);
2838 ok
&= SecDbWithSQL(dbc
->dbconn
, selectSerialRecordSQL
, &localError
, ^bool(sqlite3_stmt
*selectSerial
) {
2839 ok
&= SecDbBindInt64(selectSerial
, 1, groupId
, &localError
);
2840 ok
&= SecDbBindBlob(selectSerial
, 2, CFDataGetBytePtr(serial
),
2841 CFDataGetLength(serial
), SQLITE_TRANSIENT
, &localError
);
2842 ok
&= SecDbStep(dbc
->dbconn
, selectSerial
, &localError
, ^(bool *stop
) {
2843 int64_t foundRowId
= (int64_t)sqlite3_column_int64(selectSerial
, 0);
2844 result
= (foundRowId
> 0);
2850 if (!ok
|| localError
) {
2851 secerror("_SecRevocationDbSerialInGroup failed: %@", localError
);
2852 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationRead
, TAFatalError
,
2853 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
2855 (void) CFErrorPropagate(localError
, error
);
2859 static bool _SecRevocationDbCertHashInGroup(SecRevocationDbConnectionRef dbc
,
2862 CFErrorRef
*error
) {
2863 __block
bool result
= false;
2864 __block
bool ok
= true;
2865 __block CFErrorRef localError
= NULL
;
2866 require(dbc
&& certHash
, errOut
);
2867 ok
&= SecDbWithSQL(dbc
->dbconn
, selectHashRecordSQL
, &localError
, ^bool(sqlite3_stmt
*selectHash
) {
2868 ok
&= SecDbBindInt64(selectHash
, 1, groupId
, &localError
);
2869 ok
= SecDbBindBlob(selectHash
, 2, CFDataGetBytePtr(certHash
),
2870 CFDataGetLength(certHash
), SQLITE_TRANSIENT
, &localError
);
2871 ok
&= SecDbStep(dbc
->dbconn
, selectHash
, &localError
, ^(bool *stop
) {
2872 int64_t foundRowId
= (int64_t)sqlite3_column_int64(selectHash
, 0);
2873 result
= (foundRowId
> 0);
2879 if (!ok
|| localError
) {
2880 secerror("_SecRevocationDbCertHashInGroup failed: %@", localError
);
2881 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationRead
, TAFatalError
,
2882 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
2884 (void) CFErrorPropagate(localError
, error
);
2888 static bool _SecRevocationDbSerialInFilter(SecRevocationDbConnectionRef dbc
,
2889 CFDataRef serialData
,
2890 CFDataRef xmlData
) {
2891 /* N-To-1 filter implementation.
2892 The 'xmlData' parameter is a flattened XML dictionary,
2893 containing 'xor' and 'params' keys. First order of
2894 business is to reconstitute the blob into components.
2896 bool result
= false;
2897 CFRetainSafe(xmlData
);
2898 CFDataRef propListData
= xmlData
;
2899 /* Expand data blob if needed */
2900 CFDataRef inflatedData
= copyInflatedData(propListData
);
2902 CFReleaseSafe(propListData
);
2903 propListData
= inflatedData
;
2905 CFDataRef
xor = NULL
;
2906 CFArrayRef params
= NULL
;
2907 CFPropertyListRef nto1
= CFPropertyListCreateWithData(kCFAllocatorDefault
, propListData
, 0, NULL
, NULL
);
2909 xor = (CFDataRef
)CFDictionaryGetValue((CFDictionaryRef
)nto1
, CFSTR("xor"));
2910 params
= (CFArrayRef
)CFDictionaryGetValue((CFDictionaryRef
)nto1
, CFSTR("params"));
2912 uint8_t *hash
= (xor) ? (uint8_t*)CFDataGetBytePtr(xor) : NULL
;
2913 CFIndex hashLen
= (hash
) ? CFDataGetLength(xor) : 0;
2914 uint8_t *serial
= (serialData
) ? (uint8_t*)CFDataGetBytePtr(serialData
) : NULL
;
2915 CFIndex serialLen
= (serial
) ? CFDataGetLength(serialData
) : 0;
2917 require(hash
&& serial
&& params
, errOut
);
2919 const uint32_t FNV_OFFSET_BASIS
= 2166136261;
2920 const uint32_t FNV_PRIME
= 16777619;
2921 bool notInHash
= false;
2922 CFIndex ix
, count
= CFArrayGetCount(params
);
2923 for (ix
= 0; ix
< count
; ix
++) {
2925 CFNumberRef cfnum
= (CFNumberRef
)CFArrayGetValueAtIndex(params
, ix
);
2926 if (!isNumber(cfnum
) ||
2927 !CFNumberGetValue(cfnum
, kCFNumberSInt32Type
, ¶m
)) {
2928 secinfo("validupdate", "error processing filter params at index %ld", (long)ix
);
2931 /* process one param */
2932 uint32_t hval
= FNV_OFFSET_BASIS
^ param
;
2933 CFIndex i
= serialLen
;
2935 hval
= ((hval
^ (serial
[--i
])) * FNV_PRIME
) & 0xFFFFFFFF;
2937 hval
= hval
% (hashLen
* 8);
2938 if ((hash
[hval
/8] & (1 << (hval
% 8))) == 0) {
2939 notInHash
= true; /* definitely not in hash */
2944 /* probabilistically might be in hash if we get here. */
2949 CFReleaseSafe(nto1
);
2950 CFReleaseSafe(propListData
);
2954 static SecValidInfoRef
_SecRevocationDbValidInfoForCertificate(SecRevocationDbConnectionRef dbc
,
2955 SecCertificateRef certificate
,
2956 CFDataRef issuerHash
,
2957 CFErrorRef
*error
) {
2958 __block CFErrorRef localError
= NULL
;
2959 __block SecValidInfoFlags flags
= 0;
2960 __block SecValidInfoFormat format
= kSecValidInfoFormatUnknown
;
2961 __block CFDataRef data
= NULL
;
2963 bool matched
= false;
2964 bool isOnList
= false;
2965 int64_t groupId
= 0;
2966 CFDataRef serial
= NULL
;
2967 CFDataRef certHash
= NULL
;
2968 CFDateRef notBeforeDate
= NULL
;
2969 CFDateRef notAfterDate
= NULL
;
2970 CFDataRef nameConstraints
= NULL
;
2971 CFDataRef policyConstraints
= NULL
;
2972 SecValidInfoRef result
= NULL
;
2974 require((serial
= SecCertificateCopySerialNumberData(certificate
, NULL
)) != NULL
, errOut
);
2975 require((certHash
= SecCertificateCopySHA256Digest(certificate
)) != NULL
, errOut
);
2976 require((groupId
= _SecRevocationDbGroupIdForIssuerHash(dbc
, issuerHash
, &localError
)) > 0, errOut
);
2978 /* Look up the group record to determine flags and format. */
2979 format
= _SecRevocationDbGetGroupFormat(dbc
, groupId
, &flags
, &data
, &localError
);
2981 if (format
== kSecValidInfoFormatUnknown
) {
2982 /* No group record found for this issuer. Don't return a SecValidInfoRef */
2985 else if (format
== kSecValidInfoFormatSerial
) {
2986 /* Look up certificate's serial number in the serials table. */
2987 matched
= _SecRevocationDbSerialInGroup(dbc
, serial
, groupId
, &localError
);
2989 else if (format
== kSecValidInfoFormatSHA256
) {
2990 /* Look up certificate's SHA-256 hash in the hashes table. */
2991 matched
= _SecRevocationDbCertHashInGroup(dbc
, certHash
, groupId
, &localError
);
2993 else if (format
== kSecValidInfoFormatNto1
) {
2994 /* Perform a Bloom filter match against the serial. If matched is false,
2995 then the cert is definitely not in the list. But if matched is true,
2996 we don't know for certain, so we would need to check OCSP. */
2997 matched
= _SecRevocationDbSerialInFilter(dbc
, serial
, data
);
3001 /* Found a specific match for this certificate. */
3002 secdebug("validupdate", "Valid db matched certificate: %@, format=%d, flags=0x%lx",
3003 certHash
, format
, flags
);
3007 /* If supplemental constraints are present for this issuer, then we always match. */
3008 if ((flags
& kSecValidInfoDateConstraints
) &&
3009 (_SecRevocationDbCopyDateConstraints(dbc
, groupId
, ¬BeforeDate
, ¬AfterDate
, &localError
))) {
3010 secdebug("validupdate", "Valid db matched supplemental date constraints for groupId %lld: nb=%@, na=%@",
3011 (long long)groupId
, notBeforeDate
, notAfterDate
);
3015 /* Return SecValidInfo for certificates for which an issuer entry is found. */
3016 result
= SecValidInfoCreate(format
, flags
, isOnList
,
3017 certHash
, issuerHash
, /*anchorHash*/ NULL
,
3018 notBeforeDate
, notAfterDate
,
3019 nameConstraints
, policyConstraints
);
3021 if (result
&& SecIsAppleTrustAnchor(certificate
, 0)) {
3022 /* Prevent a catch-22. */
3023 secdebug("validupdate", "Valid db match for Apple trust anchor: %@, format=%d, flags=0x%lx",
3024 certHash
, format
, flags
);
3025 CFReleaseNull(result
);
3029 (void) CFErrorPropagate(localError
, error
);
3030 CFReleaseSafe(data
);
3031 CFReleaseSafe(certHash
);
3032 CFReleaseSafe(serial
);
3033 CFReleaseSafe(notBeforeDate
);
3034 CFReleaseSafe(notAfterDate
);
3035 CFReleaseSafe(nameConstraints
);
3036 CFReleaseSafe(policyConstraints
);
3040 static SecValidInfoRef
_SecRevocationDbCopyMatching(SecRevocationDbConnectionRef dbc
,
3041 SecCertificateRef certificate
,
3042 SecCertificateRef issuer
) {
3043 SecValidInfoRef result
= NULL
;
3044 CFErrorRef error
= NULL
;
3045 CFDataRef issuerHash
= NULL
;
3047 require(dbc
&& certificate
&& issuer
, errOut
);
3048 require(issuerHash
= SecCertificateCopySHA256Digest(issuer
), errOut
);
3050 /* Check for the result in the cache. */
3051 result
= SecRevocationDbCacheRead(dbc
->db
, certificate
, issuerHash
);
3053 /* Upon cache miss, get the result from the database and add it to the cache. */
3055 result
= _SecRevocationDbValidInfoForCertificate(dbc
, certificate
, issuerHash
, &error
);
3056 SecRevocationDbCacheWrite(dbc
->db
, result
);
3060 CFReleaseSafe(issuerHash
);
3061 CFReleaseSafe(error
);
3065 /* Return the update source as a retained CFStringRef.
3066 If the value cannot be obtained, NULL is returned.
3068 CFStringRef
SecRevocationDbCopyUpdateSource(void) {
3069 __block CFStringRef result
= NULL
;
3070 SecRevocationDbWith(^(SecRevocationDbRef db
) {
3071 (void) SecRevocationDbPerformRead(db
, NULL
, ^bool(SecRevocationDbConnectionRef dbc
, CFErrorRef
*blockError
) {
3072 result
= _SecRevocationDbCopyUpdateSource(dbc
, blockError
);
3073 return (bool)result
;
3079 /* Set the next update value for the revocation database.
3080 (This function is expected to be called only by the database
3081 maintainer, normally the system instance of trustd. If the
3082 caller does not have write access, this is a no-op.)
3084 bool SecRevocationDbSetNextUpdateTime(CFAbsoluteTime nextUpdate
, CFErrorRef
*error
) {
3085 __block
bool ok
= true;
3086 __block CFErrorRef localError
= NULL
;
3087 SecRevocationDbWith(^(SecRevocationDbRef rdb
) {
3088 ok
&= SecRevocationDbPerformWrite(rdb
, &localError
, ^bool(SecRevocationDbConnectionRef dbc
, CFErrorRef
*blockError
) {
3089 return _SecRevocationDbSetNextUpdateTime(dbc
, nextUpdate
, blockError
);
3092 (void) CFErrorPropagate(localError
, error
);
3096 /* Return the next update value as a CFAbsoluteTime.
3097 If the value cannot be obtained, -1 is returned.
3099 CFAbsoluteTime
SecRevocationDbGetNextUpdateTime(void) {
3100 __block CFAbsoluteTime result
= -1;
3101 SecRevocationDbWith(^(SecRevocationDbRef db
) {
3102 (void) SecRevocationDbPerformRead(db
, NULL
, ^bool(SecRevocationDbConnectionRef dbc
, CFErrorRef
*blockError
) {
3103 result
= _SecRevocationDbGetNextUpdateTime(dbc
, blockError
);
3110 /* Return the serial background queue for database updates.
3111 If the queue cannot be obtained, NULL is returned.
3113 dispatch_queue_t
SecRevocationDbGetUpdateQueue(void) {
3114 __block dispatch_queue_t result
= NULL
;
3115 SecRevocationDbWith(^(SecRevocationDbRef db
) {
3116 result
= (db
) ? db
->update_queue
: NULL
;
3121 /* Release all connections to the revocation database.
3123 void SecRevocationDbReleaseAllConnections(void) {
3124 SecRevocationDbWith(^(SecRevocationDbRef db
) {
3125 SecDbReleaseAllConnections((db
) ? db
->db
: NULL
);
3129 /* === SecRevocationDb API === */
3131 /* Given a certificate and its issuer, returns a SecValidInfoRef if the
3132 valid database contains matching info; otherwise returns NULL.
3133 Caller must release the returned SecValidInfoRef when finished.
3135 SecValidInfoRef
SecRevocationDbCopyMatching(SecCertificateRef certificate
,
3136 SecCertificateRef issuer
) {
3137 __block SecValidInfoRef result
= NULL
;
3138 SecRevocationDbWith(^(SecRevocationDbRef db
) {
3139 (void) SecRevocationDbPerformRead(db
, NULL
, ^bool(SecRevocationDbConnectionRef dbc
, CFErrorRef
*blockError
) {
3140 result
= _SecRevocationDbCopyMatching(dbc
, certificate
, issuer
);
3141 return (bool)result
;
3147 /* Given an issuer, returns true if an entry for this issuer exists in
3148 the database (i.e. a known CA). If the provided certificate is NULL,
3149 or its entry is not found, the function returns false.
3151 bool SecRevocationDbContainsIssuer(SecCertificateRef issuer
) {
3155 __block
bool result
= false;
3156 SecRevocationDbWith(^(SecRevocationDbRef db
) {
3157 (void) SecRevocationDbPerformRead(db
, NULL
, ^bool(SecRevocationDbConnectionRef dbc
, CFErrorRef
*blockError
) {
3158 CFDataRef issuerHash
= SecCertificateCopySHA256Digest(issuer
);
3159 int64_t groupId
= _SecRevocationDbGroupIdForIssuerHash(dbc
, issuerHash
, blockError
);
3160 CFReleaseSafe(issuerHash
);
3161 result
= (groupId
> 0);
3168 /* Return the current version of the revocation database.
3169 A version of 0 indicates an empty database which must be populated.
3170 If the version cannot be obtained, -1 is returned.
3172 CFIndex
SecRevocationDbGetVersion(void) {
3173 __block CFIndex result
= -1;
3174 SecRevocationDbWith(^(SecRevocationDbRef db
) {
3175 (void) SecRevocationDbPerformRead(db
, NULL
, ^bool(SecRevocationDbConnectionRef dbc
, CFErrorRef
*blockError
) {
3176 result
= (CFIndex
)_SecRevocationDbGetVersion(dbc
, blockError
);
3177 return (result
>= 0);
3183 /* Return the current schema version of the revocation database.
3184 A version of 0 indicates an empty database which must be populated.
3185 If the schema version cannot be obtained, -1 is returned.
3187 CFIndex
SecRevocationDbGetSchemaVersion(void) {
3188 __block CFIndex result
= -1;
3189 SecRevocationDbWith(^(SecRevocationDbRef db
) {
3190 result
= _SecRevocationDbGetSchemaVersion(db
, NULL
, NULL
);
3195 /* Return the current update format of the revocation database.
3196 A version of 0 indicates the format was unknown.
3197 If the update format cannot be obtained, -1 is returned.
3199 CFIndex
SecRevocationDbGetUpdateFormat(void) {
3200 __block CFIndex result
= -1;
3201 SecRevocationDbWith(^(SecRevocationDbRef db
) {
3202 (void) SecRevocationDbPerformRead(db
, NULL
, ^bool(SecRevocationDbConnectionRef dbc
, CFErrorRef
*blockError
) {
3203 result
= (CFIndex
)_SecRevocationDbGetUpdateFormat(dbc
, blockError
);
3204 return (result
>= 0);