2 * Copyright (c) 2016 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 <Security/SecCertificateInternal.h>
32 #include <Security/SecCMS.h>
33 #include <Security/SecFramework.h>
34 #include <Security/SecInternal.h>
35 #include <AssertMacros.h>
42 #include <dispatch/dispatch.h>
44 #include "utilities/debugging.h"
45 #include "utilities/sqlutils.h"
46 #include "utilities/SecAppleAnchorPriv.h"
47 #include "utilities/iOSforOSX.h"
48 #include <utilities/SecCFError.h>
49 #include <utilities/SecCFRelease.h>
50 #include <utilities/SecCFWrappers.h>
51 #include <utilities/SecDb.h>
52 #include <utilities/SecFileLocations.h>
55 #include <malloc/malloc.h>
56 #include <xpc/activity.h>
57 #include <xpc/private.h>
59 #include <CFNetwork/CFHTTPMessage.h>
60 #include <CoreFoundation/CFURL.h>
61 #include <CoreFoundation/CFUtilities.h>
64 static CFStringRef kAcceptEncoding
= CFSTR("Accept-Encoding");
65 static CFStringRef kAppEncoding
= CFSTR("deflate");
66 static CFStringRef kUserAgent
= CFSTR("User-Agent");
67 static CFStringRef kAppUserAgent
= CFSTR("com.apple.trustd/1.0");
68 static CFStringRef kValidUpdateServer
= CFSTR("valid.apple.com");
70 static CFStringRef kSecPrefsDomain
= CFSTR("com.apple.security");
71 static CFStringRef kUpdateServerKey
= CFSTR("ValidUpdateServer");
72 static CFStringRef kUpdateEnabledKey
= CFSTR("ValidUpdateEnabled");
73 static CFStringRef kUpdateIntervalKey
= CFSTR("ValidUpdateInterval");
74 static CFStringRef kUpdateWiFiOnlyKey
= CFSTR("ValidUpdateWiFiOnly");
76 typedef CF_OPTIONS(CFOptionFlags
, SecValidInfoFlags
) {
77 kSecValidInfoComplete
= 1u << 0,
78 kSecValidInfoCheckOCSP
= 1u << 1,
79 kSecValidInfoKnownOnly
= 1u << 2,
80 kSecValidInfoRequireCT
= 1u << 3,
81 kSecValidInfoAllowlist
= 1u << 4
84 /* minimum initial interval after process startup */
85 #define kSecMinUpdateInterval (60.0 * 5)
87 /* second and subsequent intervals */
88 #define kSecStdUpdateInterval (60.0 * 60)
90 /* maximum allowed interval */
91 #define kSecMaxUpdateInterval (60.0 * 60 * 24 * 7)
93 /* background download timeout */
94 #define kSecMaxDownloadSeconds (60.0 * 10)
96 #define kSecRevocationBasePath "/Library/Keychains/crls"
97 #define kSecRevocationDbFileName "valid.sqlite3"
99 bool SecRevocationDbVerifyUpdate(CFDictionaryRef update
);
100 CFIndex
SecRevocationDbIngestUpdate(CFDictionaryRef update
);
101 void SecRevocationDbApplyUpdate(CFDictionaryRef update
, CFIndex version
);
102 CFAbsoluteTime
SecRevocationDbComputeNextUpdateTime(CFDictionaryRef update
);
103 void SecRevocationDbSetSchemaVersion(CFIndex dbversion
);
104 void SecRevocationDbSetNextUpdateTime(CFAbsoluteTime nextUpdate
);
105 CFAbsoluteTime
SecRevocationDbGetNextUpdateTime(void);
106 dispatch_queue_t
SecRevocationDbGetUpdateQueue(void);
107 void SecRevocationDbRemoveAllEntries(void);
110 static CFDataRef
copyInflatedData(CFDataRef data
) {
115 memset(&zs
, 0, sizeof(zs
));
116 /* 32 is a magic value which enables automatic header detection
117 of gzip or zlib compressed data. */
118 if (inflateInit2(&zs
, 32+MAX_WBITS
) != Z_OK
) {
121 zs
.next_in
= (UInt8
*)(CFDataGetBytePtr(data
));
122 zs
.avail_in
= (uInt
)CFDataGetLength(data
);
124 CFMutableDataRef outData
= CFDataCreateMutable(NULL
, 0);
128 CFIndex buf_sz
= malloc_good_size(zs
.avail_in
? zs
.avail_in
: 1024 * 4);
129 unsigned char *buf
= malloc(buf_sz
);
132 zs
.next_out
= (Bytef
*)buf
;
133 zs
.avail_out
= (uInt
)buf_sz
;
134 rc
= inflate(&zs
, 0);
135 CFIndex outLen
= CFDataGetLength(outData
);
136 if (outLen
< (CFIndex
)zs
.total_out
) {
137 CFDataAppendBytes(outData
, (const UInt8
*)buf
, (CFIndex
)zs
.total_out
- outLen
);
139 } while (rc
== Z_OK
);
146 if (rc
!= Z_STREAM_END
) {
147 CFReleaseSafe(outData
);
150 return (CFDataRef
)outData
;
153 static CFDataRef
copyDeflatedData(CFDataRef data
) {
158 memset(&zs
, 0, sizeof(zs
));
159 if (deflateInit(&zs
, Z_BEST_COMPRESSION
) != Z_OK
) {
162 zs
.next_in
= (UInt8
*)(CFDataGetBytePtr(data
));
163 zs
.avail_in
= (uInt
)CFDataGetLength(data
);
165 CFMutableDataRef outData
= CFDataCreateMutable(NULL
, 0);
169 CFIndex buf_sz
= malloc_good_size(zs
.avail_in
? zs
.avail_in
: 1024 * 4);
170 unsigned char *buf
= malloc(buf_sz
);
171 int rc
= Z_BUF_ERROR
;
173 zs
.next_out
= (Bytef
*)buf
;
174 zs
.avail_out
= (uInt
)buf_sz
;
175 rc
= deflate(&zs
, Z_FINISH
);
177 if (rc
== Z_OK
|| rc
== Z_STREAM_END
) {
178 CFIndex buf_used
= buf_sz
- zs
.avail_out
;
179 CFDataAppendBytes(outData
, (const UInt8
*)buf
, buf_used
);
181 else if (rc
== Z_BUF_ERROR
) {
183 buf_sz
= malloc_good_size(buf_sz
* 2);
184 buf
= malloc(buf_sz
);
186 rc
= Z_OK
; /* try again with larger buffer */
189 } while (rc
== Z_OK
&& zs
.avail_in
);
196 if (rc
!= Z_STREAM_END
) {
197 CFReleaseSafe(outData
);
200 return (CFDataRef
)outData
;
203 static uint32_t calculateCrc32(CFDataRef data
) {
204 if (!data
) { return 0; }
205 uint32_t crc
= (uint32_t)crc32(0L, Z_NULL
, 0);
206 uint32_t len
= (uint32_t)CFDataGetLength(data
);
207 const unsigned char *bytes
= CFDataGetBytePtr(data
);
208 return (uint32_t)crc32(crc
, bytes
, len
);
211 static int checkBasePath(const char *basePath
) {
212 return mkpath_np((char*)basePath
, 0755);
215 static int writeFile(const char *fileName
,
216 const unsigned char *bytes
, // compressed data, if crc != 0
217 size_t numBytes
, // length of content to write
218 uint32_t crc
, // crc32 over uncompressed content
219 uint32_t length
) { // uncompressed content length
222 size_t numToWrite
=numBytes
;
223 const unsigned char *p
=bytes
;
225 fd
= open(fileName
, O_RDWR
| O_CREAT
| O_TRUNC
, 0644);
226 if(fd
< 0) { return errno
; }
227 off
= lseek(fd
, 0, SEEK_SET
);
228 if(off
< 0) { return errno
; }
230 /* add gzip header per RFC1952 2.2 */
231 uint8_t hdr
[10] = { 31, 139, 8, 0, 0, 0, 0, 0, 2, 3 };
232 write(fd
, hdr
, sizeof(hdr
));
233 /* skip 2-byte stream header and 4-byte trailing CRC */
234 if (numToWrite
> 6) {
239 off
= write(fd
, p
, numToWrite
);
240 if((size_t)off
!= numToWrite
) {
246 /* add gzip trailer per RFC1952 2.2 */
247 /* note: gzip seems to want these values in host byte order. */
248 write(fd
, &crc
, sizeof(crc
));
249 write(fd
, &length
, sizeof(length
));
255 static int readFile(const char *fileName
,
256 CFDataRef
*bytes
) { // allocated and returned
264 fd
= open(fileName
, O_RDONLY
);
265 if(fd
< 0) { return errno
; }
266 rtn
= fstat(fd
, &sb
);
267 if(rtn
) { goto errOut
; }
268 if (sb
.st_size
> (off_t
) ((UINT32_MAX
>> 1)-1)) {
272 size
= (size_t)sb
.st_size
;
274 *bytes
= (CFDataRef
)CFDataCreateMutable(NULL
, (CFIndex
)size
);
280 CFDataSetLength((CFMutableDataRef
)*bytes
, (CFIndex
)size
);
281 buf
= (char*)CFDataGetBytePtr(*bytes
);
282 rrc
= read(fd
, buf
, size
);
283 if(rrc
!= (ssize_t
) size
) {
293 CFReleaseNull(*bytes
);
298 static bool isDbOwner() {
299 #if TARGET_OS_EMBEDDED
300 if (getuid() == 64) // _securityd
312 // MARK: SecValidUpdateRequest
314 /* ======================================================================
315 SecValidUpdateRequest
316 ======================================================================*/
318 static CFAbsoluteTime gUpdateRequestScheduled
= 0.0;
319 static CFAbsoluteTime gNextUpdate
= 0.0;
320 static CFIndex gUpdateInterval
= 0;
321 static CFIndex gLastVersion
= 0;
323 typedef struct SecValidUpdateRequest
*SecValidUpdateRequestRef
;
324 struct SecValidUpdateRequest
{
325 asynchttp_t http
; /* Must be first field. */
326 CFStringRef server
; /* Server name. (e.g. "valid.apple.com") */
327 CFIndex version
; /* Our current version. */
328 xpc_object_t criteria
; /* Constraints dictionary for request. */
331 static void SecValidUpdateRequestRelease(SecValidUpdateRequestRef request
) {
335 CFReleaseSafe(request
->server
);
336 asynchttp_free(&request
->http
);
337 if (request
->criteria
) {
338 xpc_release(request
->criteria
);
343 static void SecValidUpdateRequestIssue(SecValidUpdateRequestRef request
) {
344 // issue the async http request now
345 CFStringRef urlStr
= CFStringCreateWithFormat(kCFAllocatorDefault
, NULL
,
346 CFSTR("https://%@/get/v%ld"),
347 request
->server
, (long)request
->version
);
349 CFURLRef url
= (urlStr
) ? CFURLCreateWithString(kCFAllocatorDefault
, urlStr
, NULL
) : NULL
;
350 CFReleaseSafe(urlStr
);
352 secnotice("validupdate", "invalid update url");
353 SecValidUpdateRequestRelease(request
);
356 CFHTTPMessageRef msg
= CFHTTPMessageCreateRequest(kCFAllocatorDefault
,
357 CFSTR("GET"), url
, kCFHTTPVersion1_1
);
360 secdebug("validupdate", "%@", msg
);
361 CFHTTPMessageSetHeaderFieldValue(msg
, CFSTR("Accept"), CFSTR("*/*"));
362 CFHTTPMessageSetHeaderFieldValue(msg
, kAcceptEncoding
, kAppEncoding
);
363 CFHTTPMessageSetHeaderFieldValue(msg
, kUserAgent
, kAppUserAgent
);
364 bool done
= asynchttp_request(msg
, kSecMaxDownloadSeconds
*NSEC_PER_SEC
, &request
->http
);
370 secdebug("validupdate", "no request issued");
371 SecValidUpdateRequestRelease(request
);
374 static bool SecValidUpdateRequestSchedule(SecValidUpdateRequestRef request
) {
375 if (!request
|| !request
->server
) {
376 secnotice("validupdate", "invalid update request");
377 SecValidUpdateRequestRelease(request
);
379 } else if (gUpdateRequestScheduled
!= 0.0) {
380 // TBD: may need a separate scheduled activity which can perform a request with
381 // fewer constraints if our request has not been satisfied for a week or so
382 secdebug("validupdate", "update request already scheduled at %f, will not reissue",
383 (double)gUpdateRequestScheduled
);
384 SecValidUpdateRequestRelease(request
);
385 return true; // request is still in the queue
387 gUpdateRequestScheduled
= CFAbsoluteTimeGetCurrent();
388 secdebug("validupdate", "scheduling update at %f", (double)gUpdateRequestScheduled
);
391 // determine whether to issue request without waiting for activity criteria to be satisfied
392 bool updateOnWiFiOnly
= true;
393 CFTypeRef value
= (CFBooleanRef
)CFPreferencesCopyValue(kUpdateWiFiOnlyKey
, kSecPrefsDomain
, kCFPreferencesAnyUser
, kCFPreferencesCurrentHost
);
394 if (isBoolean(value
)) {
395 updateOnWiFiOnly
= CFBooleanGetValue((CFBooleanRef
)value
);
397 CFReleaseNull(value
);
398 if (!updateOnWiFiOnly
) {
399 SecValidUpdateRequestIssue(request
);
400 gUpdateRequestScheduled
= 0.0;
404 xpc_object_t criteria
= xpc_dictionary_create(NULL
, NULL
, 0);
405 xpc_dictionary_set_bool(criteria
, XPC_ACTIVITY_REPEATING
, false);
406 xpc_dictionary_set_string(criteria
, XPC_ACTIVITY_PRIORITY
, XPC_ACTIVITY_PRIORITY_MAINTENANCE
);
407 // we want to start as soon as possible
408 xpc_dictionary_set_int64(criteria
, XPC_ACTIVITY_DELAY
, 0);
409 xpc_dictionary_set_int64(criteria
, XPC_ACTIVITY_GRACE_PERIOD
, 5);
410 // we are downloading data and want to use WiFi instead of cellular
411 xpc_dictionary_set_bool(criteria
, XPC_ACTIVITY_REQUIRE_NETWORK_CONNECTIVITY
, true);
412 xpc_dictionary_set_bool(criteria
, XPC_ACTIVITY_REQUIRE_INEXPENSIVE_NETWORK_CONNECTIVITY
, true);
413 xpc_dictionary_set_string(criteria
, XPC_ACTIVITY_NETWORK_TRANSFER_DIRECTION
, XPC_ACTIVITY_NETWORK_TRANSFER_DIRECTION_DOWNLOAD
);
415 if (request
->criteria
) {
416 xpc_release(request
->criteria
);
418 request
->criteria
= criteria
;
420 xpc_activity_register("com.apple.trustd.validupdate", criteria
, ^(xpc_activity_t activity
) {
421 xpc_activity_state_t activityState
= xpc_activity_get_state(activity
);
422 switch (activityState
) {
423 case XPC_ACTIVITY_STATE_CHECK_IN
: {
424 secdebug("validupdate", "xpc activity state: XPC_ACTIVITY_STATE_CHECK_IN");
427 case XPC_ACTIVITY_STATE_RUN
: {
428 secdebug("validupdate", "xpc activity state: XPC_ACTIVITY_STATE_RUN");
429 if (!xpc_activity_set_state(activity
, XPC_ACTIVITY_STATE_CONTINUE
)) {
430 secnotice("validupdate", "unable to set activity state to XPC_ACTIVITY_STATE_CONTINUE");
432 // criteria for this activity have been met; issue the network request
433 SecValidUpdateRequestIssue(request
);
434 gUpdateRequestScheduled
= 0.0;
435 if (!xpc_activity_set_state(activity
, XPC_ACTIVITY_STATE_DONE
)) {
436 secnotice("validupdate", "unable to set activity state to XPC_ACTIVITY_STATE_DONE");
441 secdebug("validupdate", "unhandled activity state (%ld)", (long)activityState
);
450 static bool SecValidUpdateRequestConsumeReply(CF_CONSUMED CFDataRef data
, CFIndex version
, bool save
) {
452 secnotice("validupdate", "invalid data");
455 CFIndex length
= CFDataGetLength(data
);
456 secdebug("validupdate", "data received: %ld bytes", (long)length
);
458 char *curPathBuf
= NULL
;
460 checkBasePath(kSecRevocationBasePath
);
461 asprintf(&curPathBuf
, "%s/%s.plist.gz", kSecRevocationBasePath
, "update-current");
463 // expand compressed data
464 CFDataRef inflatedData
= copyInflatedData(data
);
466 CFIndex cmplength
= length
;
467 length
= CFDataGetLength(inflatedData
);
469 uint32_t crc
= calculateCrc32(inflatedData
);
470 writeFile(curPathBuf
, CFDataGetBytePtr(data
), cmplength
, crc
, (uint32_t)length
);
475 secdebug("validupdate", "data expanded: %ld bytes", (long)length
);
477 // mmap the expanded data while property list object is created
478 CFPropertyListRef propertyList
= NULL
;
479 char *expPathBuf
= NULL
;
480 asprintf(&expPathBuf
, "%s/%s.plist", kSecRevocationBasePath
, "update-current");
482 writeFile(expPathBuf
, CFDataGetBytePtr(data
), length
, 0, (uint32_t)length
);
484 // no copies of data should exist in memory at this point
485 int fd
= open(expPathBuf
, O_RDONLY
);
487 secerror("unable to open %s (errno %d)", expPathBuf
, errno
);
490 void *p
= mmap(NULL
, length
, PROT_READ
, MAP_PRIVATE
, fd
, 0);
491 if (!p
|| p
== MAP_FAILED
) {
492 secerror("unable to map %s (errno %d)", expPathBuf
, errno
);
495 data
= CFDataCreateWithBytesNoCopy(NULL
, (const UInt8
*)p
, length
, kCFAllocatorNull
);
497 propertyList
= CFPropertyListCreateWithData(kCFAllocatorDefault
, data
,
498 kCFPropertyListImmutable
, NULL
, NULL
);
500 int rtn
= munmap(p
, length
);
502 secerror("unable to unmap %ld bytes at %p (error %d)", (long)length
, p
, rtn
);
507 // all done with this file
508 (void)remove(expPathBuf
);
513 CFIndex curVersion
= version
;
514 Boolean fullUpdate
= false;
515 if (isDictionary(propertyList
)) {
516 if (SecRevocationDbVerifyUpdate((CFDictionaryRef
)propertyList
)) {
517 CFTypeRef value
= (CFBooleanRef
)CFDictionaryGetValue((CFDictionaryRef
)propertyList
, CFSTR("full"));
518 if (isBoolean(value
)) {
519 fullUpdate
= CFBooleanGetValue((CFBooleanRef
)value
);
521 curVersion
= SecRevocationDbIngestUpdate((CFDictionaryRef
)propertyList
);
522 gNextUpdate
= SecRevocationDbComputeNextUpdateTime((CFDictionaryRef
)propertyList
);
525 secerror("update failed: could not create property list");
527 CFReleaseSafe(propertyList
);
529 if (curVersion
> version
) {
530 secdebug("validupdate", "update received: v%ld", (unsigned long)curVersion
);
531 // save this update and make it current
532 char *newPathBuf
= NULL
;
534 asprintf(&newPathBuf
, "%s/update-full.plist.gz", kSecRevocationBasePath
);
535 //%%% glob and remove all "update-v*.plist.gz" files here
538 asprintf(&newPathBuf
, "%s/update-v%ld.plist.gz", kSecRevocationBasePath
, (unsigned long)curVersion
);
543 // try to save the latest full update
544 (void)rename(curPathBuf
, newPathBuf
);
547 // try to remove delta updates
548 (void)remove(curPathBuf
);
553 gLastVersion
= curVersion
;
559 // remember next update time in case of restart
560 SecRevocationDbSetNextUpdateTime(gNextUpdate
);
565 static bool SecValidUpdateRequestSatisfiedLocally(SecValidUpdateRequestRef request
) {
566 // if we can read the requested data locally, we don't need a network request.
568 // note: only need this if we don't have any version and are starting from scratch.
569 // otherwise we don't know what the current version actually is; only the server
570 // can tell us that at any given time, so we have to ask it for any version >0.
571 // we cannot reuse a saved delta without being on the exact version from which
574 // - if requested version N is 0, and no 'update-full' in kSecRevocationBasePath,
575 // call a OTATrustUtilities SPI to obtain static 'update-full' asset data.
577 CFDataRef data
= NULL
;
578 char *curPathBuf
= NULL
;
579 if (0 == request
->version
) {
580 asprintf(&curPathBuf
, "%s/update-full.plist.gz", kSecRevocationBasePath
);
584 //asprintf(&curPathBuf, "%s/update-v%ld.plist.gz", kSecRevocationBasePath, (unsigned long)request->version);
587 secdebug("validupdate", "will read data from \"%s\"", curPathBuf
);
588 int rtn
= readFile(curPathBuf
, &data
);
590 if (rtn
) { CFReleaseNull(data
); }
593 secdebug("validupdate", "read %ld bytes from file", (long)CFDataGetLength(data
));
594 //%%% TBD dispatch this work on the request's queue and return true immediately
595 return SecValidUpdateRequestConsumeReply(data
, request
->version
, false);
600 static void SecValidUpdateRequestCompleted(asynchttp_t
*http
, CFTimeInterval maxAge
) {
601 // cast depends on http being first field in struct SecValidUpdateRequest.
602 SecValidUpdateRequestRef request
= (SecValidUpdateRequestRef
)http
;
604 secnotice("validupdate", "no request to complete!");
607 CFDataRef data
= (request
->http
.response
) ? CFHTTPMessageCopyBody(request
->http
.response
) : NULL
;
608 CFIndex version
= request
->version
;
609 SecValidUpdateRequestRelease(request
);
611 secdebug("validupdate", "no data received");
614 SecValidUpdateRequestConsumeReply(data
, version
, true);
619 // MARK: SecValidInfoRef
621 /* ======================================================================
623 ======================================================================
626 static SecValidInfoRef
SecValidInfoCreate(SecValidInfoFormat format
,
629 CFDataRef issuerHash
) {
630 SecValidInfoRef validInfo
;
631 validInfo
= (SecValidInfoRef
)calloc(1, sizeof(struct __SecValidInfo
));
632 if (!validInfo
) { return NULL
; }
634 CFRetainSafe(certHash
);
635 CFRetainSafe(issuerHash
);
636 validInfo
->format
= format
;
637 validInfo
->certHash
= certHash
;
638 validInfo
->issuerHash
= issuerHash
;
639 validInfo
->valid
= (flags
& kSecValidInfoAllowlist
);
640 validInfo
->complete
= (flags
& kSecValidInfoComplete
);
641 validInfo
->checkOCSP
= (flags
& kSecValidInfoCheckOCSP
);
642 validInfo
->knownOnly
= (flags
& kSecValidInfoKnownOnly
);
643 validInfo
->requireCT
= (flags
& kSecValidInfoRequireCT
);
648 void SecValidInfoRelease(SecValidInfoRef validInfo
) {
650 CFReleaseSafe(validInfo
->certHash
);
651 CFReleaseSafe(validInfo
->issuerHash
);
658 // MARK: SecRevocationDb
660 /* ======================================================================
662 ======================================================================
665 /* SecRevocationDbCheckNextUpdate returns true if we dispatched an
666 update request, otherwise false.
668 bool SecRevocationDbCheckNextUpdate(void) {
669 // are we the db owner instance?
673 CFTypeRef value
= NULL
;
675 // is it time to check?
676 CFAbsoluteTime now
= CFAbsoluteTimeGetCurrent();
677 CFAbsoluteTime minNextUpdate
= now
+ gUpdateInterval
;
678 if (0 == gNextUpdate
) {
679 // first time we're called, check if we have a saved nextUpdate value
680 gNextUpdate
= SecRevocationDbGetNextUpdateTime();
681 // pin to minimum first-time interval, so we don't perturb startup
682 minNextUpdate
= now
+ kSecMinUpdateInterval
;
683 if (gNextUpdate
< minNextUpdate
) {
684 gNextUpdate
= minNextUpdate
;
686 // allow pref to override update interval, if it exists
687 CFIndex interval
= -1;
688 value
= (CFNumberRef
)CFPreferencesCopyValue(kUpdateIntervalKey
, kSecPrefsDomain
, kCFPreferencesAnyUser
, kCFPreferencesCurrentHost
);
689 if (isNumber(value
)) {
690 if (CFNumberGetValue((CFNumberRef
)value
, kCFNumberCFIndexType
, &interval
)) {
691 if (interval
< kSecMinUpdateInterval
) {
692 interval
= kSecMinUpdateInterval
;
693 } else if (interval
> kSecMaxUpdateInterval
) {
694 interval
= kSecMaxUpdateInterval
;
698 CFReleaseNull(value
);
699 gUpdateInterval
= kSecStdUpdateInterval
;
701 gUpdateInterval
= interval
;
704 if (gNextUpdate
> now
) {
707 // set minimum next update time here in case we can't get an update
708 gNextUpdate
= minNextUpdate
;
710 // determine which server to query
712 value
= (CFStringRef
)CFPreferencesCopyValue(kUpdateServerKey
, kSecPrefsDomain
, kCFPreferencesAnyUser
, kCFPreferencesCurrentHost
);
713 if (isString(value
)) {
714 server
= (CFStringRef
) CFRetain(value
);
716 server
= (CFStringRef
) CFRetain(kValidUpdateServer
);
718 CFReleaseNull(value
);
720 // determine what version we currently have
721 CFIndex version
= SecRevocationDbGetVersion();
722 secdebug("validupdate", "got version %ld from db", (long)version
);
724 if (gLastVersion
> 0) {
725 secdebug("validupdate", "error getting version; using last good version: %ld", (long)gLastVersion
);
727 version
= gLastVersion
;
730 // determine whether we need to recreate the database
731 CFIndex db_version
= SecRevocationDbGetSchemaVersion();
732 if (db_version
== 1) {
733 /* code which created this db failed to update changed flags,
734 so we need to fully rebuild its contents. */
735 SecRevocationDbRemoveAllEntries();
736 version
= gLastVersion
= 0;
739 // determine whether update fetching is enabled
740 #if (__MAC_OS_X_VERSION_MIN_REQUIRED >= 101300 || __IPHONE_OS_VERSION_MIN_REQUIRED >= 110000)
741 bool updateEnabled
= true; // macOS 10.13 or iOS 11.0, not tvOS, not watchOS
743 bool updateEnabled
= false;
745 value
= (CFBooleanRef
)CFPreferencesCopyValue(kUpdateEnabledKey
, kSecPrefsDomain
, kCFPreferencesAnyUser
, kCFPreferencesCurrentHost
);
746 if (isBoolean(value
)) {
747 updateEnabled
= CFBooleanGetValue((CFBooleanRef
)value
);
749 CFReleaseNull(value
);
751 // set up a network request
752 SecValidUpdateRequestRef request
= (SecValidUpdateRequestRef
)calloc(1, sizeof(*request
));
753 request
->http
.queue
= SecRevocationDbGetUpdateQueue();
754 request
->http
.completed
= SecValidUpdateRequestCompleted
;
755 request
->server
= server
;
756 request
->version
= version
;
757 request
->criteria
= NULL
;
759 if (SecValidUpdateRequestSatisfiedLocally(request
)) {
760 SecValidUpdateRequestRelease(request
);
763 if (!updateEnabled
) {
764 SecValidUpdateRequestRelease(request
);
767 return SecValidUpdateRequestSchedule(request
);
770 bool SecRevocationDbVerifyUpdate(CFDictionaryRef update
) {
772 //%%% TBD: check signature with new SecPolicyRef; rdar://28619456
776 CFAbsoluteTime
SecRevocationDbComputeNextUpdateTime(CFDictionaryRef update
) {
777 CFIndex interval
= 0;
778 // get server-provided interval
780 CFTypeRef value
= (CFNumberRef
)CFDictionaryGetValue(update
, CFSTR("check-again"));
781 if (isNumber(value
)) {
782 CFNumberGetValue((CFNumberRef
)value
, kCFNumberCFIndexType
, &interval
);
785 // try to use interval preference if it exists
786 CFTypeRef value
= (CFNumberRef
)CFPreferencesCopyValue(kUpdateIntervalKey
, kSecPrefsDomain
, kCFPreferencesAnyUser
, kCFPreferencesCurrentHost
);
787 if (isNumber(value
)) {
788 CFNumberGetValue((CFNumberRef
)value
, kCFNumberCFIndexType
, &interval
);
790 CFReleaseNull(value
);
793 if (interval
< kSecMinUpdateInterval
) {
794 interval
= kSecMinUpdateInterval
;
795 } else if (interval
> kSecMaxUpdateInterval
) {
796 interval
= kSecMaxUpdateInterval
;
799 // compute randomization factor, between 0 and 50% of the interval
800 CFIndex fuzz
= arc4random() % (long)(interval
/2.0);
801 CFAbsoluteTime nextUpdate
= CFAbsoluteTimeGetCurrent() + interval
+ fuzz
;
802 secdebug("validupdate", "next update in %ld seconds", (long)(interval
+ fuzz
));
806 CFIndex
SecRevocationDbIngestUpdate(CFDictionaryRef update
) {
811 CFTypeRef value
= (CFNumberRef
)CFDictionaryGetValue(update
, CFSTR("version"));
812 if (isNumber(value
)) {
813 if (!CFNumberGetValue((CFNumberRef
)value
, kCFNumberCFIndexType
, &version
)) {
817 SecRevocationDbApplyUpdate(update
, version
);
822 /* Database management */
824 /* v1 = initial version */
825 /* v2 = fix for group entry transitions */
827 #define kSecRevocationDbSchemaVersion 2
829 #define selectGroupIdSQL CFSTR("SELECT DISTINCT groupid " \
830 "FROM issuers WHERE issuer_hash=?")
832 static SecDbRef
SecRevocationDbCreate(CFStringRef path
) {
833 /* only the db owner should open a read-write connection. */
834 bool readWrite
= isDbOwner();
837 SecDbRef result
= SecDbCreateWithOptions(path
, mode
, readWrite
, false, false, ^bool (SecDbConnectionRef dbconn
, bool didCreate
, bool *callMeAgainForNextConnection
, CFErrorRef
*error
) {
838 __block
bool ok
= true;
839 CFErrorRef localError
= NULL
;
840 if (ok
&& !SecDbWithSQL(dbconn
, selectGroupIdSQL
, &localError
, NULL
) && CFErrorGetCode(localError
) == SQLITE_ERROR
) {
841 /* SecDbWithSQL returns SQLITE_ERROR if the table we are preparing the above statement for doesn't exist. */
843 /* admin table holds these key-value (or key-ival) pairs:
844 'version' (integer) // version of database content
845 'check_again' (double) // CFAbsoluteTime of next check (optional; this value is currently stored in prefs)
846 'db_version' (integer) // version of database schema
847 'db_hash' (blob) // SHA-256 database hash
848 --> entries in admin table are unique by text key
850 issuers table holds map of issuing CA hashes to group identifiers:
851 issuer_hash (blob) // SHA-256 hash of issuer certificate (primary key)
852 groupid (integer) // associated group identifier in group ID table
853 --> entries in issuers table are unique by issuer_hash;
854 multiple issuer entries may have the same groupid!
856 groups table holds records with these attributes:
857 groupid (integer) // ordinal ID associated with this group entry
858 flags (integer) // a bitmask of the following values:
859 kSecValidInfoComplete (0x00000001) set if we have all revocation info for this issuer group
860 kSecValidInfoCheckOCSP (0x00000002) set if must check ocsp for certs from this issuer group
861 kSecValidInfoKnownOnly (0x00000004) set if any CA from this issuer group must be in database
862 kSecValidInfoRequireCT (0x00000008) set if all certs from this issuer group must have SCTs
863 kSecValidInfoAllowlist (0x00000010) set if this entry describes valid certs (i.e. is allowed)
864 format (integer) // an integer describing format of entries:
865 kSecValidInfoFormatUnknown (0) unknown format
866 kSecValidInfoFormatSerial (1) serial number, not greater than 20 bytes in length
867 kSecValidInfoFormatSHA256 (2) SHA-256 hash, 32 bytes in length
868 kSecValidInfoFormatNto1 (3) filter data blob of arbitrary length
869 data (blob) // Bloom filter data if format is 'nto1', otherwise NULL
870 --> entries in groups table are unique by groupid
872 serials table holds serial number blobs with these attributes:
873 rowid (integer) // ordinal ID associated with this serial number entry
874 serial (blob) // serial number
875 groupid (integer) // identifier for issuer group in the groups table
876 --> entries in serials table are unique by serial and groupid
878 hashes table holds SHA-256 hashes of certificates with these attributes:
879 rowid (integer) // ordinal ID associated with this sha256 hash entry
880 sha256 (blob) // SHA-256 hash of subject certificate
881 groupid (integer) // identifier for issuer group in the groups table
882 --> entries in hashes table are unique by sha256 and groupid
884 ok
&= SecDbTransaction(dbconn
, kSecDbExclusiveTransactionType
, error
, ^(bool *commit
) {
885 ok
= SecDbExec(dbconn
,
886 CFSTR("CREATE TABLE admin("
887 "key TEXT PRIMARY KEY NOT NULL,"
888 "ival INTEGER NOT NULL,"
891 "CREATE TABLE issuers("
892 "issuer_hash BLOB PRIMARY KEY NOT NULL,"
893 "groupid INTEGER NOT NULL"
895 "CREATE INDEX issuer_idx ON issuers(issuer_hash);"
896 "CREATE TABLE groups("
897 "groupid INTEGER PRIMARY KEY AUTOINCREMENT,"
898 "flags INTEGER NOT NULL,"
899 "format INTEGER NOT NULL,"
902 "CREATE TABLE serials("
903 "rowid INTEGER PRIMARY KEY AUTOINCREMENT,"
904 "serial BLOB NOT NULL,"
905 "groupid INTEGER NOT NULL,"
906 "UNIQUE(serial,groupid)"
908 "CREATE TABLE hashes("
909 "rowid INTEGER PRIMARY KEY AUTOINCREMENT,"
910 "sha256 BLOB NOT NULL,"
911 "groupid INTEGER NOT NULL,"
912 "UNIQUE(sha256,groupid)"
914 "CREATE TRIGGER group_del BEFORE DELETE ON groups FOR EACH ROW "
916 "DELETE FROM serials WHERE groupid=OLD.groupid; "
917 "DELETE FROM hashes WHERE groupid=OLD.groupid; "
918 "DELETE FROM issuers WHERE groupid=OLD.groupid; "
923 CFReleaseSafe(localError
);
925 secerror("%s failed: %@", didCreate
? "Create" : "Open", error
? *error
: NULL
);
932 typedef struct __SecRevocationDb
*SecRevocationDbRef
;
933 struct __SecRevocationDb
{
935 dispatch_queue_t update_queue
;
936 bool fullUpdateInProgress
;
939 static dispatch_once_t kSecRevocationDbOnce
;
940 static SecRevocationDbRef kSecRevocationDb
= NULL
;
942 static SecRevocationDbRef
SecRevocationDbInit(CFStringRef db_name
) {
943 SecRevocationDbRef
this;
944 dispatch_queue_attr_t attr
;
946 require(this = (SecRevocationDbRef
)malloc(sizeof(struct __SecRevocationDb
)), errOut
);
948 this->update_queue
= NULL
;
949 this->fullUpdateInProgress
= false;
951 require(this->db
= SecRevocationDbCreate(db_name
), errOut
);
952 attr
= dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL
, QOS_CLASS_BACKGROUND
, 0);
953 require(this->update_queue
= dispatch_queue_create(NULL
, attr
), errOut
);
958 secdebug("validupdate", "Failed to create db at \"%@\"", db_name
);
960 if (this->update_queue
) {
961 dispatch_release(this->update_queue
);
963 CFReleaseSafe(this->db
);
969 static CFStringRef
SecRevocationDbCopyPath(void) {
970 CFURLRef revDbURL
= NULL
;
971 CFStringRef revInfoRelPath
= NULL
;
972 if ((revInfoRelPath
= CFStringCreateWithFormat(NULL
, NULL
, CFSTR("%s"), kSecRevocationDbFileName
)) != NULL
) {
973 revDbURL
= SecCopyURLForFileInRevocationInfoDirectory(revInfoRelPath
);
975 CFReleaseSafe(revInfoRelPath
);
977 CFStringRef revDbPath
= NULL
;
979 revDbPath
= CFURLCopyFileSystemPath(revDbURL
, kCFURLPOSIXPathStyle
);
985 static void SecRevocationDbWith(void(^dbJob
)(SecRevocationDbRef db
)) {
986 dispatch_once(&kSecRevocationDbOnce
, ^{
987 CFStringRef dbPath
= SecRevocationDbCopyPath();
989 kSecRevocationDb
= SecRevocationDbInit(dbPath
);
993 // Do pre job run work here (cancel idle timers etc.)
994 if (kSecRevocationDb
->fullUpdateInProgress
) {
995 return; // this would block since SecDb has an exclusive transaction lock
997 dbJob(kSecRevocationDb
);
998 // Do post job run work here (gc timer, etc.)
1001 /* Instance implementation. */
1003 #define selectVersionSQL CFSTR("SELECT ival FROM admin " \
1004 "WHERE key='version'")
1005 #define selectDbVersionSQL CFSTR("SELECT ival FROM admin " \
1006 "WHERE key='db_version'")
1007 #define selectDbHashSQL CFSTR("SELECT value FROM admin " \
1008 "WHERE key='db_hash'")
1009 #define selectNextUpdateSQL CFSTR("SELECT value FROM admin " \
1010 "WHERE key='check_again'")
1011 #define selectGroupRecordSQL CFSTR("SELECT flags,format,data FROM " \
1012 "groups WHERE groupid=?")
1013 #define selectSerialRecordSQL CFSTR("SELECT rowid FROM serials " \
1014 "WHERE serial=? AND groupid=?")
1015 #define selectHashRecordSQL CFSTR("SELECT rowid FROM hashes " \
1016 "WHERE sha256=? AND groupid=?")
1017 #define insertAdminRecordSQL CFSTR("INSERT OR REPLACE INTO admin " \
1018 "(key,ival,value) VALUES (?,?,?)")
1019 #define insertIssuerRecordSQL CFSTR("INSERT OR REPLACE INTO issuers " \
1020 "(issuer_hash,groupid) VALUES (?,?)")
1021 #define insertGroupRecordSQL CFSTR("INSERT OR REPLACE INTO groups " \
1022 "(groupid,flags,format,data) VALUES (?,?,?,?)")
1023 #define insertSerialRecordSQL CFSTR("INSERT OR REPLACE INTO serials " \
1024 "(rowid,serial,groupid) VALUES (?,?,?)")
1025 #define insertSha256RecordSQL CFSTR("INSERT OR REPLACE INTO hashes " \
1026 "(rowid,sha256,groupid) VALUES (?,?,?)")
1027 #define deleteGroupRecordSQL CFSTR("DELETE FROM groups WHERE groupid=?")
1029 #define deleteAllEntriesSQL CFSTR("DELETE from hashes; " \
1030 "DELETE from serials; DELETE from issuers; DELETE from groups; " \
1031 "DELETE from admin; DELETE from sqlite_sequence; VACUUM")
1033 static int64_t _SecRevocationDbGetVersion(SecRevocationDbRef
this, CFErrorRef
*error
) {
1034 /* look up version entry in admin table; returns -1 on error */
1035 __block
int64_t version
= -1;
1036 __block
bool ok
= true;
1037 __block CFErrorRef localError
= NULL
;
1039 ok
&= SecDbPerformRead(this->db
, &localError
, ^(SecDbConnectionRef dbconn
) {
1040 if (ok
) ok
&= SecDbWithSQL(dbconn
, selectVersionSQL
, &localError
, ^bool(sqlite3_stmt
*selectVersion
) {
1041 ok
= SecDbStep(dbconn
, selectVersion
, &localError
, NULL
);
1042 version
= sqlite3_column_int64(selectVersion
, 0);
1046 (void) CFErrorPropagate(localError
, error
);
1050 static void _SecRevocationDbSetVersion(SecRevocationDbRef
this, CFIndex version
){
1051 secdebug("validupdate", "setting version to %ld", (long)version
);
1053 __block CFErrorRef localError
= NULL
;
1054 __block
bool ok
= true;
1055 ok
&= SecDbPerformWrite(this->db
, &localError
, ^(SecDbConnectionRef dbconn
) {
1056 ok
&= SecDbTransaction(dbconn
, kSecDbExclusiveTransactionType
, &localError
, ^(bool *commit
) {
1057 if (ok
) ok
= SecDbWithSQL(dbconn
, insertAdminRecordSQL
, &localError
, ^bool(sqlite3_stmt
*insertVersion
) {
1059 const char *versionKey
= "version";
1060 ok
= SecDbBindText(insertVersion
, 1, versionKey
, strlen(versionKey
),
1061 SQLITE_TRANSIENT
, &localError
);
1064 ok
= SecDbBindInt64(insertVersion
, 2,
1065 (sqlite3_int64
)version
, &localError
);
1068 ok
= SecDbStep(dbconn
, insertVersion
, &localError
, NULL
);
1075 secerror("_SecRevocationDbSetVersion failed: %@", localError
);
1077 CFReleaseSafe(localError
);
1080 static int64_t _SecRevocationDbGetSchemaVersion(SecRevocationDbRef
this, CFErrorRef
*error
) {
1081 /* look up db_version entry in admin table; returns -1 on error */
1082 __block
int64_t db_version
= -1;
1083 __block
bool ok
= true;
1084 __block CFErrorRef localError
= NULL
;
1086 ok
&= SecDbPerformRead(this->db
, &localError
, ^(SecDbConnectionRef dbconn
) {
1087 if (ok
) ok
&= SecDbWithSQL(dbconn
, selectDbVersionSQL
, &localError
, ^bool(sqlite3_stmt
*selectDbVersion
) {
1088 ok
= SecDbStep(dbconn
, selectDbVersion
, &localError
, NULL
);
1089 db_version
= sqlite3_column_int64(selectDbVersion
, 0);
1093 (void) CFErrorPropagate(localError
, error
);
1097 static void _SecRevocationDbSetSchemaVersion(SecRevocationDbRef
this, CFIndex dbversion
){
1098 secdebug("validupdate", "setting db_version to %ld", (long)dbversion
);
1100 __block CFErrorRef localError
= NULL
;
1101 __block
bool ok
= true;
1102 ok
&= SecDbPerformWrite(this->db
, &localError
, ^(SecDbConnectionRef dbconn
) {
1103 ok
&= SecDbTransaction(dbconn
, kSecDbExclusiveTransactionType
, &localError
, ^(bool *commit
) {
1104 if (ok
) ok
= SecDbWithSQL(dbconn
, insertAdminRecordSQL
, &localError
, ^bool(sqlite3_stmt
*insertDbVersion
) {
1106 const char *dbVersionKey
= "db_version";
1107 ok
= SecDbBindText(insertDbVersion
, 1, dbVersionKey
, strlen(dbVersionKey
),
1108 SQLITE_TRANSIENT
, &localError
);
1111 ok
= SecDbBindInt64(insertDbVersion
, 2,
1112 (sqlite3_int64
)dbversion
, &localError
);
1115 ok
= SecDbStep(dbconn
, insertDbVersion
, &localError
, NULL
);
1122 secerror("_SecRevocationDbSetSchemaVersion failed: %@", localError
);
1124 CFReleaseSafe(localError
);
1127 static CFAbsoluteTime
_SecRevocationDbGetNextUpdateTime(SecRevocationDbRef
this, CFErrorRef
*error
) {
1128 /* look up check_again entry in admin table; returns 0 on error */
1129 __block CFAbsoluteTime nextUpdate
= 0;
1130 __block
bool ok
= true;
1131 __block CFErrorRef localError
= NULL
;
1133 ok
&= SecDbPerformRead(this->db
, &localError
, ^(SecDbConnectionRef dbconn
) {
1134 if (ok
) ok
&= SecDbWithSQL(dbconn
, selectNextUpdateSQL
, &localError
, ^bool(sqlite3_stmt
*selectNextUpdate
) {
1135 ok
= SecDbStep(dbconn
, selectNextUpdate
, &localError
, NULL
);
1136 CFAbsoluteTime
*p
= (CFAbsoluteTime
*)sqlite3_column_blob(selectNextUpdate
, 0);
1138 if (sizeof(CFAbsoluteTime
) == sqlite3_column_bytes(selectNextUpdate
, 0)) {
1146 (void) CFErrorPropagate(localError
, error
);
1150 static void _SecRevocationDbSetNextUpdateTime(SecRevocationDbRef
this, CFAbsoluteTime nextUpdate
){
1151 secdebug("validupdate", "setting next update to %f", (double)nextUpdate
);
1153 __block CFErrorRef localError
= NULL
;
1154 __block
bool ok
= true;
1155 ok
&= SecDbPerformWrite(this->db
, &localError
, ^(SecDbConnectionRef dbconn
) {
1156 ok
&= SecDbTransaction(dbconn
, kSecDbExclusiveTransactionType
, &localError
, ^(bool *commit
) {
1157 if (ok
) ok
= SecDbWithSQL(dbconn
, insertAdminRecordSQL
, &localError
, ^bool(sqlite3_stmt
*insertRecord
) {
1159 const char *nextUpdateKey
= "check_again";
1160 ok
= SecDbBindText(insertRecord
, 1, nextUpdateKey
, strlen(nextUpdateKey
),
1161 SQLITE_TRANSIENT
, &localError
);
1164 ok
= SecDbBindInt64(insertRecord
, 2,
1165 (sqlite3_int64
)0, &localError
);
1168 ok
= SecDbBindBlob(insertRecord
, 3,
1169 &nextUpdate
, sizeof(CFAbsoluteTime
),
1170 SQLITE_TRANSIENT
, &localError
);
1173 ok
= SecDbStep(dbconn
, insertRecord
, &localError
, NULL
);
1180 secerror("_SecRevocationDbSetNextUpdate failed: %@", localError
);
1182 CFReleaseSafe(localError
);
1185 static bool _SecRevocationDbRemoveAllEntries(SecRevocationDbRef
this) {
1186 /* remove all entries from all tables in the database */
1187 __block
bool ok
= true;
1188 __block CFErrorRef localError
= NULL
;
1190 ok
&= SecDbPerformWrite(this->db
, &localError
, ^(SecDbConnectionRef dbconn
) {
1191 ok
&= SecDbTransaction(dbconn
, kSecDbExclusiveTransactionType
, &localError
, ^(bool *commit
) {
1192 ok
&= SecDbWithSQL(dbconn
, deleteAllEntriesSQL
, &localError
, ^bool(sqlite3_stmt
*deleteAll
) {
1193 ok
= SecDbStep(dbconn
, deleteAll
, &localError
, NULL
);
1198 /* one more thing: update the schema version */
1199 _SecRevocationDbSetSchemaVersion(this, kSecRevocationDbSchemaVersion
);
1201 CFReleaseSafe(localError
);
1205 static bool _SecRevocationDbUpdateIssuers(SecRevocationDbRef
this, int64_t groupId
, CFArrayRef issuers
, CFErrorRef
*error
) {
1206 /* insert or replace issuer records in issuers table */
1207 if (!issuers
|| groupId
< 0) {
1208 return false; /* must have something to insert, and a group to associate with it */
1210 __block
bool ok
= true;
1211 __block CFErrorRef localError
= NULL
;
1213 ok
&= SecDbPerformWrite(this->db
, &localError
, ^(SecDbConnectionRef dbconn
) {
1214 ok
&= SecDbTransaction(dbconn
, kSecDbExclusiveTransactionType
, &localError
, ^(bool *commit
) {
1215 if (isArray(issuers
)) {
1216 CFIndex issuerIX
, issuerCount
= CFArrayGetCount(issuers
);
1217 for (issuerIX
=0; issuerIX
<issuerCount
&& ok
; issuerIX
++) {
1218 CFDataRef hash
= (CFDataRef
)CFArrayGetValueAtIndex(issuers
, issuerIX
);
1219 if (!hash
) { continue; }
1220 if (ok
) ok
= SecDbWithSQL(dbconn
, insertIssuerRecordSQL
, &localError
, ^bool(sqlite3_stmt
*insertIssuer
) {
1222 ok
= SecDbBindBlob(insertIssuer
, 1,
1223 CFDataGetBytePtr(hash
),
1224 CFDataGetLength(hash
),
1225 SQLITE_TRANSIENT
, &localError
);
1228 ok
= SecDbBindInt64(insertIssuer
, 2,
1229 groupId
, &localError
);
1231 /* Execute the insert statement for this issuer record. */
1233 ok
= SecDbStep(dbconn
, insertIssuer
, &localError
, NULL
);
1242 (void) CFErrorPropagate(localError
, error
);
1246 static bool _SecRevocationDbUpdatePerIssuerData(SecRevocationDbRef
this, int64_t groupId
, CFDictionaryRef dict
, CFErrorRef
*error
) {
1247 /* insert records in serials or hashes table. */
1248 if (!dict
|| groupId
< 0) {
1249 return false; /* must have something to insert, and a group to associate with it */
1251 __block
bool ok
= true;
1252 __block CFErrorRef localError
= NULL
;
1254 ok
&= SecDbPerformWrite(this->db
, &localError
, ^(SecDbConnectionRef dbconn
) {
1255 ok
&= SecDbTransaction(dbconn
, kSecDbExclusiveTransactionType
, &localError
, ^(bool *commit
) {
1256 CFArrayRef addArray
= (CFArrayRef
)CFDictionaryGetValue(dict
, CFSTR("add"));
1257 if (isArray(addArray
)) {
1258 CFIndex identifierIX
, identifierCount
= CFArrayGetCount(addArray
);
1259 for (identifierIX
=0; identifierIX
<identifierCount
; identifierIX
++) {
1260 CFDataRef identifierData
= (CFDataRef
)CFArrayGetValueAtIndex(addArray
, identifierIX
);
1261 if (!identifierData
) { continue; }
1262 CFIndex length
= CFDataGetLength(identifierData
);
1263 /* we can figure out the format without an extra read to get the format column.
1264 len <= 20 is a serial number. len==32 is a sha256 hash. otherwise: xor. */
1265 CFStringRef sql
= NULL
;
1267 sql
= insertSerialRecordSQL
;
1268 } else if (length
== 32) {
1269 sql
= insertSha256RecordSQL
;
1271 if (!sql
) { continue; }
1273 if (ok
) ok
= SecDbWithSQL(dbconn
, sql
, &localError
, ^bool(sqlite3_stmt
*insertIdentifier
) {
1274 /* (rowid,serial|sha256,groupid) */
1275 /* rowid == column 1, autoincrement so we don't set directly */
1277 ok
= SecDbBindBlob(insertIdentifier
, 2,
1278 CFDataGetBytePtr(identifierData
),
1279 CFDataGetLength(identifierData
),
1280 SQLITE_TRANSIENT
, &localError
);
1283 ok
= SecDbBindInt64(insertIdentifier
, 3,
1284 groupId
, &localError
);
1286 /* Execute the insert statement for the identifier record. */
1288 ok
= SecDbStep(dbconn
, insertIdentifier
, &localError
, NULL
);
1297 (void) CFErrorPropagate(localError
, error
);
1301 static int64_t _SecRevocationDbUpdateGroup(SecRevocationDbRef
this, int64_t groupId
, CFDictionaryRef dict
, CFErrorRef
*error
) {
1302 /* insert group record for a given groupId.
1303 if the specified groupId is < 0, a new group entry is created.
1304 returns the groupId on success, or -1 on failure.
1306 __block
int64_t result
= -1;
1307 __block
bool ok
= true;
1308 __block CFErrorRef localError
= NULL
;
1311 return groupId
; /* no-op if no dictionary is provided */
1314 ok
&= SecDbPerformWrite(this->db
, &localError
, ^(SecDbConnectionRef dbconn
) {
1315 ok
&= SecDbTransaction(dbconn
, kSecDbExclusiveTransactionType
, &localError
, ^(bool *commit
) {
1316 ok
&= SecDbWithSQL(dbconn
, insertGroupRecordSQL
, &localError
, ^bool(sqlite3_stmt
*insertGroup
) {
1318 SecValidInfoFormat format
= kSecValidInfoFormatUnknown
;
1319 /* (groupid,flags,format,data) */
1320 /* groups.groupid */
1321 if (ok
&& !(groupId
< 0)) {
1322 /* bind to existing groupId row if known, otherwise will insert and autoincrement */
1323 ok
= SecDbBindInt64(insertGroup
, 1, groupId
, &localError
);
1328 value
= (CFBooleanRef
)CFDictionaryGetValue(dict
, CFSTR("complete"));
1329 if (isBoolean(value
)) {
1330 if (CFBooleanGetValue((CFBooleanRef
)value
)) {
1331 flags
|= kSecValidInfoComplete
;
1334 value
= (CFBooleanRef
)CFDictionaryGetValue(dict
, CFSTR("check-ocsp"));
1335 if (isBoolean(value
)) {
1336 if (CFBooleanGetValue((CFBooleanRef
)value
)) {
1337 flags
|= kSecValidInfoCheckOCSP
;
1340 value
= (CFBooleanRef
)CFDictionaryGetValue(dict
, CFSTR("known-intermediates-only"));
1341 if (isBoolean(value
)) {
1342 if (CFBooleanGetValue((CFBooleanRef
)value
)) {
1343 flags
|= kSecValidInfoKnownOnly
;
1346 value
= (CFBooleanRef
)CFDictionaryGetValue(dict
, CFSTR("require-ct"));
1347 if (isBoolean(value
)) {
1348 if (CFBooleanGetValue((CFBooleanRef
)value
)) {
1349 flags
|= kSecValidInfoRequireCT
;
1352 value
= (CFBooleanRef
)CFDictionaryGetValue(dict
, CFSTR("valid"));
1353 if (isBoolean(value
)) {
1354 if (CFBooleanGetValue((CFBooleanRef
)value
)) {
1355 flags
|= kSecValidInfoAllowlist
;
1358 ok
= SecDbBindInt(insertGroup
, 2, flags
, &localError
);
1362 value
= (CFBooleanRef
)CFDictionaryGetValue(dict
, CFSTR("format"));
1363 if (isString(value
)) {
1364 if (CFStringCompare((CFStringRef
)value
, CFSTR("serial"), 0) == kCFCompareEqualTo
) {
1365 format
= kSecValidInfoFormatSerial
;
1366 } else if (CFStringCompare((CFStringRef
)value
, CFSTR("sha256"), 0) == kCFCompareEqualTo
) {
1367 format
= kSecValidInfoFormatSHA256
;
1368 } else if (CFStringCompare((CFStringRef
)value
, CFSTR("nto1"), 0) == kCFCompareEqualTo
) {
1369 format
= kSecValidInfoFormatNto1
;
1372 ok
= SecDbBindInt(insertGroup
, 3, (int)format
, &localError
);
1375 CFDataRef xmlData
= NULL
;
1376 if (ok
&& format
== kSecValidInfoFormatNto1
) {
1377 CFMutableDictionaryRef nto1
= CFDictionaryCreateMutable(kCFAllocatorDefault
, 0,
1378 &kCFTypeDictionaryKeyCallBacks
,
1379 &kCFTypeDictionaryValueCallBacks
);
1380 value
= (CFDataRef
)CFDictionaryGetValue(dict
, CFSTR("xor"));
1381 if (isData(value
)) {
1382 CFDictionaryAddValue(nto1
, CFSTR("xor"), value
);
1384 value
= (CFArrayRef
)CFDictionaryGetValue(dict
, CFSTR("params"));
1385 if (isArray(value
)) {
1386 CFDictionaryAddValue(nto1
, CFSTR("params"), value
);
1388 xmlData
= CFPropertyListCreateData(kCFAllocatorDefault
, nto1
,
1389 kCFPropertyListXMLFormat_v1_0
, 0, &localError
);
1390 CFReleaseSafe(nto1
);
1393 // compress the xmlData blob, if possible
1394 CFDataRef deflatedData
= copyDeflatedData(xmlData
);
1396 if (CFDataGetLength(deflatedData
) < CFDataGetLength(xmlData
)) {
1398 xmlData
= deflatedData
;
1401 CFRelease(deflatedData
);
1404 ok
= SecDbBindBlob(insertGroup
, 4,
1405 CFDataGetBytePtr(xmlData
),
1406 CFDataGetLength(xmlData
),
1407 SQLITE_TRANSIENT
, &localError
);
1411 /* Execute the insert statement for the group record. */
1413 ok
= SecDbStep(dbconn
, insertGroup
, &localError
, NULL
);
1414 result
= (int64_t)sqlite3_last_insert_rowid(SecDbHandle(dbconn
));
1417 secdebug("validupdate", "Failed to insert group %ld", (long)result
);
1419 /* Clean up temporary allocation made in this block. */
1420 CFReleaseSafe(xmlData
);
1426 (void) CFErrorPropagate(localError
, error
);
1430 static int64_t _SecRevocationDbGroupIdForIssuerHash(SecRevocationDbRef
this, CFDataRef hash
, CFErrorRef
*error
) {
1431 /* look up issuer hash in issuers table to get groupid, if it exists */
1432 __block
int64_t groupId
= -1;
1433 __block
bool ok
= true;
1434 __block CFErrorRef localError
= NULL
;
1436 require(hash
, errOut
);
1438 /* This is the starting point for any lookup; find a group id for the given issuer hash.
1439 Before we do that, need to verify the current db_version. We cannot use results from a
1440 database created with schema version 1. At the next database update interval,
1441 we'll be removing and recreating the database contents with the current schema version.
1443 int64_t db_version
= _SecRevocationDbGetSchemaVersion(this, NULL
);
1444 require(db_version
> 1, errOut
);
1446 /* Look up provided issuer_hash in the issuers table.
1448 ok
&= SecDbPerformRead(this->db
, &localError
, ^(SecDbConnectionRef dbconn
) {
1449 ok
&= SecDbWithSQL(dbconn
, selectGroupIdSQL
, &localError
, ^bool(sqlite3_stmt
*selectGroupId
) {
1450 ok
= SecDbBindBlob(selectGroupId
, 1, CFDataGetBytePtr(hash
), CFDataGetLength(hash
), SQLITE_TRANSIENT
, &localError
);
1451 ok
&= SecDbStep(dbconn
, selectGroupId
, &localError
, ^(bool *stopGroupId
) {
1452 groupId
= sqlite3_column_int64(selectGroupId
, 0);
1459 (void) CFErrorPropagate(localError
, error
);
1463 static bool _SecRevocationDbApplyGroupDelete(SecRevocationDbRef
this, CFDataRef issuerHash
, CFErrorRef
*error
) {
1464 /* delete group associated with the given issuer;
1465 schema trigger will delete associated issuers, serials, and hashes. */
1466 __block
int64_t groupId
= -1;
1467 __block
bool ok
= true;
1468 __block CFErrorRef localError
= NULL
;
1470 groupId
= _SecRevocationDbGroupIdForIssuerHash(this, issuerHash
, &localError
);
1471 require(!(groupId
< 0), errOut
);
1473 ok
&= SecDbPerformWrite(this->db
, &localError
, ^(SecDbConnectionRef dbconn
) {
1474 ok
&= SecDbTransaction(dbconn
, kSecDbExclusiveTransactionType
, &localError
, ^(bool *commit
) {
1475 ok
= SecDbWithSQL(dbconn
, deleteGroupRecordSQL
, &localError
, ^bool(sqlite3_stmt
*deleteResponse
) {
1476 ok
= SecDbBindInt64(deleteResponse
, 1, groupId
, &localError
);
1477 /* Execute the delete statement. */
1479 ok
= SecDbStep(dbconn
, deleteResponse
, &localError
, NULL
);
1487 (void) CFErrorPropagate(localError
, error
);
1488 return (groupId
< 0) ? false : true;
1491 static bool _SecRevocationDbApplyGroupUpdate(SecRevocationDbRef
this, CFDictionaryRef dict
, CFErrorRef
*error
) {
1492 /* process one issuer group's update dictionary */
1493 int64_t groupId
= -1;
1494 CFErrorRef localError
= NULL
;
1496 CFArrayRef issuers
= (dict
) ? (CFArrayRef
)CFDictionaryGetValue(dict
, CFSTR("issuer-hash")) : NULL
;
1497 if (isArray(issuers
)) {
1498 CFIndex issuerIX
, issuerCount
= CFArrayGetCount(issuers
);
1499 /* while we have issuers and haven't found a matching group id */
1500 for (issuerIX
=0; issuerIX
<issuerCount
&& groupId
< 0; issuerIX
++) {
1501 CFDataRef hash
= (CFDataRef
)CFArrayGetValueAtIndex(issuers
, issuerIX
);
1502 if (!hash
) { continue; }
1503 groupId
= _SecRevocationDbGroupIdForIssuerHash(this, hash
, &localError
);
1506 /* create or update the group entry */
1507 groupId
= _SecRevocationDbUpdateGroup(this, groupId
, dict
, &localError
);
1509 secdebug("validupdate", "failed to get groupId");
1511 //secdebug("validupdate", "got groupId %ld", (long)groupId);
1512 /* create or update issuer entries, now that we know the group id */
1513 _SecRevocationDbUpdateIssuers(this, groupId
, issuers
, &localError
);
1514 /* create or update entries in serials or hashes tables */
1515 _SecRevocationDbUpdatePerIssuerData(this, groupId
, dict
, &localError
);
1518 (void) CFErrorPropagate(localError
, error
);
1519 return (groupId
< 0) ? false : true;
1522 static void _SecRevocationDbApplyUpdate(SecRevocationDbRef
this, CFDictionaryRef update
, CFIndex version
) {
1523 /* process entire update dictionary */
1524 if (!this || !update
) {
1525 secerror("_SecRevocationDbApplyUpdate failed: invalid args");
1530 __block CFDictionaryRef localUpdate
= update
;
1531 __block CFErrorRef localError
= NULL
;
1533 // This may take a while; do the work on our update queue with background priority.
1535 dispatch_async(this->update_queue
, ^{
1538 CFIndex deleteCount
= 0;
1539 CFIndex updateCount
= 0;
1541 /* check whether this is a full update */
1542 this->fullUpdateInProgress
= false;
1543 value
= (CFBooleanRef
)CFDictionaryGetValue(update
, CFSTR("full"));
1544 if (isBoolean(value
)) {
1545 this->fullUpdateInProgress
= CFBooleanGetValue((CFBooleanRef
)value
);
1548 /* process 'delete' list */
1549 value
= (CFArrayRef
)CFDictionaryGetValue(localUpdate
, CFSTR("delete"));
1550 if (isArray(value
)) {
1551 deleteCount
= CFArrayGetCount((CFArrayRef
)value
);
1552 secdebug("validupdate", "processing %ld deletes", (long)deleteCount
);
1553 for (CFIndex deleteIX
=0; deleteIX
<deleteCount
; deleteIX
++) {
1554 CFDataRef issuerHash
= (CFDataRef
)CFArrayGetValueAtIndex((CFArrayRef
)value
, deleteIX
);
1555 if (isData(issuerHash
)) {
1556 (void)_SecRevocationDbApplyGroupDelete(this, issuerHash
, &localError
);
1557 CFReleaseNull(localError
);
1562 /* process 'update' list */
1563 value
= (CFArrayRef
)CFDictionaryGetValue(localUpdate
, CFSTR("update"));
1564 if (isArray(value
)) {
1565 updateCount
= CFArrayGetCount((CFArrayRef
)value
);
1566 secdebug("validupdate", "processing %ld updates", (long)updateCount
);
1567 for (CFIndex updateIX
=0; updateIX
<updateCount
; updateIX
++) {
1568 CFDictionaryRef dict
= (CFDictionaryRef
)CFArrayGetValueAtIndex((CFArrayRef
)value
, updateIX
);
1569 if (isDictionary(dict
)) {
1570 (void)_SecRevocationDbApplyGroupUpdate(this, dict
, &localError
);
1571 CFReleaseNull(localError
);
1575 CFRelease(localUpdate
);
1578 _SecRevocationDbSetVersion(this, version
);
1580 /* set db_version if not already set */
1581 int64_t db_version
= _SecRevocationDbGetSchemaVersion(this, NULL
);
1582 if (db_version
< 0) {
1583 _SecRevocationDbSetSchemaVersion(this, kSecRevocationDbSchemaVersion
);
1586 /* compact the db */
1587 (void)SecDbPerformWrite(this->db
, &localError
, ^(SecDbConnectionRef dbconn
) {
1588 SecDbTransaction(dbconn
, kSecDbExclusiveTransactionType
, &localError
, ^(bool *commit
) {
1589 SecDbExec(dbconn
, CFSTR("VACUUM;"), &localError
);
1590 CFReleaseNull(localError
);
1593 this->fullUpdateInProgress
= false;
1598 static bool _SecRevocationDbSerialInGroup(SecRevocationDbRef
this,
1601 CFErrorRef
*error
) {
1602 __block
bool result
= false;
1603 __block
bool ok
= true;
1604 __block CFErrorRef localError
= NULL
;
1605 require(this && serial
, errOut
);
1606 ok
&= SecDbPerformRead(this->db
, &localError
, ^(SecDbConnectionRef dbconn
) {
1607 ok
&= SecDbWithSQL(dbconn
, selectSerialRecordSQL
, &localError
, ^bool(sqlite3_stmt
*selectSerial
) {
1608 ok
&= SecDbBindBlob(selectSerial
, 1, CFDataGetBytePtr(serial
),
1609 CFDataGetLength(serial
), SQLITE_TRANSIENT
, &localError
);
1610 ok
&= SecDbBindInt64(selectSerial
, 2, groupId
, &localError
);
1611 ok
&= SecDbStep(dbconn
, selectSerial
, &localError
, ^(bool *stop
) {
1612 int64_t foundRowId
= (int64_t)sqlite3_column_int64(selectSerial
, 0);
1613 result
= (foundRowId
> 0);
1620 (void) CFErrorPropagate(localError
, error
);
1624 static bool _SecRevocationDbCertHashInGroup(SecRevocationDbRef
this,
1627 CFErrorRef
*error
) {
1628 __block
bool result
= false;
1629 __block
bool ok
= true;
1630 __block CFErrorRef localError
= NULL
;
1631 require(this && certHash
, errOut
);
1632 ok
&= SecDbPerformRead(this->db
, &localError
, ^(SecDbConnectionRef dbconn
) {
1633 ok
&= SecDbWithSQL(dbconn
, selectHashRecordSQL
, &localError
, ^bool(sqlite3_stmt
*selectHash
) {
1634 ok
= SecDbBindBlob(selectHash
, 1, CFDataGetBytePtr(certHash
),
1635 CFDataGetLength(certHash
), SQLITE_TRANSIENT
, &localError
);
1636 ok
&= SecDbBindInt64(selectHash
, 2, groupId
, &localError
);
1637 ok
&= SecDbStep(dbconn
, selectHash
, &localError
, ^(bool *stop
) {
1638 int64_t foundRowId
= (int64_t)sqlite3_column_int64(selectHash
, 0);
1639 result
= (foundRowId
> 0);
1646 (void) CFErrorPropagate(localError
, error
);
1650 static bool _SecRevocationDbSerialInFilter(SecRevocationDbRef
this,
1651 CFDataRef serialData
,
1652 CFDataRef xmlData
) {
1653 /* N-To-1 filter implementation.
1654 The 'xmlData' parameter is a flattened XML dictionary,
1655 containing 'xor' and 'params' keys. First order of
1656 business is to reconstitute the blob into components.
1658 bool result
= false;
1659 CFRetainSafe(xmlData
);
1660 CFDataRef propListData
= xmlData
;
1661 /* Expand data blob if needed */
1662 CFDataRef inflatedData
= copyInflatedData(propListData
);
1664 CFReleaseSafe(propListData
);
1665 propListData
= inflatedData
;
1667 CFDataRef
xor = NULL
;
1668 CFArrayRef params
= NULL
;
1669 CFPropertyListRef nto1
= CFPropertyListCreateWithData(kCFAllocatorDefault
, propListData
, 0, NULL
, NULL
);
1671 xor = (CFDataRef
)CFDictionaryGetValue((CFDictionaryRef
)nto1
, CFSTR("xor"));
1672 params
= (CFArrayRef
)CFDictionaryGetValue((CFDictionaryRef
)nto1
, CFSTR("params"));
1674 uint8_t *hash
= (xor) ? (uint8_t*)CFDataGetBytePtr(xor) : NULL
;
1675 CFIndex hashLen
= (hash
) ? CFDataGetLength(xor) : 0;
1676 uint8_t *serial
= (serialData
) ? (uint8_t*)CFDataGetBytePtr(serialData
) : NULL
;
1677 CFIndex serialLen
= (serial
) ? CFDataGetLength(serialData
) : 0;
1679 require(hash
&& serial
&& params
, errOut
);
1681 const uint32_t FNV_OFFSET_BASIS
= 2166136261;
1682 const uint32_t FNV_PRIME
= 16777619;
1683 bool notInHash
= false;
1684 CFIndex ix
, count
= CFArrayGetCount(params
);
1685 for (ix
= 0; ix
< count
; ix
++) {
1687 CFNumberRef cfnum
= (CFNumberRef
)CFArrayGetValueAtIndex(params
, ix
);
1688 if (!isNumber(cfnum
) ||
1689 !CFNumberGetValue(cfnum
, kCFNumberSInt32Type
, ¶m
)) {
1690 secinfo("validupdate", "error processing filter params at index %ld", (long)ix
);
1693 /* process one param */
1694 uint32_t hval
= FNV_OFFSET_BASIS
^ param
;
1695 CFIndex i
= serialLen
;
1697 hval
= ((hval
^ (serial
[--i
])) * FNV_PRIME
) & 0xFFFFFFFF;
1699 hval
= hval
% (hashLen
* 8);
1700 if ((hash
[hval
/8] & (1 << (hval
% 8))) == 0) {
1701 notInHash
= true; /* definitely not in hash */
1706 /* probabilistically might be in hash if we get here. */
1711 CFReleaseSafe(nto1
);
1712 CFReleaseSafe(propListData
);
1716 static SecValidInfoRef
_SecRevocationDbValidInfoForCertificate(SecRevocationDbRef
this,
1717 SecCertificateRef certificate
,
1718 CFDataRef issuerHash
,
1719 CFErrorRef
*error
) {
1720 __block CFErrorRef localError
= NULL
;
1721 __block
bool ok
= true;
1722 __block
int flags
= 0;
1723 __block SecValidInfoFormat format
= kSecValidInfoFormatUnknown
;
1724 __block CFDataRef data
= NULL
;
1726 bool matched
= false;
1727 int64_t groupId
= 0;
1728 CFDataRef serial
= NULL
;
1729 CFDataRef certHash
= NULL
;
1730 SecValidInfoRef result
= NULL
;
1733 require(serial
= SecCertificateCopySerialNumber(certificate
, NULL
), errOut
);
1735 require(serial
= SecCertificateCopySerialNumber(certificate
), errOut
);
1737 require(certHash
= SecCertificateCopySHA256Digest(certificate
), errOut
);
1739 require(groupId
= _SecRevocationDbGroupIdForIssuerHash(this, issuerHash
, &localError
), errOut
);
1741 /* Select the group record to determine flags and format. */
1742 ok
&= SecDbPerformRead(this->db
, &localError
, ^(SecDbConnectionRef dbconn
) {
1743 ok
&= SecDbWithSQL(dbconn
, selectGroupRecordSQL
, &localError
, ^bool(sqlite3_stmt
*selectGroup
) {
1744 ok
= SecDbBindInt64(selectGroup
, 1, groupId
, &localError
);
1745 ok
&= SecDbStep(dbconn
, selectGroup
, &localError
, ^(bool *stop
) {
1746 flags
= (int)sqlite3_column_int(selectGroup
, 0);
1747 format
= (SecValidInfoFormat
)sqlite3_column_int(selectGroup
, 1);
1748 uint8_t *p
= (uint8_t *)sqlite3_column_blob(selectGroup
, 2);
1749 if (p
!= NULL
&& format
== kSecValidInfoFormatNto1
) {
1750 CFIndex length
= (CFIndex
)sqlite3_column_bytes(selectGroup
, 2);
1751 data
= CFDataCreate(kCFAllocatorDefault
, p
, length
);
1758 if (format
== kSecValidInfoFormatUnknown
) {
1759 /* No group record found for this issuer. */
1761 else if (format
== kSecValidInfoFormatSerial
) {
1762 /* Look up certificate's serial number in the serials table. */
1763 matched
= _SecRevocationDbSerialInGroup(this, serial
, groupId
, &localError
);
1765 else if (format
== kSecValidInfoFormatSHA256
) {
1766 /* Look up certificate's SHA-256 hash in the hashes table. */
1767 matched
= _SecRevocationDbCertHashInGroup(this, certHash
, groupId
, &localError
);
1769 else if (format
== kSecValidInfoFormatNto1
) {
1770 /* Perform a Bloom filter match against the serial. If matched is false,
1771 then the cert is definitely not in the list. But if matched is true,
1772 we don't know for certain, so we would need to check OCSP. */
1773 matched
= _SecRevocationDbSerialInFilter(this, serial
, data
);
1777 /* Always return SecValidInfo for a matched certificate. */
1778 secdebug("validupdate", "Valid db matched cert: %@, format=%d, flags=%d",
1779 certHash
, format
, flags
);
1780 result
= SecValidInfoCreate(format
, flags
, certHash
, issuerHash
);
1782 else if ((flags
& kSecValidInfoComplete
) && (flags
& kSecValidInfoAllowlist
)) {
1783 /* Not matching against a complete whitelist is equivalent to revocation. */
1784 secdebug("validupdate", "Valid db did NOT match cert on allowlist: %@, format=%d, flags=%d",
1785 certHash
, format
, flags
);
1786 result
= SecValidInfoCreate(format
, flags
, certHash
, issuerHash
);
1789 if (result
&& SecIsAppleTrustAnchor(certificate
, 0)) {
1790 /* Prevent a catch-22. */
1791 secdebug("validupdate", "Valid db match for Apple trust anchor: %@, format=%d, flags=%d",
1792 certHash
, format
, flags
);
1793 SecValidInfoRelease(result
);
1798 (void) CFErrorPropagate(localError
, error
);
1799 CFReleaseSafe(data
);
1800 CFReleaseSafe(certHash
);
1801 CFReleaseSafe(serial
);
1805 static SecValidInfoRef
_SecRevocationDbCopyMatching(SecRevocationDbRef db
,
1806 SecCertificateRef certificate
,
1807 SecCertificateRef issuer
) {
1808 SecValidInfoRef result
= NULL
;
1809 CFErrorRef error
= NULL
;
1810 CFDataRef issuerHash
= NULL
;
1812 require(certificate
&& issuer
, errOut
);
1813 require(issuerHash
= SecCertificateCopySHA256Digest(issuer
), errOut
);
1815 result
= _SecRevocationDbValidInfoForCertificate(db
, certificate
, issuerHash
, &error
);
1818 CFReleaseSafe(issuerHash
);
1819 CFReleaseSafe(error
);
1823 static dispatch_queue_t
_SecRevocationDbGetUpdateQueue(SecRevocationDbRef
this) {
1824 return (this) ? this->update_queue
: NULL
;
1828 /* Given a valid update dictionary, insert/replace or delete records
1829 in the revocation database. (This function is expected to be called only
1830 by the database maintainer, normally the system instance of trustd.)
1832 void SecRevocationDbApplyUpdate(CFDictionaryRef update
, CFIndex version
) {
1833 SecRevocationDbWith(^(SecRevocationDbRef db
) {
1834 _SecRevocationDbApplyUpdate(db
, update
, version
);
1838 /* Set the schema version for the revocation database.
1839 (This function is expected to be called only by the database maintainer,
1840 normally the system instance of trustd.)
1842 void SecRevocationDbSetSchemaVersion(CFIndex db_version
) {
1843 SecRevocationDbWith(^(SecRevocationDbRef db
) {
1844 _SecRevocationDbSetSchemaVersion(db
, db_version
);
1848 /* Set the next update value for the revocation database.
1849 (This function is expected to be called only by the database
1850 maintainer, normally the system instance of trustd. If the
1851 caller does not have write access, this is a no-op.)
1853 void SecRevocationDbSetNextUpdateTime(CFAbsoluteTime nextUpdate
) {
1854 SecRevocationDbWith(^(SecRevocationDbRef db
) {
1855 _SecRevocationDbSetNextUpdateTime(db
, nextUpdate
);
1859 /* Return the next update value as a CFAbsoluteTime.
1860 If the value cannot be obtained, -1 is returned.
1862 CFAbsoluteTime
SecRevocationDbGetNextUpdateTime(void) {
1863 __block CFAbsoluteTime result
= -1;
1864 SecRevocationDbWith(^(SecRevocationDbRef db
) {
1865 result
= _SecRevocationDbGetNextUpdateTime(db
, NULL
);
1870 /* Return the serial background queue for database updates.
1871 If the queue cannot be obtained, NULL is returned.
1873 dispatch_queue_t
SecRevocationDbGetUpdateQueue(void) {
1874 __block dispatch_queue_t result
= NULL
;
1875 SecRevocationDbWith(^(SecRevocationDbRef db
) {
1876 result
= _SecRevocationDbGetUpdateQueue(db
);
1881 /* Remove all entries in the revocation database and reset its version to 0.
1882 (This function is expected to be called only by the database maintainer,
1883 normally the system instance of trustd.)
1885 void SecRevocationDbRemoveAllEntries(void) {
1886 SecRevocationDbWith(^(SecRevocationDbRef db
) {
1887 _SecRevocationDbRemoveAllEntries(db
);
1891 /* === Public API === */
1893 /* Given a certificate and its issuer, returns a SecValidInfoRef if the
1894 valid database contains matching info; otherwise returns NULL.
1895 Caller must release the returned SecValidInfoRef by calling
1896 SecValidInfoRelease when finished.
1898 SecValidInfoRef
SecRevocationDbCopyMatching(SecCertificateRef certificate
,
1899 SecCertificateRef issuer
) {
1900 __block SecValidInfoRef result
= NULL
;
1901 SecRevocationDbWith(^(SecRevocationDbRef db
) {
1902 result
= _SecRevocationDbCopyMatching(db
, certificate
, issuer
);
1907 /* Return the current version of the revocation database.
1908 A version of 0 indicates an empty database which must be populated.
1909 If the version cannot be obtained, -1 is returned.
1911 CFIndex
SecRevocationDbGetVersion(void) {
1912 __block CFIndex result
= -1;
1913 SecRevocationDbWith(^(SecRevocationDbRef db
) {
1914 result
= (CFIndex
)_SecRevocationDbGetVersion(db
, NULL
);
1919 /* Return the current schema version of the revocation database.
1920 A version of 0 indicates an empty database which must be populated.
1921 If the schema version cannot be obtained, -1 is returned.
1923 CFIndex
SecRevocationDbGetSchemaVersion(void) {
1924 __block CFIndex result
= -1;
1925 SecRevocationDbWith(^(SecRevocationDbRef db
) {
1926 result
= (CFIndex
)_SecRevocationDbGetSchemaVersion(db
, NULL
);