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
, (size_t)zs
.total_out
- (size_t)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 size_t bytesRemaining
= (p
) ? (size_t)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
)) {
624 secnotice("validupdate", "unable to write %s", semPathBuf
);
631 // exit as gracefully as possible so we can replace the database
632 secnotice("validupdate", "process exiting to replace db file");
633 dispatch_after(dispatch_time(DISPATCH_TIME_NOW
, 3ull*NSEC_PER_SEC
), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT
, 0), ^{
634 xpc_transaction_exit_clean();
639 // try to copy uncompressed database asset, if available
640 const char *validDbPathBuf
= SecOTAPKIGetValidDatabaseSnapshot(otapkiRef
);
641 if (validDbPathBuf
) {
642 WithPathInRevocationInfoDirectory(CFSTR(kSecRevocationDbFileName
), ^(const char *path
) {
643 secdebug("validupdate", "will copy data from \"%s\"", validDbPathBuf
);
644 copyfile_state_t state
= copyfile_state_alloc();
645 int retval
= copyfile(validDbPathBuf
, path
, state
, COPYFILE_DATA
);
646 copyfile_state_free(state
);
648 secnotice("validupdate", "copyfile error %d", retval
);
658 // see if compressed database asset is available
659 if (validDbPathBuf
) {
660 char *validDbCmpPathBuf
= NULL
;
661 asprintf(&validDbCmpPathBuf
, "%s%s", validDbPathBuf
, ".gz");
662 if (validDbCmpPathBuf
) {
663 secdebug("validupdate", "will read data from \"%s\"", validDbCmpPathBuf
);
664 if ((rtn
= readValidFile(validDbCmpPathBuf
, &data
)) != 0) {
667 secnotice("validupdate", "readValidFile error %d", rtn
);
669 free(validDbCmpPathBuf
);
672 result
= SecValidDatabaseFromCompressed(data
);
677 // unable to use database asset; try update asset
678 const char *validUpdatePathBuf
= SecOTAPKIGetValidUpdateSnapshot(otapkiRef
);
679 if (validUpdatePathBuf
) {
680 secdebug("validupdate", "will read data from \"%s\"", validUpdatePathBuf
);
681 if ((rtn
= readValidFile(validUpdatePathBuf
, &data
)) != 0) {
684 secnotice("validupdate", "readValidFile error %d", rtn
);
687 result
= SecValidUpdateFromCompressed(data
);
690 CFReleaseNull(otapkiRef
);
693 SecRevocationDbSetUpdateSource(server
);
694 gLastVersion
= SecRevocationDbGetVersion();
696 secdebug("validupdate", "local update to g%ld/v%ld complete at %f",
697 (long)SecRevocationDbGetUpdateFormat(), (long)gLastVersion
,
698 (double)CFAbsoluteTimeGetCurrent());
700 sNumLocalUpdates
= 0; // reset counter
705 static bool SecValidUpdateSchedule(bool updateEnabled
, CFStringRef server
, CFIndex version
) {
706 /* Check if we have a later version available locally */
707 if (SecValidUpdateSatisfiedLocally(server
, version
, false)) {
711 /* If update not permitted return */
712 if (!updateEnabled
) {
716 #if !TARGET_OS_BRIDGE
717 /* Schedule as a maintenance task */
718 return SecValidUpdateRequest(SecRevocationDbGetUpdateQueue(), server
, version
);
724 void SecRevocationDbInitialize() {
725 if (!isDbOwner()) { return; }
726 __block
bool initializeDb
= false;
728 /* create base path if it doesn't exist */
729 (void)mkpath_np(kSecRevocationBasePath
, 0755);
731 /* check semaphore file */
732 WithPathInRevocationInfoDirectory(CFSTR(kSecRevocationDbReplaceFile
), ^(const char *path
) {
734 if (stat(path
, &sb
) == 0) {
735 initializeDb
= true; /* file was found, so we will replace the database */
736 if (remove(path
) == -1) {
738 secnotice("validupdate", "remove (%s): %s", path
, strerror(error
));
744 WithPathInRevocationInfoDirectory(CFSTR(kSecRevocationDbFileName
), ^(const char *path
) {
746 /* remove old database file(s) */
747 (void)removeFileWithSuffix(path
, "");
748 (void)removeFileWithSuffix(path
, "-journal");
749 (void)removeFileWithSuffix(path
, "-shm");
750 (void)removeFileWithSuffix(path
, "-wal");
754 if (stat(path
, &sb
) == -1) {
755 initializeDb
= true; /* file not found, so we will create the database */
761 return; /* database exists and doesn't need replacing */
764 /* initialize database from local asset */
765 CFTypeRef value
= (CFStringRef
)CFPreferencesCopyValue(kUpdateServerKey
, kSecPrefsDomain
, kCFPreferencesAnyUser
, kCFPreferencesCurrentHost
);
766 CFStringRef server
= (isString(value
)) ? (CFStringRef
)value
: (CFStringRef
)kValidUpdateServer
;
768 secnotice("validupdate", "initializing database");
769 if (!SecValidUpdateSatisfiedLocally(server
, version
, true)) {
770 #if !TARGET_OS_BRIDGE
771 /* Schedule full update as a maintenance task */
772 (void)SecValidUpdateRequest(SecRevocationDbGetUpdateQueue(), server
, version
);
775 CFReleaseSafe(value
);
780 // MARK: SecValidInfoRef
782 /* ======================================================================
784 ======================================================================
787 static SecValidInfoRef
SecValidInfoCreate(SecValidInfoFormat format
,
791 CFDataRef issuerHash
,
792 CFDataRef anchorHash
) {
793 SecValidInfoRef validInfo
;
794 validInfo
= (SecValidInfoRef
)calloc(1, sizeof(struct __SecValidInfo
));
795 if (!validInfo
) { return NULL
; }
797 CFRetainSafe(certHash
);
798 CFRetainSafe(issuerHash
);
799 validInfo
->format
= format
;
800 validInfo
->certHash
= certHash
;
801 validInfo
->issuerHash
= issuerHash
;
802 validInfo
->anchorHash
= anchorHash
;
803 validInfo
->isOnList
= isOnList
;
804 validInfo
->valid
= (flags
& kSecValidInfoAllowlist
);
805 validInfo
->complete
= (flags
& kSecValidInfoComplete
);
806 validInfo
->checkOCSP
= (flags
& kSecValidInfoCheckOCSP
);
807 validInfo
->knownOnly
= (flags
& kSecValidInfoKnownOnly
);
808 validInfo
->requireCT
= (flags
& kSecValidInfoRequireCT
);
809 validInfo
->noCACheck
= (flags
& kSecValidInfoNoCACheck
);
814 void SecValidInfoRelease(SecValidInfoRef validInfo
) {
816 CFReleaseSafe(validInfo
->certHash
);
817 CFReleaseSafe(validInfo
->issuerHash
);
818 CFReleaseSafe(validInfo
->anchorHash
);
823 void SecValidInfoSetAnchor(SecValidInfoRef validInfo
, SecCertificateRef anchor
) {
827 CFDataRef anchorHash
= NULL
;
829 anchorHash
= SecCertificateCopySHA256Digest(anchor
);
831 /* clear no-ca flag for anchors where we want OCSP checked [32523118] */
832 if (SecIsAppleTrustAnchor(anchor
, 0)) {
833 validInfo
->noCACheck
= false;
836 CFReleaseNull(validInfo
->anchorHash
);
837 validInfo
->anchorHash
= anchorHash
;
842 // MARK: SecRevocationDb
844 /* ======================================================================
846 ======================================================================
849 /* SecRevocationDbCheckNextUpdate returns true if we dispatched an
850 update request, otherwise false.
852 static bool _SecRevocationDbCheckNextUpdate(void) {
853 // are we the db owner instance?
857 CFTypeRef value
= NULL
;
859 // is it time to check?
860 CFAbsoluteTime now
= CFAbsoluteTimeGetCurrent();
861 CFAbsoluteTime minNextUpdate
= now
+ gUpdateInterval
;
862 gUpdateStarted
= now
;
864 if (0 == gNextUpdate
) {
865 // first time we're called, check if we have a saved nextUpdate value
866 gNextUpdate
= SecRevocationDbGetNextUpdateTime();
868 if (gNextUpdate
< minNextUpdate
) {
869 gNextUpdate
= minNextUpdate
;
871 // allow pref to override update interval, if it exists
872 CFIndex interval
= -1;
873 value
= (CFNumberRef
)CFPreferencesCopyValue(kUpdateIntervalKey
, kSecPrefsDomain
, kCFPreferencesAnyUser
, kCFPreferencesCurrentHost
);
874 if (isNumber(value
)) {
875 if (CFNumberGetValue((CFNumberRef
)value
, kCFNumberCFIndexType
, &interval
)) {
876 if (interval
< kSecMinUpdateInterval
) {
877 interval
= kSecMinUpdateInterval
;
878 } else if (interval
> kSecMaxUpdateInterval
) {
879 interval
= kSecMaxUpdateInterval
;
883 CFReleaseNull(value
);
884 gUpdateInterval
= kSecStdUpdateInterval
;
886 gUpdateInterval
= interval
;
888 // pin next update time to the preferred update interval
889 if (gNextUpdate
> (gUpdateStarted
+ gUpdateInterval
)) {
890 gNextUpdate
= gUpdateStarted
+ gUpdateInterval
;
892 secdebug("validupdate", "next update at %f (in %f seconds)",
893 (double)gUpdateStarted
, (double)gNextUpdate
-gUpdateStarted
);
895 if (gNextUpdate
> now
) {
899 secnotice("validupdate", "starting update");
901 // set minimum next update time here in case we can't get an update
902 gNextUpdate
= minNextUpdate
;
904 // determine which server to query
906 value
= (CFStringRef
)CFPreferencesCopyValue(kUpdateServerKey
, kSecPrefsDomain
, kCFPreferencesAnyUser
, kCFPreferencesCurrentHost
);
907 if (isString(value
)) {
908 server
= (CFStringRef
) CFRetain(value
);
910 server
= (CFStringRef
) CFRetain(kValidUpdateServer
);
912 CFReleaseNull(value
);
914 // determine version of our current database
915 CFIndex version
= SecRevocationDbGetVersion();
916 secdebug("validupdate", "got version %ld from db", (long)version
);
918 if (gLastVersion
> 0) {
919 secdebug("validupdate", "error getting version; using last good version: %ld", (long)gLastVersion
);
921 version
= gLastVersion
;
924 // determine source of our current database
925 // (if this ever changes, we will need to reload the db)
926 CFStringRef db_source
= SecRevocationDbCopyUpdateSource();
928 db_source
= (CFStringRef
) CFRetain(kValidUpdateServer
);
931 // determine whether we need to recreate the database
932 CFIndex db_version
= SecRevocationDbGetSchemaVersion();
933 CFIndex db_format
= SecRevocationDbGetUpdateFormat();
934 if (db_version
< kSecRevocationDbSchemaVersion
||
935 db_format
< kSecRevocationDbUpdateFormat
||
936 kCFCompareEqualTo
!= CFStringCompare(server
, db_source
, kCFCompareCaseInsensitive
)) {
937 /* we need to fully rebuild the db contents. */
938 SecRevocationDbRemoveAllEntries();
939 version
= gLastVersion
= 0;
942 // determine whether update fetching is enabled
943 #if (__MAC_OS_X_VERSION_MIN_REQUIRED >= 101300 || __IPHONE_OS_VERSION_MIN_REQUIRED >= 110000)
944 bool updateEnabled
= true; // macOS 10.13 or iOS 11.0
946 bool updateEnabled
= false;
948 value
= (CFBooleanRef
)CFPreferencesCopyValue(kUpdateEnabledKey
, kSecPrefsDomain
, kCFPreferencesAnyUser
, kCFPreferencesCurrentHost
);
949 if (isBoolean(value
)) {
950 updateEnabled
= CFBooleanGetValue((CFBooleanRef
)value
);
952 CFReleaseNull(value
);
954 // Schedule maintenance work
955 bool result
= SecValidUpdateSchedule(updateEnabled
, server
, version
);
956 CFReleaseNull(server
);
957 CFReleaseNull(db_source
);
961 void SecRevocationDbCheckNextUpdate(void) {
962 static dispatch_once_t once
;
963 static sec_action_t action
;
965 dispatch_once(&once
, ^{
966 dispatch_queue_t update_queue
= SecRevocationDbGetUpdateQueue();
967 action
= sec_action_create_with_queue(update_queue
, "update_check", kSecMinUpdateInterval
);
968 sec_action_set_handler(action
, ^{
969 (void)_SecRevocationDbCheckNextUpdate();
972 sec_action_perform(action
);
975 /* This function verifies an update, in this format:
976 1) unsigned 32-bit network-byte-order length of binary plist
978 3) unsigned 32-bit network-byte-order length of CMS message
979 4) CMS message (containing certificates and signature over binary plist)
981 The length argument is the total size of the packed update data.
983 bool SecRevocationDbVerifyUpdate(void *update
, CFIndex length
) {
984 if (!update
|| length
<= (CFIndex
)sizeof(uint32_t)) {
987 uint32_t plistLength
= OSSwapInt32(*((uint32_t *)update
));
988 if ((plistLength
+ (CFIndex
)(sizeof(uint32_t)*2)) > (uint64_t) length
) {
989 secdebug("validupdate", "ERROR: reported plist length (%lu)+%lu exceeds total length (%lu)\n",
990 (unsigned long)plistLength
, (unsigned long)sizeof(uint32_t)*2, (unsigned long)length
);
993 uint8_t *plistData
= (uint8_t *)update
+ sizeof(uint32_t);
994 uint8_t *sigData
= (uint8_t *)plistData
+ plistLength
;
995 uint32_t sigLength
= OSSwapInt32(*((uint32_t *)sigData
));
996 sigData
+= sizeof(uint32_t);
997 if ((plistLength
+ sigLength
+ (CFIndex
)(sizeof(uint32_t) * 2)) != (uint64_t) length
) {
998 secdebug("validupdate", "ERROR: reported lengths do not add up to total length\n");
1002 OSStatus status
= 0;
1003 CMSSignerStatus signerStatus
;
1004 CMSDecoderRef cms
= NULL
;
1005 SecPolicyRef policy
= NULL
;
1006 SecTrustRef trust
= NULL
;
1007 CFDataRef content
= NULL
;
1009 if ((content
= CFDataCreateWithBytesNoCopy(kCFAllocatorDefault
,
1010 (const UInt8
*)plistData
, (CFIndex
)plistLength
, kCFAllocatorNull
)) == NULL
) {
1011 secdebug("validupdate", "CFDataCreateWithBytesNoCopy failed (%ld bytes)\n", (long)plistLength
);
1015 if ((status
= CMSDecoderCreate(&cms
)) != errSecSuccess
) {
1016 secdebug("validupdate", "CMSDecoderCreate failed with error %d\n", (int)status
);
1019 if ((status
= CMSDecoderUpdateMessage(cms
, sigData
, sigLength
)) != errSecSuccess
) {
1020 secdebug("validupdate", "CMSDecoderUpdateMessage failed with error %d\n", (int)status
);
1023 if ((status
= CMSDecoderSetDetachedContent(cms
, content
)) != errSecSuccess
) {
1024 secdebug("validupdate", "CMSDecoderSetDetachedContent failed with error %d\n", (int)status
);
1027 if ((status
= CMSDecoderFinalizeMessage(cms
)) != errSecSuccess
) {
1028 secdebug("validupdate", "CMSDecoderFinalizeMessage failed with error %d\n", (int)status
);
1032 policy
= SecPolicyCreateApplePinned(CFSTR("ValidUpdate"), // kSecPolicyNameAppleValidUpdate
1033 CFSTR("1.2.840.113635.100.6.2.10"), // System Integration 2 Intermediate Certificate
1034 CFSTR("1.2.840.113635.100.6.51")); // Valid update signing OID
1036 // Check that the first signer actually signed this message.
1037 if ((status
= CMSDecoderCopySignerStatus(cms
, 0, policy
,
1038 false, &signerStatus
, &trust
, NULL
)) != errSecSuccess
) {
1039 secdebug("validupdate", "CMSDecoderCopySignerStatus failed with error %d\n", (int)status
);
1042 // Make sure the signature verifies against the detached content
1043 if (signerStatus
!= kCMSSignerValid
) {
1044 secdebug("validupdate", "ERROR: signature did not verify (signer status %d)\n", (int)signerStatus
);
1045 status
= errSecInvalidSignature
;
1048 // Make sure the signing certificate is valid for the specified policy
1049 SecTrustResultType trustResult
= kSecTrustResultInvalid
;
1050 status
= SecTrustEvaluate(trust
, &trustResult
);
1051 if (status
!= errSecSuccess
) {
1052 secdebug("validupdate", "SecTrustEvaluate failed with error %d (trust=%p)\n", (int)status
, (void *)trust
);
1053 } else if (!(trustResult
== kSecTrustResultUnspecified
|| trustResult
== kSecTrustResultProceed
)) {
1054 secdebug("validupdate", "SecTrustEvaluate failed with trust result %d\n", (int)trustResult
);
1055 status
= errSecVerificationFailure
;
1060 CFReleaseSafe(content
);
1061 CFReleaseSafe(trust
);
1062 CFReleaseSafe(policy
);
1065 return (status
== errSecSuccess
);
1068 CFAbsoluteTime
SecRevocationDbComputeNextUpdateTime(CFIndex updateInterval
) {
1069 CFIndex interval
= updateInterval
;
1070 // try to use interval preference if it exists
1071 CFTypeRef value
= (CFNumberRef
)CFPreferencesCopyValue(kUpdateIntervalKey
, kSecPrefsDomain
, kCFPreferencesAnyUser
, kCFPreferencesCurrentHost
);
1072 if (isNumber(value
)) {
1073 CFNumberGetValue((CFNumberRef
)value
, kCFNumberCFIndexType
, &interval
);
1075 CFReleaseNull(value
);
1077 if (interval
<= 0) {
1078 interval
= kSecStdUpdateInterval
;
1082 if (interval
< kSecMinUpdateInterval
) {
1083 interval
= kSecMinUpdateInterval
;
1084 } else if (interval
> kSecMaxUpdateInterval
) {
1085 interval
= kSecMaxUpdateInterval
;
1088 // compute randomization factor, between 0 and 50% of the interval
1089 CFIndex fuzz
= arc4random() % (long)(interval
/2.0);
1090 CFAbsoluteTime nextUpdate
= CFAbsoluteTimeGetCurrent() + interval
+ fuzz
;
1091 secdebug("validupdate", "next update in %ld seconds", (long)(interval
+ fuzz
));
1095 void SecRevocationDbComputeAndSetNextUpdateTime(void) {
1096 gNextUpdate
= SecRevocationDbComputeNextUpdateTime(0);
1097 SecRevocationDbSetNextUpdateTime(gNextUpdate
);
1098 gUpdateStarted
= 0; /* no update is currently in progress */
1101 CFIndex
SecRevocationDbIngestUpdate(CFDictionaryRef update
, CFIndex chunkVersion
) {
1102 CFIndex version
= 0;
1106 CFTypeRef value
= (CFNumberRef
)CFDictionaryGetValue(update
, CFSTR("version"));
1107 if (isNumber(value
)) {
1108 if (!CFNumberGetValue((CFNumberRef
)value
, kCFNumberCFIndexType
, &version
)) {
1113 // only the first chunk will have a version, so the second and
1114 // subsequent chunks will need to pass it in chunkVersion.
1115 version
= chunkVersion
;
1117 CFIndex curVersion
= SecRevocationDbGetVersion();
1118 if (version
> curVersion
|| chunkVersion
> 0) {
1119 SecRevocationDbApplyUpdate(update
, version
);
1121 secdebug("validupdate", "we have v%ld, skipping update to v%ld",
1122 (long)curVersion
, (long)version
);
1123 version
= -1; // invalid, so we know to skip subsequent chunks
1129 /* Database schema */
1131 /* admin table holds these key-value (or key-ival) pairs:
1132 'version' (integer) // version of database content
1133 'check_again' (double) // CFAbsoluteTime of next check (optional; this value is currently stored in prefs)
1134 'db_version' (integer) // version of database schema
1135 'db_hash' (blob) // SHA-256 database hash
1136 --> entries in admin table are unique by text key
1138 issuers table holds map of issuing CA hashes to group identifiers:
1139 groupid (integer) // associated group identifier in group ID table
1140 issuer_hash (blob) // SHA-256 hash of issuer certificate (primary key)
1141 --> entries in issuers table are unique by issuer_hash;
1142 multiple issuer entries may have the same groupid!
1144 groups table holds records with these attributes:
1145 groupid (integer) // ordinal ID associated with this group entry
1146 flags (integer) // a bitmask of the following values:
1147 kSecValidInfoComplete (0x00000001) set if we have all revocation info for this issuer group
1148 kSecValidInfoCheckOCSP (0x00000002) set if must check ocsp for certs from this issuer group
1149 kSecValidInfoKnownOnly (0x00000004) set if any CA from this issuer group must be in database
1150 kSecValidInfoRequireCT (0x00000008) set if all certs from this issuer group must have SCTs
1151 kSecValidInfoAllowlist (0x00000010) set if this entry describes valid certs (i.e. is allowed)
1152 kSecValidInfoNoCACheck (0x00000020) set if this entry does not require an OCSP check to accept
1153 format (integer) // an integer describing format of entries:
1154 kSecValidInfoFormatUnknown (0) unknown format
1155 kSecValidInfoFormatSerial (1) serial number, not greater than 20 bytes in length
1156 kSecValidInfoFormatSHA256 (2) SHA-256 hash, 32 bytes in length
1157 kSecValidInfoFormatNto1 (3) filter data blob of arbitrary length
1158 data (blob) // Bloom filter data if format is 'nto1', otherwise NULL
1159 --> entries in groups table are unique by groupid
1161 serials table holds serial number blobs with these attributes:
1162 rowid (integer) // ordinal ID associated with this serial number entry
1163 groupid (integer) // identifier for issuer group in the groups table
1164 serial (blob) // serial number
1165 --> entries in serials table are unique by serial and groupid
1167 hashes table holds SHA-256 hashes of certificates with these attributes:
1168 rowid (integer) // ordinal ID associated with this sha256 hash entry
1169 groupid (integer) // identifier for issuer group in the groups table
1170 sha256 (blob) // SHA-256 hash of subject certificate
1171 --> entries in hashes table are unique by sha256 and groupid
1173 #define createTablesSQL CFSTR("CREATE TABLE admin(" \
1174 "key TEXT PRIMARY KEY NOT NULL," \
1175 "ival INTEGER NOT NULL," \
1178 "CREATE TABLE issuers(" \
1179 "groupid INTEGER NOT NULL," \
1180 "issuer_hash BLOB PRIMARY KEY NOT NULL" \
1182 "CREATE INDEX issuer_idx ON issuers(issuer_hash);" \
1183 "CREATE TABLE groups(" \
1184 "groupid INTEGER PRIMARY KEY AUTOINCREMENT," \
1189 "CREATE TABLE serials(" \
1190 "rowid INTEGER PRIMARY KEY AUTOINCREMENT," \
1191 "groupid INTEGER NOT NULL," \
1192 "serial BLOB NOT NULL," \
1193 "UNIQUE(groupid,serial)" \
1195 "CREATE TABLE hashes(" \
1196 "rowid INTEGER PRIMARY KEY AUTOINCREMENT," \
1197 "groupid INTEGER NOT NULL," \
1198 "sha256 BLOB NOT NULL," \
1199 "UNIQUE(groupid,sha256)" \
1201 "CREATE TRIGGER group_del BEFORE DELETE ON groups FOR EACH ROW " \
1203 "DELETE FROM serials WHERE groupid=OLD.groupid; " \
1204 "DELETE FROM hashes WHERE groupid=OLD.groupid; " \
1205 "DELETE FROM issuers WHERE groupid=OLD.groupid; " \
1208 #define selectGroupIdSQL CFSTR("SELECT DISTINCT groupid " \
1209 "FROM issuers WHERE issuer_hash=?")
1210 #define selectVersionSQL CFSTR("SELECT ival FROM admin " \
1211 "WHERE key='version'")
1212 #define selectDbVersionSQL CFSTR("SELECT ival FROM admin " \
1213 "WHERE key='db_version'")
1214 #define selectDbFormatSQL CFSTR("SELECT ival FROM admin " \
1215 "WHERE key='db_format'")
1216 #define selectDbHashSQL CFSTR("SELECT value FROM admin " \
1217 "WHERE key='db_hash'")
1218 #define selectDbSourceSQL CFSTR("SELECT value FROM admin " \
1219 "WHERE key='db_source'")
1220 #define selectNextUpdateSQL CFSTR("SELECT value FROM admin " \
1221 "WHERE key='check_again'")
1222 #define selectGroupRecordSQL CFSTR("SELECT flags,format,data FROM " \
1223 "groups WHERE groupid=?")
1224 #define selectSerialRecordSQL CFSTR("SELECT rowid FROM serials " \
1225 "WHERE groupid=? AND serial=?")
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 insertSha256RecordSQL CFSTR("INSERT OR REPLACE INTO hashes " \
1237 "(groupid,sha256) VALUES (?,?)")
1238 #define deleteGroupRecordSQL CFSTR("DELETE FROM groups WHERE groupid=?")
1240 #define deleteAllEntriesSQL CFSTR("DELETE from hashes; " \
1241 "DELETE from serials; DELETE from issuers; DELETE from groups; " \
1242 "DELETE from admin; DELETE from sqlite_sequence")
1243 #define deleteTablesSQL CFSTR("DROP TABLE hashes; " \
1244 "DROP TABLE serials; DROP TABLE issuers; DROP TABLE groups; " \
1245 "DROP TABLE admin; DELETE from sqlite_sequence")
1247 /* Database management */
1249 static SecDbRef
SecRevocationDbCreate(CFStringRef path
) {
1250 /* only the db owner should open a read-write connection. */
1251 bool readWrite
= isDbOwner();
1254 SecDbRef result
= SecDbCreateWithOptions(path
, mode
, readWrite
, false, false, ^bool (SecDbRef db
, SecDbConnectionRef dbconn
, bool didCreate
, bool *callMeAgainForNextConnection
, CFErrorRef
*error
) {
1255 __block
bool ok
= true;
1256 CFErrorRef localError
= NULL
;
1257 if (ok
&& !SecDbWithSQL(dbconn
, selectGroupIdSQL
, &localError
, NULL
) && CFErrorGetCode(localError
) == SQLITE_ERROR
) {
1258 /* SecDbWithSQL returns SQLITE_ERROR if the table we are preparing the above statement for doesn't exist. */
1260 /* Create all database tables, indexes, and triggers. */
1261 ok
&= SecDbTransaction(dbconn
, kSecDbExclusiveTransactionType
, error
, ^(bool *commit
) {
1262 ok
= SecDbExec(dbconn
, createTablesSQL
, error
);
1266 CFReleaseSafe(localError
);
1268 secerror("%s failed: %@", didCreate
? "Create" : "Open", error
? *error
: NULL
);
1275 typedef struct __SecRevocationDb
*SecRevocationDbRef
;
1276 struct __SecRevocationDb
{
1278 dispatch_queue_t update_queue
;
1279 bool updateInProgress
;
1280 bool unsupportedVersion
;
1283 static dispatch_once_t kSecRevocationDbOnce
;
1284 static SecRevocationDbRef kSecRevocationDb
= NULL
;
1286 static SecRevocationDbRef
SecRevocationDbInit(CFStringRef db_name
) {
1287 SecRevocationDbRef rdb
;
1288 dispatch_queue_attr_t attr
;
1290 require(rdb
= (SecRevocationDbRef
)malloc(sizeof(struct __SecRevocationDb
)), errOut
);
1292 rdb
->update_queue
= NULL
;
1293 rdb
->updateInProgress
= false;
1294 rdb
->unsupportedVersion
= false;
1296 require(rdb
->db
= SecRevocationDbCreate(db_name
), errOut
);
1297 attr
= dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL
, QOS_CLASS_BACKGROUND
, 0);
1298 require(rdb
->update_queue
= dispatch_queue_create(NULL
, attr
), errOut
);
1303 secdebug("validupdate", "Failed to create db at \"%@\"", db_name
);
1305 if (rdb
->update_queue
) {
1306 dispatch_release(rdb
->update_queue
);
1308 CFReleaseSafe(rdb
->db
);
1314 static CFStringRef
SecRevocationDbCopyPath(void) {
1315 CFURLRef revDbURL
= NULL
;
1316 CFStringRef revInfoRelPath
= NULL
;
1317 if ((revInfoRelPath
= CFStringCreateWithFormat(NULL
, NULL
, CFSTR("%s"), kSecRevocationDbFileName
)) != NULL
) {
1318 revDbURL
= SecCopyURLForFileInRevocationInfoDirectory(revInfoRelPath
);
1320 CFReleaseSafe(revInfoRelPath
);
1322 CFStringRef revDbPath
= NULL
;
1324 revDbPath
= CFURLCopyFileSystemPath(revDbURL
, kCFURLPOSIXPathStyle
);
1325 CFRelease(revDbURL
);
1330 static void SecRevocationDbWith(void(^dbJob
)(SecRevocationDbRef db
)) {
1331 dispatch_once(&kSecRevocationDbOnce
, ^{
1332 CFStringRef dbPath
= SecRevocationDbCopyPath();
1334 kSecRevocationDb
= SecRevocationDbInit(dbPath
);
1338 // Do pre job run work here (cancel idle timers etc.)
1339 if (kSecRevocationDb
->updateInProgress
) {
1340 return; // this would block since SecDb has an exclusive transaction lock
1342 dbJob(kSecRevocationDb
);
1343 // Do post job run work here (gc timer, etc.)
1346 static int64_t _SecRevocationDbGetVersion(SecRevocationDbRef rdb
, CFErrorRef
*error
) {
1347 /* look up version entry in admin table; returns -1 on error */
1348 __block
int64_t version
= -1;
1349 __block
bool ok
= true;
1350 __block CFErrorRef localError
= NULL
;
1352 ok
&= SecDbPerformRead(rdb
->db
, &localError
, ^(SecDbConnectionRef dbconn
) {
1353 if (ok
) ok
&= SecDbWithSQL(dbconn
, selectVersionSQL
, &localError
, ^bool(sqlite3_stmt
*selectVersion
) {
1354 ok
= SecDbStep(dbconn
, selectVersion
, &localError
, NULL
);
1355 version
= sqlite3_column_int64(selectVersion
, 0);
1359 (void) CFErrorPropagate(localError
, error
);
1363 static void _SecRevocationDbSetVersion(SecRevocationDbRef rdb
, CFIndex version
){
1364 secdebug("validupdate", "setting version to %ld", (long)version
);
1366 __block CFErrorRef localError
= NULL
;
1367 __block
bool ok
= true;
1368 ok
&= SecDbPerformWrite(rdb
->db
, &localError
, ^(SecDbConnectionRef dbconn
) {
1369 ok
&= SecDbTransaction(dbconn
, kSecDbExclusiveTransactionType
, &localError
, ^(bool *commit
) {
1370 if (ok
) ok
= SecDbWithSQL(dbconn
, insertAdminRecordSQL
, &localError
, ^bool(sqlite3_stmt
*insertVersion
) {
1372 const char *versionKey
= "version";
1373 ok
= SecDbBindText(insertVersion
, 1, versionKey
, strlen(versionKey
),
1374 SQLITE_TRANSIENT
, &localError
);
1377 ok
= SecDbBindInt64(insertVersion
, 2,
1378 (sqlite3_int64
)version
, &localError
);
1381 ok
= SecDbStep(dbconn
, insertVersion
, &localError
, NULL
);
1388 secerror("_SecRevocationDbSetVersion failed: %@", localError
);
1390 CFReleaseSafe(localError
);
1393 static int64_t _SecRevocationDbGetSchemaVersion(SecRevocationDbRef rdb
, CFErrorRef
*error
) {
1394 /* look up db_version entry in admin table; returns -1 on error */
1395 __block
int64_t db_version
= -1;
1396 __block
bool ok
= true;
1397 __block CFErrorRef localError
= NULL
;
1399 ok
&= SecDbPerformRead(rdb
->db
, &localError
, ^(SecDbConnectionRef dbconn
) {
1400 if (ok
) ok
&= SecDbWithSQL(dbconn
, selectDbVersionSQL
, &localError
, ^bool(sqlite3_stmt
*selectDbVersion
) {
1401 ok
= SecDbStep(dbconn
, selectDbVersion
, &localError
, NULL
);
1402 db_version
= sqlite3_column_int64(selectDbVersion
, 0);
1406 (void) CFErrorPropagate(localError
, error
);
1410 static void _SecRevocationDbSetSchemaVersion(SecRevocationDbRef rdb
, CFIndex dbversion
) {
1411 secdebug("validupdate", "setting db_version to %ld", (long)dbversion
);
1413 __block CFErrorRef localError
= NULL
;
1414 __block
bool ok
= true;
1415 ok
&= SecDbPerformWrite(rdb
->db
, &localError
, ^(SecDbConnectionRef dbconn
) {
1416 ok
&= SecDbTransaction(dbconn
, kSecDbExclusiveTransactionType
, &localError
, ^(bool *commit
) {
1417 if (ok
) ok
= SecDbWithSQL(dbconn
, insertAdminRecordSQL
, &localError
, ^bool(sqlite3_stmt
*insertDbVersion
) {
1419 const char *dbVersionKey
= "db_version";
1420 ok
= SecDbBindText(insertDbVersion
, 1, dbVersionKey
, strlen(dbVersionKey
),
1421 SQLITE_TRANSIENT
, &localError
);
1424 ok
= SecDbBindInt64(insertDbVersion
, 2,
1425 (sqlite3_int64
)dbversion
, &localError
);
1428 ok
= SecDbStep(dbconn
, insertDbVersion
, &localError
, NULL
);
1435 secerror("_SecRevocationDbSetSchemaVersion failed: %@", localError
);
1437 rdb
->unsupportedVersion
= false;
1439 CFReleaseSafe(localError
);
1442 static int64_t _SecRevocationDbGetUpdateFormat(SecRevocationDbRef rdb
, CFErrorRef
*error
) {
1443 /* look up db_format entry in admin table; returns -1 on error */
1444 __block
int64_t db_format
= -1;
1445 __block
bool ok
= true;
1446 __block CFErrorRef localError
= NULL
;
1448 ok
&= SecDbPerformRead(rdb
->db
, &localError
, ^(SecDbConnectionRef dbconn
) {
1449 if (ok
) ok
&= SecDbWithSQL(dbconn
, selectDbFormatSQL
, &localError
, ^bool(sqlite3_stmt
*selectDbFormat
) {
1450 ok
= SecDbStep(dbconn
, selectDbFormat
, &localError
, NULL
);
1451 db_format
= sqlite3_column_int64(selectDbFormat
, 0);
1455 (void) CFErrorPropagate(localError
, error
);
1459 static void _SecRevocationDbSetUpdateFormat(SecRevocationDbRef rdb
, CFIndex dbformat
) {
1460 secdebug("validupdate", "setting db_format to %ld", (long)dbformat
);
1462 __block CFErrorRef localError
= NULL
;
1463 __block
bool ok
= true;
1464 ok
&= SecDbPerformWrite(rdb
->db
, &localError
, ^(SecDbConnectionRef dbconn
) {
1465 ok
&= SecDbTransaction(dbconn
, kSecDbExclusiveTransactionType
, &localError
, ^(bool *commit
) {
1466 if (ok
) ok
= SecDbWithSQL(dbconn
, insertAdminRecordSQL
, &localError
, ^bool(sqlite3_stmt
*insertDbFormat
) {
1468 const char *dbFormatKey
= "db_format";
1469 ok
= SecDbBindText(insertDbFormat
, 1, dbFormatKey
, strlen(dbFormatKey
),
1470 SQLITE_TRANSIENT
, &localError
);
1473 ok
= SecDbBindInt64(insertDbFormat
, 2,
1474 (sqlite3_int64
)dbformat
, &localError
);
1477 ok
= SecDbStep(dbconn
, insertDbFormat
, &localError
, NULL
);
1484 secerror("_SecRevocationDbSetUpdateFormat failed: %@", localError
);
1486 rdb
->unsupportedVersion
= false;
1488 CFReleaseSafe(localError
);
1491 static CFStringRef
_SecRevocationDbCopyUpdateSource(SecRevocationDbRef rdb
, CFErrorRef
*error
) {
1492 /* look up db_source entry in admin table; returns NULL on error */
1493 __block CFStringRef updateSource
= NULL
;
1494 __block
bool ok
= true;
1495 __block CFErrorRef localError
= NULL
;
1497 ok
&= SecDbPerformRead(rdb
->db
, &localError
, ^(SecDbConnectionRef dbconn
) {
1498 if (ok
) ok
&= SecDbWithSQL(dbconn
, selectDbSourceSQL
, &localError
, ^bool(sqlite3_stmt
*selectDbSource
) {
1499 ok
= SecDbStep(dbconn
, selectDbSource
, &localError
, NULL
);
1500 const UInt8
*p
= (const UInt8
*)sqlite3_column_blob(selectDbSource
, 0);
1502 CFIndex length
= (CFIndex
)sqlite3_column_bytes(selectDbSource
, 0);
1504 updateSource
= CFStringCreateWithBytes(kCFAllocatorDefault
, p
, length
, kCFStringEncodingUTF8
, false);
1511 (void) CFErrorPropagate(localError
, error
);
1512 return updateSource
;
1515 static void _SecRevocationDbSetUpdateSource(SecRevocationDbRef rdb
, CFStringRef updateSource
) {
1516 if (!updateSource
) {
1517 secerror("_SecRevocationDbSetUpdateSource failed: %d", errSecParam
);
1520 __block
char buffer
[256];
1521 __block
const char *updateSourceCStr
= CFStringGetCStringPtr(updateSource
, kCFStringEncodingUTF8
);
1522 if (!updateSourceCStr
) {
1523 if (CFStringGetCString(updateSource
, buffer
, 256, kCFStringEncodingUTF8
)) {
1524 updateSourceCStr
= buffer
;
1527 if (!updateSourceCStr
) {
1528 secerror("_SecRevocationDbSetUpdateSource failed: unable to get UTF-8 encoding");
1531 secdebug("validupdate", "setting update source to \"%s\"", updateSourceCStr
);
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 if (ok
) ok
= SecDbWithSQL(dbconn
, insertAdminRecordSQL
, &localError
, ^bool(sqlite3_stmt
*insertRecord
) {
1539 const char *dbSourceKey
= "db_source";
1540 ok
= SecDbBindText(insertRecord
, 1, dbSourceKey
, strlen(dbSourceKey
),
1541 SQLITE_TRANSIENT
, &localError
);
1544 ok
= SecDbBindInt64(insertRecord
, 2,
1545 (sqlite3_int64
)0, &localError
);
1548 ok
= SecDbBindBlob(insertRecord
, 3,
1549 updateSourceCStr
, strlen(updateSourceCStr
),
1550 SQLITE_TRANSIENT
, &localError
);
1553 ok
= SecDbStep(dbconn
, insertRecord
, &localError
, NULL
);
1560 secerror("_SecRevocationDbSetUpdateSource failed: %@", localError
);
1562 CFReleaseSafe(localError
);
1565 static CFAbsoluteTime
_SecRevocationDbGetNextUpdateTime(SecRevocationDbRef rdb
, CFErrorRef
*error
) {
1566 /* look up check_again entry in admin table; returns 0 on error */
1567 __block CFAbsoluteTime nextUpdate
= 0;
1568 __block
bool ok
= true;
1569 __block CFErrorRef localError
= NULL
;
1571 ok
&= SecDbPerformRead(rdb
->db
, &localError
, ^(SecDbConnectionRef dbconn
) {
1572 if (ok
) ok
&= SecDbWithSQL(dbconn
, selectNextUpdateSQL
, &localError
, ^bool(sqlite3_stmt
*selectNextUpdate
) {
1573 ok
= SecDbStep(dbconn
, selectNextUpdate
, &localError
, NULL
);
1574 CFAbsoluteTime
*p
= (CFAbsoluteTime
*)sqlite3_column_blob(selectNextUpdate
, 0);
1576 if (sizeof(CFAbsoluteTime
) == sqlite3_column_bytes(selectNextUpdate
, 0)) {
1584 (void) CFErrorPropagate(localError
, error
);
1588 static void _SecRevocationDbSetNextUpdateTime(SecRevocationDbRef rdb
, CFAbsoluteTime nextUpdate
){
1589 secdebug("validupdate", "setting next update to %f", (double)nextUpdate
);
1591 __block CFErrorRef localError
= NULL
;
1592 __block
bool ok
= true;
1593 ok
&= SecDbPerformWrite(rdb
->db
, &localError
, ^(SecDbConnectionRef dbconn
) {
1594 ok
&= SecDbTransaction(dbconn
, kSecDbExclusiveTransactionType
, &localError
, ^(bool *commit
) {
1595 if (ok
) ok
= SecDbWithSQL(dbconn
, insertAdminRecordSQL
, &localError
, ^bool(sqlite3_stmt
*insertRecord
) {
1597 const char *nextUpdateKey
= "check_again";
1598 ok
= SecDbBindText(insertRecord
, 1, nextUpdateKey
, strlen(nextUpdateKey
),
1599 SQLITE_TRANSIENT
, &localError
);
1602 ok
= SecDbBindInt64(insertRecord
, 2,
1603 (sqlite3_int64
)0, &localError
);
1606 ok
= SecDbBindBlob(insertRecord
, 3,
1607 &nextUpdate
, sizeof(CFAbsoluteTime
),
1608 SQLITE_TRANSIENT
, &localError
);
1611 ok
= SecDbStep(dbconn
, insertRecord
, &localError
, NULL
);
1618 secerror("_SecRevocationDbSetNextUpdate failed: %@", localError
);
1620 CFReleaseSafe(localError
);
1623 static bool _SecRevocationDbRemoveAllEntries(SecRevocationDbRef rdb
) {
1624 /* clear out the contents of the database and start fresh */
1625 __block
bool ok
= true;
1626 __block CFErrorRef localError
= NULL
;
1628 ok
&= SecDbPerformWrite(rdb
->db
, &localError
, ^(SecDbConnectionRef dbconn
) {
1629 ok
&= SecDbTransaction(dbconn
, kSecDbExclusiveTransactionType
, &localError
, ^(bool *commit
) {
1630 //ok &= SecDbWithSQL(dbconn, deleteAllEntriesSQL, &localError, ^bool(sqlite3_stmt *deleteAll) {
1631 // ok = SecDbStep(dbconn, deleteAll, &localError, NULL);
1634 /* drop all tables and recreate them, in case of schema changes */
1635 ok
&= SecDbExec(dbconn
, deleteTablesSQL
, &localError
);
1636 ok
&= SecDbExec(dbconn
, createTablesSQL
, &localError
);
1637 secdebug("validupdate", "resetting database, result: %d", (ok
) ? 1 : 0);
1640 /* compact the db (must be done outside transaction scope) */
1641 SecDbExec(dbconn
, CFSTR("VACUUM"), &localError
);
1643 /* one more thing: update the schema version and format to current */
1644 _SecRevocationDbSetSchemaVersion(rdb
, kSecRevocationDbSchemaVersion
);
1645 _SecRevocationDbSetUpdateFormat(rdb
, kSecRevocationDbUpdateFormat
);
1647 CFReleaseSafe(localError
);
1651 static bool _SecRevocationDbUpdateIssuers(SecRevocationDbRef rdb
, int64_t groupId
, CFArrayRef issuers
, CFErrorRef
*error
) {
1652 /* insert or replace issuer records in issuers table */
1653 if (!issuers
|| groupId
< 0) {
1654 return false; /* must have something to insert, and a group to associate with it */
1656 __block
bool ok
= true;
1657 __block CFErrorRef localError
= NULL
;
1659 ok
&= SecDbPerformWrite(rdb
->db
, &localError
, ^(SecDbConnectionRef dbconn
) {
1660 ok
&= SecDbTransaction(dbconn
, kSecDbExclusiveTransactionType
, &localError
, ^(bool *commit
) {
1661 if (isArray(issuers
)) {
1662 CFIndex issuerIX
, issuerCount
= CFArrayGetCount(issuers
);
1663 for (issuerIX
=0; issuerIX
<issuerCount
&& ok
; issuerIX
++) {
1664 CFDataRef hash
= (CFDataRef
)CFArrayGetValueAtIndex(issuers
, issuerIX
);
1665 if (!hash
) { continue; }
1666 if (ok
) ok
= SecDbWithSQL(dbconn
, insertIssuerRecordSQL
, &localError
, ^bool(sqlite3_stmt
*insertIssuer
) {
1668 ok
= SecDbBindInt64(insertIssuer
, 1,
1669 groupId
, &localError
);
1672 ok
= SecDbBindBlob(insertIssuer
, 2,
1673 CFDataGetBytePtr(hash
),
1674 CFDataGetLength(hash
),
1675 SQLITE_TRANSIENT
, &localError
);
1677 /* Execute the insert statement for this issuer record. */
1679 ok
= SecDbStep(dbconn
, insertIssuer
, &localError
, NULL
);
1688 (void) CFErrorPropagate(localError
, error
);
1692 static bool _SecRevocationDbUpdatePerIssuerData(SecRevocationDbRef rdb
, int64_t groupId
, CFDictionaryRef dict
, CFErrorRef
*error
) {
1693 /* update/delete records in serials or hashes table. */
1694 if (!dict
|| groupId
< 0) {
1695 return false; /* must have something to insert, and a group to associate with it */
1697 __block
bool ok
= true;
1698 __block CFErrorRef localError
= NULL
;
1700 ok
&= SecDbPerformWrite(rdb
->db
, &localError
, ^(SecDbConnectionRef dbconn
) {
1701 ok
&= SecDbTransaction(dbconn
, kSecDbExclusiveTransactionType
, &localError
, ^(bool *commit
) {
1702 CFArrayRef deleteArray
= (CFArrayRef
)CFDictionaryGetValue(dict
, CFSTR("delete"));
1703 /* process deletions */
1704 if (isArray(deleteArray
)) {
1705 //%%% delete old data here (rdar://31439625)
1707 /* process additions */
1708 CFArrayRef addArray
= (CFArrayRef
)CFDictionaryGetValue(dict
, CFSTR("add"));
1709 if (isArray(addArray
)) {
1710 CFIndex identifierIX
, identifierCount
= CFArrayGetCount(addArray
);
1711 for (identifierIX
=0; identifierIX
<identifierCount
; identifierIX
++) {
1712 CFDataRef identifierData
= (CFDataRef
)CFArrayGetValueAtIndex(addArray
, identifierIX
);
1713 if (!identifierData
) { continue; }
1714 CFIndex length
= CFDataGetLength(identifierData
);
1715 /* we can figure out the format without an extra read to get the format column.
1716 len <= 20 is a serial number. len==32 is a sha256 hash. otherwise: xor. */
1717 CFStringRef sql
= NULL
;
1719 sql
= insertSerialRecordSQL
;
1720 } else if (length
== 32) {
1721 sql
= insertSha256RecordSQL
;
1723 if (!sql
) { continue; }
1725 if (ok
) ok
= SecDbWithSQL(dbconn
, sql
, &localError
, ^bool(sqlite3_stmt
*insertIdentifier
) {
1726 /* rowid,(groupid,serial|sha256) */
1727 /* rowid is autoincremented and we never set it directly */
1729 ok
= SecDbBindInt64(insertIdentifier
, 1,
1730 groupId
, &localError
);
1733 ok
= SecDbBindBlob(insertIdentifier
, 2,
1734 CFDataGetBytePtr(identifierData
),
1735 CFDataGetLength(identifierData
),
1736 SQLITE_TRANSIENT
, &localError
);
1738 /* Execute the insert statement for the identifier record. */
1740 ok
= SecDbStep(dbconn
, insertIdentifier
, &localError
, NULL
);
1749 (void) CFErrorPropagate(localError
, error
);
1753 static SecValidInfoFormat
_SecRevocationDbGetGroupFormat(SecRevocationDbRef rdb
,
1754 int64_t groupId
, SecValidInfoFlags
*flags
, CFDataRef
*data
, CFErrorRef
*error
) {
1755 /* return group record fields for a given groupId.
1756 on success, returns a non-zero format type, and other field values in optional output parameters.
1757 caller is responsible for releasing data and error parameters, if provided.
1759 __block
bool ok
= true;
1760 __block SecValidInfoFormat format
= 0;
1761 __block CFErrorRef localError
= NULL
;
1763 /* Select the group record to determine flags and format. */
1764 ok
&= SecDbPerformRead(rdb
->db
, &localError
, ^(SecDbConnectionRef dbconn
) {
1765 ok
&= SecDbWithSQL(dbconn
, selectGroupRecordSQL
, &localError
, ^bool(sqlite3_stmt
*selectGroup
) {
1766 ok
= SecDbBindInt64(selectGroup
, 1, groupId
, &localError
);
1767 ok
&= SecDbStep(dbconn
, selectGroup
, &localError
, ^(bool *stop
) {
1769 *flags
= (SecValidInfoFlags
)sqlite3_column_int(selectGroup
, 0);
1771 format
= (SecValidInfoFormat
)sqlite3_column_int(selectGroup
, 1);
1773 //TODO: stream this from sqlite through the inflation so we return an inflated copy, then remove inflate from others
1774 uint8_t *p
= (uint8_t *)sqlite3_column_blob(selectGroup
, 2);
1775 if (p
!= NULL
&& format
== kSecValidInfoFormatNto1
) {
1776 CFIndex length
= (CFIndex
)sqlite3_column_bytes(selectGroup
, 2);
1777 *data
= CFDataCreate(kCFAllocatorDefault
, p
, length
);
1785 secdebug("validupdate", "GetGroupFormat for groupId %lu failed", (unsigned long)groupId
);
1786 format
= kSecValidInfoFormatUnknown
;
1788 (void) CFErrorPropagate(localError
, error
);
1789 if (!(format
> kSecValidInfoFormatUnknown
)) {
1790 secdebug("validupdate", "GetGroupFormat: got format %d for groupId %lld", format
, (long long)groupId
);
1795 static bool _SecRevocationDbUpdateFlags(CFDictionaryRef dict
, CFStringRef key
, SecValidInfoFlags mask
, SecValidInfoFlags
*flags
) {
1796 /* If a boolean value exists in the given dictionary for the given key,
1797 set or clear the corresponding bit(s) defined by the mask argument.
1798 Function returns true if the flags value was changed, false otherwise.
1800 bool result
= false;
1801 CFTypeRef value
= (CFBooleanRef
)CFDictionaryGetValue(dict
, key
);
1802 if (isBoolean(value
) && flags
) {
1803 SecValidInfoFlags oldFlags
= *flags
;
1804 if (CFBooleanGetValue((CFBooleanRef
)value
)) {
1809 result
= (*flags
!= oldFlags
);
1814 static bool _SecRevocationDbUpdateFilter(CFDictionaryRef dict
, CFDataRef oldData
, CFDataRef
* __nonnull CF_RETURNS_RETAINED xmlData
) {
1815 /* If xor and/or params values exist in the given dictionary, create a new
1816 property list containing the updated values, and return as a flattened
1817 data blob in the xmlData output parameter (note: caller must release.)
1818 Function returns true if there is new xmlData to save, false otherwise.
1820 bool result
= false;
1821 bool xorProvided
= false;
1822 bool paramsProvided
= false;
1823 bool missingData
= false;
1825 if (!dict
|| !xmlData
) {
1826 return result
; /* no-op if no dictionary is provided, or no way to update the data */
1829 CFDataRef xorCurrent
= NULL
;
1830 CFDataRef xorUpdate
= (CFDataRef
)CFDictionaryGetValue(dict
, CFSTR("xor"));
1831 if (isData(xorUpdate
)) {
1834 CFArrayRef paramsCurrent
= NULL
;
1835 CFArrayRef paramsUpdate
= (CFArrayRef
)CFDictionaryGetValue(dict
, CFSTR("params"));
1836 if (isArray(paramsUpdate
)) {
1837 paramsProvided
= true;
1839 if (!(xorProvided
|| paramsProvided
)) {
1840 return result
; /* nothing to update, so we can bail out here. */
1843 CFPropertyListRef nto1Current
= NULL
;
1844 CFMutableDictionaryRef nto1Update
= CFDictionaryCreateMutable(kCFAllocatorDefault
, 0,
1845 &kCFTypeDictionaryKeyCallBacks
,
1846 &kCFTypeDictionaryValueCallBacks
);
1851 /* turn old data into property list */
1852 CFDataRef data
= (CFDataRef
)CFRetainSafe(oldData
);
1853 CFDataRef inflatedData
= copyInflatedData(data
);
1855 CFReleaseSafe(data
);
1856 data
= inflatedData
;
1859 nto1Current
= CFPropertyListCreateWithData(kCFAllocatorDefault
, data
, 0, NULL
, NULL
);
1860 CFReleaseSafe(data
);
1863 xorCurrent
= (CFDataRef
)CFDictionaryGetValue((CFDictionaryRef
)nto1Current
, CFSTR("xor"));
1864 paramsCurrent
= (CFArrayRef
)CFDictionaryGetValue((CFDictionaryRef
)nto1Current
, CFSTR("params"));
1867 /* set current or updated xor data in new property list */
1869 CFDataRef xorNew
= NULL
;
1871 CFIndex xorUpdateLen
= CFDataGetLength(xorUpdate
);
1872 CFMutableDataRef
xor = CFDataCreateMutableCopy(NULL
, 0, xorCurrent
);
1873 if (xor && xorUpdateLen
> 0) {
1874 /* truncate or zero-extend data to match update size */
1875 CFDataSetLength(xor, xorUpdateLen
);
1876 /* exclusive-or update bytes over the existing data */
1877 UInt8
*xorP
= (UInt8
*)CFDataGetMutableBytePtr(xor);
1878 UInt8
*updP
= (UInt8
*)CFDataGetBytePtr(xorUpdate
);
1880 for (int idx
= 0; idx
< xorUpdateLen
; idx
++) {
1881 xorP
[idx
] = xorP
[idx
] ^ updP
[idx
];
1885 xorNew
= (CFDataRef
)xor;
1887 xorNew
= (CFDataRef
)CFRetainSafe(xorUpdate
);
1890 CFDictionaryAddValue(nto1Update
, CFSTR("xor"), xorNew
);
1891 CFReleaseSafe(xorNew
);
1893 secdebug("validupdate", "Failed to get updated filter data");
1896 } else if (xorCurrent
) {
1897 /* not provided, so use existing xor value */
1898 CFDictionaryAddValue(nto1Update
, CFSTR("xor"), xorCurrent
);
1900 secdebug("validupdate", "Failed to get current filter data");
1904 /* set current or updated params in new property list */
1905 if (paramsProvided
) {
1906 CFDictionaryAddValue(nto1Update
, CFSTR("params"), paramsUpdate
);
1907 } else if (paramsCurrent
) {
1908 /* not provided, so use existing params value */
1909 CFDictionaryAddValue(nto1Update
, CFSTR("params"), paramsCurrent
);
1911 /* missing params: neither provided nor existing */
1912 secdebug("validupdate", "Failed to get current filter params");
1916 CFReleaseSafe(nto1Current
);
1918 *xmlData
= CFPropertyListCreateData(kCFAllocatorDefault
, nto1Update
,
1919 kCFPropertyListXMLFormat_v1_0
,
1921 result
= (*xmlData
!= NULL
);
1923 CFReleaseSafe(nto1Update
);
1925 /* compress the xmlData blob, if possible */
1927 CFDataRef deflatedData
= copyDeflatedData(*xmlData
);
1929 if (CFDataGetLength(deflatedData
) < CFDataGetLength(*xmlData
)) {
1930 CFRelease(*xmlData
);
1931 *xmlData
= deflatedData
;
1933 CFRelease(deflatedData
);
1941 static int64_t _SecRevocationDbUpdateGroup(SecRevocationDbRef rdb
, int64_t groupId
, CFDictionaryRef dict
, CFErrorRef
*error
) {
1942 /* insert group record for a given groupId.
1943 if the specified groupId is < 0, a new group entry is created.
1944 returns the groupId on success, or -1 on failure.
1947 return groupId
; /* no-op if no dictionary is provided */
1950 __block
int64_t result
= -1;
1951 __block
bool ok
= true;
1952 __block
bool isFormatChange
= false;
1953 __block CFErrorRef localError
= NULL
;
1955 __block SecValidInfoFlags flags
= 0;
1956 __block SecValidInfoFormat format
= kSecValidInfoFormatUnknown
;
1957 __block SecValidInfoFormat formatUpdate
= kSecValidInfoFormatUnknown
;
1958 __block CFDataRef data
= NULL
;
1961 /* fetch the flags and data for an existing group record, in case some are being changed. */
1962 format
= _SecRevocationDbGetGroupFormat(rdb
, groupId
, &flags
, &data
, NULL
);
1963 if (format
== kSecValidInfoFormatUnknown
) {
1964 secdebug("validupdate", "existing group %lld has unknown format %d, flags=%lu",
1965 (long long)groupId
, format
, flags
);
1966 //%%% clean up by deleting all issuers with this groupId, then the group record,
1967 // or just force a full update? note: we can get here if we fail to bind the
1968 // format value in the prepared SQL statement below.
1972 CFTypeRef value
= (CFStringRef
)CFDictionaryGetValue(dict
, CFSTR("format"));
1973 if (isString(value
)) {
1974 if (CFStringCompare((CFStringRef
)value
, CFSTR("serial"), 0) == kCFCompareEqualTo
) {
1975 formatUpdate
= kSecValidInfoFormatSerial
;
1976 } else if (CFStringCompare((CFStringRef
)value
, CFSTR("sha256"), 0) == kCFCompareEqualTo
) {
1977 formatUpdate
= kSecValidInfoFormatSHA256
;
1978 } else if (CFStringCompare((CFStringRef
)value
, CFSTR("nto1"), 0) == kCFCompareEqualTo
) {
1979 formatUpdate
= kSecValidInfoFormatNto1
;
1982 /* if format value is explicitly supplied, then this is effectively a new group entry. */
1983 isFormatChange
= (formatUpdate
> kSecValidInfoFormatUnknown
&&
1984 formatUpdate
!= format
&&
1987 ok
&= SecDbPerformWrite(rdb
->db
, &localError
, ^(SecDbConnectionRef dbconn
) {
1988 ok
&= SecDbTransaction(dbconn
, kSecDbExclusiveTransactionType
, &localError
, ^(bool *commit
) {
1989 if (isFormatChange
) {
1990 secdebug("validupdate", "group %lld format change from %d to %d",
1991 (long long)groupId
, format
, formatUpdate
);
1992 /* format of an existing group is changing; delete the group first.
1993 this should ensure that all entries referencing the old groupid are deleted.
1995 ok
&= SecDbWithSQL(dbconn
, deleteGroupRecordSQL
, &localError
, ^bool(sqlite3_stmt
*deleteResponse
) {
1996 ok
= SecDbBindInt64(deleteResponse
, 1, groupId
, &localError
);
1997 /* Execute the delete statement. */
1999 ok
= SecDbStep(dbconn
, deleteResponse
, &localError
, NULL
);
2004 ok
&= SecDbWithSQL(dbconn
, insertGroupRecordSQL
, &localError
, ^bool(sqlite3_stmt
*insertGroup
) {
2005 /* (groupid,flags,format,data) */
2006 /* groups.groupid */
2007 if (ok
&& (!isFormatChange
) && (groupId
>= 0)) {
2008 /* bind to existing groupId row if known, otherwise will insert and autoincrement */
2009 ok
= SecDbBindInt64(insertGroup
, 1, groupId
, &localError
);
2011 secdebug("validupdate", "failed to set groupId %lld", (long long)groupId
);
2016 (void)_SecRevocationDbUpdateFlags(dict
, CFSTR("complete"), kSecValidInfoComplete
, &flags
);
2017 (void)_SecRevocationDbUpdateFlags(dict
, CFSTR("check-ocsp"), kSecValidInfoCheckOCSP
, &flags
);
2018 (void)_SecRevocationDbUpdateFlags(dict
, CFSTR("known-intermediates-only"), kSecValidInfoKnownOnly
, &flags
);
2019 (void)_SecRevocationDbUpdateFlags(dict
, CFSTR("require-ct"), kSecValidInfoRequireCT
, &flags
);
2020 (void)_SecRevocationDbUpdateFlags(dict
, CFSTR("valid"), kSecValidInfoAllowlist
, &flags
);
2021 (void)_SecRevocationDbUpdateFlags(dict
, CFSTR("no-ca"), kSecValidInfoNoCACheck
, &flags
);
2023 ok
= SecDbBindInt(insertGroup
, 2, (int)flags
, &localError
);
2025 secdebug("validupdate", "failed to set flags (%lu) for groupId %lld", flags
, (long long)groupId
);
2030 SecValidInfoFormat formatValue
= format
;
2031 if (formatUpdate
> kSecValidInfoFormatUnknown
) {
2032 formatValue
= formatUpdate
;
2034 ok
= SecDbBindInt(insertGroup
, 3, (int)formatValue
, &localError
);
2036 secdebug("validupdate", "failed to set format (%d) for groupId %lld", formatValue
, (long long)groupId
);
2040 CFDataRef xmlData
= NULL
;
2042 bool hasFilter
= ((formatUpdate
== kSecValidInfoFormatNto1
) ||
2043 (formatUpdate
== kSecValidInfoFormatUnknown
&&
2044 format
== kSecValidInfoFormatNto1
));
2046 CFDataRef dataValue
= data
; /* use existing data */
2047 if (_SecRevocationDbUpdateFilter(dict
, data
, &xmlData
)) {
2048 dataValue
= xmlData
; /* use updated data */
2051 ok
= SecDbBindBlob(insertGroup
, 4,
2052 CFDataGetBytePtr(dataValue
),
2053 CFDataGetLength(dataValue
),
2054 SQLITE_TRANSIENT
, &localError
);
2057 secdebug("validupdate", "failed to set data for groupId %lld",
2058 (long long)groupId
);
2061 /* else there is no data, so NULL is implicitly bound to column 4 */
2064 /* Execute the insert statement for the group record. */
2066 ok
= SecDbStep(dbconn
, insertGroup
, &localError
, NULL
);
2068 secdebug("validupdate", "failed to execute insertGroup statement for groupId %lld",
2069 (long long)groupId
);
2071 result
= (int64_t)sqlite3_last_insert_rowid(SecDbHandle(dbconn
));
2074 secdebug("validupdate", "failed to insert group %lld", (long long)result
);
2076 /* Clean up temporary allocation made in this block. */
2077 CFReleaseSafe(xmlData
);
2078 CFReleaseSafe(data
);
2084 (void) CFErrorPropagate(localError
, error
);
2088 static int64_t _SecRevocationDbGroupIdForIssuerHash(SecRevocationDbRef rdb
, CFDataRef hash
, CFErrorRef
*error
) {
2089 /* look up issuer hash in issuers table to get groupid, if it exists */
2090 __block
int64_t groupId
= -1;
2091 __block
bool ok
= true;
2092 __block CFErrorRef localError
= NULL
;
2095 secdebug("validupdate", "failed to get hash (%@)", hash
);
2097 require(hash
, errOut
);
2099 /* This is the starting point for any lookup; find a group id for the given issuer hash.
2100 Before we do that, need to verify the current db_version. We cannot use results from a
2101 database created with a schema version older than the minimum supported version.
2102 However, we may be able to use results from a newer version. At the next database
2103 update interval, if the existing schema is old, we'll be removing and recreating
2104 the database contents with the current schema version.
2106 int64_t db_version
= _SecRevocationDbGetSchemaVersion(rdb
, NULL
);
2107 if (db_version
< kSecRevocationDbMinSchemaVersion
) {
2108 if (!rdb
->unsupportedVersion
) {
2109 secdebug("validupdate", "unsupported db_version: %lld", (long long)db_version
);
2110 rdb
->unsupportedVersion
= true; /* only warn once for a given unsupported version */
2113 require_quiet(db_version
>= kSecRevocationDbMinSchemaVersion
, errOut
);
2115 /* Look up provided issuer_hash in the issuers table.
2117 ok
&= SecDbPerformRead(rdb
->db
, &localError
, ^(SecDbConnectionRef dbconn
) {
2118 ok
&= SecDbWithSQL(dbconn
, selectGroupIdSQL
, &localError
, ^bool(sqlite3_stmt
*selectGroupId
) {
2119 ok
= SecDbBindBlob(selectGroupId
, 1, CFDataGetBytePtr(hash
), CFDataGetLength(hash
), SQLITE_TRANSIENT
, &localError
);
2120 ok
&= SecDbStep(dbconn
, selectGroupId
, &localError
, ^(bool *stopGroupId
) {
2121 groupId
= sqlite3_column_int64(selectGroupId
, 0);
2128 (void) CFErrorPropagate(localError
, error
);
2132 static bool _SecRevocationDbApplyGroupDelete(SecRevocationDbRef rdb
, CFDataRef issuerHash
, CFErrorRef
*error
) {
2133 /* delete group associated with the given issuer;
2134 schema trigger will delete associated issuers, serials, and hashes. */
2135 __block
int64_t groupId
= -1;
2136 __block
bool ok
= true;
2137 __block CFErrorRef localError
= NULL
;
2139 groupId
= _SecRevocationDbGroupIdForIssuerHash(rdb
, issuerHash
, &localError
);
2140 require(!(groupId
< 0), errOut
);
2142 ok
&= SecDbPerformWrite(rdb
->db
, &localError
, ^(SecDbConnectionRef dbconn
) {
2143 ok
&= SecDbTransaction(dbconn
, kSecDbExclusiveTransactionType
, &localError
, ^(bool *commit
) {
2144 ok
= SecDbWithSQL(dbconn
, deleteGroupRecordSQL
, &localError
, ^bool(sqlite3_stmt
*deleteResponse
) {
2145 ok
= SecDbBindInt64(deleteResponse
, 1, groupId
, &localError
);
2146 /* Execute the delete statement. */
2148 ok
= SecDbStep(dbconn
, deleteResponse
, &localError
, NULL
);
2156 (void) CFErrorPropagate(localError
, error
);
2157 return (groupId
< 0) ? false : true;
2160 static bool _SecRevocationDbApplyGroupUpdate(SecRevocationDbRef rdb
, CFDictionaryRef dict
, CFErrorRef
*error
) {
2161 /* process one issuer group's update dictionary */
2162 int64_t groupId
= -1;
2163 CFErrorRef localError
= NULL
;
2165 CFArrayRef issuers
= (dict
) ? (CFArrayRef
)CFDictionaryGetValue(dict
, CFSTR("issuer-hash")) : NULL
;
2166 if (isArray(issuers
)) {
2167 CFIndex issuerIX
, issuerCount
= CFArrayGetCount(issuers
);
2168 /* while we have issuers and haven't found a matching group id */
2169 for (issuerIX
=0; issuerIX
<issuerCount
&& groupId
< 0; issuerIX
++) {
2170 CFDataRef hash
= (CFDataRef
)CFArrayGetValueAtIndex(issuers
, issuerIX
);
2171 if (!hash
) { continue; }
2172 groupId
= _SecRevocationDbGroupIdForIssuerHash(rdb
, hash
, &localError
);
2175 /* create or update the group entry */
2176 groupId
= _SecRevocationDbUpdateGroup(rdb
, groupId
, dict
, &localError
);
2178 secdebug("validupdate", "failed to get groupId");
2180 /* create or update issuer entries, now that we know the group id */
2181 _SecRevocationDbUpdateIssuers(rdb
, groupId
, issuers
, &localError
);
2182 /* create or update entries in serials or hashes tables */
2183 _SecRevocationDbUpdatePerIssuerData(rdb
, groupId
, dict
, &localError
);
2186 (void) CFErrorPropagate(localError
, error
);
2187 return (groupId
> 0) ? true : false;
2190 static void _SecRevocationDbApplyUpdate(SecRevocationDbRef rdb
, CFDictionaryRef update
, CFIndex version
) {
2191 /* process entire update dictionary */
2192 if (!rdb
|| !update
) {
2193 secerror("_SecRevocationDbApplyUpdate failed: invalid args");
2197 __block CFDictionaryRef localUpdate
= (CFDictionaryRef
)CFRetainSafe(update
);
2198 __block CFErrorRef localError
= NULL
;
2200 CFTypeRef value
= NULL
;
2201 CFIndex deleteCount
= 0;
2202 CFIndex updateCount
= 0;
2204 rdb
->updateInProgress
= true;
2206 /* check whether this is a full update */
2207 value
= (CFBooleanRef
)CFDictionaryGetValue(update
, CFSTR("full"));
2208 if (isBoolean(value
) && CFBooleanGetValue((CFBooleanRef
)value
)) {
2209 /* clear the database before processing a full update */
2210 SecRevocationDbRemoveAllEntries();
2213 /* process 'delete' list */
2214 value
= (CFArrayRef
)CFDictionaryGetValue(localUpdate
, CFSTR("delete"));
2215 if (isArray(value
)) {
2216 deleteCount
= CFArrayGetCount((CFArrayRef
)value
);
2217 secdebug("validupdate", "processing %ld deletes", (long)deleteCount
);
2218 for (CFIndex deleteIX
=0; deleteIX
<deleteCount
; deleteIX
++) {
2219 CFDataRef issuerHash
= (CFDataRef
)CFArrayGetValueAtIndex((CFArrayRef
)value
, deleteIX
);
2220 if (isData(issuerHash
)) {
2221 (void)_SecRevocationDbApplyGroupDelete(rdb
, issuerHash
, &localError
);
2222 CFReleaseNull(localError
);
2227 /* process 'update' list */
2228 value
= (CFArrayRef
)CFDictionaryGetValue(localUpdate
, CFSTR("update"));
2229 if (isArray(value
)) {
2230 updateCount
= CFArrayGetCount((CFArrayRef
)value
);
2231 secdebug("validupdate", "processing %ld updates", (long)updateCount
);
2232 for (CFIndex updateIX
=0; updateIX
<updateCount
; updateIX
++) {
2233 CFDictionaryRef dict
= (CFDictionaryRef
)CFArrayGetValueAtIndex((CFArrayRef
)value
, updateIX
);
2234 if (isDictionary(dict
)) {
2235 (void)_SecRevocationDbApplyGroupUpdate(rdb
, dict
, &localError
);
2236 CFReleaseNull(localError
);
2240 CFRelease(localUpdate
);
2243 _SecRevocationDbSetVersion(rdb
, version
);
2245 /* set db_version if not already set */
2246 int64_t db_version
= _SecRevocationDbGetSchemaVersion(rdb
, NULL
);
2247 if (db_version
<= 0) {
2248 _SecRevocationDbSetSchemaVersion(rdb
, kSecRevocationDbSchemaVersion
);
2251 /* set db_format if not already set */
2252 int64_t db_format
= _SecRevocationDbGetUpdateFormat(rdb
, NULL
);
2253 if (db_format
<= 0) {
2254 _SecRevocationDbSetUpdateFormat(rdb
, kSecRevocationDbUpdateFormat
);
2257 /* compact the db (must be done outside transaction scope) */
2258 (void)SecDbPerformWrite(rdb
->db
, &localError
, ^(SecDbConnectionRef dbconn
) {
2259 SecDbExec(dbconn
, CFSTR("VACUUM"), &localError
);
2260 CFReleaseNull(localError
);
2263 rdb
->updateInProgress
= false;
2266 static bool _SecRevocationDbSerialInGroup(SecRevocationDbRef rdb
,
2269 CFErrorRef
*error
) {
2270 __block
bool result
= false;
2271 __block
bool ok
= true;
2272 __block CFErrorRef localError
= NULL
;
2273 require(rdb
&& serial
, errOut
);
2274 ok
&= SecDbPerformRead(rdb
->db
, &localError
, ^(SecDbConnectionRef dbconn
) {
2275 ok
&= SecDbWithSQL(dbconn
, selectSerialRecordSQL
, &localError
, ^bool(sqlite3_stmt
*selectSerial
) {
2276 ok
&= SecDbBindInt64(selectSerial
, 1, groupId
, &localError
);
2277 ok
&= SecDbBindBlob(selectSerial
, 2, CFDataGetBytePtr(serial
),
2278 CFDataGetLength(serial
), SQLITE_TRANSIENT
, &localError
);
2279 ok
&= SecDbStep(dbconn
, selectSerial
, &localError
, ^(bool *stop
) {
2280 int64_t foundRowId
= (int64_t)sqlite3_column_int64(selectSerial
, 0);
2281 result
= (foundRowId
> 0);
2288 (void) CFErrorPropagate(localError
, error
);
2292 static bool _SecRevocationDbCertHashInGroup(SecRevocationDbRef rdb
,
2295 CFErrorRef
*error
) {
2296 __block
bool result
= false;
2297 __block
bool ok
= true;
2298 __block CFErrorRef localError
= NULL
;
2299 require(rdb
&& certHash
, errOut
);
2300 ok
&= SecDbPerformRead(rdb
->db
, &localError
, ^(SecDbConnectionRef dbconn
) {
2301 ok
&= SecDbWithSQL(dbconn
, selectHashRecordSQL
, &localError
, ^bool(sqlite3_stmt
*selectHash
) {
2302 ok
&= SecDbBindInt64(selectHash
, 1, groupId
, &localError
);
2303 ok
= SecDbBindBlob(selectHash
, 2, CFDataGetBytePtr(certHash
),
2304 CFDataGetLength(certHash
), SQLITE_TRANSIENT
, &localError
);
2305 ok
&= SecDbStep(dbconn
, selectHash
, &localError
, ^(bool *stop
) {
2306 int64_t foundRowId
= (int64_t)sqlite3_column_int64(selectHash
, 0);
2307 result
= (foundRowId
> 0);
2314 (void) CFErrorPropagate(localError
, error
);
2318 static bool _SecRevocationDbSerialInFilter(SecRevocationDbRef rdb
,
2319 CFDataRef serialData
,
2320 CFDataRef xmlData
) {
2321 /* N-To-1 filter implementation.
2322 The 'xmlData' parameter is a flattened XML dictionary,
2323 containing 'xor' and 'params' keys. First order of
2324 business is to reconstitute the blob into components.
2326 bool result
= false;
2327 CFRetainSafe(xmlData
);
2328 CFDataRef propListData
= xmlData
;
2329 /* Expand data blob if needed */
2330 CFDataRef inflatedData
= copyInflatedData(propListData
);
2332 CFReleaseSafe(propListData
);
2333 propListData
= inflatedData
;
2335 CFDataRef
xor = NULL
;
2336 CFArrayRef params
= NULL
;
2337 CFPropertyListRef nto1
= CFPropertyListCreateWithData(kCFAllocatorDefault
, propListData
, 0, NULL
, NULL
);
2339 xor = (CFDataRef
)CFDictionaryGetValue((CFDictionaryRef
)nto1
, CFSTR("xor"));
2340 params
= (CFArrayRef
)CFDictionaryGetValue((CFDictionaryRef
)nto1
, CFSTR("params"));
2342 uint8_t *hash
= (xor) ? (uint8_t*)CFDataGetBytePtr(xor) : NULL
;
2343 CFIndex hashLen
= (hash
) ? CFDataGetLength(xor) : 0;
2344 uint8_t *serial
= (serialData
) ? (uint8_t*)CFDataGetBytePtr(serialData
) : NULL
;
2345 CFIndex serialLen
= (serial
) ? CFDataGetLength(serialData
) : 0;
2347 require(hash
&& serial
&& params
, errOut
);
2349 const uint32_t FNV_OFFSET_BASIS
= 2166136261;
2350 const uint32_t FNV_PRIME
= 16777619;
2351 bool notInHash
= false;
2352 CFIndex ix
, count
= CFArrayGetCount(params
);
2353 for (ix
= 0; ix
< count
; ix
++) {
2355 CFNumberRef cfnum
= (CFNumberRef
)CFArrayGetValueAtIndex(params
, ix
);
2356 if (!isNumber(cfnum
) ||
2357 !CFNumberGetValue(cfnum
, kCFNumberSInt32Type
, ¶m
)) {
2358 secinfo("validupdate", "error processing filter params at index %ld", (long)ix
);
2361 /* process one param */
2362 uint32_t hval
= FNV_OFFSET_BASIS
^ param
;
2363 CFIndex i
= serialLen
;
2365 hval
= ((hval
^ (serial
[--i
])) * FNV_PRIME
) & 0xFFFFFFFF;
2367 hval
= hval
% (hashLen
* 8);
2368 if ((hash
[hval
/8] & (1 << (hval
% 8))) == 0) {
2369 notInHash
= true; /* definitely not in hash */
2374 /* probabilistically might be in hash if we get here. */
2379 CFReleaseSafe(nto1
);
2380 CFReleaseSafe(propListData
);
2384 static SecValidInfoRef
_SecRevocationDbValidInfoForCertificate(SecRevocationDbRef rdb
,
2385 SecCertificateRef certificate
,
2386 CFDataRef issuerHash
,
2387 CFErrorRef
*error
) {
2388 __block CFErrorRef localError
= NULL
;
2389 __block SecValidInfoFlags flags
= 0;
2390 __block SecValidInfoFormat format
= kSecValidInfoFormatUnknown
;
2391 __block CFDataRef data
= NULL
;
2393 bool matched
= false;
2394 bool isOnList
= false;
2395 int64_t groupId
= 0;
2396 CFDataRef serial
= NULL
;
2397 CFDataRef certHash
= NULL
;
2398 SecValidInfoRef result
= NULL
;
2400 require((serial
= SecCertificateCopySerialNumberData(certificate
, NULL
)) != NULL
, errOut
);
2401 require((certHash
= SecCertificateCopySHA256Digest(certificate
)) != NULL
, errOut
);
2402 require((groupId
= _SecRevocationDbGroupIdForIssuerHash(rdb
, issuerHash
, &localError
)) > 0, errOut
);
2404 /* Look up the group record to determine flags and format. */
2405 format
= _SecRevocationDbGetGroupFormat(rdb
, groupId
, &flags
, &data
, &localError
);
2407 if (format
== kSecValidInfoFormatUnknown
) {
2408 /* No group record found for this issuer. */
2410 else if (format
== kSecValidInfoFormatSerial
) {
2411 /* Look up certificate's serial number in the serials table. */
2412 matched
= _SecRevocationDbSerialInGroup(rdb
, serial
, groupId
, &localError
);
2414 else if (format
== kSecValidInfoFormatSHA256
) {
2415 /* Look up certificate's SHA-256 hash in the hashes table. */
2416 matched
= _SecRevocationDbCertHashInGroup(rdb
, certHash
, groupId
, &localError
);
2418 else if (format
== kSecValidInfoFormatNto1
) {
2419 /* Perform a Bloom filter match against the serial. If matched is false,
2420 then the cert is definitely not in the list. But if matched is true,
2421 we don't know for certain, so we would need to check OCSP. */
2422 matched
= _SecRevocationDbSerialInFilter(rdb
, serial
, data
);
2426 /* Found a specific match for this certificate. */
2427 secdebug("validupdate", "Valid db matched certificate: %@, format=%d, flags=%lu",
2428 certHash
, format
, flags
);
2431 else if ((flags
& kSecValidInfoComplete
) && (flags
& kSecValidInfoAllowlist
)) {
2432 /* Not matching against a complete allowlist is equivalent to revocation. */
2433 secdebug("validupdate", "Valid db did NOT match certificate on allowlist: %@, format=%d, flags=%lu",
2434 certHash
, format
, flags
);
2437 else if ((!(flags
& kSecValidInfoComplete
)) && (format
> kSecValidInfoFormatUnknown
)) {
2438 /* Not matching against an incomplete list implies we need to check OCSP. */
2439 secdebug("validupdate", "Valid db did not find certificate on incomplete list: %@, format=%d, flags=%lu",
2440 certHash
, format
, flags
);
2445 /* Return SecValidInfo for a matched certificate. */
2446 result
= SecValidInfoCreate(format
, flags
, isOnList
, certHash
, issuerHash
, NULL
);
2449 if (result
&& SecIsAppleTrustAnchor(certificate
, 0)) {
2450 /* Prevent a catch-22. */
2451 secdebug("validupdate", "Valid db match for Apple trust anchor: %@, format=%d, flags=%lu",
2452 certHash
, format
, flags
);
2453 SecValidInfoRelease(result
);
2458 (void) CFErrorPropagate(localError
, error
);
2459 CFReleaseSafe(data
);
2460 CFReleaseSafe(certHash
);
2461 CFReleaseSafe(serial
);
2465 static SecValidInfoRef
_SecRevocationDbCopyMatching(SecRevocationDbRef db
,
2466 SecCertificateRef certificate
,
2467 SecCertificateRef issuer
) {
2468 SecValidInfoRef result
= NULL
;
2469 CFErrorRef error
= NULL
;
2470 CFDataRef issuerHash
= NULL
;
2472 require(certificate
&& issuer
, errOut
);
2473 require(issuerHash
= SecCertificateCopySHA256Digest(issuer
), errOut
);
2475 result
= _SecRevocationDbValidInfoForCertificate(db
, certificate
, issuerHash
, &error
);
2478 CFReleaseSafe(issuerHash
);
2479 CFReleaseSafe(error
);
2483 static dispatch_queue_t
_SecRevocationDbGetUpdateQueue(SecRevocationDbRef rdb
) {
2484 return (rdb
) ? rdb
->update_queue
: NULL
;
2488 /* Given a valid update dictionary, insert/replace or delete records
2489 in the revocation database. (This function is expected to be called only
2490 by the database maintainer, normally the system instance of trustd.)
2492 void SecRevocationDbApplyUpdate(CFDictionaryRef update
, CFIndex version
) {
2493 SecRevocationDbWith(^(SecRevocationDbRef db
) {
2494 _SecRevocationDbApplyUpdate(db
, update
, version
);
2498 /* Set the schema version for the revocation database.
2499 (This function is expected to be called only by the database maintainer,
2500 normally the system instance of trustd.)
2502 void SecRevocationDbSetSchemaVersion(CFIndex db_version
) {
2503 SecRevocationDbWith(^(SecRevocationDbRef db
) {
2504 _SecRevocationDbSetSchemaVersion(db
, db_version
);
2508 /* Set the update format for the revocation database.
2509 (This function is expected to be called only by the database maintainer,
2510 normally the system instance of trustd.)
2512 void SecRevocationDbSetUpdateFormat(CFIndex db_format
) {
2513 SecRevocationDbWith(^(SecRevocationDbRef db
) {
2514 _SecRevocationDbSetUpdateFormat(db
, db_format
);
2518 /* Set the update source for the revocation database.
2519 (This function is expected to be called only by the database
2520 maintainer, normally the system instance of trustd. If the
2521 caller does not have write access, this is a no-op.)
2523 void SecRevocationDbSetUpdateSource(CFStringRef updateSource
) {
2524 SecRevocationDbWith(^(SecRevocationDbRef db
) {
2525 _SecRevocationDbSetUpdateSource(db
, updateSource
);
2529 /* Return the update source as a retained CFStringRef.
2530 If the value cannot be obtained, NULL is returned.
2532 CFStringRef
SecRevocationDbCopyUpdateSource(void) {
2533 __block CFStringRef result
= NULL
;
2534 SecRevocationDbWith(^(SecRevocationDbRef db
) {
2535 result
= _SecRevocationDbCopyUpdateSource(db
, NULL
);
2540 /* Set the next update value for the revocation database.
2541 (This function is expected to be called only by the database
2542 maintainer, normally the system instance of trustd. If the
2543 caller does not have write access, this is a no-op.)
2545 void SecRevocationDbSetNextUpdateTime(CFAbsoluteTime nextUpdate
) {
2546 SecRevocationDbWith(^(SecRevocationDbRef db
) {
2547 _SecRevocationDbSetNextUpdateTime(db
, nextUpdate
);
2551 /* Return the next update value as a CFAbsoluteTime.
2552 If the value cannot be obtained, -1 is returned.
2554 CFAbsoluteTime
SecRevocationDbGetNextUpdateTime(void) {
2555 __block CFAbsoluteTime result
= -1;
2556 SecRevocationDbWith(^(SecRevocationDbRef db
) {
2557 result
= _SecRevocationDbGetNextUpdateTime(db
, NULL
);
2562 /* Return the serial background queue for database updates.
2563 If the queue cannot be obtained, NULL is returned.
2565 dispatch_queue_t
SecRevocationDbGetUpdateQueue(void) {
2566 __block dispatch_queue_t result
= NULL
;
2567 SecRevocationDbWith(^(SecRevocationDbRef db
) {
2568 result
= _SecRevocationDbGetUpdateQueue(db
);
2573 /* Remove all entries in the revocation database and reset its version to 0.
2574 (This function is expected to be called only by the database maintainer,
2575 normally the system instance of trustd.)
2577 void SecRevocationDbRemoveAllEntries(void) {
2578 SecRevocationDbWith(^(SecRevocationDbRef db
) {
2579 _SecRevocationDbRemoveAllEntries(db
);
2583 /* Release all connections to the revocation database.
2585 void SecRevocationDbReleaseAllConnections(void) {
2586 SecRevocationDbWith(^(SecRevocationDbRef db
) {
2587 SecDbReleaseAllConnections((db
) ? db
->db
: NULL
);
2591 /* === SecRevocationDb API === */
2593 /* Given a certificate and its issuer, returns a SecValidInfoRef if the
2594 valid database contains matching info; otherwise returns NULL.
2595 Caller must release the returned SecValidInfoRef by calling
2596 SecValidInfoRelease when finished.
2598 SecValidInfoRef
SecRevocationDbCopyMatching(SecCertificateRef certificate
,
2599 SecCertificateRef issuer
) {
2600 __block SecValidInfoRef result
= NULL
;
2601 SecRevocationDbWith(^(SecRevocationDbRef db
) {
2602 result
= _SecRevocationDbCopyMatching(db
, certificate
, issuer
);
2607 /* Return the current version of the revocation database.
2608 A version of 0 indicates an empty database which must be populated.
2609 If the version cannot be obtained, -1 is returned.
2611 CFIndex
SecRevocationDbGetVersion(void) {
2612 __block CFIndex result
= -1;
2613 SecRevocationDbWith(^(SecRevocationDbRef db
) {
2614 result
= (CFIndex
)_SecRevocationDbGetVersion(db
, NULL
);
2619 /* Return the current schema version of the revocation database.
2620 A version of 0 indicates an empty database which must be populated.
2621 If the schema version cannot be obtained, -1 is returned.
2623 CFIndex
SecRevocationDbGetSchemaVersion(void) {
2624 __block CFIndex result
= -1;
2625 SecRevocationDbWith(^(SecRevocationDbRef db
) {
2626 result
= (CFIndex
)_SecRevocationDbGetSchemaVersion(db
, NULL
);
2631 /* Return the current update format of the revocation database.
2632 A version of 0 indicates the format was unknown.
2633 If the update format cannot be obtained, -1 is returned.
2635 CFIndex
SecRevocationDbGetUpdateFormat(void) {
2636 __block CFIndex result
= -1;
2637 SecRevocationDbWith(^(SecRevocationDbRef db
) {
2638 result
= (CFIndex
)_SecRevocationDbGetUpdateFormat(db
, NULL
);