2 * Copyright (c) 2016-2018 Apple Inc. All Rights Reserved.
4 * @APPLE_LICENSE_HEADER_START@
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
21 * @APPLE_LICENSE_HEADER_END@
29 #include <securityd/SecRevocationDb.h>
30 #include <securityd/asynchttp.h>
31 #include <securityd/OTATrustUtilities.h>
32 #include <securityd/SecRevocationNetworking.h>
33 #include <securityd/SecTrustLoggingServer.h>
34 #include <Security/SecCertificateInternal.h>
35 #include <Security/SecCMS.h>
36 #include <Security/CMSDecoder.h>
37 #include <Security/SecFramework.h>
38 #include <Security/SecInternal.h>
39 #include <Security/SecPolicyPriv.h>
40 #include <AssertMacros.h>
47 #include <dispatch/dispatch.h>
50 #include "utilities/debugging.h"
51 #include "utilities/sec_action.h"
52 #include "utilities/sqlutils.h"
53 #include "utilities/SecAppleAnchorPriv.h"
54 #include "utilities/iOSforOSX.h"
55 #include <utilities/SecCFError.h>
56 #include <utilities/SecCFRelease.h>
57 #include <utilities/SecCFWrappers.h>
58 #include <utilities/SecDb.h>
59 #include <utilities/SecFileLocations.h>
62 #include <malloc/malloc.h>
63 #include <xpc/activity.h>
64 #include <xpc/private.h>
65 #include <os/transaction_private.h>
67 #include <CFNetwork/CFHTTPMessage.h>
68 #include <CoreFoundation/CFURL.h>
69 #include <CoreFoundation/CFUtilities.h>
71 static CFStringRef kValidUpdateServer
= CFSTR("valid.apple.com");
73 static CFStringRef kSecPrefsDomain
= CFSTR("com.apple.security");
74 static CFStringRef kUpdateServerKey
= CFSTR("ValidUpdateServer");
75 static CFStringRef kUpdateEnabledKey
= CFSTR("ValidUpdateEnabled");
76 static CFStringRef kUpdateIntervalKey
= CFSTR("ValidUpdateInterval");
77 static CFStringRef kBoolTrueKey
= CFSTR("1");
78 static CFStringRef kBoolFalseKey
= CFSTR("0");
80 /* constant length of boolean string keys */
81 #define BOOL_STRING_KEY_LENGTH 1
83 typedef CF_OPTIONS(CFOptionFlags
, SecValidInfoFlags
) {
84 kSecValidInfoComplete
= 1u << 0,
85 kSecValidInfoCheckOCSP
= 1u << 1,
86 kSecValidInfoKnownOnly
= 1u << 2,
87 kSecValidInfoRequireCT
= 1u << 3,
88 kSecValidInfoAllowlist
= 1u << 4,
89 kSecValidInfoNoCACheck
= 1u << 5,
90 kSecValidInfoOverridable
= 1u << 6,
91 kSecValidInfoDateConstraints
= 1u << 7,
92 kSecValidInfoNameConstraints
= 1u << 8,
93 kSecValidInfoPolicyConstraints
= 1u << 9,
96 /* minimum update interval */
97 #define kSecMinUpdateInterval (60.0 * 5)
99 /* standard update interval */
100 #define kSecStdUpdateInterval (60.0 * 60)
102 /* maximum allowed interval */
103 #define kSecMaxUpdateInterval (60.0 * 60 * 24 * 7)
105 #define kSecRevocationBasePath "/Library/Keychains/crls"
106 #define kSecRevocationCurUpdateFile "update-current"
107 #define kSecRevocationDbFileName "valid.sqlite3"
108 #define kSecRevocationDbReplaceFile ".valid_replace"
110 #define isDbOwner SecOTAPKIIsSystemTrustd
112 /* database schema version
114 v2 = fix for group entry transitions
115 v3 = handle optional entries in update dictionaries
116 v4 = add db_format and db_source entries
117 v5 = add date constraints table, with updated group flags
119 Note: kSecRevocationDbMinSchemaVersion is the lowest version whose
120 results can be used. This allows revocation results to be obtained
121 from an existing db before the next update interval occurs, at which
122 time we'll update to the current version (kSecRevocationDbSchemaVersion).
124 #define kSecRevocationDbSchemaVersion 5 /* current version we support */
125 #define kSecRevocationDbMinSchemaVersion 5 /* minimum version we can use */
127 /* update file format
130 kSecValidUpdateFormatG1
= 1, /* initial version */
131 kSecValidUpdateFormatG2
= 2, /* signed content, single plist */
132 kSecValidUpdateFormatG3
= 3 /* signed content, multiple plists */
135 #define kSecRevocationDbUpdateFormat 3 /* current version we support */
136 #define kSecRevocationDbMinUpdateFormat 2 /* minimum version we can use */
138 bool SecRevocationDbVerifyUpdate(void *update
, CFIndex length
);
139 CFIndex
SecRevocationDbIngestUpdate(CFDictionaryRef update
, CFIndex chunkVersion
);
140 void SecRevocationDbApplyUpdate(CFDictionaryRef update
, CFIndex version
);
141 CFAbsoluteTime
SecRevocationDbComputeNextUpdateTime(CFIndex updateInterval
);
142 bool SecRevocationDbSetVersion(CFIndex version
);
143 bool SecRevocationDbSetSchemaVersion(CFIndex dbversion
);
144 bool SecRevocationDbUpdateSchema(void);
145 CFIndex
SecRevocationDbGetUpdateFormat(void);
146 void SecRevocationDbSetUpdateFormat(CFIndex dbformat
);
147 void SecRevocationDbSetUpdateSource(CFStringRef source
);
148 CFStringRef
SecRevocationDbCopyUpdateSource(void);
149 void SecRevocationDbSetNextUpdateTime(CFAbsoluteTime nextUpdate
);
150 CFAbsoluteTime
SecRevocationDbGetNextUpdateTime(void);
151 dispatch_queue_t
SecRevocationDbGetUpdateQueue(void);
152 void SecRevocationDbRemoveAllEntries(void);
153 void SecRevocationDbReleaseAllConnections(void);
156 static CFDataRef
copyInflatedData(CFDataRef data
) {
161 memset(&zs
, 0, sizeof(zs
));
162 /* 32 is a magic value which enables automatic header detection
163 of gzip or zlib compressed data. */
164 if (inflateInit2(&zs
, 32+MAX_WBITS
) != Z_OK
) {
167 zs
.next_in
= (UInt8
*)(CFDataGetBytePtr(data
));
168 zs
.avail_in
= (uInt
)CFDataGetLength(data
);
170 CFMutableDataRef outData
= CFDataCreateMutable(NULL
, 0);
174 CFIndex buf_sz
= malloc_good_size(zs
.avail_in
? zs
.avail_in
: 1024 * 4);
175 unsigned char *buf
= malloc(buf_sz
);
178 zs
.next_out
= (Bytef
*)buf
;
179 zs
.avail_out
= (uInt
)buf_sz
;
180 rc
= inflate(&zs
, 0);
181 CFIndex outLen
= CFDataGetLength(outData
);
182 if (outLen
< (CFIndex
)zs
.total_out
) {
183 CFDataAppendBytes(outData
, (const UInt8
*)buf
, (CFIndex
)zs
.total_out
- outLen
);
185 } while (rc
== Z_OK
);
192 if (rc
!= Z_STREAM_END
) {
193 CFReleaseSafe(outData
);
196 return (CFDataRef
)outData
;
199 static CFDataRef
copyInflatedDataToFile(CFDataRef data
, char *fileName
) {
204 memset(&zs
, 0, sizeof(zs
));
205 /* 32 is a magic value which enables automatic header detection
206 of gzip or zlib compressed data. */
207 if (inflateInit2(&zs
, 32+MAX_WBITS
) != Z_OK
) {
210 zs
.next_in
= (UInt8
*)(CFDataGetBytePtr(data
));
211 zs
.avail_in
= (uInt
)CFDataGetLength(data
);
213 (void)remove(fileName
); /* We need an empty file to start */
216 fd
= open(fileName
, O_RDWR
| O_CREAT
| O_TRUNC
, 0644);
217 if (fd
< 0 || (off
= lseek(fd
, 0, SEEK_SET
)) < 0) {
218 secerror("unable to open %s (errno %d)", fileName
, errno
);
225 CFIndex buf_sz
= malloc_good_size(zs
.avail_in
? zs
.avail_in
: 1024 * 4);
226 unsigned char *buf
= malloc(buf_sz
);
229 zs
.next_out
= (Bytef
*)buf
;
230 zs
.avail_out
= (uInt
)buf_sz
;
231 rc
= inflate(&zs
, 0);
232 if (off
< (int64_t)zs
.total_out
) {
233 off
= write(fd
, buf
, (size_t)zs
.total_out
- (size_t)off
);
235 } while (rc
== Z_OK
);
243 if (rc
!= Z_STREAM_END
) {
244 (void)remove(fileName
);
248 /* Now return an mmapped version of that data */
249 CFDataRef outData
= NULL
;
250 if ((rc
= readValidFile(fileName
, &outData
)) != 0) {
251 secerror("unable to read and map %s (errno %d)", fileName
, rc
);
252 CFReleaseNull(outData
);
257 static CFDataRef
copyDeflatedData(CFDataRef data
) {
262 memset(&zs
, 0, sizeof(zs
));
263 if (deflateInit(&zs
, Z_BEST_COMPRESSION
) != Z_OK
) {
266 zs
.next_in
= (UInt8
*)(CFDataGetBytePtr(data
));
267 zs
.avail_in
= (uInt
)CFDataGetLength(data
);
269 CFMutableDataRef outData
= CFDataCreateMutable(NULL
, 0);
273 CFIndex buf_sz
= malloc_good_size(zs
.avail_in
? zs
.avail_in
: 1024 * 4);
274 unsigned char *buf
= malloc(buf_sz
);
275 int rc
= Z_BUF_ERROR
;
277 zs
.next_out
= (Bytef
*)buf
;
278 zs
.avail_out
= (uInt
)buf_sz
;
279 rc
= deflate(&zs
, Z_FINISH
);
281 if (rc
== Z_OK
|| rc
== Z_STREAM_END
) {
282 CFIndex buf_used
= buf_sz
- zs
.avail_out
;
283 CFDataAppendBytes(outData
, (const UInt8
*)buf
, buf_used
);
285 else if (rc
== Z_BUF_ERROR
) {
287 buf_sz
= malloc_good_size(buf_sz
* 2);
288 buf
= malloc(buf_sz
);
290 rc
= Z_OK
; /* try again with larger buffer */
293 } while (rc
== Z_OK
&& zs
.avail_in
);
300 if (rc
!= Z_STREAM_END
) {
301 CFReleaseSafe(outData
);
304 return (CFDataRef
)outData
;
307 /* Read file opens the file, mmaps it and then closes the file. */
308 int readValidFile(const char *fileName
,
309 CFDataRef
*bytes
) { // mmapped and returned -- must be munmapped!
311 const uint8_t *buf
= NULL
;
316 fd
= open(fileName
, O_RDONLY
);
317 if (fd
< 0) { return errno
; }
318 rtn
= fstat(fd
, &sb
);
319 if (rtn
) { goto errOut
; }
320 if (sb
.st_size
> (off_t
) ((UINT32_MAX
>> 1)-1)) {
324 size
= (size_t)sb
.st_size
;
326 buf
= mmap(NULL
, size
, PROT_READ
, MAP_PRIVATE
, fd
, 0);
327 if (!buf
|| buf
== MAP_FAILED
) {
329 secerror("unable to map %s (errno %d)", fileName
, rtn
);
333 *bytes
= CFDataCreateWithBytesNoCopy(NULL
, buf
, size
, kCFAllocatorNull
);
338 CFReleaseNull(*bytes
);
340 int unmap_err
= munmap((void *)buf
, size
);
341 if (unmap_err
!= 0) {
342 secerror("unable to unmap %ld bytes at %p (error %d)", (long)size
, buf
, rtn
);
349 static void unmapData(CFDataRef CF_CONSUMED data
) {
351 int rtn
= munmap((void *)CFDataGetBytePtr(data
), CFDataGetLength(data
));
353 secerror("unable to unmap %ld bytes at %p (error %d)", CFDataGetLength(data
), CFDataGetBytePtr(data
), rtn
);
360 static bool removeFileWithSuffix(const char *basepath
, const char *suffix
) {
363 asprintf(&path
, "%s%s", basepath
, suffix
);
365 if (remove(path
) == -1) {
367 if (error
== ENOENT
) {
368 result
= true; // not an error if the file did not exist
370 secnotice("validupdate", "remove (%s): %s", path
, strerror(error
));
381 // MARK: SecValidUpdate
383 /* ======================================================================
385 ======================================================================*/
387 CFAbsoluteTime gUpdateStarted
= 0.0;
388 CFAbsoluteTime gNextUpdate
= 0.0;
389 static CFIndex gUpdateInterval
= 0;
390 static CFIndex gLastVersion
= 0;
393 1. The length of the signed data, as a 4-byte integer in network byte order.
394 2. The signed data, which consists of:
395 a. A 4-byte integer in network byte order, the count of plists to follow; and then for each plist:
396 i. A 4-byte integer, the length of each plist
397 ii. A plist, in binary form
398 b. There may be other data after the plists in the signed data, described by a future version of this specification.
399 3. The length of the following CMS blob, as a 4-byte integer in network byte order.
400 4. A detached CMS signature of the signed data described above.
401 5. There may be additional data after the CMS blob, described by a future version of this specification.
403 Note: the difference between g2 and g3 format is the addition of the 4-byte count in (2a).
405 static bool SecValidUpdateProcessData(CFIndex format
, CFDataRef updateData
) {
406 if (!updateData
|| format
< 2) {
411 CFIndex interval
= 0;
412 const UInt8
* p
= CFDataGetBytePtr(updateData
);
413 size_t bytesRemaining
= (p
) ? (size_t)CFDataGetLength(updateData
) : 0;
414 /* make sure there is enough data to contain length and count */
415 if (bytesRemaining
< ((CFIndex
)sizeof(uint32_t) * 2)) {
416 secinfo("validupdate", "Skipping property list creation (length %ld is too short)", (long)bytesRemaining
);
419 /* get length of signed data */
420 uint32_t dataLength
= OSSwapInt32(*((uint32_t *)p
));
421 bytesRemaining
-= sizeof(uint32_t);
422 p
+= sizeof(uint32_t);
424 /* get plist count (G3 format and later) */
425 uint32_t plistCount
= 1;
426 uint32_t plistTotal
= 1;
427 if (format
> kSecValidUpdateFormatG2
) {
428 plistCount
= OSSwapInt32(*((uint32_t *)p
));
429 plistTotal
= plistCount
;
430 bytesRemaining
-= sizeof(uint32_t);
431 p
+= sizeof(uint32_t);
433 if (dataLength
> bytesRemaining
) {
434 secinfo("validupdate", "Skipping property list creation (dataLength=%ld, bytesRemaining=%ld)",
435 (long)dataLength
, (long)bytesRemaining
);
439 /* process each chunked plist */
440 uint32_t plistProcessed
= 0;
441 while (plistCount
> 0 && bytesRemaining
> 0) {
442 CFPropertyListRef propertyList
= NULL
;
443 uint32_t plistLength
= dataLength
;
444 if (format
> kSecValidUpdateFormatG2
) {
445 plistLength
= OSSwapInt32(*((uint32_t *)p
));
446 bytesRemaining
-= sizeof(uint32_t);
447 p
+= sizeof(uint32_t);
452 /* We're about to use a lot of memory for the plist -- go active so we don't get jetsammed */
453 os_transaction_t transaction
;
454 transaction
= os_transaction_create("com.apple.trustd.valid");
456 if (plistLength
<= bytesRemaining
) {
457 CFDataRef data
= CFDataCreateWithBytesNoCopy(NULL
, p
, plistLength
, kCFAllocatorNull
);
458 propertyList
= CFPropertyListCreateWithData(NULL
, data
, kCFPropertyListImmutable
, NULL
, NULL
);
461 if (isDictionary(propertyList
)) {
462 secdebug("validupdate", "Ingesting plist chunk %u of %u, length: %u",
463 plistProcessed
, plistTotal
, plistLength
);
464 CFIndex curVersion
= SecRevocationDbIngestUpdate((CFDictionaryRef
)propertyList
, version
);
465 if (plistProcessed
== 1) {
466 version
= curVersion
;
467 // get server-provided interval
468 CFTypeRef value
= (CFNumberRef
)CFDictionaryGetValue((CFDictionaryRef
)propertyList
,
469 CFSTR("check-again"));
470 if (isNumber(value
)) {
471 CFNumberGetValue((CFNumberRef
)value
, kCFNumberCFIndexType
, &interval
);
474 if (curVersion
< 0) {
475 plistCount
= 0; // we already had this version; skip remaining plists
479 secinfo("validupdate", "Failed to deserialize update chunk %u of %u",
480 plistProcessed
, plistTotal
);
481 if (plistProcessed
== 1) {
482 gNextUpdate
= SecRevocationDbComputeNextUpdateTime(0);
485 /* All finished with this property list */
486 CFReleaseSafe(propertyList
);
487 os_release(transaction
);
489 bytesRemaining
-= plistLength
;
494 secdebug("validupdate", "Update received: v%ld", (long)version
);
495 gLastVersion
= version
;
496 gNextUpdate
= SecRevocationDbComputeNextUpdateTime(interval
);
497 secdebug("validupdate", "Next update time: %f", gNextUpdate
);
501 // remember next update time in case of restart
502 SecRevocationDbSetNextUpdateTime(gNextUpdate
);
507 void SecValidUpdateVerifyAndIngest(CFDataRef updateData
) {
509 secnotice("validupdate", "invalid update data");
512 /* Verify CMS signature on signed data */
513 if (SecRevocationDbVerifyUpdate((void *)CFDataGetBytePtr(updateData
), CFDataGetLength(updateData
))) {
514 bool result
= SecValidUpdateProcessData(kSecValidUpdateFormatG3
, updateData
);
516 // Try g2 update format as a fallback if we failed to read g3
517 result
= SecValidUpdateProcessData(kSecValidUpdateFormatG2
, updateData
);
520 secerror("failed to process valid update");
521 TrustdHealthAnalyticsLogErrorCode(TAEventValidUpdate
, TAFatalError
, errSecDecode
);
523 TrustdHealthAnalyticsLogSuccess(TAEventValidUpdate
);
526 secerror("failed to verify valid update");
527 TrustdHealthAnalyticsLogErrorCode(TAEventValidUpdate
, TAFatalError
, errSecVerifyFailed
);
531 static bool SecValidDatabaseFromCompressed(CFDataRef CF_CONSUMED data
) {
532 if (!data
) { return false; }
534 secdebug("validupdate", "read %ld bytes from file", (long)CFDataGetLength(data
));
536 /* We're about to use a lot of memory for the uncompressed update -- go active */
537 os_transaction_t transaction
;
538 transaction
= os_transaction_create("com.apple.trustd.valid");
540 /* Expand the database */
541 __block CFDataRef inflatedData
= NULL
;
542 WithPathInRevocationInfoDirectory(CFSTR(kSecRevocationDbFileName
), ^(const char *dbPath
) {
543 inflatedData
= copyInflatedDataToFile(data
, (char *)dbPath
);
544 secdebug("validupdate", "data expanded: %ld bytes", (long)CFDataGetLength(inflatedData
));
547 os_release(transaction
);
550 unmapData(inflatedData
);
555 static bool SecValidUpdateSatisfiedLocally(CFStringRef server
, CFIndex version
, bool safeToReplace
) {
556 __block
bool result
= false;
557 CFDataRef data
= NULL
;
558 SecOTAPKIRef otapkiRef
= NULL
;
560 static int sNumLocalUpdates
= 0;
562 // if we've replaced the database with a local asset twice in a row,
563 // something is wrong with it. Get this update from the server.
564 if (sNumLocalUpdates
> 1) {
565 secdebug("validupdate", "%d consecutive db resets, ignoring local asset", sNumLocalUpdates
);
569 // if a non-production server is specified, we will not be able to use a
570 // local production asset since its update sequence will be different.
571 if (kCFCompareEqualTo
!= CFStringCompare(server
, kValidUpdateServer
,
572 kCFCompareCaseInsensitive
)) {
573 secdebug("validupdate", "non-production server specified, ignoring local asset");
577 // check static database asset(s)
578 otapkiRef
= SecOTAPKICopyCurrentOTAPKIRef();
582 CFIndex assetVersion
= SecOTAPKIGetValidSnapshotVersion(otapkiRef
);
583 CFIndex assetFormat
= SecOTAPKIGetValidSnapshotFormat(otapkiRef
);
584 // version <= 0 means the database is invalid or empty.
585 // version > 0 means we have some version, but we need to see if a
586 // newer version is available as a local asset.
587 if (assetVersion
<= version
|| assetFormat
< kSecValidUpdateFormatG3
) {
588 // asset is not newer than ours, or its version is unknown
592 // replace database only if safe to do so (i.e. called at startup)
593 if (!safeToReplace
) {
594 // write semaphore file that we will pick up when we next launch
595 char *semPathBuf
= NULL
;
596 asprintf(&semPathBuf
, "%s/%s", kSecRevocationBasePath
, kSecRevocationDbReplaceFile
);
599 int fd
= open(semPathBuf
, O_WRONLY
| O_CREAT
, DEFFILEMODE
);
600 if (fd
== -1 || fstat(fd
, &sb
)) {
601 secnotice("validupdate", "unable to write %s", semPathBuf
);
608 // exit as gracefully as possible so we can replace the database
609 secnotice("validupdate", "process exiting to replace db file");
610 dispatch_after(dispatch_time(DISPATCH_TIME_NOW
, 3ull*NSEC_PER_SEC
), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT
, 0), ^{
611 xpc_transaction_exit_clean();
616 // try to copy uncompressed database asset, if available
617 const char *validDbPathBuf
= SecOTAPKIGetValidDatabaseSnapshot(otapkiRef
);
618 if (validDbPathBuf
) {
619 WithPathInRevocationInfoDirectory(CFSTR(kSecRevocationDbFileName
), ^(const char *path
) {
620 secdebug("validupdate", "will copy data from \"%s\"", validDbPathBuf
);
621 copyfile_state_t state
= copyfile_state_alloc();
622 int retval
= copyfile(validDbPathBuf
, path
, state
, COPYFILE_DATA
);
623 copyfile_state_free(state
);
625 secnotice("validupdate", "copyfile error %d", retval
);
635 // see if compressed database asset is available
636 if (validDbPathBuf
) {
637 char *validDbCmpPathBuf
= NULL
;
638 asprintf(&validDbCmpPathBuf
, "%s%s", validDbPathBuf
, ".gz");
639 if (validDbCmpPathBuf
) {
640 secdebug("validupdate", "will read data from \"%s\"", validDbCmpPathBuf
);
641 if ((rtn
= readValidFile(validDbCmpPathBuf
, &data
)) != 0) {
644 secnotice("validupdate", "readValidFile error %d", rtn
);
646 free(validDbCmpPathBuf
);
649 result
= SecValidDatabaseFromCompressed(data
);
652 CFReleaseNull(otapkiRef
);
655 gLastVersion
= SecRevocationDbGetVersion();
656 SecRevocationDbSetUpdateSource(server
);
657 SecRevocationDbUpdateSchema();
659 secdebug("validupdate", "local update to g%ld/v%ld complete at %f",
660 (long)SecRevocationDbGetUpdateFormat(), (long)gLastVersion
,
661 (double)CFAbsoluteTimeGetCurrent());
663 sNumLocalUpdates
= 0; // reset counter
668 static bool SecValidUpdateSchedule(bool updateEnabled
, CFStringRef server
, CFIndex version
) {
669 /* Check if we have a later version available locally */
670 if (SecValidUpdateSatisfiedLocally(server
, version
, false)) {
674 /* If update not permitted return */
675 if (!updateEnabled
) {
679 #if !TARGET_OS_BRIDGE
680 /* Schedule as a maintenance task */
681 secdebug("validupdate", "will fetch v%lu from \"%@\"", (unsigned long)version
, server
);
682 return SecValidUpdateRequest(SecRevocationDbGetUpdateQueue(), server
, version
);
688 void SecRevocationDbInitialize() {
689 if (!isDbOwner()) { return; }
690 __block
bool initializeDb
= false;
692 /* create base path if it doesn't exist */
693 (void)mkpath_np(kSecRevocationBasePath
, 0755);
695 /* check semaphore file */
696 WithPathInRevocationInfoDirectory(CFSTR(kSecRevocationDbReplaceFile
), ^(const char *path
) {
698 if (stat(path
, &sb
) == 0) {
699 initializeDb
= true; /* file was found, so we will replace the database */
700 if (remove(path
) == -1) {
702 secnotice("validupdate", "remove (%s): %s", path
, strerror(error
));
708 WithPathInRevocationInfoDirectory(CFSTR(kSecRevocationDbFileName
), ^(const char *path
) {
710 /* remove old database file(s) */
711 (void)removeFileWithSuffix(path
, "");
712 (void)removeFileWithSuffix(path
, "-journal");
713 (void)removeFileWithSuffix(path
, "-shm");
714 (void)removeFileWithSuffix(path
, "-wal");
718 if (stat(path
, &sb
) == -1) {
719 initializeDb
= true; /* file not found, so we will create the database */
725 return; /* database exists and doesn't need replacing */
728 /* initialize database from local asset */
729 CFTypeRef value
= (CFStringRef
)CFPreferencesCopyValue(kUpdateServerKey
, kSecPrefsDomain
, kCFPreferencesAnyUser
, kCFPreferencesCurrentHost
);
730 CFStringRef server
= (isString(value
)) ? (CFStringRef
)value
: (CFStringRef
)kValidUpdateServer
;
732 secnotice("validupdate", "initializing database");
733 if (!SecValidUpdateSatisfiedLocally(server
, version
, true)) {
734 #if !TARGET_OS_BRIDGE
735 /* Schedule full update as a maintenance task */
736 (void)SecValidUpdateRequest(SecRevocationDbGetUpdateQueue(), server
, version
);
739 CFReleaseSafe(value
);
744 // MARK: SecValidInfoRef
746 /* ======================================================================
748 ======================================================================
751 static SecValidInfoRef
SecValidInfoCreate(SecValidInfoFormat format
,
755 CFDataRef issuerHash
,
756 CFDataRef anchorHash
,
757 CFDateRef notBeforeDate
,
758 CFDateRef notAfterDate
,
759 CFDataRef nameConstraints
,
760 CFDataRef policyConstraints
) {
761 SecValidInfoRef validInfo
;
762 validInfo
= (SecValidInfoRef
)calloc(1, sizeof(struct __SecValidInfo
));
763 if (!validInfo
) { return NULL
; }
765 CFRetainSafe(certHash
);
766 CFRetainSafe(issuerHash
);
767 CFRetainSafe(anchorHash
);
768 CFRetainSafe(notBeforeDate
);
769 CFRetainSafe(notAfterDate
);
770 CFRetainSafe(nameConstraints
);
771 CFRetainSafe(policyConstraints
);
773 validInfo
->format
= format
;
774 validInfo
->certHash
= certHash
;
775 validInfo
->issuerHash
= issuerHash
;
776 validInfo
->anchorHash
= anchorHash
;
777 validInfo
->isOnList
= isOnList
;
778 validInfo
->valid
= (flags
& kSecValidInfoAllowlist
);
779 validInfo
->complete
= (flags
& kSecValidInfoComplete
);
780 validInfo
->checkOCSP
= (flags
& kSecValidInfoCheckOCSP
);
781 validInfo
->knownOnly
= (flags
& kSecValidInfoKnownOnly
);
782 validInfo
->requireCT
= (flags
& kSecValidInfoRequireCT
);
783 validInfo
->noCACheck
= (flags
& kSecValidInfoNoCACheck
);
784 validInfo
->overridable
= (flags
& kSecValidInfoOverridable
);
785 validInfo
->hasDateConstraints
= (flags
& kSecValidInfoDateConstraints
);
786 validInfo
->hasNameConstraints
= (flags
& kSecValidInfoNameConstraints
);
787 validInfo
->hasPolicyConstraints
= (flags
& kSecValidInfoPolicyConstraints
);
788 validInfo
->notBeforeDate
= notBeforeDate
;
789 validInfo
->notAfterDate
= notAfterDate
;
790 validInfo
->nameConstraints
= nameConstraints
;
791 validInfo
->policyConstraints
= policyConstraints
;
796 void SecValidInfoRelease(SecValidInfoRef validInfo
) {
798 CFReleaseNull(validInfo
->certHash
);
799 CFReleaseNull(validInfo
->issuerHash
);
800 CFReleaseNull(validInfo
->anchorHash
);
801 CFReleaseNull(validInfo
->notBeforeDate
);
802 CFReleaseNull(validInfo
->notAfterDate
);
803 CFReleaseNull(validInfo
->nameConstraints
);
804 CFReleaseNull(validInfo
->policyConstraints
);
809 void SecValidInfoSetAnchor(SecValidInfoRef validInfo
, SecCertificateRef anchor
) {
813 CFDataRef anchorHash
= NULL
;
815 anchorHash
= SecCertificateCopySHA256Digest(anchor
);
817 /* clear no-ca flag for anchors where we want OCSP checked [32523118] */
818 if (SecIsAppleTrustAnchor(anchor
, 0)) {
819 validInfo
->noCACheck
= false;
822 CFReleaseNull(validInfo
->anchorHash
);
823 validInfo
->anchorHash
= anchorHash
;
828 // MARK: SecRevocationDb
830 /* ======================================================================
832 ======================================================================
835 /* SecRevocationDbCheckNextUpdate returns true if we dispatched an
836 update request, otherwise false.
838 static bool _SecRevocationDbCheckNextUpdate(void) {
839 // are we the db owner instance?
843 CFTypeRef value
= NULL
;
845 // is it time to check?
846 CFAbsoluteTime now
= CFAbsoluteTimeGetCurrent();
847 CFAbsoluteTime minNextUpdate
= now
+ gUpdateInterval
;
848 gUpdateStarted
= now
;
850 if (0 == gNextUpdate
) {
851 // first time we're called, check if we have a saved nextUpdate value
852 gNextUpdate
= SecRevocationDbGetNextUpdateTime();
854 if (gNextUpdate
< minNextUpdate
) {
855 gNextUpdate
= minNextUpdate
;
857 // allow pref to override update interval, if it exists
858 CFIndex interval
= -1;
859 value
= (CFNumberRef
)CFPreferencesCopyValue(kUpdateIntervalKey
, kSecPrefsDomain
, kCFPreferencesAnyUser
, kCFPreferencesCurrentHost
);
860 if (isNumber(value
)) {
861 if (CFNumberGetValue((CFNumberRef
)value
, kCFNumberCFIndexType
, &interval
)) {
862 if (interval
< kSecMinUpdateInterval
) {
863 interval
= kSecMinUpdateInterval
;
864 } else if (interval
> kSecMaxUpdateInterval
) {
865 interval
= kSecMaxUpdateInterval
;
869 CFReleaseNull(value
);
870 gUpdateInterval
= kSecStdUpdateInterval
;
872 gUpdateInterval
= interval
;
874 // pin next update time to the preferred update interval
875 if (gNextUpdate
> (gUpdateStarted
+ gUpdateInterval
)) {
876 gNextUpdate
= gUpdateStarted
+ gUpdateInterval
;
878 secdebug("validupdate", "next update at %f (in %f seconds)",
879 (double)gUpdateStarted
, (double)gNextUpdate
-gUpdateStarted
);
881 if (gNextUpdate
> now
) {
885 secnotice("validupdate", "starting update");
887 // set minimum next update time here in case we can't get an update
888 gNextUpdate
= minNextUpdate
;
890 // determine which server to query
892 value
= (CFStringRef
)CFPreferencesCopyValue(kUpdateServerKey
, kSecPrefsDomain
, kCFPreferencesAnyUser
, kCFPreferencesCurrentHost
);
893 if (isString(value
)) {
894 server
= (CFStringRef
) CFRetain(value
);
896 server
= (CFStringRef
) CFRetain(kValidUpdateServer
);
898 CFReleaseNull(value
);
900 // determine version of our current database
901 CFIndex version
= SecRevocationDbGetVersion();
902 secdebug("validupdate", "got version %ld from db", (long)version
);
904 if (gLastVersion
> 0) {
905 secdebug("validupdate", "error getting version; using last good version: %ld", (long)gLastVersion
);
907 version
= gLastVersion
;
910 // determine source of our current database
911 // (if this ever changes, we will need to reload the db)
912 CFStringRef db_source
= SecRevocationDbCopyUpdateSource();
914 db_source
= (CFStringRef
) CFRetain(kValidUpdateServer
);
917 // determine whether we need to recreate the database
918 CFIndex db_version
= SecRevocationDbGetSchemaVersion();
919 CFIndex db_format
= SecRevocationDbGetUpdateFormat();
920 if (db_version
< kSecRevocationDbSchemaVersion
||
921 db_format
< kSecRevocationDbUpdateFormat
||
922 kCFCompareEqualTo
!= CFStringCompare(server
, db_source
, kCFCompareCaseInsensitive
)) {
923 // we need to fully rebuild the db contents, so we set our version to 0.
924 version
= gLastVersion
= 0;
927 // determine whether update fetching is enabled
928 #if (__MAC_OS_X_VERSION_MIN_REQUIRED >= 101300 || __IPHONE_OS_VERSION_MIN_REQUIRED >= 110000)
929 bool updateEnabled
= true; // macOS 10.13 or iOS 11.0
931 bool updateEnabled
= false;
933 value
= (CFBooleanRef
)CFPreferencesCopyValue(kUpdateEnabledKey
, kSecPrefsDomain
, kCFPreferencesAnyUser
, kCFPreferencesCurrentHost
);
934 if (isBoolean(value
)) {
935 updateEnabled
= CFBooleanGetValue((CFBooleanRef
)value
);
937 CFReleaseNull(value
);
939 // Schedule maintenance work
940 bool result
= SecValidUpdateSchedule(updateEnabled
, server
, version
);
941 CFReleaseNull(server
);
942 CFReleaseNull(db_source
);
946 void SecRevocationDbCheckNextUpdate(void) {
947 static dispatch_once_t once
;
948 static sec_action_t action
;
950 dispatch_once(&once
, ^{
951 dispatch_queue_t update_queue
= SecRevocationDbGetUpdateQueue();
952 action
= sec_action_create_with_queue(update_queue
, "update_check", kSecMinUpdateInterval
);
953 sec_action_set_handler(action
, ^{
954 (void)_SecRevocationDbCheckNextUpdate();
957 sec_action_perform(action
);
960 /* This function verifies an update, in this format:
961 1) unsigned 32-bit network-byte-order length of binary plist
963 3) unsigned 32-bit network-byte-order length of CMS message
964 4) CMS message (containing certificates and signature over binary plist)
966 The length argument is the total size of the packed update data.
968 bool SecRevocationDbVerifyUpdate(void *update
, CFIndex length
) {
969 if (!update
|| length
<= (CFIndex
)sizeof(uint32_t)) {
972 uint32_t plistLength
= OSSwapInt32(*((uint32_t *)update
));
973 if ((plistLength
+ (CFIndex
)(sizeof(uint32_t)*2)) > (uint64_t) length
) {
974 secdebug("validupdate", "ERROR: reported plist length (%lu)+%lu exceeds total length (%lu)\n",
975 (unsigned long)plistLength
, (unsigned long)sizeof(uint32_t)*2, (unsigned long)length
);
978 uint8_t *plistData
= (uint8_t *)update
+ sizeof(uint32_t);
979 uint8_t *sigData
= (uint8_t *)plistData
+ plistLength
;
980 uint32_t sigLength
= OSSwapInt32(*((uint32_t *)sigData
));
981 sigData
+= sizeof(uint32_t);
982 if ((plistLength
+ sigLength
+ (CFIndex
)(sizeof(uint32_t) * 2)) != (uint64_t) length
) {
983 secdebug("validupdate", "ERROR: reported lengths do not add up to total length\n");
988 CMSSignerStatus signerStatus
;
989 CMSDecoderRef cms
= NULL
;
990 SecPolicyRef policy
= NULL
;
991 SecTrustRef trust
= NULL
;
992 CFDataRef content
= NULL
;
994 if ((content
= CFDataCreateWithBytesNoCopy(kCFAllocatorDefault
,
995 (const UInt8
*)plistData
, (CFIndex
)plistLength
, kCFAllocatorNull
)) == NULL
) {
996 secdebug("validupdate", "CFDataCreateWithBytesNoCopy failed (%ld bytes)\n", (long)plistLength
);
1000 if ((status
= CMSDecoderCreate(&cms
)) != errSecSuccess
) {
1001 secdebug("validupdate", "CMSDecoderCreate failed with error %d\n", (int)status
);
1004 if ((status
= CMSDecoderUpdateMessage(cms
, sigData
, sigLength
)) != errSecSuccess
) {
1005 secdebug("validupdate", "CMSDecoderUpdateMessage failed with error %d\n", (int)status
);
1008 if ((status
= CMSDecoderSetDetachedContent(cms
, content
)) != errSecSuccess
) {
1009 secdebug("validupdate", "CMSDecoderSetDetachedContent failed with error %d\n", (int)status
);
1012 if ((status
= CMSDecoderFinalizeMessage(cms
)) != errSecSuccess
) {
1013 secdebug("validupdate", "CMSDecoderFinalizeMessage failed with error %d\n", (int)status
);
1017 policy
= SecPolicyCreateApplePinned(CFSTR("ValidUpdate"), // kSecPolicyNameAppleValidUpdate
1018 CFSTR("1.2.840.113635.100.6.2.10"), // System Integration 2 Intermediate Certificate
1019 CFSTR("1.2.840.113635.100.6.51")); // Valid update signing OID
1021 // Check that the first signer actually signed this message.
1022 if ((status
= CMSDecoderCopySignerStatus(cms
, 0, policy
,
1023 false, &signerStatus
, &trust
, NULL
)) != errSecSuccess
) {
1024 secdebug("validupdate", "CMSDecoderCopySignerStatus failed with error %d\n", (int)status
);
1027 // Make sure the signature verifies against the detached content
1028 if (signerStatus
!= kCMSSignerValid
) {
1029 secdebug("validupdate", "ERROR: signature did not verify (signer status %d)\n", (int)signerStatus
);
1030 status
= errSecInvalidSignature
;
1033 // Make sure the signing certificate is valid for the specified policy
1034 SecTrustResultType trustResult
= kSecTrustResultInvalid
;
1035 status
= SecTrustEvaluate(trust
, &trustResult
);
1036 if (status
!= errSecSuccess
) {
1037 secdebug("validupdate", "SecTrustEvaluate failed with error %d (trust=%p)\n", (int)status
, (void *)trust
);
1038 } else if (!(trustResult
== kSecTrustResultUnspecified
|| trustResult
== kSecTrustResultProceed
)) {
1039 secdebug("validupdate", "SecTrustEvaluate failed with trust result %d\n", (int)trustResult
);
1040 status
= errSecVerificationFailure
;
1045 CFReleaseSafe(content
);
1046 CFReleaseSafe(trust
);
1047 CFReleaseSafe(policy
);
1050 return (status
== errSecSuccess
);
1053 CFAbsoluteTime
SecRevocationDbComputeNextUpdateTime(CFIndex updateInterval
) {
1054 CFIndex interval
= updateInterval
;
1055 // try to use interval preference if it exists
1056 CFTypeRef value
= (CFNumberRef
)CFPreferencesCopyValue(kUpdateIntervalKey
, kSecPrefsDomain
, kCFPreferencesAnyUser
, kCFPreferencesCurrentHost
);
1057 if (isNumber(value
)) {
1058 CFNumberGetValue((CFNumberRef
)value
, kCFNumberCFIndexType
, &interval
);
1060 CFReleaseNull(value
);
1062 if (interval
<= 0) {
1063 interval
= kSecStdUpdateInterval
;
1067 if (interval
< kSecMinUpdateInterval
) {
1068 interval
= kSecMinUpdateInterval
;
1069 } else if (interval
> kSecMaxUpdateInterval
) {
1070 interval
= kSecMaxUpdateInterval
;
1073 // compute randomization factor, between 0 and 50% of the interval
1074 CFIndex fuzz
= arc4random() % (long)(interval
/2.0);
1075 CFAbsoluteTime nextUpdate
= CFAbsoluteTimeGetCurrent() + interval
+ fuzz
;
1076 secdebug("validupdate", "next update in %ld seconds", (long)(interval
+ fuzz
));
1080 void SecRevocationDbComputeAndSetNextUpdateTime(void) {
1081 gNextUpdate
= SecRevocationDbComputeNextUpdateTime(0);
1082 SecRevocationDbSetNextUpdateTime(gNextUpdate
);
1083 gUpdateStarted
= 0; /* no update is currently in progress */
1086 CFIndex
SecRevocationDbIngestUpdate(CFDictionaryRef update
, CFIndex chunkVersion
) {
1087 CFIndex version
= 0;
1091 CFTypeRef value
= (CFNumberRef
)CFDictionaryGetValue(update
, CFSTR("version"));
1092 if (isNumber(value
)) {
1093 if (!CFNumberGetValue((CFNumberRef
)value
, kCFNumberCFIndexType
, &version
)) {
1098 // only the first chunk will have a version, so the second and
1099 // subsequent chunks will need to pass it in chunkVersion.
1100 version
= chunkVersion
;
1102 CFIndex curVersion
= SecRevocationDbGetVersion();
1103 if (version
> curVersion
|| chunkVersion
> 0) {
1104 SecRevocationDbApplyUpdate(update
, version
);
1106 secdebug("validupdate", "we have v%ld, skipping update to v%ld",
1107 (long)curVersion
, (long)version
);
1108 version
= -1; // invalid, so we know to skip subsequent chunks
1114 /* Database schema */
1116 /* admin table holds these key-value (or key-ival) pairs:
1117 'version' (integer) // version of database content
1118 'check_again' (double) // CFAbsoluteTime of next check (optional)
1119 'db_version' (integer) // version of database schema
1120 'db_hash' (blob) // SHA-256 database hash
1121 --> entries in admin table are unique by text key
1123 issuers table holds map of issuing CA hashes to group identifiers:
1124 groupid (integer) // associated group identifier in group ID table
1125 issuer_hash (blob) // SHA-256 hash of issuer certificate (primary key)
1126 --> entries in issuers table are unique by issuer_hash;
1127 multiple issuer entries may have the same groupid!
1129 groups table holds records with these attributes:
1130 groupid (integer) // ordinal ID associated with this group entry
1131 flags (integer) // a bitmask of the following values:
1132 kSecValidInfoComplete (0x00000001) set if we have all revocation info for this issuer group
1133 kSecValidInfoCheckOCSP (0x00000002) set if must check ocsp for certs from this issuer group
1134 kSecValidInfoKnownOnly (0x00000004) set if any CA from this issuer group must be in database
1135 kSecValidInfoRequireCT (0x00000008) set if all certs from this issuer group must have SCTs
1136 kSecValidInfoAllowlist (0x00000010) set if this entry describes valid certs (i.e. is allowed)
1137 kSecValidInfoNoCACheck (0x00000020) set if this entry does not require an OCSP check to accept
1138 kSecValidInfoOverridable (0x00000040) set if the trust status is recoverable and can be overridden
1139 kSecValidInfoDateConstraints (0x00000080) set if this group has not-before or not-after constraints
1140 kSecValidInfoNameConstraints (0x00000100) [RESERVED] set if this group has name constraints in database
1141 kSecValidInfoPolicyConstraints (0x00000200) [RESERVED] set if this group has policy constraints in database
1142 format (integer) // an integer describing format of entries:
1143 kSecValidInfoFormatUnknown (0) unknown format
1144 kSecValidInfoFormatSerial (1) serial number, not greater than 20 bytes in length
1145 kSecValidInfoFormatSHA256 (2) SHA-256 hash, 32 bytes in length
1146 kSecValidInfoFormatNto1 (3) filter data blob of arbitrary length
1147 data (blob) // Bloom filter data if format is 'nto1', otherwise NULL
1148 --> entries in groups table are unique by groupid
1150 serials table holds serial number blobs with these attributes:
1151 groupid (integer) // identifier for issuer group in the groups table
1152 serial (blob) // serial number
1153 --> entries in serials table are unique by serial and groupid
1155 hashes table holds SHA-256 hashes of certificates with these attributes:
1156 groupid (integer) // identifier for issuer group in the groups table
1157 sha256 (blob) // SHA-256 hash of subject certificate
1158 --> entries in hashes table are unique by sha256 and groupid
1160 dates table holds notBefore and notAfter dates (as CFAbsoluteTime) with these attributes:
1161 groupid (integer) // identifier for issuer group in the groups table (primary key)
1162 notbefore (real) // issued certs are invalid if their notBefore is prior to this date
1163 notafter (real) // issued certs are invalid after this date (or their notAfter, if earlier)
1164 --> entries in dates table are unique by groupid, and only exist if kSecValidInfoDateConstraints is true
1167 #define createTablesSQL CFSTR("CREATE TABLE admin(" \
1168 "key TEXT PRIMARY KEY NOT NULL," \
1169 "ival INTEGER NOT NULL," \
1172 "CREATE TABLE issuers(" \
1173 "groupid INTEGER NOT NULL," \
1174 "issuer_hash BLOB PRIMARY KEY NOT NULL" \
1176 "CREATE INDEX issuer_idx ON issuers(issuer_hash);" \
1177 "CREATE TABLE groups(" \
1178 "groupid INTEGER PRIMARY KEY AUTOINCREMENT," \
1183 "CREATE TABLE serials(" \
1184 "groupid INTEGER NOT NULL," \
1185 "serial BLOB NOT NULL," \
1186 "UNIQUE(groupid,serial)" \
1188 "CREATE TABLE hashes(" \
1189 "groupid INTEGER NOT NULL," \
1190 "sha256 BLOB NOT NULL," \
1191 "UNIQUE(groupid,sha256)" \
1193 "CREATE TABLE dates(" \
1194 "groupid INTEGER PRIMARY KEY NOT NULL," \
1198 "CREATE TRIGGER group_del BEFORE DELETE ON groups FOR EACH ROW " \
1200 "DELETE FROM serials WHERE groupid=OLD.groupid; " \
1201 "DELETE FROM hashes WHERE groupid=OLD.groupid; " \
1202 "DELETE FROM issuers WHERE groupid=OLD.groupid; " \
1203 "DELETE FROM dates WHERE groupid=OLD.groupid; " \
1206 #define selectGroupIdSQL CFSTR("SELECT DISTINCT groupid " \
1207 "FROM issuers WHERE issuer_hash=?")
1208 #define selectVersionSQL CFSTR("SELECT ival FROM admin " \
1209 "WHERE key='version'")
1210 #define selectDbVersionSQL CFSTR("SELECT ival FROM admin " \
1211 "WHERE key='db_version'")
1212 #define selectDbFormatSQL CFSTR("SELECT ival FROM admin " \
1213 "WHERE key='db_format'")
1214 #define selectDbHashSQL CFSTR("SELECT value FROM admin " \
1215 "WHERE key='db_hash'")
1216 #define selectDbSourceSQL CFSTR("SELECT value FROM admin " \
1217 "WHERE key='db_source'")
1218 #define selectNextUpdateSQL CFSTR("SELECT value FROM admin " \
1219 "WHERE key='check_again'")
1220 #define selectGroupRecordSQL CFSTR("SELECT flags,format,data FROM " \
1221 "groups WHERE groupid=?")
1222 #define selectSerialRecordSQL CFSTR("SELECT rowid FROM serials " \
1223 "WHERE groupid=? AND serial=?")
1224 #define selectDateRecordSQL CFSTR("SELECT notbefore,notafter FROM " \
1225 "dates WHERE groupid=?")
1226 #define selectHashRecordSQL CFSTR("SELECT rowid FROM hashes " \
1227 "WHERE groupid=? AND sha256=?")
1228 #define insertAdminRecordSQL CFSTR("INSERT OR REPLACE INTO admin " \
1229 "(key,ival,value) VALUES (?,?,?)")
1230 #define insertIssuerRecordSQL CFSTR("INSERT OR REPLACE INTO issuers " \
1231 "(groupid,issuer_hash) VALUES (?,?)")
1232 #define insertGroupRecordSQL CFSTR("INSERT OR REPLACE INTO groups " \
1233 "(groupid,flags,format,data) VALUES (?,?,?,?)")
1234 #define insertSerialRecordSQL CFSTR("INSERT OR REPLACE INTO serials " \
1235 "(groupid,serial) VALUES (?,?)")
1236 #define insertDateRecordSQL CFSTR("INSERT OR REPLACE INTO dates " \
1237 "(groupid,notbefore,notafter) VALUES (?,?,?)")
1238 #define insertSha256RecordSQL CFSTR("INSERT OR REPLACE INTO hashes " \
1239 "(groupid,sha256) VALUES (?,?)")
1240 #define deleteGroupRecordSQL CFSTR("DELETE FROM groups WHERE groupid=?")
1242 #define updateConstraintsTablesSQL CFSTR("" \
1243 "CREATE TABLE if not exists dates(" \
1244 "groupid INTEGER PRIMARY KEY NOT NULL," \
1249 #define updateGroupDeleteTriggerSQL CFSTR("" \
1250 "DROP TRIGGER if exists group_del;" \
1251 "CREATE TRIGGER group_del BEFORE DELETE ON groups FOR EACH ROW " \
1253 "DELETE FROM serials WHERE groupid=OLD.groupid; " \
1254 "DELETE FROM hashes WHERE groupid=OLD.groupid; " \
1255 "DELETE FROM issuers WHERE groupid=OLD.groupid; " \
1256 "DELETE FROM dates WHERE groupid=OLD.groupid; " \
1259 #define deleteTablesSQL CFSTR("DROP TABLE hashes; " \
1260 "DROP TABLE serials; DROP TABLE issuers; " \
1261 "DROP TABLE dates; DROP TABLE groups; " \
1262 "DROP TABLE admin; DELETE from sqlite_sequence")
1264 /* Database management */
1266 static SecDbRef
SecRevocationDbCreate(CFStringRef path
) {
1267 /* only the db owner should open a read-write connection. */
1268 bool readWrite
= isDbOwner();
1271 SecDbRef result
= SecDbCreateWithOptions(path
, mode
, readWrite
, false, false, ^bool (SecDbRef db
, SecDbConnectionRef dbconn
, bool didCreate
, bool *callMeAgainForNextConnection
, CFErrorRef
*error
) {
1272 __block
bool ok
= true;
1273 CFErrorRef localError
= NULL
;
1274 if (ok
&& !SecDbWithSQL(dbconn
, selectGroupIdSQL
, &localError
, NULL
) && CFErrorGetCode(localError
) == SQLITE_ERROR
) {
1275 /* SecDbWithSQL returns SQLITE_ERROR if the table we are preparing the above statement for doesn't exist. */
1277 /* Create all database tables, indexes, and triggers. */
1278 ok
&= SecDbTransaction(dbconn
, kSecDbExclusiveTransactionType
, error
, ^(bool *commit
) {
1279 ok
= SecDbExec(dbconn
, createTablesSQL
, error
);
1283 if (!ok
|| localError
) {
1284 CFIndex errCode
= errSecInternalComponent
;
1285 if (error
&& *error
) {
1286 errCode
= CFErrorGetCode(*error
);
1288 secerror("%s failed: %@", didCreate
? "Create" : "Open", error
? *error
: NULL
);
1289 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationCreate
, TAFatalError
, errCode
);
1291 CFReleaseSafe(localError
);
1298 typedef struct __SecRevocationDb
*SecRevocationDbRef
;
1299 struct __SecRevocationDb
{
1301 dispatch_queue_t update_queue
;
1302 bool updateInProgress
;
1303 bool unsupportedVersion
;
1306 static dispatch_once_t kSecRevocationDbOnce
;
1307 static SecRevocationDbRef kSecRevocationDb
= NULL
;
1309 static SecRevocationDbRef
SecRevocationDbInit(CFStringRef db_name
) {
1310 SecRevocationDbRef rdb
;
1311 dispatch_queue_attr_t attr
;
1313 require(rdb
= (SecRevocationDbRef
)malloc(sizeof(struct __SecRevocationDb
)), errOut
);
1315 rdb
->update_queue
= NULL
;
1316 rdb
->updateInProgress
= false;
1317 rdb
->unsupportedVersion
= false;
1319 require(rdb
->db
= SecRevocationDbCreate(db_name
), errOut
);
1320 attr
= dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL
, QOS_CLASS_BACKGROUND
, 0);
1321 attr
= dispatch_queue_attr_make_with_autorelease_frequency(attr
, DISPATCH_AUTORELEASE_FREQUENCY_WORK_ITEM
);
1322 require(rdb
->update_queue
= dispatch_queue_create(NULL
, attr
), errOut
);
1327 secdebug("validupdate", "Failed to create db at \"%@\"", db_name
);
1329 if (rdb
->update_queue
) {
1330 dispatch_release(rdb
->update_queue
);
1332 CFReleaseSafe(rdb
->db
);
1338 static CFStringRef
SecRevocationDbCopyPath(void) {
1339 CFURLRef revDbURL
= NULL
;
1340 CFStringRef revInfoRelPath
= NULL
;
1341 if ((revInfoRelPath
= CFStringCreateWithFormat(NULL
, NULL
, CFSTR("%s"), kSecRevocationDbFileName
)) != NULL
) {
1342 revDbURL
= SecCopyURLForFileInRevocationInfoDirectory(revInfoRelPath
);
1344 CFReleaseSafe(revInfoRelPath
);
1346 CFStringRef revDbPath
= NULL
;
1348 revDbPath
= CFURLCopyFileSystemPath(revDbURL
, kCFURLPOSIXPathStyle
);
1349 CFRelease(revDbURL
);
1354 static void SecRevocationDbWith(void(^dbJob
)(SecRevocationDbRef db
)) {
1355 dispatch_once(&kSecRevocationDbOnce
, ^{
1356 CFStringRef dbPath
= SecRevocationDbCopyPath();
1358 kSecRevocationDb
= SecRevocationDbInit(dbPath
);
1362 // Do pre job run work here (cancel idle timers etc.)
1363 if (kSecRevocationDb
->updateInProgress
) {
1364 return; // this would block since SecDb has an exclusive transaction lock
1366 dbJob(kSecRevocationDb
);
1367 // Do post job run work here (gc timer, etc.)
1370 static int64_t _SecRevocationDbGetVersion(SecRevocationDbRef rdb
, CFErrorRef
*error
) {
1371 /* look up version entry in admin table; returns -1 on error */
1372 __block
int64_t version
= -1;
1373 __block
bool ok
= true;
1374 __block CFErrorRef localError
= NULL
;
1376 ok
&= SecDbPerformRead(rdb
->db
, &localError
, ^(SecDbConnectionRef dbconn
) {
1377 ok
&= SecDbWithSQL(dbconn
, selectVersionSQL
, &localError
, ^bool(sqlite3_stmt
*selectVersion
) {
1378 ok
&= SecDbStep(dbconn
, selectVersion
, &localError
, ^void(bool *stop
) {
1379 version
= sqlite3_column_int64(selectVersion
, 0);
1385 if (!ok
|| localError
) {
1386 secerror("_SecRevocationDbGetVersion failed: %@", localError
);
1387 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationRead
, TAFatalError
,
1388 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
1390 (void) CFErrorPropagate(localError
, error
);
1394 static void _SecRevocationDbSetVersion(SecRevocationDbRef rdb
, CFIndex version
) {
1395 secdebug("validupdate", "setting version to %ld", (long)version
);
1397 __block CFErrorRef localError
= NULL
;
1398 __block
bool ok
= true;
1399 ok
&= SecDbPerformWrite(rdb
->db
, &localError
, ^(SecDbConnectionRef dbconn
) {
1400 ok
&= SecDbTransaction(dbconn
, kSecDbExclusiveTransactionType
, &localError
, ^(bool *commit
) {
1401 ok
&= SecDbWithSQL(dbconn
, insertAdminRecordSQL
, &localError
, ^bool(sqlite3_stmt
*insertVersion
) {
1402 const char *versionKey
= "version";
1403 ok
= ok
&& SecDbBindText(insertVersion
, 1, versionKey
, strlen(versionKey
),
1404 SQLITE_TRANSIENT
, &localError
);
1405 ok
= ok
&& SecDbBindInt64(insertVersion
, 2,
1406 (sqlite3_int64
)version
, &localError
);
1407 ok
= ok
&& SecDbStep(dbconn
, insertVersion
, &localError
, NULL
);
1412 if (!ok
|| localError
) {
1413 secerror("_SecRevocationDbSetVersion failed: %@", localError
);
1414 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationWrite
, TAFatalError
,
1415 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
1417 CFReleaseSafe(localError
);
1420 static int64_t _SecRevocationDbGetSchemaVersion(SecRevocationDbRef rdb
, CFErrorRef
*error
) {
1421 /* look up db_version entry in admin table; returns -1 on error */
1422 __block
int64_t db_version
= -1;
1423 __block
bool ok
= true;
1424 __block CFErrorRef localError
= NULL
;
1426 ok
&= SecDbPerformRead(rdb
->db
, &localError
, ^(SecDbConnectionRef dbconn
) {
1427 ok
&= SecDbWithSQL(dbconn
, selectDbVersionSQL
, &localError
, ^bool(sqlite3_stmt
*selectDbVersion
) {
1428 ok
&= SecDbStep(dbconn
, selectDbVersion
, &localError
, ^void(bool *stop
) {
1429 db_version
= sqlite3_column_int64(selectDbVersion
, 0);
1435 if (!ok
|| localError
) {
1436 secerror("_SecRevocationDbGetSchemaVersion failed: %@", localError
);
1437 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationRead
, TAFatalError
,
1438 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
1440 (void) CFErrorPropagate(localError
, error
);
1444 static bool _SecRevocationDbSetSchemaVersion(SecRevocationDbRef rdb
, CFIndex dbversion
) {
1445 if (dbversion
> 0) {
1446 int64_t db_version
= _SecRevocationDbGetSchemaVersion(rdb
, NULL
);
1447 if (db_version
>= dbversion
) {
1448 return true; /* requested schema is earlier than current schema */
1451 secdebug("validupdate", "setting db_version to %ld", (long)dbversion
);
1453 __block CFErrorRef localError
= NULL
;
1454 __block
bool ok
= true;
1455 ok
&= SecDbPerformWrite(rdb
->db
, &localError
, ^(SecDbConnectionRef dbconn
) {
1456 ok
&= SecDbTransaction(dbconn
, kSecDbExclusiveTransactionType
, &localError
, ^(bool *commit
) {
1457 ok
&= SecDbWithSQL(dbconn
, insertAdminRecordSQL
, &localError
, ^bool(sqlite3_stmt
*insertDbVersion
) {
1458 const char *dbVersionKey
= "db_version";
1459 ok
= ok
&& SecDbBindText(insertDbVersion
, 1, dbVersionKey
, strlen(dbVersionKey
),
1460 SQLITE_TRANSIENT
, &localError
);
1461 ok
= ok
&& SecDbBindInt64(insertDbVersion
, 2,
1462 (sqlite3_int64
)dbversion
, &localError
);
1463 ok
= ok
&& SecDbStep(dbconn
, insertDbVersion
, &localError
, NULL
);
1468 if (!ok
|| localError
) {
1469 secerror("_SecRevocationDbSetSchemaVersion failed: %@", localError
);
1470 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationWrite
, TAFatalError
,
1471 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
1473 rdb
->unsupportedVersion
= false;
1475 CFReleaseSafe(localError
);
1479 static bool _SecRevocationDbUpdateSchema(SecRevocationDbRef rdb
) {
1480 secdebug("validupdate", "updating db schema to v%ld", (long)kSecRevocationDbSchemaVersion
);
1482 __block CFErrorRef localError
= NULL
;
1483 __block
bool ok
= true;
1484 ok
&= SecDbPerformWrite(rdb
->db
, &localError
, ^(SecDbConnectionRef dbconn
) {
1485 ok
&= SecDbTransaction(dbconn
, kSecDbExclusiveTransactionType
, &localError
, ^(bool *commit
) {
1486 ok
&= SecDbWithSQL(dbconn
, updateConstraintsTablesSQL
, &localError
, ^bool(sqlite3_stmt
*updateTables
) {
1487 ok
= SecDbStep(dbconn
, updateTables
, &localError
, NULL
);
1491 ok
&= SecDbWithSQL(dbconn
, updateGroupDeleteTriggerSQL
, &localError
, ^bool(sqlite3_stmt
*updateTrigger
) {
1492 ok
= SecDbStep(dbconn
, updateTrigger
, &localError
, NULL
);
1498 secerror("_SecRevocationDbUpdateSchema failed: %@", localError
);
1500 ok
&= _SecRevocationDbSetSchemaVersion(rdb
, kSecRevocationDbSchemaVersion
);
1502 CFReleaseSafe(localError
);
1506 static int64_t _SecRevocationDbGetUpdateFormat(SecRevocationDbRef rdb
, CFErrorRef
*error
) {
1507 /* look up db_format entry in admin table; returns -1 on error */
1508 __block
int64_t db_format
= -1;
1509 __block
bool ok
= true;
1510 __block CFErrorRef localError
= NULL
;
1512 ok
&= SecDbPerformRead(rdb
->db
, &localError
, ^(SecDbConnectionRef dbconn
) {
1513 ok
&= SecDbWithSQL(dbconn
, selectDbFormatSQL
, &localError
, ^bool(sqlite3_stmt
*selectDbFormat
) {
1514 ok
&= SecDbStep(dbconn
, selectDbFormat
, &localError
, ^void(bool *stop
) {
1515 db_format
= sqlite3_column_int64(selectDbFormat
, 0);
1521 if (!ok
|| localError
) {
1522 secerror("_SecRevocationDbGetUpdateFormat failed: %@", localError
);
1523 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationRead
, TAFatalError
,
1524 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
1526 (void) CFErrorPropagate(localError
, error
);
1530 static void _SecRevocationDbSetUpdateFormat(SecRevocationDbRef rdb
, CFIndex dbformat
) {
1531 secdebug("validupdate", "setting db_format to %ld", (long)dbformat
);
1533 __block CFErrorRef localError
= NULL
;
1534 __block
bool ok
= true;
1535 ok
&= SecDbPerformWrite(rdb
->db
, &localError
, ^(SecDbConnectionRef dbconn
) {
1536 ok
&= SecDbTransaction(dbconn
, kSecDbExclusiveTransactionType
, &localError
, ^(bool *commit
) {
1537 ok
&= SecDbWithSQL(dbconn
, insertAdminRecordSQL
, &localError
, ^bool(sqlite3_stmt
*insertDbFormat
) {
1538 const char *dbFormatKey
= "db_format";
1539 ok
= ok
&& SecDbBindText(insertDbFormat
, 1, dbFormatKey
, strlen(dbFormatKey
),
1540 SQLITE_TRANSIENT
, &localError
);
1541 ok
= ok
&& SecDbBindInt64(insertDbFormat
, 2,
1542 (sqlite3_int64
)dbformat
, &localError
);
1543 ok
= ok
&& SecDbStep(dbconn
, insertDbFormat
, &localError
, NULL
);
1548 if (!ok
|| localError
) {
1549 secerror("_SecRevocationDbSetUpdateFormat failed: %@", localError
);
1550 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationWrite
, TAFatalError
,
1551 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
1553 rdb
->unsupportedVersion
= false;
1555 CFReleaseSafe(localError
);
1558 static CFStringRef
_SecRevocationDbCopyUpdateSource(SecRevocationDbRef rdb
, CFErrorRef
*error
) {
1559 /* look up db_source entry in admin table; returns NULL on error */
1560 __block CFStringRef updateSource
= NULL
;
1561 __block
bool ok
= true;
1562 __block CFErrorRef localError
= NULL
;
1564 ok
&= SecDbPerformRead(rdb
->db
, &localError
, ^(SecDbConnectionRef dbconn
) {
1565 ok
&= SecDbWithSQL(dbconn
, selectDbSourceSQL
, &localError
, ^bool(sqlite3_stmt
*selectDbSource
) {
1566 ok
&= SecDbStep(dbconn
, selectDbSource
, &localError
, ^void(bool *stop
) {
1567 const UInt8
*p
= (const UInt8
*)sqlite3_column_blob(selectDbSource
, 0);
1569 CFIndex length
= (CFIndex
)sqlite3_column_bytes(selectDbSource
, 0);
1571 updateSource
= CFStringCreateWithBytes(kCFAllocatorDefault
, p
, length
, kCFStringEncodingUTF8
, false);
1579 if (!ok
|| localError
) {
1580 secerror("_SecRevocationDbCopyUpdateSource failed: %@", localError
);
1581 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationRead
, TAFatalError
,
1582 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
1584 (void) CFErrorPropagate(localError
, error
);
1585 return updateSource
;
1588 static void _SecRevocationDbSetUpdateSource(SecRevocationDbRef rdb
, CFStringRef updateSource
) {
1589 if (!updateSource
) {
1590 secerror("_SecRevocationDbSetUpdateSource failed: %d", errSecParam
);
1593 __block
char buffer
[256];
1594 __block
const char *updateSourceCStr
= CFStringGetCStringPtr(updateSource
, kCFStringEncodingUTF8
);
1595 if (!updateSourceCStr
) {
1596 if (CFStringGetCString(updateSource
, buffer
, 256, kCFStringEncodingUTF8
)) {
1597 updateSourceCStr
= buffer
;
1600 if (!updateSourceCStr
) {
1601 secerror("_SecRevocationDbSetUpdateSource failed: unable to get UTF-8 encoding");
1604 secdebug("validupdate", "setting update source to \"%s\"", updateSourceCStr
);
1606 __block CFErrorRef localError
= NULL
;
1607 __block
bool ok
= true;
1608 ok
&= SecDbPerformWrite(rdb
->db
, &localError
, ^(SecDbConnectionRef dbconn
) {
1609 ok
&= SecDbTransaction(dbconn
, kSecDbExclusiveTransactionType
, &localError
, ^(bool *commit
) {
1610 ok
&= SecDbWithSQL(dbconn
, insertAdminRecordSQL
, &localError
, ^bool(sqlite3_stmt
*insertRecord
) {
1611 const char *dbSourceKey
= "db_source";
1612 ok
= ok
&& SecDbBindText(insertRecord
, 1, dbSourceKey
, strlen(dbSourceKey
),
1613 SQLITE_TRANSIENT
, &localError
);
1614 ok
= ok
&& SecDbBindInt64(insertRecord
, 2,
1615 (sqlite3_int64
)0, &localError
);
1616 ok
= ok
&& SecDbBindBlob(insertRecord
, 3,
1617 updateSourceCStr
, strlen(updateSourceCStr
),
1618 SQLITE_TRANSIENT
, &localError
);
1619 ok
= ok
&& SecDbStep(dbconn
, insertRecord
, &localError
, NULL
);
1624 if (!ok
|| localError
) {
1625 secerror("_SecRevocationDbSetUpdateSource failed: %@", localError
);
1626 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationWrite
, TAFatalError
,
1627 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
1629 CFReleaseSafe(localError
);
1632 static CFAbsoluteTime
_SecRevocationDbGetNextUpdateTime(SecRevocationDbRef rdb
, CFErrorRef
*error
) {
1633 /* look up check_again entry in admin table; returns 0 on error */
1634 __block CFAbsoluteTime nextUpdate
= 0;
1635 __block
bool ok
= true;
1636 __block CFErrorRef localError
= NULL
;
1638 ok
&= SecDbPerformRead(rdb
->db
, &localError
, ^(SecDbConnectionRef dbconn
) {
1639 ok
&= SecDbWithSQL(dbconn
, selectNextUpdateSQL
, &localError
, ^bool(sqlite3_stmt
*selectNextUpdate
) {
1640 ok
&= SecDbStep(dbconn
, selectNextUpdate
, &localError
, ^void(bool *stop
) {
1641 CFAbsoluteTime
*p
= (CFAbsoluteTime
*)sqlite3_column_blob(selectNextUpdate
, 0);
1643 if (sizeof(CFAbsoluteTime
) == sqlite3_column_bytes(selectNextUpdate
, 0)) {
1652 if (!ok
|| localError
) {
1653 secerror("_SecRevocationDbGetNextUpdateTime failed: %@", localError
);
1654 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationRead
, TAFatalError
,
1655 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
1657 (void) CFErrorPropagate(localError
, error
);
1661 static void _SecRevocationDbSetNextUpdateTime(SecRevocationDbRef rdb
, CFAbsoluteTime nextUpdate
){
1662 secdebug("validupdate", "setting next update to %f", (double)nextUpdate
);
1664 __block CFErrorRef localError
= NULL
;
1665 __block
bool ok
= true;
1666 ok
&= SecDbPerformWrite(rdb
->db
, &localError
, ^(SecDbConnectionRef dbconn
) {
1667 ok
&= SecDbTransaction(dbconn
, kSecDbExclusiveTransactionType
, &localError
, ^(bool *commit
) {
1668 ok
&= SecDbWithSQL(dbconn
, insertAdminRecordSQL
, &localError
, ^bool(sqlite3_stmt
*insertRecord
) {
1669 const char *nextUpdateKey
= "check_again";
1670 ok
= ok
&& SecDbBindText(insertRecord
, 1, nextUpdateKey
, strlen(nextUpdateKey
),
1671 SQLITE_TRANSIENT
, &localError
);
1672 ok
= ok
&& SecDbBindInt64(insertRecord
, 2,
1673 (sqlite3_int64
)0, &localError
);
1674 ok
= ok
&& SecDbBindBlob(insertRecord
, 3,
1675 &nextUpdate
, sizeof(CFAbsoluteTime
),
1676 SQLITE_TRANSIENT
, &localError
);
1677 ok
= ok
&& SecDbStep(dbconn
, insertRecord
, &localError
, NULL
);
1682 if (!ok
|| localError
) {
1683 secerror("_SecRevocationDbSetNextUpdate failed: %@", localError
);
1684 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationWrite
, TAFatalError
,
1685 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
1687 CFReleaseSafe(localError
);
1690 static bool _SecRevocationDbRemoveAllEntries(SecRevocationDbRef rdb
) {
1691 /* clear out the contents of the database and start fresh */
1692 __block
bool ok
= true;
1693 __block CFErrorRef localError
= NULL
;
1695 /* update schema first */
1696 _SecRevocationDbUpdateSchema(rdb
);
1698 ok
&= SecDbPerformWrite(rdb
->db
, &localError
, ^(SecDbConnectionRef dbconn
) {
1699 ok
&= SecDbTransaction(dbconn
, kSecDbExclusiveTransactionType
, &localError
, ^(bool *commit
) {
1700 /* drop all tables and recreate them, in case of schema changes */
1701 ok
&= SecDbExec(dbconn
, deleteTablesSQL
, &localError
);
1702 ok
&= SecDbExec(dbconn
, createTablesSQL
, &localError
);
1703 secdebug("validupdate", "resetting database, result: %d", (ok
) ? 1 : 0);
1706 /* compact the db (must be done outside transaction scope) */
1707 ok
&= SecDbExec(dbconn
, CFSTR("VACUUM"), &localError
);
1709 /* one more thing: update the schema version and format to current */
1710 _SecRevocationDbSetSchemaVersion(rdb
, kSecRevocationDbSchemaVersion
);
1711 _SecRevocationDbSetUpdateFormat(rdb
, kSecRevocationDbUpdateFormat
);
1713 if (!ok
|| localError
) {
1714 secerror("_SecRevocationDbRemoveAllEntries failed: %@", localError
);
1715 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationWrite
, TAFatalError
,
1716 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
1718 CFReleaseSafe(localError
);
1722 static bool _SecRevocationDbUpdateIssuers(SecRevocationDbRef rdb
, int64_t groupId
, CFArrayRef issuers
, CFErrorRef
*error
) {
1723 /* insert or replace issuer records in issuers table */
1724 if (!issuers
|| groupId
< 0) {
1725 return false; /* must have something to insert, and a group to associate with it */
1727 __block
bool ok
= true;
1728 __block CFErrorRef localError
= NULL
;
1730 ok
&= SecDbPerformWrite(rdb
->db
, &localError
, ^(SecDbConnectionRef dbconn
) {
1731 ok
&= SecDbTransaction(dbconn
, kSecDbExclusiveTransactionType
, &localError
, ^(bool *commit
) {
1732 if (isArray(issuers
)) {
1733 CFIndex issuerIX
, issuerCount
= CFArrayGetCount(issuers
);
1734 for (issuerIX
=0; issuerIX
<issuerCount
&& ok
; issuerIX
++) {
1735 CFDataRef hash
= (CFDataRef
)CFArrayGetValueAtIndex(issuers
, issuerIX
);
1736 if (!hash
) { continue; }
1737 ok
= ok
&& SecDbWithSQL(dbconn
, insertIssuerRecordSQL
, &localError
, ^bool(sqlite3_stmt
*insertIssuer
) {
1738 ok
= ok
&& SecDbBindInt64(insertIssuer
, 1,
1739 groupId
, &localError
);
1740 ok
= ok
&& SecDbBindBlob(insertIssuer
, 2,
1741 CFDataGetBytePtr(hash
),
1742 CFDataGetLength(hash
),
1743 SQLITE_TRANSIENT
, &localError
);
1744 /* Execute the insert statement for this issuer record. */
1745 ok
= ok
&& SecDbStep(dbconn
, insertIssuer
, &localError
, NULL
);
1752 if (!ok
|| localError
) {
1753 secerror("_SecRevocationDbUpdateIssuers failed: %@", localError
);
1754 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationWrite
, TAFatalError
,
1755 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
1757 (void) CFErrorPropagate(localError
, error
);
1761 static bool _SecRevocationDbUpdateIssuerData(SecRevocationDbRef rdb
, int64_t groupId
, CFDictionaryRef dict
, CFErrorRef
*error
) {
1762 /* update/delete records in serials or hashes table. */
1763 if (!dict
|| groupId
< 0) {
1764 return false; /* must have something to insert, and a group to associate with it */
1766 __block
bool ok
= true;
1767 __block CFErrorRef localError
= NULL
;
1769 ok
&= SecDbPerformWrite(rdb
->db
, &localError
, ^(SecDbConnectionRef dbconn
) {
1770 ok
&= SecDbTransaction(dbconn
, kSecDbExclusiveTransactionType
, &localError
, ^(bool *commit
) {
1771 CFArrayRef deleteArray
= (CFArrayRef
)CFDictionaryGetValue(dict
, CFSTR("delete"));
1772 /* process deletions */
1773 if (isArray(deleteArray
)) {
1774 //%%% delete old data here (rdar://31439625)
1776 /* process additions */
1777 CFArrayRef addArray
= (CFArrayRef
)CFDictionaryGetValue(dict
, CFSTR("add"));
1778 if (isArray(addArray
)) {
1779 CFIndex identifierIX
, identifierCount
= CFArrayGetCount(addArray
);
1780 for (identifierIX
=0; identifierIX
<identifierCount
; identifierIX
++) {
1781 CFDataRef identifierData
= (CFDataRef
)CFArrayGetValueAtIndex(addArray
, identifierIX
);
1782 if (!identifierData
) { continue; }
1783 CFIndex length
= CFDataGetLength(identifierData
);
1784 /* we can figure out the format without an extra read to get the format column.
1785 len <= 20 is a serial number. len==32 is a sha256 hash. otherwise: xor. */
1786 CFStringRef sql
= NULL
;
1788 sql
= insertSerialRecordSQL
;
1789 } else if (length
== 32) {
1790 sql
= insertSha256RecordSQL
;
1792 if (!sql
) { continue; }
1794 ok
= ok
&& SecDbWithSQL(dbconn
, sql
, &localError
, ^bool(sqlite3_stmt
*insertIdentifier
) {
1795 /* rowid,(groupid,serial|sha256) */
1796 /* rowid is autoincremented and we never set it directly */
1797 ok
= ok
&& SecDbBindInt64(insertIdentifier
, 1,
1798 groupId
, &localError
);
1799 ok
= ok
&& SecDbBindBlob(insertIdentifier
, 2,
1800 CFDataGetBytePtr(identifierData
),
1801 CFDataGetLength(identifierData
),
1802 SQLITE_TRANSIENT
, &localError
);
1803 /* Execute the insert statement for the identifier record. */
1804 ok
= ok
&& SecDbStep(dbconn
, insertIdentifier
, &localError
, NULL
);
1811 if (!ok
|| localError
) {
1812 secerror("_SecRevocationDbUpdatePerIssuerData failed: %@", localError
);
1813 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationWrite
, TAFatalError
,
1814 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
1816 (void) CFErrorPropagate(localError
, error
);
1820 static bool _SecRevocationDbCopyDateConstraints(SecRevocationDbRef rdb
,
1821 int64_t groupId
, CFDateRef
*notBeforeDate
, CFDateRef
*notAfterDate
, CFErrorRef
*error
) {
1822 /* return true if one or both date constraints exist for a given groupId.
1823 the actual constraints are optionally returned in output CFDateRef parameters.
1824 caller is responsible for releasing date and error parameters, if provided.
1826 __block
bool ok
= true;
1827 __block CFDateRef localNotBefore
= NULL
;
1828 __block CFDateRef localNotAfter
= NULL
;
1829 __block CFErrorRef localError
= NULL
;
1831 ok
&= SecDbPerformRead(rdb
->db
, &localError
, ^(SecDbConnectionRef dbconn
) {
1832 ok
&= SecDbWithSQL(dbconn
, selectDateRecordSQL
, &localError
, ^bool(sqlite3_stmt
*selectDates
) {
1833 /* (groupid,notbefore,notafter) */
1834 ok
&= SecDbBindInt64(selectDates
, 1, groupId
, &localError
);
1835 ok
= ok
&& SecDbStep(dbconn
, selectDates
, &localError
, ^(bool *stop
) {
1836 /* if column has no value, its type will be SQLITE_NULL */
1837 if (SQLITE_NULL
!= sqlite3_column_type(selectDates
, 0)) {
1838 CFAbsoluteTime nb
= (CFAbsoluteTime
)sqlite3_column_double(selectDates
, 0);
1839 localNotBefore
= CFDateCreate(NULL
, nb
);
1841 if (SQLITE_NULL
!= sqlite3_column_type(selectDates
, 1)) {
1842 CFAbsoluteTime na
= (CFAbsoluteTime
)sqlite3_column_double(selectDates
, 1);
1843 localNotAfter
= CFDateCreate(NULL
, na
);
1849 /* must have at least one date constraint */
1850 ok
= ok
&& (localNotBefore
!= NULL
|| localNotAfter
!= NULL
);
1851 if (!ok
|| localError
) {
1852 secerror("_SecRevocationDbCopyDateConstraints failed: %@", localError
);
1853 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationRead
, TAFatalError
,
1854 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
1855 CFReleaseNull(localNotBefore
);
1856 CFReleaseNull(localNotAfter
);
1858 if (notBeforeDate
) {
1859 *notBeforeDate
= localNotBefore
;
1861 CFReleaseSafe(localNotBefore
);
1864 *notAfterDate
= localNotAfter
;
1866 CFReleaseSafe(localNotAfter
);
1869 (void) CFErrorPropagate(localError
, error
);
1873 static bool _SecRevocationDbUpdateIssuerConstraints(SecRevocationDbRef rdb
, int64_t groupId
, CFDictionaryRef dict
, CFErrorRef
*error
) {
1874 /* update optional records in dates, names, or policies tables. */
1875 if (!dict
|| groupId
< 0) {
1876 return false; /* must have something to insert, and a group to associate with it */
1878 __block
bool ok
= true;
1879 __block CFErrorRef localError
= NULL
;
1880 __block CFAbsoluteTime notBefore
= -3155760000.0; /* default: 1901-01-01 00:00:00-0000 */
1881 __block CFAbsoluteTime notAfter
= 31556908800.0; /* default: 3001-01-01 00:00:00-0000 */
1883 CFDateRef notBeforeDate
= (CFDateRef
)CFDictionaryGetValue(dict
, CFSTR("not-before"));
1884 CFDateRef notAfterDate
= (CFDateRef
)CFDictionaryGetValue(dict
, CFSTR("not-after"));
1885 if (isDate(notBeforeDate
)) {
1886 notBefore
= CFDateGetAbsoluteTime(notBeforeDate
);
1888 notBeforeDate
= NULL
;
1890 if (isDate(notAfterDate
)) {
1891 notAfter
= CFDateGetAbsoluteTime(notAfterDate
);
1893 notAfterDate
= NULL
;
1895 if (!(notBeforeDate
|| notAfterDate
)) {
1896 return false; /* no dates supplied, so we have nothing to update for this issuer */
1899 if (!(notBeforeDate
&& notAfterDate
)) {
1900 /* only one date was supplied, so check for existing date constraints */
1901 CFDateRef curNotBeforeDate
= NULL
;
1902 CFDateRef curNotAfterDate
= NULL
;
1903 if (_SecRevocationDbCopyDateConstraints(rdb
, groupId
, &curNotBeforeDate
,
1904 &curNotAfterDate
, &localError
)) {
1905 if (!notBeforeDate
) {
1906 notBeforeDate
= curNotBeforeDate
;
1907 notBefore
= CFDateGetAbsoluteTime(notBeforeDate
);
1909 CFReleaseSafe(curNotBeforeDate
);
1911 if (!notAfterDate
) {
1912 notAfterDate
= curNotAfterDate
;
1913 notAfter
= CFDateGetAbsoluteTime(notAfterDate
);
1915 CFReleaseSafe(curNotAfterDate
);
1920 ok
&= SecDbPerformWrite(rdb
->db
, &localError
, ^(SecDbConnectionRef dbconn
) {
1921 ok
&= SecDbTransaction(dbconn
, kSecDbExclusiveTransactionType
, &localError
, ^(bool *commit
) {
1922 ok
&= SecDbWithSQL(dbconn
, insertDateRecordSQL
, &localError
, ^bool(sqlite3_stmt
*insertDate
) {
1923 /* (groupid,notbefore,notafter) */
1924 ok
= ok
&& SecDbBindInt64(insertDate
, 1, groupId
, &localError
);
1925 ok
= ok
&& SecDbBindDouble(insertDate
, 2, notBefore
, &localError
);
1926 ok
= ok
&& SecDbBindDouble(insertDate
, 3, notAfter
, &localError
);
1927 ok
= ok
&& SecDbStep(dbconn
, insertDate
, &localError
, NULL
);
1931 /* %%% (TBI:9254570,21234699) update name and policy constraint entries here */
1935 if (!ok
|| localError
) {
1936 secinfo("validupdate", "_SecRevocationDbUpdateIssuerConstraints failed (ok=%s, localError=%@)",
1937 (ok
) ? "1" : "0", localError
);
1938 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationWrite
, TAFatalError
,
1939 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
1942 (void) CFErrorPropagate(localError
, error
);
1946 static SecValidInfoFormat
_SecRevocationDbGetGroupFormat(SecRevocationDbRef rdb
,
1947 int64_t groupId
, SecValidInfoFlags
*flags
, CFDataRef
*data
, CFErrorRef
*error
) {
1948 /* return group record fields for a given groupId.
1949 on success, returns a non-zero format type, and other field values in optional output parameters.
1950 caller is responsible for releasing data and error parameters, if provided.
1952 __block
bool ok
= true;
1953 __block SecValidInfoFormat format
= 0;
1954 __block CFErrorRef localError
= NULL
;
1956 /* Select the group record to determine flags and format. */
1957 ok
&= SecDbPerformRead(rdb
->db
, &localError
, ^(SecDbConnectionRef dbconn
) {
1958 ok
&= SecDbWithSQL(dbconn
, selectGroupRecordSQL
, &localError
, ^bool(sqlite3_stmt
*selectGroup
) {
1959 ok
= ok
&& SecDbBindInt64(selectGroup
, 1, groupId
, &localError
);
1960 ok
= ok
&& SecDbStep(dbconn
, selectGroup
, &localError
, ^(bool *stop
) {
1962 *flags
= (SecValidInfoFlags
)sqlite3_column_int(selectGroup
, 0);
1964 format
= (SecValidInfoFormat
)sqlite3_column_int(selectGroup
, 1);
1966 //%%% stream the data from the db into a streamed decompression <rdar://32142637>
1967 uint8_t *p
= (uint8_t *)sqlite3_column_blob(selectGroup
, 2);
1968 if (p
!= NULL
&& format
== kSecValidInfoFormatNto1
) {
1969 CFIndex length
= (CFIndex
)sqlite3_column_bytes(selectGroup
, 2);
1970 *data
= CFDataCreate(kCFAllocatorDefault
, p
, length
);
1977 if (!ok
|| localError
) {
1978 secdebug("validupdate", "GetGroupFormat for groupId %lu failed", (unsigned long)groupId
);
1979 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationRead
, TAFatalError
,
1980 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
1981 format
= kSecValidInfoFormatUnknown
;
1983 (void) CFErrorPropagate(localError
, error
);
1984 if (!(format
> kSecValidInfoFormatUnknown
)) {
1985 secdebug("validupdate", "GetGroupFormat: got format %d for groupId %lld", format
, (long long)groupId
);
1990 static bool _SecRevocationDbUpdateFlags(CFDictionaryRef dict
, CFStringRef key
, SecValidInfoFlags mask
, SecValidInfoFlags
*flags
) {
1991 /* If a boolean value exists in the given dictionary for the given key,
1992 or an explicit "1" or "0" is specified as the key string,
1993 set or clear the corresponding bit(s) defined by the mask argument.
1994 Function returns true if the flags value was changed, false otherwise.
1996 if (!isDictionary(dict
) || !isString(key
) || !flags
) {
1999 bool hasValue
= false, newValue
= false, result
= false;
2000 CFTypeRef value
= (CFBooleanRef
)CFDictionaryGetValue(dict
, key
);
2001 if (isBoolean(value
)) {
2002 newValue
= CFBooleanGetValue((CFBooleanRef
)value
);
2004 } else if (BOOL_STRING_KEY_LENGTH
== CFStringGetLength(key
)) {
2005 if (CFStringCompare(key
, kBoolTrueKey
, 0) == kCFCompareEqualTo
) {
2006 hasValue
= newValue
= true;
2007 } else if (CFStringCompare(key
, kBoolFalseKey
, 0) == kCFCompareEqualTo
) {
2012 SecValidInfoFlags oldFlags
= *flags
;
2018 result
= (*flags
!= oldFlags
);
2023 static bool _SecRevocationDbUpdateFilter(CFDictionaryRef dict
, CFDataRef oldData
, CFDataRef
* __nonnull CF_RETURNS_RETAINED xmlData
) {
2024 /* If xor and/or params values exist in the given dictionary, create a new
2025 property list containing the updated values, and return as a flattened
2026 data blob in the xmlData output parameter (note: caller must release.)
2027 Function returns true if there is new xmlData to save, false otherwise.
2029 bool result
= false;
2030 bool xorProvided
= false;
2031 bool paramsProvided
= false;
2032 bool missingData
= false;
2034 if (!dict
|| !xmlData
) {
2035 return result
; /* no-op if no dictionary is provided, or no way to update the data */
2038 CFDataRef xorCurrent
= NULL
;
2039 CFDataRef xorUpdate
= (CFDataRef
)CFDictionaryGetValue(dict
, CFSTR("xor"));
2040 if (isData(xorUpdate
)) {
2043 CFArrayRef paramsCurrent
= NULL
;
2044 CFArrayRef paramsUpdate
= (CFArrayRef
)CFDictionaryGetValue(dict
, CFSTR("params"));
2045 if (isArray(paramsUpdate
)) {
2046 paramsProvided
= true;
2048 if (!(xorProvided
|| paramsProvided
)) {
2049 return result
; /* nothing to update, so we can bail out here. */
2052 CFPropertyListRef nto1Current
= NULL
;
2053 CFMutableDictionaryRef nto1Update
= CFDictionaryCreateMutable(kCFAllocatorDefault
, 0,
2054 &kCFTypeDictionaryKeyCallBacks
,
2055 &kCFTypeDictionaryValueCallBacks
);
2060 /* turn old data into property list */
2061 CFDataRef data
= (CFDataRef
)CFRetainSafe(oldData
);
2062 CFDataRef inflatedData
= copyInflatedData(data
);
2064 CFReleaseSafe(data
);
2065 data
= inflatedData
;
2068 nto1Current
= CFPropertyListCreateWithData(kCFAllocatorDefault
, data
, 0, NULL
, NULL
);
2069 CFReleaseSafe(data
);
2072 xorCurrent
= (CFDataRef
)CFDictionaryGetValue((CFDictionaryRef
)nto1Current
, CFSTR("xor"));
2073 paramsCurrent
= (CFArrayRef
)CFDictionaryGetValue((CFDictionaryRef
)nto1Current
, CFSTR("params"));
2076 /* set current or updated xor data in new property list */
2078 CFDataRef xorNew
= NULL
;
2080 CFIndex xorUpdateLen
= CFDataGetLength(xorUpdate
);
2081 CFMutableDataRef
xor = CFDataCreateMutableCopy(NULL
, 0, xorCurrent
);
2082 if (xor && xorUpdateLen
> 0) {
2083 /* truncate or zero-extend data to match update size */
2084 CFDataSetLength(xor, xorUpdateLen
);
2085 /* exclusive-or update bytes over the existing data */
2086 UInt8
*xorP
= (UInt8
*)CFDataGetMutableBytePtr(xor);
2087 UInt8
*updP
= (UInt8
*)CFDataGetBytePtr(xorUpdate
);
2089 for (int idx
= 0; idx
< xorUpdateLen
; idx
++) {
2090 xorP
[idx
] = xorP
[idx
] ^ updP
[idx
];
2094 xorNew
= (CFDataRef
)xor;
2096 xorNew
= (CFDataRef
)CFRetainSafe(xorUpdate
);
2099 CFDictionaryAddValue(nto1Update
, CFSTR("xor"), xorNew
);
2100 CFReleaseSafe(xorNew
);
2102 secdebug("validupdate", "Failed to get updated filter data");
2105 } else if (xorCurrent
) {
2106 /* not provided, so use existing xor value */
2107 CFDictionaryAddValue(nto1Update
, CFSTR("xor"), xorCurrent
);
2109 secdebug("validupdate", "Failed to get current filter data");
2113 /* set current or updated params in new property list */
2114 if (paramsProvided
) {
2115 CFDictionaryAddValue(nto1Update
, CFSTR("params"), paramsUpdate
);
2116 } else if (paramsCurrent
) {
2117 /* not provided, so use existing params value */
2118 CFDictionaryAddValue(nto1Update
, CFSTR("params"), paramsCurrent
);
2120 /* missing params: neither provided nor existing */
2121 secdebug("validupdate", "Failed to get current filter params");
2125 CFReleaseSafe(nto1Current
);
2127 *xmlData
= CFPropertyListCreateData(kCFAllocatorDefault
, nto1Update
,
2128 kCFPropertyListXMLFormat_v1_0
,
2130 result
= (*xmlData
!= NULL
);
2132 CFReleaseSafe(nto1Update
);
2134 /* compress the xmlData blob, if possible */
2136 CFDataRef deflatedData
= copyDeflatedData(*xmlData
);
2138 if (CFDataGetLength(deflatedData
) < CFDataGetLength(*xmlData
)) {
2139 CFRelease(*xmlData
);
2140 *xmlData
= deflatedData
;
2142 CFRelease(deflatedData
);
2150 static int64_t _SecRevocationDbUpdateGroup(SecRevocationDbRef rdb
, int64_t groupId
, CFDictionaryRef dict
, CFErrorRef
*error
) {
2151 /* insert group record for a given groupId.
2152 if the specified groupId is < 0, a new group entry is created.
2153 returns the groupId on success, or -1 on failure.
2156 return groupId
; /* no-op if no dictionary is provided */
2159 __block
int64_t result
= -1;
2160 __block
bool ok
= true;
2161 __block
bool isFormatChange
= false;
2162 __block CFErrorRef localError
= NULL
;
2164 __block SecValidInfoFlags flags
= 0;
2165 __block SecValidInfoFormat format
= kSecValidInfoFormatUnknown
;
2166 __block SecValidInfoFormat formatUpdate
= kSecValidInfoFormatUnknown
;
2167 __block CFDataRef data
= NULL
;
2170 /* fetch the flags and data for an existing group record, in case some are being changed. */
2171 format
= _SecRevocationDbGetGroupFormat(rdb
, groupId
, &flags
, &data
, NULL
);
2172 if (format
== kSecValidInfoFormatUnknown
) {
2173 secdebug("validupdate", "existing group %lld has unknown format %d, flags=0x%lx",
2174 (long long)groupId
, format
, flags
);
2175 //%%% clean up by deleting all issuers with this groupId, then the group record,
2176 // or just force a full update? note: we can get here if we fail to bind the
2177 // format value in the prepared SQL statement below.
2181 CFTypeRef value
= (CFStringRef
)CFDictionaryGetValue(dict
, CFSTR("format"));
2182 if (isString(value
)) {
2183 if (CFStringCompare((CFStringRef
)value
, CFSTR("serial"), 0) == kCFCompareEqualTo
) {
2184 formatUpdate
= kSecValidInfoFormatSerial
;
2185 } else if (CFStringCompare((CFStringRef
)value
, CFSTR("sha256"), 0) == kCFCompareEqualTo
) {
2186 formatUpdate
= kSecValidInfoFormatSHA256
;
2187 } else if (CFStringCompare((CFStringRef
)value
, CFSTR("nto1"), 0) == kCFCompareEqualTo
) {
2188 formatUpdate
= kSecValidInfoFormatNto1
;
2191 /* if format value is explicitly supplied, then this is effectively a new group entry. */
2192 isFormatChange
= (formatUpdate
> kSecValidInfoFormatUnknown
&&
2193 formatUpdate
!= format
&&
2196 ok
&= SecDbPerformWrite(rdb
->db
, &localError
, ^(SecDbConnectionRef dbconn
) {
2197 ok
&= SecDbTransaction(dbconn
, kSecDbExclusiveTransactionType
, &localError
, ^(bool *commit
) {
2198 if (isFormatChange
) {
2199 secdebug("validupdate", "group %lld format change from %d to %d",
2200 (long long)groupId
, format
, formatUpdate
);
2201 /* format of an existing group is changing; delete the group first.
2202 this should ensure that all entries referencing the old groupid are deleted.
2204 ok
&= SecDbWithSQL(dbconn
, deleteGroupRecordSQL
, &localError
, ^bool(sqlite3_stmt
*deleteResponse
) {
2205 ok
= SecDbBindInt64(deleteResponse
, 1, groupId
, &localError
);
2206 /* Execute the delete statement. */
2207 ok
= ok
&& SecDbStep(dbconn
, deleteResponse
, &localError
, NULL
);
2211 ok
&= SecDbWithSQL(dbconn
, insertGroupRecordSQL
, &localError
, ^bool(sqlite3_stmt
*insertGroup
) {
2212 /* (groupid,flags,format,data) */
2213 /* groups.groupid */
2214 if (ok
&& (!isFormatChange
) && (groupId
>= 0)) {
2215 /* bind to existing groupId row if known, otherwise will insert and autoincrement */
2216 ok
= SecDbBindInt64(insertGroup
, 1, groupId
, &localError
);
2218 secdebug("validupdate", "failed to set groupId %lld", (long long)groupId
);
2223 (void)_SecRevocationDbUpdateFlags(dict
, CFSTR("complete"), kSecValidInfoComplete
, &flags
);
2224 (void)_SecRevocationDbUpdateFlags(dict
, CFSTR("check-ocsp"), kSecValidInfoCheckOCSP
, &flags
);
2225 (void)_SecRevocationDbUpdateFlags(dict
, CFSTR("known-intermediates-only"), kSecValidInfoKnownOnly
, &flags
);
2226 (void)_SecRevocationDbUpdateFlags(dict
, CFSTR("require-ct"), kSecValidInfoRequireCT
, &flags
);
2227 (void)_SecRevocationDbUpdateFlags(dict
, CFSTR("valid"), kSecValidInfoAllowlist
, &flags
);
2228 (void)_SecRevocationDbUpdateFlags(dict
, CFSTR("no-ca"), kSecValidInfoNoCACheck
, &flags
);
2229 (void)_SecRevocationDbUpdateFlags(dict
, CFSTR("overridable"), kSecValidInfoOverridable
, &flags
);
2231 /* date constraints exist if either "not-before" or "not-after" keys are found */
2232 CFTypeRef notBeforeValue
= (CFDateRef
)CFDictionaryGetValue(dict
, CFSTR("not-before"));
2233 CFTypeRef notAfterValue
= (CFDateRef
)CFDictionaryGetValue(dict
, CFSTR("not-after"));
2234 if (isDate(notBeforeValue
) || isDate(notAfterValue
)) {
2235 (void)_SecRevocationDbUpdateFlags(dict
, kBoolTrueKey
, kSecValidInfoDateConstraints
, &flags
);
2236 /* Note that the spec defines not-before and not-after dates as optional, such that
2237 not providing one does not change the database contents. Therefore, we can never clear
2238 this flag; either a new date entry will be supplied, or a format change will cause
2239 the entire group entry to be deleted. */
2242 /* %%% (TBI:9254570,21234699) name and policy constraints don't exist yet */
2243 (void)_SecRevocationDbUpdateFlags(dict
, kBoolFalseKey
, kSecValidInfoNameConstraints
, &flags
);
2244 (void)_SecRevocationDbUpdateFlags(dict
, kBoolFalseKey
, kSecValidInfoPolicyConstraints
, &flags
);
2246 ok
= SecDbBindInt(insertGroup
, 2, (int)flags
, &localError
);
2248 secdebug("validupdate", "failed to set flags (%lu) for groupId %lld", flags
, (long long)groupId
);
2253 SecValidInfoFormat formatValue
= format
;
2254 if (formatUpdate
> kSecValidInfoFormatUnknown
) {
2255 formatValue
= formatUpdate
;
2257 ok
= SecDbBindInt(insertGroup
, 3, (int)formatValue
, &localError
);
2259 secdebug("validupdate", "failed to set format (%d) for groupId %lld", formatValue
, (long long)groupId
);
2263 CFDataRef xmlData
= NULL
;
2265 bool hasFilter
= ((formatUpdate
== kSecValidInfoFormatNto1
) ||
2266 (formatUpdate
== kSecValidInfoFormatUnknown
&&
2267 format
== kSecValidInfoFormatNto1
));
2269 CFDataRef dataValue
= data
; /* use existing data */
2270 if (_SecRevocationDbUpdateFilter(dict
, data
, &xmlData
)) {
2271 dataValue
= xmlData
; /* use updated data */
2274 ok
= SecDbBindBlob(insertGroup
, 4,
2275 CFDataGetBytePtr(dataValue
),
2276 CFDataGetLength(dataValue
),
2277 SQLITE_TRANSIENT
, &localError
);
2280 secdebug("validupdate", "failed to set data for groupId %lld",
2281 (long long)groupId
);
2284 /* else there is no data, so NULL is implicitly bound to column 4 */
2287 /* Execute the insert statement for the group record. */
2289 ok
= SecDbStep(dbconn
, insertGroup
, &localError
, NULL
);
2291 secdebug("validupdate", "failed to execute insertGroup statement for groupId %lld",
2292 (long long)groupId
);
2294 result
= (int64_t)sqlite3_last_insert_rowid(SecDbHandle(dbconn
));
2297 secdebug("validupdate", "failed to insert group %lld", (long long)result
);
2299 /* Clean up temporary allocation made in this block. */
2300 CFReleaseSafe(xmlData
);
2301 CFReleaseSafe(data
);
2306 if (!ok
|| localError
) {
2307 secerror("_SecRevocationDbUpdateGroup failed: %@", localError
);
2308 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationWrite
, TAFatalError
,
2309 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
2311 (void) CFErrorPropagate(localError
, error
);
2315 static int64_t _SecRevocationDbGroupIdForIssuerHash(SecRevocationDbRef rdb
, CFDataRef hash
, CFErrorRef
*error
) {
2316 /* look up issuer hash in issuers table to get groupid, if it exists */
2317 __block
int64_t groupId
= -1;
2318 __block
bool ok
= true;
2319 __block CFErrorRef localError
= NULL
;
2322 secdebug("validupdate", "failed to get hash (%@)", hash
);
2324 require(hash
, errOut
);
2326 /* This is the starting point for any lookup; find a group id for the given issuer hash.
2327 Before we do that, need to verify the current db_version. We cannot use results from a
2328 database created with a schema version older than the minimum supported version.
2329 However, we may be able to use results from a newer version. At the next database
2330 update interval, if the existing schema is old, we'll be removing and recreating
2331 the database contents with the current schema version.
2333 int64_t db_version
= _SecRevocationDbGetSchemaVersion(rdb
, NULL
);
2334 if (db_version
< kSecRevocationDbMinSchemaVersion
) {
2335 if (!rdb
->unsupportedVersion
) {
2336 secdebug("validupdate", "unsupported db_version: %lld", (long long)db_version
);
2337 rdb
->unsupportedVersion
= true; /* only warn once for a given unsupported version */
2340 require_quiet(db_version
>= kSecRevocationDbMinSchemaVersion
, errOut
);
2342 /* Look up provided issuer_hash in the issuers table.
2344 ok
&= SecDbPerformRead(rdb
->db
, &localError
, ^(SecDbConnectionRef dbconn
) {
2345 ok
&= SecDbWithSQL(dbconn
, selectGroupIdSQL
, &localError
, ^bool(sqlite3_stmt
*selectGroupId
) {
2346 ok
&= SecDbBindBlob(selectGroupId
, 1, CFDataGetBytePtr(hash
), CFDataGetLength(hash
), SQLITE_TRANSIENT
, &localError
);
2347 ok
&= SecDbStep(dbconn
, selectGroupId
, &localError
, ^(bool *stopGroupId
) {
2348 groupId
= sqlite3_column_int64(selectGroupId
, 0);
2355 if (!ok
|| localError
) {
2356 secerror("_SecRevocationDbGroupIdForIssuerHash failed: %@", localError
);
2357 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationRead
, TAFatalError
,
2358 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
2360 (void) CFErrorPropagate(localError
, error
);
2364 static bool _SecRevocationDbApplyGroupDelete(SecRevocationDbRef rdb
, CFDataRef issuerHash
, CFErrorRef
*error
) {
2365 /* delete group associated with the given issuer;
2366 schema trigger will delete associated issuers, serials, and hashes. */
2367 __block
int64_t groupId
= -1;
2368 __block
bool ok
= true;
2369 __block CFErrorRef localError
= NULL
;
2371 groupId
= _SecRevocationDbGroupIdForIssuerHash(rdb
, issuerHash
, &localError
);
2372 require(!(groupId
< 0), errOut
);
2374 ok
&= SecDbPerformWrite(rdb
->db
, &localError
, ^(SecDbConnectionRef dbconn
) {
2375 ok
&= SecDbTransaction(dbconn
, kSecDbExclusiveTransactionType
, &localError
, ^(bool *commit
) {
2376 ok
&= SecDbWithSQL(dbconn
, deleteGroupRecordSQL
, &localError
, ^bool(sqlite3_stmt
*deleteResponse
) {
2377 ok
&= SecDbBindInt64(deleteResponse
, 1, groupId
, &localError
);
2378 /* Execute the delete statement. */
2379 ok
= ok
&& SecDbStep(dbconn
, deleteResponse
, &localError
, NULL
);
2386 if (!ok
|| localError
) {
2387 secerror("_SecRevocationDbApplyGroupDelete failed: %@", localError
);
2388 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationWrite
, TAFatalError
,
2389 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
2391 (void) CFErrorPropagate(localError
, error
);
2392 return (groupId
< 0) ? false : true;
2395 static bool _SecRevocationDbApplyGroupUpdate(SecRevocationDbRef rdb
, CFDictionaryRef dict
, CFErrorRef
*error
) {
2396 /* process one issuer group's update dictionary */
2397 int64_t groupId
= -1;
2398 CFErrorRef localError
= NULL
;
2400 CFArrayRef issuers
= (dict
) ? (CFArrayRef
)CFDictionaryGetValue(dict
, CFSTR("issuer-hash")) : NULL
;
2401 if (isArray(issuers
)) {
2402 CFIndex issuerIX
, issuerCount
= CFArrayGetCount(issuers
);
2403 /* while we have issuers and haven't found a matching group id */
2404 for (issuerIX
=0; issuerIX
<issuerCount
&& groupId
< 0; issuerIX
++) {
2405 CFDataRef hash
= (CFDataRef
)CFArrayGetValueAtIndex(issuers
, issuerIX
);
2406 if (!hash
) { continue; }
2407 groupId
= _SecRevocationDbGroupIdForIssuerHash(rdb
, hash
, &localError
);
2410 /* create or update the group entry */
2411 groupId
= _SecRevocationDbUpdateGroup(rdb
, groupId
, dict
, &localError
);
2413 secdebug("validupdate", "failed to get groupId");
2415 /* create or update issuer entries, now that we know the group id */
2416 _SecRevocationDbUpdateIssuers(rdb
, groupId
, issuers
, &localError
);
2417 /* create or update entries in serials or hashes tables */
2418 _SecRevocationDbUpdateIssuerData(rdb
, groupId
, dict
, &localError
);
2419 /* create or update entries in dates/names/policies tables */
2420 _SecRevocationDbUpdateIssuerConstraints(rdb
, groupId
, dict
, &localError
);
2423 (void) CFErrorPropagate(localError
, error
);
2424 return (groupId
> 0) ? true : false;
2427 static void _SecRevocationDbApplyUpdate(SecRevocationDbRef rdb
, CFDictionaryRef update
, CFIndex version
) {
2428 /* process entire update dictionary */
2429 if (!rdb
|| !update
) {
2430 secerror("_SecRevocationDbApplyUpdate failed: invalid args");
2434 __block CFDictionaryRef localUpdate
= (CFDictionaryRef
)CFRetainSafe(update
);
2435 __block CFErrorRef localError
= NULL
;
2437 CFTypeRef value
= NULL
;
2438 CFIndex deleteCount
= 0;
2439 CFIndex updateCount
= 0;
2441 rdb
->updateInProgress
= true;
2443 /* check whether this is a full update */
2444 value
= (CFBooleanRef
)CFDictionaryGetValue(update
, CFSTR("full"));
2445 if (isBoolean(value
) && CFBooleanGetValue((CFBooleanRef
)value
)) {
2446 /* clear the database before processing a full update */
2447 SecRevocationDbRemoveAllEntries();
2450 /* process 'delete' list */
2451 value
= (CFArrayRef
)CFDictionaryGetValue(localUpdate
, CFSTR("delete"));
2452 if (isArray(value
)) {
2453 deleteCount
= CFArrayGetCount((CFArrayRef
)value
);
2454 secdebug("validupdate", "processing %ld deletes", (long)deleteCount
);
2455 for (CFIndex deleteIX
=0; deleteIX
<deleteCount
; deleteIX
++) {
2456 CFDataRef issuerHash
= (CFDataRef
)CFArrayGetValueAtIndex((CFArrayRef
)value
, deleteIX
);
2457 if (isData(issuerHash
)) {
2458 (void)_SecRevocationDbApplyGroupDelete(rdb
, issuerHash
, &localError
);
2459 CFReleaseNull(localError
);
2461 secdebug("validupdate", "skipping delete %ld (hash is not a data value)", (long)deleteIX
);
2466 /* process 'update' list */
2467 value
= (CFArrayRef
)CFDictionaryGetValue(localUpdate
, CFSTR("update"));
2468 if (isArray(value
)) {
2469 updateCount
= CFArrayGetCount((CFArrayRef
)value
);
2470 secdebug("validupdate", "processing %ld updates", (long)updateCount
);
2471 for (CFIndex updateIX
=0; updateIX
<updateCount
; updateIX
++) {
2472 CFDictionaryRef dict
= (CFDictionaryRef
)CFArrayGetValueAtIndex((CFArrayRef
)value
, updateIX
);
2473 if (isDictionary(dict
)) {
2474 (void)_SecRevocationDbApplyGroupUpdate(rdb
, dict
, &localError
);
2475 CFReleaseNull(localError
);
2477 secdebug("validupdate", "skipping update %ld (not a dictionary)", (long)updateIX
);
2481 CFRelease(localUpdate
);
2484 _SecRevocationDbSetVersion(rdb
, version
);
2486 /* set db_version if not already set */
2487 int64_t db_version
= _SecRevocationDbGetSchemaVersion(rdb
, NULL
);
2488 if (db_version
<= 0) {
2489 _SecRevocationDbSetSchemaVersion(rdb
, kSecRevocationDbSchemaVersion
);
2492 /* set db_format if not already set */
2493 int64_t db_format
= _SecRevocationDbGetUpdateFormat(rdb
, NULL
);
2494 if (db_format
<= 0) {
2495 _SecRevocationDbSetUpdateFormat(rdb
, kSecRevocationDbUpdateFormat
);
2498 /* compact the db (must be done outside transaction scope) */
2499 (void)SecDbPerformWrite(rdb
->db
, &localError
, ^(SecDbConnectionRef dbconn
) {
2500 SecDbExec(dbconn
, CFSTR("VACUUM"), &localError
);
2501 CFReleaseNull(localError
);
2504 rdb
->updateInProgress
= false;
2507 static bool _SecRevocationDbSerialInGroup(SecRevocationDbRef rdb
,
2510 CFErrorRef
*error
) {
2511 __block
bool result
= false;
2512 __block
bool ok
= true;
2513 __block CFErrorRef localError
= NULL
;
2514 require(rdb
&& serial
, errOut
);
2515 ok
&= SecDbPerformRead(rdb
->db
, &localError
, ^(SecDbConnectionRef dbconn
) {
2516 ok
&= SecDbWithSQL(dbconn
, selectSerialRecordSQL
, &localError
, ^bool(sqlite3_stmt
*selectSerial
) {
2517 ok
&= SecDbBindInt64(selectSerial
, 1, groupId
, &localError
);
2518 ok
&= SecDbBindBlob(selectSerial
, 2, CFDataGetBytePtr(serial
),
2519 CFDataGetLength(serial
), SQLITE_TRANSIENT
, &localError
);
2520 ok
&= SecDbStep(dbconn
, selectSerial
, &localError
, ^(bool *stop
) {
2521 int64_t foundRowId
= (int64_t)sqlite3_column_int64(selectSerial
, 0);
2522 result
= (foundRowId
> 0);
2529 if (!ok
|| localError
) {
2530 secerror("_SecRevocationDbSerialInGroup failed: %@", localError
);
2531 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationRead
, TAFatalError
,
2532 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
2534 (void) CFErrorPropagate(localError
, error
);
2538 static bool _SecRevocationDbCertHashInGroup(SecRevocationDbRef rdb
,
2541 CFErrorRef
*error
) {
2542 __block
bool result
= false;
2543 __block
bool ok
= true;
2544 __block CFErrorRef localError
= NULL
;
2545 require(rdb
&& certHash
, errOut
);
2546 ok
&= SecDbPerformRead(rdb
->db
, &localError
, ^(SecDbConnectionRef dbconn
) {
2547 ok
&= SecDbWithSQL(dbconn
, selectHashRecordSQL
, &localError
, ^bool(sqlite3_stmt
*selectHash
) {
2548 ok
&= SecDbBindInt64(selectHash
, 1, groupId
, &localError
);
2549 ok
= SecDbBindBlob(selectHash
, 2, CFDataGetBytePtr(certHash
),
2550 CFDataGetLength(certHash
), SQLITE_TRANSIENT
, &localError
);
2551 ok
&= SecDbStep(dbconn
, selectHash
, &localError
, ^(bool *stop
) {
2552 int64_t foundRowId
= (int64_t)sqlite3_column_int64(selectHash
, 0);
2553 result
= (foundRowId
> 0);
2560 if (!ok
|| localError
) {
2561 secerror("_SecRevocationDbCertHashInGroup failed: %@", localError
);
2562 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationRead
, TAFatalError
,
2563 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
2565 (void) CFErrorPropagate(localError
, error
);
2569 static bool _SecRevocationDbSerialInFilter(SecRevocationDbRef rdb
,
2570 CFDataRef serialData
,
2571 CFDataRef xmlData
) {
2572 /* N-To-1 filter implementation.
2573 The 'xmlData' parameter is a flattened XML dictionary,
2574 containing 'xor' and 'params' keys. First order of
2575 business is to reconstitute the blob into components.
2577 bool result
= false;
2578 CFRetainSafe(xmlData
);
2579 CFDataRef propListData
= xmlData
;
2580 /* Expand data blob if needed */
2581 CFDataRef inflatedData
= copyInflatedData(propListData
);
2583 CFReleaseSafe(propListData
);
2584 propListData
= inflatedData
;
2586 CFDataRef
xor = NULL
;
2587 CFArrayRef params
= NULL
;
2588 CFPropertyListRef nto1
= CFPropertyListCreateWithData(kCFAllocatorDefault
, propListData
, 0, NULL
, NULL
);
2590 xor = (CFDataRef
)CFDictionaryGetValue((CFDictionaryRef
)nto1
, CFSTR("xor"));
2591 params
= (CFArrayRef
)CFDictionaryGetValue((CFDictionaryRef
)nto1
, CFSTR("params"));
2593 uint8_t *hash
= (xor) ? (uint8_t*)CFDataGetBytePtr(xor) : NULL
;
2594 CFIndex hashLen
= (hash
) ? CFDataGetLength(xor) : 0;
2595 uint8_t *serial
= (serialData
) ? (uint8_t*)CFDataGetBytePtr(serialData
) : NULL
;
2596 CFIndex serialLen
= (serial
) ? CFDataGetLength(serialData
) : 0;
2598 require(hash
&& serial
&& params
, errOut
);
2600 const uint32_t FNV_OFFSET_BASIS
= 2166136261;
2601 const uint32_t FNV_PRIME
= 16777619;
2602 bool notInHash
= false;
2603 CFIndex ix
, count
= CFArrayGetCount(params
);
2604 for (ix
= 0; ix
< count
; ix
++) {
2606 CFNumberRef cfnum
= (CFNumberRef
)CFArrayGetValueAtIndex(params
, ix
);
2607 if (!isNumber(cfnum
) ||
2608 !CFNumberGetValue(cfnum
, kCFNumberSInt32Type
, ¶m
)) {
2609 secinfo("validupdate", "error processing filter params at index %ld", (long)ix
);
2612 /* process one param */
2613 uint32_t hval
= FNV_OFFSET_BASIS
^ param
;
2614 CFIndex i
= serialLen
;
2616 hval
= ((hval
^ (serial
[--i
])) * FNV_PRIME
) & 0xFFFFFFFF;
2618 hval
= hval
% (hashLen
* 8);
2619 if ((hash
[hval
/8] & (1 << (hval
% 8))) == 0) {
2620 notInHash
= true; /* definitely not in hash */
2625 /* probabilistically might be in hash if we get here. */
2630 CFReleaseSafe(nto1
);
2631 CFReleaseSafe(propListData
);
2635 static SecValidInfoRef
_SecRevocationDbValidInfoForCertificate(SecRevocationDbRef rdb
,
2636 SecCertificateRef certificate
,
2637 CFDataRef issuerHash
,
2638 CFErrorRef
*error
) {
2639 __block CFErrorRef localError
= NULL
;
2640 __block SecValidInfoFlags flags
= 0;
2641 __block SecValidInfoFormat format
= kSecValidInfoFormatUnknown
;
2642 __block CFDataRef data
= NULL
;
2644 bool matched
= false;
2645 bool isOnList
= false;
2646 int64_t groupId
= 0;
2647 CFDataRef serial
= NULL
;
2648 CFDataRef certHash
= NULL
;
2649 CFDateRef notBeforeDate
= NULL
;
2650 CFDateRef notAfterDate
= NULL
;
2651 CFDataRef nameConstraints
= NULL
;
2652 CFDataRef policyConstraints
= NULL
;
2653 SecValidInfoRef result
= NULL
;
2655 require((serial
= SecCertificateCopySerialNumberData(certificate
, NULL
)) != NULL
, errOut
);
2656 require((certHash
= SecCertificateCopySHA256Digest(certificate
)) != NULL
, errOut
);
2657 require((groupId
= _SecRevocationDbGroupIdForIssuerHash(rdb
, issuerHash
, &localError
)) > 0, errOut
);
2659 /* Look up the group record to determine flags and format. */
2660 format
= _SecRevocationDbGetGroupFormat(rdb
, groupId
, &flags
, &data
, &localError
);
2662 if (format
== kSecValidInfoFormatUnknown
) {
2663 /* No group record found for this issuer. Don't return a SecValidInfoRef */
2666 else if (format
== kSecValidInfoFormatSerial
) {
2667 /* Look up certificate's serial number in the serials table. */
2668 matched
= _SecRevocationDbSerialInGroup(rdb
, serial
, groupId
, &localError
);
2670 else if (format
== kSecValidInfoFormatSHA256
) {
2671 /* Look up certificate's SHA-256 hash in the hashes table. */
2672 matched
= _SecRevocationDbCertHashInGroup(rdb
, certHash
, groupId
, &localError
);
2674 else if (format
== kSecValidInfoFormatNto1
) {
2675 /* Perform a Bloom filter match against the serial. If matched is false,
2676 then the cert is definitely not in the list. But if matched is true,
2677 we don't know for certain, so we would need to check OCSP. */
2678 matched
= _SecRevocationDbSerialInFilter(rdb
, serial
, data
);
2682 /* Found a specific match for this certificate. */
2683 secdebug("validupdate", "Valid db matched certificate: %@, format=%d, flags=0x%lx",
2684 certHash
, format
, flags
);
2688 /* If supplemental constraints are present for this issuer, then we always match. */
2689 if ((flags
& kSecValidInfoDateConstraints
) &&
2690 (_SecRevocationDbCopyDateConstraints(rdb
, groupId
, ¬BeforeDate
, ¬AfterDate
, &localError
))) {
2691 secdebug("validupdate", "Valid db matched supplemental date constraints for groupId %lld: nb=%@, na=%@",
2692 (long long)groupId
, notBeforeDate
, notAfterDate
);
2696 /* Return SecValidInfo for certificates for which an issuer entry is found. */
2697 result
= SecValidInfoCreate(format
, flags
, isOnList
,
2698 certHash
, issuerHash
, /*anchorHash*/ NULL
,
2699 notBeforeDate
, notAfterDate
,
2700 nameConstraints
, policyConstraints
);
2702 if (result
&& SecIsAppleTrustAnchor(certificate
, 0)) {
2703 /* Prevent a catch-22. */
2704 secdebug("validupdate", "Valid db match for Apple trust anchor: %@, format=%d, flags=0x%lx",
2705 certHash
, format
, flags
);
2706 SecValidInfoRelease(result
);
2711 (void) CFErrorPropagate(localError
, error
);
2712 CFReleaseSafe(data
);
2713 CFReleaseSafe(certHash
);
2714 CFReleaseSafe(serial
);
2715 CFReleaseSafe(notBeforeDate
);
2716 CFReleaseSafe(notAfterDate
);
2717 CFReleaseSafe(nameConstraints
);
2718 CFReleaseSafe(policyConstraints
);
2722 static SecValidInfoRef
_SecRevocationDbCopyMatching(SecRevocationDbRef db
,
2723 SecCertificateRef certificate
,
2724 SecCertificateRef issuer
) {
2725 SecValidInfoRef result
= NULL
;
2726 CFErrorRef error
= NULL
;
2727 CFDataRef issuerHash
= NULL
;
2729 require(certificate
&& issuer
, errOut
);
2730 require(issuerHash
= SecCertificateCopySHA256Digest(issuer
), errOut
);
2732 result
= _SecRevocationDbValidInfoForCertificate(db
, certificate
, issuerHash
, &error
);
2735 CFReleaseSafe(issuerHash
);
2736 CFReleaseSafe(error
);
2740 static dispatch_queue_t
_SecRevocationDbGetUpdateQueue(SecRevocationDbRef rdb
) {
2741 return (rdb
) ? rdb
->update_queue
: NULL
;
2745 /* Given a valid update dictionary, insert/replace or delete records
2746 in the revocation database. (This function is expected to be called only
2747 by the database maintainer, normally the system instance of trustd.)
2749 void SecRevocationDbApplyUpdate(CFDictionaryRef update
, CFIndex version
) {
2750 SecRevocationDbWith(^(SecRevocationDbRef db
) {
2751 _SecRevocationDbApplyUpdate(db
, update
, version
);
2755 /* Update the database schema, insert missing tables and replace triggers.
2756 (This function is expected to be called only by the database maintainer,
2757 normally the system instance of trustd.)
2759 bool SecRevocationDbUpdateSchema(void) {
2760 __block
bool result
= false;
2761 SecRevocationDbWith(^(SecRevocationDbRef db
) {
2762 result
= _SecRevocationDbUpdateSchema(db
);
2767 /* Set the schema version for the revocation database.
2768 (This function is expected to be called only by the database maintainer,
2769 normally the system instance of trustd.)
2771 bool SecRevocationDbSetSchemaVersion(CFIndex db_version
) {
2772 __block
bool result
= false;
2773 SecRevocationDbWith(^(SecRevocationDbRef db
) {
2774 result
= _SecRevocationDbSetSchemaVersion(db
, db_version
);
2779 /* Set the current version for the revocation database.
2780 (This function is expected to be called only by the database maintainer,
2781 normally the system instance of trustd.)
2783 bool SecRevocationDbSetVersion(CFIndex version
) {
2784 __block
bool result
= false;
2785 SecRevocationDbWith(^(SecRevocationDbRef db
) {
2786 _SecRevocationDbSetVersion(db
, version
);
2791 /* Set the update format for the revocation database.
2792 (This function is expected to be called only by the database maintainer,
2793 normally the system instance of trustd.)
2795 void SecRevocationDbSetUpdateFormat(CFIndex db_format
) {
2796 SecRevocationDbWith(^(SecRevocationDbRef db
) {
2797 _SecRevocationDbSetUpdateFormat(db
, db_format
);
2801 /* Set the update source for the revocation database.
2802 (This function is expected to be called only by the database
2803 maintainer, normally the system instance of trustd. If the
2804 caller does not have write access, this is a no-op.)
2806 void SecRevocationDbSetUpdateSource(CFStringRef updateSource
) {
2807 SecRevocationDbWith(^(SecRevocationDbRef db
) {
2808 _SecRevocationDbSetUpdateSource(db
, updateSource
);
2812 /* Return the update source as a retained CFStringRef.
2813 If the value cannot be obtained, NULL is returned.
2815 CFStringRef
SecRevocationDbCopyUpdateSource(void) {
2816 __block CFStringRef result
= NULL
;
2817 SecRevocationDbWith(^(SecRevocationDbRef db
) {
2818 result
= _SecRevocationDbCopyUpdateSource(db
, NULL
);
2823 /* Set the next update value for the revocation database.
2824 (This function is expected to be called only by the database
2825 maintainer, normally the system instance of trustd. If the
2826 caller does not have write access, this is a no-op.)
2828 void SecRevocationDbSetNextUpdateTime(CFAbsoluteTime nextUpdate
) {
2829 SecRevocationDbWith(^(SecRevocationDbRef db
) {
2830 _SecRevocationDbSetNextUpdateTime(db
, nextUpdate
);
2834 /* Return the next update value as a CFAbsoluteTime.
2835 If the value cannot be obtained, -1 is returned.
2837 CFAbsoluteTime
SecRevocationDbGetNextUpdateTime(void) {
2838 __block CFAbsoluteTime result
= -1;
2839 SecRevocationDbWith(^(SecRevocationDbRef db
) {
2840 result
= _SecRevocationDbGetNextUpdateTime(db
, NULL
);
2845 /* Return the serial background queue for database updates.
2846 If the queue cannot be obtained, NULL is returned.
2848 dispatch_queue_t
SecRevocationDbGetUpdateQueue(void) {
2849 __block dispatch_queue_t result
= NULL
;
2850 SecRevocationDbWith(^(SecRevocationDbRef db
) {
2851 result
= _SecRevocationDbGetUpdateQueue(db
);
2856 /* Remove all entries in the revocation database and reset its version to 0.
2857 (This function is expected to be called only by the database maintainer,
2858 normally the system instance of trustd.)
2860 void SecRevocationDbRemoveAllEntries(void) {
2861 SecRevocationDbWith(^(SecRevocationDbRef db
) {
2862 _SecRevocationDbRemoveAllEntries(db
);
2866 /* Release all connections to the revocation database.
2868 void SecRevocationDbReleaseAllConnections(void) {
2869 SecRevocationDbWith(^(SecRevocationDbRef db
) {
2870 SecDbReleaseAllConnections((db
) ? db
->db
: NULL
);
2874 /* === SecRevocationDb API === */
2876 /* Given a certificate and its issuer, returns a SecValidInfoRef if the
2877 valid database contains matching info; otherwise returns NULL.
2878 Caller must release the returned SecValidInfoRef by calling
2879 SecValidInfoRelease when finished.
2881 SecValidInfoRef
SecRevocationDbCopyMatching(SecCertificateRef certificate
,
2882 SecCertificateRef issuer
) {
2883 __block SecValidInfoRef result
= NULL
;
2884 SecRevocationDbWith(^(SecRevocationDbRef db
) {
2885 result
= _SecRevocationDbCopyMatching(db
, certificate
, issuer
);
2890 /* Return the current version of the revocation database.
2891 A version of 0 indicates an empty database which must be populated.
2892 If the version cannot be obtained, -1 is returned.
2894 CFIndex
SecRevocationDbGetVersion(void) {
2895 __block CFIndex result
= -1;
2896 SecRevocationDbWith(^(SecRevocationDbRef db
) {
2897 result
= (CFIndex
)_SecRevocationDbGetVersion(db
, NULL
);
2902 /* Return the current schema version of the revocation database.
2903 A version of 0 indicates an empty database which must be populated.
2904 If the schema version cannot be obtained, -1 is returned.
2906 CFIndex
SecRevocationDbGetSchemaVersion(void) {
2907 __block CFIndex result
= -1;
2908 SecRevocationDbWith(^(SecRevocationDbRef db
) {
2909 result
= (CFIndex
)_SecRevocationDbGetSchemaVersion(db
, NULL
);
2914 /* Return the current update format of the revocation database.
2915 A version of 0 indicates the format was unknown.
2916 If the update format cannot be obtained, -1 is returned.
2918 CFIndex
SecRevocationDbGetUpdateFormat(void) {
2919 __block CFIndex result
= -1;
2920 SecRevocationDbWith(^(SecRevocationDbRef db
) {
2921 result
= (CFIndex
)_SecRevocationDbGetUpdateFormat(db
, NULL
);