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 <securityd/SecRevocationDb.h>
30 #include <securityd/OTATrustUtilities.h>
31 #include <securityd/SecRevocationNetworking.h>
32 #include <securityd/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
) {
912 #if !TARGET_OS_BRIDGE
913 /* Schedule as a maintenance task */
914 secdebug("validupdate", "will fetch v%lu from \"%@\"", (unsigned long)version
, server
);
915 return SecValidUpdateRequest(SecRevocationDbGetUpdateQueue(), server
, version
);
921 static CFStringRef
SecRevocationDbGetDefaultServer(void) {
922 #if !TARGET_OS_WATCH && !TARGET_OS_BRIDGE
924 CFStringRef defaultServer
= kValidUpdateSeedServer
;
925 #else // !RC_SEED_BUILD
926 CFStringRef defaultServer
= kValidUpdateProdServer
;
927 #endif // !RC_SEED_BUILD
928 if (os_variant_has_internal_diagnostics("com.apple.security")) {
929 defaultServer
= kValidUpdateCarryServer
;
931 return defaultServer
;
932 #else // TARGET_OS_WATCH || TARGET_OS_BRIDGE
933 /* Because watchOS and bridgeOS can't update over the air, we should
934 * always use the prod server so that the valid database built into the
936 return kValidUpdateProdServer
;
940 void SecRevocationDbInitialize() {
941 if (!isDbOwner()) { return; }
942 os_transaction_t transaction
= os_transaction_create("com.apple.trustd.valid.initialize");
943 __block
bool initializeDb
= false;
945 /* create base path if it doesn't exist */
946 WithPathInRevocationInfoDirectory(NULL
, ^(const char *utf8String
) {
947 (void)mkpath_np(utf8String
, 0755);
950 /* check semaphore file */
951 WithPathInRevocationInfoDirectory(CFSTR(kSecRevocationDbReplaceFile
), ^(const char *path
) {
953 if (stat(path
, &sb
) == 0) {
954 initializeDb
= true; /* file was found, so we will replace the database */
955 if (remove(path
) == -1) {
957 secnotice("validupdate", "remove (%s): %s", path
, strerror(error
));
963 WithPathInRevocationInfoDirectory(CFSTR(kSecRevocationDbFileName
), ^(const char *path
) {
965 /* remove old database file(s) */
966 (void)removeFileWithSuffix(path
, "");
967 (void)removeFileWithSuffix(path
, "-journal");
968 (void)removeFileWithSuffix(path
, "-shm");
969 (void)removeFileWithSuffix(path
, "-wal");
973 if (stat(path
, &sb
) == -1) {
974 initializeDb
= true; /* file not found, so we will create the database */
980 os_release(transaction
);
981 return; /* database exists and doesn't need replacing */
984 /* initialize database from local asset */
985 CFTypeRef value
= (CFStringRef
)CFPreferencesCopyValue(kUpdateServerKey
, kSecPrefsDomain
, kCFPreferencesAnyUser
, kCFPreferencesCurrentHost
);
986 CFStringRef server
= (isString(value
)) ? (CFStringRef
)value
: (CFStringRef
)SecRevocationDbGetDefaultServer();
988 secnotice("validupdate", "initializing database");
989 if (!SecValidUpdateSatisfiedLocally(server
, version
, true)) {
990 #if !TARGET_OS_BRIDGE
991 /* Schedule full update as a maintenance task */
992 (void)SecValidUpdateRequest(SecRevocationDbGetUpdateQueue(), server
, version
);
995 CFReleaseSafe(value
);
996 os_release(transaction
);
1001 // MARK: SecValidInfoRef
1003 ==============================================================================
1005 ==============================================================================
1008 CFGiblisWithCompareFor(SecValidInfo
);
1010 static SecValidInfoRef
SecValidInfoCreate(SecValidInfoFormat format
,
1011 CFOptionFlags flags
,
1014 CFDataRef issuerHash
,
1015 CFDataRef anchorHash
,
1016 CFDateRef notBeforeDate
,
1017 CFDateRef notAfterDate
,
1018 CFDataRef nameConstraints
,
1019 CFDataRef policyConstraints
) {
1020 SecValidInfoRef validInfo
;
1021 validInfo
= CFTypeAllocate(SecValidInfo
, struct __SecValidInfo
, kCFAllocatorDefault
);
1022 if (!validInfo
) { return NULL
; }
1024 CFRetainSafe(certHash
);
1025 CFRetainSafe(issuerHash
);
1026 CFRetainSafe(anchorHash
);
1027 CFRetainSafe(notBeforeDate
);
1028 CFRetainSafe(notAfterDate
);
1029 CFRetainSafe(nameConstraints
);
1030 CFRetainSafe(policyConstraints
);
1032 validInfo
->format
= format
;
1033 validInfo
->certHash
= certHash
;
1034 validInfo
->issuerHash
= issuerHash
;
1035 validInfo
->anchorHash
= anchorHash
;
1036 validInfo
->isOnList
= isOnList
;
1037 validInfo
->valid
= (flags
& kSecValidInfoAllowlist
);
1038 validInfo
->complete
= (flags
& kSecValidInfoComplete
);
1039 validInfo
->checkOCSP
= (flags
& kSecValidInfoCheckOCSP
);
1040 validInfo
->knownOnly
= (flags
& kSecValidInfoKnownOnly
);
1041 validInfo
->requireCT
= (flags
& kSecValidInfoRequireCT
);
1042 validInfo
->noCACheck
= (flags
& kSecValidInfoNoCAv2Check
);
1043 validInfo
->overridable
= (flags
& kSecValidInfoOverridable
);
1044 validInfo
->hasDateConstraints
= (flags
& kSecValidInfoDateConstraints
);
1045 validInfo
->hasNameConstraints
= (flags
& kSecValidInfoNameConstraints
);
1046 validInfo
->hasPolicyConstraints
= (flags
& kSecValidInfoPolicyConstraints
);
1047 validInfo
->notBeforeDate
= notBeforeDate
;
1048 validInfo
->notAfterDate
= notAfterDate
;
1049 validInfo
->nameConstraints
= nameConstraints
;
1050 validInfo
->policyConstraints
= policyConstraints
;
1055 static void SecValidInfoDestroy(CFTypeRef cf
) {
1056 SecValidInfoRef validInfo
= (SecValidInfoRef
)cf
;
1058 CFReleaseNull(validInfo
->certHash
);
1059 CFReleaseNull(validInfo
->issuerHash
);
1060 CFReleaseNull(validInfo
->anchorHash
);
1061 CFReleaseNull(validInfo
->notBeforeDate
);
1062 CFReleaseNull(validInfo
->notAfterDate
);
1063 CFReleaseNull(validInfo
->nameConstraints
);
1064 CFReleaseNull(validInfo
->policyConstraints
);
1068 void SecValidInfoSetAnchor(SecValidInfoRef validInfo
, SecCertificateRef anchor
) {
1072 CFDataRef anchorHash
= NULL
;
1074 anchorHash
= SecCertificateCopySHA256Digest(anchor
);
1076 /* clear no-ca flag for anchors where we want OCSP checked [32523118] */
1077 if (SecIsAppleTrustAnchor(anchor
, 0)) {
1078 validInfo
->noCACheck
= false;
1081 CFReleaseNull(validInfo
->anchorHash
);
1082 validInfo
->anchorHash
= anchorHash
;
1085 static Boolean
SecValidInfoCompare(CFTypeRef a
, CFTypeRef b
) {
1086 SecValidInfoRef validInfoA
= (SecValidInfoRef
)a
;
1087 SecValidInfoRef validInfoB
= (SecValidInfoRef
)b
;
1088 if (validInfoA
== validInfoB
) {
1091 if (!validInfoA
|| !validInfoB
||
1092 (CFGetTypeID(a
) != SecValidInfoGetTypeID()) ||
1093 (CFGetTypeID(b
) != SecValidInfoGetTypeID())) {
1096 return CFEqualSafe(validInfoA
->certHash
, validInfoB
->certHash
) && CFEqualSafe(validInfoA
->issuerHash
, validInfoB
->issuerHash
);
1099 static CFStringRef
SecValidInfoCopyFormatDescription(CFTypeRef cf
, CFDictionaryRef formatOptions
) {
1100 SecValidInfoRef validInfo
= (SecValidInfoRef
)cf
;
1101 CFStringRef certHash
= CFDataCopyHexString(validInfo
->certHash
);
1102 CFStringRef issuerHash
= CFDataCopyHexString(validInfo
->issuerHash
);
1103 CFStringRef desc
= CFStringCreateWithFormat(NULL
, formatOptions
, CFSTR("validInfo certHash: %@ issuerHash: %@"), certHash
, issuerHash
);
1104 CFReleaseNull(certHash
);
1105 CFReleaseNull(issuerHash
);
1111 // MARK: SecRevocationDb
1113 ==============================================================================
1115 ==============================================================================
1118 /* SecRevocationDbCheckNextUpdate returns true if we dispatched an
1119 update request, otherwise false.
1121 static bool _SecRevocationDbCheckNextUpdate(void) {
1122 // are we the db owner instance?
1126 CFTypeRef value
= NULL
;
1128 // is it time to check?
1129 CFAbsoluteTime now
= CFAbsoluteTimeGetCurrent();
1130 CFAbsoluteTime minNextUpdate
= now
+ gUpdateInterval
;
1131 gUpdateStarted
= now
;
1133 if (0 == gNextUpdate
) {
1134 // first time we're called, check if we have a saved nextUpdate value
1135 gNextUpdate
= SecRevocationDbGetNextUpdateTime();
1136 minNextUpdate
= now
;
1137 if (gNextUpdate
< minNextUpdate
) {
1138 gNextUpdate
= minNextUpdate
;
1140 // allow pref to override update interval, if it exists
1141 CFIndex interval
= -1;
1142 value
= (CFNumberRef
)CFPreferencesCopyValue(kUpdateIntervalKey
, kSecPrefsDomain
, kCFPreferencesAnyUser
, kCFPreferencesCurrentHost
);
1143 if (isNumber(value
)) {
1144 if (CFNumberGetValue((CFNumberRef
)value
, kCFNumberCFIndexType
, &interval
)) {
1145 if (interval
< kSecMinUpdateInterval
) {
1146 interval
= kSecMinUpdateInterval
;
1147 } else if (interval
> kSecMaxUpdateInterval
) {
1148 interval
= kSecMaxUpdateInterval
;
1152 CFReleaseNull(value
);
1153 gUpdateInterval
= kSecStdUpdateInterval
;
1155 gUpdateInterval
= interval
;
1157 // pin next update time to the preferred update interval
1158 if (gNextUpdate
> (gUpdateStarted
+ gUpdateInterval
)) {
1159 gNextUpdate
= gUpdateStarted
+ gUpdateInterval
;
1161 secdebug("validupdate", "next update at %f (in %f seconds)",
1162 (double)gUpdateStarted
, (double)gNextUpdate
-gUpdateStarted
);
1164 if (gNextUpdate
> now
) {
1168 secnotice("validupdate", "starting update");
1170 // set minimum next update time here in case we can't get an update
1171 gNextUpdate
= minNextUpdate
;
1173 // determine which server to query
1175 value
= (CFStringRef
)CFPreferencesCopyValue(kUpdateServerKey
, kSecPrefsDomain
, kCFPreferencesAnyUser
, kCFPreferencesCurrentHost
);
1176 if (isString(value
)) {
1177 server
= (CFStringRef
) CFRetain(value
);
1179 server
= (CFStringRef
) CFRetain(SecRevocationDbGetDefaultServer());
1181 CFReleaseNull(value
);
1183 // determine version of our current database
1184 CFIndex version
= SecRevocationDbGetVersion();
1185 secdebug("validupdate", "got version %ld from db", (long)version
);
1187 if (gLastVersion
> 0) {
1188 secdebug("validupdate", "error getting version; using last good version: %ld", (long)gLastVersion
);
1190 version
= gLastVersion
;
1193 // determine source of our current database
1194 // (if this ever changes, we will need to reload the db)
1195 CFStringRef db_source
= SecRevocationDbCopyUpdateSource();
1197 db_source
= (CFStringRef
) CFRetain(kValidUpdateProdServer
);
1200 // determine whether we need to recreate the database
1201 CFIndex db_version
= SecRevocationDbGetSchemaVersion();
1202 CFIndex db_format
= SecRevocationDbGetUpdateFormat();
1203 if (db_version
< kSecRevocationDbSchemaVersion
||
1204 db_format
< kSecRevocationDbUpdateFormat
||
1205 kCFCompareEqualTo
!= CFStringCompare(server
, db_source
, kCFCompareCaseInsensitive
)) {
1206 // we need to fully rebuild the db contents, so we set our version to 0.
1207 version
= gLastVersion
= 0;
1210 // determine whether update fetching is enabled
1211 #if (__MAC_OS_X_VERSION_MIN_REQUIRED >= 101300 || __IPHONE_OS_VERSION_MIN_REQUIRED >= 110000)
1212 bool updateEnabled
= true; // macOS 10.13 or iOS 11.0
1214 bool updateEnabled
= false;
1216 value
= (CFBooleanRef
)CFPreferencesCopyValue(kUpdateEnabledKey
, kSecPrefsDomain
, kCFPreferencesAnyUser
, kCFPreferencesCurrentHost
);
1217 if (isBoolean(value
)) {
1218 updateEnabled
= CFBooleanGetValue((CFBooleanRef
)value
);
1220 CFReleaseNull(value
);
1222 // Schedule maintenance work
1223 bool result
= SecValidUpdateSchedule(updateEnabled
, server
, version
);
1224 CFReleaseNull(server
);
1225 CFReleaseNull(db_source
);
1229 void SecRevocationDbCheckNextUpdate(void) {
1230 static dispatch_once_t once
;
1231 static sec_action_t action
;
1233 dispatch_once(&once
, ^{
1234 dispatch_queue_t update_queue
= SecRevocationDbGetUpdateQueue();
1235 action
= sec_action_create_with_queue(update_queue
, "update_check", kSecMinUpdateInterval
);
1236 sec_action_set_handler(action
, ^{
1237 os_transaction_t transaction
= os_transaction_create("com.apple.trustd.valid.checkNextUpdate");
1238 (void)_SecRevocationDbCheckNextUpdate();
1239 os_release(transaction
);
1242 sec_action_perform(action
);
1245 /* This function verifies an update, in this format:
1246 1) unsigned 32-bit network-byte-order length of binary plist
1247 2) binary plist data
1248 3) unsigned 32-bit network-byte-order length of CMS message
1249 4) CMS message (containing certificates and signature over binary plist)
1251 The length argument is the total size of the packed update data.
1253 bool SecRevocationDbVerifyUpdate(void *update
, CFIndex length
) {
1254 if (!update
|| length
<= (CFIndex
)sizeof(uint32_t)) {
1257 uint32_t plistLength
= OSSwapInt32(*((uint32_t *)update
));
1258 if ((plistLength
+ (CFIndex
)(sizeof(uint32_t)*2)) > (uint64_t) length
) {
1259 secdebug("validupdate", "ERROR: reported plist length (%lu)+%lu exceeds total length (%lu)\n",
1260 (unsigned long)plistLength
, (unsigned long)sizeof(uint32_t)*2, (unsigned long)length
);
1263 uint8_t *plistData
= (uint8_t *)update
+ sizeof(uint32_t);
1264 uint8_t *sigData
= (uint8_t *)plistData
+ plistLength
;
1265 uint32_t sigLength
= OSSwapInt32(*((uint32_t *)sigData
));
1266 sigData
+= sizeof(uint32_t);
1267 if ((plistLength
+ sigLength
+ (CFIndex
)(sizeof(uint32_t) * 2)) != (uint64_t) length
) {
1268 secdebug("validupdate", "ERROR: reported lengths do not add up to total length\n");
1272 OSStatus status
= 0;
1273 CMSSignerStatus signerStatus
;
1274 CMSDecoderRef cms
= NULL
;
1275 SecPolicyRef policy
= NULL
;
1276 SecTrustRef trust
= NULL
;
1277 CFDataRef content
= NULL
;
1279 if ((content
= CFDataCreateWithBytesNoCopy(kCFAllocatorDefault
,
1280 (const UInt8
*)plistData
, (CFIndex
)plistLength
, kCFAllocatorNull
)) == NULL
) {
1281 secdebug("validupdate", "CFDataCreateWithBytesNoCopy failed (%ld bytes)\n", (long)plistLength
);
1285 if ((status
= CMSDecoderCreate(&cms
)) != errSecSuccess
) {
1286 secdebug("validupdate", "CMSDecoderCreate failed with error %d\n", (int)status
);
1289 if ((status
= CMSDecoderUpdateMessage(cms
, sigData
, sigLength
)) != errSecSuccess
) {
1290 secdebug("validupdate", "CMSDecoderUpdateMessage failed with error %d\n", (int)status
);
1293 if ((status
= CMSDecoderSetDetachedContent(cms
, content
)) != errSecSuccess
) {
1294 secdebug("validupdate", "CMSDecoderSetDetachedContent failed with error %d\n", (int)status
);
1297 if ((status
= CMSDecoderFinalizeMessage(cms
)) != errSecSuccess
) {
1298 secdebug("validupdate", "CMSDecoderFinalizeMessage failed with error %d\n", (int)status
);
1302 policy
= SecPolicyCreateApplePinned(CFSTR("ValidUpdate"), // kSecPolicyNameAppleValidUpdate
1303 CFSTR("1.2.840.113635.100.6.2.10"), // System Integration 2 Intermediate Certificate
1304 CFSTR("1.2.840.113635.100.6.51")); // Valid update signing OID
1306 // Check that the first signer actually signed this message.
1307 if ((status
= CMSDecoderCopySignerStatus(cms
, 0, policy
,
1308 false, &signerStatus
, &trust
, NULL
)) != errSecSuccess
) {
1309 secdebug("validupdate", "CMSDecoderCopySignerStatus failed with error %d\n", (int)status
);
1312 // Make sure the signature verifies against the detached content
1313 if (signerStatus
!= kCMSSignerValid
) {
1314 secdebug("validupdate", "ERROR: signature did not verify (signer status %d)\n", (int)signerStatus
);
1315 status
= errSecInvalidSignature
;
1318 // Make sure the signing certificate is valid for the specified policy
1319 SecTrustResultType trustResult
= kSecTrustResultInvalid
;
1320 status
= SecTrustEvaluate(trust
, &trustResult
);
1321 if (status
!= errSecSuccess
) {
1322 secdebug("validupdate", "SecTrustEvaluate failed with error %d (trust=%p)\n", (int)status
, (void *)trust
);
1323 } else if (!(trustResult
== kSecTrustResultUnspecified
|| trustResult
== kSecTrustResultProceed
)) {
1324 secdebug("validupdate", "SecTrustEvaluate failed with trust result %d\n", (int)trustResult
);
1325 status
= errSecVerificationFailure
;
1330 CFReleaseSafe(content
);
1331 CFReleaseSafe(trust
);
1332 CFReleaseSafe(policy
);
1335 return (status
== errSecSuccess
);
1338 CFAbsoluteTime
SecRevocationDbComputeNextUpdateTime(CFIndex updateInterval
) {
1339 CFIndex interval
= updateInterval
;
1340 // try to use interval preference if it exists
1341 CFTypeRef value
= (CFNumberRef
)CFPreferencesCopyValue(kUpdateIntervalKey
, kSecPrefsDomain
, kCFPreferencesAnyUser
, kCFPreferencesCurrentHost
);
1342 if (isNumber(value
)) {
1343 CFNumberGetValue((CFNumberRef
)value
, kCFNumberCFIndexType
, &interval
);
1345 CFReleaseNull(value
);
1347 if (interval
<= 0) {
1348 interval
= kSecStdUpdateInterval
;
1352 if (interval
< kSecMinUpdateInterval
) {
1353 interval
= kSecMinUpdateInterval
;
1354 } else if (interval
> kSecMaxUpdateInterval
) {
1355 interval
= kSecMaxUpdateInterval
;
1358 // compute randomization factor, between 0 and 50% of the interval
1359 CFIndex fuzz
= arc4random() % (long)(interval
/2.0);
1360 CFAbsoluteTime nextUpdate
= CFAbsoluteTimeGetCurrent() + interval
+ fuzz
;
1361 secdebug("validupdate", "next update in %ld seconds", (long)(interval
+ fuzz
));
1365 void SecRevocationDbComputeAndSetNextUpdateTime(void) {
1366 gNextUpdate
= SecRevocationDbComputeNextUpdateTime(0);
1367 (void) SecRevocationDbSetNextUpdateTime(gNextUpdate
, NULL
);
1368 gUpdateStarted
= 0; /* no update is currently in progress */
1371 bool SecRevocationDbIngestUpdate(SecRevocationDbConnectionRef dbc
, CFDictionaryRef update
, CFIndex chunkVersion
, CFIndex
*outVersion
, CFErrorRef
*error
) {
1373 CFIndex version
= 0;
1374 CFErrorRef localError
= NULL
;
1376 SecError(errSecParam
, &localError
, CFSTR("SecRevocationDbIngestUpdate: invalid update parameter"));
1377 goto setVersionAndExit
;
1379 CFTypeRef value
= (CFNumberRef
)CFDictionaryGetValue(update
, CFSTR("version"));
1380 if (isNumber(value
)) {
1381 if (!CFNumberGetValue((CFNumberRef
)value
, kCFNumberCFIndexType
, &version
)) {
1386 // only the first chunk will have a version, so the second and
1387 // subsequent chunks will need to pass it in chunkVersion.
1388 version
= chunkVersion
;
1390 // check precommitted version since update hasn't been committed yet
1391 CFIndex curVersion
= dbc
->precommitVersion
;
1392 if (version
> curVersion
|| chunkVersion
> 0) {
1393 ok
= _SecRevocationDbApplyUpdate(dbc
, update
, version
, &localError
);
1394 secdebug("validupdate", "_SecRevocationDbApplyUpdate=%s, v%ld, precommit=%ld, full=%s",
1395 (ok
) ? "1" : "0", (long)version
, (long)dbc
->precommitVersion
,
1396 (dbc
->fullUpdate
) ? "1" : "0");
1398 secdebug("validupdate", "we have v%ld, skipping update to v%ld",
1399 (long)curVersion
, (long)version
);
1400 version
= -1; // invalid, so we know to skip subsequent chunks
1401 ok
= true; // this is not an error condition
1405 *outVersion
= version
;
1407 (void) CFErrorPropagate(localError
, error
);
1412 /* Database schema */
1414 /* admin table holds these key-value (or key-ival) pairs:
1415 'version' (integer) // version of database content
1416 'check_again' (double) // CFAbsoluteTime of next check (optional)
1417 'db_version' (integer) // version of database schema
1418 'db_hash' (blob) // SHA-256 database hash
1419 --> entries in admin table are unique by text key
1421 issuers table holds map of issuing CA hashes to group identifiers:
1422 groupid (integer) // associated group identifier in group ID table
1423 issuer_hash (blob) // SHA-256 hash of issuer certificate (primary key)
1424 --> entries in issuers table are unique by issuer_hash;
1425 multiple issuer entries may have the same groupid!
1427 groups table holds records with these attributes:
1428 groupid (integer) // ordinal ID associated with this group entry
1429 flags (integer) // a bitmask of the following values:
1430 kSecValidInfoComplete (0x00000001) set if we have all revocation info for this issuer group
1431 kSecValidInfoCheckOCSP (0x00000002) set if must check ocsp for certs from this issuer group
1432 kSecValidInfoKnownOnly (0x00000004) set if any CA from this issuer group must be in database
1433 kSecValidInfoRequireCT (0x00000008) set if all certs from this issuer group must have SCTs
1434 kSecValidInfoAllowlist (0x00000010) set if this entry describes valid certs (i.e. is allowed)
1435 kSecValidInfoNoCACheck (0x00000020) set if this entry does not require an OCSP check to accept (deprecated)
1436 kSecValidInfoOverridable (0x00000040) set if the trust status is recoverable and can be overridden
1437 kSecValidInfoDateConstraints (0x00000080) set if this group has not-before or not-after constraints
1438 kSecValidInfoNameConstraints (0x00000100) set if this group has name constraints in database
1439 kSecValidInfoPolicyConstraints (0x00000200) set if this group has policy constraints in database
1440 kSecValidInfoNoCAv2Check (0x00000400) set if this entry does not require an OCSP check to accept
1441 format (integer) // an integer describing format of entries:
1442 kSecValidInfoFormatUnknown (0) unknown format
1443 kSecValidInfoFormatSerial (1) serial number, not greater than 20 bytes in length
1444 kSecValidInfoFormatSHA256 (2) SHA-256 hash, 32 bytes in length
1445 kSecValidInfoFormatNto1 (3) filter data blob of arbitrary length
1446 data (blob) // Bloom filter data if format is 'nto1', otherwise NULL
1447 policies (blob) // NULL, or uint8_t count value followed by array of int8_t policy values
1448 --> entries in groups table are unique by groupid
1450 serials table holds serial number blobs with these attributes:
1451 groupid (integer) // identifier for issuer group in the groups table
1452 serial (blob) // serial number
1453 --> entries in serials table are unique by serial and groupid
1455 hashes table holds SHA-256 hashes of certificates with these attributes:
1456 groupid (integer) // identifier for issuer group in the groups table
1457 sha256 (blob) // SHA-256 hash of subject certificate
1458 --> entries in hashes table are unique by sha256 and groupid
1460 dates table holds notBefore and notAfter dates (as CFAbsoluteTime) with these attributes:
1461 groupid (integer) // identifier for issuer group in the groups table (primary key)
1462 notbefore (real) // issued certs are invalid if their notBefore is prior to this date
1463 notafter (real) // issued certs are invalid after this date (or their notAfter, if earlier)
1464 --> entries in dates table are unique by groupid, and only exist if kSecValidInfoDateConstraints is true
1467 #define createTablesSQL CFSTR("CREATE TABLE IF NOT EXISTS admin(" \
1468 "key TEXT PRIMARY KEY NOT NULL," \
1469 "ival INTEGER NOT NULL," \
1472 "CREATE TABLE IF NOT EXISTS issuers(" \
1473 "groupid INTEGER NOT NULL," \
1474 "issuer_hash BLOB PRIMARY KEY NOT NULL" \
1476 "CREATE INDEX IF NOT EXISTS issuer_idx ON issuers(issuer_hash);" \
1477 "CREATE TABLE IF NOT EXISTS groups(" \
1478 "groupid INTEGER PRIMARY KEY AUTOINCREMENT," \
1484 "CREATE TABLE IF NOT EXISTS serials(" \
1485 "groupid INTEGER NOT NULL," \
1486 "serial BLOB NOT NULL," \
1487 "UNIQUE(groupid,serial)" \
1489 "CREATE TABLE IF NOT EXISTS hashes(" \
1490 "groupid INTEGER NOT NULL," \
1491 "sha256 BLOB NOT NULL," \
1492 "UNIQUE(groupid,sha256)" \
1494 "CREATE TABLE IF NOT EXISTS dates(" \
1495 "groupid INTEGER PRIMARY KEY NOT NULL," \
1499 "CREATE TRIGGER IF NOT EXISTS group_del " \
1500 "BEFORE DELETE ON groups FOR EACH ROW " \
1502 "DELETE FROM serials WHERE groupid=OLD.groupid; " \
1503 "DELETE FROM hashes WHERE groupid=OLD.groupid; " \
1504 "DELETE FROM issuers WHERE groupid=OLD.groupid; " \
1505 "DELETE FROM dates WHERE groupid=OLD.groupid; " \
1508 #define selectGroupIdSQL CFSTR("SELECT DISTINCT groupid " \
1509 "FROM issuers WHERE issuer_hash=?")
1510 #define selectVersionSQL CFSTR("SELECT ival FROM admin " \
1511 "WHERE key='version'")
1512 #define selectDbVersionSQL CFSTR("SELECT ival FROM admin " \
1513 "WHERE key='db_version'")
1514 #define selectDbFormatSQL CFSTR("SELECT ival FROM admin " \
1515 "WHERE key='db_format'")
1516 #define selectDbHashSQL CFSTR("SELECT value FROM admin " \
1517 "WHERE key='db_hash'")
1518 #define selectDbSourceSQL CFSTR("SELECT value FROM admin " \
1519 "WHERE key='db_source'")
1520 #define selectNextUpdateSQL CFSTR("SELECT value FROM admin " \
1521 "WHERE key='check_again'")
1522 #define selectUpdateIntervalSQL CFSTR("SELECT ival FROM admin " \
1523 "WHERE key='interval'")
1524 #define selectGroupRecordSQL CFSTR("SELECT flags,format,data,policies " \
1525 "FROM groups WHERE groupid=?")
1526 #define selectSerialRecordSQL CFSTR("SELECT rowid FROM serials " \
1527 "WHERE groupid=? AND serial=?")
1528 #define selectDateRecordSQL CFSTR("SELECT notbefore,notafter FROM " \
1529 "dates WHERE groupid=?")
1530 #define selectHashRecordSQL CFSTR("SELECT rowid FROM hashes " \
1531 "WHERE groupid=? AND sha256=?")
1532 #define insertAdminRecordSQL CFSTR("INSERT OR REPLACE INTO admin " \
1533 "(key,ival,value) VALUES (?,?,?)")
1534 #define insertIssuerRecordSQL CFSTR("INSERT OR REPLACE INTO issuers " \
1535 "(groupid,issuer_hash) VALUES (?,?)")
1536 #define insertGroupRecordSQL CFSTR("INSERT OR REPLACE INTO groups " \
1537 "(groupid,flags,format,data,policies) VALUES (?,?,?,?,?)")
1538 #define insertSerialRecordSQL CFSTR("INSERT OR REPLACE INTO serials " \
1539 "(groupid,serial) VALUES (?,?)")
1540 #define deleteSerialRecordSQL CFSTR("DELETE FROM serials " \
1541 "WHERE groupid=? AND hex(serial) LIKE ?")
1542 #define insertSha256RecordSQL CFSTR("INSERT OR REPLACE INTO hashes " \
1543 "(groupid,sha256) VALUES (?,?)")
1544 #define deleteSha256RecordSQL CFSTR("DELETE FROM hashes " \
1545 "WHERE groupid=? AND hex(sha256) LIKE ?")
1546 #define insertDateRecordSQL CFSTR("INSERT OR REPLACE INTO dates " \
1547 "(groupid,notbefore,notafter) VALUES (?,?,?)")
1548 #define deleteGroupRecordSQL CFSTR("DELETE FROM groups " \
1550 #define deleteGroupIssuersSQL CFSTR("DELETE FROM issuers " \
1552 #define addPoliciesColumnSQL CFSTR("ALTER TABLE groups " \
1553 "ADD COLUMN policies BLOB")
1554 #define updateGroupPoliciesSQL CFSTR("UPDATE OR IGNORE groups " \
1555 "SET policies=? WHERE groupid=?")
1557 #define updateConstraintsTablesSQL CFSTR("" \
1558 "CREATE TABLE IF NOT EXISTS dates(" \
1559 "groupid INTEGER PRIMARY KEY NOT NULL," \
1564 #define updateGroupDeleteTriggerSQL CFSTR("" \
1565 "DROP TRIGGER IF EXISTS group_del;" \
1566 "CREATE TRIGGER group_del BEFORE DELETE ON groups FOR EACH ROW " \
1568 "DELETE FROM serials WHERE groupid=OLD.groupid; " \
1569 "DELETE FROM hashes WHERE groupid=OLD.groupid; " \
1570 "DELETE FROM issuers WHERE groupid=OLD.groupid; " \
1571 "DELETE FROM dates WHERE groupid=OLD.groupid; " \
1574 #define deleteAllEntriesSQL CFSTR("" \
1575 "DELETE FROM groups; " \
1576 "DELETE FROM admin WHERE key='version'; " \
1577 "DELETE FROM sqlite_sequence")
1580 /* Database management */
1582 static SecDbRef
SecRevocationDbCreate(CFStringRef path
) {
1583 /* only the db owner should open a read-write connection. */
1584 __block
bool readWrite
= isDbOwner();
1587 SecDbRef result
= SecDbCreate(path
, mode
, readWrite
, false, true, true, 1, ^bool (SecDbRef db
, SecDbConnectionRef dbconn
, bool didCreate
, bool *callMeAgainForNextConnection
, CFErrorRef
*error
) {
1588 __block
bool ok
= true;
1590 ok
&= SecDbTransaction(dbconn
, kSecDbExclusiveTransactionType
, error
, ^(bool *commit
) {
1591 /* Create all database tables, indexes, and triggers.
1592 * SecDbOpen will set auto_vacuum and journal_mode for us before we get called back.*/
1593 ok
= ok
&& SecDbExec(dbconn
, createTablesSQL
, error
);
1597 if (!ok
|| (error
&& *error
)) {
1598 CFIndex errCode
= errSecInternalComponent
;
1599 if (error
&& *error
) {
1600 errCode
= CFErrorGetCode(*error
);
1602 secerror("%s failed: %@", didCreate
? "Create" : "Open", error
? *error
: NULL
);
1603 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationCreate
, TAFatalError
, errCode
);
1611 static dispatch_once_t kSecRevocationDbOnce
;
1612 static SecRevocationDbRef kSecRevocationDb
= NULL
;
1614 static SecRevocationDbRef
SecRevocationDbInit(CFStringRef db_name
) {
1615 SecRevocationDbRef rdb
;
1616 dispatch_queue_attr_t attr
;
1618 require(rdb
= (SecRevocationDbRef
)malloc(sizeof(struct __SecRevocationDb
)), errOut
);
1620 rdb
->update_queue
= NULL
;
1621 rdb
->updateInProgress
= false;
1622 rdb
->unsupportedVersion
= false;
1623 rdb
->changed
= false;
1625 require(rdb
->db
= SecRevocationDbCreate(db_name
), errOut
);
1626 attr
= dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL
, QOS_CLASS_BACKGROUND
, 0);
1627 attr
= dispatch_queue_attr_make_with_autorelease_frequency(attr
, DISPATCH_AUTORELEASE_FREQUENCY_WORK_ITEM
);
1628 require(rdb
->update_queue
= dispatch_queue_create(NULL
, attr
), errOut
);
1629 require(rdb
->info_cache_list
= CFArrayCreateMutable(NULL
, 0, &kCFTypeArrayCallBacks
), errOut
);
1630 require(rdb
->info_cache
= CFDictionaryCreateMutable(NULL
, 0, &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
), errOut
);
1631 rdb
->info_cache_lock
= OS_UNFAIR_LOCK_INIT
;
1634 /* register for changes signaled by the db owner instance */
1636 notify_register_dispatch(kSecRevocationDbChanged
, &out_token
, rdb
->update_queue
, ^(int __unused token
) {
1637 secnotice("validupdate", "Got notification of database change");
1638 SecRevocationDbResetCaches();
1644 secdebug("validupdate", "Failed to create db at \"%@\"", db_name
);
1646 if (rdb
->update_queue
) {
1647 dispatch_release(rdb
->update_queue
);
1649 CFReleaseSafe(rdb
->db
);
1655 static CFStringRef
SecRevocationDbCopyPath(void) {
1656 CFURLRef revDbURL
= NULL
;
1657 CFStringRef revInfoRelPath
= NULL
;
1658 if ((revInfoRelPath
= CFStringCreateWithFormat(NULL
, NULL
, CFSTR("%s"), kSecRevocationDbFileName
)) != NULL
) {
1659 revDbURL
= SecCopyURLForFileInRevocationInfoDirectory(revInfoRelPath
);
1661 CFReleaseSafe(revInfoRelPath
);
1663 CFStringRef revDbPath
= NULL
;
1665 revDbPath
= CFURLCopyFileSystemPath(revDbURL
, kCFURLPOSIXPathStyle
);
1666 CFRelease(revDbURL
);
1671 static void SecRevocationDbWith(void(^dbJob
)(SecRevocationDbRef db
)) {
1672 dispatch_once(&kSecRevocationDbOnce
, ^{
1673 CFStringRef dbPath
= SecRevocationDbCopyPath();
1675 kSecRevocationDb
= SecRevocationDbInit(dbPath
);
1677 if (kSecRevocationDb
&& isDbOwner()) {
1678 /* check and update schema immediately after database is opened */
1679 SecRevocationDbUpdateSchema(kSecRevocationDb
);
1683 // Do pre job run work here (cancel idle timers etc.)
1684 if (kSecRevocationDb
->updateInProgress
) {
1685 return; // this would block since SecDb has an exclusive transaction lock
1687 dbJob(kSecRevocationDb
);
1688 // Do post job run work here (gc timer, etc.)
1691 static bool SecRevocationDbPerformWrite(SecRevocationDbRef rdb
, CFErrorRef
*error
,
1692 bool(^writeJob
)(SecRevocationDbConnectionRef dbc
, CFErrorRef
*blockError
)) {
1693 __block
bool ok
= true;
1694 __block CFErrorRef localError
= NULL
;
1696 ok
&= SecDbPerformWrite(rdb
->db
, &localError
, ^(SecDbConnectionRef dbconn
) {
1697 ok
&= SecDbTransaction(dbconn
, kSecDbImmediateTransactionType
, &localError
, ^(bool *commit
) {
1698 SecRevocationDbConnectionRef dbc
= SecRevocationDbConnectionInit(rdb
, dbconn
, &localError
);
1699 ok
= ok
&& writeJob(dbc
, &localError
);
1704 ok
&= CFErrorPropagate(localError
, error
);
1708 static bool SecRevocationDbPerformRead(SecRevocationDbRef rdb
, CFErrorRef
*error
,
1709 bool(^readJob
)(SecRevocationDbConnectionRef dbc
, CFErrorRef
*blockError
)) {
1710 __block CFErrorRef localError
= NULL
;
1711 __block
bool ok
= true;
1713 ok
&= SecDbPerformRead(rdb
->db
, &localError
, ^(SecDbConnectionRef dbconn
) {
1714 SecRevocationDbConnectionRef dbc
= SecRevocationDbConnectionInit(rdb
, dbconn
, &localError
);
1715 ok
= ok
&& readJob(dbc
, &localError
);
1718 ok
&= CFErrorPropagate(localError
, error
);
1722 static SecRevocationDbConnectionRef
SecRevocationDbConnectionInit(SecRevocationDbRef db
, SecDbConnectionRef dbconn
, CFErrorRef
*error
) {
1723 SecRevocationDbConnectionRef dbc
= NULL
;
1724 CFErrorRef localError
= NULL
;
1726 dbc
= (SecRevocationDbConnectionRef
)malloc(sizeof(struct __SecRevocationDbConnection
));
1729 dbc
->dbconn
= dbconn
;
1730 dbc
->precommitVersion
= (CFIndex
)_SecRevocationDbGetVersion(dbc
, &localError
);
1731 dbc
->precommitDbVersion
= (CFIndex
)_SecRevocationDbGetSchemaVersion(db
, dbc
, &localError
);
1732 dbc
->precommitInterval
= 0; /* set only if we are explicitly given a new value */
1733 dbc
->fullUpdate
= false;
1735 (void) CFErrorPropagate(localError
, error
);
1739 static CF_RETURNS_RETAINED CFDataRef
createCacheKey(CFDataRef certHash
, CFDataRef issuerHash
) {
1740 CFMutableDataRef concat
= CFDataCreateMutableCopy(NULL
, 0, certHash
);
1741 CFDataAppend(concat
, issuerHash
);
1742 CFDataRef result
= SecSHA256DigestCreateFromData(NULL
, concat
);
1743 CFReleaseNull(concat
);
1747 static CF_RETURNS_RETAINED SecValidInfoRef
SecRevocationDbCacheRead(SecRevocationDbRef db
,
1748 SecCertificateRef certificate
,
1749 CFDataRef issuerHash
) {
1753 SecValidInfoRef result
= NULL
;
1754 if (!db
|| !db
->info_cache
|| !db
->info_cache_list
) {
1757 CFIndex ix
= kCFNotFound
;
1758 CFDataRef certHash
= SecCertificateCopySHA256Digest(certificate
);
1759 CFDataRef cacheKey
= createCacheKey(certHash
, issuerHash
);
1761 os_unfair_lock_lock(&db
->info_cache_lock
); // grab the cache lock before using the cache
1762 if (0 <= (ix
= CFArrayGetFirstIndexOfValue(db
->info_cache_list
,
1763 CFRangeMake(0, CFArrayGetCount(db
->info_cache_list
)),
1765 result
= (SecValidInfoRef
)CFDictionaryGetValue(db
->info_cache
, cacheKey
);
1766 // Verify this really is the right result
1767 if (CFEqualSafe(result
->certHash
, certHash
) && CFEqualSafe(result
->issuerHash
, issuerHash
)) {
1768 // Cache hit. Move the entry to the bottom of the list.
1769 CFArrayRemoveValueAtIndex(db
->info_cache_list
, ix
);
1770 CFArrayAppendValue(db
->info_cache_list
, cacheKey
);
1771 secdebug("validcache", "cache hit: %@", cacheKey
);
1773 // Just remove this bad entry
1774 CFArrayRemoveValueAtIndex(db
->info_cache_list
, ix
);
1775 CFDictionaryRemoveValue(db
->info_cache
, cacheKey
);
1776 secdebug("validcache", "cache remove bad: %@", cacheKey
);
1777 secnotice("validcache", "found a bad valid info cache entry at %ld", (long)ix
);
1780 CFRetainSafe(result
);
1781 os_unfair_lock_unlock(&db
->info_cache_lock
);
1782 CFReleaseSafe(certHash
);
1783 CFReleaseSafe(cacheKey
);
1787 static void SecRevocationDbCacheWrite(SecRevocationDbRef db
,
1788 SecValidInfoRef validInfo
) {
1789 if (!db
|| !validInfo
|| !db
->info_cache
|| !db
->info_cache_list
) {
1793 CFDataRef cacheKey
= createCacheKey(validInfo
->certHash
, validInfo
->issuerHash
);
1795 os_unfair_lock_lock(&db
->info_cache_lock
); // grab the cache lock before using the cache
1796 // check to make sure another thread didn't add this entry to the cache already
1797 if (0 > CFArrayGetFirstIndexOfValue(db
->info_cache_list
,
1798 CFRangeMake(0, CFArrayGetCount(db
->info_cache_list
)),
1800 CFDictionaryAddValue(db
->info_cache
, cacheKey
, validInfo
);
1801 if (kSecRevocationDbCacheSize
<= CFArrayGetCount(db
->info_cache_list
)) {
1802 // Remove least recently used cache entry.
1803 secdebug("validcache", "cache remove stale: %@", CFArrayGetValueAtIndex(db
->info_cache_list
, 0));
1804 CFDictionaryRemoveValue(db
->info_cache
, CFArrayGetValueAtIndex(db
->info_cache_list
, 0));
1805 CFArrayRemoveValueAtIndex(db
->info_cache_list
, 0);
1807 CFArrayAppendValue(db
->info_cache_list
, cacheKey
);
1808 secdebug("validcache", "cache add: %@", cacheKey
);
1810 os_unfair_lock_unlock(&db
->info_cache_lock
);
1811 CFReleaseNull(cacheKey
);
1814 static void SecRevocationDbCachePurge(SecRevocationDbRef db
) {
1815 if (!db
|| !db
->info_cache
|| !db
->info_cache_list
) {
1819 /* grab the cache lock and clear all entries */
1820 os_unfair_lock_lock(&db
->info_cache_lock
);
1821 CFArrayRemoveAllValues(db
->info_cache_list
);
1822 CFDictionaryRemoveAllValues(db
->info_cache
);
1823 secdebug("validcache", "cache purge");
1824 os_unfair_lock_unlock(&db
->info_cache_lock
);
1827 static int64_t _SecRevocationDbGetUpdateInterval(SecRevocationDbConnectionRef dbc
, CFErrorRef
*error
) {
1828 /* look up interval entry in admin table; returns -1 on error */
1829 __block
int64_t interval
= -1;
1830 __block
bool ok
= true;
1831 __block CFErrorRef localError
= NULL
;
1833 ok
= ok
&& SecDbWithSQL(dbc
->dbconn
, selectUpdateIntervalSQL
, &localError
, ^bool(sqlite3_stmt
*selectInterval
) {
1834 ok
= ok
&& SecDbStep(dbc
->dbconn
, selectInterval
, &localError
, ^void(bool *stop
) {
1835 interval
= sqlite3_column_int64(selectInterval
, 0);
1840 if (!ok
|| localError
) {
1841 secerror("_SecRevocationDbGetUpdateInterval failed: %@", localError
);
1842 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationRead
, TAFatalError
,
1843 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
1845 (void) CFErrorPropagate(localError
, error
);
1849 static bool _SecRevocationDbSetUpdateInterval(SecRevocationDbConnectionRef dbc
, int64_t interval
, CFErrorRef
*error
) {
1850 secdebug("validupdate", "setting interval to %lld", interval
);
1852 __block CFErrorRef localError
= NULL
;
1853 __block
bool ok
= (dbc
!= NULL
);
1854 ok
= ok
&& SecDbWithSQL(dbc
->dbconn
, insertAdminRecordSQL
, &localError
, ^bool(sqlite3_stmt
*insertInterval
) {
1855 const char *intervalKey
= "interval";
1856 ok
= ok
&& SecDbBindText(insertInterval
, 1, intervalKey
, strlen(intervalKey
),
1857 SQLITE_TRANSIENT
, &localError
);
1858 ok
= ok
&& SecDbBindInt64(insertInterval
, 2,
1859 (sqlite3_int64
)interval
, &localError
);
1860 ok
= ok
&& SecDbStep(dbc
->dbconn
, insertInterval
, &localError
, NULL
);
1863 if (!ok
|| localError
) {
1864 secerror("_SecRevocationDbSetUpdateInterval failed: %@", localError
);
1865 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationWrite
, TAFatalError
,
1866 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
1868 (void) CFErrorPropagate(localError
, error
);
1872 static CFArrayRef
_SecRevocationDbCopyHashes(SecRevocationDbConnectionRef dbc
, CFErrorRef
*error
) {
1873 /* return a retained copy of the db_hash array stored in the admin table; or NULL on error */
1874 __block CFMutableArrayRef hashes
= CFArrayCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeArrayCallBacks
);
1875 __block
bool ok
= (dbc
&& hashes
);
1876 __block CFErrorRef localError
= NULL
;
1878 ok
= ok
&& SecDbWithSQL(dbc
->dbconn
, selectDbHashSQL
, &localError
, ^bool(sqlite3_stmt
*selectDbHash
) {
1879 ok
= ok
&& SecDbStep(dbc
->dbconn
, selectDbHash
, &localError
, ^void(bool *stop
) {
1880 uint8_t *p
= (uint8_t *)sqlite3_column_blob(selectDbHash
, 0);
1881 uint64_t len
= sqlite3_column_bytes(selectDbHash
, 0);
1882 CFIndex hashLen
= CC_SHA256_DIGEST_LENGTH
;
1883 while (p
&& len
>= (uint64_t)hashLen
) {
1884 CFDataRef hash
= CFDataCreate(kCFAllocatorDefault
, (const UInt8
*)p
, hashLen
);
1886 CFArrayAppendValue(hashes
, hash
);
1887 CFReleaseNull(hash
);
1896 if (!ok
|| localError
) {
1897 CFReleaseNull(hashes
);
1899 (void) CFErrorPropagate(localError
, error
);
1903 static bool _SecRevocationDbSetHashes(SecRevocationDbConnectionRef dbc
, CFArrayRef hashes
, CFErrorRef
*error
) {
1904 /* flatten and store db_hash array in the admin table */
1905 __block CFErrorRef localError
= NULL
;
1906 __block
bool ok
= (dbc
&& hashes
);
1908 ok
= ok
&& SecDbWithSQL(dbc
->dbconn
, insertAdminRecordSQL
, &localError
, ^bool(sqlite3_stmt
*insertHashes
) {
1909 CFIndex count
= CFArrayGetCount(hashes
);
1910 CFIndex hashLen
= CC_SHA256_DIGEST_LENGTH
, dataLen
= hashLen
* count
;
1911 uint8_t *dataPtr
= (uint8_t *)calloc(dataLen
, 1);
1912 uint8_t *p
= dataPtr
;
1913 for (CFIndex idx
= 0; idx
< count
&& p
; idx
++) {
1914 CFDataRef hash
= CFArrayGetValueAtIndex(hashes
, idx
);
1915 uint8_t *h
= (hash
) ? (uint8_t *)CFDataGetBytePtr(hash
) : NULL
;
1916 if (h
&& CFDataGetLength(hash
) == hashLen
) { memcpy(p
, h
, hashLen
); }
1919 const char *hashKey
= "db_hash";
1920 ok
= ok
&& SecDbBindText(insertHashes
, 1, hashKey
, strlen(hashKey
),
1921 SQLITE_TRANSIENT
, &localError
);
1922 ok
= ok
&& SecDbBindInt64(insertHashes
, 2,
1923 (sqlite3_int64
)0, &localError
);
1924 ok
= ok
&& SecDbBindBlob(insertHashes
, 3,
1926 SQLITE_TRANSIENT
, &localError
);
1927 ok
= ok
&& SecDbStep(dbc
->dbconn
, insertHashes
, &localError
, NULL
);
1931 if (!ok
|| localError
) {
1933 (void) CFErrorPropagate(localError
, error
);
1937 static int64_t _SecRevocationDbGetVersion(SecRevocationDbConnectionRef dbc
, CFErrorRef
*error
) {
1938 /* look up version entry in admin table; returns -1 on error */
1939 __block
int64_t version
= -1;
1940 __block
bool ok
= (dbc
!= NULL
);
1941 __block CFErrorRef localError
= NULL
;
1943 ok
= ok
&& SecDbWithSQL(dbc
->dbconn
, selectVersionSQL
, &localError
, ^bool(sqlite3_stmt
*selectVersion
) {
1944 ok
= ok
&& SecDbStep(dbc
->dbconn
, selectVersion
, &localError
, ^void(bool *stop
) {
1945 version
= sqlite3_column_int64(selectVersion
, 0);
1950 if (!ok
|| localError
) {
1951 secerror("_SecRevocationDbGetVersion failed: %@", localError
);
1952 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationRead
, TAFatalError
,
1953 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
1955 (void) CFErrorPropagate(localError
, error
);
1959 static bool _SecRevocationDbSetVersion(SecRevocationDbConnectionRef dbc
, CFIndex version
, CFErrorRef
*error
) {
1960 secdebug("validupdate", "setting version to %ld", (long)version
);
1962 __block CFErrorRef localError
= NULL
;
1963 __block
bool ok
= (dbc
!= NULL
);
1964 ok
= ok
&& SecDbWithSQL(dbc
->dbconn
, insertAdminRecordSQL
, &localError
, ^bool(sqlite3_stmt
*insertVersion
) {
1965 const char *versionKey
= "version";
1966 ok
= ok
&& SecDbBindText(insertVersion
, 1, versionKey
, strlen(versionKey
),
1967 SQLITE_TRANSIENT
, &localError
);
1968 ok
= ok
&& SecDbBindInt64(insertVersion
, 2,
1969 (sqlite3_int64
)version
, &localError
);
1970 ok
= ok
&& SecDbStep(dbc
->dbconn
, insertVersion
, &localError
, NULL
);
1973 if (!ok
|| localError
) {
1974 secerror("_SecRevocationDbSetVersion failed: %@", localError
);
1975 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationWrite
, TAFatalError
,
1976 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
1978 (void) CFErrorPropagate(localError
, error
);
1982 static int64_t _SecRevocationDbReadSchemaVersionFromDb(SecRevocationDbConnectionRef dbc
, CFErrorRef
*error
) {
1983 /* look up db_version entry in admin table; returns -1 on error */
1984 __block
int64_t db_version
= -1;
1985 __block
bool ok
= (dbc
!= NULL
);
1986 __block CFErrorRef localError
= NULL
;
1988 ok
= ok
&& SecDbWithSQL(dbc
->dbconn
, selectDbVersionSQL
, &localError
, ^bool(sqlite3_stmt
*selectDbVersion
) {
1989 ok
= ok
&& SecDbStep(dbc
->dbconn
, selectDbVersion
, &localError
, ^void(bool *stop
) {
1990 db_version
= sqlite3_column_int64(selectDbVersion
, 0);
1995 if (!ok
|| localError
) {
1996 secerror("_SecRevocationDbReadSchemaVersionFromDb failed: %@", localError
);
1997 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationRead
, TAFatalError
,
1998 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
2000 (void) CFErrorPropagate(localError
, error
);
2004 static _Atomic
int64_t gSchemaVersion
= -1;
2005 static int64_t _SecRevocationDbGetSchemaVersion(SecRevocationDbRef rdb
, SecRevocationDbConnectionRef dbc
, CFErrorRef
*error
) {
2006 static dispatch_once_t onceToken
;
2007 dispatch_once(&onceToken
, ^{
2009 atomic_init(&gSchemaVersion
, _SecRevocationDbReadSchemaVersionFromDb(dbc
, error
));
2011 (void) SecRevocationDbPerformRead(rdb
, error
, ^bool(SecRevocationDbConnectionRef dbc
, CFErrorRef
*blockError
) {
2012 atomic_init(&gSchemaVersion
, _SecRevocationDbReadSchemaVersionFromDb(dbc
, blockError
));
2017 if (atomic_load(&gSchemaVersion
) == -1) {
2018 /* Initial read(s) failed. Try to read the schema version again. */
2020 atomic_store(&gSchemaVersion
, _SecRevocationDbReadSchemaVersionFromDb(dbc
, error
));
2022 (void) SecRevocationDbPerformRead(rdb
, error
, ^bool(SecRevocationDbConnectionRef dbc
, CFErrorRef
*blockError
) {
2023 atomic_store(&gSchemaVersion
, _SecRevocationDbReadSchemaVersionFromDb(dbc
, blockError
));
2028 return atomic_load(&gSchemaVersion
);
2031 static void SecRevocationDbResetCaches(void) {
2032 SecRevocationDbWith(^(SecRevocationDbRef db
) {
2033 db
->unsupportedVersion
= false;
2034 db
->changed
= false;
2035 (void) SecRevocationDbPerformRead(db
, NULL
, ^bool(SecRevocationDbConnectionRef dbc
, CFErrorRef
*blockError
) {
2036 atomic_store(&gSchemaVersion
, _SecRevocationDbReadSchemaVersionFromDb(dbc
, blockError
));
2039 SecRevocationDbCachePurge(db
);
2043 static bool _SecRevocationDbSetSchemaVersion(SecRevocationDbConnectionRef dbc
, CFIndex dbversion
, CFErrorRef
*error
) {
2044 if (dbversion
> 0) {
2045 int64_t db_version
= (dbc
) ? dbc
->precommitDbVersion
: -1;
2046 if (db_version
>= dbversion
) {
2047 return true; /* requested schema is earlier than current schema */
2050 secdebug("validupdate", "setting db_version to %ld", (long)dbversion
);
2052 __block CFErrorRef localError
= NULL
;
2053 __block
bool ok
= (dbc
!= NULL
);
2054 ok
= ok
&& SecDbWithSQL(dbc
->dbconn
, insertAdminRecordSQL
, &localError
, ^bool(sqlite3_stmt
*insertDbVersion
) {
2055 const char *dbVersionKey
= "db_version";
2056 ok
= ok
&& SecDbBindText(insertDbVersion
, 1, dbVersionKey
, strlen(dbVersionKey
),
2057 SQLITE_TRANSIENT
, &localError
);
2058 ok
= ok
&& SecDbBindInt64(insertDbVersion
, 2,
2059 (sqlite3_int64
)dbversion
, &localError
);
2060 ok
= ok
&& SecDbStep(dbc
->dbconn
, insertDbVersion
, &localError
, NULL
);
2063 if (!ok
|| localError
) {
2064 secerror("_SecRevocationDbSetSchemaVersion failed: %@", localError
);
2065 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationWrite
, TAFatalError
,
2066 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
2068 dbc
->db
->changed
= true; /* will notify clients of this change */
2069 dbc
->db
->unsupportedVersion
= false;
2070 dbc
->precommitDbVersion
= dbversion
;
2071 atomic_store(&gSchemaVersion
, (int64_t)dbversion
);
2073 CFReleaseSafe(localError
);
2077 static bool _SecRevocationDbUpdateSchema(SecRevocationDbConnectionRef dbc
, CFErrorRef
*error
) {
2078 __block CFErrorRef localError
= NULL
;
2079 __block
bool ok
= (dbc
!= NULL
);
2080 __block
int64_t db_version
= (dbc
) ? dbc
->precommitDbVersion
: 0;
2081 if (db_version
>= kSecRevocationDbSchemaVersion
) {
2082 return ok
; /* schema version already up to date */
2084 secdebug("validupdate", "updating db schema from v%lld to v%lld",
2085 (long long)db_version
, (long long)kSecRevocationDbSchemaVersion
);
2087 if (ok
&& db_version
< 5) {
2088 /* apply v5 changes (add dates table and replace trigger) */
2089 ok
&= SecDbWithSQL(dbc
->dbconn
, updateConstraintsTablesSQL
, &localError
, ^bool(sqlite3_stmt
*updateTables
) {
2090 ok
= SecDbStep(dbc
->dbconn
, updateTables
, &localError
, NULL
);
2093 ok
&= SecDbWithSQL(dbc
->dbconn
, updateGroupDeleteTriggerSQL
, &localError
, ^bool(sqlite3_stmt
*updateTrigger
) {
2094 ok
= SecDbStep(dbc
->dbconn
, updateTrigger
, &localError
, NULL
);
2097 secdebug("validupdate", "applied schema update to v5 (%s)", (ok
) ? "ok" : "failed!");
2099 if (ok
&& db_version
< 6) {
2100 /* apply v6 changes (the SecDb layer will update autovacuum mode if needed, so we don't execute
2101 any SQL here, but we do want the database to be replaced in case transaction scope problems
2102 with earlier versions caused missing entries.) */
2103 secdebug("validupdate", "applied schema update to v6 (%s)", (ok
) ? "ok" : "failed!");
2104 if (db_version
> 0) {
2105 SecValidUpdateForceReplaceDatabase();
2108 if (ok
&& db_version
< 7) {
2109 /* apply v7 changes (add policies column in groups table) */
2110 ok
&= SecDbWithSQL(dbc
->dbconn
, addPoliciesColumnSQL
, &localError
, ^bool(sqlite3_stmt
*addPoliciesColumn
) {
2111 ok
= SecDbStep(dbc
->dbconn
, addPoliciesColumn
, &localError
, NULL
);
2114 secdebug("validupdate", "applied schema update to v7 (%s)", (ok
) ? "ok" : "failed!");
2118 secerror("_SecRevocationDbUpdateSchema failed: %@", localError
);
2120 ok
= ok
&& _SecRevocationDbSetSchemaVersion(dbc
, kSecRevocationDbSchemaVersion
, &localError
);
2122 (void) CFErrorPropagate(localError
, error
);
2126 bool SecRevocationDbUpdateSchema(SecRevocationDbRef rdb
) {
2127 /* note: this function assumes it is called only by the database owner.
2128 non-owner (read-only) clients will fail if changes to the db are needed. */
2129 if (!rdb
|| !rdb
->db
) {
2132 __block
bool ok
= true;
2133 __block CFErrorRef localError
= NULL
;
2134 ok
&= SecRevocationDbPerformWrite(rdb
, &localError
, ^bool(SecRevocationDbConnectionRef dbc
, CFErrorRef
*blockError
) {
2135 return _SecRevocationDbUpdateSchema(dbc
, blockError
);
2137 CFReleaseSafe(localError
);
2141 static int64_t _SecRevocationDbGetUpdateFormat(SecRevocationDbConnectionRef dbc
, CFErrorRef
*error
) {
2142 /* look up db_format entry in admin table; returns -1 on error */
2143 __block
int64_t db_format
= -1;
2144 __block
bool ok
= (dbc
!= NULL
);
2145 __block CFErrorRef localError
= NULL
;
2147 ok
= ok
&& SecDbWithSQL(dbc
->dbconn
, selectDbFormatSQL
, &localError
, ^bool(sqlite3_stmt
*selectDbFormat
) {
2148 ok
&= SecDbStep(dbc
->dbconn
, selectDbFormat
, &localError
, ^void(bool *stop
) {
2149 db_format
= sqlite3_column_int64(selectDbFormat
, 0);
2154 if (!ok
|| localError
) {
2155 secerror("_SecRevocationDbGetUpdateFormat failed: %@", localError
);
2156 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationRead
, TAFatalError
,
2157 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
2159 (void) CFErrorPropagate(localError
, error
);
2163 static bool _SecRevocationDbSetUpdateFormat(SecRevocationDbConnectionRef dbc
, CFIndex dbformat
, CFErrorRef
*error
) {
2164 secdebug("validupdate", "setting db_format to %ld", (long)dbformat
);
2166 __block CFErrorRef localError
= NULL
;
2167 __block
bool ok
= (dbc
!= NULL
);
2168 ok
= ok
&& SecDbWithSQL(dbc
->dbconn
, insertAdminRecordSQL
, &localError
, ^bool(sqlite3_stmt
*insertDbFormat
) {
2169 const char *dbFormatKey
= "db_format";
2170 ok
= ok
&& SecDbBindText(insertDbFormat
, 1, dbFormatKey
, strlen(dbFormatKey
),
2171 SQLITE_TRANSIENT
, &localError
);
2172 ok
= ok
&& SecDbBindInt64(insertDbFormat
, 2,
2173 (sqlite3_int64
)dbformat
, &localError
);
2174 ok
= ok
&& SecDbStep(dbc
->dbconn
, insertDbFormat
, &localError
, NULL
);
2177 if (!ok
|| localError
) {
2178 secerror("_SecRevocationDbSetUpdateFormat failed: %@", localError
);
2179 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationWrite
, TAFatalError
,
2180 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
2182 dbc
->db
->changed
= true; /* will notify clients of this change */
2183 dbc
->db
->unsupportedVersion
= false;
2185 (void) CFErrorPropagate(localError
, error
);
2189 static CFStringRef
_SecRevocationDbCopyUpdateSource(SecRevocationDbConnectionRef dbc
, CFErrorRef
*error
) {
2190 /* look up db_source entry in admin table; returns NULL on error */
2191 __block CFStringRef updateSource
= NULL
;
2192 __block
bool ok
= (dbc
!= NULL
);
2193 __block CFErrorRef localError
= NULL
;
2195 ok
= ok
&& SecDbWithSQL(dbc
->dbconn
, selectDbSourceSQL
, &localError
, ^bool(sqlite3_stmt
*selectDbSource
) {
2196 ok
&= SecDbStep(dbc
->dbconn
, selectDbSource
, &localError
, ^void(bool *stop
) {
2197 const UInt8
*p
= (const UInt8
*)sqlite3_column_blob(selectDbSource
, 0);
2199 CFIndex length
= (CFIndex
)sqlite3_column_bytes(selectDbSource
, 0);
2201 updateSource
= CFStringCreateWithBytes(kCFAllocatorDefault
, p
, length
, kCFStringEncodingUTF8
, false);
2208 if (!ok
|| localError
) {
2209 secerror("_SecRevocationDbCopyUpdateSource failed: %@", localError
);
2210 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationRead
, TAFatalError
,
2211 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
2213 (void) CFErrorPropagate(localError
, error
);
2214 return updateSource
;
2217 bool _SecRevocationDbSetUpdateSource(SecRevocationDbConnectionRef dbc
, CFStringRef updateSource
, CFErrorRef
*error
) {
2218 if (!updateSource
) {
2219 secerror("_SecRevocationDbSetUpdateSource failed: %d", errSecParam
);
2222 __block
char buffer
[256];
2223 __block
const char *updateSourceCStr
= CFStringGetCStringPtr(updateSource
, kCFStringEncodingUTF8
);
2224 if (!updateSourceCStr
) {
2225 if (CFStringGetCString(updateSource
, buffer
, 256, kCFStringEncodingUTF8
)) {
2226 updateSourceCStr
= buffer
;
2229 if (!updateSourceCStr
) {
2230 secerror("_SecRevocationDbSetUpdateSource failed: unable to get UTF-8 encoding");
2233 secdebug("validupdate", "setting update source to \"%s\"", updateSourceCStr
);
2235 __block CFErrorRef localError
= NULL
;
2236 __block
bool ok
= (dbc
!= NULL
);
2237 ok
= ok
&& SecDbWithSQL(dbc
->dbconn
, insertAdminRecordSQL
, &localError
, ^bool(sqlite3_stmt
*insertRecord
) {
2238 const char *dbSourceKey
= "db_source";
2239 ok
= ok
&& SecDbBindText(insertRecord
, 1, dbSourceKey
, strlen(dbSourceKey
),
2240 SQLITE_TRANSIENT
, &localError
);
2241 ok
= ok
&& SecDbBindInt64(insertRecord
, 2,
2242 (sqlite3_int64
)0, &localError
);
2243 ok
= ok
&& SecDbBindBlob(insertRecord
, 3,
2244 updateSourceCStr
, strlen(updateSourceCStr
),
2245 SQLITE_TRANSIENT
, &localError
);
2246 ok
= ok
&& SecDbStep(dbc
->dbconn
, insertRecord
, &localError
, NULL
);
2249 if (!ok
|| localError
) {
2250 secerror("_SecRevocationDbSetUpdateSource failed: %@", localError
);
2251 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationWrite
, TAFatalError
,
2252 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
2254 (void) CFErrorPropagate(localError
, error
);
2255 CFReleaseSafe(localError
);
2259 bool SecRevocationDbSetUpdateSource(SecRevocationDbRef rdb
, CFStringRef updateSource
) {
2260 /* note: this function assumes it is called only by the database owner.
2261 non-owner (read-only) clients will fail if changes to the db are needed. */
2262 if (!rdb
|| !rdb
->db
) {
2265 CFErrorRef localError
= NULL
;
2267 ok
&= SecRevocationDbPerformWrite(rdb
, &localError
, ^bool(SecRevocationDbConnectionRef dbc
, CFErrorRef
*error
) {
2268 return _SecRevocationDbSetUpdateSource(dbc
, updateSource
, error
);
2270 CFReleaseSafe(localError
);
2274 static CFAbsoluteTime
_SecRevocationDbGetNextUpdateTime(SecRevocationDbConnectionRef dbc
, CFErrorRef
*error
) {
2275 /* look up check_again entry in admin table; returns 0 on error */
2276 __block CFAbsoluteTime nextUpdate
= 0;
2277 __block
bool ok
= (dbc
!= NULL
);
2278 __block CFErrorRef localError
= NULL
;
2280 ok
= ok
&& SecDbWithSQL(dbc
->dbconn
, selectNextUpdateSQL
, &localError
, ^bool(sqlite3_stmt
*selectNextUpdate
) {
2281 ok
&= SecDbStep(dbc
->dbconn
, selectNextUpdate
, &localError
, ^void(bool *stop
) {
2282 CFAbsoluteTime
*p
= (CFAbsoluteTime
*)sqlite3_column_blob(selectNextUpdate
, 0);
2284 if (sizeof(CFAbsoluteTime
) == sqlite3_column_bytes(selectNextUpdate
, 0)) {
2292 if (!ok
|| localError
) {
2293 secerror("_SecRevocationDbGetNextUpdateTime failed: %@", localError
);
2294 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationRead
, TAFatalError
,
2295 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
2297 (void) CFErrorPropagate(localError
, error
);
2301 static bool _SecRevocationDbSetNextUpdateTime(SecRevocationDbConnectionRef dbc
, CFAbsoluteTime nextUpdate
, CFErrorRef
*error
){
2302 secdebug("validupdate", "setting next update to %f", (double)nextUpdate
);
2304 __block CFErrorRef localError
= NULL
;
2305 __block
bool ok
= (dbc
!= NULL
);
2306 ok
= ok
&& SecDbWithSQL(dbc
->dbconn
, insertAdminRecordSQL
, &localError
, ^bool(sqlite3_stmt
*insertRecord
) {
2307 const char *nextUpdateKey
= "check_again";
2308 ok
= ok
&& SecDbBindText(insertRecord
, 1, nextUpdateKey
, strlen(nextUpdateKey
),
2309 SQLITE_TRANSIENT
, &localError
);
2310 ok
= ok
&& SecDbBindInt64(insertRecord
, 2,
2311 (sqlite3_int64
)0, &localError
);
2312 ok
= ok
&& SecDbBindBlob(insertRecord
, 3,
2313 &nextUpdate
, sizeof(CFAbsoluteTime
),
2314 SQLITE_TRANSIENT
, &localError
);
2315 ok
= ok
&& SecDbStep(dbc
->dbconn
, insertRecord
, &localError
, NULL
);
2318 if (!ok
|| localError
) {
2319 secerror("_SecRevocationDbSetNextUpdate failed: %@", localError
);
2320 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationWrite
, TAFatalError
,
2321 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
2323 (void) CFErrorPropagate(localError
, error
);
2327 bool _SecRevocationDbRemoveAllEntries(SecRevocationDbConnectionRef dbc
, CFErrorRef
*error
) {
2328 /* clear out the contents of the database and start fresh */
2329 bool ok
= (dbc
!= NULL
);
2330 CFErrorRef localError
= NULL
;
2332 /* _SecRevocationDbUpdateSchema was called when db was opened, so no need to do it again. */
2334 /* delete all entries */
2335 ok
= ok
&& SecDbExec(dbc
->dbconn
, deleteAllEntriesSQL
, &localError
);
2336 secnotice("validupdate", "resetting database, result: %d (expected 1)", (ok
) ? 1 : 0);
2338 /* one more thing: update the schema version and format to current */
2339 ok
= ok
&& _SecRevocationDbSetSchemaVersion(dbc
, kSecRevocationDbSchemaVersion
, &localError
);
2340 ok
= ok
&& _SecRevocationDbSetUpdateFormat(dbc
, kSecRevocationDbUpdateFormat
, &localError
);
2342 if (!ok
|| localError
) {
2343 secerror("_SecRevocationDbRemoveAllEntries failed: %@", localError
);
2344 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationWrite
, TAFatalError
,
2345 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
2347 (void) CFErrorPropagate(localError
, error
);
2351 static bool _SecRevocationDbUpdateIssuers(SecRevocationDbConnectionRef dbc
, int64_t groupId
, CFArrayRef issuers
, CFErrorRef
*error
) {
2352 /* insert or replace issuer records in issuers table */
2353 if (!issuers
|| groupId
< 0) {
2354 return false; /* must have something to insert, and a group to associate with it */
2356 __block
bool ok
= (dbc
!= NULL
);
2357 __block CFErrorRef localError
= NULL
;
2358 if (isArray(issuers
)) {
2359 CFIndex issuerIX
, issuerCount
= CFArrayGetCount(issuers
);
2360 for (issuerIX
=0; issuerIX
<issuerCount
&& ok
; issuerIX
++) {
2361 CFDataRef hash
= (CFDataRef
)CFArrayGetValueAtIndex(issuers
, issuerIX
);
2362 if (!hash
) { continue; }
2363 ok
= ok
&& SecDbWithSQL(dbc
->dbconn
, insertIssuerRecordSQL
, &localError
, ^bool(sqlite3_stmt
*insertIssuer
) {
2364 ok
= ok
&& SecDbBindInt64(insertIssuer
, 1,
2365 groupId
, &localError
);
2366 ok
= ok
&& SecDbBindBlob(insertIssuer
, 2,
2367 CFDataGetBytePtr(hash
),
2368 CFDataGetLength(hash
),
2369 SQLITE_TRANSIENT
, &localError
);
2370 /* Execute the insert statement for this issuer record. */
2371 ok
= ok
&& SecDbStep(dbc
->dbconn
, insertIssuer
, &localError
, NULL
);
2376 if (!ok
|| localError
) {
2377 secerror("_SecRevocationDbUpdateIssuers failed: %@", localError
);
2378 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationWrite
, TAFatalError
,
2379 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
2381 (void) CFErrorPropagate(localError
, error
);
2385 static SecValidInfoFormat
_SecRevocationDbGetGroupFormatForData(SecRevocationDbConnectionRef dbc
, int64_t groupId
, CFDataRef data
) {
2386 /* determine existing format if groupId is supplied and this is a partial update,
2387 otherwise return the expected format for the given data. */
2388 SecValidInfoFormat format
= kSecValidInfoFormatUnknown
;
2389 if (groupId
>= 0 && !dbc
->fullUpdate
) {
2390 format
= _SecRevocationDbGetGroupFormat(dbc
, groupId
, NULL
, NULL
, NULL
, NULL
);
2392 if (format
== kSecValidInfoFormatUnknown
&& data
!= NULL
) {
2393 /* group doesn't exist, so determine format based on length of specified data.
2394 len <= 20 is a serial number (actually, <=37, but != 32.)
2395 len==32 is a sha256 hash. otherwise: nto1. */
2396 CFIndex length
= CFDataGetLength(data
);
2398 format
= kSecValidInfoFormatSHA256
;
2399 } else if (length
<= 37) {
2400 format
= kSecValidInfoFormatSerial
;
2401 } else if (length
> 0) {
2402 format
= kSecValidInfoFormatNto1
;
2408 static bool _SecRevocationDbUpdateIssuerData(SecRevocationDbConnectionRef dbc
, int64_t groupId
, CFDictionaryRef dict
, CFErrorRef
*error
) {
2409 /* update/delete records in serials or hashes table. */
2410 if (!dict
|| groupId
< 0) {
2411 return false; /* must have something to insert, and a group to associate with it */
2413 __block
bool ok
= (dbc
!= NULL
);
2414 __block CFErrorRef localError
= NULL
;
2415 /* process deletions */
2416 CFArrayRef deleteArray
= (CFArrayRef
)CFDictionaryGetValue(dict
, CFSTR("delete"));
2417 if (isArray(deleteArray
)) {
2418 SecValidInfoFormat format
= kSecValidInfoFormatUnknown
;
2419 CFIndex processed
=0, identifierIX
, identifierCount
= CFArrayGetCount(deleteArray
);
2420 for (identifierIX
=0; identifierIX
<identifierCount
; identifierIX
++) {
2421 CFDataRef identifierData
= (CFDataRef
)CFArrayGetValueAtIndex(deleteArray
, identifierIX
);
2422 if (!identifierData
) { continue; }
2423 if (format
== kSecValidInfoFormatUnknown
) {
2424 format
= _SecRevocationDbGetGroupFormatForData(dbc
, groupId
, identifierData
);
2426 CFStringRef sql
= NULL
;
2427 if (format
== kSecValidInfoFormatSerial
) {
2428 sql
= deleteSerialRecordSQL
;
2429 } else if (format
== kSecValidInfoFormatSHA256
) {
2430 sql
= deleteSha256RecordSQL
;
2432 if (!sql
) { continue; }
2434 ok
= ok
&& SecDbWithSQL(dbc
->dbconn
, sql
, &localError
, ^bool(sqlite3_stmt
*deleteIdentifier
) {
2435 /* (groupid,serial|sha256) */
2436 CFDataRef hexData
= cfToHexData(identifierData
, true);
2437 if (!hexData
) { return false; }
2438 ok
= ok
&& SecDbBindInt64(deleteIdentifier
, 1,
2439 groupId
, &localError
);
2440 ok
= ok
&& SecDbBindBlob(deleteIdentifier
, 2,
2441 CFDataGetBytePtr(hexData
),
2442 CFDataGetLength(hexData
),
2443 SQLITE_TRANSIENT
, &localError
);
2444 /* Execute the delete statement for the identifier record. */
2445 ok
= ok
&& SecDbStep(dbc
->dbconn
, deleteIdentifier
, &localError
, NULL
);
2446 CFReleaseSafe(hexData
);
2449 if (ok
) { ++processed
; }
2452 secdebug("validupdate", "Processed %ld of %ld deletions for group %lld, result=%s",
2453 processed
, identifierCount
, groupId
, (ok
) ? "true" : "false");
2456 /* process additions */
2457 CFArrayRef addArray
= (CFArrayRef
)CFDictionaryGetValue(dict
, CFSTR("add"));
2458 if (isArray(addArray
)) {
2459 SecValidInfoFormat format
= kSecValidInfoFormatUnknown
;
2460 CFIndex processed
=0, identifierIX
, identifierCount
= CFArrayGetCount(addArray
);
2461 for (identifierIX
=0; identifierIX
<identifierCount
; identifierIX
++) {
2462 CFDataRef identifierData
= (CFDataRef
)CFArrayGetValueAtIndex(addArray
, identifierIX
);
2463 if (!identifierData
) { continue; }
2464 if (format
== kSecValidInfoFormatUnknown
) {
2465 format
= _SecRevocationDbGetGroupFormatForData(dbc
, groupId
, identifierData
);
2467 CFStringRef sql
= NULL
;
2468 if (format
== kSecValidInfoFormatSerial
) {
2469 sql
= insertSerialRecordSQL
;
2470 } else if (format
== kSecValidInfoFormatSHA256
) {
2471 sql
= insertSha256RecordSQL
;
2473 if (!sql
) { continue; }
2475 ok
= ok
&& SecDbWithSQL(dbc
->dbconn
, sql
, &localError
, ^bool(sqlite3_stmt
*insertIdentifier
) {
2476 /* rowid,(groupid,serial|sha256) */
2477 /* rowid is autoincremented and we never set it directly */
2478 ok
= ok
&& SecDbBindInt64(insertIdentifier
, 1,
2479 groupId
, &localError
);
2480 ok
= ok
&& SecDbBindBlob(insertIdentifier
, 2,
2481 CFDataGetBytePtr(identifierData
),
2482 CFDataGetLength(identifierData
),
2483 SQLITE_TRANSIENT
, &localError
);
2484 /* Execute the insert statement for the identifier record. */
2485 ok
= ok
&& SecDbStep(dbc
->dbconn
, insertIdentifier
, &localError
, NULL
);
2488 if (ok
) { ++processed
; }
2491 secdebug("validupdate", "Processed %ld of %ld additions for group %lld, result=%s",
2492 processed
, identifierCount
, groupId
, (ok
) ? "true" : "false");
2495 if (!ok
|| localError
) {
2496 secerror("_SecRevocationDbUpdatePerIssuerData failed: %@", localError
);
2497 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationWrite
, TAFatalError
,
2498 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
2500 (void) CFErrorPropagate(localError
, error
);
2504 static bool _SecRevocationDbCopyDateConstraints(SecRevocationDbConnectionRef dbc
,
2505 int64_t groupId
, CFDateRef
*notBeforeDate
, CFDateRef
*notAfterDate
, CFErrorRef
*error
) {
2506 /* return true if one or both date constraints exist for a given groupId.
2507 the actual constraints are optionally returned in output CFDateRef parameters.
2508 caller is responsible for releasing date and error parameters, if provided.
2510 __block
bool ok
= (dbc
!= NULL
);
2511 __block CFDateRef localNotBefore
= NULL
;
2512 __block CFDateRef localNotAfter
= NULL
;
2513 __block CFErrorRef localError
= NULL
;
2515 ok
= ok
&& SecDbWithSQL(dbc
->dbconn
, selectDateRecordSQL
, &localError
, ^bool(sqlite3_stmt
*selectDates
) {
2516 /* (groupid,notbefore,notafter) */
2517 ok
&= SecDbBindInt64(selectDates
, 1, groupId
, &localError
);
2518 ok
= ok
&& SecDbStep(dbc
->dbconn
, selectDates
, &localError
, ^(bool *stop
) {
2519 /* if column has no value, its type will be SQLITE_NULL */
2520 if (SQLITE_NULL
!= sqlite3_column_type(selectDates
, 0)) {
2521 CFAbsoluteTime nb
= (CFAbsoluteTime
)sqlite3_column_double(selectDates
, 0);
2522 localNotBefore
= CFDateCreate(NULL
, nb
);
2524 if (SQLITE_NULL
!= sqlite3_column_type(selectDates
, 1)) {
2525 CFAbsoluteTime na
= (CFAbsoluteTime
)sqlite3_column_double(selectDates
, 1);
2526 localNotAfter
= CFDateCreate(NULL
, na
);
2531 /* must have at least one date constraint to return true.
2532 since date constraints are optional, not finding any should not log an error. */
2533 ok
= ok
&& !localError
&& (localNotBefore
!= NULL
|| localNotAfter
!= NULL
);
2535 secerror("_SecRevocationDbCopyDateConstraints failed: %@", localError
);
2536 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationRead
, TAFatalError
,
2537 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
2540 CFReleaseNull(localNotBefore
);
2541 CFReleaseNull(localNotAfter
);
2543 if (notBeforeDate
) {
2544 *notBeforeDate
= localNotBefore
;
2546 CFReleaseSafe(localNotBefore
);
2549 *notAfterDate
= localNotAfter
;
2551 CFReleaseSafe(localNotAfter
);
2554 (void) CFErrorPropagate(localError
, error
);
2558 static bool _SecRevocationDbUpdateDateConstraints(SecRevocationDbConnectionRef dbc
, int64_t groupId
, CFDictionaryRef dict
, CFErrorRef
*error
) {
2559 /* Called only from _SecRevocationDbUpdateIssuerConstraints.
2560 Function assumes that the caller has checked the input arguments.
2562 __block
bool ok
= true;
2563 __block CFErrorRef localError
= NULL
;
2564 __block CFAbsoluteTime notBefore
= -3155760000.0; /* default: 1901-01-01 00:00:00-0000 */
2565 __block CFAbsoluteTime notAfter
= 31556908800.0; /* default: 3001-01-01 00:00:00-0000 */
2567 CFDateRef notBeforeDate
= (CFDateRef
)CFDictionaryGetValue(dict
, CFSTR("not-before"));
2568 CFDateRef notAfterDate
= (CFDateRef
)CFDictionaryGetValue(dict
, CFSTR("not-after"));
2570 if (isDate(notBeforeDate
)) {
2571 notBefore
= CFDateGetAbsoluteTime(notBeforeDate
);
2573 notBeforeDate
= NULL
;
2575 if (isDate(notAfterDate
)) {
2576 notAfter
= CFDateGetAbsoluteTime(notAfterDate
);
2578 notAfterDate
= NULL
;
2580 if (!(notBeforeDate
|| notAfterDate
)) {
2581 return ok
; /* no dates supplied, so we have nothing to update for this issuer */
2584 if (!(notBeforeDate
&& notAfterDate
) && !dbc
->fullUpdate
) {
2585 /* only one date was supplied, so check for existing date constraints */
2586 CFDateRef curNotBeforeDate
= NULL
;
2587 CFDateRef curNotAfterDate
= NULL
;
2588 if (_SecRevocationDbCopyDateConstraints(dbc
, groupId
, &curNotBeforeDate
,
2589 &curNotAfterDate
, &localError
)) {
2590 if (!notBeforeDate
) {
2591 notBeforeDate
= curNotBeforeDate
;
2592 notBefore
= CFDateGetAbsoluteTime(notBeforeDate
);
2594 CFReleaseSafe(curNotBeforeDate
);
2596 if (!notAfterDate
) {
2597 notAfterDate
= curNotAfterDate
;
2598 notAfter
= CFDateGetAbsoluteTime(notAfterDate
);
2600 CFReleaseSafe(curNotAfterDate
);
2604 ok
= ok
&& SecDbWithSQL(dbc
->dbconn
, insertDateRecordSQL
, &localError
, ^bool(sqlite3_stmt
*insertDate
) {
2605 /* (groupid,notbefore,notafter) */
2606 ok
= ok
&& SecDbBindInt64(insertDate
, 1, groupId
, &localError
);
2607 ok
= ok
&& SecDbBindDouble(insertDate
, 2, notBefore
, &localError
);
2608 ok
= ok
&& SecDbBindDouble(insertDate
, 3, notAfter
, &localError
);
2609 ok
= ok
&& SecDbStep(dbc
->dbconn
, insertDate
, &localError
, NULL
);
2613 if (!ok
|| localError
) {
2614 secinfo("validupdate", "_SecRevocationDbUpdateDateConstraints failed (ok=%s, localError=%@)",
2615 (ok
) ? "1" : "0", localError
);
2616 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationWrite
, TAFatalError
,
2617 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
2619 (void) CFErrorPropagate(localError
, error
);
2623 static bool _SecRevocationDbUpdatePolicyConstraints(SecRevocationDbConnectionRef dbc
, int64_t groupId
, CFDictionaryRef dict
, CFErrorRef
*error
) {
2624 /* Called only from _SecRevocationDbUpdateIssuerConstraints.
2625 Function assumes that the caller has checked the input arguments.
2627 __block
bool ok
= true;
2628 __block CFErrorRef localError
= NULL
;
2629 CFArrayRef policies
= (CFArrayRef
)CFDictionaryGetValue(dict
, CFSTR("policies"));
2630 if (!isArray(policies
)) {
2631 return ok
; /* no policies supplied, so nothing to update for this issuer */
2634 __block CFDataRef data
= createPoliciesData(policies
);
2635 ok
= data
&& SecDbWithSQL(dbc
->dbconn
, updateGroupPoliciesSQL
, &localError
, ^bool(sqlite3_stmt
*updatePolicies
) {
2636 /* (policies,groupid) */
2637 ok
= ok
&& SecDbBindBlob(updatePolicies
, 1,
2638 CFDataGetBytePtr(data
),
2639 CFDataGetLength(data
),
2640 SQLITE_TRANSIENT
, &localError
);
2641 ok
= ok
&& SecDbBindInt64(updatePolicies
, 2, groupId
, &localError
);
2642 ok
= ok
&& SecDbStep(dbc
->dbconn
, updatePolicies
, &localError
, NULL
);
2645 CFReleaseSafe(data
);
2647 if (!ok
|| localError
) {
2648 secinfo("validupdate", "_SecRevocationDbUpdatePolicyConstraints failed (ok=%s, localError=%@)",
2649 (ok
) ? "1" : "0", localError
);
2650 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationWrite
, TAFatalError
,
2651 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
2653 (void) CFErrorPropagate(localError
, error
);
2657 static bool _SecRevocationDbUpdateNameConstraints(SecRevocationDbConnectionRef dbc
, int64_t groupId
, CFDictionaryRef dict
, CFErrorRef
*error
) {
2658 /* Called only from _SecRevocationDbUpdateIssuerConstraints.
2659 Function assumes that the caller has checked the input arguments.
2662 /* %%% (TBI:9254570) update name constraint entries here */
2666 static bool _SecRevocationDbUpdateIssuerConstraints(SecRevocationDbConnectionRef dbc
, int64_t groupId
, CFDictionaryRef dict
, CFErrorRef
*error
) {
2667 /* check input arguments */
2668 if (!dbc
|| !dict
|| groupId
< 0) {
2672 ok
= ok
&& _SecRevocationDbUpdateDateConstraints(dbc
, groupId
, dict
, error
);
2673 ok
= ok
&& _SecRevocationDbUpdateNameConstraints(dbc
, groupId
, dict
, error
);
2674 ok
= ok
&& _SecRevocationDbUpdatePolicyConstraints(dbc
, groupId
, dict
, error
);
2678 static SecValidInfoFormat
_SecRevocationDbGetGroupFormat(SecRevocationDbConnectionRef dbc
,
2679 int64_t groupId
, SecValidInfoFlags
*flags
, CFDataRef
*data
, CFDataRef
*policies
, CFErrorRef
*error
) {
2680 /* return group record fields for a given groupId.
2681 on success, returns a non-zero format type, and other field values in optional output parameters.
2682 caller is responsible for releasing data, policies, and error parameters, if provided.
2684 __block
bool ok
= (dbc
!= NULL
);
2685 __block SecValidInfoFormat format
= 0;
2686 __block CFErrorRef localError
= NULL
;
2688 /* Select the group record to determine flags and format. */
2689 ok
= ok
&& SecDbWithSQL(dbc
->dbconn
, selectGroupRecordSQL
, &localError
, ^bool(sqlite3_stmt
*selectGroup
) {
2690 ok
= ok
&& SecDbBindInt64(selectGroup
, 1, groupId
, &localError
);
2691 ok
= ok
&& SecDbStep(dbc
->dbconn
, selectGroup
, &localError
, ^(bool *stop
) {
2693 *flags
= (SecValidInfoFlags
)sqlite3_column_int(selectGroup
, 0);
2695 format
= (SecValidInfoFormat
)sqlite3_column_int(selectGroup
, 1);
2697 //%%% stream the data from the db into a streamed decompression <rdar://32142637>
2698 uint8_t *p
= (uint8_t *)sqlite3_column_blob(selectGroup
, 2);
2699 if (p
!= NULL
&& format
== kSecValidInfoFormatNto1
) {
2700 CFIndex length
= (CFIndex
)sqlite3_column_bytes(selectGroup
, 2);
2701 *data
= CFDataCreate(kCFAllocatorDefault
, p
, length
);
2705 uint8_t *p
= (uint8_t *)sqlite3_column_blob(selectGroup
, 3);
2707 CFIndex length
= (CFIndex
)sqlite3_column_bytes(selectGroup
, 3);
2708 *policies
= CFDataCreate(kCFAllocatorDefault
, p
, length
);
2714 if (!ok
|| localError
) {
2715 secdebug("validupdate", "GetGroupFormat for groupId %lu failed", (unsigned long)groupId
);
2716 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationRead
, TAFatalError
,
2717 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
2718 format
= kSecValidInfoFormatUnknown
;
2720 (void) CFErrorPropagate(localError
, error
);
2721 if (!(format
> kSecValidInfoFormatUnknown
)) {
2722 secdebug("validupdate", "GetGroupFormat: got format %d for groupId %lld", format
, (long long)groupId
);
2727 static bool _SecRevocationDbUpdateFlags(CFDictionaryRef dict
, CFStringRef key
, SecValidInfoFlags mask
, SecValidInfoFlags
*flags
) {
2728 /* If a boolean value exists in the given dictionary for the given key,
2729 or an explicit "1" or "0" is specified as the key string,
2730 set or clear the corresponding bit(s) defined by the mask argument.
2731 Function returns true if the flags value was changed, false otherwise.
2733 if (!isDictionary(dict
) || !isString(key
) || !flags
) {
2736 bool hasValue
= false, newValue
= false, result
= false;
2737 CFTypeRef value
= (CFBooleanRef
)CFDictionaryGetValue(dict
, key
);
2738 if (isBoolean(value
)) {
2739 newValue
= CFBooleanGetValue((CFBooleanRef
)value
);
2741 } else if (BOOL_STRING_KEY_LENGTH
== CFStringGetLength(key
)) {
2742 if (CFStringCompare(key
, kBoolTrueKey
, 0) == kCFCompareEqualTo
) {
2743 hasValue
= newValue
= true;
2744 } else if (CFStringCompare(key
, kBoolFalseKey
, 0) == kCFCompareEqualTo
) {
2749 SecValidInfoFlags oldFlags
= *flags
;
2755 result
= (*flags
!= oldFlags
);
2760 static bool _SecRevocationDbUpdateFilter(CFDictionaryRef dict
, CFDataRef oldData
, CFDataRef
* __nonnull CF_RETURNS_RETAINED xmlData
) {
2761 /* If xor and/or params values exist in the given dictionary, create a new
2762 property list containing the updated values, and return as a flattened
2763 data blob in the xmlData output parameter (note: caller must release.)
2764 Function returns true if there is new xmlData to save, false otherwise.
2766 bool result
= false;
2767 bool xorProvided
= false;
2768 bool paramsProvided
= false;
2769 bool missingData
= false;
2771 if (!dict
|| !xmlData
) {
2772 return result
; /* no-op if no dictionary is provided, or no way to update the data */
2775 CFDataRef xorCurrent
= NULL
;
2776 CFDataRef xorUpdate
= (CFDataRef
)CFDictionaryGetValue(dict
, CFSTR("xor"));
2777 if (isData(xorUpdate
)) {
2780 CFArrayRef paramsCurrent
= NULL
;
2781 CFArrayRef paramsUpdate
= (CFArrayRef
)CFDictionaryGetValue(dict
, CFSTR("params"));
2782 if (isArray(paramsUpdate
)) {
2783 paramsProvided
= true;
2785 if (!(xorProvided
|| paramsProvided
)) {
2786 return result
; /* nothing to update, so we can bail out here. */
2789 CFPropertyListRef nto1Current
= NULL
;
2790 CFMutableDictionaryRef nto1Update
= CFDictionaryCreateMutable(kCFAllocatorDefault
, 0,
2791 &kCFTypeDictionaryKeyCallBacks
,
2792 &kCFTypeDictionaryValueCallBacks
);
2797 /* turn old data into property list */
2798 CFDataRef data
= (CFDataRef
)CFRetainSafe(oldData
);
2799 CFDataRef inflatedData
= copyInflatedData(data
);
2801 CFReleaseSafe(data
);
2802 data
= inflatedData
;
2805 nto1Current
= CFPropertyListCreateWithData(kCFAllocatorDefault
, data
, 0, NULL
, NULL
);
2806 CFReleaseSafe(data
);
2809 xorCurrent
= (CFDataRef
)CFDictionaryGetValue((CFDictionaryRef
)nto1Current
, CFSTR("xor"));
2810 paramsCurrent
= (CFArrayRef
)CFDictionaryGetValue((CFDictionaryRef
)nto1Current
, CFSTR("params"));
2813 /* set current or updated xor data in new property list */
2815 CFDataRef xorNew
= NULL
;
2817 CFIndex xorUpdateLen
= CFDataGetLength(xorUpdate
);
2818 CFMutableDataRef
xor = CFDataCreateMutableCopy(NULL
, 0, xorCurrent
);
2819 if (xor && xorUpdateLen
> 0) {
2820 /* truncate or zero-extend data to match update size */
2821 CFDataSetLength(xor, xorUpdateLen
);
2822 /* exclusive-or update bytes over the existing data */
2823 UInt8
*xorP
= (UInt8
*)CFDataGetMutableBytePtr(xor);
2824 UInt8
*updP
= (UInt8
*)CFDataGetBytePtr(xorUpdate
);
2826 for (int idx
= 0; idx
< xorUpdateLen
; idx
++) {
2827 xorP
[idx
] = xorP
[idx
] ^ updP
[idx
];
2831 xorNew
= (CFDataRef
)xor;
2833 xorNew
= (CFDataRef
)CFRetainSafe(xorUpdate
);
2836 CFDictionaryAddValue(nto1Update
, CFSTR("xor"), xorNew
);
2837 CFReleaseSafe(xorNew
);
2839 secdebug("validupdate", "Failed to get updated filter data");
2842 } else if (xorCurrent
) {
2843 /* not provided, so use existing xor value */
2844 CFDictionaryAddValue(nto1Update
, CFSTR("xor"), xorCurrent
);
2846 secdebug("validupdate", "Failed to get current filter data");
2850 /* set current or updated params in new property list */
2851 if (paramsProvided
) {
2852 CFDictionaryAddValue(nto1Update
, CFSTR("params"), paramsUpdate
);
2853 } else if (paramsCurrent
) {
2854 /* not provided, so use existing params value */
2855 CFDictionaryAddValue(nto1Update
, CFSTR("params"), paramsCurrent
);
2857 /* missing params: neither provided nor existing */
2858 secdebug("validupdate", "Failed to get current filter params");
2862 CFReleaseSafe(nto1Current
);
2864 *xmlData
= CFPropertyListCreateData(kCFAllocatorDefault
, nto1Update
,
2865 kCFPropertyListXMLFormat_v1_0
,
2867 result
= (*xmlData
!= NULL
);
2869 CFReleaseSafe(nto1Update
);
2871 /* compress the xmlData blob, if possible */
2873 CFDataRef deflatedData
= copyDeflatedData(*xmlData
);
2875 if (CFDataGetLength(deflatedData
) < CFDataGetLength(*xmlData
)) {
2876 CFRelease(*xmlData
);
2877 *xmlData
= deflatedData
;
2879 CFRelease(deflatedData
);
2887 static int64_t _SecRevocationDbUpdateGroup(SecRevocationDbConnectionRef dbc
, int64_t groupId
, CFDictionaryRef dict
, CFErrorRef
*error
) {
2888 /* insert group record for a given groupId.
2889 if the specified groupId is < 0, a new group entry is created.
2890 returns the groupId on success, or -1 on failure.
2893 return groupId
; /* no-op if no dictionary is provided */
2896 __block
int64_t result
= -1;
2897 __block
bool ok
= (dbc
!= NULL
);
2898 __block
bool isFormatChange
= false;
2899 __block CFErrorRef localError
= NULL
;
2901 __block SecValidInfoFlags flags
= 0;
2902 __block SecValidInfoFormat format
= kSecValidInfoFormatUnknown
;
2903 __block SecValidInfoFormat formatUpdate
= kSecValidInfoFormatUnknown
;
2904 __block CFDataRef data
= NULL
;
2905 __block CFDataRef policies
= NULL
;
2908 /* fetch the flags and data for an existing group record, in case some are being changed. */
2910 format
= _SecRevocationDbGetGroupFormat(dbc
, groupId
, &flags
, &data
, &policies
, NULL
);
2912 if (format
== kSecValidInfoFormatUnknown
) {
2913 secdebug("validupdate", "existing group %lld has unknown format %d, flags=0x%lx",
2914 (long long)groupId
, format
, flags
);
2915 //%%% clean up by deleting all issuers with this groupId, then the group record,
2916 // or just force a full update? note: we can get here if we fail to bind the
2917 // format value in the prepared SQL statement below.
2921 CFTypeRef value
= (CFStringRef
)CFDictionaryGetValue(dict
, CFSTR("format"));
2922 if (isString(value
)) {
2923 if (CFStringCompare((CFStringRef
)value
, CFSTR("serial"), 0) == kCFCompareEqualTo
) {
2924 formatUpdate
= kSecValidInfoFormatSerial
;
2925 } else if (CFStringCompare((CFStringRef
)value
, CFSTR("sha256"), 0) == kCFCompareEqualTo
) {
2926 formatUpdate
= kSecValidInfoFormatSHA256
;
2927 } else if (CFStringCompare((CFStringRef
)value
, CFSTR("nto1"), 0) == kCFCompareEqualTo
) {
2928 formatUpdate
= kSecValidInfoFormatNto1
;
2931 /* if format value is explicitly supplied, then this is effectively a new group entry. */
2932 isFormatChange
= (formatUpdate
> kSecValidInfoFormatUnknown
&&
2933 formatUpdate
!= format
&&
2936 if (isFormatChange
) {
2937 secdebug("validupdate", "group %lld format change from %d to %d",
2938 (long long)groupId
, format
, formatUpdate
);
2939 /* format of an existing group is changing; delete the group first.
2940 this should ensure that all entries referencing the old groupid are deleted.
2942 ok
= ok
&& SecDbWithSQL(dbc
->dbconn
, deleteGroupRecordSQL
, &localError
, ^bool(sqlite3_stmt
*deleteResponse
) {
2943 ok
= ok
&& SecDbBindInt64(deleteResponse
, 1, groupId
, &localError
);
2944 /* Execute the delete statement. */
2945 ok
= ok
&& SecDbStep(dbc
->dbconn
, deleteResponse
, &localError
, NULL
);
2949 ok
= ok
&& SecDbWithSQL(dbc
->dbconn
, insertGroupRecordSQL
, &localError
, ^bool(sqlite3_stmt
*insertGroup
) {
2950 /* (groupid,flags,format,data,policies) */
2951 /* groups.groupid */
2952 if (ok
&& (!isFormatChange
) && (groupId
>= 0)) {
2953 /* bind to existing groupId row if known, otherwise will insert and autoincrement */
2954 ok
= SecDbBindInt64(insertGroup
, 1, groupId
, &localError
);
2956 secdebug("validupdate", "failed to set groupId %lld", (long long)groupId
);
2961 (void)_SecRevocationDbUpdateFlags(dict
, CFSTR("complete"), kSecValidInfoComplete
, &flags
);
2962 (void)_SecRevocationDbUpdateFlags(dict
, CFSTR("check-ocsp"), kSecValidInfoCheckOCSP
, &flags
);
2963 (void)_SecRevocationDbUpdateFlags(dict
, CFSTR("known-intermediates-only"), kSecValidInfoKnownOnly
, &flags
);
2964 (void)_SecRevocationDbUpdateFlags(dict
, CFSTR("require-ct"), kSecValidInfoRequireCT
, &flags
);
2965 (void)_SecRevocationDbUpdateFlags(dict
, CFSTR("valid"), kSecValidInfoAllowlist
, &flags
);
2966 (void)_SecRevocationDbUpdateFlags(dict
, CFSTR("no-ca"), kSecValidInfoNoCACheck
, &flags
);
2967 (void)_SecRevocationDbUpdateFlags(dict
, CFSTR("no-ca-v2"), kSecValidInfoNoCAv2Check
, &flags
);
2968 (void)_SecRevocationDbUpdateFlags(dict
, CFSTR("overridable"), kSecValidInfoOverridable
, &flags
);
2970 /* date constraints exist if either "not-before" or "not-after" keys are found */
2971 CFTypeRef notBeforeValue
= (CFDateRef
)CFDictionaryGetValue(dict
, CFSTR("not-before"));
2972 CFTypeRef notAfterValue
= (CFDateRef
)CFDictionaryGetValue(dict
, CFSTR("not-after"));
2973 if (isDate(notBeforeValue
) || isDate(notAfterValue
)) {
2974 (void)_SecRevocationDbUpdateFlags(dict
, kBoolTrueKey
, kSecValidInfoDateConstraints
, &flags
);
2975 /* Note that the spec defines not-before and not-after dates as optional, such that
2976 not providing one does not change the database contents. Therefore, we can never clear
2977 this flag; either a new date entry will be supplied, or a format change will cause
2978 the entire group entry to be deleted. */
2980 /* policy constraints exist if "policies" key is found */
2981 CFTypeRef policiesValue
= (CFArrayRef
)CFDictionaryGetValue(dict
, CFSTR("policies"));
2982 if (isArray(policiesValue
)) {
2983 (void)_SecRevocationDbUpdateFlags(dict
, kBoolTrueKey
, kSecValidInfoPolicyConstraints
, &flags
);
2984 /* As above, not providing this value in an update does not change the existing state,
2985 so we never need to clear this flag once it is set. */
2988 /* %%% (TBI:9254570) name constraints don't exist yet */
2989 (void)_SecRevocationDbUpdateFlags(dict
, kBoolFalseKey
, kSecValidInfoNameConstraints
, &flags
);
2991 ok
= SecDbBindInt(insertGroup
, 2, (int)flags
, &localError
);
2993 secdebug("validupdate", "failed to set flags (%lu) for groupId %lld", flags
, (long long)groupId
);
2998 SecValidInfoFormat formatValue
= format
;
2999 if (formatUpdate
> kSecValidInfoFormatUnknown
) {
3000 formatValue
= formatUpdate
;
3002 ok
= SecDbBindInt(insertGroup
, 3, (int)formatValue
, &localError
);
3004 secdebug("validupdate", "failed to set format (%d) for groupId %lld", formatValue
, (long long)groupId
);
3008 CFDataRef xmlData
= NULL
;
3010 bool hasFilter
= ((formatUpdate
== kSecValidInfoFormatNto1
) ||
3011 (formatUpdate
== kSecValidInfoFormatUnknown
&&
3012 format
== kSecValidInfoFormatNto1
));
3014 CFDataRef dataValue
= data
; /* use existing data */
3015 if (_SecRevocationDbUpdateFilter(dict
, data
, &xmlData
)) {
3016 dataValue
= xmlData
; /* use updated data */
3019 ok
= SecDbBindBlob(insertGroup
, 4,
3020 CFDataGetBytePtr(dataValue
),
3021 CFDataGetLength(dataValue
),
3022 SQLITE_TRANSIENT
, &localError
);
3025 secdebug("validupdate", "failed to set data for groupId %lld",
3026 (long long)groupId
);
3029 /* else there is no data, so NULL is implicitly bound to column 4 */
3031 /* groups.policies */
3032 CFDataRef newPoliciesData
= NULL
;
3034 CFDataRef policiesValue
= policies
; /* use existing policies */
3035 newPoliciesData
= createPoliciesData((CFArrayRef
)CFDictionaryGetValue(dict
, CFSTR("policies")));
3036 if (newPoliciesData
) {
3037 policiesValue
= newPoliciesData
; /* use updated policies */
3039 if (policiesValue
) {
3040 ok
= SecDbBindBlob(insertGroup
, 5,
3041 CFDataGetBytePtr(policiesValue
),
3042 CFDataGetLength(policiesValue
),
3043 SQLITE_TRANSIENT
, &localError
);
3045 /* else there is no policy data, so NULL is implicitly bound to column 5 */
3047 secdebug("validupdate", "failed to set policies for groupId %lld",
3048 (long long)groupId
);
3052 /* Execute the insert statement for the group record. */
3054 ok
= SecDbStep(dbc
->dbconn
, insertGroup
, &localError
, NULL
);
3056 secdebug("validupdate", "failed to execute insertGroup statement for groupId %lld",
3057 (long long)groupId
);
3059 result
= (int64_t)sqlite3_last_insert_rowid(SecDbHandle(dbc
->dbconn
));
3062 secdebug("validupdate", "failed to insert group %lld", (long long)result
);
3064 /* Clean up temporary allocations made in this block. */
3065 CFReleaseSafe(xmlData
);
3066 CFReleaseSafe(newPoliciesData
);
3070 CFReleaseSafe(data
);
3071 CFReleaseSafe(policies
);
3073 if (!ok
|| localError
) {
3074 secerror("_SecRevocationDbUpdateGroup failed: %@", localError
);
3075 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationWrite
, TAFatalError
,
3076 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
3078 (void) CFErrorPropagate(localError
, error
);
3082 static int64_t _SecRevocationDbGroupIdForIssuerHash(SecRevocationDbConnectionRef dbc
, CFDataRef hash
, CFErrorRef
*error
) {
3083 /* look up issuer hash in issuers table to get groupid, if it exists */
3084 __block
int64_t groupId
= -1;
3085 __block
bool ok
= (dbc
!= NULL
);
3086 __block CFErrorRef localError
= NULL
;
3089 secdebug("validupdate", "failed to get hash (%@)", hash
);
3091 require(hash
&& dbc
, errOut
);
3093 /* This is the starting point for any lookup; find a group id for the given issuer hash.
3094 Before we do that, need to verify the current db_version. We cannot use results from a
3095 database created with a schema version older than the minimum supported version.
3096 However, we may be able to use results from a newer version. At the next database
3097 update interval, if the existing schema is old, we'll be removing and recreating
3098 the database contents with the current schema version.
3100 int64_t db_version
= _SecRevocationDbGetSchemaVersion(dbc
->db
, dbc
, NULL
);
3101 if (db_version
< kSecRevocationDbMinSchemaVersion
) {
3102 if (!dbc
->db
->unsupportedVersion
) {
3103 secdebug("validupdate", "unsupported db_version: %lld", (long long)db_version
);
3104 dbc
->db
->unsupportedVersion
= true; /* only warn once for a given unsupported version */
3107 require_quiet(db_version
>= kSecRevocationDbMinSchemaVersion
, errOut
);
3109 /* Look up provided issuer_hash in the issuers table.
3111 ok
= ok
&& SecDbWithSQL(dbc
->dbconn
, selectGroupIdSQL
, &localError
, ^bool(sqlite3_stmt
*selectGroupId
) {
3112 ok
&= SecDbBindBlob(selectGroupId
, 1, CFDataGetBytePtr(hash
), CFDataGetLength(hash
), SQLITE_TRANSIENT
, &localError
);
3113 ok
&= SecDbStep(dbc
->dbconn
, selectGroupId
, &localError
, ^(bool *stopGroupId
) {
3114 groupId
= sqlite3_column_int64(selectGroupId
, 0);
3120 if (!ok
|| localError
) {
3121 secerror("_SecRevocationDbGroupIdForIssuerHash failed: %@", localError
);
3122 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationRead
, TAFatalError
,
3123 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
3125 (void) CFErrorPropagate(localError
, error
);
3129 static bool _SecRevocationDbApplyGroupDelete(SecRevocationDbConnectionRef dbc
, CFDataRef issuerHash
, CFErrorRef
*error
) {
3130 /* delete group associated with the given issuer;
3131 schema trigger will delete associated issuers, serials, and hashes. */
3132 __block
int64_t groupId
= -1;
3133 __block
bool ok
= (dbc
!= NULL
);
3134 __block CFErrorRef localError
= NULL
;
3137 groupId
= _SecRevocationDbGroupIdForIssuerHash(dbc
, issuerHash
, &localError
);
3141 SecError(errSecParam
, &localError
, CFSTR("group not found for issuer"));
3145 ok
= ok
&& SecDbWithSQL(dbc
->dbconn
, deleteGroupRecordSQL
, &localError
, ^bool(sqlite3_stmt
*deleteResponse
) {
3146 ok
&= SecDbBindInt64(deleteResponse
, 1, groupId
, &localError
);
3147 /* Execute the delete statement. */
3148 ok
= ok
&& SecDbStep(dbc
->dbconn
, deleteResponse
, &localError
, NULL
);
3151 if (!ok
|| localError
) {
3152 secerror("_SecRevocationDbApplyGroupDelete failed: %@", localError
);
3153 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationWrite
, TAFatalError
,
3154 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
3156 (void) CFErrorPropagate(localError
, error
);
3160 static bool _SecRevocationDbApplyGroupUpdate(SecRevocationDbConnectionRef dbc
, CFDictionaryRef dict
, CFErrorRef
*error
) {
3161 /* process one issuer group's update dictionary */
3162 __block
int64_t groupId
= -1;
3163 __block
bool ok
= (dbc
!= NULL
);
3164 __block CFErrorRef localError
= NULL
;
3166 CFArrayRef issuers
= (dict
) ? (CFArrayRef
)CFDictionaryGetValue(dict
, CFSTR("issuer-hash")) : NULL
;
3167 /* if this is not a full update, then look for existing group id */
3168 if (ok
&& isArray(issuers
) && !dbc
->fullUpdate
) {
3169 CFIndex issuerIX
, issuerCount
= CFArrayGetCount(issuers
);
3170 /* while we have issuers and haven't found a matching group id */
3171 for (issuerIX
=0; issuerIX
<issuerCount
&& groupId
< 0; issuerIX
++) {
3172 CFDataRef hash
= (CFDataRef
)CFArrayGetValueAtIndex(issuers
, issuerIX
);
3173 if (!hash
) { continue; }
3174 groupId
= _SecRevocationDbGroupIdForIssuerHash(dbc
, hash
, &localError
);
3177 /* according to the spec, we must replace all existing issuers with
3178 the new issuers list, so delete all issuers in the group first. */
3179 ok
= ok
&& SecDbWithSQL(dbc
->dbconn
, deleteGroupIssuersSQL
, &localError
, ^bool(sqlite3_stmt
*deleteIssuers
) {
3180 ok
= ok
&& SecDbBindInt64(deleteIssuers
, 1, groupId
, &localError
);
3181 ok
= ok
&& SecDbStep(dbc
->dbconn
, deleteIssuers
, &localError
, NULL
);
3186 /* create or update the group entry */
3188 groupId
= _SecRevocationDbUpdateGroup(dbc
, groupId
, dict
, &localError
);
3191 secdebug("validupdate", "failed to get groupId");
3194 /* create or update issuer entries, now that we know the group id */
3195 ok
= ok
&& _SecRevocationDbUpdateIssuers(dbc
, groupId
, issuers
, &localError
);
3196 /* create or update entries in serials or hashes tables */
3197 ok
= ok
&& _SecRevocationDbUpdateIssuerData(dbc
, groupId
, dict
, &localError
);
3198 /* create or update entries in dates/names/policies tables */
3199 ok
= ok
&& _SecRevocationDbUpdateIssuerConstraints(dbc
, groupId
, dict
, &localError
);
3202 (void) CFErrorPropagate(localError
, error
);
3206 bool _SecRevocationDbApplyUpdate(SecRevocationDbConnectionRef dbc
, CFDictionaryRef update
, CFIndex version
, CFErrorRef
*error
) {
3207 /* process entire update dictionary */
3208 if (!dbc
|| !dbc
->db
|| !update
) {
3209 secerror("_SecRevocationDbApplyUpdate failed: invalid args");
3210 SecError(errSecParam
, error
, CFSTR("_SecRevocationDbApplyUpdate: invalid db or update parameter"));
3214 CFDictionaryRef localUpdate
= (CFDictionaryRef
)CFRetainSafe(update
);
3215 CFErrorRef localError
= NULL
;
3218 CFTypeRef value
= NULL
;
3219 CFIndex deleteCount
= 0;
3220 CFIndex updateCount
= 0;
3222 dbc
->db
->updateInProgress
= true;
3224 /* check whether this is a full update */
3225 value
= (CFBooleanRef
)CFDictionaryGetValue(update
, CFSTR("full"));
3226 if (isBoolean(value
) && CFBooleanGetValue((CFBooleanRef
)value
)) {
3227 /* clear the database before processing a full update */
3228 dbc
->fullUpdate
= true;
3229 secdebug("validupdate", "update has \"full\" attribute; clearing database");
3230 ok
= ok
&& _SecRevocationDbRemoveAllEntries(dbc
, &localError
);
3233 /* process 'delete' list */
3234 value
= (CFArrayRef
)CFDictionaryGetValue(localUpdate
, CFSTR("delete"));
3235 if (isArray(value
)) {
3236 deleteCount
= CFArrayGetCount((CFArrayRef
)value
);
3237 secdebug("validupdate", "processing %ld deletes", (long)deleteCount
);
3238 for (CFIndex deleteIX
=0; deleteIX
<deleteCount
; deleteIX
++) {
3239 CFDataRef issuerHash
= (CFDataRef
)CFArrayGetValueAtIndex((CFArrayRef
)value
, deleteIX
);
3240 if (isData(issuerHash
)) {
3241 ok
= ok
&& _SecRevocationDbApplyGroupDelete(dbc
, issuerHash
, &localError
);
3243 secdebug("validupdate", "skipping delete %ld (hash is not a data value)", (long)deleteIX
);
3248 /* process 'update' list */
3249 value
= (CFArrayRef
)CFDictionaryGetValue(localUpdate
, CFSTR("update"));
3250 if (isArray(value
)) {
3251 updateCount
= CFArrayGetCount((CFArrayRef
)value
);
3252 secdebug("validupdate", "processing %ld updates", (long)updateCount
);
3253 for (CFIndex updateIX
=0; updateIX
<updateCount
; updateIX
++) {
3254 CFDictionaryRef dict
= (CFDictionaryRef
)CFArrayGetValueAtIndex((CFArrayRef
)value
, updateIX
);
3255 if (isDictionary(dict
)) {
3256 ok
= ok
&& _SecRevocationDbApplyGroupUpdate(dbc
, dict
, &localError
);
3258 secdebug("validupdate", "skipping update %ld (not a dictionary)", (long)updateIX
);
3262 CFReleaseSafe(localUpdate
);
3265 ok
= ok
&& _SecRevocationDbSetVersion(dbc
, version
, &localError
);
3267 /* set interval if not already set, or changed */
3268 int64_t interval
= _SecRevocationDbGetUpdateInterval(dbc
, NULL
);
3269 if (interval
!= dbc
->precommitInterval
) {
3270 interval
= (dbc
->precommitInterval
> 0) ? dbc
->precommitInterval
: kSecStdUpdateInterval
;
3271 ok
= ok
&& _SecRevocationDbSetUpdateInterval(dbc
, interval
, &localError
);
3274 /* set db_version if not already set */
3275 int64_t db_version
= _SecRevocationDbGetSchemaVersion(dbc
->db
, dbc
, NULL
);
3276 if (db_version
<= 0) {
3277 ok
= ok
&& _SecRevocationDbSetSchemaVersion(dbc
, kSecRevocationDbSchemaVersion
, &localError
);
3280 /* set db_format if not already set */
3281 int64_t db_format
= _SecRevocationDbGetUpdateFormat(dbc
, NULL
);
3282 if (db_format
<= 0) {
3283 ok
= ok
&& _SecRevocationDbSetUpdateFormat(dbc
, kSecRevocationDbUpdateFormat
, &localError
);
3286 /* purge the in-memory cache */
3287 SecRevocationDbCachePurge(dbc
->db
);
3289 dbc
->db
->updateInProgress
= false;
3291 (void) CFErrorPropagate(localError
, error
);
3295 static bool _SecRevocationDbSerialInGroup(SecRevocationDbConnectionRef dbc
,
3298 CFErrorRef
*error
) {
3299 __block
bool result
= false;
3300 __block
bool ok
= true;
3301 __block CFErrorRef localError
= NULL
;
3302 require(dbc
&& serial
, errOut
);
3303 ok
&= SecDbWithSQL(dbc
->dbconn
, selectSerialRecordSQL
, &localError
, ^bool(sqlite3_stmt
*selectSerial
) {
3304 ok
&= SecDbBindInt64(selectSerial
, 1, groupId
, &localError
);
3305 ok
&= SecDbBindBlob(selectSerial
, 2, CFDataGetBytePtr(serial
),
3306 CFDataGetLength(serial
), SQLITE_TRANSIENT
, &localError
);
3307 ok
&= SecDbStep(dbc
->dbconn
, selectSerial
, &localError
, ^(bool *stop
) {
3308 int64_t foundRowId
= (int64_t)sqlite3_column_int64(selectSerial
, 0);
3309 result
= (foundRowId
> 0);
3315 if (!ok
|| localError
) {
3316 secerror("_SecRevocationDbSerialInGroup failed: %@", localError
);
3317 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationRead
, TAFatalError
,
3318 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
3320 (void) CFErrorPropagate(localError
, error
);
3324 static bool _SecRevocationDbCertHashInGroup(SecRevocationDbConnectionRef dbc
,
3327 CFErrorRef
*error
) {
3328 __block
bool result
= false;
3329 __block
bool ok
= true;
3330 __block CFErrorRef localError
= NULL
;
3331 require(dbc
&& certHash
, errOut
);
3332 ok
&= SecDbWithSQL(dbc
->dbconn
, selectHashRecordSQL
, &localError
, ^bool(sqlite3_stmt
*selectHash
) {
3333 ok
&= SecDbBindInt64(selectHash
, 1, groupId
, &localError
);
3334 ok
= SecDbBindBlob(selectHash
, 2, CFDataGetBytePtr(certHash
),
3335 CFDataGetLength(certHash
), SQLITE_TRANSIENT
, &localError
);
3336 ok
&= SecDbStep(dbc
->dbconn
, selectHash
, &localError
, ^(bool *stop
) {
3337 int64_t foundRowId
= (int64_t)sqlite3_column_int64(selectHash
, 0);
3338 result
= (foundRowId
> 0);
3344 if (!ok
|| localError
) {
3345 secerror("_SecRevocationDbCertHashInGroup failed: %@", localError
);
3346 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationRead
, TAFatalError
,
3347 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
3349 (void) CFErrorPropagate(localError
, error
);
3353 static bool _SecRevocationDbSerialInFilter(SecRevocationDbConnectionRef dbc
,
3354 CFDataRef serialData
,
3355 CFDataRef xmlData
) {
3356 /* N-To-1 filter implementation.
3357 The 'xmlData' parameter is a flattened XML dictionary,
3358 containing 'xor' and 'params' keys. First order of
3359 business is to reconstitute the blob into components.
3361 bool result
= false;
3362 CFRetainSafe(xmlData
);
3363 CFDataRef propListData
= xmlData
;
3364 /* Expand data blob if needed */
3365 CFDataRef inflatedData
= copyInflatedData(propListData
);
3367 CFReleaseSafe(propListData
);
3368 propListData
= inflatedData
;
3370 CFDataRef
xor = NULL
;
3371 CFArrayRef params
= NULL
;
3372 CFPropertyListRef nto1
= CFPropertyListCreateWithData(kCFAllocatorDefault
, propListData
, 0, NULL
, NULL
);
3374 xor = (CFDataRef
)CFDictionaryGetValue((CFDictionaryRef
)nto1
, CFSTR("xor"));
3375 params
= (CFArrayRef
)CFDictionaryGetValue((CFDictionaryRef
)nto1
, CFSTR("params"));
3377 uint8_t *hash
= (xor) ? (uint8_t*)CFDataGetBytePtr(xor) : NULL
;
3378 CFIndex hashLen
= (hash
) ? CFDataGetLength(xor) : 0;
3379 uint8_t *serial
= (serialData
) ? (uint8_t*)CFDataGetBytePtr(serialData
) : NULL
;
3380 CFIndex serialLen
= (serial
) ? CFDataGetLength(serialData
) : 0;
3382 require(hash
&& serial
&& params
, errOut
);
3384 const uint32_t FNV_OFFSET_BASIS
= 2166136261;
3385 const uint32_t FNV_PRIME
= 16777619;
3386 bool notInHash
= false;
3387 CFIndex ix
, count
= CFArrayGetCount(params
);
3388 for (ix
= 0; ix
< count
; ix
++) {
3390 CFNumberRef cfnum
= (CFNumberRef
)CFArrayGetValueAtIndex(params
, ix
);
3391 if (!isNumber(cfnum
) ||
3392 !CFNumberGetValue(cfnum
, kCFNumberSInt32Type
, ¶m
)) {
3393 secinfo("validupdate", "error processing filter params at index %ld", (long)ix
);
3396 /* process one param */
3397 uint32_t hval
= FNV_OFFSET_BASIS
^ param
;
3398 CFIndex i
= serialLen
;
3400 hval
= ((hval
^ (serial
[--i
])) * FNV_PRIME
) & 0xFFFFFFFF;
3402 hval
= hval
% (hashLen
* 8);
3403 if ((hash
[hval
/8] & (1 << (hval
% 8))) == 0) {
3404 notInHash
= true; /* definitely not in hash */
3409 /* probabilistically might be in hash if we get here. */
3414 CFReleaseSafe(nto1
);
3415 CFReleaseSafe(propListData
);
3419 static SecValidInfoRef
_SecRevocationDbValidInfoForCertificate(SecRevocationDbConnectionRef dbc
,
3420 SecCertificateRef certificate
,
3421 CFDataRef issuerHash
,
3422 CFErrorRef
*error
) {
3423 __block CFErrorRef localError
= NULL
;
3424 __block SecValidInfoFlags flags
= 0;
3425 __block SecValidInfoFormat format
= kSecValidInfoFormatUnknown
;
3426 __block CFDataRef data
= NULL
;
3428 bool matched
= false;
3429 bool isOnList
= false;
3430 int64_t groupId
= 0;
3431 CFDataRef serial
= NULL
;
3432 CFDataRef certHash
= NULL
;
3433 CFDateRef notBeforeDate
= NULL
;
3434 CFDateRef notAfterDate
= NULL
;
3435 CFDataRef names
= NULL
;
3436 CFDataRef policies
= NULL
;
3437 SecValidInfoRef result
= NULL
;
3439 require((serial
= SecCertificateCopySerialNumberData(certificate
, NULL
)) != NULL
, errOut
);
3440 require((certHash
= SecCertificateCopySHA256Digest(certificate
)) != NULL
, errOut
);
3441 require_quiet((groupId
= _SecRevocationDbGroupIdForIssuerHash(dbc
, issuerHash
, &localError
)) > 0, errOut
);
3443 /* Look up the group record to determine flags and format. */
3444 format
= _SecRevocationDbGetGroupFormat(dbc
, groupId
, &flags
, &data
, &policies
, &localError
);
3446 if (format
== kSecValidInfoFormatUnknown
) {
3447 /* No group record found for this issuer. Don't return a SecValidInfoRef */
3450 else if (format
== kSecValidInfoFormatSerial
) {
3451 /* Look up certificate's serial number in the serials table. */
3452 matched
= _SecRevocationDbSerialInGroup(dbc
, serial
, groupId
, &localError
);
3454 else if (format
== kSecValidInfoFormatSHA256
) {
3455 /* Look up certificate's SHA-256 hash in the hashes table. */
3456 matched
= _SecRevocationDbCertHashInGroup(dbc
, certHash
, groupId
, &localError
);
3458 else if (format
== kSecValidInfoFormatNto1
) {
3459 /* Perform a Bloom filter match against the serial. If matched is false,
3460 then the cert is definitely not in the list. But if matched is true,
3461 we don't know for certain, so we would need to check OCSP. */
3462 matched
= _SecRevocationDbSerialInFilter(dbc
, serial
, data
);
3466 /* Found a specific match for this certificate. */
3467 secdebug("validupdate", "Valid db matched certificate: %@, format=%d, flags=0x%lx",
3468 certHash
, format
, flags
);
3472 /* If supplemental constraints are present for this issuer, then we always match. */
3473 if ((flags
& kSecValidInfoDateConstraints
) &&
3474 (_SecRevocationDbCopyDateConstraints(dbc
, groupId
, ¬BeforeDate
, ¬AfterDate
, &localError
))) {
3475 secdebug("validupdate", "Valid db matched supplemental date constraints for groupId %lld: nb=%@, na=%@",
3476 (long long)groupId
, notBeforeDate
, notAfterDate
);
3480 /* Return SecValidInfo for certificates for which an issuer entry is found. */
3481 result
= SecValidInfoCreate(format
, flags
, isOnList
,
3482 certHash
, issuerHash
, /*anchorHash*/ NULL
,
3483 notBeforeDate
, notAfterDate
,
3486 if (result
&& SecIsAppleTrustAnchor(certificate
, 0)) {
3487 /* Prevent a catch-22. */
3488 secdebug("validupdate", "Valid db match for Apple trust anchor: %@, format=%d, flags=0x%lx",
3489 certHash
, format
, flags
);
3490 CFReleaseNull(result
);
3494 (void) CFErrorPropagate(localError
, error
);
3495 CFReleaseSafe(data
);
3496 CFReleaseSafe(certHash
);
3497 CFReleaseSafe(serial
);
3498 CFReleaseSafe(notBeforeDate
);
3499 CFReleaseSafe(notAfterDate
);
3500 CFReleaseSafe(names
);
3501 CFReleaseSafe(policies
);
3505 static SecValidInfoRef
_SecRevocationDbCopyMatching(SecRevocationDbConnectionRef dbc
,
3506 SecCertificateRef certificate
,
3507 SecCertificateRef issuer
) {
3508 SecValidInfoRef result
= NULL
;
3509 CFErrorRef error
= NULL
;
3510 CFDataRef issuerHash
= NULL
;
3512 require(dbc
&& certificate
&& issuer
, errOut
);
3513 require(issuerHash
= SecCertificateCopySHA256Digest(issuer
), errOut
);
3515 /* Check for the result in the cache. */
3516 result
= SecRevocationDbCacheRead(dbc
->db
, certificate
, issuerHash
);
3518 /* Upon cache miss, get the result from the database and add it to the cache. */
3520 result
= _SecRevocationDbValidInfoForCertificate(dbc
, certificate
, issuerHash
, &error
);
3521 SecRevocationDbCacheWrite(dbc
->db
, result
);
3525 CFReleaseSafe(issuerHash
);
3526 CFReleaseSafe(error
);
3530 /* Return the update source as a retained CFStringRef.
3531 If the value cannot be obtained, NULL is returned.
3533 CFStringRef
SecRevocationDbCopyUpdateSource(void) {
3534 __block CFStringRef result
= NULL
;
3535 SecRevocationDbWith(^(SecRevocationDbRef db
) {
3536 (void) SecRevocationDbPerformRead(db
, NULL
, ^bool(SecRevocationDbConnectionRef dbc
, CFErrorRef
*blockError
) {
3537 result
= _SecRevocationDbCopyUpdateSource(dbc
, blockError
);
3538 return (bool)result
;
3544 /* Set the next update value for the revocation database.
3545 (This function is expected to be called only by the database
3546 maintainer, normally the system instance of trustd. If the
3547 caller does not have write access, this is a no-op.)
3549 bool SecRevocationDbSetNextUpdateTime(CFAbsoluteTime nextUpdate
, CFErrorRef
*error
) {
3550 __block
bool ok
= true;
3551 __block CFErrorRef localError
= NULL
;
3552 SecRevocationDbWith(^(SecRevocationDbRef rdb
) {
3553 ok
&= SecRevocationDbPerformWrite(rdb
, &localError
, ^bool(SecRevocationDbConnectionRef dbc
, CFErrorRef
*blockError
) {
3554 return _SecRevocationDbSetNextUpdateTime(dbc
, nextUpdate
, blockError
);
3557 (void) CFErrorPropagate(localError
, error
);
3561 /* Return the next update value as a CFAbsoluteTime.
3562 If the value cannot be obtained, -1 is returned.
3564 CFAbsoluteTime
SecRevocationDbGetNextUpdateTime(void) {
3565 __block CFAbsoluteTime result
= -1;
3566 SecRevocationDbWith(^(SecRevocationDbRef db
) {
3567 (void) SecRevocationDbPerformRead(db
, NULL
, ^bool(SecRevocationDbConnectionRef dbc
, CFErrorRef
*blockError
) {
3568 result
= _SecRevocationDbGetNextUpdateTime(dbc
, blockError
);
3575 /* Return the serial background queue for database updates.
3576 If the queue cannot be obtained, NULL is returned.
3578 dispatch_queue_t
SecRevocationDbGetUpdateQueue(void) {
3579 __block dispatch_queue_t result
= NULL
;
3580 SecRevocationDbWith(^(SecRevocationDbRef db
) {
3581 result
= (db
) ? db
->update_queue
: NULL
;
3586 /* Release all connections to the revocation database.
3588 void SecRevocationDbReleaseAllConnections(void) {
3589 SecRevocationDbWith(^(SecRevocationDbRef db
) {
3590 SecDbReleaseAllConnections((db
) ? db
->db
: NULL
);
3594 /* === SecRevocationDb API === */
3596 /* Given a certificate and its issuer, returns a SecValidInfoRef if the
3597 valid database contains matching info; otherwise returns NULL.
3598 Caller must release the returned SecValidInfoRef when finished.
3600 SecValidInfoRef
SecRevocationDbCopyMatching(SecCertificateRef certificate
,
3601 SecCertificateRef issuer
) {
3602 __block SecValidInfoRef result
= NULL
;
3603 SecRevocationDbWith(^(SecRevocationDbRef db
) {
3604 (void) SecRevocationDbPerformRead(db
, NULL
, ^bool(SecRevocationDbConnectionRef dbc
, CFErrorRef
*blockError
) {
3605 result
= _SecRevocationDbCopyMatching(dbc
, certificate
, issuer
);
3606 return (bool)result
;
3612 /* Given an issuer, returns true if an entry for this issuer exists in
3613 the database (i.e. a known CA). If the provided certificate is NULL,
3614 or its entry is not found, the function returns false.
3616 bool SecRevocationDbContainsIssuer(SecCertificateRef issuer
) {
3620 __block
bool result
= false;
3621 SecRevocationDbWith(^(SecRevocationDbRef db
) {
3622 (void) SecRevocationDbPerformRead(db
, NULL
, ^bool(SecRevocationDbConnectionRef dbc
, CFErrorRef
*blockError
) {
3623 CFDataRef issuerHash
= SecCertificateCopySHA256Digest(issuer
);
3624 int64_t groupId
= _SecRevocationDbGroupIdForIssuerHash(dbc
, issuerHash
, blockError
);
3625 CFReleaseSafe(issuerHash
);
3626 result
= (groupId
> 0);
3633 /* Return the current version of the revocation database.
3634 A version of 0 indicates an empty database which must be populated.
3635 If the version cannot be obtained, -1 is returned.
3637 CFIndex
SecRevocationDbGetVersion(void) {
3638 __block CFIndex result
= -1;
3639 SecRevocationDbWith(^(SecRevocationDbRef db
) {
3640 (void) SecRevocationDbPerformRead(db
, NULL
, ^bool(SecRevocationDbConnectionRef dbc
, CFErrorRef
*blockError
) {
3641 result
= (CFIndex
)_SecRevocationDbGetVersion(dbc
, blockError
);
3642 return (result
>= 0);
3648 /* Return the current schema version of the revocation database.
3649 A version of 0 indicates an empty database which must be populated.
3650 If the schema version cannot be obtained, -1 is returned.
3652 CFIndex
SecRevocationDbGetSchemaVersion(void) {
3653 __block CFIndex result
= -1;
3654 SecRevocationDbWith(^(SecRevocationDbRef db
) {
3655 result
= (CFIndex
)_SecRevocationDbGetSchemaVersion(db
, NULL
, NULL
);
3660 /* Return the current update format of the revocation database.
3661 A version of 0 indicates the format was unknown.
3662 If the update format cannot be obtained, -1 is returned.
3664 CFIndex
SecRevocationDbGetUpdateFormat(void) {
3665 __block CFIndex result
= -1;
3666 SecRevocationDbWith(^(SecRevocationDbRef db
) {
3667 (void) SecRevocationDbPerformRead(db
, NULL
, ^bool(SecRevocationDbConnectionRef dbc
, CFErrorRef
*blockError
) {
3668 result
= (CFIndex
)_SecRevocationDbGetUpdateFormat(dbc
, blockError
);
3669 return (result
>= 0);
3678 ==============================================================================
3680 ==============================================================================
3683 /* Returns array of SHA-256 hashes computed over the contents of a valid.sqlite3
3684 database, in the order specified by the valid-server-api documentation. The
3685 resulting hashes can be compared against those in the update's 'hash' array.
3687 Hash 0: full database (all fields in initial Valid specification)
3688 Hash 1: all issuer_hash arrays, plus not-after and not-before dates for each
3689 Hash 2: subset of issuer_hash arrays where the no-ca-v2 flag is set
3691 static CF_RETURNS_RETAINED CFArrayRef
SecRevocationDbComputeFullContentDigests(SecRevocationDbConnectionRef dbc
, CFErrorRef
*error
) {
3692 if (!dbc
) { return NULL
; }
3693 __block
bool ok
= true;
3694 __block CFErrorRef localError
= NULL
;
3695 __block
uint32_t N
[4]={0,0,0,0};
3696 __block CC_SHA256_CTX hash0_ctx
, hash1_ctx
, hash2_ctx
;
3697 CC_SHA256_Init(&hash0_ctx
);
3698 CC_SHA256_Init(&hash1_ctx
);
3699 CC_SHA256_Init(&hash2_ctx
);
3701 // Add version, check-again, and update (array count) fields as array of N.
3702 // (Note: 'N' is defined as "unsigned 32-bit integer in network byte order")
3703 int64_t version
= _SecRevocationDbGetVersion(dbc
, NULL
);
3704 N
[0] = OSSwapInt32(version
& 0xffffffff);
3705 int64_t interval
= _SecRevocationDbGetUpdateInterval(dbc
, NULL
);
3707 interval
= kSecStdUpdateInterval
; // if we didn't store it, assume default
3709 N
[1] = OSSwapInt32(interval
& 0xffffffff);
3710 __block
int64_t count
= 0;
3711 ok
= ok
&& SecDbWithSQL(dbc
->dbconn
, CFSTR("SELECT count(*) FROM groups"), &localError
, ^bool(sqlite3_stmt
*selectGroupsCount
) {
3712 ok
= ok
&& SecDbStep(dbc
->dbconn
, selectGroupsCount
, &localError
, ^void(bool *stop
) {
3713 count
= sqlite3_column_int64(selectGroupsCount
, 0);
3718 N
[2] = OSSwapInt32(count
& 0xffffffff);
3719 CC_SHA256_Update(&hash0_ctx
, N
, sizeof(uint32_t) * 3);
3721 // Sort the update array in order of minimum 'issuer-hash' entry.
3722 // The issuer-hash array is first sorted to determine the lowest issuer-hash,
3723 // and that value is used to sort the update entries.
3725 // For our sqlite database, recreating the update array order means fetching
3726 // the groupid column from the issuers table after sorting on issuer_hash,
3727 // using DISTINCT to remove duplicates. Then, for each returned groupid, we
3728 // obtain its list of issuers, its list of serials or hashes, and other data.
3730 ok
= ok
&& SecDbWithSQL(dbc
->dbconn
, CFSTR("SELECT DISTINCT groupid FROM issuers ORDER BY issuer_hash ASC"), &localError
, ^bool(sqlite3_stmt
*selectGroups
) {
3731 ok
= ok
&& SecDbForEach(dbc
->dbconn
, selectGroups
, &localError
, ^bool(int row_index
) {
3732 __block
int64_t groupId
= sqlite3_column_int64(selectGroups
, 0);
3733 ok
= ok
&& SecDbWithSQL(dbc
->dbconn
, CFSTR("SELECT flags,format,data FROM groups WHERE groupid=?"), &localError
, ^bool(sqlite3_stmt
*selectGroup
) {
3734 ok
= ok
&& SecDbBindInt64(selectGroup
, 1, groupId
, &localError
);
3735 ok
= ok
&& SecDbStep(dbc
->dbconn
, selectGroup
, &localError
, ^(bool *stop
) {
3736 // per-group info is hashed in the following order:
3737 // - issuer_hash array data (sorted)
3738 // - flag bytes, in order listed below
3739 // - format string [serial|sha256|nto1]
3740 // - add array data (sorted), if [serial|sha256]
3741 // - params (if present)
3742 // - xor data (if present)
3744 int64_t flags
= sqlite3_column_int64(selectGroup
, 0);
3745 bool noCAv2
= (flags
& kSecValidInfoNoCAv2Check
);
3747 // instead of recreating the issuer_hash array in memory,
3748 // hash its length (item count) followed by the data of each issuer_hash.
3749 ok
= ok
&& SecDbWithSQL(dbc
->dbconn
, CFSTR("SELECT count(*) FROM issuers WHERE groupid=?"), &localError
, ^bool(sqlite3_stmt
*selectIssuersCount
) {
3750 ok
= ok
&& SecDbBindInt64(selectIssuersCount
, 1, groupId
, &localError
);
3751 ok
= ok
&& SecDbStep(dbc
->dbconn
, selectIssuersCount
, &localError
, ^void(bool *stop
) {
3752 count
= sqlite3_column_int64(selectIssuersCount
, 0);
3757 uint32_t n
= OSSwapInt32(count
& 0xffffffff);
3758 CC_SHA256_Update(&hash0_ctx
, &n
, sizeof(uint32_t));
3759 CC_SHA256_Update(&hash1_ctx
, &n
, sizeof(uint32_t));
3761 CC_SHA256_Update(&hash2_ctx
, &n
, sizeof(uint32_t));
3764 // process issuer_hash entries for this group
3765 ok
= ok
&& SecDbWithSQL(dbc
->dbconn
, CFSTR("SELECT issuer_hash FROM issuers WHERE groupid=? ORDER BY issuer_hash ASC"), &localError
, ^bool(sqlite3_stmt
*selectIssuerHash
) {
3766 ok
= ok
&& SecDbBindInt64(selectIssuerHash
, 1, groupId
, &localError
);
3767 ok
= ok
&& SecDbForEach(dbc
->dbconn
, selectIssuerHash
, &localError
, ^bool(int row_index
) {
3768 uint8_t *p
= (uint8_t *)sqlite3_column_blob(selectIssuerHash
, 0);
3769 CFDataRef data
= NULL
;
3771 CFIndex length
= (CFIndex
)sqlite3_column_bytes(selectIssuerHash
, 0);
3772 data
= CFDataCreate(kCFAllocatorDefault
, p
, length
);
3775 hashData(data
, &hash0_ctx
);
3776 hashData(data
, &hash1_ctx
);
3778 hashData(data
, &hash2_ctx
);
3789 // process flags, converting to array of unsigned 8-bit values, either 0 or 1:
3790 // [ complete, check-ocsp, known-intermediates-only, no-ca, overridable, require-ct, valid ]
3791 uint8_t C
[8]={0,0,0,0,0,0,0,0};
3792 C
[0] = (flags
& kSecValidInfoComplete
) ? 1 : 0;
3793 C
[1] = (flags
& kSecValidInfoCheckOCSP
) ? 1 : 0;
3794 C
[2] = (flags
& kSecValidInfoKnownOnly
) ? 1 : 0;
3795 C
[3] = (flags
& kSecValidInfoNoCACheck
) ? 1 : 0;
3796 C
[4] = (flags
& kSecValidInfoOverridable
) ? 1 : 0;
3797 C
[5] = (flags
& kSecValidInfoRequireCT
) ? 1 : 0;
3798 C
[6] = (flags
& kSecValidInfoAllowlist
) ? 1 : 0;
3799 CC_SHA256_Update(&hash0_ctx
, C
, sizeof(uint8_t) * 7);
3801 // process format, converting integer to string value [serial|sha256|nto1]
3802 SecValidInfoFormat format
= (SecValidInfoFormat
)sqlite3_column_int(selectGroup
, 1);
3804 case kSecValidInfoFormatSerial
:
3805 hashString(CFSTR("serial"), &hash0_ctx
);
3807 case kSecValidInfoFormatSHA256
:
3808 hashString(CFSTR("sha256"), &hash0_ctx
);
3810 case kSecValidInfoFormatNto1
:
3811 hashString(CFSTR("nto1"), &hash0_ctx
);
3813 case kSecValidInfoFormatUnknown
:
3815 ok
= false; // unexpected format values are not allowed
3818 // process 'add' array (serial or sha256 format).
3819 // instead of recreating the 'add' array in memory,
3820 // hash its length (item count) followed by the data of each entry.
3821 CFStringRef arrayCountSql
= NULL
;
3822 if (format
== kSecValidInfoFormatSerial
) {
3823 arrayCountSql
= CFSTR("SELECT count(*) FROM serials WHERE groupid=?");
3824 } else if (format
== kSecValidInfoFormatSHA256
) {
3825 arrayCountSql
= CFSTR("SELECT count(*) FROM hashes WHERE groupid=?");
3827 if (arrayCountSql
) {
3828 ok
= ok
&& SecDbWithSQL(dbc
->dbconn
, arrayCountSql
, &localError
, ^bool(sqlite3_stmt
*selectAddCount
) {
3829 ok
= ok
&& SecDbBindInt64(selectAddCount
, 1, groupId
, &localError
);
3830 ok
= ok
&& SecDbStep(dbc
->dbconn
, selectAddCount
, &localError
, ^void(bool *stop
) {
3831 count
= sqlite3_column_int64(selectAddCount
, 0);
3836 n
= OSSwapInt32(count
& 0xffffffff);
3837 CC_SHA256_Update(&hash0_ctx
, &n
, sizeof(uint32_t));
3839 // process data entries for this group
3840 CFStringRef arrayDataSql
= NULL
;
3841 if (format
== kSecValidInfoFormatSerial
) {
3842 arrayDataSql
= CFSTR("SELECT serial FROM serials WHERE groupid=? ORDER BY serial ASC");
3843 } else if (format
== kSecValidInfoFormatSHA256
) {
3844 arrayDataSql
= CFSTR("SELECT sha256 FROM hashes WHERE groupid=? ORDER by sha256 ASC");
3847 ok
= ok
&& SecDbWithSQL(dbc
->dbconn
, arrayDataSql
, &localError
, ^bool(sqlite3_stmt
*selectAddData
) {
3848 ok
= ok
&& SecDbBindInt64(selectAddData
, 1, groupId
, &localError
);
3849 ok
= ok
&& SecDbForEach(dbc
->dbconn
, selectAddData
, &localError
, ^bool(int row_index
) {
3850 uint8_t *p
= (uint8_t *)sqlite3_column_blob(selectAddData
, 0);
3851 CFDataRef data
= NULL
;
3853 CFIndex length
= (CFIndex
)sqlite3_column_bytes(selectAddData
, 0);
3854 data
= CFDataCreate(kCFAllocatorDefault
, p
, length
);
3857 hashData(data
, &hash0_ctx
);
3868 // process params and xor data, if format is nto1
3869 if (format
== kSecValidInfoFormatNto1
) {
3870 uint8_t *p
= (uint8_t *)sqlite3_column_blob(selectGroup
, 2);
3871 CFDataRef data
= NULL
;
3873 CFIndex length
= (CFIndex
)sqlite3_column_bytes(selectGroup
, 2);
3874 data
= CFDataCreate(kCFAllocatorDefault
, p
, length
);
3877 // unpack params and xor data
3878 CFDataRef
xor = NULL
;
3879 CFArrayRef params
= NULL
;
3880 if (copyFilterComponents(data
, &xor, ¶ms
)) {
3881 hashArray(params
, &hash0_ctx
);
3882 hashData(xor, &hash0_ctx
);
3887 CFReleaseSafe(params
);
3889 CFReleaseSafe(data
);
3892 // process date constraints [not-after, not-before]
3893 CFAbsoluteTime notBefore
= -3155760000.0; /* default: 1901-01-01 00:00:00-0000 */
3894 CFAbsoluteTime notAfter
= 31556908800.0; /* default: 3001-01-01 00:00:00-0000 */
3895 CFDateRef notBeforeDate
= NULL
;
3896 CFDateRef notAfterDate
= NULL
;
3897 if (_SecRevocationDbCopyDateConstraints(dbc
, groupId
, ¬BeforeDate
, ¬AfterDate
, &localError
)) {
3898 if (notBeforeDate
) {
3899 notBefore
= CFDateGetAbsoluteTime(notBeforeDate
);
3900 CFReleaseNull(notBeforeDate
);
3903 notAfter
= CFDateGetAbsoluteTime(notAfterDate
);
3904 CFReleaseNull(notAfterDate
);
3907 double nb
= htond(notBefore
);
3908 double na
= htond(notAfter
);
3909 CC_SHA256_Update(&hash1_ctx
, &na
, sizeof(double));
3910 CC_SHA256_Update(&hash1_ctx
, &nb
, sizeof(double));
3913 }); // per-group step
3915 }); // per-group select
3917 }); // for each group in list
3919 }); // select full group list
3921 CFMutableArrayRef result
= CFArrayCreateMutable(NULL
, 0, &kCFTypeArrayCallBacks
);
3923 uint8_t digest
[CC_SHA256_DIGEST_LENGTH
];
3924 CFDataRef data
= NULL
;
3925 CC_SHA256_Final(digest
, &hash0_ctx
);
3926 if ((data
= CFDataCreate(NULL
, (const UInt8
*)digest
, CC_SHA256_DIGEST_LENGTH
)) != NULL
) {
3927 CFArrayAppendValue(result
, data
);
3928 CFReleaseNull(data
);
3930 CC_SHA256_Final(digest
, &hash1_ctx
);
3931 if ((data
= CFDataCreate(NULL
, (const UInt8
*)digest
, CC_SHA256_DIGEST_LENGTH
)) != NULL
) {
3932 CFArrayAppendValue(result
, data
);
3933 CFReleaseNull(data
);
3935 CC_SHA256_Final(digest
, &hash2_ctx
);
3936 if ((data
= CFDataCreate(NULL
, (const UInt8
*)digest
, CC_SHA256_DIGEST_LENGTH
)) != NULL
) {
3937 CFArrayAppendValue(result
, data
);
3938 CFReleaseNull(data
);
3941 (void) CFErrorPropagate(localError
, error
);
3945 static bool SecRevocationDbComputeDigests(SecRevocationDbConnectionRef dbc
, CFErrorRef
*error
) {
3946 secinfo("validupdate", "Started verifying db content");
3948 CFArrayRef expectedList
= _SecRevocationDbCopyHashes(dbc
, error
);
3949 CFIndex expectedCount
= (expectedList
) ? CFArrayGetCount(expectedList
) : 0;
3950 if (expectedCount
< 1) {
3951 secinfo("validupdate", "Unable to read db_hash values");
3952 CFReleaseNull(expectedList
);
3953 return result
; // %%%% this will happen on first update, when db_hash isn't there
3955 CFArrayRef computedList
= SecRevocationDbComputeFullContentDigests(dbc
, error
);
3956 CFIndex computedCount
= (computedList
) ? CFArrayGetCount(computedList
) : 0;
3957 for (CFIndex idx
= 0; idx
< expectedCount
; idx
++) {
3958 if (idx
>= computedCount
) {
3959 continue; // server provided additional hash value that we don't yet compute
3961 CFDataRef expectedHash
= (CFDataRef
)CFArrayGetValueAtIndex(expectedList
, idx
);
3962 CFDataRef computedHash
= (CFDataRef
)CFArrayGetValueAtIndex(computedList
, idx
);
3963 if (!CFEqualSafe(expectedHash
, computedHash
)) {
3969 secinfo("validupdate", "Expected: %@", expectedList
);
3970 secinfo("validupdate", "Computed: %@", computedList
);
3972 secinfo("validupdate", "Finished verifying db content; result=%s",
3973 (result
) ? "SUCCESS" : "FAIL");
3974 CFReleaseSafe(expectedList
);
3975 CFReleaseSafe(computedList
);