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 kValidUpdateSeedServer
= CFSTR("valid.apple.com/seed");
76 const CFStringRef kValidUpdateCarryServer
= CFSTR("valid.apple.com/carry");
78 static CFStringRef kSecPrefsDomain
= CFSTR("com.apple.security");
79 static CFStringRef kUpdateServerKey
= CFSTR("ValidUpdateServer");
80 static CFStringRef kUpdateEnabledKey
= CFSTR("ValidUpdateEnabled");
81 static CFStringRef kUpdateIntervalKey
= CFSTR("ValidUpdateInterval");
82 static CFStringRef kBoolTrueKey
= CFSTR("1");
83 static CFStringRef kBoolFalseKey
= CFSTR("0");
85 /* constant length of boolean string keys */
86 #define BOOL_STRING_KEY_LENGTH 1
88 typedef CF_OPTIONS(CFOptionFlags
, SecValidInfoFlags
) {
89 kSecValidInfoComplete
= 1u << 0,
90 kSecValidInfoCheckOCSP
= 1u << 1,
91 kSecValidInfoKnownOnly
= 1u << 2,
92 kSecValidInfoRequireCT
= 1u << 3,
93 kSecValidInfoAllowlist
= 1u << 4,
94 kSecValidInfoNoCACheck
= 1u << 5,
95 kSecValidInfoOverridable
= 1u << 6,
96 kSecValidInfoDateConstraints
= 1u << 7,
97 kSecValidInfoNameConstraints
= 1u << 8,
98 kSecValidInfoPolicyConstraints
= 1u << 9,
99 kSecValidInfoNoCAv2Check
= 1u << 10,
102 /* minimum update interval */
103 #define kSecMinUpdateInterval (60.0 * 5)
105 /* standard update interval */
106 #define kSecStdUpdateInterval (60.0 * 60)
108 /* maximum allowed interval */
109 #define kSecMaxUpdateInterval (60.0 * 60 * 24 * 7)
111 #define kSecRevocationBasePath "/Library/Keychains/crls"
112 #define kSecRevocationCurUpdateFile "update-current"
113 #define kSecRevocationDbFileName "valid.sqlite3"
114 #define kSecRevocationDbReplaceFile ".valid_replace"
116 #define isDbOwner SecOTAPKIIsSystemTrustd
118 #define kSecRevocationDbChanged "com.apple.trustd.valid.db-changed"
120 /* database schema version
122 v2 = fix for group entry transitions
123 v3 = handle optional entries in update dictionaries
124 v4 = add db_format and db_source entries
125 v5 = add date constraints table, with updated group flags
126 v6 = explicitly set autovacuum and journal modes at db creation
128 Note: kSecRevocationDbMinSchemaVersion is the lowest version whose
129 results can be used. This allows revocation results to be obtained
130 from an existing db before the next update interval occurs, at which
131 time we'll update to the current version (kSecRevocationDbSchemaVersion).
133 #define kSecRevocationDbSchemaVersion 6 /* current version we support */
134 #define kSecRevocationDbMinSchemaVersion 6 /* minimum version we can use */
136 /* update file format
139 kSecValidUpdateFormatG1
= 1, /* initial version */
140 kSecValidUpdateFormatG2
= 2, /* signed content, single plist */
141 kSecValidUpdateFormatG3
= 3 /* signed content, multiple plists */
144 #define kSecRevocationDbUpdateFormat 3 /* current version we support */
145 #define kSecRevocationDbMinUpdateFormat 2 /* minimum version we can use */
147 #define kSecRevocationDbCacheSize 100
149 typedef struct __SecRevocationDb
*SecRevocationDbRef
;
150 struct __SecRevocationDb
{
152 dispatch_queue_t update_queue
;
153 bool updateInProgress
;
154 bool unsupportedVersion
;
156 CFMutableArrayRef info_cache_list
;
157 CFMutableDictionaryRef info_cache
;
158 os_unfair_lock info_cache_lock
;
161 typedef struct __SecRevocationDbConnection
*SecRevocationDbConnectionRef
;
162 struct __SecRevocationDbConnection
{
163 SecRevocationDbRef db
;
164 SecDbConnectionRef dbconn
;
165 CFIndex precommitVersion
;
166 CFIndex precommitDbVersion
;
170 bool SecRevocationDbVerifyUpdate(void *update
, CFIndex length
);
171 bool SecRevocationDbIngestUpdate(SecRevocationDbConnectionRef dbc
, CFDictionaryRef update
, CFIndex chunkVersion
, CFIndex
*outVersion
, CFErrorRef
*error
);
172 bool _SecRevocationDbApplyUpdate(SecRevocationDbConnectionRef dbc
, CFDictionaryRef update
, CFIndex version
, CFErrorRef
*error
);
173 CFAbsoluteTime
SecRevocationDbComputeNextUpdateTime(CFIndex updateInterval
);
174 bool SecRevocationDbUpdateSchema(SecRevocationDbRef rdb
);
175 CFIndex
SecRevocationDbGetUpdateFormat(void);
176 bool _SecRevocationDbSetUpdateSource(SecRevocationDbConnectionRef dbc
, CFStringRef source
, CFErrorRef
*error
);
177 bool SecRevocationDbSetUpdateSource(SecRevocationDbRef rdb
, CFStringRef source
);
178 CFStringRef
SecRevocationDbCopyUpdateSource(void);
179 bool SecRevocationDbSetNextUpdateTime(CFAbsoluteTime nextUpdate
, CFErrorRef
*error
);
180 CFAbsoluteTime
SecRevocationDbGetNextUpdateTime(void);
181 dispatch_queue_t
SecRevocationDbGetUpdateQueue(void);
182 bool _SecRevocationDbRemoveAllEntries(SecRevocationDbConnectionRef dbc
, CFErrorRef
*error
);
183 void SecRevocationDbReleaseAllConnections(void);
184 static void SecRevocationDbWith(void(^dbJob
)(SecRevocationDbRef db
));
185 static bool SecRevocationDbPerformWrite(SecRevocationDbRef rdb
, CFErrorRef
*error
, bool(^writeJob
)(SecRevocationDbConnectionRef dbc
, CFErrorRef
*blockError
));
186 static SecValidInfoFormat
_SecRevocationDbGetGroupFormat(SecRevocationDbConnectionRef dbc
, int64_t groupId
, SecValidInfoFlags
*flags
, CFDataRef
*data
, CFErrorRef
*error
);
187 static bool _SecRevocationDbSetVersion(SecRevocationDbConnectionRef dbc
, CFIndex version
, CFErrorRef
*error
);
188 static int64_t _SecRevocationDbGetVersion(SecRevocationDbConnectionRef dbc
, CFErrorRef
*error
);
189 static int64_t _SecRevocationDbGetSchemaVersion(SecRevocationDbRef rdb
, SecRevocationDbConnectionRef dbc
, CFErrorRef
*error
);
190 static void SecRevocationDbResetCaches(void);
191 static SecRevocationDbConnectionRef
SecRevocationDbConnectionInit(SecRevocationDbRef db
, SecDbConnectionRef dbconn
, CFErrorRef
*error
);
194 static CFDataRef
copyInflatedData(CFDataRef data
) {
199 memset(&zs
, 0, sizeof(zs
));
200 /* 32 is a magic value which enables automatic header detection
201 of gzip or zlib compressed data. */
202 if (inflateInit2(&zs
, 32+MAX_WBITS
) != Z_OK
) {
205 zs
.next_in
= (UInt8
*)(CFDataGetBytePtr(data
));
206 zs
.avail_in
= (uInt
)CFDataGetLength(data
);
208 CFMutableDataRef outData
= CFDataCreateMutable(NULL
, 0);
212 CFIndex buf_sz
= malloc_good_size(zs
.avail_in
? zs
.avail_in
: 1024 * 4);
213 unsigned char *buf
= malloc(buf_sz
);
216 zs
.next_out
= (Bytef
*)buf
;
217 zs
.avail_out
= (uInt
)buf_sz
;
218 rc
= inflate(&zs
, 0);
219 CFIndex outLen
= CFDataGetLength(outData
);
220 if (outLen
< (CFIndex
)zs
.total_out
) {
221 CFDataAppendBytes(outData
, (const UInt8
*)buf
, (CFIndex
)zs
.total_out
- outLen
);
223 } while (rc
== Z_OK
);
230 if (rc
!= Z_STREAM_END
) {
231 CFReleaseSafe(outData
);
234 return (CFDataRef
)outData
;
237 static CFDataRef
copyDeflatedData(CFDataRef data
) {
242 memset(&zs
, 0, sizeof(zs
));
243 if (deflateInit(&zs
, Z_BEST_COMPRESSION
) != Z_OK
) {
246 zs
.next_in
= (UInt8
*)(CFDataGetBytePtr(data
));
247 zs
.avail_in
= (uInt
)CFDataGetLength(data
);
249 CFMutableDataRef outData
= CFDataCreateMutable(NULL
, 0);
253 CFIndex buf_sz
= malloc_good_size(zs
.avail_in
? zs
.avail_in
: 1024 * 4);
254 unsigned char *buf
= malloc(buf_sz
);
255 int rc
= Z_BUF_ERROR
;
257 zs
.next_out
= (Bytef
*)buf
;
258 zs
.avail_out
= (uInt
)buf_sz
;
259 rc
= deflate(&zs
, Z_FINISH
);
261 if (rc
== Z_OK
|| rc
== Z_STREAM_END
) {
262 CFIndex buf_used
= buf_sz
- zs
.avail_out
;
263 CFDataAppendBytes(outData
, (const UInt8
*)buf
, buf_used
);
265 else if (rc
== Z_BUF_ERROR
) {
267 buf_sz
= malloc_good_size(buf_sz
* 2);
268 buf
= malloc(buf_sz
);
270 rc
= Z_OK
; /* try again with larger buffer */
273 } while (rc
== Z_OK
&& zs
.avail_in
);
280 if (rc
!= Z_STREAM_END
) {
281 CFReleaseSafe(outData
);
284 return (CFDataRef
)outData
;
287 /* Read file opens the file, mmaps it and then closes the file. */
288 int readValidFile(const char *fileName
,
289 CFDataRef
*bytes
) { // mmapped and returned -- must be munmapped!
291 const uint8_t *buf
= NULL
;
296 fd
= open(fileName
, O_RDONLY
);
297 if (fd
< 0) { return errno
; }
298 rtn
= fstat(fd
, &sb
);
299 if (rtn
) { goto errOut
; }
300 if (sb
.st_size
> (off_t
) ((UINT32_MAX
>> 1)-1)) {
304 size
= (size_t)sb
.st_size
;
306 buf
= mmap(NULL
, size
, PROT_READ
, MAP_PRIVATE
, fd
, 0);
307 if (!buf
|| buf
== MAP_FAILED
) {
309 secerror("unable to map %s (errno %d)", fileName
, rtn
);
313 *bytes
= CFDataCreateWithBytesNoCopy(NULL
, buf
, size
, kCFAllocatorNull
);
318 CFReleaseNull(*bytes
);
320 int unmap_err
= munmap((void *)buf
, size
);
321 if (unmap_err
!= 0) {
322 secerror("unable to unmap %ld bytes at %p (error %d)", (long)size
, buf
, rtn
);
329 static bool removeFileWithSuffix(const char *basepath
, const char *suffix
) {
332 asprintf(&path
, "%s%s", basepath
, suffix
);
334 if (remove(path
) == -1) {
336 if (error
== ENOENT
) {
337 result
= true; // not an error if the file did not exist
339 secnotice("validupdate", "remove (%s): %s", path
, strerror(error
));
349 static CFDataRef CF_RETURNS_RETAINED
cfToHexData(CFDataRef data
, bool prependWildcard
) {
350 if (!isData(data
)) { return NULL
; }
351 CFIndex len
= CFDataGetLength(data
) * 2;
352 CFMutableStringRef hex
= CFStringCreateMutable(NULL
, len
+1);
353 static const char* digits
[]={
354 "0","1","2","3","4","5","6","7","8","9","A","B","C","D","E","F"};
355 if (prependWildcard
) {
356 CFStringAppendCString(hex
, "%", 1);
358 const uint8_t* p
= CFDataGetBytePtr(data
);
359 for (CFIndex i
= 0; i
< CFDataGetLength(data
); i
++) {
360 CFStringAppendCString(hex
, digits
[p
[i
] >> 4], 1);
361 CFStringAppendCString(hex
, digits
[p
[i
] & 0xf], 1);
363 CFDataRef result
= CFStringCreateExternalRepresentation(NULL
, hex
, kCFStringEncodingUTF8
, 0);
369 // MARK: SecValidUpdate
371 /* ======================================================================
373 ======================================================================*/
375 CFAbsoluteTime gUpdateStarted
= 0.0;
376 CFAbsoluteTime gNextUpdate
= 0.0;
377 static CFIndex gUpdateInterval
= 0;
378 static CFIndex gLastVersion
= 0;
381 1. The length of the signed data, as a 4-byte integer in network byte order.
382 2. The signed data, which consists of:
383 a. A 4-byte integer in network byte order, the count of plists to follow; and then for each plist:
384 i. A 4-byte integer, the length of each plist
385 ii. A plist, in binary form
386 b. There may be other data after the plists in the signed data, described by a future version of this specification.
387 3. The length of the following CMS blob, as a 4-byte integer in network byte order.
388 4. A detached CMS signature of the signed data described above.
389 5. There may be additional data after the CMS blob, described by a future version of this specification.
391 Note: the difference between g2 and g3 format is the addition of the 4-byte count in (2a).
393 static bool SecValidUpdateProcessData(SecRevocationDbConnectionRef dbc
, CFIndex format
, CFDataRef updateData
, CFErrorRef
*error
) {
395 if (!updateData
|| format
< 2) {
396 SecError(errSecParam
, error
, CFSTR("SecValidUpdateProcessData: invalid update format"));
400 CFIndex interval
= 0;
401 const UInt8
* p
= CFDataGetBytePtr(updateData
);
402 size_t bytesRemaining
= (p
) ? (size_t)CFDataGetLength(updateData
) : 0;
403 /* make sure there is enough data to contain length and count */
404 if (bytesRemaining
< ((CFIndex
)sizeof(uint32_t) * 2)) {
405 secinfo("validupdate", "Skipping property list creation (length %ld is too short)", (long)bytesRemaining
);
406 SecError(errSecParam
, error
, CFSTR("SecValidUpdateProcessData: data length is too short"));
409 /* get length of signed data */
410 uint32_t dataLength
= OSSwapInt32(*((uint32_t *)p
));
411 bytesRemaining
-= sizeof(uint32_t);
412 p
+= sizeof(uint32_t);
414 /* get plist count (G3 format and later) */
415 uint32_t plistCount
= 1;
416 uint32_t plistTotal
= 1;
417 if (format
> kSecValidUpdateFormatG2
) {
418 plistCount
= OSSwapInt32(*((uint32_t *)p
));
419 plistTotal
= plistCount
;
420 bytesRemaining
-= sizeof(uint32_t);
421 p
+= sizeof(uint32_t);
423 if (dataLength
> bytesRemaining
) {
424 secinfo("validupdate", "Skipping property list creation (dataLength=%ld, bytesRemaining=%ld)",
425 (long)dataLength
, (long)bytesRemaining
);
426 SecError(errSecParam
, error
, CFSTR("SecValidUpdateProcessData: data longer than expected"));
430 /* process each chunked plist */
432 CFErrorRef localError
= NULL
;
433 uint32_t plistProcessed
= 0;
434 while (plistCount
> 0 && bytesRemaining
> 0) {
435 CFPropertyListRef propertyList
= NULL
;
436 uint32_t plistLength
= dataLength
;
437 if (format
> kSecValidUpdateFormatG2
) {
438 plistLength
= OSSwapInt32(*((uint32_t *)p
));
439 bytesRemaining
-= sizeof(uint32_t);
440 p
+= sizeof(uint32_t);
445 if (plistLength
<= bytesRemaining
) {
446 CFDataRef data
= CFDataCreateWithBytesNoCopy(NULL
, p
, plistLength
, kCFAllocatorNull
);
447 propertyList
= CFPropertyListCreateWithData(NULL
, data
, kCFPropertyListImmutable
, NULL
, NULL
);
450 if (isDictionary(propertyList
)) {
451 secdebug("validupdate", "Ingesting plist chunk %u of %u, length: %u",
452 plistProcessed
, plistTotal
, plistLength
);
453 CFIndex curVersion
= -1;
454 ok
= ok
&& SecRevocationDbIngestUpdate(dbc
, (CFDictionaryRef
)propertyList
, version
, &curVersion
, &localError
);
455 if (plistProcessed
== 1) {
456 dbc
->precommitVersion
= version
= curVersion
;
457 // get server-provided interval
458 CFTypeRef value
= (CFNumberRef
)CFDictionaryGetValue((CFDictionaryRef
)propertyList
,
459 CFSTR("check-again"));
460 if (isNumber(value
)) {
461 CFNumberGetValue((CFNumberRef
)value
, kCFNumberCFIndexType
, &interval
);
464 if (ok
&& curVersion
< 0) {
465 plistCount
= 0; // we already had this version; skip remaining plists
469 secinfo("validupdate", "Failed to deserialize update chunk %u of %u",
470 plistProcessed
, plistTotal
);
471 SecError(errSecParam
, error
, CFSTR("SecValidUpdateProcessData: failed to get update chunk"));
472 if (plistProcessed
== 1) {
473 gNextUpdate
= SecRevocationDbComputeNextUpdateTime(0);
476 /* All finished with this property list */
477 CFReleaseSafe(propertyList
);
479 bytesRemaining
-= plistLength
;
483 if (ok
&& version
> 0) {
484 secdebug("validupdate", "Update received: v%ld", (long)version
);
485 gLastVersion
= version
;
486 gNextUpdate
= SecRevocationDbComputeNextUpdateTime(interval
);
487 secdebug("validupdate", "Next update time: %f", gNextUpdate
);
491 (void) CFErrorPropagate(localError
, error
);
495 void SecValidUpdateVerifyAndIngest(CFDataRef updateData
, CFStringRef updateServer
, bool fullUpdate
) {
497 secnotice("validupdate", "invalid update data");
500 /* Verify CMS signature on signed data */
501 if (!SecRevocationDbVerifyUpdate((void *)CFDataGetBytePtr(updateData
), CFDataGetLength(updateData
))) {
502 secerror("failed to verify valid update");
503 TrustdHealthAnalyticsLogErrorCode(TAEventValidUpdate
, TAFatalError
, errSecVerifyFailed
);
506 /* Read current update source from database. */
507 CFStringRef dbSource
= SecRevocationDbCopyUpdateSource();
508 if (dbSource
&& updateServer
&& (kCFCompareEqualTo
!= CFStringCompare(dbSource
, updateServer
,
509 kCFCompareCaseInsensitive
))) {
510 secnotice("validupdate", "switching db source from \"%@\" to \"%@\"", dbSource
, updateServer
);
512 CFReleaseNull(dbSource
);
514 /* Ingest the update. This is now performed under a single immediate write transaction,
515 so other writers are blocked (but not other readers), and the changes can be rolled back
516 in their entirety if any error occurs. */
517 __block
bool ok
= true;
518 __block CFErrorRef localError
= NULL
;
519 __block SecRevocationDbConnectionRef dbc
= NULL
;
520 SecRevocationDbWith(^(SecRevocationDbRef rdb
) {
521 ok
&= SecRevocationDbPerformWrite(rdb
, &localError
, ^bool(SecRevocationDbConnectionRef dbc
, CFErrorRef
*blockError
) {
523 /* Must completely replace existing database contents */
524 secdebug("validupdate", "starting to process full update; clearing database");
525 ok
= ok
&& _SecRevocationDbRemoveAllEntries(dbc
,blockError
);
526 ok
= ok
&& _SecRevocationDbSetUpdateSource(dbc
, updateServer
, blockError
);
528 dbc
->precommitVersion
= 0;
529 dbc
->fullUpdate
= true;
532 ok
= ok
&& SecValidUpdateProcessData(dbc
, kSecValidUpdateFormatG3
, updateData
, blockError
);
534 secerror("failed to process valid update: %@", blockError
? *blockError
: NULL
);
535 TrustdHealthAnalyticsLogErrorCode(TAEventValidUpdate
, TAFatalError
, errSecDecode
);
537 TrustdHealthAnalyticsLogSuccess(TAEventValidUpdate
);
542 rdb
->changed
= false;
543 /* signal other trustd instances that the database has been updated */
544 notify_post(kSecRevocationDbChanged
);
548 /* remember next update time in case of restart (separate write transaction) */
549 (void) SecRevocationDbSetNextUpdateTime(gNextUpdate
, NULL
);
554 CFReleaseSafe(localError
);
557 static bool SecValidUpdateForceReplaceDatabase(void) {
560 // write semaphore file that we will pick up when we next launch
561 char *semPathBuf
= NULL
;
562 asprintf(&semPathBuf
, "%s/%s", kSecRevocationBasePath
, kSecRevocationDbReplaceFile
);
565 int fd
= open(semPathBuf
, O_WRONLY
| O_CREAT
, DEFFILEMODE
);
566 if (fd
== -1 || fstat(fd
, &sb
)) {
567 secnotice("validupdate", "unable to write %s", semPathBuf
);
577 // exit as gracefully as possible so we can replace the database
578 secnotice("validupdate", "process exiting to replace db file");
579 dispatch_after(dispatch_time(DISPATCH_TIME_NOW
, 3ull*NSEC_PER_SEC
), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT
, 0), ^{
580 xpc_transaction_exit_clean();
586 static bool SecValidUpdateSatisfiedLocally(CFStringRef server
, CFIndex version
, bool safeToReplace
) {
587 __block
bool result
= false;
588 SecOTAPKIRef otapkiRef
= NULL
;
589 bool relaunching
= false;
590 static int sNumLocalUpdates
= 0;
592 // if we've replaced the database with a local asset twice in a row,
593 // something is wrong with it. Get this update from the server.
594 if (sNumLocalUpdates
> 1) {
595 secdebug("validupdate", "%d consecutive db resets, ignoring local asset", sNumLocalUpdates
);
599 // if a non-production server is specified, we will not be able to use a
600 // local production asset since its update sequence will be different.
601 if (kCFCompareEqualTo
!= CFStringCompare(server
, kValidUpdateProdServer
,
602 kCFCompareCaseInsensitive
)) {
603 secdebug("validupdate", "non-production server specified, ignoring local asset");
607 // check static database asset(s)
608 otapkiRef
= SecOTAPKICopyCurrentOTAPKIRef();
612 CFIndex assetVersion
= SecOTAPKIGetValidSnapshotVersion(otapkiRef
);
613 CFIndex assetFormat
= SecOTAPKIGetValidSnapshotFormat(otapkiRef
);
614 // version <= 0 means the database is invalid or empty.
615 // version > 0 means we have some version, but we need to see if a
616 // newer version is available as a local asset.
617 if (assetVersion
<= version
|| assetFormat
< kSecValidUpdateFormatG3
) {
618 // asset is not newer than ours, or its version is unknown
622 // replace database only if safe to do so (i.e. called at startup)
623 if (!safeToReplace
) {
624 relaunching
= SecValidUpdateForceReplaceDatabase();
628 // try to copy uncompressed database asset, if available
629 const char *validDbPathBuf
= SecOTAPKIGetValidDatabaseSnapshot(otapkiRef
);
630 if (validDbPathBuf
) {
631 WithPathInRevocationInfoDirectory(CFSTR(kSecRevocationDbFileName
), ^(const char *path
) {
632 secdebug("validupdate", "will copy data from \"%s\"", validDbPathBuf
);
633 copyfile_state_t state
= copyfile_state_alloc();
634 int retval
= copyfile(validDbPathBuf
, path
, state
, COPYFILE_DATA
);
635 copyfile_state_free(state
);
637 secnotice("validupdate", "copyfile error %d", retval
);
645 CFReleaseNull(otapkiRef
);
648 gLastVersion
= SecRevocationDbGetVersion();
649 // note: snapshot should already have latest schema and production source,
650 // but set it here anyway so we don't keep trying to replace the db.
651 SecRevocationDbWith(^(SecRevocationDbRef db
) {
652 (void)SecRevocationDbSetUpdateSource(db
, server
);
653 (void)SecRevocationDbUpdateSchema(db
);
656 secdebug("validupdate", "local update to g%ld/v%ld complete at %f",
657 (long)SecRevocationDbGetUpdateFormat(), (long)gLastVersion
,
658 (double)CFAbsoluteTimeGetCurrent());
660 sNumLocalUpdates
= 0; // reset counter
663 // request is locally satisfied; don't schedule a network update
669 static bool SecValidUpdateSchedule(bool updateEnabled
, CFStringRef server
, CFIndex version
) {
670 /* Check if we have a later version available locally */
671 if (SecValidUpdateSatisfiedLocally(server
, version
, false)) {
675 /* If update not permitted return */
676 if (!updateEnabled
) {
680 #if !TARGET_OS_BRIDGE
681 /* Schedule as a maintenance task */
682 secdebug("validupdate", "will fetch v%lu from \"%@\"", (unsigned long)version
, server
);
683 return SecValidUpdateRequest(SecRevocationDbGetUpdateQueue(), server
, version
);
689 static CFStringRef
SecRevocationDbGetDefaultServer(void) {
690 #if !TARGET_OS_WATCH && !TARGET_OS_BRIDGE
692 CFStringRef defaultServer
= kValidUpdateSeedServer
;
693 #else // !RC_SEED_BUILD
694 CFStringRef defaultServer
= kValidUpdateProdServer
;
695 #endif // !RC_SEED_BUILD
696 if (os_variant_has_internal_diagnostics("com.apple.security")) {
697 defaultServer
= kValidUpdateCarryServer
;
699 return defaultServer
;
700 #else // TARGET_OS_WATCH || TARGET_OS_BRIDGE
701 /* Because watchOS and bridgeOS can't update over the air, we should
702 * always use the prod server so that the valid database built into the
704 return kValidUpdateProdServer
;
708 void SecRevocationDbInitialize() {
709 if (!isDbOwner()) { return; }
710 os_transaction_t transaction
= os_transaction_create("com.apple.trustd.valid.initialize");
711 __block
bool initializeDb
= false;
713 /* create base path if it doesn't exist */
714 (void)mkpath_np(kSecRevocationBasePath
, 0755);
716 /* check semaphore file */
717 WithPathInRevocationInfoDirectory(CFSTR(kSecRevocationDbReplaceFile
), ^(const char *path
) {
719 if (stat(path
, &sb
) == 0) {
720 initializeDb
= true; /* file was found, so we will replace the database */
721 if (remove(path
) == -1) {
723 secnotice("validupdate", "remove (%s): %s", path
, strerror(error
));
729 WithPathInRevocationInfoDirectory(CFSTR(kSecRevocationDbFileName
), ^(const char *path
) {
731 /* remove old database file(s) */
732 (void)removeFileWithSuffix(path
, "");
733 (void)removeFileWithSuffix(path
, "-journal");
734 (void)removeFileWithSuffix(path
, "-shm");
735 (void)removeFileWithSuffix(path
, "-wal");
739 if (stat(path
, &sb
) == -1) {
740 initializeDb
= true; /* file not found, so we will create the database */
746 os_release(transaction
);
747 return; /* database exists and doesn't need replacing */
750 /* initialize database from local asset */
751 CFTypeRef value
= (CFStringRef
)CFPreferencesCopyValue(kUpdateServerKey
, kSecPrefsDomain
, kCFPreferencesAnyUser
, kCFPreferencesCurrentHost
);
752 CFStringRef server
= (isString(value
)) ? (CFStringRef
)value
: (CFStringRef
)SecRevocationDbGetDefaultServer();
754 secnotice("validupdate", "initializing database");
755 if (!SecValidUpdateSatisfiedLocally(server
, version
, true)) {
756 #if !TARGET_OS_BRIDGE
757 /* Schedule full update as a maintenance task */
758 (void)SecValidUpdateRequest(SecRevocationDbGetUpdateQueue(), server
, version
);
761 CFReleaseSafe(value
);
762 os_release(transaction
);
767 // MARK: SecValidInfoRef
769 /* ======================================================================
771 ======================================================================
774 CFGiblisWithCompareFor(SecValidInfo
);
776 static SecValidInfoRef
SecValidInfoCreate(SecValidInfoFormat format
,
780 CFDataRef issuerHash
,
781 CFDataRef anchorHash
,
782 CFDateRef notBeforeDate
,
783 CFDateRef notAfterDate
,
784 CFDataRef nameConstraints
,
785 CFDataRef policyConstraints
) {
786 SecValidInfoRef validInfo
;
787 validInfo
= CFTypeAllocate(SecValidInfo
, struct __SecValidInfo
, kCFAllocatorDefault
);
788 if (!validInfo
) { return NULL
; }
790 CFRetainSafe(certHash
);
791 CFRetainSafe(issuerHash
);
792 CFRetainSafe(anchorHash
);
793 CFRetainSafe(notBeforeDate
);
794 CFRetainSafe(notAfterDate
);
795 CFRetainSafe(nameConstraints
);
796 CFRetainSafe(policyConstraints
);
798 validInfo
->format
= format
;
799 validInfo
->certHash
= certHash
;
800 validInfo
->issuerHash
= issuerHash
;
801 validInfo
->anchorHash
= anchorHash
;
802 validInfo
->isOnList
= isOnList
;
803 validInfo
->valid
= (flags
& kSecValidInfoAllowlist
);
804 validInfo
->complete
= (flags
& kSecValidInfoComplete
);
805 validInfo
->checkOCSP
= (flags
& kSecValidInfoCheckOCSP
);
806 validInfo
->knownOnly
= (flags
& kSecValidInfoKnownOnly
);
807 validInfo
->requireCT
= (flags
& kSecValidInfoRequireCT
);
808 validInfo
->noCACheck
= (flags
& kSecValidInfoNoCAv2Check
);
809 validInfo
->overridable
= (flags
& kSecValidInfoOverridable
);
810 validInfo
->hasDateConstraints
= (flags
& kSecValidInfoDateConstraints
);
811 validInfo
->hasNameConstraints
= (flags
& kSecValidInfoNameConstraints
);
812 validInfo
->hasPolicyConstraints
= (flags
& kSecValidInfoPolicyConstraints
);
813 validInfo
->notBeforeDate
= notBeforeDate
;
814 validInfo
->notAfterDate
= notAfterDate
;
815 validInfo
->nameConstraints
= nameConstraints
;
816 validInfo
->policyConstraints
= policyConstraints
;
821 static void SecValidInfoDestroy(CFTypeRef cf
) {
822 SecValidInfoRef validInfo
= (SecValidInfoRef
)cf
;
824 CFReleaseNull(validInfo
->certHash
);
825 CFReleaseNull(validInfo
->issuerHash
);
826 CFReleaseNull(validInfo
->anchorHash
);
827 CFReleaseNull(validInfo
->notBeforeDate
);
828 CFReleaseNull(validInfo
->notAfterDate
);
829 CFReleaseNull(validInfo
->nameConstraints
);
830 CFReleaseNull(validInfo
->policyConstraints
);
834 void SecValidInfoSetAnchor(SecValidInfoRef validInfo
, SecCertificateRef anchor
) {
838 CFDataRef anchorHash
= NULL
;
840 anchorHash
= SecCertificateCopySHA256Digest(anchor
);
842 /* clear no-ca flag for anchors where we want OCSP checked [32523118] */
843 if (SecIsAppleTrustAnchor(anchor
, 0)) {
844 validInfo
->noCACheck
= false;
847 CFReleaseNull(validInfo
->anchorHash
);
848 validInfo
->anchorHash
= anchorHash
;
851 static Boolean
SecValidInfoCompare(CFTypeRef a
, CFTypeRef b
) {
852 SecValidInfoRef validInfoA
= (SecValidInfoRef
)a
;
853 SecValidInfoRef validInfoB
= (SecValidInfoRef
)b
;
854 if (validInfoA
== validInfoB
) {
857 if (!validInfoA
|| !validInfoB
||
858 (CFGetTypeID(a
) != SecValidInfoGetTypeID()) ||
859 (CFGetTypeID(b
) != SecValidInfoGetTypeID())) {
862 return CFEqualSafe(validInfoA
->certHash
, validInfoB
->certHash
) && CFEqualSafe(validInfoA
->issuerHash
, validInfoB
->issuerHash
);
865 static CFStringRef
SecValidInfoCopyFormatDescription(CFTypeRef cf
, CFDictionaryRef formatOptions
) {
866 SecValidInfoRef validInfo
= (SecValidInfoRef
)cf
;
867 CFStringRef certHash
= CFDataCopyHexString(validInfo
->certHash
);
868 CFStringRef issuerHash
= CFDataCopyHexString(validInfo
->issuerHash
);
869 CFStringRef desc
= CFStringCreateWithFormat(NULL
, formatOptions
, CFSTR("validInfo certHash: %@ issuerHash: %@"), certHash
, issuerHash
);
870 CFReleaseNull(certHash
);
871 CFReleaseNull(issuerHash
);
877 // MARK: SecRevocationDb
879 /* ======================================================================
881 ======================================================================
884 /* SecRevocationDbCheckNextUpdate returns true if we dispatched an
885 update request, otherwise false.
887 static bool _SecRevocationDbCheckNextUpdate(void) {
888 // are we the db owner instance?
892 CFTypeRef value
= NULL
;
894 // is it time to check?
895 CFAbsoluteTime now
= CFAbsoluteTimeGetCurrent();
896 CFAbsoluteTime minNextUpdate
= now
+ gUpdateInterval
;
897 gUpdateStarted
= now
;
899 if (0 == gNextUpdate
) {
900 // first time we're called, check if we have a saved nextUpdate value
901 gNextUpdate
= SecRevocationDbGetNextUpdateTime();
903 if (gNextUpdate
< minNextUpdate
) {
904 gNextUpdate
= minNextUpdate
;
906 // allow pref to override update interval, if it exists
907 CFIndex interval
= -1;
908 value
= (CFNumberRef
)CFPreferencesCopyValue(kUpdateIntervalKey
, kSecPrefsDomain
, kCFPreferencesAnyUser
, kCFPreferencesCurrentHost
);
909 if (isNumber(value
)) {
910 if (CFNumberGetValue((CFNumberRef
)value
, kCFNumberCFIndexType
, &interval
)) {
911 if (interval
< kSecMinUpdateInterval
) {
912 interval
= kSecMinUpdateInterval
;
913 } else if (interval
> kSecMaxUpdateInterval
) {
914 interval
= kSecMaxUpdateInterval
;
918 CFReleaseNull(value
);
919 gUpdateInterval
= kSecStdUpdateInterval
;
921 gUpdateInterval
= interval
;
923 // pin next update time to the preferred update interval
924 if (gNextUpdate
> (gUpdateStarted
+ gUpdateInterval
)) {
925 gNextUpdate
= gUpdateStarted
+ gUpdateInterval
;
927 secdebug("validupdate", "next update at %f (in %f seconds)",
928 (double)gUpdateStarted
, (double)gNextUpdate
-gUpdateStarted
);
930 if (gNextUpdate
> now
) {
934 secnotice("validupdate", "starting update");
936 // set minimum next update time here in case we can't get an update
937 gNextUpdate
= minNextUpdate
;
939 // determine which server to query
941 value
= (CFStringRef
)CFPreferencesCopyValue(kUpdateServerKey
, kSecPrefsDomain
, kCFPreferencesAnyUser
, kCFPreferencesCurrentHost
);
942 if (isString(value
)) {
943 server
= (CFStringRef
) CFRetain(value
);
945 server
= (CFStringRef
) CFRetain(SecRevocationDbGetDefaultServer());
947 CFReleaseNull(value
);
949 // determine version of our current database
950 CFIndex version
= SecRevocationDbGetVersion();
951 secdebug("validupdate", "got version %ld from db", (long)version
);
953 if (gLastVersion
> 0) {
954 secdebug("validupdate", "error getting version; using last good version: %ld", (long)gLastVersion
);
956 version
= gLastVersion
;
959 // determine source of our current database
960 // (if this ever changes, we will need to reload the db)
961 CFStringRef db_source
= SecRevocationDbCopyUpdateSource();
963 db_source
= (CFStringRef
) CFRetain(kValidUpdateProdServer
);
966 // determine whether we need to recreate the database
967 CFIndex db_version
= SecRevocationDbGetSchemaVersion();
968 CFIndex db_format
= SecRevocationDbGetUpdateFormat();
969 if (db_version
< kSecRevocationDbSchemaVersion
||
970 db_format
< kSecRevocationDbUpdateFormat
||
971 kCFCompareEqualTo
!= CFStringCompare(server
, db_source
, kCFCompareCaseInsensitive
)) {
972 // we need to fully rebuild the db contents, so we set our version to 0.
973 version
= gLastVersion
= 0;
976 // determine whether update fetching is enabled
977 #if (__MAC_OS_X_VERSION_MIN_REQUIRED >= 101300 || __IPHONE_OS_VERSION_MIN_REQUIRED >= 110000)
978 bool updateEnabled
= true; // macOS 10.13 or iOS 11.0
980 bool updateEnabled
= false;
982 value
= (CFBooleanRef
)CFPreferencesCopyValue(kUpdateEnabledKey
, kSecPrefsDomain
, kCFPreferencesAnyUser
, kCFPreferencesCurrentHost
);
983 if (isBoolean(value
)) {
984 updateEnabled
= CFBooleanGetValue((CFBooleanRef
)value
);
986 CFReleaseNull(value
);
988 // Schedule maintenance work
989 bool result
= SecValidUpdateSchedule(updateEnabled
, server
, version
);
990 CFReleaseNull(server
);
991 CFReleaseNull(db_source
);
995 void SecRevocationDbCheckNextUpdate(void) {
996 static dispatch_once_t once
;
997 static sec_action_t action
;
999 dispatch_once(&once
, ^{
1000 dispatch_queue_t update_queue
= SecRevocationDbGetUpdateQueue();
1001 action
= sec_action_create_with_queue(update_queue
, "update_check", kSecMinUpdateInterval
);
1002 sec_action_set_handler(action
, ^{
1003 os_transaction_t transaction
= os_transaction_create("com.apple.trustd.valid.checkNextUpdate");
1004 (void)_SecRevocationDbCheckNextUpdate();
1005 os_release(transaction
);
1008 sec_action_perform(action
);
1011 /* This function verifies an update, in this format:
1012 1) unsigned 32-bit network-byte-order length of binary plist
1013 2) binary plist data
1014 3) unsigned 32-bit network-byte-order length of CMS message
1015 4) CMS message (containing certificates and signature over binary plist)
1017 The length argument is the total size of the packed update data.
1019 bool SecRevocationDbVerifyUpdate(void *update
, CFIndex length
) {
1020 if (!update
|| length
<= (CFIndex
)sizeof(uint32_t)) {
1023 uint32_t plistLength
= OSSwapInt32(*((uint32_t *)update
));
1024 if ((plistLength
+ (CFIndex
)(sizeof(uint32_t)*2)) > (uint64_t) length
) {
1025 secdebug("validupdate", "ERROR: reported plist length (%lu)+%lu exceeds total length (%lu)\n",
1026 (unsigned long)plistLength
, (unsigned long)sizeof(uint32_t)*2, (unsigned long)length
);
1029 uint8_t *plistData
= (uint8_t *)update
+ sizeof(uint32_t);
1030 uint8_t *sigData
= (uint8_t *)plistData
+ plistLength
;
1031 uint32_t sigLength
= OSSwapInt32(*((uint32_t *)sigData
));
1032 sigData
+= sizeof(uint32_t);
1033 if ((plistLength
+ sigLength
+ (CFIndex
)(sizeof(uint32_t) * 2)) != (uint64_t) length
) {
1034 secdebug("validupdate", "ERROR: reported lengths do not add up to total length\n");
1038 OSStatus status
= 0;
1039 CMSSignerStatus signerStatus
;
1040 CMSDecoderRef cms
= NULL
;
1041 SecPolicyRef policy
= NULL
;
1042 SecTrustRef trust
= NULL
;
1043 CFDataRef content
= NULL
;
1045 if ((content
= CFDataCreateWithBytesNoCopy(kCFAllocatorDefault
,
1046 (const UInt8
*)plistData
, (CFIndex
)plistLength
, kCFAllocatorNull
)) == NULL
) {
1047 secdebug("validupdate", "CFDataCreateWithBytesNoCopy failed (%ld bytes)\n", (long)plistLength
);
1051 if ((status
= CMSDecoderCreate(&cms
)) != errSecSuccess
) {
1052 secdebug("validupdate", "CMSDecoderCreate failed with error %d\n", (int)status
);
1055 if ((status
= CMSDecoderUpdateMessage(cms
, sigData
, sigLength
)) != errSecSuccess
) {
1056 secdebug("validupdate", "CMSDecoderUpdateMessage failed with error %d\n", (int)status
);
1059 if ((status
= CMSDecoderSetDetachedContent(cms
, content
)) != errSecSuccess
) {
1060 secdebug("validupdate", "CMSDecoderSetDetachedContent failed with error %d\n", (int)status
);
1063 if ((status
= CMSDecoderFinalizeMessage(cms
)) != errSecSuccess
) {
1064 secdebug("validupdate", "CMSDecoderFinalizeMessage failed with error %d\n", (int)status
);
1068 policy
= SecPolicyCreateApplePinned(CFSTR("ValidUpdate"), // kSecPolicyNameAppleValidUpdate
1069 CFSTR("1.2.840.113635.100.6.2.10"), // System Integration 2 Intermediate Certificate
1070 CFSTR("1.2.840.113635.100.6.51")); // Valid update signing OID
1072 // Check that the first signer actually signed this message.
1073 if ((status
= CMSDecoderCopySignerStatus(cms
, 0, policy
,
1074 false, &signerStatus
, &trust
, NULL
)) != errSecSuccess
) {
1075 secdebug("validupdate", "CMSDecoderCopySignerStatus failed with error %d\n", (int)status
);
1078 // Make sure the signature verifies against the detached content
1079 if (signerStatus
!= kCMSSignerValid
) {
1080 secdebug("validupdate", "ERROR: signature did not verify (signer status %d)\n", (int)signerStatus
);
1081 status
= errSecInvalidSignature
;
1084 // Make sure the signing certificate is valid for the specified policy
1085 SecTrustResultType trustResult
= kSecTrustResultInvalid
;
1086 status
= SecTrustEvaluate(trust
, &trustResult
);
1087 if (status
!= errSecSuccess
) {
1088 secdebug("validupdate", "SecTrustEvaluate failed with error %d (trust=%p)\n", (int)status
, (void *)trust
);
1089 } else if (!(trustResult
== kSecTrustResultUnspecified
|| trustResult
== kSecTrustResultProceed
)) {
1090 secdebug("validupdate", "SecTrustEvaluate failed with trust result %d\n", (int)trustResult
);
1091 status
= errSecVerificationFailure
;
1096 CFReleaseSafe(content
);
1097 CFReleaseSafe(trust
);
1098 CFReleaseSafe(policy
);
1101 return (status
== errSecSuccess
);
1104 CFAbsoluteTime
SecRevocationDbComputeNextUpdateTime(CFIndex updateInterval
) {
1105 CFIndex interval
= updateInterval
;
1106 // try to use interval preference if it exists
1107 CFTypeRef value
= (CFNumberRef
)CFPreferencesCopyValue(kUpdateIntervalKey
, kSecPrefsDomain
, kCFPreferencesAnyUser
, kCFPreferencesCurrentHost
);
1108 if (isNumber(value
)) {
1109 CFNumberGetValue((CFNumberRef
)value
, kCFNumberCFIndexType
, &interval
);
1111 CFReleaseNull(value
);
1113 if (interval
<= 0) {
1114 interval
= kSecStdUpdateInterval
;
1118 if (interval
< kSecMinUpdateInterval
) {
1119 interval
= kSecMinUpdateInterval
;
1120 } else if (interval
> kSecMaxUpdateInterval
) {
1121 interval
= kSecMaxUpdateInterval
;
1124 // compute randomization factor, between 0 and 50% of the interval
1125 CFIndex fuzz
= arc4random() % (long)(interval
/2.0);
1126 CFAbsoluteTime nextUpdate
= CFAbsoluteTimeGetCurrent() + interval
+ fuzz
;
1127 secdebug("validupdate", "next update in %ld seconds", (long)(interval
+ fuzz
));
1131 void SecRevocationDbComputeAndSetNextUpdateTime(void) {
1132 gNextUpdate
= SecRevocationDbComputeNextUpdateTime(0);
1133 (void) SecRevocationDbSetNextUpdateTime(gNextUpdate
, NULL
);
1134 gUpdateStarted
= 0; /* no update is currently in progress */
1137 bool SecRevocationDbIngestUpdate(SecRevocationDbConnectionRef dbc
, CFDictionaryRef update
, CFIndex chunkVersion
, CFIndex
*outVersion
, CFErrorRef
*error
) {
1139 CFIndex version
= 0;
1140 CFErrorRef localError
= NULL
;
1142 SecError(errSecParam
, &localError
, CFSTR("SecRevocationDbIngestUpdate: invalid update parameter"));
1143 goto setVersionAndExit
;
1145 CFTypeRef value
= (CFNumberRef
)CFDictionaryGetValue(update
, CFSTR("version"));
1146 if (isNumber(value
)) {
1147 if (!CFNumberGetValue((CFNumberRef
)value
, kCFNumberCFIndexType
, &version
)) {
1152 // only the first chunk will have a version, so the second and
1153 // subsequent chunks will need to pass it in chunkVersion.
1154 version
= chunkVersion
;
1156 // check precommitted version since update hasn't been committed yet
1157 CFIndex curVersion
= dbc
->precommitVersion
;
1158 if (version
> curVersion
|| chunkVersion
> 0) {
1159 ok
= _SecRevocationDbApplyUpdate(dbc
, update
, version
, &localError
);
1160 secdebug("validupdate", "_SecRevocationDbApplyUpdate=%s, v%ld, precommit=%ld, full=%s",
1161 (ok
) ? "1" : "0", (long)version
, (long)dbc
->precommitVersion
,
1162 (dbc
->fullUpdate
) ? "1" : "0");
1164 secdebug("validupdate", "we have v%ld, skipping update to v%ld",
1165 (long)curVersion
, (long)version
);
1166 version
= -1; // invalid, so we know to skip subsequent chunks
1167 ok
= true; // this is not an error condition
1171 *outVersion
= version
;
1173 (void) CFErrorPropagate(localError
, error
);
1178 /* Database schema */
1180 /* admin table holds these key-value (or key-ival) pairs:
1181 'version' (integer) // version of database content
1182 'check_again' (double) // CFAbsoluteTime of next check (optional)
1183 'db_version' (integer) // version of database schema
1184 'db_hash' (blob) // SHA-256 database hash
1185 --> entries in admin table are unique by text key
1187 issuers table holds map of issuing CA hashes to group identifiers:
1188 groupid (integer) // associated group identifier in group ID table
1189 issuer_hash (blob) // SHA-256 hash of issuer certificate (primary key)
1190 --> entries in issuers table are unique by issuer_hash;
1191 multiple issuer entries may have the same groupid!
1193 groups table holds records with these attributes:
1194 groupid (integer) // ordinal ID associated with this group entry
1195 flags (integer) // a bitmask of the following values:
1196 kSecValidInfoComplete (0x00000001) set if we have all revocation info for this issuer group
1197 kSecValidInfoCheckOCSP (0x00000002) set if must check ocsp for certs from this issuer group
1198 kSecValidInfoKnownOnly (0x00000004) set if any CA from this issuer group must be in database
1199 kSecValidInfoRequireCT (0x00000008) set if all certs from this issuer group must have SCTs
1200 kSecValidInfoAllowlist (0x00000010) set if this entry describes valid certs (i.e. is allowed)
1201 kSecValidInfoNoCACheck (0x00000020) set if this entry does not require an OCSP check to accept (deprecated)
1202 kSecValidInfoOverridable (0x00000040) set if the trust status is recoverable and can be overridden
1203 kSecValidInfoDateConstraints (0x00000080) set if this group has not-before or not-after constraints
1204 kSecValidInfoNameConstraints (0x00000100) [RESERVED] set if this group has name constraints in database
1205 kSecValidInfoPolicyConstraints (0x00000200) [RESERVED] set if this group has policy constraints in database
1206 kSecValidInfoNoCAv2Check (0x00000400) set if this entry does not require an OCSP check to accept
1207 format (integer) // an integer describing format of entries:
1208 kSecValidInfoFormatUnknown (0) unknown format
1209 kSecValidInfoFormatSerial (1) serial number, not greater than 20 bytes in length
1210 kSecValidInfoFormatSHA256 (2) SHA-256 hash, 32 bytes in length
1211 kSecValidInfoFormatNto1 (3) filter data blob of arbitrary length
1212 data (blob) // Bloom filter data if format is 'nto1', otherwise NULL
1213 --> entries in groups table are unique by groupid
1215 serials table holds serial number blobs with these attributes:
1216 groupid (integer) // identifier for issuer group in the groups table
1217 serial (blob) // serial number
1218 --> entries in serials table are unique by serial and groupid
1220 hashes table holds SHA-256 hashes of certificates with these attributes:
1221 groupid (integer) // identifier for issuer group in the groups table
1222 sha256 (blob) // SHA-256 hash of subject certificate
1223 --> entries in hashes table are unique by sha256 and groupid
1225 dates table holds notBefore and notAfter dates (as CFAbsoluteTime) with these attributes:
1226 groupid (integer) // identifier for issuer group in the groups table (primary key)
1227 notbefore (real) // issued certs are invalid if their notBefore is prior to this date
1228 notafter (real) // issued certs are invalid after this date (or their notAfter, if earlier)
1229 --> entries in dates table are unique by groupid, and only exist if kSecValidInfoDateConstraints is true
1232 #define createTablesSQL CFSTR("CREATE TABLE IF NOT EXISTS admin(" \
1233 "key TEXT PRIMARY KEY NOT NULL," \
1234 "ival INTEGER NOT NULL," \
1237 "CREATE TABLE IF NOT EXISTS issuers(" \
1238 "groupid INTEGER NOT NULL," \
1239 "issuer_hash BLOB PRIMARY KEY NOT NULL" \
1241 "CREATE INDEX IF NOT EXISTS issuer_idx ON issuers(issuer_hash);" \
1242 "CREATE TABLE IF NOT EXISTS groups(" \
1243 "groupid INTEGER PRIMARY KEY AUTOINCREMENT," \
1248 "CREATE TABLE IF NOT EXISTS serials(" \
1249 "groupid INTEGER NOT NULL," \
1250 "serial BLOB NOT NULL," \
1251 "UNIQUE(groupid,serial)" \
1253 "CREATE TABLE IF NOT EXISTS hashes(" \
1254 "groupid INTEGER NOT NULL," \
1255 "sha256 BLOB NOT NULL," \
1256 "UNIQUE(groupid,sha256)" \
1258 "CREATE TABLE IF NOT EXISTS dates(" \
1259 "groupid INTEGER PRIMARY KEY NOT NULL," \
1263 "CREATE TRIGGER IF NOT EXISTS group_del " \
1264 "BEFORE DELETE ON groups FOR EACH ROW " \
1266 "DELETE FROM serials WHERE groupid=OLD.groupid; " \
1267 "DELETE FROM hashes WHERE groupid=OLD.groupid; " \
1268 "DELETE FROM issuers WHERE groupid=OLD.groupid; " \
1269 "DELETE FROM dates WHERE groupid=OLD.groupid; " \
1272 #define selectGroupIdSQL CFSTR("SELECT DISTINCT groupid " \
1273 "FROM issuers WHERE issuer_hash=?")
1274 #define selectVersionSQL CFSTR("SELECT ival FROM admin " \
1275 "WHERE key='version'")
1276 #define selectDbVersionSQL CFSTR("SELECT ival FROM admin " \
1277 "WHERE key='db_version'")
1278 #define selectDbFormatSQL CFSTR("SELECT ival FROM admin " \
1279 "WHERE key='db_format'")
1280 #define selectDbHashSQL CFSTR("SELECT value FROM admin " \
1281 "WHERE key='db_hash'")
1282 #define selectDbSourceSQL CFSTR("SELECT value FROM admin " \
1283 "WHERE key='db_source'")
1284 #define selectNextUpdateSQL CFSTR("SELECT value FROM admin " \
1285 "WHERE key='check_again'")
1286 #define selectGroupRecordSQL CFSTR("SELECT flags,format,data FROM " \
1287 "groups WHERE groupid=?")
1288 #define selectSerialRecordSQL CFSTR("SELECT rowid FROM serials " \
1289 "WHERE groupid=? AND serial=?")
1290 #define selectDateRecordSQL CFSTR("SELECT notbefore,notafter FROM " \
1291 "dates WHERE groupid=?")
1292 #define selectHashRecordSQL CFSTR("SELECT rowid FROM hashes " \
1293 "WHERE groupid=? AND sha256=?")
1294 #define insertAdminRecordSQL CFSTR("INSERT OR REPLACE INTO admin " \
1295 "(key,ival,value) VALUES (?,?,?)")
1296 #define insertIssuerRecordSQL CFSTR("INSERT OR REPLACE INTO issuers " \
1297 "(groupid,issuer_hash) VALUES (?,?)")
1298 #define insertGroupRecordSQL CFSTR("INSERT OR REPLACE INTO groups " \
1299 "(groupid,flags,format,data) VALUES (?,?,?,?)")
1300 #define insertSerialRecordSQL CFSTR("INSERT OR REPLACE INTO serials " \
1301 "(groupid,serial) VALUES (?,?)")
1302 #define deleteSerialRecordSQL CFSTR("DELETE FROM serials " \
1303 "WHERE groupid=? AND hex(serial) LIKE ?")
1304 #define insertSha256RecordSQL CFSTR("INSERT OR REPLACE INTO hashes " \
1305 "(groupid,sha256) VALUES (?,?)")
1306 #define deleteSha256RecordSQL CFSTR("DELETE FROM hashes " \
1307 "WHERE groupid=? AND hex(sha256) LIKE ?")
1308 #define insertDateRecordSQL CFSTR("INSERT OR REPLACE INTO dates " \
1309 "(groupid,notbefore,notafter) VALUES (?,?,?)")
1310 #define deleteGroupRecordSQL CFSTR("DELETE FROM groups " \
1312 #define deleteGroupIssuersSQL CFSTR("DELETE FROM issuers " \
1315 #define updateConstraintsTablesSQL CFSTR("" \
1316 "CREATE TABLE IF NOT EXISTS dates(" \
1317 "groupid INTEGER PRIMARY KEY NOT NULL," \
1322 #define updateGroupDeleteTriggerSQL CFSTR("" \
1323 "DROP TRIGGER IF EXISTS group_del;" \
1324 "CREATE TRIGGER group_del BEFORE DELETE ON groups FOR EACH ROW " \
1326 "DELETE FROM serials WHERE groupid=OLD.groupid; " \
1327 "DELETE FROM hashes WHERE groupid=OLD.groupid; " \
1328 "DELETE FROM issuers WHERE groupid=OLD.groupid; " \
1329 "DELETE FROM dates WHERE groupid=OLD.groupid; " \
1332 #define deleteAllEntriesSQL CFSTR("" \
1333 "DELETE FROM groups; " \
1334 "DELETE FROM admin WHERE key='version'; " \
1335 "DELETE FROM sqlite_sequence")
1338 /* Database management */
1340 static SecDbRef
SecRevocationDbCreate(CFStringRef path
) {
1341 /* only the db owner should open a read-write connection. */
1342 __block
bool readWrite
= isDbOwner();
1345 SecDbRef result
= SecDbCreate(path
, mode
, readWrite
, false, true, true, 1, ^bool (SecDbRef db
, SecDbConnectionRef dbconn
, bool didCreate
, bool *callMeAgainForNextConnection
, CFErrorRef
*error
) {
1346 __block
bool ok
= true;
1348 ok
&= SecDbTransaction(dbconn
, kSecDbExclusiveTransactionType
, error
, ^(bool *commit
) {
1349 /* Create all database tables, indexes, and triggers.
1350 * SecDbOpen will set auto_vacuum and journal_mode for us before we get called back.*/
1351 ok
= ok
&& SecDbExec(dbconn
, createTablesSQL
, error
);
1355 if (!ok
|| (error
&& *error
)) {
1356 CFIndex errCode
= errSecInternalComponent
;
1357 if (error
&& *error
) {
1358 errCode
= CFErrorGetCode(*error
);
1360 secerror("%s failed: %@", didCreate
? "Create" : "Open", error
? *error
: NULL
);
1361 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationCreate
, TAFatalError
, errCode
);
1369 static dispatch_once_t kSecRevocationDbOnce
;
1370 static SecRevocationDbRef kSecRevocationDb
= NULL
;
1372 static SecRevocationDbRef
SecRevocationDbInit(CFStringRef db_name
) {
1373 SecRevocationDbRef rdb
;
1374 dispatch_queue_attr_t attr
;
1376 require(rdb
= (SecRevocationDbRef
)malloc(sizeof(struct __SecRevocationDb
)), errOut
);
1378 rdb
->update_queue
= NULL
;
1379 rdb
->updateInProgress
= false;
1380 rdb
->unsupportedVersion
= false;
1381 rdb
->changed
= false;
1383 require(rdb
->db
= SecRevocationDbCreate(db_name
), errOut
);
1384 attr
= dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL
, QOS_CLASS_BACKGROUND
, 0);
1385 attr
= dispatch_queue_attr_make_with_autorelease_frequency(attr
, DISPATCH_AUTORELEASE_FREQUENCY_WORK_ITEM
);
1386 require(rdb
->update_queue
= dispatch_queue_create(NULL
, attr
), errOut
);
1387 require(rdb
->info_cache_list
= CFArrayCreateMutable(NULL
, 0, &kCFTypeArrayCallBacks
), errOut
);
1388 require(rdb
->info_cache
= CFDictionaryCreateMutable(NULL
, 0, &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
), errOut
);
1389 rdb
->info_cache_lock
= OS_UNFAIR_LOCK_INIT
;
1392 /* register for changes signaled by the db owner instance */
1394 notify_register_dispatch(kSecRevocationDbChanged
, &out_token
, rdb
->update_queue
, ^(int __unused token
) {
1395 secnotice("validupdate", "Got notification of database change");
1396 SecRevocationDbResetCaches();
1402 secdebug("validupdate", "Failed to create db at \"%@\"", db_name
);
1404 if (rdb
->update_queue
) {
1405 dispatch_release(rdb
->update_queue
);
1407 CFReleaseSafe(rdb
->db
);
1413 static CFStringRef
SecRevocationDbCopyPath(void) {
1414 CFURLRef revDbURL
= NULL
;
1415 CFStringRef revInfoRelPath
= NULL
;
1416 if ((revInfoRelPath
= CFStringCreateWithFormat(NULL
, NULL
, CFSTR("%s"), kSecRevocationDbFileName
)) != NULL
) {
1417 revDbURL
= SecCopyURLForFileInRevocationInfoDirectory(revInfoRelPath
);
1419 CFReleaseSafe(revInfoRelPath
);
1421 CFStringRef revDbPath
= NULL
;
1423 revDbPath
= CFURLCopyFileSystemPath(revDbURL
, kCFURLPOSIXPathStyle
);
1424 CFRelease(revDbURL
);
1429 static void SecRevocationDbWith(void(^dbJob
)(SecRevocationDbRef db
)) {
1430 dispatch_once(&kSecRevocationDbOnce
, ^{
1431 CFStringRef dbPath
= SecRevocationDbCopyPath();
1433 kSecRevocationDb
= SecRevocationDbInit(dbPath
);
1435 if (kSecRevocationDb
&& isDbOwner()) {
1436 /* check and update schema immediately after database is opened */
1437 SecRevocationDbUpdateSchema(kSecRevocationDb
);
1441 // Do pre job run work here (cancel idle timers etc.)
1442 if (kSecRevocationDb
->updateInProgress
) {
1443 return; // this would block since SecDb has an exclusive transaction lock
1445 dbJob(kSecRevocationDb
);
1446 // Do post job run work here (gc timer, etc.)
1449 static bool SecRevocationDbPerformWrite(SecRevocationDbRef rdb
, CFErrorRef
*error
,
1450 bool(^writeJob
)(SecRevocationDbConnectionRef dbc
, CFErrorRef
*blockError
)) {
1451 __block
bool ok
= true;
1452 __block CFErrorRef localError
= NULL
;
1454 ok
&= SecDbPerformWrite(rdb
->db
, &localError
, ^(SecDbConnectionRef dbconn
) {
1455 ok
&= SecDbTransaction(dbconn
, kSecDbImmediateTransactionType
, &localError
, ^(bool *commit
) {
1456 SecRevocationDbConnectionRef dbc
= SecRevocationDbConnectionInit(rdb
, dbconn
, &localError
);
1457 ok
= ok
&& writeJob(dbc
, &localError
);
1462 ok
&= CFErrorPropagate(localError
, error
);
1466 static bool SecRevocationDbPerformRead(SecRevocationDbRef rdb
, CFErrorRef
*error
,
1467 bool(^readJob
)(SecRevocationDbConnectionRef dbc
, CFErrorRef
*blockError
)) {
1468 __block CFErrorRef localError
= NULL
;
1469 __block
bool ok
= true;
1471 ok
&= SecDbPerformRead(rdb
->db
, &localError
, ^(SecDbConnectionRef dbconn
) {
1472 SecRevocationDbConnectionRef dbc
= SecRevocationDbConnectionInit(rdb
, dbconn
, &localError
);
1473 ok
= ok
&& readJob(dbc
, &localError
);
1476 ok
&= CFErrorPropagate(localError
, error
);
1480 static SecRevocationDbConnectionRef
SecRevocationDbConnectionInit(SecRevocationDbRef db
, SecDbConnectionRef dbconn
, CFErrorRef
*error
) {
1481 SecRevocationDbConnectionRef dbc
= NULL
;
1482 CFErrorRef localError
= NULL
;
1484 dbc
= (SecRevocationDbConnectionRef
)malloc(sizeof(struct __SecRevocationDbConnection
));
1487 dbc
->dbconn
= dbconn
;
1488 dbc
->precommitVersion
= _SecRevocationDbGetVersion(dbc
, &localError
);
1489 dbc
->precommitDbVersion
= _SecRevocationDbGetSchemaVersion(db
, dbc
, &localError
);
1490 dbc
->fullUpdate
= false;
1492 (void) CFErrorPropagate(localError
, error
);
1496 static CF_RETURNS_RETAINED CFDataRef
createCacheKey(CFDataRef certHash
, CFDataRef issuerHash
) {
1497 CFMutableDataRef concat
= CFDataCreateMutableCopy(NULL
, 0, certHash
);
1498 CFDataAppend(concat
, issuerHash
);
1499 CFDataRef result
= SecSHA256DigestCreateFromData(NULL
, concat
);
1500 CFReleaseNull(concat
);
1504 static CF_RETURNS_RETAINED SecValidInfoRef
SecRevocationDbCacheRead(SecRevocationDbRef db
,
1505 SecCertificateRef certificate
,
1506 CFDataRef issuerHash
) {
1510 SecValidInfoRef result
= NULL
;
1511 if (!db
|| !db
->info_cache
|| !db
->info_cache_list
) {
1514 CFIndex ix
= kCFNotFound
;
1515 CFDataRef certHash
= SecCertificateCopySHA256Digest(certificate
);
1516 CFDataRef cacheKey
= createCacheKey(certHash
, issuerHash
);
1518 os_unfair_lock_lock(&db
->info_cache_lock
); // grab the cache lock before using the cache
1519 if (0 <= (ix
= CFArrayGetFirstIndexOfValue(db
->info_cache_list
,
1520 CFRangeMake(0, CFArrayGetCount(db
->info_cache_list
)),
1522 result
= (SecValidInfoRef
)CFDictionaryGetValue(db
->info_cache
, cacheKey
);
1523 // Verify this really is the right result
1524 if (CFEqualSafe(result
->certHash
, certHash
) && CFEqualSafe(result
->issuerHash
, issuerHash
)) {
1525 // Cache hit. Move the entry to the bottom of the list.
1526 CFArrayRemoveValueAtIndex(db
->info_cache_list
, ix
);
1527 CFArrayAppendValue(db
->info_cache_list
, cacheKey
);
1528 secdebug("validcache", "cache hit: %@", cacheKey
);
1530 // Just remove this bad entry
1531 CFArrayRemoveValueAtIndex(db
->info_cache_list
, ix
);
1532 CFDictionaryRemoveValue(db
->info_cache
, cacheKey
);
1533 secdebug("validcache", "cache remove bad: %@", cacheKey
);
1534 secnotice("validcache", "found a bad valid info cache entry at %ld", (long)ix
);
1537 CFRetainSafe(result
);
1538 os_unfair_lock_unlock(&db
->info_cache_lock
);
1539 CFReleaseSafe(certHash
);
1540 CFReleaseSafe(cacheKey
);
1544 static void SecRevocationDbCacheWrite(SecRevocationDbRef db
,
1545 SecValidInfoRef validInfo
) {
1546 if (!db
|| !validInfo
|| !db
->info_cache
|| !db
->info_cache_list
) {
1550 CFDataRef cacheKey
= createCacheKey(validInfo
->certHash
, validInfo
->issuerHash
);
1552 os_unfair_lock_lock(&db
->info_cache_lock
); // grab the cache lock before using the cache
1553 // check to make sure another thread didn't add this entry to the cache already
1554 if (0 > CFArrayGetFirstIndexOfValue(db
->info_cache_list
,
1555 CFRangeMake(0, CFArrayGetCount(db
->info_cache_list
)),
1557 CFDictionaryAddValue(db
->info_cache
, cacheKey
, validInfo
);
1558 if (kSecRevocationDbCacheSize
<= CFArrayGetCount(db
->info_cache_list
)) {
1559 // Remove least recently used cache entry.
1560 secdebug("validcache", "cache remove stale: %@", CFArrayGetValueAtIndex(db
->info_cache_list
, 0));
1561 CFDictionaryRemoveValue(db
->info_cache
, CFArrayGetValueAtIndex(db
->info_cache_list
, 0));
1562 CFArrayRemoveValueAtIndex(db
->info_cache_list
, 0);
1564 CFArrayAppendValue(db
->info_cache_list
, cacheKey
);
1565 secdebug("validcache", "cache add: %@", cacheKey
);
1567 os_unfair_lock_unlock(&db
->info_cache_lock
);
1568 CFReleaseNull(cacheKey
);
1571 static void SecRevocationDbCachePurge(SecRevocationDbRef db
) {
1572 if (!db
|| !db
->info_cache
|| !db
->info_cache_list
) {
1576 /* grab the cache lock and clear all entries */
1577 os_unfair_lock_lock(&db
->info_cache_lock
);
1578 CFArrayRemoveAllValues(db
->info_cache_list
);
1579 CFDictionaryRemoveAllValues(db
->info_cache
);
1580 secdebug("validcache", "cache purge");
1581 os_unfair_lock_unlock(&db
->info_cache_lock
);
1584 static int64_t _SecRevocationDbGetVersion(SecRevocationDbConnectionRef dbc
, CFErrorRef
*error
) {
1585 /* look up version entry in admin table; returns -1 on error */
1586 __block
int64_t version
= -1;
1587 __block
bool ok
= (dbc
!= NULL
);
1588 __block CFErrorRef localError
= NULL
;
1590 ok
= ok
&& SecDbWithSQL(dbc
->dbconn
, selectVersionSQL
, &localError
, ^bool(sqlite3_stmt
*selectVersion
) {
1591 ok
= ok
&& SecDbStep(dbc
->dbconn
, selectVersion
, &localError
, ^void(bool *stop
) {
1592 version
= sqlite3_column_int64(selectVersion
, 0);
1597 if (!ok
|| localError
) {
1598 secerror("_SecRevocationDbGetVersion failed: %@", localError
);
1599 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationRead
, TAFatalError
,
1600 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
1602 (void) CFErrorPropagate(localError
, error
);
1606 static bool _SecRevocationDbSetVersion(SecRevocationDbConnectionRef dbc
, CFIndex version
, CFErrorRef
*error
) {
1607 secdebug("validupdate", "setting version to %ld", (long)version
);
1609 __block CFErrorRef localError
= NULL
;
1610 __block
bool ok
= (dbc
!= NULL
);
1611 ok
= ok
&& SecDbWithSQL(dbc
->dbconn
, insertAdminRecordSQL
, &localError
, ^bool(sqlite3_stmt
*insertVersion
) {
1612 const char *versionKey
= "version";
1613 ok
= ok
&& SecDbBindText(insertVersion
, 1, versionKey
, strlen(versionKey
),
1614 SQLITE_TRANSIENT
, &localError
);
1615 ok
= ok
&& SecDbBindInt64(insertVersion
, 2,
1616 (sqlite3_int64
)version
, &localError
);
1617 ok
= ok
&& SecDbStep(dbc
->dbconn
, insertVersion
, &localError
, NULL
);
1620 if (!ok
|| localError
) {
1621 secerror("_SecRevocationDbSetVersion failed: %@", localError
);
1622 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationWrite
, TAFatalError
,
1623 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
1625 (void) CFErrorPropagate(localError
, error
);
1629 static int64_t _SecRevocationDbReadSchemaVersionFromDb(SecRevocationDbConnectionRef dbc
, CFErrorRef
*error
) {
1630 /* look up db_version entry in admin table; returns -1 on error */
1631 __block
int64_t db_version
= -1;
1632 __block
bool ok
= (dbc
!= NULL
);
1633 __block CFErrorRef localError
= NULL
;
1635 ok
= ok
&& SecDbWithSQL(dbc
->dbconn
, selectDbVersionSQL
, &localError
, ^bool(sqlite3_stmt
*selectDbVersion
) {
1636 ok
= ok
&& SecDbStep(dbc
->dbconn
, selectDbVersion
, &localError
, ^void(bool *stop
) {
1637 db_version
= sqlite3_column_int64(selectDbVersion
, 0);
1642 if (!ok
|| localError
) {
1643 secerror("_SecRevocationDbReadSchemaVersionFromDb failed: %@", localError
);
1644 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationRead
, TAFatalError
,
1645 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
1647 (void) CFErrorPropagate(localError
, error
);
1651 static _Atomic
int64_t gSchemaVersion
= -1;
1652 static int64_t _SecRevocationDbGetSchemaVersion(SecRevocationDbRef rdb
, SecRevocationDbConnectionRef dbc
, CFErrorRef
*error
) {
1653 static dispatch_once_t onceToken
;
1654 dispatch_once(&onceToken
, ^{
1656 atomic_init(&gSchemaVersion
, _SecRevocationDbReadSchemaVersionFromDb(dbc
, error
));
1658 (void) SecRevocationDbPerformRead(rdb
, error
, ^bool(SecRevocationDbConnectionRef dbc
, CFErrorRef
*blockError
) {
1659 atomic_init(&gSchemaVersion
, _SecRevocationDbReadSchemaVersionFromDb(dbc
, blockError
));
1664 if (atomic_load(&gSchemaVersion
) == -1) {
1665 /* Initial read(s) failed. Try to read the schema version again. */
1667 atomic_store(&gSchemaVersion
, _SecRevocationDbReadSchemaVersionFromDb(dbc
, error
));
1669 (void) SecRevocationDbPerformRead(rdb
, error
, ^bool(SecRevocationDbConnectionRef dbc
, CFErrorRef
*blockError
) {
1670 atomic_store(&gSchemaVersion
, _SecRevocationDbReadSchemaVersionFromDb(dbc
, blockError
));
1675 return atomic_load(&gSchemaVersion
);
1678 static void SecRevocationDbResetCaches(void) {
1679 SecRevocationDbWith(^(SecRevocationDbRef db
) {
1680 db
->unsupportedVersion
= false;
1681 db
->changed
= false;
1682 (void) SecRevocationDbPerformRead(db
, NULL
, ^bool(SecRevocationDbConnectionRef dbc
, CFErrorRef
*blockError
) {
1683 atomic_store(&gSchemaVersion
, _SecRevocationDbReadSchemaVersionFromDb(dbc
, blockError
));
1686 SecRevocationDbCachePurge(db
);
1690 static bool _SecRevocationDbSetSchemaVersion(SecRevocationDbConnectionRef dbc
, CFIndex dbversion
, CFErrorRef
*error
) {
1691 if (dbversion
> 0) {
1692 int64_t db_version
= (dbc
) ? dbc
->precommitDbVersion
: -1;
1693 if (db_version
>= dbversion
) {
1694 return true; /* requested schema is earlier than current schema */
1697 secdebug("validupdate", "setting db_version to %ld", (long)dbversion
);
1699 __block CFErrorRef localError
= NULL
;
1700 __block
bool ok
= (dbc
!= NULL
);
1701 ok
= ok
&& SecDbWithSQL(dbc
->dbconn
, insertAdminRecordSQL
, &localError
, ^bool(sqlite3_stmt
*insertDbVersion
) {
1702 const char *dbVersionKey
= "db_version";
1703 ok
= ok
&& SecDbBindText(insertDbVersion
, 1, dbVersionKey
, strlen(dbVersionKey
),
1704 SQLITE_TRANSIENT
, &localError
);
1705 ok
= ok
&& SecDbBindInt64(insertDbVersion
, 2,
1706 (sqlite3_int64
)dbversion
, &localError
);
1707 ok
= ok
&& SecDbStep(dbc
->dbconn
, insertDbVersion
, &localError
, NULL
);
1710 if (!ok
|| localError
) {
1711 secerror("_SecRevocationDbSetSchemaVersion failed: %@", localError
);
1712 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationWrite
, TAFatalError
,
1713 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
1715 dbc
->db
->changed
= true; /* will notify clients of this change */
1716 dbc
->db
->unsupportedVersion
= false;
1717 dbc
->precommitDbVersion
= dbversion
;
1718 atomic_store(&gSchemaVersion
, (int64_t)dbversion
);
1720 CFReleaseSafe(localError
);
1724 static bool _SecRevocationDbUpdateSchema(SecRevocationDbConnectionRef dbc
, CFErrorRef
*error
) {
1725 __block CFErrorRef localError
= NULL
;
1726 __block
bool ok
= (dbc
!= NULL
);
1727 __block
int64_t db_version
= (dbc
) ? dbc
->precommitDbVersion
: 0;
1728 if (db_version
>= kSecRevocationDbSchemaVersion
) {
1729 return ok
; /* schema version already up to date */
1731 secdebug("validupdate", "updating db schema from v%lld to v%lld",
1732 (long long)db_version
, (long long)kSecRevocationDbSchemaVersion
);
1734 if (ok
&& db_version
< 5) {
1735 /* apply v5 changes (add dates table and replace trigger) */
1736 ok
&= SecDbWithSQL(dbc
->dbconn
, updateConstraintsTablesSQL
, &localError
, ^bool(sqlite3_stmt
*updateTables
) {
1737 ok
= SecDbStep(dbc
->dbconn
, updateTables
, &localError
, NULL
);
1740 ok
&= SecDbWithSQL(dbc
->dbconn
, updateGroupDeleteTriggerSQL
, &localError
, ^bool(sqlite3_stmt
*updateTrigger
) {
1741 ok
= SecDbStep(dbc
->dbconn
, updateTrigger
, &localError
, NULL
);
1744 secdebug("validupdate", "applied schema update to v5 (%s)", (ok
) ? "ok" : "failed!");
1746 if (ok
&& db_version
< 6) {
1747 /* apply v6 changes (the SecDb layer will update autovacuum mode if needed, so we don't execute
1748 any SQL here, but we do want the database to be replaced in case transaction scope problems
1749 with earlier versions caused missing entries.) */
1750 secdebug("validupdate", "applied schema update to v6 (%s)", (ok
) ? "ok" : "failed!");
1751 if (db_version
> 0) {
1752 SecValidUpdateForceReplaceDatabase();
1757 secerror("_SecRevocationDbUpdateSchema failed: %@", localError
);
1759 ok
= ok
&& _SecRevocationDbSetSchemaVersion(dbc
, kSecRevocationDbSchemaVersion
, &localError
);
1761 (void) CFErrorPropagate(localError
, error
);
1765 bool SecRevocationDbUpdateSchema(SecRevocationDbRef rdb
) {
1766 /* note: this function assumes it is called only by the database owner.
1767 non-owner (read-only) clients will fail if changes to the db are needed. */
1768 if (!rdb
|| !rdb
->db
) {
1771 __block
bool ok
= true;
1772 __block CFErrorRef localError
= NULL
;
1773 ok
&= SecRevocationDbPerformWrite(rdb
, &localError
, ^bool(SecRevocationDbConnectionRef dbc
, CFErrorRef
*blockError
) {
1774 return _SecRevocationDbUpdateSchema(dbc
, blockError
);
1776 CFReleaseSafe(localError
);
1780 static int64_t _SecRevocationDbGetUpdateFormat(SecRevocationDbConnectionRef dbc
, CFErrorRef
*error
) {
1781 /* look up db_format entry in admin table; returns -1 on error */
1782 __block
int64_t db_format
= -1;
1783 __block
bool ok
= (dbc
!= NULL
);
1784 __block CFErrorRef localError
= NULL
;
1786 ok
= ok
&& SecDbWithSQL(dbc
->dbconn
, selectDbFormatSQL
, &localError
, ^bool(sqlite3_stmt
*selectDbFormat
) {
1787 ok
&= SecDbStep(dbc
->dbconn
, selectDbFormat
, &localError
, ^void(bool *stop
) {
1788 db_format
= sqlite3_column_int64(selectDbFormat
, 0);
1793 if (!ok
|| localError
) {
1794 secerror("_SecRevocationDbGetUpdateFormat failed: %@", localError
);
1795 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationRead
, TAFatalError
,
1796 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
1798 (void) CFErrorPropagate(localError
, error
);
1802 static bool _SecRevocationDbSetUpdateFormat(SecRevocationDbConnectionRef dbc
, CFIndex dbformat
, CFErrorRef
*error
) {
1803 secdebug("validupdate", "setting db_format to %ld", (long)dbformat
);
1805 __block CFErrorRef localError
= NULL
;
1806 __block
bool ok
= (dbc
!= NULL
);
1807 ok
= ok
&& SecDbWithSQL(dbc
->dbconn
, insertAdminRecordSQL
, &localError
, ^bool(sqlite3_stmt
*insertDbFormat
) {
1808 const char *dbFormatKey
= "db_format";
1809 ok
= ok
&& SecDbBindText(insertDbFormat
, 1, dbFormatKey
, strlen(dbFormatKey
),
1810 SQLITE_TRANSIENT
, &localError
);
1811 ok
= ok
&& SecDbBindInt64(insertDbFormat
, 2,
1812 (sqlite3_int64
)dbformat
, &localError
);
1813 ok
= ok
&& SecDbStep(dbc
->dbconn
, insertDbFormat
, &localError
, NULL
);
1816 if (!ok
|| localError
) {
1817 secerror("_SecRevocationDbSetUpdateFormat failed: %@", localError
);
1818 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationWrite
, TAFatalError
,
1819 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
1821 dbc
->db
->changed
= true; /* will notify clients of this change */
1822 dbc
->db
->unsupportedVersion
= false;
1824 (void) CFErrorPropagate(localError
, error
);
1828 static CFStringRef
_SecRevocationDbCopyUpdateSource(SecRevocationDbConnectionRef dbc
, CFErrorRef
*error
) {
1829 /* look up db_source entry in admin table; returns NULL on error */
1830 __block CFStringRef updateSource
= NULL
;
1831 __block
bool ok
= (dbc
!= NULL
);
1832 __block CFErrorRef localError
= NULL
;
1834 ok
= ok
&& SecDbWithSQL(dbc
->dbconn
, selectDbSourceSQL
, &localError
, ^bool(sqlite3_stmt
*selectDbSource
) {
1835 ok
&= SecDbStep(dbc
->dbconn
, selectDbSource
, &localError
, ^void(bool *stop
) {
1836 const UInt8
*p
= (const UInt8
*)sqlite3_column_blob(selectDbSource
, 0);
1838 CFIndex length
= (CFIndex
)sqlite3_column_bytes(selectDbSource
, 0);
1840 updateSource
= CFStringCreateWithBytes(kCFAllocatorDefault
, p
, length
, kCFStringEncodingUTF8
, false);
1847 if (!ok
|| localError
) {
1848 secerror("_SecRevocationDbCopyUpdateSource failed: %@", localError
);
1849 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationRead
, TAFatalError
,
1850 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
1852 (void) CFErrorPropagate(localError
, error
);
1853 return updateSource
;
1856 bool _SecRevocationDbSetUpdateSource(SecRevocationDbConnectionRef dbc
, CFStringRef updateSource
, CFErrorRef
*error
) {
1857 if (!updateSource
) {
1858 secerror("_SecRevocationDbSetUpdateSource failed: %d", errSecParam
);
1861 __block
char buffer
[256];
1862 __block
const char *updateSourceCStr
= CFStringGetCStringPtr(updateSource
, kCFStringEncodingUTF8
);
1863 if (!updateSourceCStr
) {
1864 if (CFStringGetCString(updateSource
, buffer
, 256, kCFStringEncodingUTF8
)) {
1865 updateSourceCStr
= buffer
;
1868 if (!updateSourceCStr
) {
1869 secerror("_SecRevocationDbSetUpdateSource failed: unable to get UTF-8 encoding");
1872 secdebug("validupdate", "setting update source to \"%s\"", updateSourceCStr
);
1874 __block CFErrorRef localError
= NULL
;
1875 __block
bool ok
= (dbc
!= NULL
);
1876 ok
= ok
&& SecDbWithSQL(dbc
->dbconn
, insertAdminRecordSQL
, &localError
, ^bool(sqlite3_stmt
*insertRecord
) {
1877 const char *dbSourceKey
= "db_source";
1878 ok
= ok
&& SecDbBindText(insertRecord
, 1, dbSourceKey
, strlen(dbSourceKey
),
1879 SQLITE_TRANSIENT
, &localError
);
1880 ok
= ok
&& SecDbBindInt64(insertRecord
, 2,
1881 (sqlite3_int64
)0, &localError
);
1882 ok
= ok
&& SecDbBindBlob(insertRecord
, 3,
1883 updateSourceCStr
, strlen(updateSourceCStr
),
1884 SQLITE_TRANSIENT
, &localError
);
1885 ok
= ok
&& SecDbStep(dbc
->dbconn
, insertRecord
, &localError
, NULL
);
1888 if (!ok
|| localError
) {
1889 secerror("_SecRevocationDbSetUpdateSource failed: %@", localError
);
1890 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationWrite
, TAFatalError
,
1891 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
1893 (void) CFErrorPropagate(localError
, error
);
1894 CFReleaseSafe(localError
);
1898 bool SecRevocationDbSetUpdateSource(SecRevocationDbRef rdb
, CFStringRef updateSource
) {
1899 /* note: this function assumes it is called only by the database owner.
1900 non-owner (read-only) clients will fail if changes to the db are needed. */
1901 if (!rdb
|| !rdb
->db
) {
1904 CFErrorRef localError
= NULL
;
1906 ok
&= SecRevocationDbPerformWrite(rdb
, &localError
, ^bool(SecRevocationDbConnectionRef dbc
, CFErrorRef
*error
) {
1907 return _SecRevocationDbSetUpdateSource(dbc
, updateSource
, error
);
1909 CFReleaseSafe(localError
);
1913 static CFAbsoluteTime
_SecRevocationDbGetNextUpdateTime(SecRevocationDbConnectionRef dbc
, CFErrorRef
*error
) {
1914 /* look up check_again entry in admin table; returns 0 on error */
1915 __block CFAbsoluteTime nextUpdate
= 0;
1916 __block
bool ok
= (dbc
!= NULL
);
1917 __block CFErrorRef localError
= NULL
;
1919 ok
= ok
&& SecDbWithSQL(dbc
->dbconn
, selectNextUpdateSQL
, &localError
, ^bool(sqlite3_stmt
*selectNextUpdate
) {
1920 ok
&= SecDbStep(dbc
->dbconn
, selectNextUpdate
, &localError
, ^void(bool *stop
) {
1921 CFAbsoluteTime
*p
= (CFAbsoluteTime
*)sqlite3_column_blob(selectNextUpdate
, 0);
1923 if (sizeof(CFAbsoluteTime
) == sqlite3_column_bytes(selectNextUpdate
, 0)) {
1931 if (!ok
|| localError
) {
1932 secerror("_SecRevocationDbGetNextUpdateTime failed: %@", localError
);
1933 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationRead
, TAFatalError
,
1934 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
1936 (void) CFErrorPropagate(localError
, error
);
1940 static bool _SecRevocationDbSetNextUpdateTime(SecRevocationDbConnectionRef dbc
, CFAbsoluteTime nextUpdate
, CFErrorRef
*error
){
1941 secdebug("validupdate", "setting next update to %f", (double)nextUpdate
);
1943 __block CFErrorRef localError
= NULL
;
1944 __block
bool ok
= (dbc
!= NULL
);
1945 ok
= ok
&& SecDbWithSQL(dbc
->dbconn
, insertAdminRecordSQL
, &localError
, ^bool(sqlite3_stmt
*insertRecord
) {
1946 const char *nextUpdateKey
= "check_again";
1947 ok
= ok
&& SecDbBindText(insertRecord
, 1, nextUpdateKey
, strlen(nextUpdateKey
),
1948 SQLITE_TRANSIENT
, &localError
);
1949 ok
= ok
&& SecDbBindInt64(insertRecord
, 2,
1950 (sqlite3_int64
)0, &localError
);
1951 ok
= ok
&& SecDbBindBlob(insertRecord
, 3,
1952 &nextUpdate
, sizeof(CFAbsoluteTime
),
1953 SQLITE_TRANSIENT
, &localError
);
1954 ok
= ok
&& SecDbStep(dbc
->dbconn
, insertRecord
, &localError
, NULL
);
1957 if (!ok
|| localError
) {
1958 secerror("_SecRevocationDbSetNextUpdate failed: %@", localError
);
1959 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationWrite
, TAFatalError
,
1960 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
1962 (void) CFErrorPropagate(localError
, error
);
1966 bool _SecRevocationDbRemoveAllEntries(SecRevocationDbConnectionRef dbc
, CFErrorRef
*error
) {
1967 /* clear out the contents of the database and start fresh */
1968 bool ok
= (dbc
!= NULL
);
1969 CFErrorRef localError
= NULL
;
1971 /* _SecRevocationDbUpdateSchema was called when db was opened, so no need to do it again. */
1973 /* delete all entries */
1974 ok
= ok
&& SecDbExec(dbc
->dbconn
, deleteAllEntriesSQL
, &localError
);
1975 secnotice("validupdate", "resetting database, result: %d (expected 1)", (ok
) ? 1 : 0);
1977 /* one more thing: update the schema version and format to current */
1978 ok
= ok
&& _SecRevocationDbSetSchemaVersion(dbc
, kSecRevocationDbSchemaVersion
, &localError
);
1979 ok
= ok
&& _SecRevocationDbSetUpdateFormat(dbc
, kSecRevocationDbUpdateFormat
, &localError
);
1981 if (!ok
|| localError
) {
1982 secerror("_SecRevocationDbRemoveAllEntries failed: %@", localError
);
1983 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationWrite
, TAFatalError
,
1984 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
1986 (void) CFErrorPropagate(localError
, error
);
1990 static bool _SecRevocationDbUpdateIssuers(SecRevocationDbConnectionRef dbc
, int64_t groupId
, CFArrayRef issuers
, CFErrorRef
*error
) {
1991 /* insert or replace issuer records in issuers table */
1992 if (!issuers
|| groupId
< 0) {
1993 return false; /* must have something to insert, and a group to associate with it */
1995 __block
bool ok
= (dbc
!= NULL
);
1996 __block CFErrorRef localError
= NULL
;
1997 if (isArray(issuers
)) {
1998 CFIndex issuerIX
, issuerCount
= CFArrayGetCount(issuers
);
1999 for (issuerIX
=0; issuerIX
<issuerCount
&& ok
; issuerIX
++) {
2000 CFDataRef hash
= (CFDataRef
)CFArrayGetValueAtIndex(issuers
, issuerIX
);
2001 if (!hash
) { continue; }
2002 ok
= ok
&& SecDbWithSQL(dbc
->dbconn
, insertIssuerRecordSQL
, &localError
, ^bool(sqlite3_stmt
*insertIssuer
) {
2003 ok
= ok
&& SecDbBindInt64(insertIssuer
, 1,
2004 groupId
, &localError
);
2005 ok
= ok
&& SecDbBindBlob(insertIssuer
, 2,
2006 CFDataGetBytePtr(hash
),
2007 CFDataGetLength(hash
),
2008 SQLITE_TRANSIENT
, &localError
);
2009 /* Execute the insert statement for this issuer record. */
2010 ok
= ok
&& SecDbStep(dbc
->dbconn
, insertIssuer
, &localError
, NULL
);
2015 if (!ok
|| localError
) {
2016 secerror("_SecRevocationDbUpdateIssuers failed: %@", localError
);
2017 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationWrite
, TAFatalError
,
2018 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
2020 (void) CFErrorPropagate(localError
, error
);
2024 static SecValidInfoFormat
_SecRevocationDbGetGroupFormatForData(SecRevocationDbConnectionRef dbc
, int64_t groupId
, CFDataRef data
) {
2025 /* determine existing format if groupId is supplied and this is a partial update,
2026 otherwise return the expected format for the given data. */
2027 SecValidInfoFormat format
= kSecValidInfoFormatUnknown
;
2028 if (groupId
>= 0 && !dbc
->fullUpdate
) {
2029 format
= _SecRevocationDbGetGroupFormat(dbc
, groupId
, NULL
, NULL
, NULL
);
2031 if (format
== kSecValidInfoFormatUnknown
&& data
!= NULL
) {
2032 /* group doesn't exist, so determine format based on length of specified data.
2033 len <= 20 is a serial number (actually, <=37, but != 32.)
2034 len==32 is a sha256 hash. otherwise: nto1. */
2035 CFIndex length
= CFDataGetLength(data
);
2037 format
= kSecValidInfoFormatSHA256
;
2038 } else if (length
<= 37) {
2039 format
= kSecValidInfoFormatSerial
;
2040 } else if (length
> 0) {
2041 format
= kSecValidInfoFormatNto1
;
2047 static bool _SecRevocationDbUpdateIssuerData(SecRevocationDbConnectionRef dbc
, int64_t groupId
, CFDictionaryRef dict
, CFErrorRef
*error
) {
2048 /* update/delete records in serials or hashes table. */
2049 if (!dict
|| groupId
< 0) {
2050 return false; /* must have something to insert, and a group to associate with it */
2052 __block
bool ok
= (dbc
!= NULL
);
2053 __block CFErrorRef localError
= NULL
;
2054 /* process deletions */
2055 CFArrayRef deleteArray
= (CFArrayRef
)CFDictionaryGetValue(dict
, CFSTR("delete"));
2056 if (isArray(deleteArray
)) {
2057 SecValidInfoFormat format
= kSecValidInfoFormatUnknown
;
2058 CFIndex processed
=0, identifierIX
, identifierCount
= CFArrayGetCount(deleteArray
);
2059 for (identifierIX
=0; identifierIX
<identifierCount
; identifierIX
++) {
2060 CFDataRef identifierData
= (CFDataRef
)CFArrayGetValueAtIndex(deleteArray
, identifierIX
);
2061 if (!identifierData
) { continue; }
2062 if (format
== kSecValidInfoFormatUnknown
) {
2063 format
= _SecRevocationDbGetGroupFormatForData(dbc
, groupId
, identifierData
);
2065 CFStringRef sql
= NULL
;
2066 if (format
== kSecValidInfoFormatSerial
) {
2067 sql
= deleteSerialRecordSQL
;
2068 } else if (format
== kSecValidInfoFormatSHA256
) {
2069 sql
= deleteSha256RecordSQL
;
2071 if (!sql
) { continue; }
2073 ok
= ok
&& SecDbWithSQL(dbc
->dbconn
, sql
, &localError
, ^bool(sqlite3_stmt
*deleteIdentifier
) {
2074 /* (groupid,serial|sha256) */
2075 CFDataRef hexData
= cfToHexData(identifierData
, true);
2076 if (!hexData
) { return false; }
2077 ok
= ok
&& SecDbBindInt64(deleteIdentifier
, 1,
2078 groupId
, &localError
);
2079 ok
= ok
&& SecDbBindBlob(deleteIdentifier
, 2,
2080 CFDataGetBytePtr(hexData
),
2081 CFDataGetLength(hexData
),
2082 SQLITE_TRANSIENT
, &localError
);
2083 /* Execute the delete statement for the identifier record. */
2084 ok
= ok
&& SecDbStep(dbc
->dbconn
, deleteIdentifier
, &localError
, NULL
);
2085 CFReleaseSafe(hexData
);
2088 if (ok
) { ++processed
; }
2091 secdebug("validupdate", "Processed %ld of %ld deletions for group %lld, result=%s",
2092 processed
, identifierCount
, groupId
, (ok
) ? "true" : "false");
2095 /* process additions */
2096 CFArrayRef addArray
= (CFArrayRef
)CFDictionaryGetValue(dict
, CFSTR("add"));
2097 if (isArray(addArray
)) {
2098 SecValidInfoFormat format
= kSecValidInfoFormatUnknown
;
2099 CFIndex processed
=0, identifierIX
, identifierCount
= CFArrayGetCount(addArray
);
2100 for (identifierIX
=0; identifierIX
<identifierCount
; identifierIX
++) {
2101 CFDataRef identifierData
= (CFDataRef
)CFArrayGetValueAtIndex(addArray
, identifierIX
);
2102 if (!identifierData
) { continue; }
2103 if (format
== kSecValidInfoFormatUnknown
) {
2104 format
= _SecRevocationDbGetGroupFormatForData(dbc
, groupId
, identifierData
);
2106 CFStringRef sql
= NULL
;
2107 if (format
== kSecValidInfoFormatSerial
) {
2108 sql
= insertSerialRecordSQL
;
2109 } else if (format
== kSecValidInfoFormatSHA256
) {
2110 sql
= insertSha256RecordSQL
;
2112 if (!sql
) { continue; }
2114 ok
= ok
&& SecDbWithSQL(dbc
->dbconn
, sql
, &localError
, ^bool(sqlite3_stmt
*insertIdentifier
) {
2115 /* rowid,(groupid,serial|sha256) */
2116 /* rowid is autoincremented and we never set it directly */
2117 ok
= ok
&& SecDbBindInt64(insertIdentifier
, 1,
2118 groupId
, &localError
);
2119 ok
= ok
&& SecDbBindBlob(insertIdentifier
, 2,
2120 CFDataGetBytePtr(identifierData
),
2121 CFDataGetLength(identifierData
),
2122 SQLITE_TRANSIENT
, &localError
);
2123 /* Execute the insert statement for the identifier record. */
2124 ok
= ok
&& SecDbStep(dbc
->dbconn
, insertIdentifier
, &localError
, NULL
);
2127 if (ok
) { ++processed
; }
2130 secdebug("validupdate", "Processed %ld of %ld additions for group %lld, result=%s",
2131 processed
, identifierCount
, groupId
, (ok
) ? "true" : "false");
2134 if (!ok
|| localError
) {
2135 secerror("_SecRevocationDbUpdatePerIssuerData failed: %@", localError
);
2136 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationWrite
, TAFatalError
,
2137 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
2139 (void) CFErrorPropagate(localError
, error
);
2143 static bool _SecRevocationDbCopyDateConstraints(SecRevocationDbConnectionRef dbc
,
2144 int64_t groupId
, CFDateRef
*notBeforeDate
, CFDateRef
*notAfterDate
, CFErrorRef
*error
) {
2145 /* return true if one or both date constraints exist for a given groupId.
2146 the actual constraints are optionally returned in output CFDateRef parameters.
2147 caller is responsible for releasing date and error parameters, if provided.
2149 __block
bool ok
= (dbc
!= NULL
);
2150 __block CFDateRef localNotBefore
= NULL
;
2151 __block CFDateRef localNotAfter
= NULL
;
2152 __block CFErrorRef localError
= NULL
;
2154 ok
= ok
&& SecDbWithSQL(dbc
->dbconn
, selectDateRecordSQL
, &localError
, ^bool(sqlite3_stmt
*selectDates
) {
2155 /* (groupid,notbefore,notafter) */
2156 ok
&= SecDbBindInt64(selectDates
, 1, groupId
, &localError
);
2157 ok
= ok
&& SecDbStep(dbc
->dbconn
, selectDates
, &localError
, ^(bool *stop
) {
2158 /* if column has no value, its type will be SQLITE_NULL */
2159 if (SQLITE_NULL
!= sqlite3_column_type(selectDates
, 0)) {
2160 CFAbsoluteTime nb
= (CFAbsoluteTime
)sqlite3_column_double(selectDates
, 0);
2161 localNotBefore
= CFDateCreate(NULL
, nb
);
2163 if (SQLITE_NULL
!= sqlite3_column_type(selectDates
, 1)) {
2164 CFAbsoluteTime na
= (CFAbsoluteTime
)sqlite3_column_double(selectDates
, 1);
2165 localNotAfter
= CFDateCreate(NULL
, na
);
2170 /* must have at least one date constraint to return true.
2171 since date constraints are optional, not finding any should not log an error. */
2172 ok
= ok
&& !localError
&& (localNotBefore
!= NULL
|| localNotAfter
!= NULL
);
2174 secerror("_SecRevocationDbCopyDateConstraints failed: %@", localError
);
2175 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationRead
, TAFatalError
,
2176 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
2179 CFReleaseNull(localNotBefore
);
2180 CFReleaseNull(localNotAfter
);
2182 if (notBeforeDate
) {
2183 *notBeforeDate
= localNotBefore
;
2185 CFReleaseSafe(localNotBefore
);
2188 *notAfterDate
= localNotAfter
;
2190 CFReleaseSafe(localNotAfter
);
2193 (void) CFErrorPropagate(localError
, error
);
2197 static bool _SecRevocationDbUpdateIssuerConstraints(SecRevocationDbConnectionRef dbc
, int64_t groupId
, CFDictionaryRef dict
, CFErrorRef
*error
) {
2198 /* update optional records in dates, names, or policies tables. */
2199 if (!dbc
|| !dict
|| groupId
< 0) {
2200 return false; /* must have something to insert, and a group to associate with it */
2202 __block
bool ok
= true;
2203 __block CFErrorRef localError
= NULL
;
2204 __block CFAbsoluteTime notBefore
= -3155760000.0; /* default: 1901-01-01 00:00:00-0000 */
2205 __block CFAbsoluteTime notAfter
= 31556908800.0; /* default: 3001-01-01 00:00:00-0000 */
2207 CFDateRef notBeforeDate
= (CFDateRef
)CFDictionaryGetValue(dict
, CFSTR("not-before"));
2208 CFDateRef notAfterDate
= (CFDateRef
)CFDictionaryGetValue(dict
, CFSTR("not-after"));
2209 if (isDate(notBeforeDate
)) {
2210 notBefore
= CFDateGetAbsoluteTime(notBeforeDate
);
2212 notBeforeDate
= NULL
;
2214 if (isDate(notAfterDate
)) {
2215 notAfter
= CFDateGetAbsoluteTime(notAfterDate
);
2217 notAfterDate
= NULL
;
2219 if (!(notBeforeDate
|| notAfterDate
)) {
2220 return ok
; /* no dates supplied, so we have nothing to update for this issuer */
2223 if (!(notBeforeDate
&& notAfterDate
) && !dbc
->fullUpdate
) {
2224 /* only one date was supplied, so check for existing date constraints */
2225 CFDateRef curNotBeforeDate
= NULL
;
2226 CFDateRef curNotAfterDate
= NULL
;
2227 if (_SecRevocationDbCopyDateConstraints(dbc
, groupId
, &curNotBeforeDate
,
2228 &curNotAfterDate
, &localError
)) {
2229 if (!notBeforeDate
) {
2230 notBeforeDate
= curNotBeforeDate
;
2231 notBefore
= CFDateGetAbsoluteTime(notBeforeDate
);
2233 CFReleaseSafe(curNotBeforeDate
);
2235 if (!notAfterDate
) {
2236 notAfterDate
= curNotAfterDate
;
2237 notAfter
= CFDateGetAbsoluteTime(notAfterDate
);
2239 CFReleaseSafe(curNotAfterDate
);
2244 ok
= ok
&& SecDbWithSQL(dbc
->dbconn
, insertDateRecordSQL
, &localError
, ^bool(sqlite3_stmt
*insertDate
) {
2245 /* (groupid,notbefore,notafter) */
2246 ok
= ok
&& SecDbBindInt64(insertDate
, 1, groupId
, &localError
);
2247 ok
= ok
&& SecDbBindDouble(insertDate
, 2, notBefore
, &localError
);
2248 ok
= ok
&& SecDbBindDouble(insertDate
, 3, notAfter
, &localError
);
2249 ok
= ok
&& SecDbStep(dbc
->dbconn
, insertDate
, &localError
, NULL
);
2253 /* %%% (TBI:9254570,21234699) update name and policy constraint entries here */
2255 if (!ok
|| localError
) {
2256 secinfo("validupdate", "_SecRevocationDbUpdateIssuerConstraints failed (ok=%s, localError=%@)",
2257 (ok
) ? "1" : "0", localError
);
2258 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationWrite
, TAFatalError
,
2259 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
2262 (void) CFErrorPropagate(localError
, error
);
2266 static SecValidInfoFormat
_SecRevocationDbGetGroupFormat(SecRevocationDbConnectionRef dbc
,
2267 int64_t groupId
, SecValidInfoFlags
*flags
, CFDataRef
*data
, CFErrorRef
*error
) {
2268 /* return group record fields for a given groupId.
2269 on success, returns a non-zero format type, and other field values in optional output parameters.
2270 caller is responsible for releasing data and error parameters, if provided.
2272 __block
bool ok
= (dbc
!= NULL
);
2273 __block SecValidInfoFormat format
= 0;
2274 __block CFErrorRef localError
= NULL
;
2276 /* Select the group record to determine flags and format. */
2277 ok
= ok
&& SecDbWithSQL(dbc
->dbconn
, selectGroupRecordSQL
, &localError
, ^bool(sqlite3_stmt
*selectGroup
) {
2278 ok
= ok
&& SecDbBindInt64(selectGroup
, 1, groupId
, &localError
);
2279 ok
= ok
&& SecDbStep(dbc
->dbconn
, selectGroup
, &localError
, ^(bool *stop
) {
2281 *flags
= (SecValidInfoFlags
)sqlite3_column_int(selectGroup
, 0);
2283 format
= (SecValidInfoFormat
)sqlite3_column_int(selectGroup
, 1);
2285 //%%% stream the data from the db into a streamed decompression <rdar://32142637>
2286 uint8_t *p
= (uint8_t *)sqlite3_column_blob(selectGroup
, 2);
2287 if (p
!= NULL
&& format
== kSecValidInfoFormatNto1
) {
2288 CFIndex length
= (CFIndex
)sqlite3_column_bytes(selectGroup
, 2);
2289 *data
= CFDataCreate(kCFAllocatorDefault
, p
, length
);
2295 if (!ok
|| localError
) {
2296 secdebug("validupdate", "GetGroupFormat for groupId %lu failed", (unsigned long)groupId
);
2297 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationRead
, TAFatalError
,
2298 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
2299 format
= kSecValidInfoFormatUnknown
;
2301 (void) CFErrorPropagate(localError
, error
);
2302 if (!(format
> kSecValidInfoFormatUnknown
)) {
2303 secdebug("validupdate", "GetGroupFormat: got format %d for groupId %lld", format
, (long long)groupId
);
2308 static bool _SecRevocationDbUpdateFlags(CFDictionaryRef dict
, CFStringRef key
, SecValidInfoFlags mask
, SecValidInfoFlags
*flags
) {
2309 /* If a boolean value exists in the given dictionary for the given key,
2310 or an explicit "1" or "0" is specified as the key string,
2311 set or clear the corresponding bit(s) defined by the mask argument.
2312 Function returns true if the flags value was changed, false otherwise.
2314 if (!isDictionary(dict
) || !isString(key
) || !flags
) {
2317 bool hasValue
= false, newValue
= false, result
= false;
2318 CFTypeRef value
= (CFBooleanRef
)CFDictionaryGetValue(dict
, key
);
2319 if (isBoolean(value
)) {
2320 newValue
= CFBooleanGetValue((CFBooleanRef
)value
);
2322 } else if (BOOL_STRING_KEY_LENGTH
== CFStringGetLength(key
)) {
2323 if (CFStringCompare(key
, kBoolTrueKey
, 0) == kCFCompareEqualTo
) {
2324 hasValue
= newValue
= true;
2325 } else if (CFStringCompare(key
, kBoolFalseKey
, 0) == kCFCompareEqualTo
) {
2330 SecValidInfoFlags oldFlags
= *flags
;
2336 result
= (*flags
!= oldFlags
);
2341 static bool _SecRevocationDbUpdateFilter(CFDictionaryRef dict
, CFDataRef oldData
, CFDataRef
* __nonnull CF_RETURNS_RETAINED xmlData
) {
2342 /* If xor and/or params values exist in the given dictionary, create a new
2343 property list containing the updated values, and return as a flattened
2344 data blob in the xmlData output parameter (note: caller must release.)
2345 Function returns true if there is new xmlData to save, false otherwise.
2347 bool result
= false;
2348 bool xorProvided
= false;
2349 bool paramsProvided
= false;
2350 bool missingData
= false;
2352 if (!dict
|| !xmlData
) {
2353 return result
; /* no-op if no dictionary is provided, or no way to update the data */
2356 CFDataRef xorCurrent
= NULL
;
2357 CFDataRef xorUpdate
= (CFDataRef
)CFDictionaryGetValue(dict
, CFSTR("xor"));
2358 if (isData(xorUpdate
)) {
2361 CFArrayRef paramsCurrent
= NULL
;
2362 CFArrayRef paramsUpdate
= (CFArrayRef
)CFDictionaryGetValue(dict
, CFSTR("params"));
2363 if (isArray(paramsUpdate
)) {
2364 paramsProvided
= true;
2366 if (!(xorProvided
|| paramsProvided
)) {
2367 return result
; /* nothing to update, so we can bail out here. */
2370 CFPropertyListRef nto1Current
= NULL
;
2371 CFMutableDictionaryRef nto1Update
= CFDictionaryCreateMutable(kCFAllocatorDefault
, 0,
2372 &kCFTypeDictionaryKeyCallBacks
,
2373 &kCFTypeDictionaryValueCallBacks
);
2378 /* turn old data into property list */
2379 CFDataRef data
= (CFDataRef
)CFRetainSafe(oldData
);
2380 CFDataRef inflatedData
= copyInflatedData(data
);
2382 CFReleaseSafe(data
);
2383 data
= inflatedData
;
2386 nto1Current
= CFPropertyListCreateWithData(kCFAllocatorDefault
, data
, 0, NULL
, NULL
);
2387 CFReleaseSafe(data
);
2390 xorCurrent
= (CFDataRef
)CFDictionaryGetValue((CFDictionaryRef
)nto1Current
, CFSTR("xor"));
2391 paramsCurrent
= (CFArrayRef
)CFDictionaryGetValue((CFDictionaryRef
)nto1Current
, CFSTR("params"));
2394 /* set current or updated xor data in new property list */
2396 CFDataRef xorNew
= NULL
;
2398 CFIndex xorUpdateLen
= CFDataGetLength(xorUpdate
);
2399 CFMutableDataRef
xor = CFDataCreateMutableCopy(NULL
, 0, xorCurrent
);
2400 if (xor && xorUpdateLen
> 0) {
2401 /* truncate or zero-extend data to match update size */
2402 CFDataSetLength(xor, xorUpdateLen
);
2403 /* exclusive-or update bytes over the existing data */
2404 UInt8
*xorP
= (UInt8
*)CFDataGetMutableBytePtr(xor);
2405 UInt8
*updP
= (UInt8
*)CFDataGetBytePtr(xorUpdate
);
2407 for (int idx
= 0; idx
< xorUpdateLen
; idx
++) {
2408 xorP
[idx
] = xorP
[idx
] ^ updP
[idx
];
2412 xorNew
= (CFDataRef
)xor;
2414 xorNew
= (CFDataRef
)CFRetainSafe(xorUpdate
);
2417 CFDictionaryAddValue(nto1Update
, CFSTR("xor"), xorNew
);
2418 CFReleaseSafe(xorNew
);
2420 secdebug("validupdate", "Failed to get updated filter data");
2423 } else if (xorCurrent
) {
2424 /* not provided, so use existing xor value */
2425 CFDictionaryAddValue(nto1Update
, CFSTR("xor"), xorCurrent
);
2427 secdebug("validupdate", "Failed to get current filter data");
2431 /* set current or updated params in new property list */
2432 if (paramsProvided
) {
2433 CFDictionaryAddValue(nto1Update
, CFSTR("params"), paramsUpdate
);
2434 } else if (paramsCurrent
) {
2435 /* not provided, so use existing params value */
2436 CFDictionaryAddValue(nto1Update
, CFSTR("params"), paramsCurrent
);
2438 /* missing params: neither provided nor existing */
2439 secdebug("validupdate", "Failed to get current filter params");
2443 CFReleaseSafe(nto1Current
);
2445 *xmlData
= CFPropertyListCreateData(kCFAllocatorDefault
, nto1Update
,
2446 kCFPropertyListXMLFormat_v1_0
,
2448 result
= (*xmlData
!= NULL
);
2450 CFReleaseSafe(nto1Update
);
2452 /* compress the xmlData blob, if possible */
2454 CFDataRef deflatedData
= copyDeflatedData(*xmlData
);
2456 if (CFDataGetLength(deflatedData
) < CFDataGetLength(*xmlData
)) {
2457 CFRelease(*xmlData
);
2458 *xmlData
= deflatedData
;
2460 CFRelease(deflatedData
);
2468 static int64_t _SecRevocationDbUpdateGroup(SecRevocationDbConnectionRef dbc
, int64_t groupId
, CFDictionaryRef dict
, CFErrorRef
*error
) {
2469 /* insert group record for a given groupId.
2470 if the specified groupId is < 0, a new group entry is created.
2471 returns the groupId on success, or -1 on failure.
2474 return groupId
; /* no-op if no dictionary is provided */
2477 __block
int64_t result
= -1;
2478 __block
bool ok
= (dbc
!= NULL
);
2479 __block
bool isFormatChange
= false;
2480 __block CFErrorRef localError
= NULL
;
2482 __block SecValidInfoFlags flags
= 0;
2483 __block SecValidInfoFormat format
= kSecValidInfoFormatUnknown
;
2484 __block SecValidInfoFormat formatUpdate
= kSecValidInfoFormatUnknown
;
2485 __block CFDataRef data
= NULL
;
2488 /* fetch the flags and data for an existing group record, in case some are being changed. */
2490 format
= _SecRevocationDbGetGroupFormat(dbc
, groupId
, &flags
, &data
, NULL
);
2492 if (format
== kSecValidInfoFormatUnknown
) {
2493 secdebug("validupdate", "existing group %lld has unknown format %d, flags=0x%lx",
2494 (long long)groupId
, format
, flags
);
2495 //%%% clean up by deleting all issuers with this groupId, then the group record,
2496 // or just force a full update? note: we can get here if we fail to bind the
2497 // format value in the prepared SQL statement below.
2501 CFTypeRef value
= (CFStringRef
)CFDictionaryGetValue(dict
, CFSTR("format"));
2502 if (isString(value
)) {
2503 if (CFStringCompare((CFStringRef
)value
, CFSTR("serial"), 0) == kCFCompareEqualTo
) {
2504 formatUpdate
= kSecValidInfoFormatSerial
;
2505 } else if (CFStringCompare((CFStringRef
)value
, CFSTR("sha256"), 0) == kCFCompareEqualTo
) {
2506 formatUpdate
= kSecValidInfoFormatSHA256
;
2507 } else if (CFStringCompare((CFStringRef
)value
, CFSTR("nto1"), 0) == kCFCompareEqualTo
) {
2508 formatUpdate
= kSecValidInfoFormatNto1
;
2511 /* if format value is explicitly supplied, then this is effectively a new group entry. */
2512 isFormatChange
= (formatUpdate
> kSecValidInfoFormatUnknown
&&
2513 formatUpdate
!= format
&&
2516 if (isFormatChange
) {
2517 secdebug("validupdate", "group %lld format change from %d to %d",
2518 (long long)groupId
, format
, formatUpdate
);
2519 /* format of an existing group is changing; delete the group first.
2520 this should ensure that all entries referencing the old groupid are deleted.
2522 ok
= ok
&& SecDbWithSQL(dbc
->dbconn
, deleteGroupRecordSQL
, &localError
, ^bool(sqlite3_stmt
*deleteResponse
) {
2523 ok
= ok
&& SecDbBindInt64(deleteResponse
, 1, groupId
, &localError
);
2524 /* Execute the delete statement. */
2525 ok
= ok
&& SecDbStep(dbc
->dbconn
, deleteResponse
, &localError
, NULL
);
2529 ok
= ok
&& SecDbWithSQL(dbc
->dbconn
, insertGroupRecordSQL
, &localError
, ^bool(sqlite3_stmt
*insertGroup
) {
2530 /* (groupid,flags,format,data) */
2531 /* groups.groupid */
2532 if (ok
&& (!isFormatChange
) && (groupId
>= 0)) {
2533 /* bind to existing groupId row if known, otherwise will insert and autoincrement */
2534 ok
= SecDbBindInt64(insertGroup
, 1, groupId
, &localError
);
2536 secdebug("validupdate", "failed to set groupId %lld", (long long)groupId
);
2541 (void)_SecRevocationDbUpdateFlags(dict
, CFSTR("complete"), kSecValidInfoComplete
, &flags
);
2542 (void)_SecRevocationDbUpdateFlags(dict
, CFSTR("check-ocsp"), kSecValidInfoCheckOCSP
, &flags
);
2543 (void)_SecRevocationDbUpdateFlags(dict
, CFSTR("known-intermediates-only"), kSecValidInfoKnownOnly
, &flags
);
2544 (void)_SecRevocationDbUpdateFlags(dict
, CFSTR("require-ct"), kSecValidInfoRequireCT
, &flags
);
2545 (void)_SecRevocationDbUpdateFlags(dict
, CFSTR("valid"), kSecValidInfoAllowlist
, &flags
);
2546 (void)_SecRevocationDbUpdateFlags(dict
, CFSTR("no-ca"), kSecValidInfoNoCACheck
, &flags
);
2547 (void)_SecRevocationDbUpdateFlags(dict
, CFSTR("no-ca-v2"), kSecValidInfoNoCAv2Check
, &flags
);
2548 (void)_SecRevocationDbUpdateFlags(dict
, CFSTR("overridable"), kSecValidInfoOverridable
, &flags
);
2550 /* date constraints exist if either "not-before" or "not-after" keys are found */
2551 CFTypeRef notBeforeValue
= (CFDateRef
)CFDictionaryGetValue(dict
, CFSTR("not-before"));
2552 CFTypeRef notAfterValue
= (CFDateRef
)CFDictionaryGetValue(dict
, CFSTR("not-after"));
2553 if (isDate(notBeforeValue
) || isDate(notAfterValue
)) {
2554 (void)_SecRevocationDbUpdateFlags(dict
, kBoolTrueKey
, kSecValidInfoDateConstraints
, &flags
);
2555 /* Note that the spec defines not-before and not-after dates as optional, such that
2556 not providing one does not change the database contents. Therefore, we can never clear
2557 this flag; either a new date entry will be supplied, or a format change will cause
2558 the entire group entry to be deleted. */
2561 /* %%% (TBI:9254570,21234699) name and policy constraints don't exist yet */
2562 (void)_SecRevocationDbUpdateFlags(dict
, kBoolFalseKey
, kSecValidInfoNameConstraints
, &flags
);
2563 (void)_SecRevocationDbUpdateFlags(dict
, kBoolFalseKey
, kSecValidInfoPolicyConstraints
, &flags
);
2565 ok
= SecDbBindInt(insertGroup
, 2, (int)flags
, &localError
);
2567 secdebug("validupdate", "failed to set flags (%lu) for groupId %lld", flags
, (long long)groupId
);
2572 SecValidInfoFormat formatValue
= format
;
2573 if (formatUpdate
> kSecValidInfoFormatUnknown
) {
2574 formatValue
= formatUpdate
;
2576 ok
= SecDbBindInt(insertGroup
, 3, (int)formatValue
, &localError
);
2578 secdebug("validupdate", "failed to set format (%d) for groupId %lld", formatValue
, (long long)groupId
);
2582 CFDataRef xmlData
= NULL
;
2584 bool hasFilter
= ((formatUpdate
== kSecValidInfoFormatNto1
) ||
2585 (formatUpdate
== kSecValidInfoFormatUnknown
&&
2586 format
== kSecValidInfoFormatNto1
));
2588 CFDataRef dataValue
= data
; /* use existing data */
2589 if (_SecRevocationDbUpdateFilter(dict
, data
, &xmlData
)) {
2590 dataValue
= xmlData
; /* use updated data */
2593 ok
= SecDbBindBlob(insertGroup
, 4,
2594 CFDataGetBytePtr(dataValue
),
2595 CFDataGetLength(dataValue
),
2596 SQLITE_TRANSIENT
, &localError
);
2599 secdebug("validupdate", "failed to set data for groupId %lld",
2600 (long long)groupId
);
2603 /* else there is no data, so NULL is implicitly bound to column 4 */
2606 /* Execute the insert statement for the group record. */
2608 ok
= SecDbStep(dbc
->dbconn
, insertGroup
, &localError
, NULL
);
2610 secdebug("validupdate", "failed to execute insertGroup statement for groupId %lld",
2611 (long long)groupId
);
2613 result
= (int64_t)sqlite3_last_insert_rowid(SecDbHandle(dbc
->dbconn
));
2616 secdebug("validupdate", "failed to insert group %lld", (long long)result
);
2618 /* Clean up temporary allocation made in this block. */
2619 CFReleaseSafe(xmlData
);
2620 CFReleaseSafe(data
);
2623 if (!ok
|| localError
) {
2624 secerror("_SecRevocationDbUpdateGroup failed: %@", localError
);
2625 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationWrite
, TAFatalError
,
2626 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
2628 (void) CFErrorPropagate(localError
, error
);
2632 static int64_t _SecRevocationDbGroupIdForIssuerHash(SecRevocationDbConnectionRef dbc
, CFDataRef hash
, CFErrorRef
*error
) {
2633 /* look up issuer hash in issuers table to get groupid, if it exists */
2634 __block
int64_t groupId
= -1;
2635 __block
bool ok
= (dbc
!= NULL
);
2636 __block CFErrorRef localError
= NULL
;
2639 secdebug("validupdate", "failed to get hash (%@)", hash
);
2641 require(hash
&& dbc
, errOut
);
2643 /* This is the starting point for any lookup; find a group id for the given issuer hash.
2644 Before we do that, need to verify the current db_version. We cannot use results from a
2645 database created with a schema version older than the minimum supported version.
2646 However, we may be able to use results from a newer version. At the next database
2647 update interval, if the existing schema is old, we'll be removing and recreating
2648 the database contents with the current schema version.
2650 int64_t db_version
= _SecRevocationDbGetSchemaVersion(dbc
->db
, dbc
, NULL
);
2651 if (db_version
< kSecRevocationDbMinSchemaVersion
) {
2652 if (!dbc
->db
->unsupportedVersion
) {
2653 secdebug("validupdate", "unsupported db_version: %lld", (long long)db_version
);
2654 dbc
->db
->unsupportedVersion
= true; /* only warn once for a given unsupported version */
2657 require_quiet(db_version
>= kSecRevocationDbMinSchemaVersion
, errOut
);
2659 /* Look up provided issuer_hash in the issuers table.
2661 ok
= ok
&& SecDbWithSQL(dbc
->dbconn
, selectGroupIdSQL
, &localError
, ^bool(sqlite3_stmt
*selectGroupId
) {
2662 ok
&= SecDbBindBlob(selectGroupId
, 1, CFDataGetBytePtr(hash
), CFDataGetLength(hash
), SQLITE_TRANSIENT
, &localError
);
2663 ok
&= SecDbStep(dbc
->dbconn
, selectGroupId
, &localError
, ^(bool *stopGroupId
) {
2664 groupId
= sqlite3_column_int64(selectGroupId
, 0);
2670 if (!ok
|| localError
) {
2671 secerror("_SecRevocationDbGroupIdForIssuerHash failed: %@", localError
);
2672 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationRead
, TAFatalError
,
2673 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
2675 (void) CFErrorPropagate(localError
, error
);
2679 static bool _SecRevocationDbApplyGroupDelete(SecRevocationDbConnectionRef dbc
, CFDataRef issuerHash
, CFErrorRef
*error
) {
2680 /* delete group associated with the given issuer;
2681 schema trigger will delete associated issuers, serials, and hashes. */
2682 __block
int64_t groupId
= -1;
2683 __block
bool ok
= (dbc
!= NULL
);
2684 __block CFErrorRef localError
= NULL
;
2687 groupId
= _SecRevocationDbGroupIdForIssuerHash(dbc
, issuerHash
, &localError
);
2691 SecError(errSecParam
, &localError
, CFSTR("group not found for issuer"));
2695 ok
= ok
&& SecDbWithSQL(dbc
->dbconn
, deleteGroupRecordSQL
, &localError
, ^bool(sqlite3_stmt
*deleteResponse
) {
2696 ok
&= SecDbBindInt64(deleteResponse
, 1, groupId
, &localError
);
2697 /* Execute the delete statement. */
2698 ok
= ok
&& SecDbStep(dbc
->dbconn
, deleteResponse
, &localError
, NULL
);
2701 if (!ok
|| localError
) {
2702 secerror("_SecRevocationDbApplyGroupDelete failed: %@", localError
);
2703 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationWrite
, TAFatalError
,
2704 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
2706 (void) CFErrorPropagate(localError
, error
);
2710 static bool _SecRevocationDbApplyGroupUpdate(SecRevocationDbConnectionRef dbc
, CFDictionaryRef dict
, CFErrorRef
*error
) {
2711 /* process one issuer group's update dictionary */
2712 __block
int64_t groupId
= -1;
2713 __block
bool ok
= (dbc
!= NULL
);
2714 __block CFErrorRef localError
= NULL
;
2716 CFArrayRef issuers
= (dict
) ? (CFArrayRef
)CFDictionaryGetValue(dict
, CFSTR("issuer-hash")) : NULL
;
2717 /* if this is not a full update, then look for existing group id */
2718 if (ok
&& isArray(issuers
) && !dbc
->fullUpdate
) {
2719 CFIndex issuerIX
, issuerCount
= CFArrayGetCount(issuers
);
2720 /* while we have issuers and haven't found a matching group id */
2721 for (issuerIX
=0; issuerIX
<issuerCount
&& groupId
< 0; issuerIX
++) {
2722 CFDataRef hash
= (CFDataRef
)CFArrayGetValueAtIndex(issuers
, issuerIX
);
2723 if (!hash
) { continue; }
2724 groupId
= _SecRevocationDbGroupIdForIssuerHash(dbc
, hash
, &localError
);
2727 /* according to the spec, we must replace all existing issuers with
2728 the new issuers list, so delete all issuers in the group first. */
2729 ok
= ok
&& SecDbWithSQL(dbc
->dbconn
, deleteGroupIssuersSQL
, &localError
, ^bool(sqlite3_stmt
*deleteIssuers
) {
2730 ok
= ok
&& SecDbBindInt64(deleteIssuers
, 1, groupId
, &localError
);
2731 ok
= ok
&& SecDbStep(dbc
->dbconn
, deleteIssuers
, &localError
, NULL
);
2736 /* create or update the group entry */
2738 groupId
= _SecRevocationDbUpdateGroup(dbc
, groupId
, dict
, &localError
);
2741 secdebug("validupdate", "failed to get groupId");
2744 /* create or update issuer entries, now that we know the group id */
2745 ok
= ok
&& _SecRevocationDbUpdateIssuers(dbc
, groupId
, issuers
, &localError
);
2746 /* create or update entries in serials or hashes tables */
2747 ok
= ok
&& _SecRevocationDbUpdateIssuerData(dbc
, groupId
, dict
, &localError
);
2748 /* create or update entries in dates/names/policies tables */
2749 ok
= ok
&& _SecRevocationDbUpdateIssuerConstraints(dbc
, groupId
, dict
, &localError
);
2752 (void) CFErrorPropagate(localError
, error
);
2756 bool _SecRevocationDbApplyUpdate(SecRevocationDbConnectionRef dbc
, CFDictionaryRef update
, CFIndex version
, CFErrorRef
*error
) {
2757 /* process entire update dictionary */
2758 if (!dbc
|| !dbc
->db
|| !update
) {
2759 secerror("_SecRevocationDbApplyUpdate failed: invalid args");
2760 SecError(errSecParam
, error
, CFSTR("_SecRevocationDbApplyUpdate: invalid db or update parameter"));
2764 CFDictionaryRef localUpdate
= (CFDictionaryRef
)CFRetainSafe(update
);
2765 CFErrorRef localError
= NULL
;
2768 CFTypeRef value
= NULL
;
2769 CFIndex deleteCount
= 0;
2770 CFIndex updateCount
= 0;
2772 dbc
->db
->updateInProgress
= true;
2774 /* check whether this is a full update */
2775 value
= (CFBooleanRef
)CFDictionaryGetValue(update
, CFSTR("full"));
2776 if (isBoolean(value
) && CFBooleanGetValue((CFBooleanRef
)value
)) {
2777 /* clear the database before processing a full update */
2778 dbc
->fullUpdate
= true;
2779 secdebug("validupdate", "update has \"full\" attribute; clearing database");
2780 ok
= ok
&& _SecRevocationDbRemoveAllEntries(dbc
, &localError
);
2783 /* process 'delete' list */
2784 value
= (CFArrayRef
)CFDictionaryGetValue(localUpdate
, CFSTR("delete"));
2785 if (isArray(value
)) {
2786 deleteCount
= CFArrayGetCount((CFArrayRef
)value
);
2787 secdebug("validupdate", "processing %ld deletes", (long)deleteCount
);
2788 for (CFIndex deleteIX
=0; deleteIX
<deleteCount
; deleteIX
++) {
2789 CFDataRef issuerHash
= (CFDataRef
)CFArrayGetValueAtIndex((CFArrayRef
)value
, deleteIX
);
2790 if (isData(issuerHash
)) {
2791 ok
= ok
&& _SecRevocationDbApplyGroupDelete(dbc
, issuerHash
, &localError
);
2793 secdebug("validupdate", "skipping delete %ld (hash is not a data value)", (long)deleteIX
);
2798 /* process 'update' list */
2799 value
= (CFArrayRef
)CFDictionaryGetValue(localUpdate
, CFSTR("update"));
2800 if (isArray(value
)) {
2801 updateCount
= CFArrayGetCount((CFArrayRef
)value
);
2802 secdebug("validupdate", "processing %ld updates", (long)updateCount
);
2803 for (CFIndex updateIX
=0; updateIX
<updateCount
; updateIX
++) {
2804 CFDictionaryRef dict
= (CFDictionaryRef
)CFArrayGetValueAtIndex((CFArrayRef
)value
, updateIX
);
2805 if (isDictionary(dict
)) {
2806 ok
= ok
&& _SecRevocationDbApplyGroupUpdate(dbc
, dict
, &localError
);
2808 secdebug("validupdate", "skipping update %ld (not a dictionary)", (long)updateIX
);
2812 CFReleaseSafe(localUpdate
);
2815 ok
= ok
&& _SecRevocationDbSetVersion(dbc
, version
, &localError
);
2817 /* set db_version if not already set */
2818 int64_t db_version
= _SecRevocationDbGetSchemaVersion(dbc
->db
, dbc
, NULL
);
2819 if (db_version
<= 0) {
2820 ok
= ok
&& _SecRevocationDbSetSchemaVersion(dbc
, kSecRevocationDbSchemaVersion
, &localError
);
2823 /* set db_format if not already set */
2824 int64_t db_format
= _SecRevocationDbGetUpdateFormat(dbc
, NULL
);
2825 if (db_format
<= 0) {
2826 ok
= ok
&& _SecRevocationDbSetUpdateFormat(dbc
, kSecRevocationDbUpdateFormat
, &localError
);
2829 /* purge the in-memory cache */
2830 SecRevocationDbCachePurge(dbc
->db
);
2832 dbc
->db
->updateInProgress
= false;
2834 (void) CFErrorPropagate(localError
, error
);
2838 static bool _SecRevocationDbSerialInGroup(SecRevocationDbConnectionRef dbc
,
2841 CFErrorRef
*error
) {
2842 __block
bool result
= false;
2843 __block
bool ok
= true;
2844 __block CFErrorRef localError
= NULL
;
2845 require(dbc
&& serial
, errOut
);
2846 ok
&= SecDbWithSQL(dbc
->dbconn
, selectSerialRecordSQL
, &localError
, ^bool(sqlite3_stmt
*selectSerial
) {
2847 ok
&= SecDbBindInt64(selectSerial
, 1, groupId
, &localError
);
2848 ok
&= SecDbBindBlob(selectSerial
, 2, CFDataGetBytePtr(serial
),
2849 CFDataGetLength(serial
), SQLITE_TRANSIENT
, &localError
);
2850 ok
&= SecDbStep(dbc
->dbconn
, selectSerial
, &localError
, ^(bool *stop
) {
2851 int64_t foundRowId
= (int64_t)sqlite3_column_int64(selectSerial
, 0);
2852 result
= (foundRowId
> 0);
2858 if (!ok
|| localError
) {
2859 secerror("_SecRevocationDbSerialInGroup failed: %@", localError
);
2860 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationRead
, TAFatalError
,
2861 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
2863 (void) CFErrorPropagate(localError
, error
);
2867 static bool _SecRevocationDbCertHashInGroup(SecRevocationDbConnectionRef dbc
,
2870 CFErrorRef
*error
) {
2871 __block
bool result
= false;
2872 __block
bool ok
= true;
2873 __block CFErrorRef localError
= NULL
;
2874 require(dbc
&& certHash
, errOut
);
2875 ok
&= SecDbWithSQL(dbc
->dbconn
, selectHashRecordSQL
, &localError
, ^bool(sqlite3_stmt
*selectHash
) {
2876 ok
&= SecDbBindInt64(selectHash
, 1, groupId
, &localError
);
2877 ok
= SecDbBindBlob(selectHash
, 2, CFDataGetBytePtr(certHash
),
2878 CFDataGetLength(certHash
), SQLITE_TRANSIENT
, &localError
);
2879 ok
&= SecDbStep(dbc
->dbconn
, selectHash
, &localError
, ^(bool *stop
) {
2880 int64_t foundRowId
= (int64_t)sqlite3_column_int64(selectHash
, 0);
2881 result
= (foundRowId
> 0);
2887 if (!ok
|| localError
) {
2888 secerror("_SecRevocationDbCertHashInGroup failed: %@", localError
);
2889 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationRead
, TAFatalError
,
2890 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
2892 (void) CFErrorPropagate(localError
, error
);
2896 static bool _SecRevocationDbSerialInFilter(SecRevocationDbConnectionRef dbc
,
2897 CFDataRef serialData
,
2898 CFDataRef xmlData
) {
2899 /* N-To-1 filter implementation.
2900 The 'xmlData' parameter is a flattened XML dictionary,
2901 containing 'xor' and 'params' keys. First order of
2902 business is to reconstitute the blob into components.
2904 bool result
= false;
2905 CFRetainSafe(xmlData
);
2906 CFDataRef propListData
= xmlData
;
2907 /* Expand data blob if needed */
2908 CFDataRef inflatedData
= copyInflatedData(propListData
);
2910 CFReleaseSafe(propListData
);
2911 propListData
= inflatedData
;
2913 CFDataRef
xor = NULL
;
2914 CFArrayRef params
= NULL
;
2915 CFPropertyListRef nto1
= CFPropertyListCreateWithData(kCFAllocatorDefault
, propListData
, 0, NULL
, NULL
);
2917 xor = (CFDataRef
)CFDictionaryGetValue((CFDictionaryRef
)nto1
, CFSTR("xor"));
2918 params
= (CFArrayRef
)CFDictionaryGetValue((CFDictionaryRef
)nto1
, CFSTR("params"));
2920 uint8_t *hash
= (xor) ? (uint8_t*)CFDataGetBytePtr(xor) : NULL
;
2921 CFIndex hashLen
= (hash
) ? CFDataGetLength(xor) : 0;
2922 uint8_t *serial
= (serialData
) ? (uint8_t*)CFDataGetBytePtr(serialData
) : NULL
;
2923 CFIndex serialLen
= (serial
) ? CFDataGetLength(serialData
) : 0;
2925 require(hash
&& serial
&& params
, errOut
);
2927 const uint32_t FNV_OFFSET_BASIS
= 2166136261;
2928 const uint32_t FNV_PRIME
= 16777619;
2929 bool notInHash
= false;
2930 CFIndex ix
, count
= CFArrayGetCount(params
);
2931 for (ix
= 0; ix
< count
; ix
++) {
2933 CFNumberRef cfnum
= (CFNumberRef
)CFArrayGetValueAtIndex(params
, ix
);
2934 if (!isNumber(cfnum
) ||
2935 !CFNumberGetValue(cfnum
, kCFNumberSInt32Type
, ¶m
)) {
2936 secinfo("validupdate", "error processing filter params at index %ld", (long)ix
);
2939 /* process one param */
2940 uint32_t hval
= FNV_OFFSET_BASIS
^ param
;
2941 CFIndex i
= serialLen
;
2943 hval
= ((hval
^ (serial
[--i
])) * FNV_PRIME
) & 0xFFFFFFFF;
2945 hval
= hval
% (hashLen
* 8);
2946 if ((hash
[hval
/8] & (1 << (hval
% 8))) == 0) {
2947 notInHash
= true; /* definitely not in hash */
2952 /* probabilistically might be in hash if we get here. */
2957 CFReleaseSafe(nto1
);
2958 CFReleaseSafe(propListData
);
2962 static SecValidInfoRef
_SecRevocationDbValidInfoForCertificate(SecRevocationDbConnectionRef dbc
,
2963 SecCertificateRef certificate
,
2964 CFDataRef issuerHash
,
2965 CFErrorRef
*error
) {
2966 __block CFErrorRef localError
= NULL
;
2967 __block SecValidInfoFlags flags
= 0;
2968 __block SecValidInfoFormat format
= kSecValidInfoFormatUnknown
;
2969 __block CFDataRef data
= NULL
;
2971 bool matched
= false;
2972 bool isOnList
= false;
2973 int64_t groupId
= 0;
2974 CFDataRef serial
= NULL
;
2975 CFDataRef certHash
= NULL
;
2976 CFDateRef notBeforeDate
= NULL
;
2977 CFDateRef notAfterDate
= NULL
;
2978 CFDataRef nameConstraints
= NULL
;
2979 CFDataRef policyConstraints
= NULL
;
2980 SecValidInfoRef result
= NULL
;
2982 require((serial
= SecCertificateCopySerialNumberData(certificate
, NULL
)) != NULL
, errOut
);
2983 require((certHash
= SecCertificateCopySHA256Digest(certificate
)) != NULL
, errOut
);
2984 require((groupId
= _SecRevocationDbGroupIdForIssuerHash(dbc
, issuerHash
, &localError
)) > 0, errOut
);
2986 /* Look up the group record to determine flags and format. */
2987 format
= _SecRevocationDbGetGroupFormat(dbc
, groupId
, &flags
, &data
, &localError
);
2989 if (format
== kSecValidInfoFormatUnknown
) {
2990 /* No group record found for this issuer. Don't return a SecValidInfoRef */
2993 else if (format
== kSecValidInfoFormatSerial
) {
2994 /* Look up certificate's serial number in the serials table. */
2995 matched
= _SecRevocationDbSerialInGroup(dbc
, serial
, groupId
, &localError
);
2997 else if (format
== kSecValidInfoFormatSHA256
) {
2998 /* Look up certificate's SHA-256 hash in the hashes table. */
2999 matched
= _SecRevocationDbCertHashInGroup(dbc
, certHash
, groupId
, &localError
);
3001 else if (format
== kSecValidInfoFormatNto1
) {
3002 /* Perform a Bloom filter match against the serial. If matched is false,
3003 then the cert is definitely not in the list. But if matched is true,
3004 we don't know for certain, so we would need to check OCSP. */
3005 matched
= _SecRevocationDbSerialInFilter(dbc
, serial
, data
);
3009 /* Found a specific match for this certificate. */
3010 secdebug("validupdate", "Valid db matched certificate: %@, format=%d, flags=0x%lx",
3011 certHash
, format
, flags
);
3015 /* If supplemental constraints are present for this issuer, then we always match. */
3016 if ((flags
& kSecValidInfoDateConstraints
) &&
3017 (_SecRevocationDbCopyDateConstraints(dbc
, groupId
, ¬BeforeDate
, ¬AfterDate
, &localError
))) {
3018 secdebug("validupdate", "Valid db matched supplemental date constraints for groupId %lld: nb=%@, na=%@",
3019 (long long)groupId
, notBeforeDate
, notAfterDate
);
3023 /* Return SecValidInfo for certificates for which an issuer entry is found. */
3024 result
= SecValidInfoCreate(format
, flags
, isOnList
,
3025 certHash
, issuerHash
, /*anchorHash*/ NULL
,
3026 notBeforeDate
, notAfterDate
,
3027 nameConstraints
, policyConstraints
);
3029 if (result
&& SecIsAppleTrustAnchor(certificate
, 0)) {
3030 /* Prevent a catch-22. */
3031 secdebug("validupdate", "Valid db match for Apple trust anchor: %@, format=%d, flags=0x%lx",
3032 certHash
, format
, flags
);
3033 CFReleaseNull(result
);
3037 (void) CFErrorPropagate(localError
, error
);
3038 CFReleaseSafe(data
);
3039 CFReleaseSafe(certHash
);
3040 CFReleaseSafe(serial
);
3041 CFReleaseSafe(notBeforeDate
);
3042 CFReleaseSafe(notAfterDate
);
3043 CFReleaseSafe(nameConstraints
);
3044 CFReleaseSafe(policyConstraints
);
3048 static SecValidInfoRef
_SecRevocationDbCopyMatching(SecRevocationDbConnectionRef dbc
,
3049 SecCertificateRef certificate
,
3050 SecCertificateRef issuer
) {
3051 SecValidInfoRef result
= NULL
;
3052 CFErrorRef error
= NULL
;
3053 CFDataRef issuerHash
= NULL
;
3055 require(dbc
&& certificate
&& issuer
, errOut
);
3056 require(issuerHash
= SecCertificateCopySHA256Digest(issuer
), errOut
);
3058 /* Check for the result in the cache. */
3059 result
= SecRevocationDbCacheRead(dbc
->db
, certificate
, issuerHash
);
3061 /* Upon cache miss, get the result from the database and add it to the cache. */
3063 result
= _SecRevocationDbValidInfoForCertificate(dbc
, certificate
, issuerHash
, &error
);
3064 SecRevocationDbCacheWrite(dbc
->db
, result
);
3068 CFReleaseSafe(issuerHash
);
3069 CFReleaseSafe(error
);
3073 /* Return the update source as a retained CFStringRef.
3074 If the value cannot be obtained, NULL is returned.
3076 CFStringRef
SecRevocationDbCopyUpdateSource(void) {
3077 __block CFStringRef result
= NULL
;
3078 SecRevocationDbWith(^(SecRevocationDbRef db
) {
3079 (void) SecRevocationDbPerformRead(db
, NULL
, ^bool(SecRevocationDbConnectionRef dbc
, CFErrorRef
*blockError
) {
3080 result
= _SecRevocationDbCopyUpdateSource(dbc
, blockError
);
3081 return (bool)result
;
3087 /* Set the next update value for the revocation database.
3088 (This function is expected to be called only by the database
3089 maintainer, normally the system instance of trustd. If the
3090 caller does not have write access, this is a no-op.)
3092 bool SecRevocationDbSetNextUpdateTime(CFAbsoluteTime nextUpdate
, CFErrorRef
*error
) {
3093 __block
bool ok
= true;
3094 __block CFErrorRef localError
= NULL
;
3095 SecRevocationDbWith(^(SecRevocationDbRef rdb
) {
3096 ok
&= SecRevocationDbPerformWrite(rdb
, &localError
, ^bool(SecRevocationDbConnectionRef dbc
, CFErrorRef
*blockError
) {
3097 return _SecRevocationDbSetNextUpdateTime(dbc
, nextUpdate
, blockError
);
3100 (void) CFErrorPropagate(localError
, error
);
3104 /* Return the next update value as a CFAbsoluteTime.
3105 If the value cannot be obtained, -1 is returned.
3107 CFAbsoluteTime
SecRevocationDbGetNextUpdateTime(void) {
3108 __block CFAbsoluteTime result
= -1;
3109 SecRevocationDbWith(^(SecRevocationDbRef db
) {
3110 (void) SecRevocationDbPerformRead(db
, NULL
, ^bool(SecRevocationDbConnectionRef dbc
, CFErrorRef
*blockError
) {
3111 result
= _SecRevocationDbGetNextUpdateTime(dbc
, blockError
);
3118 /* Return the serial background queue for database updates.
3119 If the queue cannot be obtained, NULL is returned.
3121 dispatch_queue_t
SecRevocationDbGetUpdateQueue(void) {
3122 __block dispatch_queue_t result
= NULL
;
3123 SecRevocationDbWith(^(SecRevocationDbRef db
) {
3124 result
= (db
) ? db
->update_queue
: NULL
;
3129 /* Release all connections to the revocation database.
3131 void SecRevocationDbReleaseAllConnections(void) {
3132 SecRevocationDbWith(^(SecRevocationDbRef db
) {
3133 SecDbReleaseAllConnections((db
) ? db
->db
: NULL
);
3137 /* === SecRevocationDb API === */
3139 /* Given a certificate and its issuer, returns a SecValidInfoRef if the
3140 valid database contains matching info; otherwise returns NULL.
3141 Caller must release the returned SecValidInfoRef when finished.
3143 SecValidInfoRef
SecRevocationDbCopyMatching(SecCertificateRef certificate
,
3144 SecCertificateRef issuer
) {
3145 __block SecValidInfoRef result
= NULL
;
3146 SecRevocationDbWith(^(SecRevocationDbRef db
) {
3147 (void) SecRevocationDbPerformRead(db
, NULL
, ^bool(SecRevocationDbConnectionRef dbc
, CFErrorRef
*blockError
) {
3148 result
= _SecRevocationDbCopyMatching(dbc
, certificate
, issuer
);
3149 return (bool)result
;
3155 /* Given an issuer, returns true if an entry for this issuer exists in
3156 the database (i.e. a known CA). If the provided certificate is NULL,
3157 or its entry is not found, the function returns false.
3159 bool SecRevocationDbContainsIssuer(SecCertificateRef issuer
) {
3163 __block
bool result
= false;
3164 SecRevocationDbWith(^(SecRevocationDbRef db
) {
3165 (void) SecRevocationDbPerformRead(db
, NULL
, ^bool(SecRevocationDbConnectionRef dbc
, CFErrorRef
*blockError
) {
3166 CFDataRef issuerHash
= SecCertificateCopySHA256Digest(issuer
);
3167 int64_t groupId
= _SecRevocationDbGroupIdForIssuerHash(dbc
, issuerHash
, blockError
);
3168 CFReleaseSafe(issuerHash
);
3169 result
= (groupId
> 0);
3176 /* Return the current version of the revocation database.
3177 A version of 0 indicates an empty database which must be populated.
3178 If the version cannot be obtained, -1 is returned.
3180 CFIndex
SecRevocationDbGetVersion(void) {
3181 __block CFIndex result
= -1;
3182 SecRevocationDbWith(^(SecRevocationDbRef db
) {
3183 (void) SecRevocationDbPerformRead(db
, NULL
, ^bool(SecRevocationDbConnectionRef dbc
, CFErrorRef
*blockError
) {
3184 result
= (CFIndex
)_SecRevocationDbGetVersion(dbc
, blockError
);
3185 return (result
>= 0);
3191 /* Return the current schema version of the revocation database.
3192 A version of 0 indicates an empty database which must be populated.
3193 If the schema version cannot be obtained, -1 is returned.
3195 CFIndex
SecRevocationDbGetSchemaVersion(void) {
3196 __block CFIndex result
= -1;
3197 SecRevocationDbWith(^(SecRevocationDbRef db
) {
3198 result
= _SecRevocationDbGetSchemaVersion(db
, NULL
, NULL
);
3203 /* Return the current update format of the revocation database.
3204 A version of 0 indicates the format was unknown.
3205 If the update format cannot be obtained, -1 is returned.
3207 CFIndex
SecRevocationDbGetUpdateFormat(void) {
3208 __block CFIndex result
= -1;
3209 SecRevocationDbWith(^(SecRevocationDbRef db
) {
3210 (void) SecRevocationDbPerformRead(db
, NULL
, ^bool(SecRevocationDbConnectionRef dbc
, CFErrorRef
*blockError
) {
3211 result
= (CFIndex
)_SecRevocationDbGetUpdateFormat(dbc
, blockError
);
3212 return (result
>= 0);