2 * Copyright (c) 2016-2019 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 "trust/trustd/SecRevocationDb.h"
30 #include "trust/trustd/OTATrustUtilities.h"
31 #include "trust/trustd/SecRevocationNetworking.h"
32 #include "trust/trustd/SecTrustLoggingServer.h"
33 #include <Security/SecCertificateInternal.h>
34 #include <Security/SecCMS.h>
35 #include <Security/CMSDecoder.h>
36 #include <Security/SecFramework.h>
37 #include <Security/SecInternal.h>
38 #include <Security/SecPolicyPriv.h>
39 #include <AssertMacros.h>
41 #include <stdatomic.h>
47 #include <dispatch/dispatch.h>
51 #include "utilities/debugging.h"
52 #include "utilities/sec_action.h"
53 #include "utilities/sqlutils.h"
54 #include "utilities/SecAppleAnchorPriv.h"
55 #include "utilities/iOSforOSX.h"
56 #include <utilities/SecCFError.h>
57 #include <utilities/SecCFRelease.h>
58 #include <utilities/SecCFWrappers.h>
59 #include <utilities/SecDb.h>
60 #include <utilities/SecFileLocations.h>
63 #include <malloc/malloc.h>
64 #include <xpc/activity.h>
65 #include <xpc/private.h>
66 #include <os/transaction_private.h>
67 #include <os/variant_private.h>
70 #include <CommonCrypto/CommonDigest.h>
71 #include <CFNetwork/CFHTTPMessage.h>
72 #include <CoreFoundation/CFURL.h>
73 #include <CoreFoundation/CFUtilities.h>
76 ==============================================================================
77 CoreFoundation utilities
78 ==============================================================================
81 static bool hashCFThing(CFTypeRef thing
, CC_SHA256_CTX
* hash_ctx
);
83 /* comparison function for sorting dictionary keys alphabetically */
84 static int compareCFStrings(const void *p
, const void *q
) {
85 CFStringRef str1
= *(CFStringRef
*)p
;
86 CFStringRef str2
= *(CFStringRef
*)q
;
87 if (!(isString(str1
) && isString(str2
))) {
88 return -1; /* can't compare non-string types */
90 CFComparisonResult result
= CFStringCompare(str1
, str2
, 0);
91 if (result
== kCFCompareLessThan
) {
93 } else if (result
== kCFCompareGreaterThan
) {
96 return 0; /* (result == kCFCompareEqualTo) */
99 static bool hashData(CFDataRef data
, CC_SHA256_CTX
* hash_ctx
) {
100 if (!isData(data
)) { return false; }
101 uint32_t length
= (uint32_t)(CFDataGetLength(data
) & 0xFFFFFFFF);
102 uint32_t n
= OSSwapInt32(length
);
103 CC_SHA256_Update(hash_ctx
, &n
, sizeof(uint32_t));
104 const uint8_t *p
= (uint8_t*)CFDataGetBytePtr(data
);
105 if (p
) { CC_SHA256_Update(hash_ctx
, p
, length
); }
109 static bool hashString(CFStringRef str
, CC_SHA256_CTX
* hash_ctx
) {
110 if (!isString(str
)) { return false; }
111 __block
bool ok
= false;
112 CFStringPerformWithCString(str
, ^(const char *strbuf
) {
113 // hash string length (in bytes, not counting null terminator)
114 uint32_t c
= (uint32_t)(strlen(strbuf
) & 0xFFFFFFFF);
115 uint32_t n
= OSSwapInt32(c
);
116 CC_SHA256_Update(hash_ctx
, &n
, sizeof(uint32_t));
118 CC_SHA256_Update(hash_ctx
, strbuf
, c
);
124 static bool hashNumber(CFNumberRef num
, CC_SHA256_CTX
* hash_ctx
) {
126 if (!isNumber(num
) || !CFNumberGetValue(num
, kCFNumberSInt32Type
, &n
)) {
130 CC_SHA256_Update(hash_ctx
, &n
, sizeof(uint32_t));
134 static bool hashBoolean(CFBooleanRef value
, CC_SHA256_CTX
* hash_ctx
) {
135 if (!isBoolean(value
)) { return false; }
136 uint8_t c
= CFBooleanGetValue(value
) ? 1 : 0;
137 CC_SHA256_Update(hash_ctx
, &c
, sizeof(uint8_t));
141 static bool hashArray(CFArrayRef array
, CC_SHA256_CTX
* hash_ctx
) {
142 if (!isArray(array
)) { return false; }
143 CFIndex count
= CFArrayGetCount(array
);
144 uint32_t n
= OSSwapInt32(count
& 0xFFFFFFFF);
145 CC_SHA256_Update(hash_ctx
, &n
, sizeof(uint32_t));
146 __block
bool ok
= true;
147 CFArrayForEach(array
, ^(const void *thing
) {
148 ok
&= hashCFThing(thing
, hash_ctx
);
153 static bool hashDictionary(CFDictionaryRef dictionary
, CC_SHA256_CTX
* hash_ctx
) {
154 if (!isDictionary(dictionary
)) { return false; }
155 CFIndex count
= CFDictionaryGetCount(dictionary
);
156 const void **keys
= (const void **)malloc(sizeof(void*) * count
);
157 const void **vals
= (const void **)malloc(sizeof(void*) * count
);
158 bool ok
= (keys
&& vals
);
160 CFDictionaryGetKeysAndValues(dictionary
, keys
, vals
);
161 qsort(keys
, count
, sizeof(CFStringRef
), compareCFStrings
);
162 uint32_t n
= OSSwapInt32(count
& 0xFFFFFFFF);
163 CC_SHA256_Update(hash_ctx
, &n
, sizeof(uint32_t));
165 for (CFIndex idx
= 0; ok
&& idx
< count
; idx
++) {
166 CFStringRef key
= (CFStringRef
)keys
[idx
];
167 CFTypeRef value
= (CFTypeRef
)CFDictionaryGetValue(dictionary
, key
);
168 ok
&= hashString(key
, hash_ctx
);
169 ok
&= hashCFThing(value
, hash_ctx
);
176 static bool hashCFThing(CFTypeRef thing
, CC_SHA256_CTX
* hash_ctx
) {
177 if (isArray(thing
)) {
178 return hashArray(thing
, hash_ctx
);
179 } else if (isDictionary(thing
)) {
180 return hashDictionary(thing
, hash_ctx
);
181 } else if (isData(thing
)) {
182 return hashData(thing
, hash_ctx
);
183 } else if (isString(thing
)) {
184 return hashString(thing
, hash_ctx
);
185 } else if (isNumber(thing
)) {
186 return hashNumber(thing
, hash_ctx
);
187 } else if (isBoolean(thing
)) {
188 return hashBoolean(thing
, hash_ctx
);
193 static double htond(double h
) {
194 /* no-op if big endian */
195 if (OSHostByteOrder() == OSBigEndian
) {
200 char *hp
= (char*)&h
;
201 char *np
= (char*)&n
;
202 while (i
< sizeof(h
)) { np
[i
] = hp
[(sizeof(h
)-1)-i
]; ++i
; }
208 // MARK: Valid definitions
210 ==============================================================================
212 ==============================================================================
215 const CFStringRef kValidUpdateProdServer
= CFSTR("valid.apple.com");
216 const CFStringRef kValidUpdateSeedServer
= CFSTR("valid.apple.com/seed");
217 const CFStringRef kValidUpdateCarryServer
= CFSTR("valid.apple.com/carry");
219 static CFStringRef kSecPrefsDomain
= CFSTR("com.apple.security");
220 static CFStringRef kUpdateServerKey
= CFSTR("ValidUpdateServer");
221 static CFStringRef kUpdateEnabledKey
= CFSTR("ValidUpdateEnabled");
222 static CFStringRef kVerifyEnabledKey
= CFSTR("ValidVerifyEnabled");
223 static CFStringRef kUpdateIntervalKey
= CFSTR("ValidUpdateInterval");
224 static CFStringRef kBoolTrueKey
= CFSTR("1");
225 static CFStringRef kBoolFalseKey
= CFSTR("0");
227 /* constant length of boolean string keys */
228 #define BOOL_STRING_KEY_LENGTH 1
230 typedef CF_OPTIONS(CFOptionFlags
, SecValidInfoFlags
) {
231 kSecValidInfoComplete
= 1u << 0,
232 kSecValidInfoCheckOCSP
= 1u << 1,
233 kSecValidInfoKnownOnly
= 1u << 2,
234 kSecValidInfoRequireCT
= 1u << 3,
235 kSecValidInfoAllowlist
= 1u << 4,
236 kSecValidInfoNoCACheck
= 1u << 5,
237 kSecValidInfoOverridable
= 1u << 6,
238 kSecValidInfoDateConstraints
= 1u << 7,
239 kSecValidInfoNameConstraints
= 1u << 8,
240 kSecValidInfoPolicyConstraints
= 1u << 9,
241 kSecValidInfoNoCAv2Check
= 1u << 10,
244 /* minimum update interval */
245 #define kSecMinUpdateInterval (60.0 * 5)
247 /* standard update interval */
248 #define kSecStdUpdateInterval (60.0 * 60 * 3)
250 /* maximum allowed interval */
251 #define kSecMaxUpdateInterval (60.0 * 60 * 24 * 7)
253 #define kSecRevocationBasePath "/Library/Keychains/crls"
254 #define kSecRevocationCurUpdateFile "update-current"
255 #define kSecRevocationDbFileName "valid.sqlite3"
256 #define kSecRevocationDbReplaceFile ".valid_replace"
258 #define isDbOwner SecOTAPKIIsSystemTrustd
260 #define kSecRevocationDbChanged "com.apple.trustd.valid.db-changed"
262 /* database schema version
264 v2 = fix for group entry transitions
265 v3 = handle optional entries in update dictionaries
266 v4 = add db_format and db_source entries
267 v5 = add date constraints table, with updated group flags
268 v6 = explicitly set autovacuum and journal modes at db creation
269 v7 = add policies column to groups table (policy constraints)
271 Note: kSecRevocationDbMinSchemaVersion is the lowest version whose
272 results can be used. This allows revocation results to be obtained
273 from an existing db before the next update interval occurs, at which
274 time we'll update to the current version (kSecRevocationDbSchemaVersion).
276 #define kSecRevocationDbSchemaVersion 7 /* current version we support */
277 #define kSecRevocationDbMinSchemaVersion 7 /* minimum version we can use */
279 /* update file format
282 kSecValidUpdateFormatG1
= 1, /* initial version */
283 kSecValidUpdateFormatG2
= 2, /* signed content, single plist */
284 kSecValidUpdateFormatG3
= 3 /* signed content, multiple plists */
287 #define kSecRevocationDbUpdateFormat 3 /* current version we support */
288 #define kSecRevocationDbMinUpdateFormat 2 /* minimum version we can use */
290 #define kSecRevocationDbCacheSize 100
292 typedef struct __SecRevocationDb
*SecRevocationDbRef
;
293 struct __SecRevocationDb
{
295 dispatch_queue_t update_queue
;
296 bool updateInProgress
;
297 bool unsupportedVersion
;
299 CFMutableArrayRef info_cache_list
;
300 CFMutableDictionaryRef info_cache
;
301 os_unfair_lock info_cache_lock
;
304 typedef struct __SecRevocationDbConnection
*SecRevocationDbConnectionRef
;
305 struct __SecRevocationDbConnection
{
306 SecRevocationDbRef db
;
307 SecDbConnectionRef dbconn
;
308 CFIndex precommitVersion
;
309 CFIndex precommitDbVersion
;
310 CFIndex precommitInterval
;
314 bool SecRevocationDbVerifyUpdate(void *update
, CFIndex length
);
315 bool SecRevocationDbIngestUpdate(SecRevocationDbConnectionRef dbc
, CFDictionaryRef update
, CFIndex chunkVersion
, CFIndex
*outVersion
, CFErrorRef
*error
);
316 bool _SecRevocationDbApplyUpdate(SecRevocationDbConnectionRef dbc
, CFDictionaryRef update
, CFIndex version
, CFErrorRef
*error
);
317 CFAbsoluteTime
SecRevocationDbComputeNextUpdateTime(CFIndex updateInterval
);
318 bool SecRevocationDbUpdateSchema(SecRevocationDbRef rdb
);
319 CFIndex
SecRevocationDbGetUpdateFormat(void);
320 bool _SecRevocationDbSetUpdateSource(SecRevocationDbConnectionRef dbc
, CFStringRef source
, CFErrorRef
*error
);
321 bool SecRevocationDbSetUpdateSource(SecRevocationDbRef rdb
, CFStringRef source
);
322 CFStringRef
SecRevocationDbCopyUpdateSource(void);
323 bool SecRevocationDbSetNextUpdateTime(CFAbsoluteTime nextUpdate
, CFErrorRef
*error
);
324 CFAbsoluteTime
SecRevocationDbGetNextUpdateTime(void);
325 dispatch_queue_t
SecRevocationDbGetUpdateQueue(void);
326 bool _SecRevocationDbRemoveAllEntries(SecRevocationDbConnectionRef dbc
, CFErrorRef
*error
);
327 void SecRevocationDbReleaseAllConnections(void);
328 static bool SecValidUpdateForceReplaceDatabase(void);
329 static void SecRevocationDbWith(void(^dbJob
)(SecRevocationDbRef db
));
330 static bool SecRevocationDbPerformWrite(SecRevocationDbRef rdb
, CFErrorRef
*error
, bool(^writeJob
)(SecRevocationDbConnectionRef dbc
, CFErrorRef
*blockError
));
331 static bool SecRevocationDbPerformRead(SecRevocationDbRef rdb
, CFErrorRef
*error
, bool(^readJob
)(SecRevocationDbConnectionRef dbc
, CFErrorRef
*blockError
));
332 static SecValidInfoFormat
_SecRevocationDbGetGroupFormat(SecRevocationDbConnectionRef dbc
, int64_t groupId
, SecValidInfoFlags
*flags
, CFDataRef
*data
, CFDataRef
*policies
, CFErrorRef
*error
);
333 static bool _SecRevocationDbSetUpdateInterval(SecRevocationDbConnectionRef dbc
, int64_t interval
, CFErrorRef
*error
);
334 static int64_t _SecRevocationDbGetUpdateInterval(SecRevocationDbConnectionRef dbc
, CFErrorRef
*error
);
335 static bool _SecRevocationDbSetVersion(SecRevocationDbConnectionRef dbc
, CFIndex version
, CFErrorRef
*error
);
336 static int64_t _SecRevocationDbGetVersion(SecRevocationDbConnectionRef dbc
, CFErrorRef
*error
);
337 static int64_t _SecRevocationDbGetSchemaVersion(SecRevocationDbRef rdb
, SecRevocationDbConnectionRef dbc
, CFErrorRef
*error
);
338 static CFArrayRef
_SecRevocationDbCopyHashes(SecRevocationDbConnectionRef dbc
, CFErrorRef
*error
);
339 static bool _SecRevocationDbSetHashes(SecRevocationDbConnectionRef dbc
, CFArrayRef hashes
, CFErrorRef
*error
);
340 static void SecRevocationDbResetCaches(void);
341 static SecRevocationDbConnectionRef
SecRevocationDbConnectionInit(SecRevocationDbRef db
, SecDbConnectionRef dbconn
, CFErrorRef
*error
);
342 static bool SecRevocationDbComputeDigests(SecRevocationDbConnectionRef dbc
, CFErrorRef
*error
);
345 static CFDataRef
copyInflatedData(CFDataRef data
) {
350 memset(&zs
, 0, sizeof(zs
));
351 /* 32 is a magic value which enables automatic header detection
352 of gzip or zlib compressed data. */
353 if (inflateInit2(&zs
, 32+MAX_WBITS
) != Z_OK
) {
356 zs
.next_in
= (UInt8
*)(CFDataGetBytePtr(data
));
357 zs
.avail_in
= (uInt
)CFDataGetLength(data
);
359 CFMutableDataRef outData
= CFDataCreateMutable(NULL
, 0);
363 CFIndex buf_sz
= malloc_good_size(zs
.avail_in
? zs
.avail_in
: 1024 * 4);
364 unsigned char *buf
= malloc(buf_sz
);
367 zs
.next_out
= (Bytef
*)buf
;
368 zs
.avail_out
= (uInt
)buf_sz
;
369 rc
= inflate(&zs
, 0);
370 CFIndex outLen
= CFDataGetLength(outData
);
371 if (outLen
< (CFIndex
)zs
.total_out
) {
372 CFDataAppendBytes(outData
, (const UInt8
*)buf
, (CFIndex
)zs
.total_out
- outLen
);
374 } while (rc
== Z_OK
);
378 if (rc
!= Z_STREAM_END
) {
379 CFReleaseSafe(outData
);
382 return (CFDataRef
)outData
;
385 static CFDataRef
copyDeflatedData(CFDataRef data
) {
390 memset(&zs
, 0, sizeof(zs
));
391 if (deflateInit(&zs
, Z_BEST_COMPRESSION
) != Z_OK
) {
394 zs
.next_in
= (UInt8
*)(CFDataGetBytePtr(data
));
395 zs
.avail_in
= (uInt
)CFDataGetLength(data
);
397 CFMutableDataRef outData
= CFDataCreateMutable(NULL
, 0);
401 CFIndex buf_sz
= malloc_good_size(zs
.avail_in
? zs
.avail_in
: 1024 * 4);
402 unsigned char *buf
= malloc(buf_sz
);
403 int rc
= Z_BUF_ERROR
;
405 zs
.next_out
= (Bytef
*)buf
;
406 zs
.avail_out
= (uInt
)buf_sz
;
407 rc
= deflate(&zs
, Z_FINISH
);
409 if (rc
== Z_OK
|| rc
== Z_STREAM_END
) {
410 CFIndex buf_used
= buf_sz
- zs
.avail_out
;
411 CFDataAppendBytes(outData
, (const UInt8
*)buf
, buf_used
);
413 else if (rc
== Z_BUF_ERROR
) {
415 buf_sz
= malloc_good_size(buf_sz
* 2);
416 buf
= malloc(buf_sz
);
418 rc
= Z_OK
; /* try again with larger buffer */
421 } while (rc
== Z_OK
&& zs
.avail_in
);
425 if (rc
!= Z_STREAM_END
) {
426 CFReleaseSafe(outData
);
429 return (CFDataRef
)outData
;
432 /* Read file opens the file, mmaps it and then closes the file. */
433 int readValidFile(const char *fileName
,
434 CFDataRef
*bytes
) { // mmapped and returned -- must be munmapped!
436 const uint8_t *buf
= NULL
;
441 fd
= open(fileName
, O_RDONLY
);
442 if (fd
< 0) { return errno
; }
443 rtn
= fstat(fd
, &sb
);
444 if (rtn
) { goto errOut
; }
445 if (sb
.st_size
> (off_t
) ((UINT32_MAX
>> 1)-1)) {
449 size
= (size_t)sb
.st_size
;
451 buf
= mmap(NULL
, size
, PROT_READ
, MAP_PRIVATE
, fd
, 0);
452 if (!buf
|| buf
== MAP_FAILED
) {
454 secerror("unable to map %s (errno %d)", fileName
, rtn
);
458 *bytes
= CFDataCreateWithBytesNoCopy(NULL
, buf
, size
, kCFAllocatorNull
);
463 CFReleaseNull(*bytes
);
465 int unmap_err
= munmap((void *)buf
, size
);
466 if (unmap_err
!= 0) {
467 secerror("unable to unmap %ld bytes at %p (error %d)", (long)size
, buf
, rtn
);
474 static bool removeFileWithSuffix(const char *basepath
, const char *suffix
) {
477 asprintf(&path
, "%s%s", basepath
, suffix
);
479 if (remove(path
) == -1) {
481 if (error
== ENOENT
) {
482 result
= true; // not an error if the file did not exist
484 secnotice("validupdate", "remove (%s): %s", path
, strerror(error
));
494 static CFDataRef CF_RETURNS_RETAINED
createPoliciesData(CFArrayRef policies
) {
496 * Given an array of CFNumber values (in the range 0..127),
497 * allocate and return a CFDataRef representation. Per Valid specification,
498 * a zero-length array is allowed, meaning no policies are permitted.
500 CFIndex count
= (policies
) ? CFArrayGetCount(policies
) : -1;
501 if (count
< 0 || count
> 127) {
502 return NULL
; /* either no constraints, or far more than we expect. */
504 CFDataRef data
= NULL
;
505 CFIndex length
= 1 + (sizeof(int8_t) * count
);
506 int8_t *bytes
= malloc(length
);
509 *p
++ = (int8_t)(count
& 0xFF);
510 for (CFIndex idx
= 0; idx
< count
; idx
++) {
512 CFNumberRef value
= (CFNumberRef
)CFArrayGetValueAtIndex(policies
, idx
);
513 if (isNumber(value
)) {
514 (void)CFNumberGetValue(value
, kCFNumberSInt8Type
, &pval
);
518 data
= CFDataCreate(kCFAllocatorDefault
, (const UInt8
*)bytes
, length
);
524 static CFDataRef CF_RETURNS_RETAINED
cfToHexData(CFDataRef data
, bool prependWildcard
) {
525 if (!isData(data
)) { return NULL
; }
526 CFIndex len
= CFDataGetLength(data
) * 2;
527 CFMutableStringRef hex
= CFStringCreateMutable(NULL
, len
+1);
528 static const char* digits
[]={
529 "0","1","2","3","4","5","6","7","8","9","A","B","C","D","E","F"};
530 if (prependWildcard
) {
531 CFStringAppendCString(hex
, "%", 1);
533 const uint8_t* p
= CFDataGetBytePtr(data
);
534 for (CFIndex i
= 0; i
< CFDataGetLength(data
); i
++) {
535 CFStringAppendCString(hex
, digits
[p
[i
] >> 4], 1);
536 CFStringAppendCString(hex
, digits
[p
[i
] & 0xf], 1);
538 CFDataRef result
= CFStringCreateExternalRepresentation(NULL
, hex
, kCFStringEncodingUTF8
, 0);
543 static bool copyFilterComponents(CFDataRef xmlData
, CFDataRef
* CF_RETURNS_RETAINED
xor,
544 CFArrayRef
* CF_RETURNS_RETAINED params
) {
546 The 'xmlData' parameter is a flattened XML dictionary,
547 containing 'xor' and 'params' keys. First order of
548 business is to reconstitute the blob into components.
551 CFRetainSafe(xmlData
);
552 CFDataRef propListData
= xmlData
;
553 /* Expand data blob if needed */
554 CFDataRef inflatedData
= copyInflatedData(propListData
);
556 CFReleaseSafe(propListData
);
557 propListData
= inflatedData
;
559 CFDataRef xorData
= NULL
;
560 CFArrayRef paramsArray
= NULL
;
561 CFPropertyListRef nto1
= CFPropertyListCreateWithData(kCFAllocatorDefault
, propListData
, 0, NULL
, NULL
);
562 CFReleaseSafe(propListData
);
564 xorData
= (CFDataRef
)CFDictionaryGetValue((CFDictionaryRef
)nto1
, CFSTR("xor"));
565 CFRetainSafe(xorData
);
566 paramsArray
= (CFArrayRef
)CFDictionaryGetValue((CFDictionaryRef
)nto1
, CFSTR("params"));
567 CFRetainSafe(paramsArray
);
570 result
= (xorData
&& paramsArray
);
574 CFReleaseSafe(xorData
);
577 *params
= paramsArray
;
579 CFReleaseSafe(paramsArray
);
585 // MARK: SecValidUpdate
587 ==============================================================================
589 ==============================================================================
592 CFAbsoluteTime gUpdateStarted
= 0.0;
593 CFAbsoluteTime gNextUpdate
= 0.0;
594 static CFIndex gUpdateInterval
= 0;
595 static CFIndex gLastVersion
= 0;
598 1. The length of the signed data, as a 4-byte integer in network byte order.
599 2. The signed data, which consists of:
600 a. A 4-byte integer in network byte order, the count of plists to follow; and then for each plist:
601 i. A 4-byte integer, the length of each plist
602 ii. A plist, in binary form
603 b. There may be other data after the plists in the signed data, described by a future version of this specification.
604 3. The length of the following CMS blob, as a 4-byte integer in network byte order.
605 4. A detached CMS signature of the signed data described above.
606 5. There may be additional data after the CMS blob, described by a future version of this specification.
608 Note: the difference between g2 and g3 format is the addition of the 4-byte count in (2a).
610 static bool SecValidUpdateProcessData(SecRevocationDbConnectionRef dbc
, CFIndex format
, CFDataRef updateData
, CFErrorRef
*error
) {
612 if (!updateData
|| format
< 2) {
613 SecError(errSecParam
, error
, CFSTR("SecValidUpdateProcessData: invalid update format"));
617 CFIndex interval
= 0;
618 const UInt8
* p
= CFDataGetBytePtr(updateData
);
619 size_t bytesRemaining
= (p
) ? (size_t)CFDataGetLength(updateData
) : 0;
620 /* make sure there is enough data to contain length and count */
621 if (bytesRemaining
< ((CFIndex
)sizeof(uint32_t) * 2)) {
622 secinfo("validupdate", "Skipping property list creation (length %ld is too short)", (long)bytesRemaining
);
623 SecError(errSecParam
, error
, CFSTR("SecValidUpdateProcessData: data length is too short"));
626 /* get length of signed data */
627 uint32_t dataLength
= OSSwapInt32(*((uint32_t *)p
));
628 bytesRemaining
-= sizeof(uint32_t);
629 p
+= sizeof(uint32_t);
631 /* get plist count (G3 format and later) */
632 uint32_t plistCount
= 1;
633 uint32_t plistTotal
= 1;
634 if (format
> kSecValidUpdateFormatG2
) {
635 plistCount
= OSSwapInt32(*((uint32_t *)p
));
636 plistTotal
= plistCount
;
637 bytesRemaining
-= sizeof(uint32_t);
638 p
+= sizeof(uint32_t);
640 if (dataLength
> bytesRemaining
) {
641 secinfo("validupdate", "Skipping property list creation (dataLength=%ld, bytesRemaining=%ld)",
642 (long)dataLength
, (long)bytesRemaining
);
643 SecError(errSecParam
, error
, CFSTR("SecValidUpdateProcessData: data longer than expected"));
647 /* process each chunked plist */
649 CFErrorRef localError
= NULL
;
650 uint32_t plistProcessed
= 0;
651 while (plistCount
> 0 && bytesRemaining
> 0) {
652 CFPropertyListRef propertyList
= NULL
;
653 uint32_t plistLength
= dataLength
;
654 if (format
> kSecValidUpdateFormatG2
) {
655 plistLength
= OSSwapInt32(*((uint32_t *)p
));
656 bytesRemaining
-= sizeof(uint32_t);
657 p
+= sizeof(uint32_t);
662 if (plistLength
<= bytesRemaining
) {
663 CFDataRef data
= CFDataCreateWithBytesNoCopy(NULL
, p
, plistLength
, kCFAllocatorNull
);
664 propertyList
= CFPropertyListCreateWithData(NULL
, data
, kCFPropertyListImmutable
, NULL
, NULL
);
667 if (isDictionary(propertyList
)) {
668 secdebug("validupdate", "Ingesting plist chunk %u of %u, length: %u",
669 plistProcessed
, plistTotal
, plistLength
);
670 CFIndex curVersion
= -1;
671 ok
= ok
&& SecRevocationDbIngestUpdate(dbc
, (CFDictionaryRef
)propertyList
, version
, &curVersion
, &localError
);
672 if (plistProcessed
== 1) {
673 dbc
->precommitVersion
= version
= curVersion
;
674 // get server-provided interval
675 CFTypeRef value
= (CFNumberRef
)CFDictionaryGetValue((CFDictionaryRef
)propertyList
,
676 CFSTR("check-again"));
677 if (isNumber(value
)) {
678 CFNumberGetValue((CFNumberRef
)value
, kCFNumberCFIndexType
, &interval
);
680 // get server-provided hash list
681 value
= (CFArrayRef
)CFDictionaryGetValue((CFDictionaryRef
)propertyList
,
683 ok
= _SecRevocationDbSetHashes(dbc
, (CFArrayRef
)value
, &localError
);
685 if (ok
&& curVersion
< 0) {
686 plistCount
= 0; // we already had this version; skip remaining plists
690 secinfo("validupdate", "Failed to deserialize update chunk %u of %u",
691 plistProcessed
, plistTotal
);
692 SecError(errSecParam
, error
, CFSTR("SecValidUpdateProcessData: failed to get update chunk"));
693 if (plistProcessed
== 1) {
694 gNextUpdate
= SecRevocationDbComputeNextUpdateTime(0);
697 /* All finished with this property list */
698 CFReleaseSafe(propertyList
);
700 bytesRemaining
-= plistLength
;
704 if (ok
&& version
> 0) {
705 secdebug("validupdate", "Update received: v%ld", (long)version
);
706 gLastVersion
= version
;
707 gNextUpdate
= SecRevocationDbComputeNextUpdateTime(interval
);
708 secdebug("validupdate", "Next update time: %f", gNextUpdate
);
712 (void) CFErrorPropagate(localError
, error
);
716 void SecValidUpdateVerifyAndIngest(CFDataRef updateData
, CFStringRef updateServer
, bool fullUpdate
) {
718 secnotice("validupdate", "invalid update data");
721 /* Verify CMS signature on signed data */
722 if (!SecRevocationDbVerifyUpdate((void *)CFDataGetBytePtr(updateData
), CFDataGetLength(updateData
))) {
723 secerror("failed to verify valid update");
724 TrustdHealthAnalyticsLogErrorCode(TAEventValidUpdate
, TAFatalError
, errSecVerifyFailed
);
727 /* Read current update source from database. */
728 CFStringRef dbSource
= SecRevocationDbCopyUpdateSource();
729 if (dbSource
&& updateServer
&& (kCFCompareEqualTo
!= CFStringCompare(dbSource
, updateServer
,
730 kCFCompareCaseInsensitive
))) {
731 secnotice("validupdate", "switching db source from \"%@\" to \"%@\"", dbSource
, updateServer
);
733 CFReleaseNull(dbSource
);
735 /* Ingest the update. This is now performed under a single immediate write transaction,
736 so other writers are blocked (but not other readers), and the changes can be rolled back
737 in their entirety if any error occurs. */
738 __block
bool ok
= true;
739 __block CFErrorRef localError
= NULL
;
740 SecRevocationDbWith(^(SecRevocationDbRef rdb
) {
741 ok
&= SecRevocationDbPerformWrite(rdb
, &localError
, ^bool(SecRevocationDbConnectionRef dbc
, CFErrorRef
*blockError
) {
743 /* Must completely replace existing database contents */
744 secdebug("validupdate", "starting to process full update; clearing database");
745 ok
= ok
&& _SecRevocationDbRemoveAllEntries(dbc
, blockError
);
746 ok
= ok
&& _SecRevocationDbSetUpdateSource(dbc
, updateServer
, blockError
);
747 dbc
->precommitVersion
= 0;
748 dbc
->fullUpdate
= true;
750 CFIndex startingVersion
= dbc
->precommitVersion
;
751 ok
= ok
&& SecValidUpdateProcessData(dbc
, kSecValidUpdateFormatG3
, updateData
, blockError
);
752 rdb
->changed
= ok
&& (startingVersion
< dbc
->precommitVersion
);
754 secerror("failed to process valid update: %@", blockError
? *blockError
: NULL
);
755 TrustdHealthAnalyticsLogErrorCode(TAEventValidUpdate
, TAFatalError
, errSecDecode
);
757 TrustdHealthAnalyticsLogSuccess(TAEventValidUpdate
);
762 rdb
->changed
= false;
763 bool verifyEnabled
= false;
764 CFBooleanRef value
= (CFBooleanRef
)CFPreferencesCopyValue(kVerifyEnabledKey
,
765 kSecPrefsDomain
, kCFPreferencesAnyUser
, kCFPreferencesCurrentHost
);
766 if (isBoolean(value
)) {
767 verifyEnabled
= CFBooleanGetValue(value
);
769 CFReleaseNull(value
);
771 /* compute and verify database content hashes */
772 ok
= ok
&& SecRevocationDbPerformRead(rdb
, &localError
, ^bool(SecRevocationDbConnectionRef dbc
, CFErrorRef
*blockError
) {
773 ok
= ok
&& SecRevocationDbComputeDigests(dbc
, blockError
);
775 /* digests failed to verify, so roll back to known-good snapshot */
776 (void) SecValidUpdateForceReplaceDatabase();
781 /* signal other trustd instances that the database has been updated */
782 notify_post(kSecRevocationDbChanged
);
786 /* remember next update time in case of restart (separate write transaction) */
787 (void) SecRevocationDbSetNextUpdateTime(gNextUpdate
, NULL
);
789 CFReleaseSafe(localError
);
792 static bool SecValidUpdateForceReplaceDatabase(void) {
793 __block
bool result
= false;
795 // write semaphore file that we will pick up when we next launch
796 WithPathInRevocationInfoDirectory(CFSTR(kSecRevocationDbReplaceFile
), ^(const char *utf8String
) {
798 int fd
= open(utf8String
, O_WRONLY
| O_CREAT
, DEFFILEMODE
);
799 if (fd
== -1 || fstat(fd
, &sb
)) {
800 secnotice("validupdate", "unable to write %s (error %d)", utf8String
, errno
);
809 // exit as gracefully as possible so we can replace the database
810 secnotice("validupdate", "process exiting to replace db file");
811 dispatch_after(dispatch_time(DISPATCH_TIME_NOW
, 3ull*NSEC_PER_SEC
), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT
, 0), ^{
812 xpc_transaction_exit_clean();
818 static bool SecValidUpdateSatisfiedLocally(CFStringRef server
, CFIndex version
, bool safeToReplace
) {
819 __block
bool result
= false;
820 SecOTAPKIRef otapkiRef
= NULL
;
821 bool relaunching
= false;
822 static int sNumLocalUpdates
= 0;
824 // if we've replaced the database with a local asset twice in a row,
825 // something is wrong with it. Get this update from the server.
826 if (sNumLocalUpdates
> 1) {
827 secdebug("validupdate", "%d consecutive db resets, ignoring local asset", sNumLocalUpdates
);
831 // if a non-production server is specified, we will not be able to use a
832 // local production asset since its update sequence will be different.
833 if (kCFCompareEqualTo
!= CFStringCompare(server
, kValidUpdateProdServer
,
834 kCFCompareCaseInsensitive
)) {
835 secdebug("validupdate", "non-production server specified, ignoring local asset");
839 // check static database asset(s)
840 otapkiRef
= SecOTAPKICopyCurrentOTAPKIRef();
844 CFIndex assetVersion
= SecOTAPKIGetValidSnapshotVersion(otapkiRef
);
845 CFIndex assetFormat
= SecOTAPKIGetValidSnapshotFormat(otapkiRef
);
846 // version <= 0 means the database is invalid or empty.
847 // version > 0 means we have some version, but we need to see if a
848 // newer version is available as a local asset.
849 if (assetVersion
<= version
|| assetFormat
< kSecValidUpdateFormatG3
) {
850 // asset is not newer than ours, or its version is unknown
854 // replace database only if safe to do so (i.e. called at startup)
855 if (!safeToReplace
) {
856 relaunching
= SecValidUpdateForceReplaceDatabase();
860 // try to copy uncompressed database asset, if available
861 const char *validDbPathBuf
= SecOTAPKIGetValidDatabaseSnapshot(otapkiRef
);
862 if (validDbPathBuf
) {
863 WithPathInRevocationInfoDirectory(CFSTR(kSecRevocationDbFileName
), ^(const char *path
) {
864 secdebug("validupdate", "will copy data from \"%s\"", validDbPathBuf
);
865 copyfile_state_t state
= copyfile_state_alloc();
866 int retval
= copyfile(validDbPathBuf
, path
, state
, COPYFILE_DATA
);
867 copyfile_state_free(state
);
869 secnotice("validupdate", "copyfile error %d", retval
);
877 CFReleaseNull(otapkiRef
);
880 gLastVersion
= SecRevocationDbGetVersion();
881 // note: snapshot should already have latest schema and production source,
882 // but set it here anyway so we don't keep trying to replace the db.
883 SecRevocationDbWith(^(SecRevocationDbRef db
) {
884 (void)SecRevocationDbSetUpdateSource(db
, server
);
885 (void)SecRevocationDbUpdateSchema(db
);
888 secdebug("validupdate", "local update to g%ld/v%ld complete at %f",
889 (long)SecRevocationDbGetUpdateFormat(), (long)gLastVersion
,
890 (double)CFAbsoluteTimeGetCurrent());
892 sNumLocalUpdates
= 0; // reset counter
895 // request is locally satisfied; don't schedule a network update
901 static bool SecValidUpdateSchedule(bool updateEnabled
, CFStringRef server
, CFIndex version
) {
902 /* Check if we have a later version available locally */
903 if (SecValidUpdateSatisfiedLocally(server
, version
, false)) {
907 /* If update not permitted return */
908 if (!updateEnabled
) {
909 secnotice("validupdate", "skipping update");
913 #if !TARGET_OS_BRIDGE
914 /* Schedule as a maintenance task */
915 secdebug("validupdate", "will fetch v%lu from \"%@\"", (unsigned long)version
, server
);
916 return SecValidUpdateRequest(SecRevocationDbGetUpdateQueue(), server
, version
);
922 static CFStringRef
SecRevocationDbGetDefaultServer(void) {
923 #if !TARGET_OS_WATCH && !TARGET_OS_BRIDGE
925 CFStringRef defaultServer
= kValidUpdateSeedServer
;
926 #else // !RC_SEED_BUILD
927 CFStringRef defaultServer
= kValidUpdateProdServer
;
928 #endif // !RC_SEED_BUILD
929 if (os_variant_has_internal_diagnostics("com.apple.security")) {
930 defaultServer
= kValidUpdateCarryServer
;
932 return defaultServer
;
933 #else // TARGET_OS_WATCH || TARGET_OS_BRIDGE
934 /* Because watchOS and bridgeOS can't update over the air, we should
935 * always use the prod server so that the valid database built into the
937 return kValidUpdateProdServer
;
941 static CF_RETURNS_RETAINED CFStringRef
SecRevocationDbCopyServer(void) {
942 /* Prefer a in-process setting for the update server, as used in testing */
943 CFTypeRef value
= CFPreferencesCopyAppValue(kUpdateServerKey
, kSecPrefsDomain
);
945 value
= (CFStringRef
)CFPreferencesCopyValue(kUpdateServerKey
, kSecPrefsDomain
, kCFPreferencesAnyUser
, kCFPreferencesCurrentHost
);
947 CFStringRef server
= (isString(value
)) ? (CFStringRef
)value
: CFRetainSafe(SecRevocationDbGetDefaultServer());
951 void SecRevocationDbInitialize() {
952 if (!isDbOwner()) { return; }
953 os_transaction_t transaction
= os_transaction_create("com.apple.trustd.valid.initialize");
954 __block
bool initializeDb
= false;
956 /* create base path if it doesn't exist */
957 WithPathInRevocationInfoDirectory(NULL
, ^(const char *utf8String
) {
958 (void)mkpath_np(utf8String
, 0755);
961 /* check semaphore file */
962 WithPathInRevocationInfoDirectory(CFSTR(kSecRevocationDbReplaceFile
), ^(const char *path
) {
964 if (stat(path
, &sb
) == 0) {
965 initializeDb
= true; /* file was found, so we will replace the database */
966 if (remove(path
) == -1) {
968 secnotice("validupdate", "remove (%s): %s", path
, strerror(error
));
974 WithPathInRevocationInfoDirectory(CFSTR(kSecRevocationDbFileName
), ^(const char *path
) {
976 /* remove old database file(s) */
977 (void)removeFileWithSuffix(path
, "");
978 (void)removeFileWithSuffix(path
, "-journal");
979 (void)removeFileWithSuffix(path
, "-shm");
980 (void)removeFileWithSuffix(path
, "-wal");
984 if (stat(path
, &sb
) == -1) {
985 initializeDb
= true; /* file not found, so we will create the database */
991 os_release(transaction
);
992 return; /* database exists and doesn't need replacing */
995 /* initialize database from local asset */
996 CFStringRef server
= SecRevocationDbCopyServer();
998 secnotice("validupdate", "initializing database");
999 if (!SecValidUpdateSatisfiedLocally(server
, version
, true)) {
1000 #if !TARGET_OS_BRIDGE
1001 /* Schedule full update as a maintenance task */
1002 (void)SecValidUpdateRequest(SecRevocationDbGetUpdateQueue(), server
, version
);
1005 CFReleaseSafe(server
);
1006 os_release(transaction
);
1011 // MARK: SecValidInfoRef
1013 ==============================================================================
1015 ==============================================================================
1018 CFGiblisWithCompareFor(SecValidInfo
);
1020 static SecValidInfoRef
SecValidInfoCreate(SecValidInfoFormat format
,
1021 CFOptionFlags flags
,
1024 CFDataRef issuerHash
,
1025 CFDataRef anchorHash
,
1026 CFDateRef notBeforeDate
,
1027 CFDateRef notAfterDate
,
1028 CFDataRef nameConstraints
,
1029 CFDataRef policyConstraints
) {
1030 SecValidInfoRef validInfo
;
1031 validInfo
= CFTypeAllocate(SecValidInfo
, struct __SecValidInfo
, kCFAllocatorDefault
);
1032 if (!validInfo
) { return NULL
; }
1034 CFRetainSafe(certHash
);
1035 CFRetainSafe(issuerHash
);
1036 CFRetainSafe(anchorHash
);
1037 CFRetainSafe(notBeforeDate
);
1038 CFRetainSafe(notAfterDate
);
1039 CFRetainSafe(nameConstraints
);
1040 CFRetainSafe(policyConstraints
);
1042 validInfo
->format
= format
;
1043 validInfo
->certHash
= certHash
;
1044 validInfo
->issuerHash
= issuerHash
;
1045 validInfo
->anchorHash
= anchorHash
;
1046 validInfo
->isOnList
= isOnList
;
1047 validInfo
->valid
= (flags
& kSecValidInfoAllowlist
);
1048 validInfo
->complete
= (flags
& kSecValidInfoComplete
);
1049 validInfo
->checkOCSP
= (flags
& kSecValidInfoCheckOCSP
);
1050 validInfo
->knownOnly
= (flags
& kSecValidInfoKnownOnly
);
1051 validInfo
->requireCT
= (flags
& kSecValidInfoRequireCT
);
1052 validInfo
->noCACheck
= (flags
& kSecValidInfoNoCAv2Check
);
1053 validInfo
->overridable
= (flags
& kSecValidInfoOverridable
);
1054 validInfo
->hasDateConstraints
= (flags
& kSecValidInfoDateConstraints
);
1055 validInfo
->hasNameConstraints
= (flags
& kSecValidInfoNameConstraints
);
1056 validInfo
->hasPolicyConstraints
= (flags
& kSecValidInfoPolicyConstraints
);
1057 validInfo
->notBeforeDate
= notBeforeDate
;
1058 validInfo
->notAfterDate
= notAfterDate
;
1059 validInfo
->nameConstraints
= nameConstraints
;
1060 validInfo
->policyConstraints
= policyConstraints
;
1065 static void SecValidInfoDestroy(CFTypeRef cf
) {
1066 SecValidInfoRef validInfo
= (SecValidInfoRef
)cf
;
1068 CFReleaseNull(validInfo
->certHash
);
1069 CFReleaseNull(validInfo
->issuerHash
);
1070 CFReleaseNull(validInfo
->anchorHash
);
1071 CFReleaseNull(validInfo
->notBeforeDate
);
1072 CFReleaseNull(validInfo
->notAfterDate
);
1073 CFReleaseNull(validInfo
->nameConstraints
);
1074 CFReleaseNull(validInfo
->policyConstraints
);
1078 void SecValidInfoSetAnchor(SecValidInfoRef validInfo
, SecCertificateRef anchor
) {
1082 CFDataRef anchorHash
= NULL
;
1084 anchorHash
= SecCertificateCopySHA256Digest(anchor
);
1086 /* clear no-ca flag for anchors where we want OCSP checked [32523118] */
1087 if (SecIsAppleTrustAnchor(anchor
, 0)) {
1088 validInfo
->noCACheck
= false;
1091 CFReleaseNull(validInfo
->anchorHash
);
1092 validInfo
->anchorHash
= anchorHash
;
1095 static Boolean
SecValidInfoCompare(CFTypeRef a
, CFTypeRef b
) {
1096 SecValidInfoRef validInfoA
= (SecValidInfoRef
)a
;
1097 SecValidInfoRef validInfoB
= (SecValidInfoRef
)b
;
1098 if (validInfoA
== validInfoB
) {
1101 if (!validInfoA
|| !validInfoB
||
1102 (CFGetTypeID(a
) != SecValidInfoGetTypeID()) ||
1103 (CFGetTypeID(b
) != SecValidInfoGetTypeID())) {
1106 return CFEqualSafe(validInfoA
->certHash
, validInfoB
->certHash
) && CFEqualSafe(validInfoA
->issuerHash
, validInfoB
->issuerHash
);
1109 static CFStringRef
SecValidInfoCopyFormatDescription(CFTypeRef cf
, CFDictionaryRef formatOptions
) {
1110 SecValidInfoRef validInfo
= (SecValidInfoRef
)cf
;
1111 CFStringRef certHash
= CFDataCopyHexString(validInfo
->certHash
);
1112 CFStringRef issuerHash
= CFDataCopyHexString(validInfo
->issuerHash
);
1113 CFStringRef desc
= CFStringCreateWithFormat(NULL
, formatOptions
, CFSTR("validInfo certHash: %@ issuerHash: %@"), certHash
, issuerHash
);
1114 CFReleaseNull(certHash
);
1115 CFReleaseNull(issuerHash
);
1121 // MARK: SecRevocationDb
1123 ==============================================================================
1125 ==============================================================================
1128 /* SecRevocationDbCheckNextUpdate returns true if we dispatched an
1129 update request, otherwise false.
1131 static bool _SecRevocationDbCheckNextUpdate(void) {
1132 // are we the db owner instance?
1136 CFTypeRef value
= NULL
;
1138 // is it time to check?
1139 CFAbsoluteTime now
= CFAbsoluteTimeGetCurrent();
1140 CFAbsoluteTime minNextUpdate
= now
+ gUpdateInterval
;
1141 gUpdateStarted
= now
;
1143 if (0 == gNextUpdate
) {
1144 // first time we're called, check if we have a saved nextUpdate value
1145 gNextUpdate
= SecRevocationDbGetNextUpdateTime();
1146 minNextUpdate
= now
;
1147 if (gNextUpdate
< minNextUpdate
) {
1148 gNextUpdate
= minNextUpdate
;
1150 // allow pref to override update interval, if it exists
1151 CFIndex interval
= -1;
1152 value
= (CFNumberRef
)CFPreferencesCopyValue(kUpdateIntervalKey
, kSecPrefsDomain
, kCFPreferencesAnyUser
, kCFPreferencesCurrentHost
);
1153 if (isNumber(value
)) {
1154 if (CFNumberGetValue((CFNumberRef
)value
, kCFNumberCFIndexType
, &interval
)) {
1155 if (interval
< kSecMinUpdateInterval
) {
1156 interval
= kSecMinUpdateInterval
;
1157 } else if (interval
> kSecMaxUpdateInterval
) {
1158 interval
= kSecMaxUpdateInterval
;
1162 CFReleaseNull(value
);
1163 gUpdateInterval
= kSecStdUpdateInterval
;
1165 gUpdateInterval
= interval
;
1167 // pin next update time to the preferred update interval
1168 if (gNextUpdate
> (gUpdateStarted
+ gUpdateInterval
)) {
1169 gNextUpdate
= gUpdateStarted
+ gUpdateInterval
;
1171 secdebug("validupdate", "next update at %f (in %f seconds)",
1172 (double)gUpdateStarted
, (double)gNextUpdate
-gUpdateStarted
);
1174 if (gNextUpdate
> now
) {
1178 secnotice("validupdate", "starting update");
1180 // set minimum next update time here in case we can't get an update
1181 gNextUpdate
= minNextUpdate
;
1183 // determine which server to query
1184 CFStringRef server
= SecRevocationDbCopyServer();
1186 // determine version of our current database
1187 CFIndex version
= SecRevocationDbGetVersion();
1188 secdebug("validupdate", "got version %ld from db", (long)version
);
1190 if (gLastVersion
> 0) {
1191 secdebug("validupdate", "error getting version; using last good version: %ld", (long)gLastVersion
);
1193 version
= gLastVersion
;
1196 // determine source of our current database
1197 // (if this ever changes, we will need to reload the db)
1198 CFStringRef db_source
= SecRevocationDbCopyUpdateSource();
1200 db_source
= (CFStringRef
) CFRetain(kValidUpdateProdServer
);
1203 // determine whether we need to recreate the database
1204 CFIndex db_version
= SecRevocationDbGetSchemaVersion();
1205 CFIndex db_format
= SecRevocationDbGetUpdateFormat();
1206 if (db_version
< kSecRevocationDbSchemaVersion
||
1207 db_format
< kSecRevocationDbUpdateFormat
||
1208 kCFCompareEqualTo
!= CFStringCompare(server
, db_source
, kCFCompareCaseInsensitive
)) {
1209 // we need to fully rebuild the db contents, so we set our version to 0.
1210 version
= gLastVersion
= 0;
1213 // determine whether update fetching is enabled
1214 #if !TARGET_OS_WATCH && !TARGET_OS_BRIDGE
1215 // Valid update fetching was initially enabled on macOS 10.13 and iOS 11.0.
1216 // This conditional has been changed to include every platform and version
1217 // except for those where the db should not be updated over the air.
1218 bool updateEnabled
= true;
1220 bool updateEnabled
= false;
1222 value
= (CFBooleanRef
)CFPreferencesCopyValue(kUpdateEnabledKey
, kSecPrefsDomain
, kCFPreferencesAnyUser
, kCFPreferencesCurrentHost
);
1223 if (isBoolean(value
)) {
1224 updateEnabled
= CFBooleanGetValue((CFBooleanRef
)value
);
1226 CFReleaseNull(value
);
1228 // Schedule maintenance work
1229 bool result
= SecValidUpdateSchedule(updateEnabled
, server
, version
);
1230 CFReleaseNull(server
);
1231 CFReleaseNull(db_source
);
1235 void SecRevocationDbCheckNextUpdate(void) {
1236 static dispatch_once_t once
;
1237 static sec_action_t action
;
1239 dispatch_once(&once
, ^{
1240 dispatch_queue_t update_queue
= SecRevocationDbGetUpdateQueue();
1241 action
= sec_action_create_with_queue(update_queue
, "update_check", kSecMinUpdateInterval
);
1242 sec_action_set_handler(action
, ^{
1243 os_transaction_t transaction
= os_transaction_create("com.apple.trustd.valid.checkNextUpdate");
1244 (void)_SecRevocationDbCheckNextUpdate();
1245 os_release(transaction
);
1248 sec_action_perform(action
);
1251 /* This function verifies an update, in this format:
1252 1) unsigned 32-bit network-byte-order length of binary plist
1253 2) binary plist data
1254 3) unsigned 32-bit network-byte-order length of CMS message
1255 4) CMS message (containing certificates and signature over binary plist)
1257 The length argument is the total size of the packed update data.
1259 bool SecRevocationDbVerifyUpdate(void *update
, CFIndex length
) {
1260 if (!update
|| length
<= (CFIndex
)sizeof(uint32_t)) {
1263 uint32_t plistLength
= OSSwapInt32(*((uint32_t *)update
));
1264 if ((plistLength
+ (CFIndex
)(sizeof(uint32_t)*2)) > (uint64_t) length
) {
1265 secdebug("validupdate", "ERROR: reported plist length (%lu)+%lu exceeds total length (%lu)\n",
1266 (unsigned long)plistLength
, (unsigned long)sizeof(uint32_t)*2, (unsigned long)length
);
1269 uint8_t *plistData
= (uint8_t *)update
+ sizeof(uint32_t);
1270 uint8_t *sigData
= (uint8_t *)plistData
+ plistLength
;
1271 uint32_t sigLength
= OSSwapInt32(*((uint32_t *)sigData
));
1272 sigData
+= sizeof(uint32_t);
1273 if ((plistLength
+ sigLength
+ (CFIndex
)(sizeof(uint32_t) * 2)) != (uint64_t) length
) {
1274 secdebug("validupdate", "ERROR: reported lengths do not add up to total length\n");
1278 OSStatus status
= 0;
1279 CMSSignerStatus signerStatus
;
1280 CMSDecoderRef cms
= NULL
;
1281 SecPolicyRef policy
= NULL
;
1282 SecTrustRef trust
= NULL
;
1283 CFDataRef content
= NULL
;
1285 if ((content
= CFDataCreateWithBytesNoCopy(kCFAllocatorDefault
,
1286 (const UInt8
*)plistData
, (CFIndex
)plistLength
, kCFAllocatorNull
)) == NULL
) {
1287 secdebug("validupdate", "CFDataCreateWithBytesNoCopy failed (%ld bytes)\n", (long)plistLength
);
1291 if ((status
= CMSDecoderCreate(&cms
)) != errSecSuccess
) {
1292 secdebug("validupdate", "CMSDecoderCreate failed with error %d\n", (int)status
);
1295 if ((status
= CMSDecoderUpdateMessage(cms
, sigData
, sigLength
)) != errSecSuccess
) {
1296 secdebug("validupdate", "CMSDecoderUpdateMessage failed with error %d\n", (int)status
);
1299 if ((status
= CMSDecoderSetDetachedContent(cms
, content
)) != errSecSuccess
) {
1300 secdebug("validupdate", "CMSDecoderSetDetachedContent failed with error %d\n", (int)status
);
1303 if ((status
= CMSDecoderFinalizeMessage(cms
)) != errSecSuccess
) {
1304 secdebug("validupdate", "CMSDecoderFinalizeMessage failed with error %d\n", (int)status
);
1308 policy
= SecPolicyCreateApplePinned(CFSTR("ValidUpdate"), // kSecPolicyNameAppleValidUpdate
1309 CFSTR("1.2.840.113635.100.6.2.10"), // System Integration 2 Intermediate Certificate
1310 CFSTR("1.2.840.113635.100.6.51")); // Valid update signing OID
1312 // Check that the first signer actually signed this message.
1313 if ((status
= CMSDecoderCopySignerStatus(cms
, 0, policy
,
1314 false, &signerStatus
, &trust
, NULL
)) != errSecSuccess
) {
1315 secdebug("validupdate", "CMSDecoderCopySignerStatus failed with error %d\n", (int)status
);
1318 // Make sure the signature verifies against the detached content
1319 if (signerStatus
!= kCMSSignerValid
) {
1320 secdebug("validupdate", "ERROR: signature did not verify (signer status %d)\n", (int)signerStatus
);
1321 status
= errSecInvalidSignature
;
1324 // Make sure the signing certificate is valid for the specified policy
1325 SecTrustResultType trustResult
= kSecTrustResultInvalid
;
1326 status
= SecTrustEvaluate(trust
, &trustResult
);
1327 if (status
!= errSecSuccess
) {
1328 secdebug("validupdate", "SecTrustEvaluate failed with error %d (trust=%p)\n", (int)status
, (void *)trust
);
1329 } else if (!(trustResult
== kSecTrustResultUnspecified
|| trustResult
== kSecTrustResultProceed
)) {
1330 secdebug("validupdate", "SecTrustEvaluate failed with trust result %d\n", (int)trustResult
);
1331 status
= errSecVerificationFailure
;
1336 CFReleaseSafe(content
);
1337 CFReleaseSafe(trust
);
1338 CFReleaseSafe(policy
);
1341 return (status
== errSecSuccess
);
1344 CFAbsoluteTime
SecRevocationDbComputeNextUpdateTime(CFIndex updateInterval
) {
1345 CFIndex interval
= updateInterval
;
1346 // try to use interval preference if it exists
1347 CFTypeRef value
= (CFNumberRef
)CFPreferencesCopyValue(kUpdateIntervalKey
, kSecPrefsDomain
, kCFPreferencesAnyUser
, kCFPreferencesCurrentHost
);
1348 if (isNumber(value
)) {
1349 CFNumberGetValue((CFNumberRef
)value
, kCFNumberCFIndexType
, &interval
);
1351 CFReleaseNull(value
);
1353 if (interval
<= 0) {
1354 interval
= kSecStdUpdateInterval
;
1358 if (interval
< kSecMinUpdateInterval
) {
1359 interval
= kSecMinUpdateInterval
;
1360 } else if (interval
> kSecMaxUpdateInterval
) {
1361 interval
= kSecMaxUpdateInterval
;
1364 // compute randomization factor, between 0 and 50% of the interval
1365 CFIndex fuzz
= arc4random() % (long)(interval
/2.0);
1366 CFAbsoluteTime nextUpdate
= CFAbsoluteTimeGetCurrent() + interval
+ fuzz
;
1367 secdebug("validupdate", "next update in %ld seconds", (long)(interval
+ fuzz
));
1371 void SecRevocationDbComputeAndSetNextUpdateTime(void) {
1372 gNextUpdate
= SecRevocationDbComputeNextUpdateTime(0);
1373 (void) SecRevocationDbSetNextUpdateTime(gNextUpdate
, NULL
);
1374 gUpdateStarted
= 0; /* no update is currently in progress */
1377 bool SecRevocationDbIngestUpdate(SecRevocationDbConnectionRef dbc
, CFDictionaryRef update
, CFIndex chunkVersion
, CFIndex
*outVersion
, CFErrorRef
*error
) {
1379 CFIndex version
= 0;
1380 CFErrorRef localError
= NULL
;
1382 SecError(errSecParam
, &localError
, CFSTR("SecRevocationDbIngestUpdate: invalid update parameter"));
1383 goto setVersionAndExit
;
1385 CFTypeRef value
= (CFNumberRef
)CFDictionaryGetValue(update
, CFSTR("version"));
1386 if (isNumber(value
)) {
1387 if (!CFNumberGetValue((CFNumberRef
)value
, kCFNumberCFIndexType
, &version
)) {
1392 // only the first chunk will have a version, so the second and
1393 // subsequent chunks will need to pass it in chunkVersion.
1394 version
= chunkVersion
;
1396 // check precommitted version since update hasn't been committed yet
1397 CFIndex curVersion
= dbc
->precommitVersion
;
1398 if (version
> curVersion
|| chunkVersion
> 0) {
1399 ok
= _SecRevocationDbApplyUpdate(dbc
, update
, version
, &localError
);
1400 secdebug("validupdate", "_SecRevocationDbApplyUpdate=%s, v%ld, precommit=%ld, full=%s",
1401 (ok
) ? "1" : "0", (long)version
, (long)dbc
->precommitVersion
,
1402 (dbc
->fullUpdate
) ? "1" : "0");
1404 secdebug("validupdate", "we have v%ld, skipping update to v%ld",
1405 (long)curVersion
, (long)version
);
1406 version
= -1; // invalid, so we know to skip subsequent chunks
1407 ok
= true; // this is not an error condition
1411 *outVersion
= version
;
1413 (void) CFErrorPropagate(localError
, error
);
1418 /* Database schema */
1420 /* admin table holds these key-value (or key-ival) pairs:
1421 'version' (integer) // version of database content
1422 'check_again' (double) // CFAbsoluteTime of next check (optional)
1423 'db_version' (integer) // version of database schema
1424 'db_hash' (blob) // SHA-256 database hash
1425 --> entries in admin table are unique by text key
1427 issuers table holds map of issuing CA hashes to group identifiers:
1428 groupid (integer) // associated group identifier in group ID table
1429 issuer_hash (blob) // SHA-256 hash of issuer certificate (primary key)
1430 --> entries in issuers table are unique by issuer_hash;
1431 multiple issuer entries may have the same groupid!
1433 groups table holds records with these attributes:
1434 groupid (integer) // ordinal ID associated with this group entry
1435 flags (integer) // a bitmask of the following values:
1436 kSecValidInfoComplete (0x00000001) set if we have all revocation info for this issuer group
1437 kSecValidInfoCheckOCSP (0x00000002) set if must check ocsp for certs from this issuer group
1438 kSecValidInfoKnownOnly (0x00000004) set if any CA from this issuer group must be in database
1439 kSecValidInfoRequireCT (0x00000008) set if all certs from this issuer group must have SCTs
1440 kSecValidInfoAllowlist (0x00000010) set if this entry describes valid certs (i.e. is allowed)
1441 kSecValidInfoNoCACheck (0x00000020) set if this entry does not require an OCSP check to accept (deprecated)
1442 kSecValidInfoOverridable (0x00000040) set if the trust status is recoverable and can be overridden
1443 kSecValidInfoDateConstraints (0x00000080) set if this group has not-before or not-after constraints
1444 kSecValidInfoNameConstraints (0x00000100) set if this group has name constraints in database
1445 kSecValidInfoPolicyConstraints (0x00000200) set if this group has policy constraints in database
1446 kSecValidInfoNoCAv2Check (0x00000400) set if this entry does not require an OCSP check to accept
1447 format (integer) // an integer describing format of entries:
1448 kSecValidInfoFormatUnknown (0) unknown format
1449 kSecValidInfoFormatSerial (1) serial number, not greater than 20 bytes in length
1450 kSecValidInfoFormatSHA256 (2) SHA-256 hash, 32 bytes in length
1451 kSecValidInfoFormatNto1 (3) filter data blob of arbitrary length
1452 data (blob) // Bloom filter data if format is 'nto1', otherwise NULL
1453 policies (blob) // NULL, or uint8_t count value followed by array of int8_t policy values
1454 --> entries in groups table are unique by groupid
1456 serials table holds serial number blobs with these attributes:
1457 groupid (integer) // identifier for issuer group in the groups table
1458 serial (blob) // serial number
1459 --> entries in serials table are unique by serial and groupid
1461 hashes table holds SHA-256 hashes of certificates with these attributes:
1462 groupid (integer) // identifier for issuer group in the groups table
1463 sha256 (blob) // SHA-256 hash of subject certificate
1464 --> entries in hashes table are unique by sha256 and groupid
1466 dates table holds notBefore and notAfter dates (as CFAbsoluteTime) with these attributes:
1467 groupid (integer) // identifier for issuer group in the groups table (primary key)
1468 notbefore (real) // issued certs are invalid if their notBefore is prior to this date
1469 notafter (real) // issued certs are invalid after this date (or their notAfter, if earlier)
1470 --> entries in dates table are unique by groupid, and only exist if kSecValidInfoDateConstraints is true
1473 #define createTablesSQL CFSTR("CREATE TABLE IF NOT EXISTS admin(" \
1474 "key TEXT PRIMARY KEY NOT NULL," \
1475 "ival INTEGER NOT NULL," \
1478 "CREATE TABLE IF NOT EXISTS issuers(" \
1479 "groupid INTEGER NOT NULL," \
1480 "issuer_hash BLOB PRIMARY KEY NOT NULL" \
1482 "CREATE INDEX IF NOT EXISTS issuer_idx ON issuers(issuer_hash);" \
1483 "CREATE TABLE IF NOT EXISTS groups(" \
1484 "groupid INTEGER PRIMARY KEY AUTOINCREMENT," \
1490 "CREATE TABLE IF NOT EXISTS serials(" \
1491 "groupid INTEGER NOT NULL," \
1492 "serial BLOB NOT NULL," \
1493 "UNIQUE(groupid,serial)" \
1495 "CREATE TABLE IF NOT EXISTS hashes(" \
1496 "groupid INTEGER NOT NULL," \
1497 "sha256 BLOB NOT NULL," \
1498 "UNIQUE(groupid,sha256)" \
1500 "CREATE TABLE IF NOT EXISTS dates(" \
1501 "groupid INTEGER PRIMARY KEY NOT NULL," \
1505 "CREATE TRIGGER IF NOT EXISTS group_del " \
1506 "BEFORE DELETE ON groups FOR EACH ROW " \
1508 "DELETE FROM serials WHERE groupid=OLD.groupid; " \
1509 "DELETE FROM hashes WHERE groupid=OLD.groupid; " \
1510 "DELETE FROM issuers WHERE groupid=OLD.groupid; " \
1511 "DELETE FROM dates WHERE groupid=OLD.groupid; " \
1514 #define selectGroupIdSQL CFSTR("SELECT DISTINCT groupid " \
1515 "FROM issuers WHERE issuer_hash=?")
1516 #define selectVersionSQL CFSTR("SELECT ival FROM admin " \
1517 "WHERE key='version'")
1518 #define selectDbVersionSQL CFSTR("SELECT ival FROM admin " \
1519 "WHERE key='db_version'")
1520 #define selectDbFormatSQL CFSTR("SELECT ival FROM admin " \
1521 "WHERE key='db_format'")
1522 #define selectDbHashSQL CFSTR("SELECT value FROM admin " \
1523 "WHERE key='db_hash'")
1524 #define selectDbSourceSQL CFSTR("SELECT value FROM admin " \
1525 "WHERE key='db_source'")
1526 #define selectNextUpdateSQL CFSTR("SELECT value FROM admin " \
1527 "WHERE key='check_again'")
1528 #define selectUpdateIntervalSQL CFSTR("SELECT ival FROM admin " \
1529 "WHERE key='interval'")
1530 #define selectGroupRecordSQL CFSTR("SELECT flags,format,data,policies " \
1531 "FROM groups WHERE groupid=?")
1532 #define selectSerialRecordSQL CFSTR("SELECT rowid FROM serials " \
1533 "WHERE groupid=? AND serial=?")
1534 #define selectDateRecordSQL CFSTR("SELECT notbefore,notafter FROM " \
1535 "dates WHERE groupid=?")
1536 #define selectHashRecordSQL CFSTR("SELECT rowid FROM hashes " \
1537 "WHERE groupid=? AND sha256=?")
1538 #define insertAdminRecordSQL CFSTR("INSERT OR REPLACE INTO admin " \
1539 "(key,ival,value) VALUES (?,?,?)")
1540 #define insertIssuerRecordSQL CFSTR("INSERT OR REPLACE INTO issuers " \
1541 "(groupid,issuer_hash) VALUES (?,?)")
1542 #define insertGroupRecordSQL CFSTR("INSERT OR REPLACE INTO groups " \
1543 "(groupid,flags,format,data,policies) VALUES (?,?,?,?,?)")
1544 #define insertSerialRecordSQL CFSTR("INSERT OR REPLACE INTO serials " \
1545 "(groupid,serial) VALUES (?,?)")
1546 #define deleteSerialRecordSQL CFSTR("DELETE FROM serials " \
1547 "WHERE groupid=? AND hex(serial) LIKE ?")
1548 #define insertSha256RecordSQL CFSTR("INSERT OR REPLACE INTO hashes " \
1549 "(groupid,sha256) VALUES (?,?)")
1550 #define deleteSha256RecordSQL CFSTR("DELETE FROM hashes " \
1551 "WHERE groupid=? AND hex(sha256) LIKE ?")
1552 #define insertDateRecordSQL CFSTR("INSERT OR REPLACE INTO dates " \
1553 "(groupid,notbefore,notafter) VALUES (?,?,?)")
1554 #define deleteGroupRecordSQL CFSTR("DELETE FROM groups " \
1556 #define deleteGroupIssuersSQL CFSTR("DELETE FROM issuers " \
1558 #define addPoliciesColumnSQL CFSTR("ALTER TABLE groups " \
1559 "ADD COLUMN policies BLOB")
1560 #define updateGroupPoliciesSQL CFSTR("UPDATE OR IGNORE groups " \
1561 "SET policies=? WHERE groupid=?")
1563 #define updateConstraintsTablesSQL CFSTR("" \
1564 "CREATE TABLE IF NOT EXISTS dates(" \
1565 "groupid INTEGER PRIMARY KEY NOT NULL," \
1570 #define updateGroupDeleteTriggerSQL CFSTR("" \
1571 "DROP TRIGGER IF EXISTS group_del;" \
1572 "CREATE TRIGGER group_del BEFORE DELETE ON groups FOR EACH ROW " \
1574 "DELETE FROM serials WHERE groupid=OLD.groupid; " \
1575 "DELETE FROM hashes WHERE groupid=OLD.groupid; " \
1576 "DELETE FROM issuers WHERE groupid=OLD.groupid; " \
1577 "DELETE FROM dates WHERE groupid=OLD.groupid; " \
1580 #define deleteAllEntriesSQL CFSTR("" \
1581 "DELETE FROM groups; " \
1582 "DELETE FROM admin WHERE key='version'; " \
1583 "DELETE FROM sqlite_sequence")
1586 /* Database management */
1588 static SecDbRef
SecRevocationDbCreate(CFStringRef path
) {
1589 /* only the db owner should open a read-write connection. */
1590 __block
bool readWrite
= isDbOwner();
1593 SecDbRef result
= SecDbCreate(path
, mode
, readWrite
, false, true, true, 1, ^bool (SecDbRef db
, SecDbConnectionRef dbconn
, bool didCreate
, bool *callMeAgainForNextConnection
, CFErrorRef
*error
) {
1594 __block
bool ok
= true;
1596 ok
&= SecDbTransaction(dbconn
, kSecDbExclusiveTransactionType
, error
, ^(bool *commit
) {
1597 /* Create all database tables, indexes, and triggers.
1598 * SecDbOpen will set auto_vacuum and journal_mode for us before we get called back.*/
1599 ok
= ok
&& SecDbExec(dbconn
, createTablesSQL
, error
);
1603 if (!ok
|| (error
&& *error
)) {
1604 CFIndex errCode
= errSecInternalComponent
;
1605 if (error
&& *error
) {
1606 errCode
= CFErrorGetCode(*error
);
1608 secerror("%s failed: %@", didCreate
? "Create" : "Open", error
? *error
: NULL
);
1609 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationCreate
, TAFatalError
, errCode
);
1617 static dispatch_once_t kSecRevocationDbOnce
;
1618 static SecRevocationDbRef kSecRevocationDb
= NULL
;
1620 static SecRevocationDbRef
SecRevocationDbInit(CFStringRef db_name
) {
1621 SecRevocationDbRef rdb
;
1622 dispatch_queue_attr_t attr
;
1624 require(rdb
= (SecRevocationDbRef
)malloc(sizeof(struct __SecRevocationDb
)), errOut
);
1626 rdb
->update_queue
= NULL
;
1627 rdb
->updateInProgress
= false;
1628 rdb
->unsupportedVersion
= false;
1629 rdb
->changed
= false;
1631 require(rdb
->db
= SecRevocationDbCreate(db_name
), errOut
);
1632 attr
= dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL
, QOS_CLASS_BACKGROUND
, 0);
1633 attr
= dispatch_queue_attr_make_with_autorelease_frequency(attr
, DISPATCH_AUTORELEASE_FREQUENCY_WORK_ITEM
);
1634 require(rdb
->update_queue
= dispatch_queue_create(NULL
, attr
), errOut
);
1635 require(rdb
->info_cache_list
= CFArrayCreateMutable(NULL
, 0, &kCFTypeArrayCallBacks
), errOut
);
1636 require(rdb
->info_cache
= CFDictionaryCreateMutable(NULL
, 0, &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
), errOut
);
1637 rdb
->info_cache_lock
= OS_UNFAIR_LOCK_INIT
;
1640 /* register for changes signaled by the db owner instance */
1642 notify_register_dispatch(kSecRevocationDbChanged
, &out_token
, rdb
->update_queue
, ^(int __unused token
) {
1643 secnotice("validupdate", "Got notification of database change");
1644 SecRevocationDbResetCaches();
1650 secdebug("validupdate", "Failed to create db at \"%@\"", db_name
);
1652 if (rdb
->update_queue
) {
1653 dispatch_release(rdb
->update_queue
);
1655 CFReleaseSafe(rdb
->db
);
1661 static CFStringRef
SecRevocationDbCopyPath(void) {
1662 CFURLRef revDbURL
= NULL
;
1663 CFStringRef revInfoRelPath
= NULL
;
1664 if ((revInfoRelPath
= CFStringCreateWithFormat(NULL
, NULL
, CFSTR("%s"), kSecRevocationDbFileName
)) != NULL
) {
1665 revDbURL
= SecCopyURLForFileInRevocationInfoDirectory(revInfoRelPath
);
1667 CFReleaseSafe(revInfoRelPath
);
1669 CFStringRef revDbPath
= NULL
;
1671 revDbPath
= CFURLCopyFileSystemPath(revDbURL
, kCFURLPOSIXPathStyle
);
1672 CFRelease(revDbURL
);
1677 static void SecRevocationDbWith(void(^dbJob
)(SecRevocationDbRef db
)) {
1678 dispatch_once(&kSecRevocationDbOnce
, ^{
1679 CFStringRef dbPath
= SecRevocationDbCopyPath();
1681 kSecRevocationDb
= SecRevocationDbInit(dbPath
);
1683 if (kSecRevocationDb
&& isDbOwner()) {
1684 /* check and update schema immediately after database is opened */
1685 SecRevocationDbUpdateSchema(kSecRevocationDb
);
1689 // Do pre job run work here (cancel idle timers etc.)
1690 if (kSecRevocationDb
->updateInProgress
) {
1691 return; // this would block since SecDb has an exclusive transaction lock
1693 dbJob(kSecRevocationDb
);
1694 // Do post job run work here (gc timer, etc.)
1697 static bool SecRevocationDbPerformWrite(SecRevocationDbRef rdb
, CFErrorRef
*error
,
1698 bool(^writeJob
)(SecRevocationDbConnectionRef dbc
, CFErrorRef
*blockError
)) {
1699 __block
bool ok
= true;
1700 __block CFErrorRef localError
= NULL
;
1702 ok
&= SecDbPerformWrite(rdb
->db
, &localError
, ^(SecDbConnectionRef dbconn
) {
1703 ok
&= SecDbTransaction(dbconn
, kSecDbImmediateTransactionType
, &localError
, ^(bool *commit
) {
1704 SecRevocationDbConnectionRef dbc
= SecRevocationDbConnectionInit(rdb
, dbconn
, &localError
);
1705 ok
= ok
&& writeJob(dbc
, &localError
);
1710 ok
&= CFErrorPropagate(localError
, error
);
1714 static bool SecRevocationDbPerformRead(SecRevocationDbRef rdb
, CFErrorRef
*error
,
1715 bool(^readJob
)(SecRevocationDbConnectionRef dbc
, CFErrorRef
*blockError
)) {
1716 __block CFErrorRef localError
= NULL
;
1717 __block
bool ok
= true;
1719 ok
&= SecDbPerformRead(rdb
->db
, &localError
, ^(SecDbConnectionRef dbconn
) {
1720 SecRevocationDbConnectionRef dbc
= SecRevocationDbConnectionInit(rdb
, dbconn
, &localError
);
1721 ok
= ok
&& readJob(dbc
, &localError
);
1724 ok
&= CFErrorPropagate(localError
, error
);
1728 static SecRevocationDbConnectionRef
SecRevocationDbConnectionInit(SecRevocationDbRef db
, SecDbConnectionRef dbconn
, CFErrorRef
*error
) {
1729 SecRevocationDbConnectionRef dbc
= NULL
;
1730 CFErrorRef localError
= NULL
;
1732 dbc
= (SecRevocationDbConnectionRef
)malloc(sizeof(struct __SecRevocationDbConnection
));
1735 dbc
->dbconn
= dbconn
;
1736 dbc
->precommitVersion
= (CFIndex
)_SecRevocationDbGetVersion(dbc
, &localError
);
1737 dbc
->precommitDbVersion
= (CFIndex
)_SecRevocationDbGetSchemaVersion(db
, dbc
, &localError
);
1738 dbc
->precommitInterval
= 0; /* set only if we are explicitly given a new value */
1739 dbc
->fullUpdate
= false;
1741 (void) CFErrorPropagate(localError
, error
);
1745 static CF_RETURNS_RETAINED CFDataRef
createCacheKey(CFDataRef certHash
, CFDataRef issuerHash
) {
1746 CFMutableDataRef concat
= CFDataCreateMutableCopy(NULL
, 0, certHash
);
1747 CFDataAppend(concat
, issuerHash
);
1748 CFDataRef result
= SecSHA256DigestCreateFromData(NULL
, concat
);
1749 CFReleaseNull(concat
);
1753 static CF_RETURNS_RETAINED SecValidInfoRef
SecRevocationDbCacheRead(SecRevocationDbRef db
,
1754 SecCertificateRef certificate
,
1755 CFDataRef issuerHash
) {
1759 SecValidInfoRef result
= NULL
;
1760 if (!db
|| !db
->info_cache
|| !db
->info_cache_list
) {
1763 CFIndex ix
= kCFNotFound
;
1764 CFDataRef certHash
= SecCertificateCopySHA256Digest(certificate
);
1765 CFDataRef cacheKey
= createCacheKey(certHash
, issuerHash
);
1767 os_unfair_lock_lock(&db
->info_cache_lock
); // grab the cache lock before using the cache
1768 if (0 <= (ix
= CFArrayGetFirstIndexOfValue(db
->info_cache_list
,
1769 CFRangeMake(0, CFArrayGetCount(db
->info_cache_list
)),
1771 result
= (SecValidInfoRef
)CFDictionaryGetValue(db
->info_cache
, cacheKey
);
1772 // Verify this really is the right result
1773 if (CFEqualSafe(result
->certHash
, certHash
) && CFEqualSafe(result
->issuerHash
, issuerHash
)) {
1774 // Cache hit. Move the entry to the bottom of the list.
1775 CFArrayRemoveValueAtIndex(db
->info_cache_list
, ix
);
1776 CFArrayAppendValue(db
->info_cache_list
, cacheKey
);
1777 secdebug("validcache", "cache hit: %@", cacheKey
);
1779 // Just remove this bad entry
1780 CFArrayRemoveValueAtIndex(db
->info_cache_list
, ix
);
1781 CFDictionaryRemoveValue(db
->info_cache
, cacheKey
);
1782 secdebug("validcache", "cache remove bad: %@", cacheKey
);
1783 secnotice("validcache", "found a bad valid info cache entry at %ld", (long)ix
);
1786 CFRetainSafe(result
);
1787 os_unfair_lock_unlock(&db
->info_cache_lock
);
1788 CFReleaseSafe(certHash
);
1789 CFReleaseSafe(cacheKey
);
1793 static void SecRevocationDbCacheWrite(SecRevocationDbRef db
,
1794 SecValidInfoRef validInfo
) {
1795 if (!db
|| !validInfo
|| !db
->info_cache
|| !db
->info_cache_list
) {
1799 CFDataRef cacheKey
= createCacheKey(validInfo
->certHash
, validInfo
->issuerHash
);
1801 os_unfair_lock_lock(&db
->info_cache_lock
); // grab the cache lock before using the cache
1802 // check to make sure another thread didn't add this entry to the cache already
1803 if (0 > CFArrayGetFirstIndexOfValue(db
->info_cache_list
,
1804 CFRangeMake(0, CFArrayGetCount(db
->info_cache_list
)),
1806 CFDictionaryAddValue(db
->info_cache
, cacheKey
, validInfo
);
1807 if (kSecRevocationDbCacheSize
<= CFArrayGetCount(db
->info_cache_list
)) {
1808 // Remove least recently used cache entry.
1809 secdebug("validcache", "cache remove stale: %@", CFArrayGetValueAtIndex(db
->info_cache_list
, 0));
1810 CFDictionaryRemoveValue(db
->info_cache
, CFArrayGetValueAtIndex(db
->info_cache_list
, 0));
1811 CFArrayRemoveValueAtIndex(db
->info_cache_list
, 0);
1813 CFArrayAppendValue(db
->info_cache_list
, cacheKey
);
1814 secdebug("validcache", "cache add: %@", cacheKey
);
1816 os_unfair_lock_unlock(&db
->info_cache_lock
);
1817 CFReleaseNull(cacheKey
);
1820 static void SecRevocationDbCachePurge(SecRevocationDbRef db
) {
1821 if (!db
|| !db
->info_cache
|| !db
->info_cache_list
) {
1825 /* grab the cache lock and clear all entries */
1826 os_unfair_lock_lock(&db
->info_cache_lock
);
1827 CFArrayRemoveAllValues(db
->info_cache_list
);
1828 CFDictionaryRemoveAllValues(db
->info_cache
);
1829 secdebug("validcache", "cache purge");
1830 os_unfair_lock_unlock(&db
->info_cache_lock
);
1833 static int64_t _SecRevocationDbGetUpdateInterval(SecRevocationDbConnectionRef dbc
, CFErrorRef
*error
) {
1834 /* look up interval entry in admin table; returns -1 on error */
1835 __block
int64_t interval
= -1;
1836 __block
bool ok
= true;
1837 __block CFErrorRef localError
= NULL
;
1839 ok
= ok
&& SecDbWithSQL(dbc
->dbconn
, selectUpdateIntervalSQL
, &localError
, ^bool(sqlite3_stmt
*selectInterval
) {
1840 ok
= ok
&& SecDbStep(dbc
->dbconn
, selectInterval
, &localError
, ^void(bool *stop
) {
1841 interval
= sqlite3_column_int64(selectInterval
, 0);
1846 if (!ok
|| localError
) {
1847 secerror("_SecRevocationDbGetUpdateInterval failed: %@", localError
);
1848 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationRead
, TAFatalError
,
1849 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
1851 (void) CFErrorPropagate(localError
, error
);
1855 static bool _SecRevocationDbSetUpdateInterval(SecRevocationDbConnectionRef dbc
, int64_t interval
, CFErrorRef
*error
) {
1856 secdebug("validupdate", "setting interval to %lld", interval
);
1858 __block CFErrorRef localError
= NULL
;
1859 __block
bool ok
= (dbc
!= NULL
);
1860 ok
= ok
&& SecDbWithSQL(dbc
->dbconn
, insertAdminRecordSQL
, &localError
, ^bool(sqlite3_stmt
*insertInterval
) {
1861 const char *intervalKey
= "interval";
1862 ok
= ok
&& SecDbBindText(insertInterval
, 1, intervalKey
, strlen(intervalKey
),
1863 SQLITE_TRANSIENT
, &localError
);
1864 ok
= ok
&& SecDbBindInt64(insertInterval
, 2,
1865 (sqlite3_int64
)interval
, &localError
);
1866 ok
= ok
&& SecDbStep(dbc
->dbconn
, insertInterval
, &localError
, NULL
);
1869 if (!ok
|| localError
) {
1870 secerror("_SecRevocationDbSetUpdateInterval failed: %@", localError
);
1871 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationWrite
, TAFatalError
,
1872 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
1874 (void) CFErrorPropagate(localError
, error
);
1878 static CFArrayRef
_SecRevocationDbCopyHashes(SecRevocationDbConnectionRef dbc
, CFErrorRef
*error
) {
1879 /* return a retained copy of the db_hash array stored in the admin table; or NULL on error */
1880 __block CFMutableArrayRef hashes
= CFArrayCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeArrayCallBacks
);
1881 __block
bool ok
= (dbc
&& hashes
);
1882 __block CFErrorRef localError
= NULL
;
1884 ok
= ok
&& SecDbWithSQL(dbc
->dbconn
, selectDbHashSQL
, &localError
, ^bool(sqlite3_stmt
*selectDbHash
) {
1885 ok
= ok
&& SecDbStep(dbc
->dbconn
, selectDbHash
, &localError
, ^void(bool *stop
) {
1886 uint8_t *p
= (uint8_t *)sqlite3_column_blob(selectDbHash
, 0);
1887 uint64_t len
= sqlite3_column_bytes(selectDbHash
, 0);
1888 CFIndex hashLen
= CC_SHA256_DIGEST_LENGTH
;
1889 while (p
&& len
>= (uint64_t)hashLen
) {
1890 CFDataRef hash
= CFDataCreate(kCFAllocatorDefault
, (const UInt8
*)p
, hashLen
);
1892 CFArrayAppendValue(hashes
, hash
);
1893 CFReleaseNull(hash
);
1902 if (!ok
|| localError
) {
1903 CFReleaseNull(hashes
);
1905 (void) CFErrorPropagate(localError
, error
);
1909 static bool _SecRevocationDbSetHashes(SecRevocationDbConnectionRef dbc
, CFArrayRef hashes
, CFErrorRef
*error
) {
1910 /* flatten and store db_hash array in the admin table */
1911 __block CFErrorRef localError
= NULL
;
1912 __block
bool ok
= (dbc
&& hashes
);
1914 ok
= ok
&& SecDbWithSQL(dbc
->dbconn
, insertAdminRecordSQL
, &localError
, ^bool(sqlite3_stmt
*insertHashes
) {
1915 CFIndex count
= CFArrayGetCount(hashes
);
1916 CFIndex hashLen
= CC_SHA256_DIGEST_LENGTH
, dataLen
= hashLen
* count
;
1917 uint8_t *dataPtr
= (uint8_t *)calloc(dataLen
, 1);
1918 uint8_t *p
= dataPtr
;
1919 for (CFIndex idx
= 0; idx
< count
&& p
; idx
++) {
1920 CFDataRef hash
= CFArrayGetValueAtIndex(hashes
, idx
);
1921 uint8_t *h
= (hash
) ? (uint8_t *)CFDataGetBytePtr(hash
) : NULL
;
1922 if (h
&& CFDataGetLength(hash
) == hashLen
) { memcpy(p
, h
, hashLen
); }
1925 const char *hashKey
= "db_hash";
1926 ok
= ok
&& SecDbBindText(insertHashes
, 1, hashKey
, strlen(hashKey
),
1927 SQLITE_TRANSIENT
, &localError
);
1928 ok
= ok
&& SecDbBindInt64(insertHashes
, 2,
1929 (sqlite3_int64
)0, &localError
);
1930 ok
= ok
&& SecDbBindBlob(insertHashes
, 3,
1932 SQLITE_TRANSIENT
, &localError
);
1933 ok
= ok
&& SecDbStep(dbc
->dbconn
, insertHashes
, &localError
, NULL
);
1937 if (!ok
|| localError
) {
1939 (void) CFErrorPropagate(localError
, error
);
1943 static int64_t _SecRevocationDbGetVersion(SecRevocationDbConnectionRef dbc
, CFErrorRef
*error
) {
1944 /* look up version entry in admin table; returns -1 on error */
1945 __block
int64_t version
= -1;
1946 __block
bool ok
= (dbc
!= NULL
);
1947 __block CFErrorRef localError
= NULL
;
1949 ok
= ok
&& SecDbWithSQL(dbc
->dbconn
, selectVersionSQL
, &localError
, ^bool(sqlite3_stmt
*selectVersion
) {
1950 ok
= ok
&& SecDbStep(dbc
->dbconn
, selectVersion
, &localError
, ^void(bool *stop
) {
1951 version
= sqlite3_column_int64(selectVersion
, 0);
1956 if (!ok
|| localError
) {
1957 secerror("_SecRevocationDbGetVersion failed: %@", localError
);
1958 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationRead
, TAFatalError
,
1959 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
1961 (void) CFErrorPropagate(localError
, error
);
1965 static bool _SecRevocationDbSetVersion(SecRevocationDbConnectionRef dbc
, CFIndex version
, CFErrorRef
*error
) {
1966 secdebug("validupdate", "setting version to %ld", (long)version
);
1968 __block CFErrorRef localError
= NULL
;
1969 __block
bool ok
= (dbc
!= NULL
);
1970 ok
= ok
&& SecDbWithSQL(dbc
->dbconn
, insertAdminRecordSQL
, &localError
, ^bool(sqlite3_stmt
*insertVersion
) {
1971 const char *versionKey
= "version";
1972 ok
= ok
&& SecDbBindText(insertVersion
, 1, versionKey
, strlen(versionKey
),
1973 SQLITE_TRANSIENT
, &localError
);
1974 ok
= ok
&& SecDbBindInt64(insertVersion
, 2,
1975 (sqlite3_int64
)version
, &localError
);
1976 ok
= ok
&& SecDbStep(dbc
->dbconn
, insertVersion
, &localError
, NULL
);
1979 if (!ok
|| localError
) {
1980 secerror("_SecRevocationDbSetVersion failed: %@", localError
);
1981 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationWrite
, TAFatalError
,
1982 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
1984 (void) CFErrorPropagate(localError
, error
);
1988 static int64_t _SecRevocationDbReadSchemaVersionFromDb(SecRevocationDbConnectionRef dbc
, CFErrorRef
*error
) {
1989 /* look up db_version entry in admin table; returns -1 on error */
1990 __block
int64_t db_version
= -1;
1991 __block
bool ok
= (dbc
!= NULL
);
1992 __block CFErrorRef localError
= NULL
;
1994 ok
= ok
&& SecDbWithSQL(dbc
->dbconn
, selectDbVersionSQL
, &localError
, ^bool(sqlite3_stmt
*selectDbVersion
) {
1995 ok
= ok
&& SecDbStep(dbc
->dbconn
, selectDbVersion
, &localError
, ^void(bool *stop
) {
1996 db_version
= sqlite3_column_int64(selectDbVersion
, 0);
2001 if (!ok
|| localError
) {
2002 secerror("_SecRevocationDbReadSchemaVersionFromDb failed: %@", localError
);
2003 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationRead
, TAFatalError
,
2004 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
2006 (void) CFErrorPropagate(localError
, error
);
2010 static _Atomic
int64_t gSchemaVersion
= -1;
2011 static int64_t _SecRevocationDbGetSchemaVersion(SecRevocationDbRef rdb
, SecRevocationDbConnectionRef dbc
, CFErrorRef
*error
) {
2012 static dispatch_once_t onceToken
;
2013 dispatch_once(&onceToken
, ^{
2015 atomic_init(&gSchemaVersion
, _SecRevocationDbReadSchemaVersionFromDb(dbc
, error
));
2017 (void) SecRevocationDbPerformRead(rdb
, error
, ^bool(SecRevocationDbConnectionRef dbc
, CFErrorRef
*blockError
) {
2018 atomic_init(&gSchemaVersion
, _SecRevocationDbReadSchemaVersionFromDb(dbc
, blockError
));
2023 if (atomic_load(&gSchemaVersion
) == -1) {
2024 /* Initial read(s) failed. Try to read the schema version again. */
2026 atomic_store(&gSchemaVersion
, _SecRevocationDbReadSchemaVersionFromDb(dbc
, error
));
2028 (void) SecRevocationDbPerformRead(rdb
, error
, ^bool(SecRevocationDbConnectionRef dbc
, CFErrorRef
*blockError
) {
2029 atomic_store(&gSchemaVersion
, _SecRevocationDbReadSchemaVersionFromDb(dbc
, blockError
));
2034 return atomic_load(&gSchemaVersion
);
2037 static void SecRevocationDbResetCaches(void) {
2038 SecRevocationDbWith(^(SecRevocationDbRef db
) {
2039 db
->unsupportedVersion
= false;
2040 db
->changed
= false;
2041 (void) SecRevocationDbPerformRead(db
, NULL
, ^bool(SecRevocationDbConnectionRef dbc
, CFErrorRef
*blockError
) {
2042 atomic_store(&gSchemaVersion
, _SecRevocationDbReadSchemaVersionFromDb(dbc
, blockError
));
2045 SecRevocationDbCachePurge(db
);
2049 static bool _SecRevocationDbSetSchemaVersion(SecRevocationDbConnectionRef dbc
, CFIndex dbversion
, CFErrorRef
*error
) {
2050 if (dbversion
> 0) {
2051 int64_t db_version
= (dbc
) ? dbc
->precommitDbVersion
: -1;
2052 if (db_version
>= dbversion
) {
2053 return true; /* requested schema is earlier than current schema */
2056 secdebug("validupdate", "setting db_version to %ld", (long)dbversion
);
2058 __block CFErrorRef localError
= NULL
;
2059 __block
bool ok
= (dbc
!= NULL
);
2060 ok
= ok
&& SecDbWithSQL(dbc
->dbconn
, insertAdminRecordSQL
, &localError
, ^bool(sqlite3_stmt
*insertDbVersion
) {
2061 const char *dbVersionKey
= "db_version";
2062 ok
= ok
&& SecDbBindText(insertDbVersion
, 1, dbVersionKey
, strlen(dbVersionKey
),
2063 SQLITE_TRANSIENT
, &localError
);
2064 ok
= ok
&& SecDbBindInt64(insertDbVersion
, 2,
2065 (sqlite3_int64
)dbversion
, &localError
);
2066 ok
= ok
&& SecDbStep(dbc
->dbconn
, insertDbVersion
, &localError
, NULL
);
2069 if (!ok
|| localError
) {
2070 secerror("_SecRevocationDbSetSchemaVersion failed: %@", localError
);
2071 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationWrite
, TAFatalError
,
2072 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
2074 dbc
->db
->changed
= true; /* will notify clients of this change */
2075 dbc
->db
->unsupportedVersion
= false;
2076 dbc
->precommitDbVersion
= dbversion
;
2077 atomic_store(&gSchemaVersion
, (int64_t)dbversion
);
2079 CFReleaseSafe(localError
);
2083 static bool _SecRevocationDbUpdateSchema(SecRevocationDbConnectionRef dbc
, CFErrorRef
*error
) {
2084 __block CFErrorRef localError
= NULL
;
2085 __block
bool ok
= (dbc
!= NULL
);
2086 __block
int64_t db_version
= (dbc
) ? dbc
->precommitDbVersion
: 0;
2087 if (db_version
>= kSecRevocationDbSchemaVersion
) {
2088 return ok
; /* schema version already up to date */
2090 secdebug("validupdate", "updating db schema from v%lld to v%lld",
2091 (long long)db_version
, (long long)kSecRevocationDbSchemaVersion
);
2093 if (ok
&& db_version
< 5) {
2094 /* apply v5 changes (add dates table and replace trigger) */
2095 ok
&= SecDbWithSQL(dbc
->dbconn
, updateConstraintsTablesSQL
, &localError
, ^bool(sqlite3_stmt
*updateTables
) {
2096 ok
= SecDbStep(dbc
->dbconn
, updateTables
, &localError
, NULL
);
2099 ok
&= SecDbWithSQL(dbc
->dbconn
, updateGroupDeleteTriggerSQL
, &localError
, ^bool(sqlite3_stmt
*updateTrigger
) {
2100 ok
= SecDbStep(dbc
->dbconn
, updateTrigger
, &localError
, NULL
);
2103 secdebug("validupdate", "applied schema update to v5 (%s)", (ok
) ? "ok" : "failed!");
2105 if (ok
&& db_version
< 6) {
2106 /* apply v6 changes (the SecDb layer will update autovacuum mode if needed, so we don't execute
2107 any SQL here, but we do want the database to be replaced in case transaction scope problems
2108 with earlier versions caused missing entries.) */
2109 secdebug("validupdate", "applied schema update to v6 (%s)", (ok
) ? "ok" : "failed!");
2110 if (db_version
> 0) {
2111 SecValidUpdateForceReplaceDatabase();
2114 if (ok
&& db_version
< 7) {
2115 /* apply v7 changes (add policies column in groups table) */
2116 ok
&= SecDbWithSQL(dbc
->dbconn
, addPoliciesColumnSQL
, &localError
, ^bool(sqlite3_stmt
*addPoliciesColumn
) {
2117 ok
= SecDbStep(dbc
->dbconn
, addPoliciesColumn
, &localError
, NULL
);
2120 secdebug("validupdate", "applied schema update to v7 (%s)", (ok
) ? "ok" : "failed!");
2124 secerror("_SecRevocationDbUpdateSchema failed: %@", localError
);
2126 ok
= ok
&& _SecRevocationDbSetSchemaVersion(dbc
, kSecRevocationDbSchemaVersion
, &localError
);
2128 (void) CFErrorPropagate(localError
, error
);
2132 bool SecRevocationDbUpdateSchema(SecRevocationDbRef rdb
) {
2133 /* note: this function assumes it is called only by the database owner.
2134 non-owner (read-only) clients will fail if changes to the db are needed. */
2135 if (!rdb
|| !rdb
->db
) {
2138 __block
bool ok
= true;
2139 __block CFErrorRef localError
= NULL
;
2140 ok
&= SecRevocationDbPerformWrite(rdb
, &localError
, ^bool(SecRevocationDbConnectionRef dbc
, CFErrorRef
*blockError
) {
2141 return _SecRevocationDbUpdateSchema(dbc
, blockError
);
2143 CFReleaseSafe(localError
);
2147 static int64_t _SecRevocationDbGetUpdateFormat(SecRevocationDbConnectionRef dbc
, CFErrorRef
*error
) {
2148 /* look up db_format entry in admin table; returns -1 on error */
2149 __block
int64_t db_format
= -1;
2150 __block
bool ok
= (dbc
!= NULL
);
2151 __block CFErrorRef localError
= NULL
;
2153 ok
= ok
&& SecDbWithSQL(dbc
->dbconn
, selectDbFormatSQL
, &localError
, ^bool(sqlite3_stmt
*selectDbFormat
) {
2154 ok
&= SecDbStep(dbc
->dbconn
, selectDbFormat
, &localError
, ^void(bool *stop
) {
2155 db_format
= sqlite3_column_int64(selectDbFormat
, 0);
2160 if (!ok
|| localError
) {
2161 secerror("_SecRevocationDbGetUpdateFormat failed: %@", localError
);
2162 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationRead
, TAFatalError
,
2163 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
2165 (void) CFErrorPropagate(localError
, error
);
2169 static bool _SecRevocationDbSetUpdateFormat(SecRevocationDbConnectionRef dbc
, CFIndex dbformat
, CFErrorRef
*error
) {
2170 secdebug("validupdate", "setting db_format to %ld", (long)dbformat
);
2172 __block CFErrorRef localError
= NULL
;
2173 __block
bool ok
= (dbc
!= NULL
);
2174 ok
= ok
&& SecDbWithSQL(dbc
->dbconn
, insertAdminRecordSQL
, &localError
, ^bool(sqlite3_stmt
*insertDbFormat
) {
2175 const char *dbFormatKey
= "db_format";
2176 ok
= ok
&& SecDbBindText(insertDbFormat
, 1, dbFormatKey
, strlen(dbFormatKey
),
2177 SQLITE_TRANSIENT
, &localError
);
2178 ok
= ok
&& SecDbBindInt64(insertDbFormat
, 2,
2179 (sqlite3_int64
)dbformat
, &localError
);
2180 ok
= ok
&& SecDbStep(dbc
->dbconn
, insertDbFormat
, &localError
, NULL
);
2183 if (!ok
|| localError
) {
2184 secerror("_SecRevocationDbSetUpdateFormat failed: %@", localError
);
2185 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationWrite
, TAFatalError
,
2186 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
2188 dbc
->db
->changed
= true; /* will notify clients of this change */
2189 dbc
->db
->unsupportedVersion
= false;
2191 (void) CFErrorPropagate(localError
, error
);
2195 static CFStringRef
_SecRevocationDbCopyUpdateSource(SecRevocationDbConnectionRef dbc
, CFErrorRef
*error
) {
2196 /* look up db_source entry in admin table; returns NULL on error */
2197 __block CFStringRef updateSource
= NULL
;
2198 __block
bool ok
= (dbc
!= NULL
);
2199 __block CFErrorRef localError
= NULL
;
2201 ok
= ok
&& SecDbWithSQL(dbc
->dbconn
, selectDbSourceSQL
, &localError
, ^bool(sqlite3_stmt
*selectDbSource
) {
2202 ok
&= SecDbStep(dbc
->dbconn
, selectDbSource
, &localError
, ^void(bool *stop
) {
2203 const UInt8
*p
= (const UInt8
*)sqlite3_column_blob(selectDbSource
, 0);
2205 CFIndex length
= (CFIndex
)sqlite3_column_bytes(selectDbSource
, 0);
2207 updateSource
= CFStringCreateWithBytes(kCFAllocatorDefault
, p
, length
, kCFStringEncodingUTF8
, false);
2214 if (!ok
|| localError
) {
2215 secerror("_SecRevocationDbCopyUpdateSource failed: %@", localError
);
2216 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationRead
, TAFatalError
,
2217 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
2219 (void) CFErrorPropagate(localError
, error
);
2220 return updateSource
;
2223 bool _SecRevocationDbSetUpdateSource(SecRevocationDbConnectionRef dbc
, CFStringRef updateSource
, CFErrorRef
*error
) {
2224 if (!updateSource
) {
2225 secerror("_SecRevocationDbSetUpdateSource failed: %d", errSecParam
);
2228 __block
char buffer
[256];
2229 __block
const char *updateSourceCStr
= CFStringGetCStringPtr(updateSource
, kCFStringEncodingUTF8
);
2230 if (!updateSourceCStr
) {
2231 if (CFStringGetCString(updateSource
, buffer
, 256, kCFStringEncodingUTF8
)) {
2232 updateSourceCStr
= buffer
;
2235 if (!updateSourceCStr
) {
2236 secerror("_SecRevocationDbSetUpdateSource failed: unable to get UTF-8 encoding");
2239 secdebug("validupdate", "setting update source to \"%s\"", updateSourceCStr
);
2241 __block CFErrorRef localError
= NULL
;
2242 __block
bool ok
= (dbc
!= NULL
);
2243 ok
= ok
&& SecDbWithSQL(dbc
->dbconn
, insertAdminRecordSQL
, &localError
, ^bool(sqlite3_stmt
*insertRecord
) {
2244 const char *dbSourceKey
= "db_source";
2245 ok
= ok
&& SecDbBindText(insertRecord
, 1, dbSourceKey
, strlen(dbSourceKey
),
2246 SQLITE_TRANSIENT
, &localError
);
2247 ok
= ok
&& SecDbBindInt64(insertRecord
, 2,
2248 (sqlite3_int64
)0, &localError
);
2249 ok
= ok
&& SecDbBindBlob(insertRecord
, 3,
2250 updateSourceCStr
, strlen(updateSourceCStr
),
2251 SQLITE_TRANSIENT
, &localError
);
2252 ok
= ok
&& SecDbStep(dbc
->dbconn
, insertRecord
, &localError
, NULL
);
2255 if (!ok
|| localError
) {
2256 secerror("_SecRevocationDbSetUpdateSource failed: %@", localError
);
2257 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationWrite
, TAFatalError
,
2258 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
2260 (void) CFErrorPropagate(localError
, error
);
2261 CFReleaseSafe(localError
);
2265 bool SecRevocationDbSetUpdateSource(SecRevocationDbRef rdb
, CFStringRef updateSource
) {
2266 /* note: this function assumes it is called only by the database owner.
2267 non-owner (read-only) clients will fail if changes to the db are needed. */
2268 if (!rdb
|| !rdb
->db
) {
2271 CFErrorRef localError
= NULL
;
2273 ok
&= SecRevocationDbPerformWrite(rdb
, &localError
, ^bool(SecRevocationDbConnectionRef dbc
, CFErrorRef
*error
) {
2274 return _SecRevocationDbSetUpdateSource(dbc
, updateSource
, error
);
2276 CFReleaseSafe(localError
);
2280 static CFAbsoluteTime
_SecRevocationDbGetNextUpdateTime(SecRevocationDbConnectionRef dbc
, CFErrorRef
*error
) {
2281 /* look up check_again entry in admin table; returns 0 on error */
2282 __block CFAbsoluteTime nextUpdate
= 0;
2283 __block
bool ok
= (dbc
!= NULL
);
2284 __block CFErrorRef localError
= NULL
;
2286 ok
= ok
&& SecDbWithSQL(dbc
->dbconn
, selectNextUpdateSQL
, &localError
, ^bool(sqlite3_stmt
*selectNextUpdate
) {
2287 ok
&= SecDbStep(dbc
->dbconn
, selectNextUpdate
, &localError
, ^void(bool *stop
) {
2288 CFAbsoluteTime
*p
= (CFAbsoluteTime
*)sqlite3_column_blob(selectNextUpdate
, 0);
2290 if (sizeof(CFAbsoluteTime
) == sqlite3_column_bytes(selectNextUpdate
, 0)) {
2298 if (!ok
|| localError
) {
2299 secerror("_SecRevocationDbGetNextUpdateTime failed: %@", localError
);
2300 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationRead
, TAFatalError
,
2301 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
2303 (void) CFErrorPropagate(localError
, error
);
2307 static bool _SecRevocationDbSetNextUpdateTime(SecRevocationDbConnectionRef dbc
, CFAbsoluteTime nextUpdate
, CFErrorRef
*error
){
2308 secdebug("validupdate", "setting next update to %f", (double)nextUpdate
);
2310 __block CFErrorRef localError
= NULL
;
2311 __block
bool ok
= (dbc
!= NULL
);
2312 ok
= ok
&& SecDbWithSQL(dbc
->dbconn
, insertAdminRecordSQL
, &localError
, ^bool(sqlite3_stmt
*insertRecord
) {
2313 const char *nextUpdateKey
= "check_again";
2314 ok
= ok
&& SecDbBindText(insertRecord
, 1, nextUpdateKey
, strlen(nextUpdateKey
),
2315 SQLITE_TRANSIENT
, &localError
);
2316 ok
= ok
&& SecDbBindInt64(insertRecord
, 2,
2317 (sqlite3_int64
)0, &localError
);
2318 ok
= ok
&& SecDbBindBlob(insertRecord
, 3,
2319 &nextUpdate
, sizeof(CFAbsoluteTime
),
2320 SQLITE_TRANSIENT
, &localError
);
2321 ok
= ok
&& SecDbStep(dbc
->dbconn
, insertRecord
, &localError
, NULL
);
2324 if (!ok
|| localError
) {
2325 secerror("_SecRevocationDbSetNextUpdate failed: %@", localError
);
2326 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationWrite
, TAFatalError
,
2327 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
2329 (void) CFErrorPropagate(localError
, error
);
2333 bool _SecRevocationDbRemoveAllEntries(SecRevocationDbConnectionRef dbc
, CFErrorRef
*error
) {
2334 /* clear out the contents of the database and start fresh */
2335 bool ok
= (dbc
!= NULL
);
2336 CFErrorRef localError
= NULL
;
2338 /* _SecRevocationDbUpdateSchema was called when db was opened, so no need to do it again. */
2340 /* delete all entries */
2341 ok
= ok
&& SecDbExec(dbc
->dbconn
, deleteAllEntriesSQL
, &localError
);
2342 secnotice("validupdate", "resetting database, result: %d (expected 1)", (ok
) ? 1 : 0);
2344 /* one more thing: update the schema version and format to current */
2345 ok
= ok
&& _SecRevocationDbSetSchemaVersion(dbc
, kSecRevocationDbSchemaVersion
, &localError
);
2346 ok
= ok
&& _SecRevocationDbSetUpdateFormat(dbc
, kSecRevocationDbUpdateFormat
, &localError
);
2348 if (!ok
|| localError
) {
2349 secerror("_SecRevocationDbRemoveAllEntries failed: %@", localError
);
2350 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationWrite
, TAFatalError
,
2351 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
2353 (void) CFErrorPropagate(localError
, error
);
2357 static bool _SecRevocationDbUpdateIssuers(SecRevocationDbConnectionRef dbc
, int64_t groupId
, CFArrayRef issuers
, CFErrorRef
*error
) {
2358 /* insert or replace issuer records in issuers table */
2359 if (!issuers
|| groupId
< 0) {
2360 return false; /* must have something to insert, and a group to associate with it */
2362 __block
bool ok
= (dbc
!= NULL
);
2363 __block CFErrorRef localError
= NULL
;
2364 if (isArray(issuers
)) {
2365 CFIndex issuerIX
, issuerCount
= CFArrayGetCount(issuers
);
2366 for (issuerIX
=0; issuerIX
<issuerCount
&& ok
; issuerIX
++) {
2367 CFDataRef hash
= (CFDataRef
)CFArrayGetValueAtIndex(issuers
, issuerIX
);
2368 if (!hash
) { continue; }
2369 ok
= ok
&& SecDbWithSQL(dbc
->dbconn
, insertIssuerRecordSQL
, &localError
, ^bool(sqlite3_stmt
*insertIssuer
) {
2370 ok
= ok
&& SecDbBindInt64(insertIssuer
, 1,
2371 groupId
, &localError
);
2372 ok
= ok
&& SecDbBindBlob(insertIssuer
, 2,
2373 CFDataGetBytePtr(hash
),
2374 CFDataGetLength(hash
),
2375 SQLITE_TRANSIENT
, &localError
);
2376 /* Execute the insert statement for this issuer record. */
2377 ok
= ok
&& SecDbStep(dbc
->dbconn
, insertIssuer
, &localError
, NULL
);
2382 if (!ok
|| localError
) {
2383 secerror("_SecRevocationDbUpdateIssuers failed: %@", localError
);
2384 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationWrite
, TAFatalError
,
2385 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
2387 (void) CFErrorPropagate(localError
, error
);
2391 static SecValidInfoFormat
_SecRevocationDbGetGroupFormatForData(SecRevocationDbConnectionRef dbc
, int64_t groupId
, CFDataRef data
) {
2392 /* determine existing format if groupId is supplied and this is a partial update,
2393 otherwise return the expected format for the given data. */
2394 SecValidInfoFormat format
= kSecValidInfoFormatUnknown
;
2395 if (groupId
>= 0 && !dbc
->fullUpdate
) {
2396 format
= _SecRevocationDbGetGroupFormat(dbc
, groupId
, NULL
, NULL
, NULL
, NULL
);
2398 if (format
== kSecValidInfoFormatUnknown
&& data
!= NULL
) {
2399 /* group doesn't exist, so determine format based on length of specified data.
2400 len <= 20 is a serial number (actually, <=37, but != 32.)
2401 len==32 is a sha256 hash. otherwise: nto1. */
2402 CFIndex length
= CFDataGetLength(data
);
2404 format
= kSecValidInfoFormatSHA256
;
2405 } else if (length
<= 37) {
2406 format
= kSecValidInfoFormatSerial
;
2407 } else if (length
> 0) {
2408 format
= kSecValidInfoFormatNto1
;
2414 static bool _SecRevocationDbUpdateIssuerData(SecRevocationDbConnectionRef dbc
, int64_t groupId
, CFDictionaryRef dict
, CFErrorRef
*error
) {
2415 /* update/delete records in serials or hashes table. */
2416 if (!dict
|| groupId
< 0) {
2417 return false; /* must have something to insert, and a group to associate with it */
2419 __block
bool ok
= (dbc
!= NULL
);
2420 __block CFErrorRef localError
= NULL
;
2421 /* process deletions */
2422 CFArrayRef deleteArray
= (CFArrayRef
)CFDictionaryGetValue(dict
, CFSTR("delete"));
2423 if (isArray(deleteArray
)) {
2424 SecValidInfoFormat format
= kSecValidInfoFormatUnknown
;
2425 CFIndex processed
=0, identifierIX
, identifierCount
= CFArrayGetCount(deleteArray
);
2426 for (identifierIX
=0; identifierIX
<identifierCount
; identifierIX
++) {
2427 CFDataRef identifierData
= (CFDataRef
)CFArrayGetValueAtIndex(deleteArray
, identifierIX
);
2428 if (!identifierData
) { continue; }
2429 if (format
== kSecValidInfoFormatUnknown
) {
2430 format
= _SecRevocationDbGetGroupFormatForData(dbc
, groupId
, identifierData
);
2432 CFStringRef sql
= NULL
;
2433 if (format
== kSecValidInfoFormatSerial
) {
2434 sql
= deleteSerialRecordSQL
;
2435 } else if (format
== kSecValidInfoFormatSHA256
) {
2436 sql
= deleteSha256RecordSQL
;
2438 if (!sql
) { continue; }
2440 ok
= ok
&& SecDbWithSQL(dbc
->dbconn
, sql
, &localError
, ^bool(sqlite3_stmt
*deleteIdentifier
) {
2441 /* (groupid,serial|sha256) */
2442 CFDataRef hexData
= cfToHexData(identifierData
, true);
2443 if (!hexData
) { return false; }
2444 ok
= ok
&& SecDbBindInt64(deleteIdentifier
, 1,
2445 groupId
, &localError
);
2446 ok
= ok
&& SecDbBindBlob(deleteIdentifier
, 2,
2447 CFDataGetBytePtr(hexData
),
2448 CFDataGetLength(hexData
),
2449 SQLITE_TRANSIENT
, &localError
);
2450 /* Execute the delete statement for the identifier record. */
2451 ok
= ok
&& SecDbStep(dbc
->dbconn
, deleteIdentifier
, &localError
, NULL
);
2452 CFReleaseSafe(hexData
);
2455 if (ok
) { ++processed
; }
2458 secdebug("validupdate", "Processed %ld of %ld deletions for group %lld, result=%s",
2459 processed
, identifierCount
, groupId
, (ok
) ? "true" : "false");
2462 /* process additions */
2463 CFArrayRef addArray
= (CFArrayRef
)CFDictionaryGetValue(dict
, CFSTR("add"));
2464 if (isArray(addArray
)) {
2465 SecValidInfoFormat format
= kSecValidInfoFormatUnknown
;
2466 CFIndex processed
=0, identifierIX
, identifierCount
= CFArrayGetCount(addArray
);
2467 for (identifierIX
=0; identifierIX
<identifierCount
; identifierIX
++) {
2468 CFDataRef identifierData
= (CFDataRef
)CFArrayGetValueAtIndex(addArray
, identifierIX
);
2469 if (!identifierData
) { continue; }
2470 if (format
== kSecValidInfoFormatUnknown
) {
2471 format
= _SecRevocationDbGetGroupFormatForData(dbc
, groupId
, identifierData
);
2473 CFStringRef sql
= NULL
;
2474 if (format
== kSecValidInfoFormatSerial
) {
2475 sql
= insertSerialRecordSQL
;
2476 } else if (format
== kSecValidInfoFormatSHA256
) {
2477 sql
= insertSha256RecordSQL
;
2479 if (!sql
) { continue; }
2481 ok
= ok
&& SecDbWithSQL(dbc
->dbconn
, sql
, &localError
, ^bool(sqlite3_stmt
*insertIdentifier
) {
2482 /* rowid,(groupid,serial|sha256) */
2483 /* rowid is autoincremented and we never set it directly */
2484 ok
= ok
&& SecDbBindInt64(insertIdentifier
, 1,
2485 groupId
, &localError
);
2486 ok
= ok
&& SecDbBindBlob(insertIdentifier
, 2,
2487 CFDataGetBytePtr(identifierData
),
2488 CFDataGetLength(identifierData
),
2489 SQLITE_TRANSIENT
, &localError
);
2490 /* Execute the insert statement for the identifier record. */
2491 ok
= ok
&& SecDbStep(dbc
->dbconn
, insertIdentifier
, &localError
, NULL
);
2494 if (ok
) { ++processed
; }
2497 secdebug("validupdate", "Processed %ld of %ld additions for group %lld, result=%s",
2498 processed
, identifierCount
, groupId
, (ok
) ? "true" : "false");
2501 if (!ok
|| localError
) {
2502 secerror("_SecRevocationDbUpdatePerIssuerData failed: %@", localError
);
2503 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationWrite
, TAFatalError
,
2504 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
2506 (void) CFErrorPropagate(localError
, error
);
2510 static bool _SecRevocationDbCopyDateConstraints(SecRevocationDbConnectionRef dbc
,
2511 int64_t groupId
, CFDateRef
*notBeforeDate
, CFDateRef
*notAfterDate
, CFErrorRef
*error
) {
2512 /* return true if one or both date constraints exist for a given groupId.
2513 the actual constraints are optionally returned in output CFDateRef parameters.
2514 caller is responsible for releasing date and error parameters, if provided.
2516 __block
bool ok
= (dbc
!= NULL
);
2517 __block CFDateRef localNotBefore
= NULL
;
2518 __block CFDateRef localNotAfter
= NULL
;
2519 __block CFErrorRef localError
= NULL
;
2521 ok
= ok
&& SecDbWithSQL(dbc
->dbconn
, selectDateRecordSQL
, &localError
, ^bool(sqlite3_stmt
*selectDates
) {
2522 /* (groupid,notbefore,notafter) */
2523 ok
&= SecDbBindInt64(selectDates
, 1, groupId
, &localError
);
2524 ok
= ok
&& SecDbStep(dbc
->dbconn
, selectDates
, &localError
, ^(bool *stop
) {
2525 /* if column has no value, its type will be SQLITE_NULL */
2526 if (SQLITE_NULL
!= sqlite3_column_type(selectDates
, 0)) {
2527 CFAbsoluteTime nb
= (CFAbsoluteTime
)sqlite3_column_double(selectDates
, 0);
2528 localNotBefore
= CFDateCreate(NULL
, nb
);
2530 if (SQLITE_NULL
!= sqlite3_column_type(selectDates
, 1)) {
2531 CFAbsoluteTime na
= (CFAbsoluteTime
)sqlite3_column_double(selectDates
, 1);
2532 localNotAfter
= CFDateCreate(NULL
, na
);
2537 /* must have at least one date constraint to return true.
2538 since date constraints are optional, not finding any should not log an error. */
2539 ok
= ok
&& !localError
&& (localNotBefore
!= NULL
|| localNotAfter
!= NULL
);
2541 secerror("_SecRevocationDbCopyDateConstraints failed: %@", localError
);
2542 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationRead
, TAFatalError
,
2543 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
2546 CFReleaseNull(localNotBefore
);
2547 CFReleaseNull(localNotAfter
);
2549 if (notBeforeDate
) {
2550 *notBeforeDate
= localNotBefore
;
2552 CFReleaseSafe(localNotBefore
);
2555 *notAfterDate
= localNotAfter
;
2557 CFReleaseSafe(localNotAfter
);
2560 (void) CFErrorPropagate(localError
, error
);
2564 static bool _SecRevocationDbUpdateDateConstraints(SecRevocationDbConnectionRef dbc
, int64_t groupId
, CFDictionaryRef dict
, CFErrorRef
*error
) {
2565 /* Called only from _SecRevocationDbUpdateIssuerConstraints.
2566 Function assumes that the caller has checked the input arguments.
2568 __block
bool ok
= true;
2569 __block CFErrorRef localError
= NULL
;
2570 __block CFAbsoluteTime notBefore
= -3155760000.0; /* default: 1901-01-01 00:00:00-0000 */
2571 __block CFAbsoluteTime notAfter
= 31556908800.0; /* default: 3001-01-01 00:00:00-0000 */
2573 CFDateRef notBeforeDate
= (CFDateRef
)CFDictionaryGetValue(dict
, CFSTR("not-before"));
2574 CFDateRef notAfterDate
= (CFDateRef
)CFDictionaryGetValue(dict
, CFSTR("not-after"));
2576 if (isDate(notBeforeDate
)) {
2577 notBefore
= CFDateGetAbsoluteTime(notBeforeDate
);
2579 notBeforeDate
= NULL
;
2581 if (isDate(notAfterDate
)) {
2582 notAfter
= CFDateGetAbsoluteTime(notAfterDate
);
2584 notAfterDate
= NULL
;
2586 if (!(notBeforeDate
|| notAfterDate
)) {
2587 return ok
; /* no dates supplied, so we have nothing to update for this issuer */
2590 if (!(notBeforeDate
&& notAfterDate
) && !dbc
->fullUpdate
) {
2591 /* only one date was supplied, so check for existing date constraints */
2592 CFDateRef curNotBeforeDate
= NULL
;
2593 CFDateRef curNotAfterDate
= NULL
;
2594 if (_SecRevocationDbCopyDateConstraints(dbc
, groupId
, &curNotBeforeDate
,
2595 &curNotAfterDate
, &localError
)) {
2596 if (!notBeforeDate
) {
2597 notBeforeDate
= curNotBeforeDate
;
2598 notBefore
= CFDateGetAbsoluteTime(notBeforeDate
);
2600 CFReleaseSafe(curNotBeforeDate
);
2602 if (!notAfterDate
) {
2603 notAfterDate
= curNotAfterDate
;
2604 notAfter
= CFDateGetAbsoluteTime(notAfterDate
);
2606 CFReleaseSafe(curNotAfterDate
);
2610 ok
= ok
&& SecDbWithSQL(dbc
->dbconn
, insertDateRecordSQL
, &localError
, ^bool(sqlite3_stmt
*insertDate
) {
2611 /* (groupid,notbefore,notafter) */
2612 ok
= ok
&& SecDbBindInt64(insertDate
, 1, groupId
, &localError
);
2613 ok
= ok
&& SecDbBindDouble(insertDate
, 2, notBefore
, &localError
);
2614 ok
= ok
&& SecDbBindDouble(insertDate
, 3, notAfter
, &localError
);
2615 ok
= ok
&& SecDbStep(dbc
->dbconn
, insertDate
, &localError
, NULL
);
2619 if (!ok
|| localError
) {
2620 secinfo("validupdate", "_SecRevocationDbUpdateDateConstraints failed (ok=%s, localError=%@)",
2621 (ok
) ? "1" : "0", localError
);
2622 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationWrite
, TAFatalError
,
2623 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
2625 (void) CFErrorPropagate(localError
, error
);
2629 static bool _SecRevocationDbUpdatePolicyConstraints(SecRevocationDbConnectionRef dbc
, int64_t groupId
, CFDictionaryRef dict
, CFErrorRef
*error
) {
2630 /* Called only from _SecRevocationDbUpdateIssuerConstraints.
2631 Function assumes that the caller has checked the input arguments.
2633 __block
bool ok
= true;
2634 __block CFErrorRef localError
= NULL
;
2635 CFArrayRef policies
= (CFArrayRef
)CFDictionaryGetValue(dict
, CFSTR("policies"));
2636 if (!isArray(policies
)) {
2637 return ok
; /* no policies supplied, so nothing to update for this issuer */
2640 __block CFDataRef data
= createPoliciesData(policies
);
2641 ok
= data
&& SecDbWithSQL(dbc
->dbconn
, updateGroupPoliciesSQL
, &localError
, ^bool(sqlite3_stmt
*updatePolicies
) {
2642 /* (policies,groupid) */
2643 ok
= ok
&& SecDbBindBlob(updatePolicies
, 1,
2644 CFDataGetBytePtr(data
),
2645 CFDataGetLength(data
),
2646 SQLITE_TRANSIENT
, &localError
);
2647 ok
= ok
&& SecDbBindInt64(updatePolicies
, 2, groupId
, &localError
);
2648 ok
= ok
&& SecDbStep(dbc
->dbconn
, updatePolicies
, &localError
, NULL
);
2651 CFReleaseSafe(data
);
2653 if (!ok
|| localError
) {
2654 secinfo("validupdate", "_SecRevocationDbUpdatePolicyConstraints failed (ok=%s, localError=%@)",
2655 (ok
) ? "1" : "0", localError
);
2656 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationWrite
, TAFatalError
,
2657 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
2659 (void) CFErrorPropagate(localError
, error
);
2663 static bool _SecRevocationDbUpdateNameConstraints(SecRevocationDbConnectionRef dbc
, int64_t groupId
, CFDictionaryRef dict
, CFErrorRef
*error
) {
2664 /* Called only from _SecRevocationDbUpdateIssuerConstraints.
2665 Function assumes that the caller has checked the input arguments.
2668 /* %%% (TBI:9254570) update name constraint entries here */
2672 static bool _SecRevocationDbUpdateIssuerConstraints(SecRevocationDbConnectionRef dbc
, int64_t groupId
, CFDictionaryRef dict
, CFErrorRef
*error
) {
2673 /* check input arguments */
2674 if (!dbc
|| !dict
|| groupId
< 0) {
2678 ok
= ok
&& _SecRevocationDbUpdateDateConstraints(dbc
, groupId
, dict
, error
);
2679 ok
= ok
&& _SecRevocationDbUpdateNameConstraints(dbc
, groupId
, dict
, error
);
2680 ok
= ok
&& _SecRevocationDbUpdatePolicyConstraints(dbc
, groupId
, dict
, error
);
2684 static SecValidInfoFormat
_SecRevocationDbGetGroupFormat(SecRevocationDbConnectionRef dbc
,
2685 int64_t groupId
, SecValidInfoFlags
*flags
, CFDataRef
*data
, CFDataRef
*policies
, CFErrorRef
*error
) {
2686 /* return group record fields for a given groupId.
2687 on success, returns a non-zero format type, and other field values in optional output parameters.
2688 caller is responsible for releasing data, policies, and error parameters, if provided.
2690 __block
bool ok
= (dbc
!= NULL
);
2691 __block SecValidInfoFormat format
= 0;
2692 __block CFErrorRef localError
= NULL
;
2694 /* Select the group record to determine flags and format. */
2695 ok
= ok
&& SecDbWithSQL(dbc
->dbconn
, selectGroupRecordSQL
, &localError
, ^bool(sqlite3_stmt
*selectGroup
) {
2696 ok
= ok
&& SecDbBindInt64(selectGroup
, 1, groupId
, &localError
);
2697 ok
= ok
&& SecDbStep(dbc
->dbconn
, selectGroup
, &localError
, ^(bool *stop
) {
2699 *flags
= (SecValidInfoFlags
)sqlite3_column_int(selectGroup
, 0);
2701 format
= (SecValidInfoFormat
)sqlite3_column_int(selectGroup
, 1);
2703 //%%% stream the data from the db into a streamed decompression <rdar://32142637>
2704 uint8_t *p
= (uint8_t *)sqlite3_column_blob(selectGroup
, 2);
2705 if (p
!= NULL
&& format
== kSecValidInfoFormatNto1
) {
2706 CFIndex length
= (CFIndex
)sqlite3_column_bytes(selectGroup
, 2);
2707 *data
= CFDataCreate(kCFAllocatorDefault
, p
, length
);
2711 uint8_t *p
= (uint8_t *)sqlite3_column_blob(selectGroup
, 3);
2713 CFIndex length
= (CFIndex
)sqlite3_column_bytes(selectGroup
, 3);
2714 *policies
= CFDataCreate(kCFAllocatorDefault
, p
, length
);
2720 if (!ok
|| localError
) {
2721 secdebug("validupdate", "GetGroupFormat for groupId %lu failed", (unsigned long)groupId
);
2722 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationRead
, TAFatalError
,
2723 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
2724 format
= kSecValidInfoFormatUnknown
;
2726 (void) CFErrorPropagate(localError
, error
);
2727 if (!(format
> kSecValidInfoFormatUnknown
)) {
2728 secdebug("validupdate", "GetGroupFormat: got format %d for groupId %lld", format
, (long long)groupId
);
2733 static bool _SecRevocationDbUpdateFlags(CFDictionaryRef dict
, CFStringRef key
, SecValidInfoFlags mask
, SecValidInfoFlags
*flags
) {
2734 /* If a boolean value exists in the given dictionary for the given key,
2735 or an explicit "1" or "0" is specified as the key string,
2736 set or clear the corresponding bit(s) defined by the mask argument.
2737 Function returns true if the flags value was changed, false otherwise.
2739 if (!isDictionary(dict
) || !isString(key
) || !flags
) {
2742 bool hasValue
= false, newValue
= false, result
= false;
2743 CFTypeRef value
= (CFBooleanRef
)CFDictionaryGetValue(dict
, key
);
2744 if (isBoolean(value
)) {
2745 newValue
= CFBooleanGetValue((CFBooleanRef
)value
);
2747 } else if (BOOL_STRING_KEY_LENGTH
== CFStringGetLength(key
)) {
2748 if (CFStringCompare(key
, kBoolTrueKey
, 0) == kCFCompareEqualTo
) {
2749 hasValue
= newValue
= true;
2750 } else if (CFStringCompare(key
, kBoolFalseKey
, 0) == kCFCompareEqualTo
) {
2755 SecValidInfoFlags oldFlags
= *flags
;
2761 result
= (*flags
!= oldFlags
);
2766 static bool _SecRevocationDbUpdateFilter(CFDictionaryRef dict
, CFDataRef oldData
, CFDataRef
* __nonnull CF_RETURNS_RETAINED xmlData
) {
2767 /* If xor and/or params values exist in the given dictionary, create a new
2768 property list containing the updated values, and return as a flattened
2769 data blob in the xmlData output parameter (note: caller must release.)
2770 Function returns true if there is new xmlData to save, false otherwise.
2772 bool result
= false;
2773 bool xorProvided
= false;
2774 bool paramsProvided
= false;
2775 bool missingData
= false;
2777 if (!dict
|| !xmlData
) {
2778 return result
; /* no-op if no dictionary is provided, or no way to update the data */
2781 CFDataRef xorCurrent
= NULL
;
2782 CFDataRef xorUpdate
= (CFDataRef
)CFDictionaryGetValue(dict
, CFSTR("xor"));
2783 if (isData(xorUpdate
)) {
2786 CFArrayRef paramsCurrent
= NULL
;
2787 CFArrayRef paramsUpdate
= (CFArrayRef
)CFDictionaryGetValue(dict
, CFSTR("params"));
2788 if (isArray(paramsUpdate
)) {
2789 paramsProvided
= true;
2791 if (!(xorProvided
|| paramsProvided
)) {
2792 return result
; /* nothing to update, so we can bail out here. */
2795 CFPropertyListRef nto1Current
= NULL
;
2796 CFMutableDictionaryRef nto1Update
= CFDictionaryCreateMutable(kCFAllocatorDefault
, 0,
2797 &kCFTypeDictionaryKeyCallBacks
,
2798 &kCFTypeDictionaryValueCallBacks
);
2803 /* turn old data into property list */
2804 CFDataRef data
= (CFDataRef
)CFRetainSafe(oldData
);
2805 CFDataRef inflatedData
= copyInflatedData(data
);
2807 CFReleaseSafe(data
);
2808 data
= inflatedData
;
2811 nto1Current
= CFPropertyListCreateWithData(kCFAllocatorDefault
, data
, 0, NULL
, NULL
);
2812 CFReleaseSafe(data
);
2815 xorCurrent
= (CFDataRef
)CFDictionaryGetValue((CFDictionaryRef
)nto1Current
, CFSTR("xor"));
2816 paramsCurrent
= (CFArrayRef
)CFDictionaryGetValue((CFDictionaryRef
)nto1Current
, CFSTR("params"));
2819 /* set current or updated xor data in new property list */
2821 CFDataRef xorNew
= NULL
;
2823 CFIndex xorUpdateLen
= CFDataGetLength(xorUpdate
);
2824 CFMutableDataRef
xor = CFDataCreateMutableCopy(NULL
, 0, xorCurrent
);
2825 if (xor && xorUpdateLen
> 0) {
2826 /* truncate or zero-extend data to match update size */
2827 CFDataSetLength(xor, xorUpdateLen
);
2828 /* exclusive-or update bytes over the existing data */
2829 UInt8
*xorP
= (UInt8
*)CFDataGetMutableBytePtr(xor);
2830 UInt8
*updP
= (UInt8
*)CFDataGetBytePtr(xorUpdate
);
2832 for (int idx
= 0; idx
< xorUpdateLen
; idx
++) {
2833 xorP
[idx
] = xorP
[idx
] ^ updP
[idx
];
2837 xorNew
= (CFDataRef
)xor;
2839 xorNew
= (CFDataRef
)CFRetainSafe(xorUpdate
);
2842 CFDictionaryAddValue(nto1Update
, CFSTR("xor"), xorNew
);
2843 CFReleaseSafe(xorNew
);
2845 secdebug("validupdate", "Failed to get updated filter data");
2848 } else if (xorCurrent
) {
2849 /* not provided, so use existing xor value */
2850 CFDictionaryAddValue(nto1Update
, CFSTR("xor"), xorCurrent
);
2852 secdebug("validupdate", "Failed to get current filter data");
2856 /* set current or updated params in new property list */
2857 if (paramsProvided
) {
2858 CFDictionaryAddValue(nto1Update
, CFSTR("params"), paramsUpdate
);
2859 } else if (paramsCurrent
) {
2860 /* not provided, so use existing params value */
2861 CFDictionaryAddValue(nto1Update
, CFSTR("params"), paramsCurrent
);
2863 /* missing params: neither provided nor existing */
2864 secdebug("validupdate", "Failed to get current filter params");
2868 CFReleaseSafe(nto1Current
);
2870 *xmlData
= CFPropertyListCreateData(kCFAllocatorDefault
, nto1Update
,
2871 kCFPropertyListXMLFormat_v1_0
,
2873 result
= (*xmlData
!= NULL
);
2875 CFReleaseSafe(nto1Update
);
2877 /* compress the xmlData blob, if possible */
2879 CFDataRef deflatedData
= copyDeflatedData(*xmlData
);
2881 if (CFDataGetLength(deflatedData
) < CFDataGetLength(*xmlData
)) {
2882 CFRelease(*xmlData
);
2883 *xmlData
= deflatedData
;
2885 CFRelease(deflatedData
);
2893 static int64_t _SecRevocationDbUpdateGroup(SecRevocationDbConnectionRef dbc
, int64_t groupId
, CFDictionaryRef dict
, CFErrorRef
*error
) {
2894 /* insert group record for a given groupId.
2895 if the specified groupId is < 0, a new group entry is created.
2896 returns the groupId on success, or -1 on failure.
2899 return groupId
; /* no-op if no dictionary is provided */
2902 __block
int64_t result
= -1;
2903 __block
bool ok
= (dbc
!= NULL
);
2904 __block
bool isFormatChange
= false;
2905 __block CFErrorRef localError
= NULL
;
2907 __block SecValidInfoFlags flags
= 0;
2908 __block SecValidInfoFormat format
= kSecValidInfoFormatUnknown
;
2909 __block SecValidInfoFormat formatUpdate
= kSecValidInfoFormatUnknown
;
2910 __block CFDataRef data
= NULL
;
2911 __block CFDataRef policies
= NULL
;
2914 /* fetch the flags and data for an existing group record, in case some are being changed. */
2916 format
= _SecRevocationDbGetGroupFormat(dbc
, groupId
, &flags
, &data
, &policies
, NULL
);
2918 if (format
== kSecValidInfoFormatUnknown
) {
2919 secdebug("validupdate", "existing group %lld has unknown format %d, flags=0x%lx",
2920 (long long)groupId
, format
, flags
);
2921 //%%% clean up by deleting all issuers with this groupId, then the group record,
2922 // or just force a full update? note: we can get here if we fail to bind the
2923 // format value in the prepared SQL statement below.
2927 CFTypeRef value
= (CFStringRef
)CFDictionaryGetValue(dict
, CFSTR("format"));
2928 if (isString(value
)) {
2929 if (CFStringCompare((CFStringRef
)value
, CFSTR("serial"), 0) == kCFCompareEqualTo
) {
2930 formatUpdate
= kSecValidInfoFormatSerial
;
2931 } else if (CFStringCompare((CFStringRef
)value
, CFSTR("sha256"), 0) == kCFCompareEqualTo
) {
2932 formatUpdate
= kSecValidInfoFormatSHA256
;
2933 } else if (CFStringCompare((CFStringRef
)value
, CFSTR("nto1"), 0) == kCFCompareEqualTo
) {
2934 formatUpdate
= kSecValidInfoFormatNto1
;
2937 /* if format value is explicitly supplied, then this is effectively a new group entry. */
2938 isFormatChange
= (formatUpdate
> kSecValidInfoFormatUnknown
&&
2939 formatUpdate
!= format
&&
2942 if (isFormatChange
) {
2943 secdebug("validupdate", "group %lld format change from %d to %d",
2944 (long long)groupId
, format
, formatUpdate
);
2945 /* format of an existing group is changing; delete the group first.
2946 this should ensure that all entries referencing the old groupid are deleted.
2948 ok
= ok
&& SecDbWithSQL(dbc
->dbconn
, deleteGroupRecordSQL
, &localError
, ^bool(sqlite3_stmt
*deleteResponse
) {
2949 ok
= ok
&& SecDbBindInt64(deleteResponse
, 1, groupId
, &localError
);
2950 /* Execute the delete statement. */
2951 ok
= ok
&& SecDbStep(dbc
->dbconn
, deleteResponse
, &localError
, NULL
);
2955 ok
= ok
&& SecDbWithSQL(dbc
->dbconn
, insertGroupRecordSQL
, &localError
, ^bool(sqlite3_stmt
*insertGroup
) {
2956 /* (groupid,flags,format,data,policies) */
2957 /* groups.groupid */
2958 if (ok
&& (!isFormatChange
) && (groupId
>= 0)) {
2959 /* bind to existing groupId row if known, otherwise will insert and autoincrement */
2960 ok
= SecDbBindInt64(insertGroup
, 1, groupId
, &localError
);
2962 secdebug("validupdate", "failed to set groupId %lld", (long long)groupId
);
2967 (void)_SecRevocationDbUpdateFlags(dict
, CFSTR("complete"), kSecValidInfoComplete
, &flags
);
2968 (void)_SecRevocationDbUpdateFlags(dict
, CFSTR("check-ocsp"), kSecValidInfoCheckOCSP
, &flags
);
2969 (void)_SecRevocationDbUpdateFlags(dict
, CFSTR("known-intermediates-only"), kSecValidInfoKnownOnly
, &flags
);
2970 (void)_SecRevocationDbUpdateFlags(dict
, CFSTR("require-ct"), kSecValidInfoRequireCT
, &flags
);
2971 (void)_SecRevocationDbUpdateFlags(dict
, CFSTR("valid"), kSecValidInfoAllowlist
, &flags
);
2972 (void)_SecRevocationDbUpdateFlags(dict
, CFSTR("no-ca"), kSecValidInfoNoCACheck
, &flags
);
2973 (void)_SecRevocationDbUpdateFlags(dict
, CFSTR("no-ca-v2"), kSecValidInfoNoCAv2Check
, &flags
);
2974 (void)_SecRevocationDbUpdateFlags(dict
, CFSTR("overridable"), kSecValidInfoOverridable
, &flags
);
2976 /* date constraints exist if either "not-before" or "not-after" keys are found */
2977 CFTypeRef notBeforeValue
= (CFDateRef
)CFDictionaryGetValue(dict
, CFSTR("not-before"));
2978 CFTypeRef notAfterValue
= (CFDateRef
)CFDictionaryGetValue(dict
, CFSTR("not-after"));
2979 if (isDate(notBeforeValue
) || isDate(notAfterValue
)) {
2980 (void)_SecRevocationDbUpdateFlags(dict
, kBoolTrueKey
, kSecValidInfoDateConstraints
, &flags
);
2981 /* Note that the spec defines not-before and not-after dates as optional, such that
2982 not providing one does not change the database contents. Therefore, we can never clear
2983 this flag; either a new date entry will be supplied, or a format change will cause
2984 the entire group entry to be deleted. */
2986 /* policy constraints exist if "policies" key is found */
2987 CFTypeRef policiesValue
= (CFArrayRef
)CFDictionaryGetValue(dict
, CFSTR("policies"));
2988 if (isArray(policiesValue
)) {
2989 (void)_SecRevocationDbUpdateFlags(dict
, kBoolTrueKey
, kSecValidInfoPolicyConstraints
, &flags
);
2990 /* As above, not providing this value in an update does not change the existing state,
2991 so we never need to clear this flag once it is set. */
2994 /* %%% (TBI:9254570) name constraints don't exist yet */
2995 (void)_SecRevocationDbUpdateFlags(dict
, kBoolFalseKey
, kSecValidInfoNameConstraints
, &flags
);
2997 ok
= SecDbBindInt(insertGroup
, 2, (int)flags
, &localError
);
2999 secdebug("validupdate", "failed to set flags (%lu) for groupId %lld", flags
, (long long)groupId
);
3004 SecValidInfoFormat formatValue
= format
;
3005 if (formatUpdate
> kSecValidInfoFormatUnknown
) {
3006 formatValue
= formatUpdate
;
3008 ok
= SecDbBindInt(insertGroup
, 3, (int)formatValue
, &localError
);
3010 secdebug("validupdate", "failed to set format (%d) for groupId %lld", formatValue
, (long long)groupId
);
3014 CFDataRef xmlData
= NULL
;
3016 bool hasFilter
= ((formatUpdate
== kSecValidInfoFormatNto1
) ||
3017 (formatUpdate
== kSecValidInfoFormatUnknown
&&
3018 format
== kSecValidInfoFormatNto1
));
3020 CFDataRef dataValue
= data
; /* use existing data */
3021 if (_SecRevocationDbUpdateFilter(dict
, data
, &xmlData
)) {
3022 dataValue
= xmlData
; /* use updated data */
3025 ok
= SecDbBindBlob(insertGroup
, 4,
3026 CFDataGetBytePtr(dataValue
),
3027 CFDataGetLength(dataValue
),
3028 SQLITE_TRANSIENT
, &localError
);
3031 secdebug("validupdate", "failed to set data for groupId %lld",
3032 (long long)groupId
);
3035 /* else there is no data, so NULL is implicitly bound to column 4 */
3037 /* groups.policies */
3038 CFDataRef newPoliciesData
= NULL
;
3040 CFDataRef policiesValue
= policies
; /* use existing policies */
3041 newPoliciesData
= createPoliciesData((CFArrayRef
)CFDictionaryGetValue(dict
, CFSTR("policies")));
3042 if (newPoliciesData
) {
3043 policiesValue
= newPoliciesData
; /* use updated policies */
3045 if (policiesValue
) {
3046 ok
= SecDbBindBlob(insertGroup
, 5,
3047 CFDataGetBytePtr(policiesValue
),
3048 CFDataGetLength(policiesValue
),
3049 SQLITE_TRANSIENT
, &localError
);
3051 /* else there is no policy data, so NULL is implicitly bound to column 5 */
3053 secdebug("validupdate", "failed to set policies for groupId %lld",
3054 (long long)groupId
);
3058 /* Execute the insert statement for the group record. */
3060 ok
= SecDbStep(dbc
->dbconn
, insertGroup
, &localError
, NULL
);
3062 secdebug("validupdate", "failed to execute insertGroup statement for groupId %lld",
3063 (long long)groupId
);
3065 result
= (int64_t)sqlite3_last_insert_rowid(SecDbHandle(dbc
->dbconn
));
3068 secdebug("validupdate", "failed to insert group %lld", (long long)result
);
3070 /* Clean up temporary allocations made in this block. */
3071 CFReleaseSafe(xmlData
);
3072 CFReleaseSafe(newPoliciesData
);
3076 CFReleaseSafe(data
);
3077 CFReleaseSafe(policies
);
3079 if (!ok
|| localError
) {
3080 secerror("_SecRevocationDbUpdateGroup failed: %@", localError
);
3081 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationWrite
, TAFatalError
,
3082 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
3084 (void) CFErrorPropagate(localError
, error
);
3088 static int64_t _SecRevocationDbGroupIdForIssuerHash(SecRevocationDbConnectionRef dbc
, CFDataRef hash
, CFErrorRef
*error
) {
3089 /* look up issuer hash in issuers table to get groupid, if it exists */
3090 __block
int64_t groupId
= -1;
3091 __block
bool ok
= (dbc
!= NULL
);
3092 __block CFErrorRef localError
= NULL
;
3095 secdebug("validupdate", "failed to get hash (%@)", hash
);
3097 require(hash
&& dbc
, errOut
);
3099 /* This is the starting point for any lookup; find a group id for the given issuer hash.
3100 Before we do that, need to verify the current db_version. We cannot use results from a
3101 database created with a schema version older than the minimum supported version.
3102 However, we may be able to use results from a newer version. At the next database
3103 update interval, if the existing schema is old, we'll be removing and recreating
3104 the database contents with the current schema version.
3106 int64_t db_version
= _SecRevocationDbGetSchemaVersion(dbc
->db
, dbc
, NULL
);
3107 if (db_version
< kSecRevocationDbMinSchemaVersion
) {
3108 if (!dbc
->db
->unsupportedVersion
) {
3109 secdebug("validupdate", "unsupported db_version: %lld", (long long)db_version
);
3110 dbc
->db
->unsupportedVersion
= true; /* only warn once for a given unsupported version */
3113 require_quiet(db_version
>= kSecRevocationDbMinSchemaVersion
, errOut
);
3115 /* Look up provided issuer_hash in the issuers table.
3117 ok
= ok
&& SecDbWithSQL(dbc
->dbconn
, selectGroupIdSQL
, &localError
, ^bool(sqlite3_stmt
*selectGroupId
) {
3118 ok
&= SecDbBindBlob(selectGroupId
, 1, CFDataGetBytePtr(hash
), CFDataGetLength(hash
), SQLITE_TRANSIENT
, &localError
);
3119 ok
&= SecDbStep(dbc
->dbconn
, selectGroupId
, &localError
, ^(bool *stopGroupId
) {
3120 groupId
= sqlite3_column_int64(selectGroupId
, 0);
3126 if (!ok
|| localError
) {
3127 secerror("_SecRevocationDbGroupIdForIssuerHash failed: %@", localError
);
3128 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationRead
, TAFatalError
,
3129 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
3131 (void) CFErrorPropagate(localError
, error
);
3135 static bool _SecRevocationDbApplyGroupDelete(SecRevocationDbConnectionRef dbc
, CFDataRef issuerHash
, CFErrorRef
*error
) {
3136 /* delete group associated with the given issuer;
3137 schema trigger will delete associated issuers, serials, and hashes. */
3138 __block
int64_t groupId
= -1;
3139 __block
bool ok
= (dbc
!= NULL
);
3140 __block CFErrorRef localError
= NULL
;
3143 groupId
= _SecRevocationDbGroupIdForIssuerHash(dbc
, issuerHash
, &localError
);
3147 SecError(errSecParam
, &localError
, CFSTR("group not found for issuer"));
3151 ok
= ok
&& SecDbWithSQL(dbc
->dbconn
, deleteGroupRecordSQL
, &localError
, ^bool(sqlite3_stmt
*deleteResponse
) {
3152 ok
&= SecDbBindInt64(deleteResponse
, 1, groupId
, &localError
);
3153 /* Execute the delete statement. */
3154 ok
= ok
&& SecDbStep(dbc
->dbconn
, deleteResponse
, &localError
, NULL
);
3157 if (!ok
|| localError
) {
3158 secerror("_SecRevocationDbApplyGroupDelete failed: %@", localError
);
3159 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationWrite
, TAFatalError
,
3160 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
3162 (void) CFErrorPropagate(localError
, error
);
3166 static bool _SecRevocationDbApplyGroupUpdate(SecRevocationDbConnectionRef dbc
, CFDictionaryRef dict
, CFErrorRef
*error
) {
3167 /* process one issuer group's update dictionary */
3168 __block
int64_t groupId
= -1;
3169 __block
bool ok
= (dbc
!= NULL
);
3170 __block CFErrorRef localError
= NULL
;
3172 CFArrayRef issuers
= (dict
) ? (CFArrayRef
)CFDictionaryGetValue(dict
, CFSTR("issuer-hash")) : NULL
;
3173 /* if this is not a full update, then look for existing group id */
3174 if (ok
&& isArray(issuers
) && !dbc
->fullUpdate
) {
3175 CFIndex issuerIX
, issuerCount
= CFArrayGetCount(issuers
);
3176 /* while we have issuers and haven't found a matching group id */
3177 for (issuerIX
=0; issuerIX
<issuerCount
&& groupId
< 0; issuerIX
++) {
3178 CFDataRef hash
= (CFDataRef
)CFArrayGetValueAtIndex(issuers
, issuerIX
);
3179 if (!hash
) { continue; }
3180 groupId
= _SecRevocationDbGroupIdForIssuerHash(dbc
, hash
, &localError
);
3183 /* according to the spec, we must replace all existing issuers with
3184 the new issuers list, so delete all issuers in the group first. */
3185 ok
= ok
&& SecDbWithSQL(dbc
->dbconn
, deleteGroupIssuersSQL
, &localError
, ^bool(sqlite3_stmt
*deleteIssuers
) {
3186 ok
= ok
&& SecDbBindInt64(deleteIssuers
, 1, groupId
, &localError
);
3187 ok
= ok
&& SecDbStep(dbc
->dbconn
, deleteIssuers
, &localError
, NULL
);
3192 /* create or update the group entry */
3194 groupId
= _SecRevocationDbUpdateGroup(dbc
, groupId
, dict
, &localError
);
3197 secdebug("validupdate", "failed to get groupId");
3200 /* create or update issuer entries, now that we know the group id */
3201 ok
= ok
&& _SecRevocationDbUpdateIssuers(dbc
, groupId
, issuers
, &localError
);
3202 /* create or update entries in serials or hashes tables */
3203 ok
= ok
&& _SecRevocationDbUpdateIssuerData(dbc
, groupId
, dict
, &localError
);
3204 /* create or update entries in dates/names/policies tables */
3205 ok
= ok
&& _SecRevocationDbUpdateIssuerConstraints(dbc
, groupId
, dict
, &localError
);
3208 (void) CFErrorPropagate(localError
, error
);
3212 bool _SecRevocationDbApplyUpdate(SecRevocationDbConnectionRef dbc
, CFDictionaryRef update
, CFIndex version
, CFErrorRef
*error
) {
3213 /* process entire update dictionary */
3214 if (!dbc
|| !dbc
->db
|| !update
) {
3215 secerror("_SecRevocationDbApplyUpdate failed: invalid args");
3216 SecError(errSecParam
, error
, CFSTR("_SecRevocationDbApplyUpdate: invalid db or update parameter"));
3220 CFDictionaryRef localUpdate
= (CFDictionaryRef
)CFRetainSafe(update
);
3221 CFErrorRef localError
= NULL
;
3224 CFTypeRef value
= NULL
;
3225 CFIndex deleteCount
= 0;
3226 CFIndex updateCount
= 0;
3228 dbc
->db
->updateInProgress
= true;
3230 /* check whether this is a full update */
3231 value
= (CFBooleanRef
)CFDictionaryGetValue(update
, CFSTR("full"));
3232 if (isBoolean(value
) && CFBooleanGetValue((CFBooleanRef
)value
)) {
3233 /* clear the database before processing a full update */
3234 dbc
->fullUpdate
= true;
3235 secdebug("validupdate", "update has \"full\" attribute; clearing database");
3236 ok
= ok
&& _SecRevocationDbRemoveAllEntries(dbc
, &localError
);
3239 /* process 'delete' list */
3240 value
= (CFArrayRef
)CFDictionaryGetValue(localUpdate
, CFSTR("delete"));
3241 if (isArray(value
)) {
3242 deleteCount
= CFArrayGetCount((CFArrayRef
)value
);
3243 secdebug("validupdate", "processing %ld deletes", (long)deleteCount
);
3244 for (CFIndex deleteIX
=0; deleteIX
<deleteCount
; deleteIX
++) {
3245 CFDataRef issuerHash
= (CFDataRef
)CFArrayGetValueAtIndex((CFArrayRef
)value
, deleteIX
);
3246 if (isData(issuerHash
)) {
3247 ok
= ok
&& _SecRevocationDbApplyGroupDelete(dbc
, issuerHash
, &localError
);
3249 secdebug("validupdate", "skipping delete %ld (hash is not a data value)", (long)deleteIX
);
3254 /* process 'update' list */
3255 value
= (CFArrayRef
)CFDictionaryGetValue(localUpdate
, CFSTR("update"));
3256 if (isArray(value
)) {
3257 updateCount
= CFArrayGetCount((CFArrayRef
)value
);
3258 secdebug("validupdate", "processing %ld updates", (long)updateCount
);
3259 for (CFIndex updateIX
=0; updateIX
<updateCount
; updateIX
++) {
3260 CFDictionaryRef dict
= (CFDictionaryRef
)CFArrayGetValueAtIndex((CFArrayRef
)value
, updateIX
);
3261 if (isDictionary(dict
)) {
3262 ok
= ok
&& _SecRevocationDbApplyGroupUpdate(dbc
, dict
, &localError
);
3264 secdebug("validupdate", "skipping update %ld (not a dictionary)", (long)updateIX
);
3268 CFReleaseSafe(localUpdate
);
3271 ok
= ok
&& _SecRevocationDbSetVersion(dbc
, version
, &localError
);
3273 /* set interval if not already set, or changed */
3274 int64_t interval
= _SecRevocationDbGetUpdateInterval(dbc
, NULL
);
3275 if (interval
!= dbc
->precommitInterval
) {
3276 interval
= (dbc
->precommitInterval
> 0) ? dbc
->precommitInterval
: kSecStdUpdateInterval
;
3277 ok
= ok
&& _SecRevocationDbSetUpdateInterval(dbc
, interval
, &localError
);
3280 /* set db_version if not already set */
3281 int64_t db_version
= _SecRevocationDbGetSchemaVersion(dbc
->db
, dbc
, NULL
);
3282 if (db_version
<= 0) {
3283 ok
= ok
&& _SecRevocationDbSetSchemaVersion(dbc
, kSecRevocationDbSchemaVersion
, &localError
);
3286 /* set db_format if not already set */
3287 int64_t db_format
= _SecRevocationDbGetUpdateFormat(dbc
, NULL
);
3288 if (db_format
<= 0) {
3289 ok
= ok
&& _SecRevocationDbSetUpdateFormat(dbc
, kSecRevocationDbUpdateFormat
, &localError
);
3292 /* purge the in-memory cache */
3293 SecRevocationDbCachePurge(dbc
->db
);
3295 dbc
->db
->updateInProgress
= false;
3297 (void) CFErrorPropagate(localError
, error
);
3301 static bool _SecRevocationDbSerialInGroup(SecRevocationDbConnectionRef dbc
,
3304 CFErrorRef
*error
) {
3305 __block
bool result
= false;
3306 __block
bool ok
= true;
3307 __block CFErrorRef localError
= NULL
;
3308 require(dbc
&& serial
, errOut
);
3309 ok
&= SecDbWithSQL(dbc
->dbconn
, selectSerialRecordSQL
, &localError
, ^bool(sqlite3_stmt
*selectSerial
) {
3310 ok
&= SecDbBindInt64(selectSerial
, 1, groupId
, &localError
);
3311 ok
&= SecDbBindBlob(selectSerial
, 2, CFDataGetBytePtr(serial
),
3312 CFDataGetLength(serial
), SQLITE_TRANSIENT
, &localError
);
3313 ok
&= SecDbStep(dbc
->dbconn
, selectSerial
, &localError
, ^(bool *stop
) {
3314 int64_t foundRowId
= (int64_t)sqlite3_column_int64(selectSerial
, 0);
3315 result
= (foundRowId
> 0);
3321 if (!ok
|| localError
) {
3322 secerror("_SecRevocationDbSerialInGroup failed: %@", localError
);
3323 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationRead
, TAFatalError
,
3324 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
3326 (void) CFErrorPropagate(localError
, error
);
3330 static bool _SecRevocationDbCertHashInGroup(SecRevocationDbConnectionRef dbc
,
3333 CFErrorRef
*error
) {
3334 __block
bool result
= false;
3335 __block
bool ok
= true;
3336 __block CFErrorRef localError
= NULL
;
3337 require(dbc
&& certHash
, errOut
);
3338 ok
&= SecDbWithSQL(dbc
->dbconn
, selectHashRecordSQL
, &localError
, ^bool(sqlite3_stmt
*selectHash
) {
3339 ok
&= SecDbBindInt64(selectHash
, 1, groupId
, &localError
);
3340 ok
= SecDbBindBlob(selectHash
, 2, CFDataGetBytePtr(certHash
),
3341 CFDataGetLength(certHash
), SQLITE_TRANSIENT
, &localError
);
3342 ok
&= SecDbStep(dbc
->dbconn
, selectHash
, &localError
, ^(bool *stop
) {
3343 int64_t foundRowId
= (int64_t)sqlite3_column_int64(selectHash
, 0);
3344 result
= (foundRowId
> 0);
3350 if (!ok
|| localError
) {
3351 secerror("_SecRevocationDbCertHashInGroup failed: %@", localError
);
3352 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationRead
, TAFatalError
,
3353 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
3355 (void) CFErrorPropagate(localError
, error
);
3359 static bool _SecRevocationDbSerialInFilter(SecRevocationDbConnectionRef dbc
,
3360 CFDataRef serialData
,
3361 CFDataRef xmlData
) {
3362 /* N-To-1 filter implementation.
3363 The 'xmlData' parameter is a flattened XML dictionary,
3364 containing 'xor' and 'params' keys. First order of
3365 business is to reconstitute the blob into components.
3367 bool result
= false;
3368 CFRetainSafe(xmlData
);
3369 CFDataRef propListData
= xmlData
;
3370 /* Expand data blob if needed */
3371 CFDataRef inflatedData
= copyInflatedData(propListData
);
3373 CFReleaseSafe(propListData
);
3374 propListData
= inflatedData
;
3376 CFDataRef
xor = NULL
;
3377 CFArrayRef params
= NULL
;
3378 CFPropertyListRef nto1
= CFPropertyListCreateWithData(kCFAllocatorDefault
, propListData
, 0, NULL
, NULL
);
3380 xor = (CFDataRef
)CFDictionaryGetValue((CFDictionaryRef
)nto1
, CFSTR("xor"));
3381 params
= (CFArrayRef
)CFDictionaryGetValue((CFDictionaryRef
)nto1
, CFSTR("params"));
3383 uint8_t *hash
= (xor) ? (uint8_t*)CFDataGetBytePtr(xor) : NULL
;
3384 CFIndex hashLen
= (hash
) ? CFDataGetLength(xor) : 0;
3385 uint8_t *serial
= (serialData
) ? (uint8_t*)CFDataGetBytePtr(serialData
) : NULL
;
3386 CFIndex serialLen
= (serial
) ? CFDataGetLength(serialData
) : 0;
3388 require(hash
&& serial
&& params
, errOut
);
3390 const uint32_t FNV_OFFSET_BASIS
= 2166136261;
3391 const uint32_t FNV_PRIME
= 16777619;
3392 bool notInHash
= false;
3393 CFIndex ix
, count
= CFArrayGetCount(params
);
3394 for (ix
= 0; ix
< count
; ix
++) {
3396 CFNumberRef cfnum
= (CFNumberRef
)CFArrayGetValueAtIndex(params
, ix
);
3397 if (!isNumber(cfnum
) ||
3398 !CFNumberGetValue(cfnum
, kCFNumberSInt32Type
, ¶m
)) {
3399 secinfo("validupdate", "error processing filter params at index %ld", (long)ix
);
3402 /* process one param */
3403 uint32_t hval
= FNV_OFFSET_BASIS
^ param
;
3404 CFIndex i
= serialLen
;
3406 hval
= ((hval
^ (serial
[--i
])) * FNV_PRIME
) & 0xFFFFFFFF;
3408 hval
= hval
% (hashLen
* 8);
3409 if ((hash
[hval
/8] & (1 << (hval
% 8))) == 0) {
3410 notInHash
= true; /* definitely not in hash */
3415 /* probabilistically might be in hash if we get here. */
3420 CFReleaseSafe(nto1
);
3421 CFReleaseSafe(propListData
);
3425 static SecValidInfoRef
_SecRevocationDbValidInfoForCertificate(SecRevocationDbConnectionRef dbc
,
3426 SecCertificateRef certificate
,
3427 CFDataRef issuerHash
,
3428 CFErrorRef
*error
) {
3429 __block CFErrorRef localError
= NULL
;
3430 __block SecValidInfoFlags flags
= 0;
3431 __block SecValidInfoFormat format
= kSecValidInfoFormatUnknown
;
3432 __block CFDataRef data
= NULL
;
3434 bool matched
= false;
3435 bool isOnList
= false;
3436 int64_t groupId
= 0;
3437 CFDataRef serial
= NULL
;
3438 CFDataRef certHash
= NULL
;
3439 CFDateRef notBeforeDate
= NULL
;
3440 CFDateRef notAfterDate
= NULL
;
3441 CFDataRef names
= NULL
;
3442 CFDataRef policies
= NULL
;
3443 SecValidInfoRef result
= NULL
;
3445 require((serial
= SecCertificateCopySerialNumberData(certificate
, NULL
)) != NULL
, errOut
);
3446 require((certHash
= SecCertificateCopySHA256Digest(certificate
)) != NULL
, errOut
);
3447 require_quiet((groupId
= _SecRevocationDbGroupIdForIssuerHash(dbc
, issuerHash
, &localError
)) > 0, errOut
);
3449 /* Look up the group record to determine flags and format. */
3450 format
= _SecRevocationDbGetGroupFormat(dbc
, groupId
, &flags
, &data
, &policies
, &localError
);
3452 if (format
== kSecValidInfoFormatUnknown
) {
3453 /* No group record found for this issuer. Don't return a SecValidInfoRef */
3456 else if (format
== kSecValidInfoFormatSerial
) {
3457 /* Look up certificate's serial number in the serials table. */
3458 matched
= _SecRevocationDbSerialInGroup(dbc
, serial
, groupId
, &localError
);
3460 else if (format
== kSecValidInfoFormatSHA256
) {
3461 /* Look up certificate's SHA-256 hash in the hashes table. */
3462 matched
= _SecRevocationDbCertHashInGroup(dbc
, certHash
, groupId
, &localError
);
3464 else if (format
== kSecValidInfoFormatNto1
) {
3465 /* Perform a Bloom filter match against the serial. If matched is false,
3466 then the cert is definitely not in the list. But if matched is true,
3467 we don't know for certain, so we would need to check OCSP. */
3468 matched
= _SecRevocationDbSerialInFilter(dbc
, serial
, data
);
3472 /* Found a specific match for this certificate. */
3473 secdebug("validupdate", "Valid db matched certificate: %@, format=%d, flags=0x%lx",
3474 certHash
, format
, flags
);
3478 /* If supplemental constraints are present for this issuer, then we always match. */
3479 if ((flags
& kSecValidInfoDateConstraints
) &&
3480 (_SecRevocationDbCopyDateConstraints(dbc
, groupId
, ¬BeforeDate
, ¬AfterDate
, &localError
))) {
3481 secdebug("validupdate", "Valid db matched supplemental date constraints for groupId %lld: nb=%@, na=%@",
3482 (long long)groupId
, notBeforeDate
, notAfterDate
);
3486 /* Return SecValidInfo for certificates for which an issuer entry is found. */
3487 result
= SecValidInfoCreate(format
, flags
, isOnList
,
3488 certHash
, issuerHash
, /*anchorHash*/ NULL
,
3489 notBeforeDate
, notAfterDate
,
3492 if (result
&& SecIsAppleTrustAnchor(certificate
, 0)) {
3493 /* Prevent a catch-22. */
3494 secdebug("validupdate", "Valid db match for Apple trust anchor: %@, format=%d, flags=0x%lx",
3495 certHash
, format
, flags
);
3496 CFReleaseNull(result
);
3500 (void) CFErrorPropagate(localError
, error
);
3501 CFReleaseSafe(data
);
3502 CFReleaseSafe(certHash
);
3503 CFReleaseSafe(serial
);
3504 CFReleaseSafe(notBeforeDate
);
3505 CFReleaseSafe(notAfterDate
);
3506 CFReleaseSafe(names
);
3507 CFReleaseSafe(policies
);
3511 static SecValidInfoRef
_SecRevocationDbCopyMatching(SecRevocationDbConnectionRef dbc
,
3512 SecCertificateRef certificate
,
3513 SecCertificateRef issuer
) {
3514 SecValidInfoRef result
= NULL
;
3515 CFErrorRef error
= NULL
;
3516 CFDataRef issuerHash
= NULL
;
3518 require(dbc
&& certificate
&& issuer
, errOut
);
3519 require(issuerHash
= SecCertificateCopySHA256Digest(issuer
), errOut
);
3521 /* Check for the result in the cache. */
3522 result
= SecRevocationDbCacheRead(dbc
->db
, certificate
, issuerHash
);
3524 /* Upon cache miss, get the result from the database and add it to the cache. */
3526 result
= _SecRevocationDbValidInfoForCertificate(dbc
, certificate
, issuerHash
, &error
);
3527 SecRevocationDbCacheWrite(dbc
->db
, result
);
3531 CFReleaseSafe(issuerHash
);
3532 CFReleaseSafe(error
);
3536 /* Return the update source as a retained CFStringRef.
3537 If the value cannot be obtained, NULL is returned.
3539 CFStringRef
SecRevocationDbCopyUpdateSource(void) {
3540 __block CFStringRef result
= NULL
;
3541 SecRevocationDbWith(^(SecRevocationDbRef db
) {
3542 (void) SecRevocationDbPerformRead(db
, NULL
, ^bool(SecRevocationDbConnectionRef dbc
, CFErrorRef
*blockError
) {
3543 result
= _SecRevocationDbCopyUpdateSource(dbc
, blockError
);
3544 return (bool)result
;
3550 /* Set the next update value for the revocation database.
3551 (This function is expected to be called only by the database
3552 maintainer, normally the system instance of trustd. If the
3553 caller does not have write access, this is a no-op.)
3555 bool SecRevocationDbSetNextUpdateTime(CFAbsoluteTime nextUpdate
, CFErrorRef
*error
) {
3556 __block
bool ok
= true;
3557 __block CFErrorRef localError
= NULL
;
3558 SecRevocationDbWith(^(SecRevocationDbRef rdb
) {
3559 ok
&= SecRevocationDbPerformWrite(rdb
, &localError
, ^bool(SecRevocationDbConnectionRef dbc
, CFErrorRef
*blockError
) {
3560 return _SecRevocationDbSetNextUpdateTime(dbc
, nextUpdate
, blockError
);
3563 (void) CFErrorPropagate(localError
, error
);
3567 /* Return the next update value as a CFAbsoluteTime.
3568 If the value cannot be obtained, -1 is returned.
3570 CFAbsoluteTime
SecRevocationDbGetNextUpdateTime(void) {
3571 __block CFAbsoluteTime result
= -1;
3572 SecRevocationDbWith(^(SecRevocationDbRef db
) {
3573 (void) SecRevocationDbPerformRead(db
, NULL
, ^bool(SecRevocationDbConnectionRef dbc
, CFErrorRef
*blockError
) {
3574 result
= _SecRevocationDbGetNextUpdateTime(dbc
, blockError
);
3581 /* Return the serial background queue for database updates.
3582 If the queue cannot be obtained, NULL is returned.
3584 dispatch_queue_t
SecRevocationDbGetUpdateQueue(void) {
3585 __block dispatch_queue_t result
= NULL
;
3586 SecRevocationDbWith(^(SecRevocationDbRef db
) {
3587 result
= (db
) ? db
->update_queue
: NULL
;
3592 /* Release all connections to the revocation database.
3594 void SecRevocationDbReleaseAllConnections(void) {
3595 SecRevocationDbWith(^(SecRevocationDbRef db
) {
3596 SecDbReleaseAllConnections((db
) ? db
->db
: NULL
);
3600 /* === SecRevocationDb API === */
3602 /* Given a certificate and its issuer, returns a SecValidInfoRef if the
3603 valid database contains matching info; otherwise returns NULL.
3604 Caller must release the returned SecValidInfoRef when finished.
3606 SecValidInfoRef
SecRevocationDbCopyMatching(SecCertificateRef certificate
,
3607 SecCertificateRef issuer
) {
3608 __block SecValidInfoRef result
= NULL
;
3609 SecRevocationDbWith(^(SecRevocationDbRef db
) {
3610 (void) SecRevocationDbPerformRead(db
, NULL
, ^bool(SecRevocationDbConnectionRef dbc
, CFErrorRef
*blockError
) {
3611 result
= _SecRevocationDbCopyMatching(dbc
, certificate
, issuer
);
3612 return (bool)result
;
3618 /* Given an issuer, returns true if an entry for this issuer exists in
3619 the database (i.e. a known CA). If the provided certificate is NULL,
3620 or its entry is not found, the function returns false.
3622 bool SecRevocationDbContainsIssuer(SecCertificateRef issuer
) {
3626 __block
bool result
= false;
3627 SecRevocationDbWith(^(SecRevocationDbRef db
) {
3628 (void) SecRevocationDbPerformRead(db
, NULL
, ^bool(SecRevocationDbConnectionRef dbc
, CFErrorRef
*blockError
) {
3629 CFDataRef issuerHash
= SecCertificateCopySHA256Digest(issuer
);
3630 int64_t groupId
= _SecRevocationDbGroupIdForIssuerHash(dbc
, issuerHash
, blockError
);
3631 CFReleaseSafe(issuerHash
);
3632 result
= (groupId
> 0);
3639 /* Return the current version of the revocation database.
3640 A version of 0 indicates an empty database which must be populated.
3641 If the version cannot be obtained, -1 is returned.
3643 CFIndex
SecRevocationDbGetVersion(void) {
3644 __block CFIndex result
= -1;
3645 SecRevocationDbWith(^(SecRevocationDbRef db
) {
3646 (void) SecRevocationDbPerformRead(db
, NULL
, ^bool(SecRevocationDbConnectionRef dbc
, CFErrorRef
*blockError
) {
3647 result
= (CFIndex
)_SecRevocationDbGetVersion(dbc
, blockError
);
3648 return (result
>= 0);
3654 /* Return the current schema version of the revocation database.
3655 A version of 0 indicates an empty database which must be populated.
3656 If the schema version cannot be obtained, -1 is returned.
3658 CFIndex
SecRevocationDbGetSchemaVersion(void) {
3659 __block CFIndex result
= -1;
3660 SecRevocationDbWith(^(SecRevocationDbRef db
) {
3661 result
= (CFIndex
)_SecRevocationDbGetSchemaVersion(db
, NULL
, NULL
);
3666 /* Return the current update format of the revocation database.
3667 A version of 0 indicates the format was unknown.
3668 If the update format cannot be obtained, -1 is returned.
3670 CFIndex
SecRevocationDbGetUpdateFormat(void) {
3671 __block CFIndex result
= -1;
3672 SecRevocationDbWith(^(SecRevocationDbRef db
) {
3673 (void) SecRevocationDbPerformRead(db
, NULL
, ^bool(SecRevocationDbConnectionRef dbc
, CFErrorRef
*blockError
) {
3674 result
= (CFIndex
)_SecRevocationDbGetUpdateFormat(dbc
, blockError
);
3675 return (result
>= 0);
3684 ==============================================================================
3686 ==============================================================================
3689 /* Returns array of SHA-256 hashes computed over the contents of a valid.sqlite3
3690 database, in the order specified by the valid-server-api documentation. The
3691 resulting hashes can be compared against those in the update's 'hash' array.
3693 Hash 0: full database (all fields in initial Valid specification)
3694 Hash 1: all issuer_hash arrays, plus not-after and not-before dates for each
3695 Hash 2: subset of issuer_hash arrays where the no-ca-v2 flag is set
3697 static CF_RETURNS_RETAINED CFArrayRef
SecRevocationDbComputeFullContentDigests(SecRevocationDbConnectionRef dbc
, CFErrorRef
*error
) {
3698 if (!dbc
) { return NULL
; }
3699 __block
bool ok
= true;
3700 __block CFErrorRef localError
= NULL
;
3701 __block
uint32_t N
[4]={0,0,0,0};
3702 __block CC_SHA256_CTX hash0_ctx
, hash1_ctx
, hash2_ctx
;
3703 CC_SHA256_Init(&hash0_ctx
);
3704 CC_SHA256_Init(&hash1_ctx
);
3705 CC_SHA256_Init(&hash2_ctx
);
3707 // Add version, check-again, and update (array count) fields as array of N.
3708 // (Note: 'N' is defined as "unsigned 32-bit integer in network byte order")
3709 int64_t version
= _SecRevocationDbGetVersion(dbc
, NULL
);
3710 N
[0] = OSSwapInt32(version
& 0xffffffff);
3711 int64_t interval
= _SecRevocationDbGetUpdateInterval(dbc
, NULL
);
3713 interval
= kSecStdUpdateInterval
; // if we didn't store it, assume default
3715 N
[1] = OSSwapInt32(interval
& 0xffffffff);
3716 __block
int64_t count
= 0;
3717 ok
= ok
&& SecDbWithSQL(dbc
->dbconn
, CFSTR("SELECT count(*) FROM groups"), &localError
, ^bool(sqlite3_stmt
*selectGroupsCount
) {
3718 ok
= ok
&& SecDbStep(dbc
->dbconn
, selectGroupsCount
, &localError
, ^void(bool *stop
) {
3719 count
= sqlite3_column_int64(selectGroupsCount
, 0);
3724 N
[2] = OSSwapInt32(count
& 0xffffffff);
3725 CC_SHA256_Update(&hash0_ctx
, N
, sizeof(uint32_t) * 3);
3727 // Sort the update array in order of minimum 'issuer-hash' entry.
3728 // The issuer-hash array is first sorted to determine the lowest issuer-hash,
3729 // and that value is used to sort the update entries.
3731 // For our sqlite database, recreating the update array order means fetching
3732 // the groupid column from the issuers table after sorting on issuer_hash,
3733 // using DISTINCT to remove duplicates. Then, for each returned groupid, we
3734 // obtain its list of issuers, its list of serials or hashes, and other data.
3736 ok
= ok
&& SecDbWithSQL(dbc
->dbconn
, CFSTR("SELECT DISTINCT groupid FROM issuers ORDER BY issuer_hash ASC"), &localError
, ^bool(sqlite3_stmt
*selectGroups
) {
3737 ok
= ok
&& SecDbForEach(dbc
->dbconn
, selectGroups
, &localError
, ^bool(int row_index
) {
3738 __block
int64_t groupId
= sqlite3_column_int64(selectGroups
, 0);
3739 ok
= ok
&& SecDbWithSQL(dbc
->dbconn
, CFSTR("SELECT flags,format,data FROM groups WHERE groupid=?"), &localError
, ^bool(sqlite3_stmt
*selectGroup
) {
3740 ok
= ok
&& SecDbBindInt64(selectGroup
, 1, groupId
, &localError
);
3741 ok
= ok
&& SecDbStep(dbc
->dbconn
, selectGroup
, &localError
, ^(bool *stop
) {
3742 // per-group info is hashed in the following order:
3743 // - issuer_hash array data (sorted)
3744 // - flag bytes, in order listed below
3745 // - format string [serial|sha256|nto1]
3746 // - add array data (sorted), if [serial|sha256]
3747 // - params (if present)
3748 // - xor data (if present)
3750 int64_t flags
= sqlite3_column_int64(selectGroup
, 0);
3751 bool noCAv2
= (flags
& kSecValidInfoNoCAv2Check
);
3753 // instead of recreating the issuer_hash array in memory,
3754 // hash its length (item count) followed by the data of each issuer_hash.
3755 ok
= ok
&& SecDbWithSQL(dbc
->dbconn
, CFSTR("SELECT count(*) FROM issuers WHERE groupid=?"), &localError
, ^bool(sqlite3_stmt
*selectIssuersCount
) {
3756 ok
= ok
&& SecDbBindInt64(selectIssuersCount
, 1, groupId
, &localError
);
3757 ok
= ok
&& SecDbStep(dbc
->dbconn
, selectIssuersCount
, &localError
, ^void(bool *stop
) {
3758 count
= sqlite3_column_int64(selectIssuersCount
, 0);
3763 uint32_t n
= OSSwapInt32(count
& 0xffffffff);
3764 CC_SHA256_Update(&hash0_ctx
, &n
, sizeof(uint32_t));
3765 CC_SHA256_Update(&hash1_ctx
, &n
, sizeof(uint32_t));
3767 CC_SHA256_Update(&hash2_ctx
, &n
, sizeof(uint32_t));
3770 // process issuer_hash entries for this group
3771 ok
= ok
&& SecDbWithSQL(dbc
->dbconn
, CFSTR("SELECT issuer_hash FROM issuers WHERE groupid=? ORDER BY issuer_hash ASC"), &localError
, ^bool(sqlite3_stmt
*selectIssuerHash
) {
3772 ok
= ok
&& SecDbBindInt64(selectIssuerHash
, 1, groupId
, &localError
);
3773 ok
= ok
&& SecDbForEach(dbc
->dbconn
, selectIssuerHash
, &localError
, ^bool(int row_index
) {
3774 uint8_t *p
= (uint8_t *)sqlite3_column_blob(selectIssuerHash
, 0);
3775 CFDataRef data
= NULL
;
3777 CFIndex length
= (CFIndex
)sqlite3_column_bytes(selectIssuerHash
, 0);
3778 data
= CFDataCreate(kCFAllocatorDefault
, p
, length
);
3781 hashData(data
, &hash0_ctx
);
3782 hashData(data
, &hash1_ctx
);
3784 hashData(data
, &hash2_ctx
);
3795 // process flags, converting to array of unsigned 8-bit values, either 0 or 1:
3796 // [ complete, check-ocsp, known-intermediates-only, no-ca, overridable, require-ct, valid ]
3797 uint8_t C
[8]={0,0,0,0,0,0,0,0};
3798 C
[0] = (flags
& kSecValidInfoComplete
) ? 1 : 0;
3799 C
[1] = (flags
& kSecValidInfoCheckOCSP
) ? 1 : 0;
3800 C
[2] = (flags
& kSecValidInfoKnownOnly
) ? 1 : 0;
3801 C
[3] = (flags
& kSecValidInfoNoCACheck
) ? 1 : 0;
3802 C
[4] = (flags
& kSecValidInfoOverridable
) ? 1 : 0;
3803 C
[5] = (flags
& kSecValidInfoRequireCT
) ? 1 : 0;
3804 C
[6] = (flags
& kSecValidInfoAllowlist
) ? 1 : 0;
3805 CC_SHA256_Update(&hash0_ctx
, C
, sizeof(uint8_t) * 7);
3807 // process format, converting integer to string value [serial|sha256|nto1]
3808 SecValidInfoFormat format
= (SecValidInfoFormat
)sqlite3_column_int(selectGroup
, 1);
3810 case kSecValidInfoFormatSerial
:
3811 hashString(CFSTR("serial"), &hash0_ctx
);
3813 case kSecValidInfoFormatSHA256
:
3814 hashString(CFSTR("sha256"), &hash0_ctx
);
3816 case kSecValidInfoFormatNto1
:
3817 hashString(CFSTR("nto1"), &hash0_ctx
);
3819 case kSecValidInfoFormatUnknown
:
3821 ok
= false; // unexpected format values are not allowed
3824 // process 'add' array (serial or sha256 format).
3825 // instead of recreating the 'add' array in memory,
3826 // hash its length (item count) followed by the data of each entry.
3827 CFStringRef arrayCountSql
= NULL
;
3828 if (format
== kSecValidInfoFormatSerial
) {
3829 arrayCountSql
= CFSTR("SELECT count(*) FROM serials WHERE groupid=?");
3830 } else if (format
== kSecValidInfoFormatSHA256
) {
3831 arrayCountSql
= CFSTR("SELECT count(*) FROM hashes WHERE groupid=?");
3833 if (arrayCountSql
) {
3834 ok
= ok
&& SecDbWithSQL(dbc
->dbconn
, arrayCountSql
, &localError
, ^bool(sqlite3_stmt
*selectAddCount
) {
3835 ok
= ok
&& SecDbBindInt64(selectAddCount
, 1, groupId
, &localError
);
3836 ok
= ok
&& SecDbStep(dbc
->dbconn
, selectAddCount
, &localError
, ^void(bool *stop
) {
3837 count
= sqlite3_column_int64(selectAddCount
, 0);
3842 n
= OSSwapInt32(count
& 0xffffffff);
3843 CC_SHA256_Update(&hash0_ctx
, &n
, sizeof(uint32_t));
3845 // process data entries for this group
3846 CFStringRef arrayDataSql
= NULL
;
3847 if (format
== kSecValidInfoFormatSerial
) {
3848 arrayDataSql
= CFSTR("SELECT serial FROM serials WHERE groupid=? ORDER BY serial ASC");
3849 } else if (format
== kSecValidInfoFormatSHA256
) {
3850 arrayDataSql
= CFSTR("SELECT sha256 FROM hashes WHERE groupid=? ORDER by sha256 ASC");
3853 ok
= ok
&& SecDbWithSQL(dbc
->dbconn
, arrayDataSql
, &localError
, ^bool(sqlite3_stmt
*selectAddData
) {
3854 ok
= ok
&& SecDbBindInt64(selectAddData
, 1, groupId
, &localError
);
3855 ok
= ok
&& SecDbForEach(dbc
->dbconn
, selectAddData
, &localError
, ^bool(int row_index
) {
3856 uint8_t *p
= (uint8_t *)sqlite3_column_blob(selectAddData
, 0);
3857 CFDataRef data
= NULL
;
3859 CFIndex length
= (CFIndex
)sqlite3_column_bytes(selectAddData
, 0);
3860 data
= CFDataCreate(kCFAllocatorDefault
, p
, length
);
3863 hashData(data
, &hash0_ctx
);
3874 // process params and xor data, if format is nto1
3875 if (format
== kSecValidInfoFormatNto1
) {
3876 uint8_t *p
= (uint8_t *)sqlite3_column_blob(selectGroup
, 2);
3877 CFDataRef data
= NULL
;
3879 CFIndex length
= (CFIndex
)sqlite3_column_bytes(selectGroup
, 2);
3880 data
= CFDataCreate(kCFAllocatorDefault
, p
, length
);
3883 // unpack params and xor data
3884 CFDataRef
xor = NULL
;
3885 CFArrayRef params
= NULL
;
3886 if (copyFilterComponents(data
, &xor, ¶ms
)) {
3887 hashArray(params
, &hash0_ctx
);
3888 hashData(xor, &hash0_ctx
);
3893 CFReleaseSafe(params
);
3895 CFReleaseSafe(data
);
3898 // process date constraints [not-after, not-before]
3899 CFAbsoluteTime notBefore
= -3155760000.0; /* default: 1901-01-01 00:00:00-0000 */
3900 CFAbsoluteTime notAfter
= 31556908800.0; /* default: 3001-01-01 00:00:00-0000 */
3901 CFDateRef notBeforeDate
= NULL
;
3902 CFDateRef notAfterDate
= NULL
;
3903 if (_SecRevocationDbCopyDateConstraints(dbc
, groupId
, ¬BeforeDate
, ¬AfterDate
, &localError
)) {
3904 if (notBeforeDate
) {
3905 notBefore
= CFDateGetAbsoluteTime(notBeforeDate
);
3906 CFReleaseNull(notBeforeDate
);
3909 notAfter
= CFDateGetAbsoluteTime(notAfterDate
);
3910 CFReleaseNull(notAfterDate
);
3913 double nb
= htond(notBefore
);
3914 double na
= htond(notAfter
);
3915 CC_SHA256_Update(&hash1_ctx
, &na
, sizeof(double));
3916 CC_SHA256_Update(&hash1_ctx
, &nb
, sizeof(double));
3919 }); // per-group step
3921 }); // per-group select
3923 }); // for each group in list
3925 }); // select full group list
3927 CFMutableArrayRef result
= CFArrayCreateMutable(NULL
, 0, &kCFTypeArrayCallBacks
);
3929 uint8_t digest
[CC_SHA256_DIGEST_LENGTH
];
3930 CFDataRef data
= NULL
;
3931 CC_SHA256_Final(digest
, &hash0_ctx
);
3932 if ((data
= CFDataCreate(NULL
, (const UInt8
*)digest
, CC_SHA256_DIGEST_LENGTH
)) != NULL
) {
3933 CFArrayAppendValue(result
, data
);
3934 CFReleaseNull(data
);
3936 CC_SHA256_Final(digest
, &hash1_ctx
);
3937 if ((data
= CFDataCreate(NULL
, (const UInt8
*)digest
, CC_SHA256_DIGEST_LENGTH
)) != NULL
) {
3938 CFArrayAppendValue(result
, data
);
3939 CFReleaseNull(data
);
3941 CC_SHA256_Final(digest
, &hash2_ctx
);
3942 if ((data
= CFDataCreate(NULL
, (const UInt8
*)digest
, CC_SHA256_DIGEST_LENGTH
)) != NULL
) {
3943 CFArrayAppendValue(result
, data
);
3944 CFReleaseNull(data
);
3947 (void) CFErrorPropagate(localError
, error
);
3951 static bool SecRevocationDbComputeDigests(SecRevocationDbConnectionRef dbc
, CFErrorRef
*error
) {
3952 secinfo("validupdate", "Started verifying db content");
3954 CFArrayRef expectedList
= _SecRevocationDbCopyHashes(dbc
, error
);
3955 CFIndex expectedCount
= (expectedList
) ? CFArrayGetCount(expectedList
) : 0;
3956 if (expectedCount
< 1) {
3957 secinfo("validupdate", "Unable to read db_hash values");
3958 CFReleaseNull(expectedList
);
3959 return result
; // %%%% this will happen on first update, when db_hash isn't there
3961 CFArrayRef computedList
= SecRevocationDbComputeFullContentDigests(dbc
, error
);
3962 CFIndex computedCount
= (computedList
) ? CFArrayGetCount(computedList
) : 0;
3963 for (CFIndex idx
= 0; idx
< expectedCount
; idx
++) {
3964 if (idx
>= computedCount
) {
3965 continue; // server provided additional hash value that we don't yet compute
3967 CFDataRef expectedHash
= (CFDataRef
)CFArrayGetValueAtIndex(expectedList
, idx
);
3968 CFDataRef computedHash
= (CFDataRef
)CFArrayGetValueAtIndex(computedList
, idx
);
3969 if (!CFEqualSafe(expectedHash
, computedHash
)) {
3975 secinfo("validupdate", "Expected: %@", expectedList
);
3976 secinfo("validupdate", "Computed: %@", computedList
);
3978 secinfo("validupdate", "Finished verifying db content; result=%s",
3979 (result
) ? "SUCCESS" : "FAIL");
3980 CFReleaseSafe(expectedList
);
3981 CFReleaseSafe(computedList
);