2 * Copyright (c) 2016-2017 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 <Security/SecCertificateInternal.h>
34 #include <Security/SecCMS.h>
35 #include <Security/CMSDecoder.h>
36 #include <Security/SecFramework.h>
37 #include <Security/SecInternal.h>
38 #include <Security/SecPolicyPriv.h>
39 #include <AssertMacros.h>
46 #include <dispatch/dispatch.h>
49 #include "utilities/debugging.h"
50 #include "utilities/sec_action.h"
51 #include "utilities/sqlutils.h"
52 #include "utilities/SecAppleAnchorPriv.h"
53 #include "utilities/iOSforOSX.h"
54 #include <utilities/SecCFError.h>
55 #include <utilities/SecCFRelease.h>
56 #include <utilities/SecCFWrappers.h>
57 #include <utilities/SecDb.h>
58 #include <utilities/SecFileLocations.h>
61 #include <malloc/malloc.h>
62 #include <xpc/activity.h>
63 #include <xpc/private.h>
64 #include <os/transaction_private.h>
66 #include <CFNetwork/CFHTTPMessage.h>
67 #include <CoreFoundation/CFURL.h>
68 #include <CoreFoundation/CFUtilities.h>
70 static CFStringRef kValidUpdateServer
= CFSTR("valid.apple.com");
72 static CFStringRef kSecPrefsDomain
= CFSTR("com.apple.security");
73 static CFStringRef kUpdateServerKey
= CFSTR("ValidUpdateServer");
74 static CFStringRef kUpdateEnabledKey
= CFSTR("ValidUpdateEnabled");
75 static CFStringRef kUpdateIntervalKey
= CFSTR("ValidUpdateInterval");
77 typedef CF_OPTIONS(CFOptionFlags
, SecValidInfoFlags
) {
78 kSecValidInfoComplete
= 1u << 0,
79 kSecValidInfoCheckOCSP
= 1u << 1,
80 kSecValidInfoKnownOnly
= 1u << 2,
81 kSecValidInfoRequireCT
= 1u << 3,
82 kSecValidInfoAllowlist
= 1u << 4,
83 kSecValidInfoNoCACheck
= 1u << 5
86 /* minimum update interval */
87 #define kSecMinUpdateInterval (60.0 * 5)
89 /* standard update interval */
90 #define kSecStdUpdateInterval (60.0 * 60)
92 /* maximum allowed interval */
93 #define kSecMaxUpdateInterval (60.0 * 60 * 24 * 7)
95 #define kSecRevocationBasePath "/Library/Keychains/crls"
96 #define kSecRevocationCurUpdateFile "update-current"
97 #define kSecRevocationDbFileName "valid.sqlite3"
98 #define kSecRevocationDbReplaceFile ".valid_replace"
100 /* database schema version
102 v2 = fix for group entry transitions
103 v3 = handle optional entries in update dictionaries
104 v4 = add db_format and db_source entries
106 Note: kSecRevocationDbMinSchemaVersion is the lowest version whose
107 results can be used. This allows revocation results to be obtained
108 from an existing db before the next update interval occurs, at which
109 time we'll update to the current version (kSecRevocationDbSchemaVersion).
111 #define kSecRevocationDbSchemaVersion 4 /* current version we support */
112 #define kSecRevocationDbMinSchemaVersion 3 /* minimum version we can use */
114 /* update file format
117 kSecValidUpdateFormatG1
= 1, /* initial version */
118 kSecValidUpdateFormatG2
= 2, /* signed content, single plist */
119 kSecValidUpdateFormatG3
= 3 /* signed content, multiple plists */
122 #define kSecRevocationDbUpdateFormat 3 /* current version we support */
123 #define kSecRevocationDbMinUpdateFormat 2 /* minimum version we can use */
125 bool SecRevocationDbVerifyUpdate(void *update
, CFIndex length
);
126 CFIndex
SecRevocationDbIngestUpdate(CFDictionaryRef update
, CFIndex chunkVersion
);
127 void SecRevocationDbApplyUpdate(CFDictionaryRef update
, CFIndex version
);
128 CFAbsoluteTime
SecRevocationDbComputeNextUpdateTime(CFIndex updateInterval
);
129 void SecRevocationDbSetSchemaVersion(CFIndex dbversion
);
130 CFIndex
SecRevocationDbGetUpdateFormat(void);
131 void SecRevocationDbSetUpdateFormat(CFIndex dbformat
);
132 void SecRevocationDbSetUpdateSource(CFStringRef source
);
133 CFStringRef
SecRevocationDbCopyUpdateSource(void);
134 void SecRevocationDbSetNextUpdateTime(CFAbsoluteTime nextUpdate
);
135 CFAbsoluteTime
SecRevocationDbGetNextUpdateTime(void);
136 dispatch_queue_t
SecRevocationDbGetUpdateQueue(void);
137 void SecRevocationDbRemoveAllEntries(void);
138 void SecRevocationDbReleaseAllConnections(void);
141 static CFDataRef
copyInflatedData(CFDataRef data
) {
146 memset(&zs
, 0, sizeof(zs
));
147 /* 32 is a magic value which enables automatic header detection
148 of gzip or zlib compressed data. */
149 if (inflateInit2(&zs
, 32+MAX_WBITS
) != Z_OK
) {
152 zs
.next_in
= (UInt8
*)(CFDataGetBytePtr(data
));
153 zs
.avail_in
= (uInt
)CFDataGetLength(data
);
155 CFMutableDataRef outData
= CFDataCreateMutable(NULL
, 0);
159 CFIndex buf_sz
= malloc_good_size(zs
.avail_in
? zs
.avail_in
: 1024 * 4);
160 unsigned char *buf
= malloc(buf_sz
);
163 zs
.next_out
= (Bytef
*)buf
;
164 zs
.avail_out
= (uInt
)buf_sz
;
165 rc
= inflate(&zs
, 0);
166 CFIndex outLen
= CFDataGetLength(outData
);
167 if (outLen
< (CFIndex
)zs
.total_out
) {
168 CFDataAppendBytes(outData
, (const UInt8
*)buf
, (CFIndex
)zs
.total_out
- outLen
);
170 } while (rc
== Z_OK
);
177 if (rc
!= Z_STREAM_END
) {
178 CFReleaseSafe(outData
);
181 return (CFDataRef
)outData
;
184 static CFDataRef
copyInflatedDataToFile(CFDataRef data
, char *fileName
) {
189 memset(&zs
, 0, sizeof(zs
));
190 /* 32 is a magic value which enables automatic header detection
191 of gzip or zlib compressed data. */
192 if (inflateInit2(&zs
, 32+MAX_WBITS
) != Z_OK
) {
195 zs
.next_in
= (UInt8
*)(CFDataGetBytePtr(data
));
196 zs
.avail_in
= (uInt
)CFDataGetLength(data
);
198 (void)remove(fileName
); /* We need an empty file to start */
201 fd
= open(fileName
, O_RDWR
| O_CREAT
| O_TRUNC
, 0644);
202 if (fd
< 0 || (off
= lseek(fd
, 0, SEEK_SET
)) < 0) {
203 secerror("unable to open %s (errno %d)", fileName
, errno
);
210 CFIndex buf_sz
= malloc_good_size(zs
.avail_in
? zs
.avail_in
: 1024 * 4);
211 unsigned char *buf
= malloc(buf_sz
);
214 zs
.next_out
= (Bytef
*)buf
;
215 zs
.avail_out
= (uInt
)buf_sz
;
216 rc
= inflate(&zs
, 0);
217 if (off
< (int64_t)zs
.total_out
) {
218 off
= write(fd
, buf
, (int64_t)zs
.total_out
- off
);
220 } while (rc
== Z_OK
);
228 if (rc
!= Z_STREAM_END
) {
229 (void)remove(fileName
);
233 /* Now return an mmapped version of that data */
234 CFDataRef outData
= NULL
;
235 if ((rc
= readValidFile(fileName
, &outData
)) != 0) {
236 secerror("unable to read and map %s (errno %d)", fileName
, rc
);
237 CFReleaseNull(outData
);
242 static CFDataRef
copyDeflatedData(CFDataRef data
) {
247 memset(&zs
, 0, sizeof(zs
));
248 if (deflateInit(&zs
, Z_BEST_COMPRESSION
) != Z_OK
) {
251 zs
.next_in
= (UInt8
*)(CFDataGetBytePtr(data
));
252 zs
.avail_in
= (uInt
)CFDataGetLength(data
);
254 CFMutableDataRef outData
= CFDataCreateMutable(NULL
, 0);
258 CFIndex buf_sz
= malloc_good_size(zs
.avail_in
? zs
.avail_in
: 1024 * 4);
259 unsigned char *buf
= malloc(buf_sz
);
260 int rc
= Z_BUF_ERROR
;
262 zs
.next_out
= (Bytef
*)buf
;
263 zs
.avail_out
= (uInt
)buf_sz
;
264 rc
= deflate(&zs
, Z_FINISH
);
266 if (rc
== Z_OK
|| rc
== Z_STREAM_END
) {
267 CFIndex buf_used
= buf_sz
- zs
.avail_out
;
268 CFDataAppendBytes(outData
, (const UInt8
*)buf
, buf_used
);
270 else if (rc
== Z_BUF_ERROR
) {
272 buf_sz
= malloc_good_size(buf_sz
* 2);
273 buf
= malloc(buf_sz
);
275 rc
= Z_OK
; /* try again with larger buffer */
278 } while (rc
== Z_OK
&& zs
.avail_in
);
285 if (rc
!= Z_STREAM_END
) {
286 CFReleaseSafe(outData
);
289 return (CFDataRef
)outData
;
292 /* Read file opens the file, mmaps it and then closes the file. */
293 int readValidFile(const char *fileName
,
294 CFDataRef
*bytes
) { // mmapped and returned -- must be munmapped!
296 const uint8_t *buf
= NULL
;
301 fd
= open(fileName
, O_RDONLY
);
302 if (fd
< 0) { return errno
; }
303 rtn
= fstat(fd
, &sb
);
304 if (rtn
) { goto errOut
; }
305 if (sb
.st_size
> (off_t
) ((UINT32_MAX
>> 1)-1)) {
309 size
= (size_t)sb
.st_size
;
311 buf
= mmap(NULL
, size
, PROT_READ
, MAP_PRIVATE
, fd
, 0);
312 if (!buf
|| buf
== MAP_FAILED
) {
314 secerror("unable to map %s (errno %d)", fileName
, rtn
);
318 *bytes
= CFDataCreateWithBytesNoCopy(NULL
, buf
, size
, kCFAllocatorNull
);
323 CFReleaseNull(*bytes
);
325 int unmap_err
= munmap((void *)buf
, size
);
326 if (unmap_err
!= 0) {
327 secerror("unable to unmap %ld bytes at %p (error %d)", (long)size
, buf
, rtn
);
334 static void unmapData(CFDataRef CF_CONSUMED data
) {
336 int rtn
= munmap((void *)CFDataGetBytePtr(data
), CFDataGetLength(data
));
338 secerror("unable to unmap %ld bytes at %p (error %d)", CFDataGetLength(data
), CFDataGetBytePtr(data
), rtn
);
345 static bool removeFileWithSuffix(const char *basepath
, const char *suffix
) {
348 asprintf(&path
, "%s%s", basepath
, suffix
);
350 if (remove(path
) == -1) {
352 if (error
== ENOENT
) {
353 result
= true; // not an error if the file did not exist
355 secnotice("validupdate", "remove (%s): %s", path
, strerror(error
));
365 static bool isDbOwner() {
366 #if TARGET_OS_EMBEDDED
367 if (getuid() == 64) // _securityd
379 // MARK: SecValidUpdate
381 /* ======================================================================
383 ======================================================================*/
385 CFAbsoluteTime gUpdateStarted
= 0.0;
386 CFAbsoluteTime gNextUpdate
= 0.0;
387 static CFIndex gUpdateInterval
= 0;
388 static CFIndex gLastVersion
= 0;
391 1. The length of the signed data, as a 4-byte integer in network byte order.
392 2. The signed data, which consists of:
393 a. A 4-byte integer in network byte order, the count of plists to follow; and then for each plist:
394 i. A 4-byte integer, the length of each plist
395 ii. A plist, in binary form
396 b. There may be other data after the plists in the signed data, described by a future version of this specification.
397 3. The length of the following CMS blob, as a 4-byte integer in network byte order.
398 4. A detached CMS signature of the signed data described above.
399 5. There may be additional data after the CMS blob, described by a future version of this specification.
401 Note: the difference between g2 and g3 format is the addition of the 4-byte count in (2a).
403 static bool SecValidUpdateProcessData(CFIndex format
, CFDataRef updateData
) {
404 if (!updateData
|| format
< 2) {
409 CFIndex interval
= 0;
410 const UInt8
* p
= CFDataGetBytePtr(updateData
);
411 CFIndex bytesRemaining
= (p
) ? CFDataGetLength(updateData
) : 0;
412 /* make sure there is enough data to contain length and count */
413 if (bytesRemaining
< ((CFIndex
)sizeof(uint32_t) * 2)) {
414 secinfo("validupdate", "Skipping property list creation (length %ld is too short)", (long)bytesRemaining
);
417 /* get length of signed data */
418 uint32_t dataLength
= OSSwapInt32(*((uint32_t *)p
));
419 bytesRemaining
-= sizeof(uint32_t);
420 p
+= sizeof(uint32_t);
422 /* get plist count (G3 format and later) */
423 uint32_t plistCount
= 1;
424 uint32_t plistTotal
= 1;
425 if (format
> kSecValidUpdateFormatG2
) {
426 plistCount
= OSSwapInt32(*((uint32_t *)p
));
427 plistTotal
= plistCount
;
428 bytesRemaining
-= sizeof(uint32_t);
429 p
+= sizeof(uint32_t);
431 if (dataLength
> bytesRemaining
) {
432 secinfo("validupdate", "Skipping property list creation (dataLength=%ld, bytesRemaining=%ld)",
433 (long)dataLength
, (long)bytesRemaining
);
437 /* process each chunked plist */
438 uint32_t plistProcessed
= 0;
439 while (plistCount
> 0 && bytesRemaining
> 0) {
440 CFPropertyListRef propertyList
= NULL
;
441 uint32_t plistLength
= dataLength
;
442 if (format
> kSecValidUpdateFormatG2
) {
443 plistLength
= OSSwapInt32(*((uint32_t *)p
));
444 bytesRemaining
-= sizeof(uint32_t);
445 p
+= sizeof(uint32_t);
450 /* We're about to use a lot of memory for the plist -- go active so we don't get jetsammed */
451 os_transaction_t transaction
;
452 transaction
= os_transaction_create("com.apple.trustd.valid");
454 if (plistLength
<= bytesRemaining
) {
455 CFDataRef data
= CFDataCreateWithBytesNoCopy(NULL
, p
, plistLength
, kCFAllocatorNull
);
456 propertyList
= CFPropertyListCreateWithData(NULL
, data
, kCFPropertyListImmutable
, NULL
, NULL
);
459 if (isDictionary(propertyList
)) {
460 secdebug("validupdate", "Ingesting plist chunk %u of %u, length: %u",
461 plistProcessed
, plistTotal
, plistLength
);
462 CFIndex curVersion
= SecRevocationDbIngestUpdate((CFDictionaryRef
)propertyList
, version
);
463 if (plistProcessed
== 1) {
464 version
= curVersion
;
465 // get server-provided interval
466 CFTypeRef value
= (CFNumberRef
)CFDictionaryGetValue((CFDictionaryRef
)propertyList
,
467 CFSTR("check-again"));
468 if (isNumber(value
)) {
469 CFNumberGetValue((CFNumberRef
)value
, kCFNumberCFIndexType
, &interval
);
472 if (curVersion
< 0) {
473 plistCount
= 0; // we already had this version; skip remaining plists
477 secinfo("validupdate", "Failed to deserialize update chunk %u of %u",
478 plistProcessed
, plistTotal
);
479 if (plistProcessed
== 1) {
480 gNextUpdate
= SecRevocationDbComputeNextUpdateTime(0);
483 /* All finished with this property list */
484 CFReleaseSafe(propertyList
);
485 os_release(transaction
);
487 bytesRemaining
-= plistLength
;
492 secdebug("validupdate", "Update received: v%lu", (unsigned long)version
);
493 gLastVersion
= version
;
494 gNextUpdate
= SecRevocationDbComputeNextUpdateTime(interval
);
495 secdebug("validupdate", "Next update time: %f", gNextUpdate
);
499 // remember next update time in case of restart
500 SecRevocationDbSetNextUpdateTime(gNextUpdate
);
505 void SecValidUpdateVerifyAndIngest(CFDataRef updateData
) {
507 secnotice("validupdate", "invalid update data");
510 /* Verify CMS signature on signed data */
511 if (SecRevocationDbVerifyUpdate((void *)CFDataGetBytePtr(updateData
), CFDataGetLength(updateData
))) {
512 bool result
= SecValidUpdateProcessData(kSecValidUpdateFormatG3
, updateData
);
514 // Try g2 update format as a fallback if we failed to read g3
515 result
= SecValidUpdateProcessData(kSecValidUpdateFormatG2
, updateData
);
518 secerror("failed to process valid update");
521 secerror("failed to verify valid update");
525 static bool SecValidUpdateFromCompressed(CFDataRef CF_CONSUMED data
) {
526 if (!data
) { return false; }
528 /* We're about to use a lot of memory for the uncompressed update -- go active */
529 os_transaction_t transaction
;
530 transaction
= os_transaction_create("com.apple.trustd.valid");
532 /* Expand the update */
533 __block CFDataRef inflatedData
= NULL
;
534 WithPathInRevocationInfoDirectory(CFSTR(kSecRevocationCurUpdateFile
), ^(const char *curUpdatePath
) {
535 inflatedData
= copyInflatedDataToFile(data
, (char *)curUpdatePath
);
536 secdebug("validupdate", "data expanded: %ld bytes", (long)CFDataGetLength(inflatedData
));
539 os_release(transaction
);
542 SecValidUpdateVerifyAndIngest(inflatedData
);
543 unmapData(inflatedData
);
546 /* All done with the temporary file */
547 WithPathInRevocationInfoDirectory(CFSTR(kSecRevocationCurUpdateFile
), ^(const char *curUpdatePath
) {
548 (void)removeFileWithSuffix(curUpdatePath
, "");
554 static bool SecValidDatabaseFromCompressed(CFDataRef CF_CONSUMED data
) {
555 if (!data
) { return false; }
557 secdebug("validupdate", "read %ld bytes from file", (long)CFDataGetLength(data
));
559 /* We're about to use a lot of memory for the uncompressed update -- go active */
560 os_transaction_t transaction
;
561 transaction
= os_transaction_create("com.apple.trustd.valid");
563 /* Expand the database */
564 __block CFDataRef inflatedData
= NULL
;
565 WithPathInRevocationInfoDirectory(CFSTR(kSecRevocationDbFileName
), ^(const char *dbPath
) {
566 inflatedData
= copyInflatedDataToFile(data
, (char *)dbPath
);
567 secdebug("validupdate", "data expanded: %ld bytes", (long)CFDataGetLength(inflatedData
));
570 os_release(transaction
);
573 unmapData(inflatedData
);
578 static bool SecValidUpdateSatisfiedLocally(CFStringRef server
, CFIndex version
, bool safeToReplace
) {
579 __block
bool result
= false;
580 CFDataRef data
= NULL
;
581 SecOTAPKIRef otapkiRef
= NULL
;
583 static int sNumLocalUpdates
= 0;
585 // if we've replaced the database with a local asset twice in a row,
586 // something is wrong with it. Get this update from the server.
587 if (sNumLocalUpdates
> 1) {
588 secdebug("validupdate", "%d consecutive db resets, ignoring local asset", sNumLocalUpdates
);
592 // if a non-production server is specified, we will not be able to use a
593 // local production asset since its update sequence will be different.
594 if (kCFCompareEqualTo
!= CFStringCompare(server
, kValidUpdateServer
,
595 kCFCompareCaseInsensitive
)) {
596 secdebug("validupdate", "non-production server specified, ignoring local asset");
600 // check static database asset(s)
601 otapkiRef
= SecOTAPKICopyCurrentOTAPKIRef();
605 CFIndex assetVersion
= SecOTAPKIGetValidSnapshotVersion(otapkiRef
);
606 CFIndex assetFormat
= SecOTAPKIGetValidSnapshotFormat(otapkiRef
);
607 // version <= 0 means the database is invalid or empty.
608 // version > 0 means we have some version, but we need to see if a
609 // newer version is available as a local asset.
610 if (assetVersion
<= version
|| assetFormat
< kSecValidUpdateFormatG3
) {
611 // asset is not newer than ours, or its version is unknown
615 // replace database only if safe to do so (i.e. called at startup)
616 if (!safeToReplace
) {
617 // write semaphore file that we will pick up when we next launch
618 char *semPathBuf
= NULL
;
619 asprintf(&semPathBuf
, "%s/%s", kSecRevocationBasePath
, kSecRevocationDbReplaceFile
);
622 int fd
= open(semPathBuf
, O_WRONLY
| O_CREAT
, DEFFILEMODE
);
623 if (fd
== -1 || fstat(fd
, &sb
) || close(fd
)) {
624 secnotice("validupdate", "unable to write %s", semPathBuf
);
628 // exit as gracefully as possible so we can replace the database
629 secnotice("validupdate", "process exiting to replace db file");
630 dispatch_after(dispatch_time(DISPATCH_TIME_NOW
, 3ull*NSEC_PER_SEC
), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT
, 0), ^{
631 xpc_transaction_exit_clean();
636 // try to copy uncompressed database asset, if available
637 const char *validDbPathBuf
= SecOTAPKIGetValidDatabaseSnapshot(otapkiRef
);
638 if (validDbPathBuf
) {
639 WithPathInRevocationInfoDirectory(CFSTR(kSecRevocationDbFileName
), ^(const char *path
) {
640 secdebug("validupdate", "will copy data from \"%s\"", validDbPathBuf
);
641 copyfile_state_t state
= copyfile_state_alloc();
642 int retval
= copyfile(validDbPathBuf
, path
, state
, COPYFILE_DATA
);
643 copyfile_state_free(state
);
645 secnotice("validupdate", "copyfile error %d", retval
);
655 // see if compressed database asset is available
656 if (validDbPathBuf
) {
657 char *validDbCmpPathBuf
= NULL
;
658 asprintf(&validDbCmpPathBuf
, "%s%s", validDbPathBuf
, ".gz");
659 if (validDbCmpPathBuf
) {
660 secdebug("validupdate", "will read data from \"%s\"", validDbCmpPathBuf
);
661 if ((rtn
= readValidFile(validDbCmpPathBuf
, &data
)) != 0) {
664 secnotice("validupdate", "readValidFile error %d", rtn
);
666 free(validDbCmpPathBuf
);
669 result
= SecValidDatabaseFromCompressed(data
);
674 // unable to use database asset; try update asset
675 const char *validUpdatePathBuf
= SecOTAPKIGetValidUpdateSnapshot(otapkiRef
);
676 if (validUpdatePathBuf
) {
677 secdebug("validupdate", "will read data from \"%s\"", validUpdatePathBuf
);
678 if ((rtn
= readValidFile(validUpdatePathBuf
, &data
)) != 0) {
681 secnotice("validupdate", "readValidFile error %d", rtn
);
684 result
= SecValidUpdateFromCompressed(data
);
687 CFReleaseNull(otapkiRef
);
690 SecRevocationDbSetUpdateSource(server
);
691 gLastVersion
= SecRevocationDbGetVersion();
693 secdebug("validupdate", "local update to g%ld/v%ld complete at %f",
694 (long)SecRevocationDbGetUpdateFormat(), (long)gLastVersion
,
695 (double)CFAbsoluteTimeGetCurrent());
697 sNumLocalUpdates
= 0; // reset counter
702 static bool SecValidUpdateSchedule(bool updateEnabled
, CFStringRef server
, CFIndex version
) {
703 /* Check if we have a later version available locally */
704 if (SecValidUpdateSatisfiedLocally(server
, version
, false)) {
708 /* If update not permitted return */
709 if (!updateEnabled
) {
713 #if !TARGET_OS_BRIDGE
714 /* Schedule as a maintenance task */
715 return SecValidUpdateRequest(SecRevocationDbGetUpdateQueue(), server
, version
);
721 void SecRevocationDbInitialize() {
722 if (!isDbOwner()) { return; }
723 __block
bool initializeDb
= false;
725 /* create base path if it doesn't exist */
726 (void)mkpath_np(kSecRevocationBasePath
, 0755);
728 /* check semaphore file */
729 WithPathInRevocationInfoDirectory(CFSTR(kSecRevocationDbReplaceFile
), ^(const char *path
) {
731 if (stat(path
, &sb
) == 0) {
732 initializeDb
= true; /* file was found, so we will replace the database */
733 if (remove(path
) == -1) {
735 secnotice("validupdate", "remove (%s): %s", path
, strerror(error
));
741 WithPathInRevocationInfoDirectory(CFSTR(kSecRevocationDbFileName
), ^(const char *path
) {
743 /* remove old database file(s) */
744 (void)removeFileWithSuffix(path
, "");
745 (void)removeFileWithSuffix(path
, "-journal");
746 (void)removeFileWithSuffix(path
, "-shm");
747 (void)removeFileWithSuffix(path
, "-wal");
751 if (stat(path
, &sb
) == -1) {
752 initializeDb
= true; /* file not found, so we will create the database */
758 return; /* database exists and doesn't need replacing */
761 /* initialize database from local asset */
762 CFTypeRef value
= (CFStringRef
)CFPreferencesCopyValue(kUpdateServerKey
, kSecPrefsDomain
, kCFPreferencesAnyUser
, kCFPreferencesCurrentHost
);
763 CFStringRef server
= (isString(value
)) ? (CFStringRef
)value
: (CFStringRef
)kValidUpdateServer
;
765 secnotice("validupdate", "initializing database");
766 if (!SecValidUpdateSatisfiedLocally(server
, version
, true)) {
767 #if !TARGET_OS_BRIDGE
768 /* Schedule full update as a maintenance task */
769 (void)SecValidUpdateRequest(SecRevocationDbGetUpdateQueue(), server
, version
);
772 CFReleaseSafe(value
);
777 // MARK: SecValidInfoRef
779 /* ======================================================================
781 ======================================================================
784 static SecValidInfoRef
SecValidInfoCreate(SecValidInfoFormat format
,
788 CFDataRef issuerHash
,
789 CFDataRef anchorHash
) {
790 SecValidInfoRef validInfo
;
791 validInfo
= (SecValidInfoRef
)calloc(1, sizeof(struct __SecValidInfo
));
792 if (!validInfo
) { return NULL
; }
794 CFRetainSafe(certHash
);
795 CFRetainSafe(issuerHash
);
796 validInfo
->format
= format
;
797 validInfo
->certHash
= certHash
;
798 validInfo
->issuerHash
= issuerHash
;
799 validInfo
->anchorHash
= anchorHash
;
800 validInfo
->isOnList
= isOnList
;
801 validInfo
->valid
= (flags
& kSecValidInfoAllowlist
);
802 validInfo
->complete
= (flags
& kSecValidInfoComplete
);
803 validInfo
->checkOCSP
= (flags
& kSecValidInfoCheckOCSP
);
804 validInfo
->knownOnly
= (flags
& kSecValidInfoKnownOnly
);
805 validInfo
->requireCT
= (flags
& kSecValidInfoRequireCT
);
806 validInfo
->noCACheck
= (flags
& kSecValidInfoNoCACheck
);
811 void SecValidInfoRelease(SecValidInfoRef validInfo
) {
813 CFReleaseSafe(validInfo
->certHash
);
814 CFReleaseSafe(validInfo
->issuerHash
);
815 CFReleaseSafe(validInfo
->anchorHash
);
820 void SecValidInfoSetAnchor(SecValidInfoRef validInfo
, SecCertificateRef anchor
) {
824 CFDataRef anchorHash
= NULL
;
826 anchorHash
= SecCertificateCopySHA256Digest(anchor
);
828 /* clear no-ca flag for anchors where we want OCSP checked [32523118] */
829 if (SecIsAppleTrustAnchor(anchor
, 0)) {
830 validInfo
->noCACheck
= false;
833 CFReleaseNull(validInfo
->anchorHash
);
834 validInfo
->anchorHash
= anchorHash
;
839 // MARK: SecRevocationDb
841 /* ======================================================================
843 ======================================================================
846 /* SecRevocationDbCheckNextUpdate returns true if we dispatched an
847 update request, otherwise false.
849 static bool _SecRevocationDbCheckNextUpdate(void) {
850 // are we the db owner instance?
854 CFTypeRef value
= NULL
;
856 // is it time to check?
857 CFAbsoluteTime now
= CFAbsoluteTimeGetCurrent();
858 CFAbsoluteTime minNextUpdate
= now
+ gUpdateInterval
;
859 gUpdateStarted
= now
;
861 if (0 == gNextUpdate
) {
862 // first time we're called, check if we have a saved nextUpdate value
863 gNextUpdate
= SecRevocationDbGetNextUpdateTime();
865 if (gNextUpdate
< minNextUpdate
) {
866 gNextUpdate
= minNextUpdate
;
868 // allow pref to override update interval, if it exists
869 CFIndex interval
= -1;
870 value
= (CFNumberRef
)CFPreferencesCopyValue(kUpdateIntervalKey
, kSecPrefsDomain
, kCFPreferencesAnyUser
, kCFPreferencesCurrentHost
);
871 if (isNumber(value
)) {
872 if (CFNumberGetValue((CFNumberRef
)value
, kCFNumberCFIndexType
, &interval
)) {
873 if (interval
< kSecMinUpdateInterval
) {
874 interval
= kSecMinUpdateInterval
;
875 } else if (interval
> kSecMaxUpdateInterval
) {
876 interval
= kSecMaxUpdateInterval
;
880 CFReleaseNull(value
);
881 gUpdateInterval
= kSecStdUpdateInterval
;
883 gUpdateInterval
= interval
;
885 // pin next update time to the preferred update interval
886 if (gNextUpdate
> (gUpdateStarted
+ gUpdateInterval
)) {
887 gNextUpdate
= gUpdateStarted
+ gUpdateInterval
;
889 secdebug("validupdate", "next update at %f (in %f seconds)",
890 (double)gUpdateStarted
, (double)gNextUpdate
-gUpdateStarted
);
892 if (gNextUpdate
> now
) {
896 secnotice("validupdate", "starting update");
898 // set minimum next update time here in case we can't get an update
899 gNextUpdate
= minNextUpdate
;
901 // determine which server to query
903 value
= (CFStringRef
)CFPreferencesCopyValue(kUpdateServerKey
, kSecPrefsDomain
, kCFPreferencesAnyUser
, kCFPreferencesCurrentHost
);
904 if (isString(value
)) {
905 server
= (CFStringRef
) CFRetain(value
);
907 server
= (CFStringRef
) CFRetain(kValidUpdateServer
);
909 CFReleaseNull(value
);
911 // determine version of our current database
912 CFIndex version
= SecRevocationDbGetVersion();
913 secdebug("validupdate", "got version %ld from db", (long)version
);
915 if (gLastVersion
> 0) {
916 secdebug("validupdate", "error getting version; using last good version: %ld", (long)gLastVersion
);
918 version
= gLastVersion
;
921 // determine source of our current database
922 // (if this ever changes, we will need to reload the db)
923 CFStringRef db_source
= SecRevocationDbCopyUpdateSource();
925 db_source
= (CFStringRef
) CFRetain(kValidUpdateServer
);
928 // determine whether we need to recreate the database
929 CFIndex db_version
= SecRevocationDbGetSchemaVersion();
930 CFIndex db_format
= SecRevocationDbGetUpdateFormat();
931 if (db_version
< kSecRevocationDbSchemaVersion
||
932 db_format
< kSecRevocationDbUpdateFormat
||
933 kCFCompareEqualTo
!= CFStringCompare(server
, db_source
, kCFCompareCaseInsensitive
)) {
934 /* we need to fully rebuild the db contents. */
935 SecRevocationDbRemoveAllEntries();
936 version
= gLastVersion
= 0;
939 // determine whether update fetching is enabled
940 #if (__MAC_OS_X_VERSION_MIN_REQUIRED >= 101300 || __IPHONE_OS_VERSION_MIN_REQUIRED >= 110000)
941 bool updateEnabled
= true; // macOS 10.13 or iOS 11.0
943 bool updateEnabled
= false;
945 value
= (CFBooleanRef
)CFPreferencesCopyValue(kUpdateEnabledKey
, kSecPrefsDomain
, kCFPreferencesAnyUser
, kCFPreferencesCurrentHost
);
946 if (isBoolean(value
)) {
947 updateEnabled
= CFBooleanGetValue((CFBooleanRef
)value
);
949 CFReleaseNull(value
);
951 // Schedule maintenance work
952 bool result
= SecValidUpdateSchedule(updateEnabled
, server
, version
);
953 CFReleaseNull(server
);
954 CFReleaseNull(db_source
);
958 void SecRevocationDbCheckNextUpdate(void) {
959 static dispatch_once_t once
;
960 static sec_action_t action
;
962 dispatch_once(&once
, ^{
963 dispatch_queue_t update_queue
= SecRevocationDbGetUpdateQueue();
964 action
= sec_action_create_with_queue(update_queue
, "update_check", kSecMinUpdateInterval
);
965 sec_action_set_handler(action
, ^{
966 (void)_SecRevocationDbCheckNextUpdate();
969 sec_action_perform(action
);
972 /* This function verifies an update, in this format:
973 1) unsigned 32-bit network-byte-order length of binary plist
975 3) unsigned 32-bit network-byte-order length of CMS message
976 4) CMS message (containing certificates and signature over binary plist)
978 The length argument is the total size of the packed update data.
980 bool SecRevocationDbVerifyUpdate(void *update
, CFIndex length
) {
981 if (!update
|| length
<= (CFIndex
)sizeof(uint32_t)) {
984 uint32_t plistLength
= OSSwapInt32(*((uint32_t *)update
));
985 if ((plistLength
+ (CFIndex
)(sizeof(uint32_t)*2)) > (uint64_t) length
) {
986 secdebug("validupdate", "ERROR: reported plist length (%lu)+%lu exceeds total length (%lu)\n",
987 (unsigned long)plistLength
, (unsigned long)sizeof(uint32_t)*2, (unsigned long)length
);
990 uint8_t *plistData
= (uint8_t *)update
+ sizeof(uint32_t);
991 uint8_t *sigData
= (uint8_t *)plistData
+ plistLength
;
992 uint32_t sigLength
= OSSwapInt32(*((uint32_t *)sigData
));
993 sigData
+= sizeof(uint32_t);
994 if ((plistLength
+ sigLength
+ (CFIndex
)(sizeof(uint32_t) * 2)) != (uint64_t) length
) {
995 secdebug("validupdate", "ERROR: reported lengths do not add up to total length\n");
1000 CMSSignerStatus signerStatus
;
1001 CMSDecoderRef cms
= NULL
;
1002 SecPolicyRef policy
= NULL
;
1003 SecTrustRef trust
= NULL
;
1004 CFDataRef content
= NULL
;
1006 if ((content
= CFDataCreateWithBytesNoCopy(kCFAllocatorDefault
,
1007 (const UInt8
*)plistData
, (CFIndex
)plistLength
, kCFAllocatorNull
)) == NULL
) {
1008 secdebug("validupdate", "CFDataCreateWithBytesNoCopy failed (%ld bytes)\n", (long)plistLength
);
1012 if ((status
= CMSDecoderCreate(&cms
)) != errSecSuccess
) {
1013 secdebug("validupdate", "CMSDecoderCreate failed with error %d\n", (int)status
);
1016 if ((status
= CMSDecoderUpdateMessage(cms
, sigData
, sigLength
)) != errSecSuccess
) {
1017 secdebug("validupdate", "CMSDecoderUpdateMessage failed with error %d\n", (int)status
);
1020 if ((status
= CMSDecoderSetDetachedContent(cms
, content
)) != errSecSuccess
) {
1021 secdebug("validupdate", "CMSDecoderSetDetachedContent failed with error %d\n", (int)status
);
1024 if ((status
= CMSDecoderFinalizeMessage(cms
)) != errSecSuccess
) {
1025 secdebug("validupdate", "CMSDecoderFinalizeMessage failed with error %d\n", (int)status
);
1029 policy
= SecPolicyCreateApplePinned(CFSTR("ValidUpdate"), // kSecPolicyNameAppleValidUpdate
1030 CFSTR("1.2.840.113635.100.6.2.10"), // System Integration 2 Intermediate Certificate
1031 CFSTR("1.2.840.113635.100.6.51")); // Valid update signing OID
1033 // Check that the first signer actually signed this message.
1034 if ((status
= CMSDecoderCopySignerStatus(cms
, 0, policy
,
1035 false, &signerStatus
, &trust
, NULL
)) != errSecSuccess
) {
1036 secdebug("validupdate", "CMSDecoderCopySignerStatus failed with error %d\n", (int)status
);
1039 // Make sure the signature verifies against the detached content
1040 if (signerStatus
!= kCMSSignerValid
) {
1041 secdebug("validupdate", "ERROR: signature did not verify (signer status %d)\n", (int)signerStatus
);
1042 status
= errSecInvalidSignature
;
1045 // Make sure the signing certificate is valid for the specified policy
1046 SecTrustResultType trustResult
= kSecTrustResultInvalid
;
1047 status
= SecTrustEvaluate(trust
, &trustResult
);
1048 if (status
!= errSecSuccess
) {
1049 secdebug("validupdate", "SecTrustEvaluate failed with error %d (trust=%p)\n", (int)status
, (void *)trust
);
1050 } else if (!(trustResult
== kSecTrustResultUnspecified
|| trustResult
== kSecTrustResultProceed
)) {
1051 secdebug("validupdate", "SecTrustEvaluate failed with trust result %d\n", (int)trustResult
);
1052 status
= errSecVerificationFailure
;
1057 CFReleaseSafe(content
);
1058 CFReleaseSafe(trust
);
1059 CFReleaseSafe(policy
);
1062 return (status
== errSecSuccess
);
1065 CFAbsoluteTime
SecRevocationDbComputeNextUpdateTime(CFIndex updateInterval
) {
1066 CFIndex interval
= updateInterval
;
1067 // try to use interval preference if it exists
1068 CFTypeRef value
= (CFNumberRef
)CFPreferencesCopyValue(kUpdateIntervalKey
, kSecPrefsDomain
, kCFPreferencesAnyUser
, kCFPreferencesCurrentHost
);
1069 if (isNumber(value
)) {
1070 CFNumberGetValue((CFNumberRef
)value
, kCFNumberCFIndexType
, &interval
);
1072 CFReleaseNull(value
);
1074 if (interval
<= 0) {
1075 interval
= kSecStdUpdateInterval
;
1079 if (interval
< kSecMinUpdateInterval
) {
1080 interval
= kSecMinUpdateInterval
;
1081 } else if (interval
> kSecMaxUpdateInterval
) {
1082 interval
= kSecMaxUpdateInterval
;
1085 // compute randomization factor, between 0 and 50% of the interval
1086 CFIndex fuzz
= arc4random() % (long)(interval
/2.0);
1087 CFAbsoluteTime nextUpdate
= CFAbsoluteTimeGetCurrent() + interval
+ fuzz
;
1088 secdebug("validupdate", "next update in %ld seconds", (long)(interval
+ fuzz
));
1092 void SecRevocationDbComputeAndSetNextUpdateTime(void) {
1093 gNextUpdate
= SecRevocationDbComputeNextUpdateTime(0);
1094 SecRevocationDbSetNextUpdateTime(gNextUpdate
);
1095 gUpdateStarted
= 0; /* no update is currently in progress */
1098 CFIndex
SecRevocationDbIngestUpdate(CFDictionaryRef update
, CFIndex chunkVersion
) {
1099 CFIndex version
= 0;
1103 CFTypeRef value
= (CFNumberRef
)CFDictionaryGetValue(update
, CFSTR("version"));
1104 if (isNumber(value
)) {
1105 if (!CFNumberGetValue((CFNumberRef
)value
, kCFNumberCFIndexType
, &version
)) {
1110 // only the first chunk will have a version, so the second and
1111 // subsequent chunks will need to pass it in chunkVersion.
1112 version
= chunkVersion
;
1114 CFIndex curVersion
= SecRevocationDbGetVersion();
1115 if (version
> curVersion
|| chunkVersion
> 0) {
1116 SecRevocationDbApplyUpdate(update
, version
);
1118 secdebug("validupdate", "we have v%ld, skipping update to v%ld",
1119 (long)curVersion
, (long)version
);
1120 version
= -1; // invalid, so we know to skip subsequent chunks
1126 /* Database schema */
1128 /* admin table holds these key-value (or key-ival) pairs:
1129 'version' (integer) // version of database content
1130 'check_again' (double) // CFAbsoluteTime of next check (optional; this value is currently stored in prefs)
1131 'db_version' (integer) // version of database schema
1132 'db_hash' (blob) // SHA-256 database hash
1133 --> entries in admin table are unique by text key
1135 issuers table holds map of issuing CA hashes to group identifiers:
1136 groupid (integer) // associated group identifier in group ID table
1137 issuer_hash (blob) // SHA-256 hash of issuer certificate (primary key)
1138 --> entries in issuers table are unique by issuer_hash;
1139 multiple issuer entries may have the same groupid!
1141 groups table holds records with these attributes:
1142 groupid (integer) // ordinal ID associated with this group entry
1143 flags (integer) // a bitmask of the following values:
1144 kSecValidInfoComplete (0x00000001) set if we have all revocation info for this issuer group
1145 kSecValidInfoCheckOCSP (0x00000002) set if must check ocsp for certs from this issuer group
1146 kSecValidInfoKnownOnly (0x00000004) set if any CA from this issuer group must be in database
1147 kSecValidInfoRequireCT (0x00000008) set if all certs from this issuer group must have SCTs
1148 kSecValidInfoAllowlist (0x00000010) set if this entry describes valid certs (i.e. is allowed)
1149 kSecValidInfoNoCACheck (0x00000020) set if this entry does not require an OCSP check to accept
1150 format (integer) // an integer describing format of entries:
1151 kSecValidInfoFormatUnknown (0) unknown format
1152 kSecValidInfoFormatSerial (1) serial number, not greater than 20 bytes in length
1153 kSecValidInfoFormatSHA256 (2) SHA-256 hash, 32 bytes in length
1154 kSecValidInfoFormatNto1 (3) filter data blob of arbitrary length
1155 data (blob) // Bloom filter data if format is 'nto1', otherwise NULL
1156 --> entries in groups table are unique by groupid
1158 serials table holds serial number blobs with these attributes:
1159 rowid (integer) // ordinal ID associated with this serial number entry
1160 groupid (integer) // identifier for issuer group in the groups table
1161 serial (blob) // serial number
1162 --> entries in serials table are unique by serial and groupid
1164 hashes table holds SHA-256 hashes of certificates with these attributes:
1165 rowid (integer) // ordinal ID associated with this sha256 hash entry
1166 groupid (integer) // identifier for issuer group in the groups table
1167 sha256 (blob) // SHA-256 hash of subject certificate
1168 --> entries in hashes table are unique by sha256 and groupid
1170 #define createTablesSQL CFSTR("CREATE TABLE admin(" \
1171 "key TEXT PRIMARY KEY NOT NULL," \
1172 "ival INTEGER NOT NULL," \
1175 "CREATE TABLE issuers(" \
1176 "groupid INTEGER NOT NULL," \
1177 "issuer_hash BLOB PRIMARY KEY NOT NULL" \
1179 "CREATE INDEX issuer_idx ON issuers(issuer_hash);" \
1180 "CREATE TABLE groups(" \
1181 "groupid INTEGER PRIMARY KEY AUTOINCREMENT," \
1186 "CREATE TABLE serials(" \
1187 "rowid INTEGER PRIMARY KEY AUTOINCREMENT," \
1188 "groupid INTEGER NOT NULL," \
1189 "serial BLOB NOT NULL," \
1190 "UNIQUE(groupid,serial)" \
1192 "CREATE TABLE hashes(" \
1193 "rowid INTEGER PRIMARY KEY AUTOINCREMENT," \
1194 "groupid INTEGER NOT NULL," \
1195 "sha256 BLOB NOT NULL," \
1196 "UNIQUE(groupid,sha256)" \
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; " \
1205 #define selectGroupIdSQL CFSTR("SELECT DISTINCT groupid " \
1206 "FROM issuers WHERE issuer_hash=?")
1207 #define selectVersionSQL CFSTR("SELECT ival FROM admin " \
1208 "WHERE key='version'")
1209 #define selectDbVersionSQL CFSTR("SELECT ival FROM admin " \
1210 "WHERE key='db_version'")
1211 #define selectDbFormatSQL CFSTR("SELECT ival FROM admin " \
1212 "WHERE key='db_format'")
1213 #define selectDbHashSQL CFSTR("SELECT value FROM admin " \
1214 "WHERE key='db_hash'")
1215 #define selectDbSourceSQL CFSTR("SELECT value FROM admin " \
1216 "WHERE key='db_source'")
1217 #define selectNextUpdateSQL CFSTR("SELECT value FROM admin " \
1218 "WHERE key='check_again'")
1219 #define selectGroupRecordSQL CFSTR("SELECT flags,format,data FROM " \
1220 "groups WHERE groupid=?")
1221 #define selectSerialRecordSQL CFSTR("SELECT rowid FROM serials " \
1222 "WHERE groupid=? AND serial=?")
1223 #define selectHashRecordSQL CFSTR("SELECT rowid FROM hashes " \
1224 "WHERE groupid=? AND sha256=?")
1225 #define insertAdminRecordSQL CFSTR("INSERT OR REPLACE INTO admin " \
1226 "(key,ival,value) VALUES (?,?,?)")
1227 #define insertIssuerRecordSQL CFSTR("INSERT OR REPLACE INTO issuers " \
1228 "(groupid,issuer_hash) VALUES (?,?)")
1229 #define insertGroupRecordSQL CFSTR("INSERT OR REPLACE INTO groups " \
1230 "(groupid,flags,format,data) VALUES (?,?,?,?)")
1231 #define insertSerialRecordSQL CFSTR("INSERT OR REPLACE INTO serials " \
1232 "(groupid,serial) VALUES (?,?)")
1233 #define insertSha256RecordSQL CFSTR("INSERT OR REPLACE INTO hashes " \
1234 "(groupid,sha256) VALUES (?,?)")
1235 #define deleteGroupRecordSQL CFSTR("DELETE FROM groups WHERE groupid=?")
1237 #define deleteAllEntriesSQL CFSTR("DELETE from hashes; " \
1238 "DELETE from serials; DELETE from issuers; DELETE from groups; " \
1239 "DELETE from admin; DELETE from sqlite_sequence")
1240 #define deleteTablesSQL CFSTR("DROP TABLE hashes; " \
1241 "DROP TABLE serials; DROP TABLE issuers; DROP TABLE groups; " \
1242 "DROP TABLE admin; DELETE from sqlite_sequence")
1244 /* Database management */
1246 static SecDbRef
SecRevocationDbCreate(CFStringRef path
) {
1247 /* only the db owner should open a read-write connection. */
1248 bool readWrite
= isDbOwner();
1251 SecDbRef result
= SecDbCreateWithOptions(path
, mode
, readWrite
, false, false, ^bool (SecDbRef db
, SecDbConnectionRef dbconn
, bool didCreate
, bool *callMeAgainForNextConnection
, CFErrorRef
*error
) {
1252 __block
bool ok
= true;
1253 CFErrorRef localError
= NULL
;
1254 if (ok
&& !SecDbWithSQL(dbconn
, selectGroupIdSQL
, &localError
, NULL
) && CFErrorGetCode(localError
) == SQLITE_ERROR
) {
1255 /* SecDbWithSQL returns SQLITE_ERROR if the table we are preparing the above statement for doesn't exist. */
1257 /* Create all database tables, indexes, and triggers. */
1258 ok
&= SecDbTransaction(dbconn
, kSecDbExclusiveTransactionType
, error
, ^(bool *commit
) {
1259 ok
= SecDbExec(dbconn
, createTablesSQL
, error
);
1263 CFReleaseSafe(localError
);
1265 secerror("%s failed: %@", didCreate
? "Create" : "Open", error
? *error
: NULL
);
1272 typedef struct __SecRevocationDb
*SecRevocationDbRef
;
1273 struct __SecRevocationDb
{
1275 dispatch_queue_t update_queue
;
1276 bool updateInProgress
;
1277 bool unsupportedVersion
;
1280 static dispatch_once_t kSecRevocationDbOnce
;
1281 static SecRevocationDbRef kSecRevocationDb
= NULL
;
1283 static SecRevocationDbRef
SecRevocationDbInit(CFStringRef db_name
) {
1284 SecRevocationDbRef rdb
;
1285 dispatch_queue_attr_t attr
;
1287 require(rdb
= (SecRevocationDbRef
)malloc(sizeof(struct __SecRevocationDb
)), errOut
);
1289 rdb
->update_queue
= NULL
;
1290 rdb
->updateInProgress
= false;
1291 rdb
->unsupportedVersion
= false;
1293 require(rdb
->db
= SecRevocationDbCreate(db_name
), errOut
);
1294 attr
= dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL
, QOS_CLASS_BACKGROUND
, 0);
1295 require(rdb
->update_queue
= dispatch_queue_create(NULL
, attr
), errOut
);
1300 secdebug("validupdate", "Failed to create db at \"%@\"", db_name
);
1302 if (rdb
->update_queue
) {
1303 dispatch_release(rdb
->update_queue
);
1305 CFReleaseSafe(rdb
->db
);
1311 static CFStringRef
SecRevocationDbCopyPath(void) {
1312 CFURLRef revDbURL
= NULL
;
1313 CFStringRef revInfoRelPath
= NULL
;
1314 if ((revInfoRelPath
= CFStringCreateWithFormat(NULL
, NULL
, CFSTR("%s"), kSecRevocationDbFileName
)) != NULL
) {
1315 revDbURL
= SecCopyURLForFileInRevocationInfoDirectory(revInfoRelPath
);
1317 CFReleaseSafe(revInfoRelPath
);
1319 CFStringRef revDbPath
= NULL
;
1321 revDbPath
= CFURLCopyFileSystemPath(revDbURL
, kCFURLPOSIXPathStyle
);
1322 CFRelease(revDbURL
);
1327 static void SecRevocationDbWith(void(^dbJob
)(SecRevocationDbRef db
)) {
1328 dispatch_once(&kSecRevocationDbOnce
, ^{
1329 CFStringRef dbPath
= SecRevocationDbCopyPath();
1331 kSecRevocationDb
= SecRevocationDbInit(dbPath
);
1335 // Do pre job run work here (cancel idle timers etc.)
1336 if (kSecRevocationDb
->updateInProgress
) {
1337 return; // this would block since SecDb has an exclusive transaction lock
1339 dbJob(kSecRevocationDb
);
1340 // Do post job run work here (gc timer, etc.)
1343 static int64_t _SecRevocationDbGetVersion(SecRevocationDbRef rdb
, CFErrorRef
*error
) {
1344 /* look up version entry in admin table; returns -1 on error */
1345 __block
int64_t version
= -1;
1346 __block
bool ok
= true;
1347 __block CFErrorRef localError
= NULL
;
1349 ok
&= SecDbPerformRead(rdb
->db
, &localError
, ^(SecDbConnectionRef dbconn
) {
1350 if (ok
) ok
&= SecDbWithSQL(dbconn
, selectVersionSQL
, &localError
, ^bool(sqlite3_stmt
*selectVersion
) {
1351 ok
= SecDbStep(dbconn
, selectVersion
, &localError
, NULL
);
1352 version
= sqlite3_column_int64(selectVersion
, 0);
1356 (void) CFErrorPropagate(localError
, error
);
1360 static void _SecRevocationDbSetVersion(SecRevocationDbRef rdb
, CFIndex version
){
1361 secdebug("validupdate", "setting version to %ld", (long)version
);
1363 __block CFErrorRef localError
= NULL
;
1364 __block
bool ok
= true;
1365 ok
&= SecDbPerformWrite(rdb
->db
, &localError
, ^(SecDbConnectionRef dbconn
) {
1366 ok
&= SecDbTransaction(dbconn
, kSecDbExclusiveTransactionType
, &localError
, ^(bool *commit
) {
1367 if (ok
) ok
= SecDbWithSQL(dbconn
, insertAdminRecordSQL
, &localError
, ^bool(sqlite3_stmt
*insertVersion
) {
1369 const char *versionKey
= "version";
1370 ok
= SecDbBindText(insertVersion
, 1, versionKey
, strlen(versionKey
),
1371 SQLITE_TRANSIENT
, &localError
);
1374 ok
= SecDbBindInt64(insertVersion
, 2,
1375 (sqlite3_int64
)version
, &localError
);
1378 ok
= SecDbStep(dbconn
, insertVersion
, &localError
, NULL
);
1385 secerror("_SecRevocationDbSetVersion failed: %@", localError
);
1387 CFReleaseSafe(localError
);
1390 static int64_t _SecRevocationDbGetSchemaVersion(SecRevocationDbRef rdb
, CFErrorRef
*error
) {
1391 /* look up db_version entry in admin table; returns -1 on error */
1392 __block
int64_t db_version
= -1;
1393 __block
bool ok
= true;
1394 __block CFErrorRef localError
= NULL
;
1396 ok
&= SecDbPerformRead(rdb
->db
, &localError
, ^(SecDbConnectionRef dbconn
) {
1397 if (ok
) ok
&= SecDbWithSQL(dbconn
, selectDbVersionSQL
, &localError
, ^bool(sqlite3_stmt
*selectDbVersion
) {
1398 ok
= SecDbStep(dbconn
, selectDbVersion
, &localError
, NULL
);
1399 db_version
= sqlite3_column_int64(selectDbVersion
, 0);
1403 (void) CFErrorPropagate(localError
, error
);
1407 static void _SecRevocationDbSetSchemaVersion(SecRevocationDbRef rdb
, CFIndex dbversion
) {
1408 secdebug("validupdate", "setting db_version to %ld", (long)dbversion
);
1410 __block CFErrorRef localError
= NULL
;
1411 __block
bool ok
= true;
1412 ok
&= SecDbPerformWrite(rdb
->db
, &localError
, ^(SecDbConnectionRef dbconn
) {
1413 ok
&= SecDbTransaction(dbconn
, kSecDbExclusiveTransactionType
, &localError
, ^(bool *commit
) {
1414 if (ok
) ok
= SecDbWithSQL(dbconn
, insertAdminRecordSQL
, &localError
, ^bool(sqlite3_stmt
*insertDbVersion
) {
1416 const char *dbVersionKey
= "db_version";
1417 ok
= SecDbBindText(insertDbVersion
, 1, dbVersionKey
, strlen(dbVersionKey
),
1418 SQLITE_TRANSIENT
, &localError
);
1421 ok
= SecDbBindInt64(insertDbVersion
, 2,
1422 (sqlite3_int64
)dbversion
, &localError
);
1425 ok
= SecDbStep(dbconn
, insertDbVersion
, &localError
, NULL
);
1432 secerror("_SecRevocationDbSetSchemaVersion failed: %@", localError
);
1434 rdb
->unsupportedVersion
= false;
1436 CFReleaseSafe(localError
);
1439 static int64_t _SecRevocationDbGetUpdateFormat(SecRevocationDbRef rdb
, CFErrorRef
*error
) {
1440 /* look up db_format entry in admin table; returns -1 on error */
1441 __block
int64_t db_format
= -1;
1442 __block
bool ok
= true;
1443 __block CFErrorRef localError
= NULL
;
1445 ok
&= SecDbPerformRead(rdb
->db
, &localError
, ^(SecDbConnectionRef dbconn
) {
1446 if (ok
) ok
&= SecDbWithSQL(dbconn
, selectDbFormatSQL
, &localError
, ^bool(sqlite3_stmt
*selectDbFormat
) {
1447 ok
= SecDbStep(dbconn
, selectDbFormat
, &localError
, NULL
);
1448 db_format
= sqlite3_column_int64(selectDbFormat
, 0);
1452 (void) CFErrorPropagate(localError
, error
);
1456 static void _SecRevocationDbSetUpdateFormat(SecRevocationDbRef rdb
, CFIndex dbformat
) {
1457 secdebug("validupdate", "setting db_format to %ld", (long)dbformat
);
1459 __block CFErrorRef localError
= NULL
;
1460 __block
bool ok
= true;
1461 ok
&= SecDbPerformWrite(rdb
->db
, &localError
, ^(SecDbConnectionRef dbconn
) {
1462 ok
&= SecDbTransaction(dbconn
, kSecDbExclusiveTransactionType
, &localError
, ^(bool *commit
) {
1463 if (ok
) ok
= SecDbWithSQL(dbconn
, insertAdminRecordSQL
, &localError
, ^bool(sqlite3_stmt
*insertDbFormat
) {
1465 const char *dbFormatKey
= "db_format";
1466 ok
= SecDbBindText(insertDbFormat
, 1, dbFormatKey
, strlen(dbFormatKey
),
1467 SQLITE_TRANSIENT
, &localError
);
1470 ok
= SecDbBindInt64(insertDbFormat
, 2,
1471 (sqlite3_int64
)dbformat
, &localError
);
1474 ok
= SecDbStep(dbconn
, insertDbFormat
, &localError
, NULL
);
1481 secerror("_SecRevocationDbSetUpdateFormat failed: %@", localError
);
1483 rdb
->unsupportedVersion
= false;
1485 CFReleaseSafe(localError
);
1488 static CFStringRef
_SecRevocationDbCopyUpdateSource(SecRevocationDbRef rdb
, CFErrorRef
*error
) {
1489 /* look up db_source entry in admin table; returns NULL on error */
1490 __block CFStringRef updateSource
= NULL
;
1491 __block
bool ok
= true;
1492 __block CFErrorRef localError
= NULL
;
1494 ok
&= SecDbPerformRead(rdb
->db
, &localError
, ^(SecDbConnectionRef dbconn
) {
1495 if (ok
) ok
&= SecDbWithSQL(dbconn
, selectDbSourceSQL
, &localError
, ^bool(sqlite3_stmt
*selectDbSource
) {
1496 ok
= SecDbStep(dbconn
, selectDbSource
, &localError
, NULL
);
1497 const UInt8
*p
= (const UInt8
*)sqlite3_column_blob(selectDbSource
, 0);
1499 CFIndex length
= (CFIndex
)sqlite3_column_bytes(selectDbSource
, 0);
1501 updateSource
= CFStringCreateWithBytes(kCFAllocatorDefault
, p
, length
, kCFStringEncodingUTF8
, false);
1508 (void) CFErrorPropagate(localError
, error
);
1509 return updateSource
;
1512 static void _SecRevocationDbSetUpdateSource(SecRevocationDbRef rdb
, CFStringRef updateSource
) {
1513 if (!updateSource
) {
1514 secerror("_SecRevocationDbSetUpdateSource failed: %d", errSecParam
);
1517 __block
char buffer
[256];
1518 __block
const char *updateSourceCStr
= CFStringGetCStringPtr(updateSource
, kCFStringEncodingUTF8
);
1519 if (!updateSourceCStr
) {
1520 if (CFStringGetCString(updateSource
, buffer
, 256, kCFStringEncodingUTF8
)) {
1521 updateSourceCStr
= buffer
;
1524 if (!updateSourceCStr
) {
1525 secerror("_SecRevocationDbSetUpdateSource failed: unable to get UTF-8 encoding");
1528 secdebug("validupdate", "setting update source to \"%s\"", updateSourceCStr
);
1530 __block CFErrorRef localError
= NULL
;
1531 __block
bool ok
= true;
1532 ok
&= SecDbPerformWrite(rdb
->db
, &localError
, ^(SecDbConnectionRef dbconn
) {
1533 ok
&= SecDbTransaction(dbconn
, kSecDbExclusiveTransactionType
, &localError
, ^(bool *commit
) {
1534 if (ok
) ok
= SecDbWithSQL(dbconn
, insertAdminRecordSQL
, &localError
, ^bool(sqlite3_stmt
*insertRecord
) {
1536 const char *dbSourceKey
= "db_source";
1537 ok
= SecDbBindText(insertRecord
, 1, dbSourceKey
, strlen(dbSourceKey
),
1538 SQLITE_TRANSIENT
, &localError
);
1541 ok
= SecDbBindInt64(insertRecord
, 2,
1542 (sqlite3_int64
)0, &localError
);
1545 ok
= SecDbBindBlob(insertRecord
, 3,
1546 updateSourceCStr
, strlen(updateSourceCStr
),
1547 SQLITE_TRANSIENT
, &localError
);
1550 ok
= SecDbStep(dbconn
, insertRecord
, &localError
, NULL
);
1557 secerror("_SecRevocationDbSetUpdateSource failed: %@", localError
);
1559 CFReleaseSafe(localError
);
1562 static CFAbsoluteTime
_SecRevocationDbGetNextUpdateTime(SecRevocationDbRef rdb
, CFErrorRef
*error
) {
1563 /* look up check_again entry in admin table; returns 0 on error */
1564 __block CFAbsoluteTime nextUpdate
= 0;
1565 __block
bool ok
= true;
1566 __block CFErrorRef localError
= NULL
;
1568 ok
&= SecDbPerformRead(rdb
->db
, &localError
, ^(SecDbConnectionRef dbconn
) {
1569 if (ok
) ok
&= SecDbWithSQL(dbconn
, selectNextUpdateSQL
, &localError
, ^bool(sqlite3_stmt
*selectNextUpdate
) {
1570 ok
= SecDbStep(dbconn
, selectNextUpdate
, &localError
, NULL
);
1571 CFAbsoluteTime
*p
= (CFAbsoluteTime
*)sqlite3_column_blob(selectNextUpdate
, 0);
1573 if (sizeof(CFAbsoluteTime
) == sqlite3_column_bytes(selectNextUpdate
, 0)) {
1581 (void) CFErrorPropagate(localError
, error
);
1585 static void _SecRevocationDbSetNextUpdateTime(SecRevocationDbRef rdb
, CFAbsoluteTime nextUpdate
){
1586 secdebug("validupdate", "setting next update to %f", (double)nextUpdate
);
1588 __block CFErrorRef localError
= NULL
;
1589 __block
bool ok
= true;
1590 ok
&= SecDbPerformWrite(rdb
->db
, &localError
, ^(SecDbConnectionRef dbconn
) {
1591 ok
&= SecDbTransaction(dbconn
, kSecDbExclusiveTransactionType
, &localError
, ^(bool *commit
) {
1592 if (ok
) ok
= SecDbWithSQL(dbconn
, insertAdminRecordSQL
, &localError
, ^bool(sqlite3_stmt
*insertRecord
) {
1594 const char *nextUpdateKey
= "check_again";
1595 ok
= SecDbBindText(insertRecord
, 1, nextUpdateKey
, strlen(nextUpdateKey
),
1596 SQLITE_TRANSIENT
, &localError
);
1599 ok
= SecDbBindInt64(insertRecord
, 2,
1600 (sqlite3_int64
)0, &localError
);
1603 ok
= SecDbBindBlob(insertRecord
, 3,
1604 &nextUpdate
, sizeof(CFAbsoluteTime
),
1605 SQLITE_TRANSIENT
, &localError
);
1608 ok
= SecDbStep(dbconn
, insertRecord
, &localError
, NULL
);
1615 secerror("_SecRevocationDbSetNextUpdate failed: %@", localError
);
1617 CFReleaseSafe(localError
);
1620 static bool _SecRevocationDbRemoveAllEntries(SecRevocationDbRef rdb
) {
1621 /* clear out the contents of the database and start fresh */
1622 __block
bool ok
= true;
1623 __block CFErrorRef localError
= NULL
;
1625 ok
&= SecDbPerformWrite(rdb
->db
, &localError
, ^(SecDbConnectionRef dbconn
) {
1626 ok
&= SecDbTransaction(dbconn
, kSecDbExclusiveTransactionType
, &localError
, ^(bool *commit
) {
1627 //ok &= SecDbWithSQL(dbconn, deleteAllEntriesSQL, &localError, ^bool(sqlite3_stmt *deleteAll) {
1628 // ok = SecDbStep(dbconn, deleteAll, &localError, NULL);
1631 /* drop all tables and recreate them, in case of schema changes */
1632 ok
&= SecDbExec(dbconn
, deleteTablesSQL
, &localError
);
1633 ok
&= SecDbExec(dbconn
, createTablesSQL
, &localError
);
1634 secdebug("validupdate", "resetting database, result: %d", (ok
) ? 1 : 0);
1637 /* compact the db (must be done outside transaction scope) */
1638 SecDbExec(dbconn
, CFSTR("VACUUM"), &localError
);
1640 /* one more thing: update the schema version and format to current */
1641 _SecRevocationDbSetSchemaVersion(rdb
, kSecRevocationDbSchemaVersion
);
1642 _SecRevocationDbSetUpdateFormat(rdb
, kSecRevocationDbUpdateFormat
);
1644 CFReleaseSafe(localError
);
1648 static bool _SecRevocationDbUpdateIssuers(SecRevocationDbRef rdb
, int64_t groupId
, CFArrayRef issuers
, CFErrorRef
*error
) {
1649 /* insert or replace issuer records in issuers table */
1650 if (!issuers
|| groupId
< 0) {
1651 return false; /* must have something to insert, and a group to associate with it */
1653 __block
bool ok
= true;
1654 __block CFErrorRef localError
= NULL
;
1656 ok
&= SecDbPerformWrite(rdb
->db
, &localError
, ^(SecDbConnectionRef dbconn
) {
1657 ok
&= SecDbTransaction(dbconn
, kSecDbExclusiveTransactionType
, &localError
, ^(bool *commit
) {
1658 if (isArray(issuers
)) {
1659 CFIndex issuerIX
, issuerCount
= CFArrayGetCount(issuers
);
1660 for (issuerIX
=0; issuerIX
<issuerCount
&& ok
; issuerIX
++) {
1661 CFDataRef hash
= (CFDataRef
)CFArrayGetValueAtIndex(issuers
, issuerIX
);
1662 if (!hash
) { continue; }
1663 if (ok
) ok
= SecDbWithSQL(dbconn
, insertIssuerRecordSQL
, &localError
, ^bool(sqlite3_stmt
*insertIssuer
) {
1665 ok
= SecDbBindInt64(insertIssuer
, 1,
1666 groupId
, &localError
);
1669 ok
= SecDbBindBlob(insertIssuer
, 2,
1670 CFDataGetBytePtr(hash
),
1671 CFDataGetLength(hash
),
1672 SQLITE_TRANSIENT
, &localError
);
1674 /* Execute the insert statement for this issuer record. */
1676 ok
= SecDbStep(dbconn
, insertIssuer
, &localError
, NULL
);
1685 (void) CFErrorPropagate(localError
, error
);
1689 static bool _SecRevocationDbUpdatePerIssuerData(SecRevocationDbRef rdb
, int64_t groupId
, CFDictionaryRef dict
, CFErrorRef
*error
) {
1690 /* update/delete records in serials or hashes table. */
1691 if (!dict
|| groupId
< 0) {
1692 return false; /* must have something to insert, and a group to associate with it */
1694 __block
bool ok
= true;
1695 __block CFErrorRef localError
= NULL
;
1697 ok
&= SecDbPerformWrite(rdb
->db
, &localError
, ^(SecDbConnectionRef dbconn
) {
1698 ok
&= SecDbTransaction(dbconn
, kSecDbExclusiveTransactionType
, &localError
, ^(bool *commit
) {
1699 CFArrayRef deleteArray
= (CFArrayRef
)CFDictionaryGetValue(dict
, CFSTR("delete"));
1700 /* process deletions */
1701 if (isArray(deleteArray
)) {
1702 //%%% delete old data here (rdar://31439625)
1704 /* process additions */
1705 CFArrayRef addArray
= (CFArrayRef
)CFDictionaryGetValue(dict
, CFSTR("add"));
1706 if (isArray(addArray
)) {
1707 CFIndex identifierIX
, identifierCount
= CFArrayGetCount(addArray
);
1708 for (identifierIX
=0; identifierIX
<identifierCount
; identifierIX
++) {
1709 CFDataRef identifierData
= (CFDataRef
)CFArrayGetValueAtIndex(addArray
, identifierIX
);
1710 if (!identifierData
) { continue; }
1711 CFIndex length
= CFDataGetLength(identifierData
);
1712 /* we can figure out the format without an extra read to get the format column.
1713 len <= 20 is a serial number. len==32 is a sha256 hash. otherwise: xor. */
1714 CFStringRef sql
= NULL
;
1716 sql
= insertSerialRecordSQL
;
1717 } else if (length
== 32) {
1718 sql
= insertSha256RecordSQL
;
1720 if (!sql
) { continue; }
1722 if (ok
) ok
= SecDbWithSQL(dbconn
, sql
, &localError
, ^bool(sqlite3_stmt
*insertIdentifier
) {
1723 /* rowid,(groupid,serial|sha256) */
1724 /* rowid is autoincremented and we never set it directly */
1726 ok
= SecDbBindInt64(insertIdentifier
, 1,
1727 groupId
, &localError
);
1730 ok
= SecDbBindBlob(insertIdentifier
, 2,
1731 CFDataGetBytePtr(identifierData
),
1732 CFDataGetLength(identifierData
),
1733 SQLITE_TRANSIENT
, &localError
);
1735 /* Execute the insert statement for the identifier record. */
1737 ok
= SecDbStep(dbconn
, insertIdentifier
, &localError
, NULL
);
1746 (void) CFErrorPropagate(localError
, error
);
1750 static SecValidInfoFormat
_SecRevocationDbGetGroupFormat(SecRevocationDbRef rdb
,
1751 int64_t groupId
, SecValidInfoFlags
*flags
, CFDataRef
*data
, CFErrorRef
*error
) {
1752 /* return group record fields for a given groupId.
1753 on success, returns a non-zero format type, and other field values in optional output parameters.
1754 caller is responsible for releasing data and error parameters, if provided.
1756 __block
bool ok
= true;
1757 __block SecValidInfoFormat format
= 0;
1758 __block CFErrorRef localError
= NULL
;
1760 /* Select the group record to determine flags and format. */
1761 ok
&= SecDbPerformRead(rdb
->db
, &localError
, ^(SecDbConnectionRef dbconn
) {
1762 ok
&= SecDbWithSQL(dbconn
, selectGroupRecordSQL
, &localError
, ^bool(sqlite3_stmt
*selectGroup
) {
1763 ok
= SecDbBindInt64(selectGroup
, 1, groupId
, &localError
);
1764 ok
&= SecDbStep(dbconn
, selectGroup
, &localError
, ^(bool *stop
) {
1766 *flags
= (SecValidInfoFlags
)sqlite3_column_int(selectGroup
, 0);
1768 format
= (SecValidInfoFormat
)sqlite3_column_int(selectGroup
, 1);
1770 //TODO: stream this from sqlite through the inflation so we return an inflated copy, then remove inflate from others
1771 uint8_t *p
= (uint8_t *)sqlite3_column_blob(selectGroup
, 2);
1772 if (p
!= NULL
&& format
== kSecValidInfoFormatNto1
) {
1773 CFIndex length
= (CFIndex
)sqlite3_column_bytes(selectGroup
, 2);
1774 *data
= CFDataCreate(kCFAllocatorDefault
, p
, length
);
1782 secdebug("validupdate", "GetGroupFormat for groupId %lu failed", (unsigned long)groupId
);
1783 format
= kSecValidInfoFormatUnknown
;
1785 (void) CFErrorPropagate(localError
, error
);
1786 if (!(format
> kSecValidInfoFormatUnknown
)) {
1787 secdebug("validupdate", "GetGroupFormat: got format %d for groupId %lld", format
, (long long)groupId
);
1792 static bool _SecRevocationDbUpdateFlags(CFDictionaryRef dict
, CFStringRef key
, SecValidInfoFlags mask
, SecValidInfoFlags
*flags
) {
1793 /* If a boolean value exists in the given dictionary for the given key,
1794 set or clear the corresponding bit(s) defined by the mask argument.
1795 Function returns true if the flags value was changed, false otherwise.
1797 bool result
= false;
1798 CFTypeRef value
= (CFBooleanRef
)CFDictionaryGetValue(dict
, key
);
1799 if (isBoolean(value
) && flags
) {
1800 SecValidInfoFlags oldFlags
= *flags
;
1801 if (CFBooleanGetValue((CFBooleanRef
)value
)) {
1806 result
= (*flags
!= oldFlags
);
1811 static bool _SecRevocationDbUpdateFilter(CFDictionaryRef dict
, CFDataRef oldData
, CFDataRef
* __nonnull CF_RETURNS_RETAINED xmlData
) {
1812 /* If xor and/or params values exist in the given dictionary, create a new
1813 property list containing the updated values, and return as a flattened
1814 data blob in the xmlData output parameter (note: caller must release.)
1815 Function returns true if there is new xmlData to save, false otherwise.
1817 bool result
= false;
1818 bool xorProvided
= false;
1819 bool paramsProvided
= false;
1820 bool missingData
= false;
1822 if (!dict
|| !xmlData
) {
1823 return result
; /* no-op if no dictionary is provided, or no way to update the data */
1826 CFDataRef xorCurrent
= NULL
;
1827 CFDataRef xorUpdate
= (CFDataRef
)CFDictionaryGetValue(dict
, CFSTR("xor"));
1828 if (isData(xorUpdate
)) {
1831 CFArrayRef paramsCurrent
= NULL
;
1832 CFArrayRef paramsUpdate
= (CFArrayRef
)CFDictionaryGetValue(dict
, CFSTR("params"));
1833 if (isArray(paramsUpdate
)) {
1834 paramsProvided
= true;
1836 if (!(xorProvided
|| paramsProvided
)) {
1837 return result
; /* nothing to update, so we can bail out here. */
1840 CFPropertyListRef nto1Current
= NULL
;
1841 CFMutableDictionaryRef nto1Update
= CFDictionaryCreateMutable(kCFAllocatorDefault
, 0,
1842 &kCFTypeDictionaryKeyCallBacks
,
1843 &kCFTypeDictionaryValueCallBacks
);
1848 /* turn old data into property list */
1849 CFDataRef data
= (CFDataRef
)CFRetainSafe(oldData
);
1850 CFDataRef inflatedData
= copyInflatedData(data
);
1852 CFReleaseSafe(data
);
1853 data
= inflatedData
;
1856 nto1Current
= CFPropertyListCreateWithData(kCFAllocatorDefault
, data
, 0, NULL
, NULL
);
1857 CFReleaseSafe(data
);
1860 xorCurrent
= (CFDataRef
)CFDictionaryGetValue((CFDictionaryRef
)nto1Current
, CFSTR("xor"));
1861 paramsCurrent
= (CFArrayRef
)CFDictionaryGetValue((CFDictionaryRef
)nto1Current
, CFSTR("params"));
1864 /* set current or updated xor data in new property list */
1866 CFDataRef xorNew
= NULL
;
1868 CFIndex xorUpdateLen
= CFDataGetLength(xorUpdate
);
1869 CFMutableDataRef
xor = CFDataCreateMutableCopy(NULL
, 0, xorCurrent
);
1870 if (xor && xorUpdateLen
> 0) {
1871 /* truncate or zero-extend data to match update size */
1872 CFDataSetLength(xor, xorUpdateLen
);
1873 /* exclusive-or update bytes over the existing data */
1874 UInt8
*xorP
= (UInt8
*)CFDataGetMutableBytePtr(xor);
1875 UInt8
*updP
= (UInt8
*)CFDataGetBytePtr(xorUpdate
);
1877 for (int idx
= 0; idx
< xorUpdateLen
; idx
++) {
1878 xorP
[idx
] = xorP
[idx
] ^ updP
[idx
];
1882 xorNew
= (CFDataRef
)xor;
1884 xorNew
= (CFDataRef
)CFRetainSafe(xorUpdate
);
1887 CFDictionaryAddValue(nto1Update
, CFSTR("xor"), xorNew
);
1888 CFReleaseSafe(xorNew
);
1890 secdebug("validupdate", "Failed to get updated filter data");
1893 } else if (xorCurrent
) {
1894 /* not provided, so use existing xor value */
1895 CFDictionaryAddValue(nto1Update
, CFSTR("xor"), xorCurrent
);
1897 secdebug("validupdate", "Failed to get current filter data");
1901 /* set current or updated params in new property list */
1902 if (paramsProvided
) {
1903 CFDictionaryAddValue(nto1Update
, CFSTR("params"), paramsUpdate
);
1904 } else if (paramsCurrent
) {
1905 /* not provided, so use existing params value */
1906 CFDictionaryAddValue(nto1Update
, CFSTR("params"), paramsCurrent
);
1908 /* missing params: neither provided nor existing */
1909 secdebug("validupdate", "Failed to get current filter params");
1913 CFReleaseSafe(nto1Current
);
1915 *xmlData
= CFPropertyListCreateData(kCFAllocatorDefault
, nto1Update
,
1916 kCFPropertyListXMLFormat_v1_0
,
1918 result
= (*xmlData
!= NULL
);
1920 CFReleaseSafe(nto1Update
);
1922 /* compress the xmlData blob, if possible */
1924 CFDataRef deflatedData
= copyDeflatedData(*xmlData
);
1926 if (CFDataGetLength(deflatedData
) < CFDataGetLength(*xmlData
)) {
1927 CFRelease(*xmlData
);
1928 *xmlData
= deflatedData
;
1930 CFRelease(deflatedData
);
1938 static int64_t _SecRevocationDbUpdateGroup(SecRevocationDbRef rdb
, int64_t groupId
, CFDictionaryRef dict
, CFErrorRef
*error
) {
1939 /* insert group record for a given groupId.
1940 if the specified groupId is < 0, a new group entry is created.
1941 returns the groupId on success, or -1 on failure.
1944 return groupId
; /* no-op if no dictionary is provided */
1947 __block
int64_t result
= -1;
1948 __block
bool ok
= true;
1949 __block
bool isFormatChange
= false;
1950 __block CFErrorRef localError
= NULL
;
1952 __block SecValidInfoFlags flags
= 0;
1953 __block SecValidInfoFormat format
= kSecValidInfoFormatUnknown
;
1954 __block SecValidInfoFormat formatUpdate
= kSecValidInfoFormatUnknown
;
1955 __block CFDataRef data
= NULL
;
1958 /* fetch the flags and data for an existing group record, in case some are being changed. */
1959 format
= _SecRevocationDbGetGroupFormat(rdb
, groupId
, &flags
, &data
, NULL
);
1960 if (format
== kSecValidInfoFormatUnknown
) {
1961 secdebug("validupdate", "existing group %lld has unknown format %d, flags=%lu",
1962 (long long)groupId
, format
, flags
);
1963 //%%% clean up by deleting all issuers with this groupId, then the group record,
1964 // or just force a full update? note: we can get here if we fail to bind the
1965 // format value in the prepared SQL statement below.
1969 CFTypeRef value
= (CFStringRef
)CFDictionaryGetValue(dict
, CFSTR("format"));
1970 if (isString(value
)) {
1971 if (CFStringCompare((CFStringRef
)value
, CFSTR("serial"), 0) == kCFCompareEqualTo
) {
1972 formatUpdate
= kSecValidInfoFormatSerial
;
1973 } else if (CFStringCompare((CFStringRef
)value
, CFSTR("sha256"), 0) == kCFCompareEqualTo
) {
1974 formatUpdate
= kSecValidInfoFormatSHA256
;
1975 } else if (CFStringCompare((CFStringRef
)value
, CFSTR("nto1"), 0) == kCFCompareEqualTo
) {
1976 formatUpdate
= kSecValidInfoFormatNto1
;
1979 /* if format value is explicitly supplied, then this is effectively a new group entry. */
1980 isFormatChange
= (formatUpdate
> kSecValidInfoFormatUnknown
&&
1981 formatUpdate
!= format
&&
1984 ok
&= SecDbPerformWrite(rdb
->db
, &localError
, ^(SecDbConnectionRef dbconn
) {
1985 ok
&= SecDbTransaction(dbconn
, kSecDbExclusiveTransactionType
, &localError
, ^(bool *commit
) {
1986 if (isFormatChange
) {
1987 secdebug("validupdate", "group %lld format change from %d to %d",
1988 (long long)groupId
, format
, formatUpdate
);
1989 /* format of an existing group is changing; delete the group first.
1990 this should ensure that all entries referencing the old groupid are deleted.
1992 ok
&= SecDbWithSQL(dbconn
, deleteGroupRecordSQL
, &localError
, ^bool(sqlite3_stmt
*deleteResponse
) {
1993 ok
= SecDbBindInt64(deleteResponse
, 1, groupId
, &localError
);
1994 /* Execute the delete statement. */
1996 ok
= SecDbStep(dbconn
, deleteResponse
, &localError
, NULL
);
2001 ok
&= SecDbWithSQL(dbconn
, insertGroupRecordSQL
, &localError
, ^bool(sqlite3_stmt
*insertGroup
) {
2002 /* (groupid,flags,format,data) */
2003 /* groups.groupid */
2004 if (ok
&& (!isFormatChange
) && (groupId
>= 0)) {
2005 /* bind to existing groupId row if known, otherwise will insert and autoincrement */
2006 ok
= SecDbBindInt64(insertGroup
, 1, groupId
, &localError
);
2008 secdebug("validupdate", "failed to set groupId %lld", (long long)groupId
);
2013 (void)_SecRevocationDbUpdateFlags(dict
, CFSTR("complete"), kSecValidInfoComplete
, &flags
);
2014 (void)_SecRevocationDbUpdateFlags(dict
, CFSTR("check-ocsp"), kSecValidInfoCheckOCSP
, &flags
);
2015 (void)_SecRevocationDbUpdateFlags(dict
, CFSTR("known-intermediates-only"), kSecValidInfoKnownOnly
, &flags
);
2016 (void)_SecRevocationDbUpdateFlags(dict
, CFSTR("require-ct"), kSecValidInfoRequireCT
, &flags
);
2017 (void)_SecRevocationDbUpdateFlags(dict
, CFSTR("valid"), kSecValidInfoAllowlist
, &flags
);
2018 (void)_SecRevocationDbUpdateFlags(dict
, CFSTR("no-ca"), kSecValidInfoNoCACheck
, &flags
);
2020 ok
= SecDbBindInt(insertGroup
, 2, (int)flags
, &localError
);
2022 secdebug("validupdate", "failed to set flags (%lu) for groupId %lld", flags
, (long long)groupId
);
2027 SecValidInfoFormat formatValue
= format
;
2028 if (formatUpdate
> kSecValidInfoFormatUnknown
) {
2029 formatValue
= formatUpdate
;
2031 ok
= SecDbBindInt(insertGroup
, 3, (int)formatValue
, &localError
);
2033 secdebug("validupdate", "failed to set format (%d) for groupId %lld", formatValue
, (long long)groupId
);
2037 CFDataRef xmlData
= NULL
;
2039 bool hasFilter
= ((formatUpdate
== kSecValidInfoFormatNto1
) ||
2040 (formatUpdate
== kSecValidInfoFormatUnknown
&&
2041 format
== kSecValidInfoFormatNto1
));
2043 CFDataRef dataValue
= data
; /* use existing data */
2044 if (_SecRevocationDbUpdateFilter(dict
, data
, &xmlData
)) {
2045 dataValue
= xmlData
; /* use updated data */
2048 ok
= SecDbBindBlob(insertGroup
, 4,
2049 CFDataGetBytePtr(dataValue
),
2050 CFDataGetLength(dataValue
),
2051 SQLITE_TRANSIENT
, &localError
);
2054 secdebug("validupdate", "failed to set data for groupId %lld",
2055 (long long)groupId
);
2058 /* else there is no data, so NULL is implicitly bound to column 4 */
2061 /* Execute the insert statement for the group record. */
2063 ok
= SecDbStep(dbconn
, insertGroup
, &localError
, NULL
);
2065 secdebug("validupdate", "failed to execute insertGroup statement for groupId %lld",
2066 (long long)groupId
);
2068 result
= (int64_t)sqlite3_last_insert_rowid(SecDbHandle(dbconn
));
2071 secdebug("validupdate", "failed to insert group %lld", (long long)result
);
2073 /* Clean up temporary allocation made in this block. */
2074 CFReleaseSafe(xmlData
);
2075 CFReleaseSafe(data
);
2081 (void) CFErrorPropagate(localError
, error
);
2085 static int64_t _SecRevocationDbGroupIdForIssuerHash(SecRevocationDbRef rdb
, CFDataRef hash
, CFErrorRef
*error
) {
2086 /* look up issuer hash in issuers table to get groupid, if it exists */
2087 __block
int64_t groupId
= -1;
2088 __block
bool ok
= true;
2089 __block CFErrorRef localError
= NULL
;
2092 secdebug("validupdate", "failed to get hash (%@)", hash
);
2094 require(hash
, errOut
);
2096 /* This is the starting point for any lookup; find a group id for the given issuer hash.
2097 Before we do that, need to verify the current db_version. We cannot use results from a
2098 database created with a schema version older than the minimum supported version.
2099 However, we may be able to use results from a newer version. At the next database
2100 update interval, if the existing schema is old, we'll be removing and recreating
2101 the database contents with the current schema version.
2103 int64_t db_version
= _SecRevocationDbGetSchemaVersion(rdb
, NULL
);
2104 if (db_version
< kSecRevocationDbMinSchemaVersion
) {
2105 if (!rdb
->unsupportedVersion
) {
2106 secdebug("validupdate", "unsupported db_version: %lld", (long long)db_version
);
2107 rdb
->unsupportedVersion
= true; /* only warn once for a given unsupported version */
2110 require_quiet(db_version
>= kSecRevocationDbMinSchemaVersion
, errOut
);
2112 /* Look up provided issuer_hash in the issuers table.
2114 ok
&= SecDbPerformRead(rdb
->db
, &localError
, ^(SecDbConnectionRef dbconn
) {
2115 ok
&= SecDbWithSQL(dbconn
, selectGroupIdSQL
, &localError
, ^bool(sqlite3_stmt
*selectGroupId
) {
2116 ok
= SecDbBindBlob(selectGroupId
, 1, CFDataGetBytePtr(hash
), CFDataGetLength(hash
), SQLITE_TRANSIENT
, &localError
);
2117 ok
&= SecDbStep(dbconn
, selectGroupId
, &localError
, ^(bool *stopGroupId
) {
2118 groupId
= sqlite3_column_int64(selectGroupId
, 0);
2125 (void) CFErrorPropagate(localError
, error
);
2129 static bool _SecRevocationDbApplyGroupDelete(SecRevocationDbRef rdb
, CFDataRef issuerHash
, CFErrorRef
*error
) {
2130 /* delete group associated with the given issuer;
2131 schema trigger will delete associated issuers, serials, and hashes. */
2132 __block
int64_t groupId
= -1;
2133 __block
bool ok
= true;
2134 __block CFErrorRef localError
= NULL
;
2136 groupId
= _SecRevocationDbGroupIdForIssuerHash(rdb
, issuerHash
, &localError
);
2137 require(!(groupId
< 0), errOut
);
2139 ok
&= SecDbPerformWrite(rdb
->db
, &localError
, ^(SecDbConnectionRef dbconn
) {
2140 ok
&= SecDbTransaction(dbconn
, kSecDbExclusiveTransactionType
, &localError
, ^(bool *commit
) {
2141 ok
= SecDbWithSQL(dbconn
, deleteGroupRecordSQL
, &localError
, ^bool(sqlite3_stmt
*deleteResponse
) {
2142 ok
= SecDbBindInt64(deleteResponse
, 1, groupId
, &localError
);
2143 /* Execute the delete statement. */
2145 ok
= SecDbStep(dbconn
, deleteResponse
, &localError
, NULL
);
2153 (void) CFErrorPropagate(localError
, error
);
2154 return (groupId
< 0) ? false : true;
2157 static bool _SecRevocationDbApplyGroupUpdate(SecRevocationDbRef rdb
, CFDictionaryRef dict
, CFErrorRef
*error
) {
2158 /* process one issuer group's update dictionary */
2159 int64_t groupId
= -1;
2160 CFErrorRef localError
= NULL
;
2162 CFArrayRef issuers
= (dict
) ? (CFArrayRef
)CFDictionaryGetValue(dict
, CFSTR("issuer-hash")) : NULL
;
2163 if (isArray(issuers
)) {
2164 CFIndex issuerIX
, issuerCount
= CFArrayGetCount(issuers
);
2165 /* while we have issuers and haven't found a matching group id */
2166 for (issuerIX
=0; issuerIX
<issuerCount
&& groupId
< 0; issuerIX
++) {
2167 CFDataRef hash
= (CFDataRef
)CFArrayGetValueAtIndex(issuers
, issuerIX
);
2168 if (!hash
) { continue; }
2169 groupId
= _SecRevocationDbGroupIdForIssuerHash(rdb
, hash
, &localError
);
2172 /* create or update the group entry */
2173 groupId
= _SecRevocationDbUpdateGroup(rdb
, groupId
, dict
, &localError
);
2175 secdebug("validupdate", "failed to get groupId");
2177 /* create or update issuer entries, now that we know the group id */
2178 _SecRevocationDbUpdateIssuers(rdb
, groupId
, issuers
, &localError
);
2179 /* create or update entries in serials or hashes tables */
2180 _SecRevocationDbUpdatePerIssuerData(rdb
, groupId
, dict
, &localError
);
2183 (void) CFErrorPropagate(localError
, error
);
2184 return (groupId
> 0) ? true : false;
2187 static void _SecRevocationDbApplyUpdate(SecRevocationDbRef rdb
, CFDictionaryRef update
, CFIndex version
) {
2188 /* process entire update dictionary */
2189 if (!rdb
|| !update
) {
2190 secerror("_SecRevocationDbApplyUpdate failed: invalid args");
2194 __block CFDictionaryRef localUpdate
= (CFDictionaryRef
)CFRetainSafe(update
);
2195 __block CFErrorRef localError
= NULL
;
2197 CFTypeRef value
= NULL
;
2198 CFIndex deleteCount
= 0;
2199 CFIndex updateCount
= 0;
2201 rdb
->updateInProgress
= true;
2203 /* check whether this is a full update */
2204 value
= (CFBooleanRef
)CFDictionaryGetValue(update
, CFSTR("full"));
2205 if (isBoolean(value
) && CFBooleanGetValue((CFBooleanRef
)value
)) {
2206 /* clear the database before processing a full update */
2207 SecRevocationDbRemoveAllEntries();
2210 /* process 'delete' list */
2211 value
= (CFArrayRef
)CFDictionaryGetValue(localUpdate
, CFSTR("delete"));
2212 if (isArray(value
)) {
2213 deleteCount
= CFArrayGetCount((CFArrayRef
)value
);
2214 secdebug("validupdate", "processing %ld deletes", (long)deleteCount
);
2215 for (CFIndex deleteIX
=0; deleteIX
<deleteCount
; deleteIX
++) {
2216 CFDataRef issuerHash
= (CFDataRef
)CFArrayGetValueAtIndex((CFArrayRef
)value
, deleteIX
);
2217 if (isData(issuerHash
)) {
2218 (void)_SecRevocationDbApplyGroupDelete(rdb
, issuerHash
, &localError
);
2219 CFReleaseNull(localError
);
2224 /* process 'update' list */
2225 value
= (CFArrayRef
)CFDictionaryGetValue(localUpdate
, CFSTR("update"));
2226 if (isArray(value
)) {
2227 updateCount
= CFArrayGetCount((CFArrayRef
)value
);
2228 secdebug("validupdate", "processing %ld updates", (long)updateCount
);
2229 for (CFIndex updateIX
=0; updateIX
<updateCount
; updateIX
++) {
2230 CFDictionaryRef dict
= (CFDictionaryRef
)CFArrayGetValueAtIndex((CFArrayRef
)value
, updateIX
);
2231 if (isDictionary(dict
)) {
2232 (void)_SecRevocationDbApplyGroupUpdate(rdb
, dict
, &localError
);
2233 CFReleaseNull(localError
);
2237 CFRelease(localUpdate
);
2240 _SecRevocationDbSetVersion(rdb
, version
);
2242 /* set db_version if not already set */
2243 int64_t db_version
= _SecRevocationDbGetSchemaVersion(rdb
, NULL
);
2244 if (db_version
<= 0) {
2245 _SecRevocationDbSetSchemaVersion(rdb
, kSecRevocationDbSchemaVersion
);
2248 /* set db_format if not already set */
2249 int64_t db_format
= _SecRevocationDbGetUpdateFormat(rdb
, NULL
);
2250 if (db_format
<= 0) {
2251 _SecRevocationDbSetUpdateFormat(rdb
, kSecRevocationDbUpdateFormat
);
2254 /* compact the db (must be done outside transaction scope) */
2255 (void)SecDbPerformWrite(rdb
->db
, &localError
, ^(SecDbConnectionRef dbconn
) {
2256 SecDbExec(dbconn
, CFSTR("VACUUM"), &localError
);
2257 CFReleaseNull(localError
);
2260 rdb
->updateInProgress
= false;
2263 static bool _SecRevocationDbSerialInGroup(SecRevocationDbRef rdb
,
2266 CFErrorRef
*error
) {
2267 __block
bool result
= false;
2268 __block
bool ok
= true;
2269 __block CFErrorRef localError
= NULL
;
2270 require(rdb
&& serial
, errOut
);
2271 ok
&= SecDbPerformRead(rdb
->db
, &localError
, ^(SecDbConnectionRef dbconn
) {
2272 ok
&= SecDbWithSQL(dbconn
, selectSerialRecordSQL
, &localError
, ^bool(sqlite3_stmt
*selectSerial
) {
2273 ok
&= SecDbBindInt64(selectSerial
, 1, groupId
, &localError
);
2274 ok
&= SecDbBindBlob(selectSerial
, 2, CFDataGetBytePtr(serial
),
2275 CFDataGetLength(serial
), SQLITE_TRANSIENT
, &localError
);
2276 ok
&= SecDbStep(dbconn
, selectSerial
, &localError
, ^(bool *stop
) {
2277 int64_t foundRowId
= (int64_t)sqlite3_column_int64(selectSerial
, 0);
2278 result
= (foundRowId
> 0);
2285 (void) CFErrorPropagate(localError
, error
);
2289 static bool _SecRevocationDbCertHashInGroup(SecRevocationDbRef rdb
,
2292 CFErrorRef
*error
) {
2293 __block
bool result
= false;
2294 __block
bool ok
= true;
2295 __block CFErrorRef localError
= NULL
;
2296 require(rdb
&& certHash
, errOut
);
2297 ok
&= SecDbPerformRead(rdb
->db
, &localError
, ^(SecDbConnectionRef dbconn
) {
2298 ok
&= SecDbWithSQL(dbconn
, selectHashRecordSQL
, &localError
, ^bool(sqlite3_stmt
*selectHash
) {
2299 ok
&= SecDbBindInt64(selectHash
, 1, groupId
, &localError
);
2300 ok
= SecDbBindBlob(selectHash
, 2, CFDataGetBytePtr(certHash
),
2301 CFDataGetLength(certHash
), SQLITE_TRANSIENT
, &localError
);
2302 ok
&= SecDbStep(dbconn
, selectHash
, &localError
, ^(bool *stop
) {
2303 int64_t foundRowId
= (int64_t)sqlite3_column_int64(selectHash
, 0);
2304 result
= (foundRowId
> 0);
2311 (void) CFErrorPropagate(localError
, error
);
2315 static bool _SecRevocationDbSerialInFilter(SecRevocationDbRef rdb
,
2316 CFDataRef serialData
,
2317 CFDataRef xmlData
) {
2318 /* N-To-1 filter implementation.
2319 The 'xmlData' parameter is a flattened XML dictionary,
2320 containing 'xor' and 'params' keys. First order of
2321 business is to reconstitute the blob into components.
2323 bool result
= false;
2324 CFRetainSafe(xmlData
);
2325 CFDataRef propListData
= xmlData
;
2326 /* Expand data blob if needed */
2327 CFDataRef inflatedData
= copyInflatedData(propListData
);
2329 CFReleaseSafe(propListData
);
2330 propListData
= inflatedData
;
2332 CFDataRef
xor = NULL
;
2333 CFArrayRef params
= NULL
;
2334 CFPropertyListRef nto1
= CFPropertyListCreateWithData(kCFAllocatorDefault
, propListData
, 0, NULL
, NULL
);
2336 xor = (CFDataRef
)CFDictionaryGetValue((CFDictionaryRef
)nto1
, CFSTR("xor"));
2337 params
= (CFArrayRef
)CFDictionaryGetValue((CFDictionaryRef
)nto1
, CFSTR("params"));
2339 uint8_t *hash
= (xor) ? (uint8_t*)CFDataGetBytePtr(xor) : NULL
;
2340 CFIndex hashLen
= (hash
) ? CFDataGetLength(xor) : 0;
2341 uint8_t *serial
= (serialData
) ? (uint8_t*)CFDataGetBytePtr(serialData
) : NULL
;
2342 CFIndex serialLen
= (serial
) ? CFDataGetLength(serialData
) : 0;
2344 require(hash
&& serial
&& params
, errOut
);
2346 const uint32_t FNV_OFFSET_BASIS
= 2166136261;
2347 const uint32_t FNV_PRIME
= 16777619;
2348 bool notInHash
= false;
2349 CFIndex ix
, count
= CFArrayGetCount(params
);
2350 for (ix
= 0; ix
< count
; ix
++) {
2352 CFNumberRef cfnum
= (CFNumberRef
)CFArrayGetValueAtIndex(params
, ix
);
2353 if (!isNumber(cfnum
) ||
2354 !CFNumberGetValue(cfnum
, kCFNumberSInt32Type
, ¶m
)) {
2355 secinfo("validupdate", "error processing filter params at index %ld", (long)ix
);
2358 /* process one param */
2359 uint32_t hval
= FNV_OFFSET_BASIS
^ param
;
2360 CFIndex i
= serialLen
;
2362 hval
= ((hval
^ (serial
[--i
])) * FNV_PRIME
) & 0xFFFFFFFF;
2364 hval
= hval
% (hashLen
* 8);
2365 if ((hash
[hval
/8] & (1 << (hval
% 8))) == 0) {
2366 notInHash
= true; /* definitely not in hash */
2371 /* probabilistically might be in hash if we get here. */
2376 CFReleaseSafe(nto1
);
2377 CFReleaseSafe(propListData
);
2381 static SecValidInfoRef
_SecRevocationDbValidInfoForCertificate(SecRevocationDbRef rdb
,
2382 SecCertificateRef certificate
,
2383 CFDataRef issuerHash
,
2384 CFErrorRef
*error
) {
2385 __block CFErrorRef localError
= NULL
;
2386 __block SecValidInfoFlags flags
= 0;
2387 __block SecValidInfoFormat format
= kSecValidInfoFormatUnknown
;
2388 __block CFDataRef data
= NULL
;
2390 bool matched
= false;
2391 bool isOnList
= false;
2392 int64_t groupId
= 0;
2393 CFDataRef serial
= NULL
;
2394 CFDataRef certHash
= NULL
;
2395 SecValidInfoRef result
= NULL
;
2397 require((serial
= SecCertificateCopySerialNumberData(certificate
, NULL
)) != NULL
, errOut
);
2398 require((certHash
= SecCertificateCopySHA256Digest(certificate
)) != NULL
, errOut
);
2399 require((groupId
= _SecRevocationDbGroupIdForIssuerHash(rdb
, issuerHash
, &localError
)) > 0, errOut
);
2401 /* Look up the group record to determine flags and format. */
2402 format
= _SecRevocationDbGetGroupFormat(rdb
, groupId
, &flags
, &data
, &localError
);
2404 if (format
== kSecValidInfoFormatUnknown
) {
2405 /* No group record found for this issuer. */
2407 else if (format
== kSecValidInfoFormatSerial
) {
2408 /* Look up certificate's serial number in the serials table. */
2409 matched
= _SecRevocationDbSerialInGroup(rdb
, serial
, groupId
, &localError
);
2411 else if (format
== kSecValidInfoFormatSHA256
) {
2412 /* Look up certificate's SHA-256 hash in the hashes table. */
2413 matched
= _SecRevocationDbCertHashInGroup(rdb
, certHash
, groupId
, &localError
);
2415 else if (format
== kSecValidInfoFormatNto1
) {
2416 /* Perform a Bloom filter match against the serial. If matched is false,
2417 then the cert is definitely not in the list. But if matched is true,
2418 we don't know for certain, so we would need to check OCSP. */
2419 matched
= _SecRevocationDbSerialInFilter(rdb
, serial
, data
);
2423 /* Found a specific match for this certificate. */
2424 secdebug("validupdate", "Valid db matched certificate: %@, format=%d, flags=%lu",
2425 certHash
, format
, flags
);
2428 else if ((flags
& kSecValidInfoComplete
) && (flags
& kSecValidInfoAllowlist
)) {
2429 /* Not matching against a complete allowlist is equivalent to revocation. */
2430 secdebug("validupdate", "Valid db did NOT match certificate on allowlist: %@, format=%d, flags=%lu",
2431 certHash
, format
, flags
);
2434 else if ((!(flags
& kSecValidInfoComplete
)) && (format
> kSecValidInfoFormatUnknown
)) {
2435 /* Not matching against an incomplete list implies we need to check OCSP. */
2436 secdebug("validupdate", "Valid db did not find certificate on incomplete list: %@, format=%d, flags=%lu",
2437 certHash
, format
, flags
);
2442 /* Return SecValidInfo for a matched certificate. */
2443 result
= SecValidInfoCreate(format
, flags
, isOnList
, certHash
, issuerHash
, NULL
);
2446 if (result
&& SecIsAppleTrustAnchor(certificate
, 0)) {
2447 /* Prevent a catch-22. */
2448 secdebug("validupdate", "Valid db match for Apple trust anchor: %@, format=%d, flags=%lu",
2449 certHash
, format
, flags
);
2450 SecValidInfoRelease(result
);
2455 (void) CFErrorPropagate(localError
, error
);
2456 CFReleaseSafe(data
);
2457 CFReleaseSafe(certHash
);
2458 CFReleaseSafe(serial
);
2462 static SecValidInfoRef
_SecRevocationDbCopyMatching(SecRevocationDbRef db
,
2463 SecCertificateRef certificate
,
2464 SecCertificateRef issuer
) {
2465 SecValidInfoRef result
= NULL
;
2466 CFErrorRef error
= NULL
;
2467 CFDataRef issuerHash
= NULL
;
2469 require(certificate
&& issuer
, errOut
);
2470 require(issuerHash
= SecCertificateCopySHA256Digest(issuer
), errOut
);
2472 result
= _SecRevocationDbValidInfoForCertificate(db
, certificate
, issuerHash
, &error
);
2475 CFReleaseSafe(issuerHash
);
2476 CFReleaseSafe(error
);
2480 static dispatch_queue_t
_SecRevocationDbGetUpdateQueue(SecRevocationDbRef rdb
) {
2481 return (rdb
) ? rdb
->update_queue
: NULL
;
2485 /* Given a valid update dictionary, insert/replace or delete records
2486 in the revocation database. (This function is expected to be called only
2487 by the database maintainer, normally the system instance of trustd.)
2489 void SecRevocationDbApplyUpdate(CFDictionaryRef update
, CFIndex version
) {
2490 SecRevocationDbWith(^(SecRevocationDbRef db
) {
2491 _SecRevocationDbApplyUpdate(db
, update
, version
);
2495 /* Set the schema version for the revocation database.
2496 (This function is expected to be called only by the database maintainer,
2497 normally the system instance of trustd.)
2499 void SecRevocationDbSetSchemaVersion(CFIndex db_version
) {
2500 SecRevocationDbWith(^(SecRevocationDbRef db
) {
2501 _SecRevocationDbSetSchemaVersion(db
, db_version
);
2505 /* Set the update format for the revocation database.
2506 (This function is expected to be called only by the database maintainer,
2507 normally the system instance of trustd.)
2509 void SecRevocationDbSetUpdateFormat(CFIndex db_format
) {
2510 SecRevocationDbWith(^(SecRevocationDbRef db
) {
2511 _SecRevocationDbSetUpdateFormat(db
, db_format
);
2515 /* Set the update source for the revocation database.
2516 (This function is expected to be called only by the database
2517 maintainer, normally the system instance of trustd. If the
2518 caller does not have write access, this is a no-op.)
2520 void SecRevocationDbSetUpdateSource(CFStringRef updateSource
) {
2521 SecRevocationDbWith(^(SecRevocationDbRef db
) {
2522 _SecRevocationDbSetUpdateSource(db
, updateSource
);
2526 /* Return the update source as a retained CFStringRef.
2527 If the value cannot be obtained, NULL is returned.
2529 CFStringRef
SecRevocationDbCopyUpdateSource(void) {
2530 __block CFStringRef result
= NULL
;
2531 SecRevocationDbWith(^(SecRevocationDbRef db
) {
2532 result
= _SecRevocationDbCopyUpdateSource(db
, NULL
);
2537 /* Set the next update value for the revocation database.
2538 (This function is expected to be called only by the database
2539 maintainer, normally the system instance of trustd. If the
2540 caller does not have write access, this is a no-op.)
2542 void SecRevocationDbSetNextUpdateTime(CFAbsoluteTime nextUpdate
) {
2543 SecRevocationDbWith(^(SecRevocationDbRef db
) {
2544 _SecRevocationDbSetNextUpdateTime(db
, nextUpdate
);
2548 /* Return the next update value as a CFAbsoluteTime.
2549 If the value cannot be obtained, -1 is returned.
2551 CFAbsoluteTime
SecRevocationDbGetNextUpdateTime(void) {
2552 __block CFAbsoluteTime result
= -1;
2553 SecRevocationDbWith(^(SecRevocationDbRef db
) {
2554 result
= _SecRevocationDbGetNextUpdateTime(db
, NULL
);
2559 /* Return the serial background queue for database updates.
2560 If the queue cannot be obtained, NULL is returned.
2562 dispatch_queue_t
SecRevocationDbGetUpdateQueue(void) {
2563 __block dispatch_queue_t result
= NULL
;
2564 SecRevocationDbWith(^(SecRevocationDbRef db
) {
2565 result
= _SecRevocationDbGetUpdateQueue(db
);
2570 /* Remove all entries in the revocation database and reset its version to 0.
2571 (This function is expected to be called only by the database maintainer,
2572 normally the system instance of trustd.)
2574 void SecRevocationDbRemoveAllEntries(void) {
2575 SecRevocationDbWith(^(SecRevocationDbRef db
) {
2576 _SecRevocationDbRemoveAllEntries(db
);
2580 /* Release all connections to the revocation database.
2582 void SecRevocationDbReleaseAllConnections(void) {
2583 SecRevocationDbWith(^(SecRevocationDbRef db
) {
2584 SecDbReleaseAllConnections((db
) ? db
->db
: NULL
);
2588 /* === SecRevocationDb API === */
2590 /* Given a certificate and its issuer, returns a SecValidInfoRef if the
2591 valid database contains matching info; otherwise returns NULL.
2592 Caller must release the returned SecValidInfoRef by calling
2593 SecValidInfoRelease when finished.
2595 SecValidInfoRef
SecRevocationDbCopyMatching(SecCertificateRef certificate
,
2596 SecCertificateRef issuer
) {
2597 __block SecValidInfoRef result
= NULL
;
2598 SecRevocationDbWith(^(SecRevocationDbRef db
) {
2599 result
= _SecRevocationDbCopyMatching(db
, certificate
, issuer
);
2604 /* Return the current version of the revocation database.
2605 A version of 0 indicates an empty database which must be populated.
2606 If the version cannot be obtained, -1 is returned.
2608 CFIndex
SecRevocationDbGetVersion(void) {
2609 __block CFIndex result
= -1;
2610 SecRevocationDbWith(^(SecRevocationDbRef db
) {
2611 result
= (CFIndex
)_SecRevocationDbGetVersion(db
, NULL
);
2616 /* Return the current schema version of the revocation database.
2617 A version of 0 indicates an empty database which must be populated.
2618 If the schema version cannot be obtained, -1 is returned.
2620 CFIndex
SecRevocationDbGetSchemaVersion(void) {
2621 __block CFIndex result
= -1;
2622 SecRevocationDbWith(^(SecRevocationDbRef db
) {
2623 result
= (CFIndex
)_SecRevocationDbGetSchemaVersion(db
, NULL
);
2628 /* Return the current update format of the revocation database.
2629 A version of 0 indicates the format was unknown.
2630 If the update format cannot be obtained, -1 is returned.
2632 CFIndex
SecRevocationDbGetUpdateFormat(void) {
2633 __block CFIndex result
= -1;
2634 SecRevocationDbWith(^(SecRevocationDbRef db
) {
2635 result
= (CFIndex
)_SecRevocationDbGetUpdateFormat(db
, NULL
);