2 * Copyright (c) 2016-2020 Apple Inc. All Rights Reserved.
4 * @APPLE_LICENSE_HEADER_START@
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
21 * @APPLE_LICENSE_HEADER_END@
29 #include "trust/trustd/SecRevocationDb.h"
30 #include "trust/trustd/OTATrustUtilities.h"
31 #include "trust/trustd/SecRevocationNetworking.h"
32 #include "trust/trustd/SecTrustLoggingServer.h"
33 #include <Security/SecCertificateInternal.h>
34 #include <Security/SecCMS.h>
35 #include <Security/CMSDecoder.h>
36 #include <Security/SecFramework.h>
37 #include <Security/SecInternal.h>
38 #include <Security/SecPolicyPriv.h>
39 #include <AssertMacros.h>
41 #include <stdatomic.h>
47 #include <dispatch/dispatch.h>
51 #include "utilities/debugging.h"
52 #include "utilities/sec_action.h"
53 #include "utilities/sqlutils.h"
54 #include "utilities/SecAppleAnchorPriv.h"
55 #include "utilities/iOSforOSX.h"
56 #include <utilities/SecCFError.h>
57 #include <utilities/SecCFRelease.h>
58 #include <utilities/SecCFWrappers.h>
59 #include <utilities/SecDb.h>
60 #include <utilities/SecFileLocations.h>
63 #include <malloc/malloc.h>
64 #include <xpc/activity.h>
65 #include <xpc/private.h>
66 #include <os/transaction_private.h>
67 #include <os/variant_private.h>
70 #include <CommonCrypto/CommonDigest.h>
71 #include <CFNetwork/CFHTTPMessage.h>
72 #include <CoreFoundation/CFURL.h>
73 #include <CoreFoundation/CFUtilities.h>
76 ==============================================================================
77 CoreFoundation utilities
78 ==============================================================================
81 static bool hashCFThing(CFTypeRef thing
, CC_SHA256_CTX
* hash_ctx
);
83 /* comparison function for sorting dictionary keys alphabetically */
84 static int compareCFStrings(const void *p
, const void *q
) {
85 CFStringRef str1
= *(CFStringRef
*)p
;
86 CFStringRef str2
= *(CFStringRef
*)q
;
87 if (!(isString(str1
) && isString(str2
))) {
88 return -1; /* can't compare non-string types */
90 CFComparisonResult result
= CFStringCompare(str1
, str2
, 0);
91 if (result
== kCFCompareLessThan
) {
93 } else if (result
== kCFCompareGreaterThan
) {
96 return 0; /* (result == kCFCompareEqualTo) */
99 static bool hashData(CFDataRef data
, CC_SHA256_CTX
* hash_ctx
) {
100 if (!isData(data
)) { return false; }
101 uint32_t length
= (uint32_t)(CFDataGetLength(data
) & 0xFFFFFFFF);
102 uint32_t n
= OSSwapInt32(length
);
103 CC_SHA256_Update(hash_ctx
, &n
, sizeof(uint32_t));
104 const uint8_t *p
= (uint8_t*)CFDataGetBytePtr(data
);
105 if (p
) { CC_SHA256_Update(hash_ctx
, p
, length
); }
109 static bool hashString(CFStringRef str
, CC_SHA256_CTX
* hash_ctx
) {
110 if (!isString(str
)) { return false; }
111 __block
bool ok
= false;
112 CFStringPerformWithCString(str
, ^(const char *strbuf
) {
113 // hash string length (in bytes, not counting null terminator)
114 uint32_t c
= (uint32_t)(strlen(strbuf
) & 0xFFFFFFFF);
115 uint32_t n
= OSSwapInt32(c
);
116 CC_SHA256_Update(hash_ctx
, &n
, sizeof(uint32_t));
118 CC_SHA256_Update(hash_ctx
, strbuf
, c
);
124 static bool hashNumber(CFNumberRef num
, CC_SHA256_CTX
* hash_ctx
) {
126 if (!isNumber(num
) || !CFNumberGetValue(num
, kCFNumberSInt32Type
, &n
)) {
130 CC_SHA256_Update(hash_ctx
, &n
, sizeof(uint32_t));
134 static bool hashBoolean(CFBooleanRef value
, CC_SHA256_CTX
* hash_ctx
) {
135 if (!isBoolean(value
)) { return false; }
136 uint8_t c
= CFBooleanGetValue(value
) ? 1 : 0;
137 CC_SHA256_Update(hash_ctx
, &c
, sizeof(uint8_t));
141 static bool hashArray(CFArrayRef array
, CC_SHA256_CTX
* hash_ctx
) {
142 if (!isArray(array
)) { return false; }
143 CFIndex count
= CFArrayGetCount(array
);
144 uint32_t n
= OSSwapInt32(count
& 0xFFFFFFFF);
145 CC_SHA256_Update(hash_ctx
, &n
, sizeof(uint32_t));
146 __block
bool ok
= true;
147 CFArrayForEach(array
, ^(const void *thing
) {
148 ok
&= hashCFThing(thing
, hash_ctx
);
153 static bool hashDictionary(CFDictionaryRef dictionary
, CC_SHA256_CTX
* hash_ctx
) {
154 if (!isDictionary(dictionary
)) { return false; }
155 CFIndex count
= CFDictionaryGetCount(dictionary
);
156 const void **keys
= (const void **)malloc(sizeof(void*) * count
);
157 const void **vals
= (const void **)malloc(sizeof(void*) * count
);
158 bool ok
= (keys
&& vals
);
160 CFDictionaryGetKeysAndValues(dictionary
, keys
, vals
);
161 qsort(keys
, count
, sizeof(CFStringRef
), compareCFStrings
);
162 uint32_t n
= OSSwapInt32(count
& 0xFFFFFFFF);
163 CC_SHA256_Update(hash_ctx
, &n
, sizeof(uint32_t));
165 for (CFIndex idx
= 0; ok
&& idx
< count
; idx
++) {
166 CFStringRef key
= (CFStringRef
)keys
[idx
];
167 CFTypeRef value
= (CFTypeRef
)CFDictionaryGetValue(dictionary
, key
);
168 ok
&= hashString(key
, hash_ctx
);
169 ok
&= hashCFThing(value
, hash_ctx
);
176 static bool hashCFThing(CFTypeRef thing
, CC_SHA256_CTX
* hash_ctx
) {
177 if (isArray(thing
)) {
178 return hashArray(thing
, hash_ctx
);
179 } else if (isDictionary(thing
)) {
180 return hashDictionary(thing
, hash_ctx
);
181 } else if (isData(thing
)) {
182 return hashData(thing
, hash_ctx
);
183 } else if (isString(thing
)) {
184 return hashString(thing
, hash_ctx
);
185 } else if (isNumber(thing
)) {
186 return hashNumber(thing
, hash_ctx
);
187 } else if (isBoolean(thing
)) {
188 return hashBoolean(thing
, hash_ctx
);
193 static double htond(double h
) {
194 /* no-op if big endian */
195 if (OSHostByteOrder() == OSBigEndian
) {
200 char *hp
= (char*)&h
;
201 char *np
= (char*)&n
;
202 while (i
< sizeof(h
)) { np
[i
] = hp
[(sizeof(h
)-1)-i
]; ++i
; }
206 /* Obtain swapped value of the 32-bit integer referenced by the given pointer.
207 Since a generic void or char pointer may not be aligned on a 4-byte boundary,
208 the UB sanitizer does not let us directly dereference it as *(uint32_t*).
210 static uint32_t SecSwapInt32Ptr(const void* P
) {
211 uint32_t _i
=0; memcpy(&_i
,P
,sizeof(_i
)); return OSSwapInt32(_i
);
216 // MARK: Valid definitions
218 ==============================================================================
220 ==============================================================================
223 const CFStringRef kValidUpdateProdServer
= CFSTR("valid.apple.com");
224 const CFStringRef kValidUpdateSeedServer
= CFSTR("valid.apple.com/seed");
225 const CFStringRef kValidUpdateCarryServer
= CFSTR("valid.apple.com/carry");
227 static CFStringRef kSecPrefsDomain
= CFSTR("com.apple.security");
228 static CFStringRef kUpdateServerKey
= CFSTR("ValidUpdateServer");
229 static CFStringRef kUpdateEnabledKey
= CFSTR("ValidUpdateEnabled");
230 static CFStringRef kVerifyEnabledKey
= CFSTR("ValidVerifyEnabled");
231 static CFStringRef kUpdateIntervalKey
= CFSTR("ValidUpdateInterval");
232 static CFStringRef kBoolTrueKey
= CFSTR("1");
233 static CFStringRef kBoolFalseKey
= CFSTR("0");
235 /* constant length of boolean string keys */
236 #define BOOL_STRING_KEY_LENGTH 1
238 typedef CF_OPTIONS(CFOptionFlags
, SecValidInfoFlags
) {
239 kSecValidInfoComplete
= 1u << 0,
240 kSecValidInfoCheckOCSP
= 1u << 1,
241 kSecValidInfoKnownOnly
= 1u << 2,
242 kSecValidInfoRequireCT
= 1u << 3,
243 kSecValidInfoAllowlist
= 1u << 4,
244 kSecValidInfoNoCACheck
= 1u << 5,
245 kSecValidInfoOverridable
= 1u << 6,
246 kSecValidInfoDateConstraints
= 1u << 7,
247 kSecValidInfoNameConstraints
= 1u << 8,
248 kSecValidInfoPolicyConstraints
= 1u << 9,
249 kSecValidInfoNoCAv2Check
= 1u << 10,
252 /* minimum update interval */
253 #define kSecMinUpdateInterval (60.0 * 5)
255 /* standard update interval */
256 #define kSecStdUpdateInterval (60.0 * 60 * 3)
258 /* maximum allowed interval */
259 #define kSecMaxUpdateInterval (60.0 * 60 * 24 * 7)
261 /* filenames we use, relative to revocation info directory */
262 #define kSecRevocationCurUpdateFile "update-current"
263 #define kSecRevocationDbFileName "valid.sqlite3"
264 #define kSecRevocationDbReplaceFile ".valid_replace"
266 #define isDbOwner SecOTAPKIIsSystemTrustd
268 #define kSecRevocationDbChanged "com.apple.trustd.valid.db-changed"
270 /* database schema version
272 v2 = fix for group entry transitions
273 v3 = handle optional entries in update dictionaries
274 v4 = add db_format and db_source entries
275 v5 = add date constraints table, with updated group flags
276 v6 = explicitly set autovacuum and journal modes at db creation
277 v7 = add policies column to groups table (policy constraints)
279 Note: kSecRevocationDbMinSchemaVersion is the lowest version whose
280 results can be used. This allows revocation results to be obtained
281 from an existing db before the next update interval occurs, at which
282 time we'll update to the current version (kSecRevocationDbSchemaVersion).
284 #define kSecRevocationDbSchemaVersion 7 /* current version we support */
285 #define kSecRevocationDbMinSchemaVersion 7 /* minimum version we can use */
287 /* update file format
290 kSecValidUpdateFormatG1
= 1, /* initial version */
291 kSecValidUpdateFormatG2
= 2, /* signed content, single plist */
292 kSecValidUpdateFormatG3
= 3 /* signed content, multiple plists */
295 #define kSecRevocationDbUpdateFormat 3 /* current version we support */
296 #define kSecRevocationDbMinUpdateFormat 2 /* minimum version we can use */
298 #define kSecRevocationDbCacheSize 100
300 typedef struct __SecRevocationDb
*SecRevocationDbRef
;
301 struct __SecRevocationDb
{
303 dispatch_queue_t update_queue
;
304 bool updateInProgress
;
305 bool unsupportedVersion
;
307 CFMutableArrayRef info_cache_list
;
308 CFMutableDictionaryRef info_cache
;
309 os_unfair_lock info_cache_lock
;
312 typedef struct __SecRevocationDbConnection
*SecRevocationDbConnectionRef
;
313 struct __SecRevocationDbConnection
{
314 SecRevocationDbRef db
;
315 SecDbConnectionRef dbconn
;
316 CFIndex precommitVersion
;
317 CFIndex precommitDbVersion
;
318 CFIndex precommitInterval
;
322 bool SecRevocationDbVerifyUpdate(void *update
, CFIndex length
);
323 bool SecRevocationDbIngestUpdate(SecRevocationDbConnectionRef dbc
, CFDictionaryRef update
, CFIndex chunkVersion
, CFIndex
*outVersion
, CFErrorRef
*error
);
324 bool _SecRevocationDbApplyUpdate(SecRevocationDbConnectionRef dbc
, CFDictionaryRef update
, CFIndex version
, CFErrorRef
*error
);
325 CFAbsoluteTime
SecRevocationDbComputeNextUpdateTime(CFIndex updateInterval
);
326 bool SecRevocationDbUpdateSchema(SecRevocationDbRef rdb
);
327 CFIndex
SecRevocationDbGetUpdateFormat(void);
328 bool _SecRevocationDbSetUpdateSource(SecRevocationDbConnectionRef dbc
, CFStringRef source
, CFErrorRef
*error
);
329 bool SecRevocationDbSetUpdateSource(SecRevocationDbRef rdb
, CFStringRef source
);
330 CF_RETURNS_RETAINED CFStringRef
SecRevocationDbCopyUpdateSource(void);
331 bool SecRevocationDbSetNextUpdateTime(CFAbsoluteTime nextUpdate
, CFErrorRef
*error
);
332 CFAbsoluteTime
SecRevocationDbGetNextUpdateTime(void);
333 dispatch_queue_t
SecRevocationDbGetUpdateQueue(void);
334 bool _SecRevocationDbRemoveAllEntries(SecRevocationDbConnectionRef dbc
, CFErrorRef
*error
);
335 void SecRevocationDbReleaseAllConnections(void);
336 static bool SecValidUpdateForceReplaceDatabase(void);
337 static void SecRevocationDbWith(void(^dbJob
)(SecRevocationDbRef db
));
338 static bool SecRevocationDbPerformWrite(SecRevocationDbRef rdb
, CFErrorRef
*error
, bool(^writeJob
)(SecRevocationDbConnectionRef dbc
, CFErrorRef
*blockError
));
339 static bool SecRevocationDbPerformRead(SecRevocationDbRef rdb
, CFErrorRef
*error
, bool(^readJob
)(SecRevocationDbConnectionRef dbc
, CFErrorRef
*blockError
));
340 static SecValidInfoFormat
_SecRevocationDbGetGroupFormat(SecRevocationDbConnectionRef dbc
, int64_t groupId
, SecValidInfoFlags
*flags
, CFDataRef
*data
, CFDataRef
*policies
, CFErrorRef
*error
);
341 static bool _SecRevocationDbSetUpdateInterval(SecRevocationDbConnectionRef dbc
, int64_t interval
, CFErrorRef
*error
);
342 static int64_t _SecRevocationDbGetUpdateInterval(SecRevocationDbConnectionRef dbc
, CFErrorRef
*error
);
343 static bool _SecRevocationDbSetVersion(SecRevocationDbConnectionRef dbc
, CFIndex version
, CFErrorRef
*error
);
344 static int64_t _SecRevocationDbGetVersion(SecRevocationDbConnectionRef dbc
, CFErrorRef
*error
);
345 static int64_t _SecRevocationDbGetSchemaVersion(SecRevocationDbRef rdb
, SecRevocationDbConnectionRef dbc
, CFErrorRef
*error
);
346 static CFArrayRef
_SecRevocationDbCopyHashes(SecRevocationDbConnectionRef dbc
, CFErrorRef
*error
);
347 static bool _SecRevocationDbSetHashes(SecRevocationDbConnectionRef dbc
, CFArrayRef hashes
, CFErrorRef
*error
);
348 static void SecRevocationDbResetCaches(void);
349 static SecRevocationDbConnectionRef
SecRevocationDbConnectionInit(SecRevocationDbRef db
, SecDbConnectionRef dbconn
, CFErrorRef
*error
);
350 static bool SecRevocationDbComputeDigests(SecRevocationDbConnectionRef dbc
, CFErrorRef
*error
);
353 static CFDataRef
copyInflatedData(CFDataRef data
) {
358 memset(&zs
, 0, sizeof(zs
));
359 /* 32 is a magic value which enables automatic header detection
360 of gzip or zlib compressed data. */
361 if (inflateInit2(&zs
, 32+MAX_WBITS
) != Z_OK
) {
364 zs
.next_in
= (UInt8
*)(CFDataGetBytePtr(data
));
365 zs
.avail_in
= (uInt
)CFDataGetLength(data
);
367 CFMutableDataRef outData
= CFDataCreateMutable(NULL
, 0);
371 CFIndex buf_sz
= malloc_good_size(zs
.avail_in
? zs
.avail_in
: 1024 * 4);
372 unsigned char *buf
= malloc(buf_sz
);
375 zs
.next_out
= (Bytef
*)buf
;
376 zs
.avail_out
= (uInt
)buf_sz
;
377 rc
= inflate(&zs
, 0);
378 CFIndex outLen
= CFDataGetLength(outData
);
379 if (outLen
< (CFIndex
)zs
.total_out
) {
380 CFDataAppendBytes(outData
, (const UInt8
*)buf
, (CFIndex
)zs
.total_out
- outLen
);
382 } while (rc
== Z_OK
);
386 if (rc
!= Z_STREAM_END
) {
387 CFReleaseSafe(outData
);
390 return (CFDataRef
)outData
;
393 static CFDataRef
copyDeflatedData(CFDataRef data
) {
398 memset(&zs
, 0, sizeof(zs
));
399 if (deflateInit(&zs
, Z_BEST_COMPRESSION
) != Z_OK
) {
402 zs
.next_in
= (UInt8
*)(CFDataGetBytePtr(data
));
403 zs
.avail_in
= (uInt
)CFDataGetLength(data
);
405 CFMutableDataRef outData
= CFDataCreateMutable(NULL
, 0);
409 CFIndex buf_sz
= malloc_good_size(zs
.avail_in
? zs
.avail_in
: 1024 * 4);
410 unsigned char *buf
= malloc(buf_sz
);
411 int rc
= Z_BUF_ERROR
;
413 zs
.next_out
= (Bytef
*)buf
;
414 zs
.avail_out
= (uInt
)buf_sz
;
415 rc
= deflate(&zs
, Z_FINISH
);
417 if (rc
== Z_OK
|| rc
== Z_STREAM_END
) {
418 CFIndex buf_used
= buf_sz
- zs
.avail_out
;
419 CFDataAppendBytes(outData
, (const UInt8
*)buf
, buf_used
);
421 else if (rc
== Z_BUF_ERROR
) {
423 buf_sz
= malloc_good_size(buf_sz
* 2);
424 buf
= malloc(buf_sz
);
426 rc
= Z_OK
; /* try again with larger buffer */
429 } while (rc
== Z_OK
&& zs
.avail_in
);
433 if (rc
!= Z_STREAM_END
) {
434 CFReleaseSafe(outData
);
437 return (CFDataRef
)outData
;
440 /* Read file opens the file, mmaps it and then closes the file. */
441 int readValidFile(const char *fileName
,
442 CFDataRef
*bytes
) { // mmapped and returned -- must be munmapped!
444 const uint8_t *buf
= NULL
;
449 fd
= open(fileName
, O_RDONLY
);
450 if (fd
< 0) { return errno
; }
451 rtn
= fstat(fd
, &sb
);
452 if (rtn
) { goto errOut
; }
453 if (sb
.st_size
> (off_t
) ((UINT32_MAX
>> 1)-1)) {
457 size
= (size_t)sb
.st_size
;
459 buf
= mmap(NULL
, size
, PROT_READ
, MAP_PRIVATE
, fd
, 0);
460 if (!buf
|| buf
== MAP_FAILED
) {
462 secerror("unable to map %s (errno %d)", fileName
, rtn
);
466 *bytes
= CFDataCreateWithBytesNoCopy(NULL
, buf
, size
, kCFAllocatorNull
);
471 CFReleaseNull(*bytes
);
473 int unmap_err
= munmap((void *)buf
, size
);
474 if (unmap_err
!= 0) {
475 secerror("unable to unmap %ld bytes at %p (error %d)", (long)size
, buf
, rtn
);
482 static bool removeFileWithSuffix(const char *basepath
, const char *suffix
) {
485 asprintf(&path
, "%s%s", basepath
, suffix
);
487 if (remove(path
) == -1) {
489 if (error
== ENOENT
) {
490 result
= true; // not an error if the file did not exist
492 secnotice("validupdate", "remove (%s): %s", path
, strerror(error
));
502 static CFDataRef CF_RETURNS_RETAINED
createPoliciesData(CFArrayRef policies
) {
504 * Given an array of CFNumber values (in the range 0..127),
505 * allocate and return a CFDataRef representation. Per Valid specification,
506 * a zero-length array is allowed, meaning no policies are permitted.
508 CFIndex count
= (policies
) ? CFArrayGetCount(policies
) : -1;
509 if (count
< 0 || count
> 127) {
510 return NULL
; /* either no constraints, or far more than we expect. */
512 CFDataRef data
= NULL
;
513 CFIndex length
= 1 + (sizeof(int8_t) * count
);
514 int8_t *bytes
= malloc(length
);
517 *p
++ = (int8_t)(count
& 0xFF);
518 for (CFIndex idx
= 0; idx
< count
; idx
++) {
520 CFNumberRef value
= (CFNumberRef
)CFArrayGetValueAtIndex(policies
, idx
);
521 if (isNumber(value
)) {
522 (void)CFNumberGetValue(value
, kCFNumberSInt8Type
, &pval
);
526 data
= CFDataCreate(kCFAllocatorDefault
, (const UInt8
*)bytes
, length
);
532 static CFDataRef CF_RETURNS_RETAINED
cfToHexData(CFDataRef data
, bool prependWildcard
) {
533 if (!isData(data
)) { return NULL
; }
534 CFIndex len
= CFDataGetLength(data
) * 2;
535 CFMutableStringRef hex
= CFStringCreateMutable(NULL
, len
+1);
536 static const char* digits
[]={
537 "0","1","2","3","4","5","6","7","8","9","A","B","C","D","E","F"};
538 if (prependWildcard
) {
539 CFStringAppendCString(hex
, "%", 1);
541 const uint8_t* p
= CFDataGetBytePtr(data
);
542 for (CFIndex i
= 0; i
< CFDataGetLength(data
); i
++) {
543 CFStringAppendCString(hex
, digits
[p
[i
] >> 4], 1);
544 CFStringAppendCString(hex
, digits
[p
[i
] & 0xf], 1);
546 CFDataRef result
= CFStringCreateExternalRepresentation(NULL
, hex
, kCFStringEncodingUTF8
, 0);
551 static bool copyFilterComponents(CFDataRef xmlData
, CFDataRef
* CF_RETURNS_RETAINED
xor,
552 CFArrayRef
* CF_RETURNS_RETAINED params
) {
554 The 'xmlData' parameter is a flattened XML dictionary,
555 containing 'xor' and 'params' keys. First order of
556 business is to reconstitute the blob into components.
559 CFRetainSafe(xmlData
);
560 CFDataRef propListData
= xmlData
;
561 /* Expand data blob if needed */
562 CFDataRef inflatedData
= copyInflatedData(propListData
);
564 CFReleaseSafe(propListData
);
565 propListData
= inflatedData
;
567 CFDataRef xorData
= NULL
;
568 CFArrayRef paramsArray
= NULL
;
569 CFPropertyListRef nto1
= CFPropertyListCreateWithData(kCFAllocatorDefault
, propListData
, 0, NULL
, NULL
);
570 CFReleaseSafe(propListData
);
572 xorData
= (CFDataRef
)CFDictionaryGetValue((CFDictionaryRef
)nto1
, CFSTR("xor"));
573 CFRetainSafe(xorData
);
574 paramsArray
= (CFArrayRef
)CFDictionaryGetValue((CFDictionaryRef
)nto1
, CFSTR("params"));
575 CFRetainSafe(paramsArray
);
578 result
= (xorData
&& paramsArray
);
582 CFReleaseSafe(xorData
);
585 *params
= paramsArray
;
587 CFReleaseSafe(paramsArray
);
593 // MARK: SecValidUpdate
595 ==============================================================================
597 ==============================================================================
600 CFAbsoluteTime gUpdateStarted
= 0.0;
601 CFAbsoluteTime gNextUpdate
= 0.0;
602 static CFIndex gUpdateInterval
= 0;
603 static CFIndex gLastVersion
= 0;
606 1. The length of the signed data, as a 4-byte integer in network byte order.
607 2. The signed data, which consists of:
608 a. A 4-byte integer in network byte order, the count of plists to follow; and then for each plist:
609 i. A 4-byte integer, the length of each plist
610 ii. A plist, in binary form
611 b. There may be other data after the plists in the signed data, described by a future version of this specification.
612 3. The length of the following CMS blob, as a 4-byte integer in network byte order.
613 4. A detached CMS signature of the signed data described above.
614 5. There may be additional data after the CMS blob, described by a future version of this specification.
616 Note: the difference between g2 and g3 format is the addition of the 4-byte count in (2a).
618 static bool SecValidUpdateProcessData(SecRevocationDbConnectionRef dbc
, CFIndex format
, CFDataRef updateData
, CFErrorRef
*error
) {
620 if (!updateData
|| format
< 2) {
621 SecError(errSecParam
, error
, CFSTR("SecValidUpdateProcessData: invalid update format"));
625 CFIndex interval
= 0;
626 const UInt8
* p
= CFDataGetBytePtr(updateData
);
627 size_t bytesRemaining
= (p
) ? (size_t)CFDataGetLength(updateData
) : 0;
628 /* make sure there is enough data to contain length and count */
629 if (bytesRemaining
< ((CFIndex
)sizeof(uint32_t) * 2)) {
630 secinfo("validupdate", "Skipping property list creation (length %ld is too short)", (long)bytesRemaining
);
631 SecError(errSecParam
, error
, CFSTR("SecValidUpdateProcessData: data length is too short"));
634 uint32_t dataLength
= SecSwapInt32Ptr(p
);
635 bytesRemaining
-= sizeof(uint32_t);
636 p
+= sizeof(uint32_t);
638 /* get plist count (G3 format and later) */
639 uint32_t plistCount
= 1;
640 uint32_t plistTotal
= 1;
641 if (format
> kSecValidUpdateFormatG2
) {
642 plistCount
= SecSwapInt32Ptr(p
);
643 plistTotal
= plistCount
;
644 bytesRemaining
-= sizeof(uint32_t);
645 p
+= sizeof(uint32_t);
647 if (dataLength
> bytesRemaining
) {
648 secinfo("validupdate", "Skipping property list creation (dataLength=%ld, bytesRemaining=%ld)",
649 (long)dataLength
, (long)bytesRemaining
);
650 SecError(errSecParam
, error
, CFSTR("SecValidUpdateProcessData: data longer than expected"));
654 /* process each chunked plist */
656 CFErrorRef localError
= NULL
;
657 uint32_t plistProcessed
= 0;
658 while (plistCount
> 0 && bytesRemaining
> 0) {
659 CFPropertyListRef propertyList
= NULL
;
660 uint32_t plistLength
= dataLength
;
661 if (format
> kSecValidUpdateFormatG2
) {
662 plistLength
= SecSwapInt32Ptr(p
);
663 bytesRemaining
-= sizeof(uint32_t);
664 p
+= sizeof(uint32_t);
669 if (plistLength
<= bytesRemaining
) {
670 CFDataRef data
= CFDataCreateWithBytesNoCopy(NULL
, p
, plistLength
, kCFAllocatorNull
);
671 propertyList
= CFPropertyListCreateWithData(NULL
, data
, kCFPropertyListImmutable
, NULL
, NULL
);
674 if (isDictionary(propertyList
)) {
675 secdebug("validupdate", "Ingesting plist chunk %u of %u, length: %u",
676 plistProcessed
, plistTotal
, plistLength
);
677 CFIndex curVersion
= -1;
678 ok
= ok
&& SecRevocationDbIngestUpdate(dbc
, (CFDictionaryRef
)propertyList
, version
, &curVersion
, &localError
);
679 if (plistProcessed
== 1) {
680 dbc
->precommitVersion
= version
= curVersion
;
681 // get server-provided interval
682 CFTypeRef value
= (CFNumberRef
)CFDictionaryGetValue((CFDictionaryRef
)propertyList
,
683 CFSTR("check-again"));
684 if (isNumber(value
)) {
685 CFNumberGetValue((CFNumberRef
)value
, kCFNumberCFIndexType
, &interval
);
687 // get server-provided hash list
688 value
= (CFArrayRef
)CFDictionaryGetValue((CFDictionaryRef
)propertyList
,
690 ok
= _SecRevocationDbSetHashes(dbc
, (CFArrayRef
)value
, &localError
);
692 if (ok
&& curVersion
< 0) {
693 plistCount
= 0; // we already had this version; skip remaining plists
697 secinfo("validupdate", "Failed to deserialize update chunk %u of %u",
698 plistProcessed
, plistTotal
);
699 SecError(errSecParam
, error
, CFSTR("SecValidUpdateProcessData: failed to get update chunk"));
700 if (plistProcessed
== 1) {
701 gNextUpdate
= SecRevocationDbComputeNextUpdateTime(0);
704 /* All finished with this property list */
705 CFReleaseSafe(propertyList
);
707 bytesRemaining
-= plistLength
;
711 if (ok
&& version
> 0) {
712 secdebug("validupdate", "Update received: v%ld", (long)version
);
713 gLastVersion
= version
;
714 gNextUpdate
= SecRevocationDbComputeNextUpdateTime(interval
);
715 secdebug("validupdate", "Next update time: %f", gNextUpdate
);
719 (void) CFErrorPropagate(localError
, error
);
723 void SecValidUpdateVerifyAndIngest(CFDataRef updateData
, CFStringRef updateServer
, bool fullUpdate
) {
725 secnotice("validupdate", "invalid update data");
728 /* Verify CMS signature on signed data */
729 if (!SecRevocationDbVerifyUpdate((void *)CFDataGetBytePtr(updateData
), CFDataGetLength(updateData
))) {
730 secerror("failed to verify valid update");
731 TrustdHealthAnalyticsLogErrorCode(TAEventValidUpdate
, TAFatalError
, errSecVerifyFailed
);
734 /* Read current update source from database. */
735 CFStringRef dbSource
= SecRevocationDbCopyUpdateSource();
736 if (dbSource
&& updateServer
&& (kCFCompareEqualTo
!= CFStringCompare(dbSource
, updateServer
,
737 kCFCompareCaseInsensitive
))) {
738 secnotice("validupdate", "switching db source from \"%@\" to \"%@\"", dbSource
, updateServer
);
740 CFReleaseNull(dbSource
);
742 /* Ingest the update. This is now performed under a single immediate write transaction,
743 so other writers are blocked (but not other readers), and the changes can be rolled back
744 in their entirety if any error occurs. */
745 __block
bool ok
= true;
746 __block CFErrorRef localError
= NULL
;
747 SecRevocationDbWith(^(SecRevocationDbRef rdb
) {
748 ok
&= SecRevocationDbPerformWrite(rdb
, &localError
, ^bool(SecRevocationDbConnectionRef dbc
, CFErrorRef
*blockError
) {
750 /* Must completely replace existing database contents */
751 secdebug("validupdate", "starting to process full update; clearing database");
752 ok
= ok
&& _SecRevocationDbRemoveAllEntries(dbc
, blockError
);
753 ok
= ok
&& _SecRevocationDbSetUpdateSource(dbc
, updateServer
, blockError
);
754 dbc
->precommitVersion
= 0;
755 dbc
->fullUpdate
= true;
757 CFIndex startingVersion
= dbc
->precommitVersion
;
758 ok
= ok
&& SecValidUpdateProcessData(dbc
, kSecValidUpdateFormatG3
, updateData
, blockError
);
759 rdb
->changed
= ok
&& (startingVersion
< dbc
->precommitVersion
);
761 secerror("failed to process valid update: %@", blockError
? *blockError
: NULL
);
762 TrustdHealthAnalyticsLogErrorCode(TAEventValidUpdate
, TAFatalError
, errSecDecode
);
764 TrustdHealthAnalyticsLogSuccess(TAEventValidUpdate
);
769 rdb
->changed
= false;
770 bool verifyEnabled
= false;
771 CFBooleanRef value
= (CFBooleanRef
)CFPreferencesCopyValue(kVerifyEnabledKey
,
772 kSecPrefsDomain
, kCFPreferencesAnyUser
, kCFPreferencesCurrentHost
);
773 if (isBoolean(value
)) {
774 verifyEnabled
= CFBooleanGetValue(value
);
776 CFReleaseNull(value
);
778 /* compute and verify database content hashes */
779 ok
= ok
&& SecRevocationDbPerformRead(rdb
, &localError
, ^bool(SecRevocationDbConnectionRef dbc
, CFErrorRef
*blockError
) {
780 ok
= ok
&& SecRevocationDbComputeDigests(dbc
, blockError
);
782 /* digests failed to verify, so roll back to known-good snapshot */
783 (void) SecValidUpdateForceReplaceDatabase();
788 /* signal other trustd instances that the database has been updated */
789 notify_post(kSecRevocationDbChanged
);
793 /* remember next update time in case of restart (separate write transaction) */
794 (void) SecRevocationDbSetNextUpdateTime(gNextUpdate
, NULL
);
796 CFReleaseSafe(localError
);
799 static bool SecValidUpdateForceReplaceDatabase(void) {
800 __block
bool result
= false;
802 // write semaphore file that we will pick up when we next launch
803 WithPathInRevocationInfoDirectory(CFSTR(kSecRevocationDbReplaceFile
), ^(const char *utf8String
) {
805 int fd
= open(utf8String
, O_WRONLY
| O_CREAT
, DEFFILEMODE
);
806 if (fd
== -1 || fstat(fd
, &sb
)) {
807 secnotice("validupdate", "unable to write %s (error %d)", utf8String
, errno
);
816 // exit as gracefully as possible so we can replace the database
817 secnotice("validupdate", "process exiting to replace db file");
818 dispatch_after(dispatch_time(DISPATCH_TIME_NOW
, 3ull*NSEC_PER_SEC
), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT
, 0), ^{
819 xpc_transaction_exit_clean();
825 static bool SecValidUpdateSatisfiedLocally(CFStringRef server
, CFIndex version
, bool safeToReplace
) {
826 __block
bool result
= false;
827 SecOTAPKIRef otapkiRef
= NULL
;
828 bool relaunching
= false;
829 static int sNumLocalUpdates
= 0;
831 // if we've replaced the database with a local asset twice in a row,
832 // something is wrong with it. Get this update from the server.
833 if (sNumLocalUpdates
> 1) {
834 secdebug("validupdate", "%d consecutive db resets, ignoring local asset", sNumLocalUpdates
);
838 // if a non-production server is specified, we will not be able to use a
839 // local production asset since its update sequence will be different.
840 if (kCFCompareEqualTo
!= CFStringCompare(server
, kValidUpdateProdServer
,
841 kCFCompareCaseInsensitive
)) {
842 secdebug("validupdate", "non-production server specified, ignoring local asset");
846 // check static database asset(s)
847 otapkiRef
= SecOTAPKICopyCurrentOTAPKIRef();
851 CFIndex assetVersion
= SecOTAPKIGetValidSnapshotVersion(otapkiRef
);
852 CFIndex assetFormat
= SecOTAPKIGetValidSnapshotFormat(otapkiRef
);
853 // version <= 0 means the database is invalid or empty.
854 // version > 0 means we have some version, but we need to see if a
855 // newer version is available as a local asset.
856 if (assetVersion
<= version
|| assetFormat
< kSecValidUpdateFormatG3
) {
857 // asset is not newer than ours, or its version is unknown
861 // replace database only if safe to do so (i.e. called at startup)
862 if (!safeToReplace
) {
863 relaunching
= SecValidUpdateForceReplaceDatabase();
867 // try to copy uncompressed database asset, if available
868 const char *validDbPathBuf
= SecOTAPKIGetValidDatabaseSnapshot(otapkiRef
);
869 if (validDbPathBuf
) {
870 WithPathInRevocationInfoDirectory(CFSTR(kSecRevocationDbFileName
), ^(const char *path
) {
871 secdebug("validupdate", "will copy data from \"%s\"", validDbPathBuf
);
872 copyfile_state_t state
= copyfile_state_alloc();
873 int retval
= copyfile(validDbPathBuf
, path
, state
, COPYFILE_DATA
);
874 copyfile_state_free(state
);
876 secnotice("validupdate", "copyfile error %d", retval
);
884 CFReleaseNull(otapkiRef
);
887 gLastVersion
= SecRevocationDbGetVersion();
888 // note: snapshot should already have latest schema and production source,
889 // but set it here anyway so we don't keep trying to replace the db.
890 SecRevocationDbWith(^(SecRevocationDbRef db
) {
891 (void)SecRevocationDbSetUpdateSource(db
, server
);
892 (void)SecRevocationDbUpdateSchema(db
);
895 secdebug("validupdate", "local update to g%ld/v%ld complete at %f",
896 (long)SecRevocationDbGetUpdateFormat(), (long)gLastVersion
,
897 (double)CFAbsoluteTimeGetCurrent());
899 sNumLocalUpdates
= 0; // reset counter
902 // request is locally satisfied; don't schedule a network update
908 static bool SecValidUpdateSchedule(bool updateEnabled
, CFStringRef server
, CFIndex version
) {
909 /* Check if we have a later version available locally */
910 if (SecValidUpdateSatisfiedLocally(server
, version
, false)) {
914 /* If update not permitted return */
915 if (!updateEnabled
) {
916 secnotice("validupdate", "skipping update");
920 #if !TARGET_OS_BRIDGE
921 /* Schedule as a maintenance task */
922 secdebug("validupdate", "will fetch v%lu from \"%@\"", (unsigned long)version
, server
);
923 return SecValidUpdateRequest(SecRevocationDbGetUpdateQueue(), server
, version
);
929 static CFStringRef
SecRevocationDbGetDefaultServer(void) {
930 #if !TARGET_OS_WATCH && !TARGET_OS_BRIDGE
932 CFStringRef defaultServer
= kValidUpdateSeedServer
;
933 #else // !RC_SEED_BUILD
934 CFStringRef defaultServer
= kValidUpdateProdServer
;
935 #endif // !RC_SEED_BUILD
936 if (os_variant_has_internal_diagnostics("com.apple.security")) {
937 defaultServer
= kValidUpdateCarryServer
;
939 return defaultServer
;
940 #else // TARGET_OS_WATCH || TARGET_OS_BRIDGE
941 /* Because watchOS and bridgeOS can't update over the air, we should
942 * always use the prod server so that the valid database built into the
944 return kValidUpdateProdServer
;
948 static CF_RETURNS_RETAINED CFStringRef
SecRevocationDbCopyServer(void) {
949 /* Prefer a in-process setting for the update server, as used in testing */
950 CFTypeRef value
= CFPreferencesCopyAppValue(kUpdateServerKey
, kSecPrefsDomain
);
952 value
= CFPreferencesCopyValue(kUpdateServerKey
, kSecPrefsDomain
, kCFPreferencesAnyUser
, kCFPreferencesCurrentHost
);
954 CFStringRef server
= NULL
;
955 if (isString(value
)) {
956 server
= (CFStringRef
)value
;
958 CFReleaseNull(value
);
959 server
= CFRetainSafe(SecRevocationDbGetDefaultServer());
964 void SecRevocationDbInitialize() {
965 if (!isDbOwner()) { return; }
966 os_transaction_t transaction
= os_transaction_create("com.apple.trustd.valid.initialize");
967 __block
bool initializeDb
= false;
969 /* create base path if it doesn't exist */
970 WithPathInRevocationInfoDirectory(NULL
, ^(const char *utf8String
) {
971 (void)mkpath_np(utf8String
, 0755);
974 /* check semaphore file */
975 WithPathInRevocationInfoDirectory(CFSTR(kSecRevocationDbReplaceFile
), ^(const char *path
) {
977 if (stat(path
, &sb
) == 0) {
978 initializeDb
= true; /* file was found, so we will replace the database */
979 if (remove(path
) == -1) {
981 secnotice("validupdate", "remove (%s): %s", path
, strerror(error
));
987 WithPathInRevocationInfoDirectory(CFSTR(kSecRevocationDbFileName
), ^(const char *path
) {
989 /* remove old database file(s) */
990 (void)removeFileWithSuffix(path
, "");
991 (void)removeFileWithSuffix(path
, "-journal");
992 (void)removeFileWithSuffix(path
, "-shm");
993 (void)removeFileWithSuffix(path
, "-wal");
997 if (stat(path
, &sb
) == -1) {
998 initializeDb
= true; /* file not found, so we will create the database */
1003 if (!initializeDb
) {
1004 os_release(transaction
);
1005 return; /* database exists and doesn't need replacing */
1008 /* initialize database from local asset */
1009 CFStringRef server
= SecRevocationDbCopyServer();
1010 CFIndex version
= 0;
1011 secnotice("validupdate", "initializing database");
1012 if (!SecValidUpdateSatisfiedLocally(server
, version
, true)) {
1013 #if !TARGET_OS_BRIDGE
1014 /* Schedule full update as a maintenance task */
1015 (void)SecValidUpdateRequest(SecRevocationDbGetUpdateQueue(), server
, version
);
1018 CFReleaseSafe(server
);
1019 os_release(transaction
);
1024 // MARK: SecValidInfoRef
1026 ==============================================================================
1028 ==============================================================================
1031 CFGiblisWithCompareFor(SecValidInfo
);
1033 static SecValidInfoRef
SecValidInfoCreate(SecValidInfoFormat format
,
1034 CFOptionFlags flags
,
1037 CFDataRef issuerHash
,
1038 CFDataRef anchorHash
,
1039 CFDateRef notBeforeDate
,
1040 CFDateRef notAfterDate
,
1041 CFDataRef nameConstraints
,
1042 CFDataRef policyConstraints
) {
1043 SecValidInfoRef validInfo
;
1044 validInfo
= CFTypeAllocate(SecValidInfo
, struct __SecValidInfo
, kCFAllocatorDefault
);
1045 if (!validInfo
) { return NULL
; }
1047 CFRetainSafe(certHash
);
1048 CFRetainSafe(issuerHash
);
1049 CFRetainSafe(anchorHash
);
1050 CFRetainSafe(notBeforeDate
);
1051 CFRetainSafe(notAfterDate
);
1052 CFRetainSafe(nameConstraints
);
1053 CFRetainSafe(policyConstraints
);
1055 validInfo
->format
= format
;
1056 validInfo
->certHash
= certHash
;
1057 validInfo
->issuerHash
= issuerHash
;
1058 validInfo
->anchorHash
= anchorHash
;
1059 validInfo
->isOnList
= isOnList
;
1060 validInfo
->valid
= (flags
& kSecValidInfoAllowlist
);
1061 validInfo
->complete
= (flags
& kSecValidInfoComplete
);
1062 validInfo
->checkOCSP
= (flags
& kSecValidInfoCheckOCSP
);
1063 validInfo
->knownOnly
= (flags
& kSecValidInfoKnownOnly
);
1064 validInfo
->requireCT
= (flags
& kSecValidInfoRequireCT
);
1065 validInfo
->noCACheck
= (flags
& kSecValidInfoNoCAv2Check
);
1066 validInfo
->overridable
= (flags
& kSecValidInfoOverridable
);
1067 validInfo
->hasDateConstraints
= (flags
& kSecValidInfoDateConstraints
);
1068 validInfo
->hasNameConstraints
= (flags
& kSecValidInfoNameConstraints
);
1069 validInfo
->hasPolicyConstraints
= (flags
& kSecValidInfoPolicyConstraints
);
1070 validInfo
->notBeforeDate
= notBeforeDate
;
1071 validInfo
->notAfterDate
= notAfterDate
;
1072 validInfo
->nameConstraints
= nameConstraints
;
1073 validInfo
->policyConstraints
= policyConstraints
;
1078 static void SecValidInfoDestroy(CFTypeRef cf
) {
1079 SecValidInfoRef validInfo
= (SecValidInfoRef
)cf
;
1081 CFReleaseNull(validInfo
->certHash
);
1082 CFReleaseNull(validInfo
->issuerHash
);
1083 CFReleaseNull(validInfo
->anchorHash
);
1084 CFReleaseNull(validInfo
->notBeforeDate
);
1085 CFReleaseNull(validInfo
->notAfterDate
);
1086 CFReleaseNull(validInfo
->nameConstraints
);
1087 CFReleaseNull(validInfo
->policyConstraints
);
1091 void SecValidInfoSetAnchor(SecValidInfoRef validInfo
, SecCertificateRef anchor
) {
1095 CFDataRef anchorHash
= NULL
;
1097 anchorHash
= SecCertificateCopySHA256Digest(anchor
);
1099 /* clear no-ca flag for anchors where we want OCSP checked [32523118] */
1100 if (SecIsAppleTrustAnchor(anchor
, 0)) {
1101 validInfo
->noCACheck
= false;
1104 CFReleaseNull(validInfo
->anchorHash
);
1105 validInfo
->anchorHash
= anchorHash
;
1108 static Boolean
SecValidInfoCompare(CFTypeRef a
, CFTypeRef b
) {
1109 SecValidInfoRef validInfoA
= (SecValidInfoRef
)a
;
1110 SecValidInfoRef validInfoB
= (SecValidInfoRef
)b
;
1111 if (validInfoA
== validInfoB
) {
1114 if (!validInfoA
|| !validInfoB
||
1115 (CFGetTypeID(a
) != SecValidInfoGetTypeID()) ||
1116 (CFGetTypeID(b
) != SecValidInfoGetTypeID())) {
1119 return CFEqualSafe(validInfoA
->certHash
, validInfoB
->certHash
) && CFEqualSafe(validInfoA
->issuerHash
, validInfoB
->issuerHash
);
1122 static CFStringRef
SecValidInfoCopyFormatDescription(CFTypeRef cf
, CFDictionaryRef formatOptions
) {
1123 SecValidInfoRef validInfo
= (SecValidInfoRef
)cf
;
1124 CFStringRef certHash
= CFDataCopyHexString(validInfo
->certHash
);
1125 CFStringRef issuerHash
= CFDataCopyHexString(validInfo
->issuerHash
);
1126 CFStringRef desc
= CFStringCreateWithFormat(NULL
, formatOptions
, CFSTR("validInfo certHash: %@ issuerHash: %@"), certHash
, issuerHash
);
1127 CFReleaseNull(certHash
);
1128 CFReleaseNull(issuerHash
);
1134 // MARK: SecRevocationDb
1136 ==============================================================================
1138 ==============================================================================
1141 static CFIndex
_SecRevocationDbGetUpdateVersion(CFStringRef server
) {
1142 // determine version of our current database
1143 CFIndex version
= SecRevocationDbGetVersion();
1144 secdebug("validupdate", "got version %ld from db", (long)version
);
1146 if (gLastVersion
> 0) {
1147 secdebug("validupdate", "error getting version; using last good version: %ld", (long)gLastVersion
);
1149 version
= gLastVersion
;
1152 // determine source of our current database
1153 // (if this ever changes, we will need to reload the db)
1154 CFStringRef db_source
= SecRevocationDbCopyUpdateSource();
1156 db_source
= (CFStringRef
) CFRetain(kValidUpdateProdServer
);
1159 // determine whether we need to recreate the database
1160 CFIndex db_version
= SecRevocationDbGetSchemaVersion();
1161 CFIndex db_format
= SecRevocationDbGetUpdateFormat();
1162 if (db_version
< kSecRevocationDbSchemaVersion
||
1163 db_format
< kSecRevocationDbUpdateFormat
||
1164 kCFCompareEqualTo
!= CFStringCompare(server
, db_source
, kCFCompareCaseInsensitive
)) {
1165 // we need to fully rebuild the db contents, so we set our version to 0.
1166 version
= gLastVersion
= 0;
1168 CFReleaseNull(db_source
);
1172 static bool _SecRevocationDbIsUpdateEnabled(void) {
1173 CFTypeRef value
= NULL
;
1174 // determine whether update fetching is enabled
1175 #if !TARGET_OS_WATCH && !TARGET_OS_BRIDGE
1176 // Valid update fetching was initially enabled on macOS 10.13 and iOS 11.0.
1177 // This conditional has been changed to include every platform and version
1178 // except for those where the db should not be updated over the air.
1179 bool updateEnabled
= true;
1181 bool updateEnabled
= false;
1183 value
= (CFBooleanRef
)CFPreferencesCopyValue(kUpdateEnabledKey
, kSecPrefsDomain
, kCFPreferencesAnyUser
, kCFPreferencesCurrentHost
);
1184 if (isBoolean(value
)) {
1185 updateEnabled
= CFBooleanGetValue((CFBooleanRef
)value
);
1187 CFReleaseNull(value
);
1188 return updateEnabled
;
1191 /* SecRevocationDbCheckNextUpdate returns true if we dispatched an
1192 update request, otherwise false.
1194 static bool _SecRevocationDbCheckNextUpdate(void) {
1195 // are we the db owner instance?
1199 CFTypeRef value
= NULL
;
1201 // is it time to check?
1202 CFAbsoluteTime now
= CFAbsoluteTimeGetCurrent();
1203 CFAbsoluteTime minNextUpdate
= now
+ gUpdateInterval
;
1204 gUpdateStarted
= now
;
1206 if (0 == gNextUpdate
) {
1207 // first time we're called, check if we have a saved nextUpdate value
1208 gNextUpdate
= SecRevocationDbGetNextUpdateTime();
1209 minNextUpdate
= now
;
1210 if (gNextUpdate
< minNextUpdate
) {
1211 gNextUpdate
= minNextUpdate
;
1213 // allow pref to override update interval, if it exists
1214 CFIndex interval
= -1;
1215 value
= (CFNumberRef
)CFPreferencesCopyValue(kUpdateIntervalKey
, kSecPrefsDomain
, kCFPreferencesAnyUser
, kCFPreferencesCurrentHost
);
1216 if (isNumber(value
)) {
1217 if (CFNumberGetValue((CFNumberRef
)value
, kCFNumberCFIndexType
, &interval
)) {
1218 if (interval
< kSecMinUpdateInterval
) {
1219 interval
= kSecMinUpdateInterval
;
1220 } else if (interval
> kSecMaxUpdateInterval
) {
1221 interval
= kSecMaxUpdateInterval
;
1225 CFReleaseNull(value
);
1226 gUpdateInterval
= kSecStdUpdateInterval
;
1228 gUpdateInterval
= interval
;
1230 // pin next update time to the preferred update interval
1231 if (gNextUpdate
> (gUpdateStarted
+ gUpdateInterval
)) {
1232 gNextUpdate
= gUpdateStarted
+ gUpdateInterval
;
1234 secdebug("validupdate", "next update at %f (in %f seconds)",
1235 (double)gUpdateStarted
, (double)gNextUpdate
-gUpdateStarted
);
1237 if (gNextUpdate
> now
) {
1241 secnotice("validupdate", "starting update");
1243 // set minimum next update time here in case we can't get an update
1244 gNextUpdate
= minNextUpdate
;
1246 // determine which server to query
1247 CFStringRef server
= SecRevocationDbCopyServer();
1249 // determine version to update from
1250 CFIndex version
= _SecRevocationDbGetUpdateVersion(server
);
1252 // determine if update is enabled for this device
1253 bool updateEnabled
= _SecRevocationDbIsUpdateEnabled();
1255 // Schedule maintenance work
1256 bool result
= SecValidUpdateSchedule(updateEnabled
, server
, version
);
1257 CFReleaseNull(server
);
1261 void SecRevocationDbCheckNextUpdate(void) {
1262 static dispatch_once_t once
;
1263 static sec_action_t action
;
1265 dispatch_once(&once
, ^{
1266 dispatch_queue_t update_queue
= SecRevocationDbGetUpdateQueue();
1267 action
= sec_action_create_with_queue(update_queue
, "update_check", kSecMinUpdateInterval
);
1268 sec_action_set_handler(action
, ^{
1269 os_transaction_t transaction
= os_transaction_create("com.apple.trustd.valid.checkNextUpdate");
1270 (void)_SecRevocationDbCheckNextUpdate();
1271 os_release(transaction
);
1274 sec_action_perform(action
);
1277 bool SecRevocationDbUpdate(CFErrorRef
*error
)
1279 // are we the db owner instance?
1281 return SecError(errSecWrPerm
, error
, CFSTR("Unable to update Valid DB from user agent"));
1284 if (!_SecRevocationDbIsUpdateEnabled()) {
1285 return SecError(errSecWrPerm
, error
, CFSTR("Valid updates not enabled on this device"));
1288 CFStringRef server
= SecRevocationDbCopyServer();
1289 CFIndex version
= _SecRevocationDbGetUpdateVersion(server
);
1291 secdebug("validupdate", "will fetch v%lu from \"%@\" now", (unsigned long)version
, server
);
1292 bool result
= SecValidUpdateUpdateNow(SecRevocationDbGetUpdateQueue(), server
, version
);
1293 CFReleaseNull(server
);
1297 /* This function verifies an update, in this format:
1298 1) unsigned 32-bit network-byte-order length of binary plist
1299 2) binary plist data
1300 3) unsigned 32-bit network-byte-order length of CMS message
1301 4) CMS message (containing certificates and signature over binary plist)
1303 The length argument is the total size of the packed update data.
1305 bool SecRevocationDbVerifyUpdate(void *update
, CFIndex length
) {
1306 if (!update
|| length
<= (CFIndex
)sizeof(uint32_t)) {
1309 uint32_t plistLength
= SecSwapInt32Ptr(update
);
1310 if ((plistLength
+ (CFIndex
)(sizeof(uint32_t)*2)) > (uint64_t) length
) {
1311 secdebug("validupdate", "ERROR: reported plist length (%lu)+%lu exceeds total length (%lu)\n",
1312 (unsigned long)plistLength
, (unsigned long)sizeof(uint32_t)*2, (unsigned long)length
);
1315 uint8_t *plistData
= (uint8_t *)update
+ sizeof(uint32_t);
1316 uint8_t *sigData
= (uint8_t *)plistData
+ plistLength
;
1317 uint32_t sigLength
= SecSwapInt32Ptr(sigData
);
1318 sigData
+= sizeof(uint32_t);
1319 if ((plistLength
+ sigLength
+ (CFIndex
)(sizeof(uint32_t) * 2)) != (uint64_t) length
) {
1320 secdebug("validupdate", "ERROR: reported lengths do not add up to total length\n");
1324 OSStatus status
= 0;
1325 CMSSignerStatus signerStatus
;
1326 CMSDecoderRef cms
= NULL
;
1327 SecPolicyRef policy
= NULL
;
1328 SecTrustRef trust
= NULL
;
1329 CFDataRef content
= NULL
;
1331 if ((content
= CFDataCreateWithBytesNoCopy(kCFAllocatorDefault
,
1332 (const UInt8
*)plistData
, (CFIndex
)plistLength
, kCFAllocatorNull
)) == NULL
) {
1333 secdebug("validupdate", "CFDataCreateWithBytesNoCopy failed (%ld bytes)\n", (long)plistLength
);
1337 if ((status
= CMSDecoderCreate(&cms
)) != errSecSuccess
) {
1338 secdebug("validupdate", "CMSDecoderCreate failed with error %d\n", (int)status
);
1341 if ((status
= CMSDecoderUpdateMessage(cms
, sigData
, sigLength
)) != errSecSuccess
) {
1342 secdebug("validupdate", "CMSDecoderUpdateMessage failed with error %d\n", (int)status
);
1345 if ((status
= CMSDecoderSetDetachedContent(cms
, content
)) != errSecSuccess
) {
1346 secdebug("validupdate", "CMSDecoderSetDetachedContent failed with error %d\n", (int)status
);
1349 if ((status
= CMSDecoderFinalizeMessage(cms
)) != errSecSuccess
) {
1350 secdebug("validupdate", "CMSDecoderFinalizeMessage failed with error %d\n", (int)status
);
1354 policy
= SecPolicyCreateApplePinned(CFSTR("ValidUpdate"), // kSecPolicyNameAppleValidUpdate
1355 CFSTR("1.2.840.113635.100.6.2.10"), // System Integration 2 Intermediate Certificate
1356 CFSTR("1.2.840.113635.100.6.51")); // Valid update signing OID
1358 // Check that the first signer actually signed this message.
1359 if ((status
= CMSDecoderCopySignerStatus(cms
, 0, policy
,
1360 false, &signerStatus
, &trust
, NULL
)) != errSecSuccess
) {
1361 secdebug("validupdate", "CMSDecoderCopySignerStatus failed with error %d\n", (int)status
);
1364 // Make sure the signature verifies against the detached content
1365 if (signerStatus
!= kCMSSignerValid
) {
1366 secdebug("validupdate", "ERROR: signature did not verify (signer status %d)\n", (int)signerStatus
);
1367 status
= errSecInvalidSignature
;
1370 // Make sure the signing certificate is valid for the specified policy
1371 SecTrustResultType trustResult
= kSecTrustResultInvalid
;
1372 status
= SecTrustEvaluate(trust
, &trustResult
);
1373 if (status
!= errSecSuccess
) {
1374 secdebug("validupdate", "SecTrustEvaluate failed with error %d (trust=%p)\n", (int)status
, (void *)trust
);
1375 } else if (!(trustResult
== kSecTrustResultUnspecified
|| trustResult
== kSecTrustResultProceed
)) {
1376 secdebug("validupdate", "SecTrustEvaluate failed with trust result %d\n", (int)trustResult
);
1377 status
= errSecVerificationFailure
;
1382 CFReleaseSafe(content
);
1383 CFReleaseSafe(trust
);
1384 CFReleaseSafe(policy
);
1387 return (status
== errSecSuccess
);
1390 CFAbsoluteTime
SecRevocationDbComputeNextUpdateTime(CFIndex updateInterval
) {
1391 CFIndex interval
= updateInterval
;
1392 // try to use interval preference if it exists
1393 CFTypeRef value
= (CFNumberRef
)CFPreferencesCopyValue(kUpdateIntervalKey
, kSecPrefsDomain
, kCFPreferencesAnyUser
, kCFPreferencesCurrentHost
);
1394 if (isNumber(value
)) {
1395 CFNumberGetValue((CFNumberRef
)value
, kCFNumberCFIndexType
, &interval
);
1397 CFReleaseNull(value
);
1399 if (interval
<= 0) {
1400 interval
= kSecStdUpdateInterval
;
1404 if (interval
< kSecMinUpdateInterval
) {
1405 interval
= kSecMinUpdateInterval
;
1406 } else if (interval
> kSecMaxUpdateInterval
) {
1407 interval
= kSecMaxUpdateInterval
;
1410 // compute randomization factor, between 0 and 50% of the interval
1411 CFIndex fuzz
= arc4random() % (long)(interval
/2.0);
1412 CFAbsoluteTime nextUpdate
= CFAbsoluteTimeGetCurrent() + interval
+ fuzz
;
1413 secdebug("validupdate", "next update in %ld seconds", (long)(interval
+ fuzz
));
1417 void SecRevocationDbComputeAndSetNextUpdateTime(void) {
1418 gNextUpdate
= SecRevocationDbComputeNextUpdateTime(0);
1419 (void) SecRevocationDbSetNextUpdateTime(gNextUpdate
, NULL
);
1420 gUpdateStarted
= 0; /* no update is currently in progress */
1423 bool SecRevocationDbIngestUpdate(SecRevocationDbConnectionRef dbc
, CFDictionaryRef update
, CFIndex chunkVersion
, CFIndex
*outVersion
, CFErrorRef
*error
) {
1425 CFIndex version
= 0;
1426 CFErrorRef localError
= NULL
;
1428 SecError(errSecParam
, &localError
, CFSTR("SecRevocationDbIngestUpdate: invalid update parameter"));
1429 goto setVersionAndExit
;
1431 CFTypeRef value
= (CFNumberRef
)CFDictionaryGetValue(update
, CFSTR("version"));
1432 if (isNumber(value
)) {
1433 if (!CFNumberGetValue((CFNumberRef
)value
, kCFNumberCFIndexType
, &version
)) {
1438 // only the first chunk will have a version, so the second and
1439 // subsequent chunks will need to pass it in chunkVersion.
1440 version
= chunkVersion
;
1442 // check precommitted version since update hasn't been committed yet
1443 CFIndex curVersion
= dbc
->precommitVersion
;
1444 if (version
> curVersion
|| chunkVersion
> 0) {
1445 ok
= _SecRevocationDbApplyUpdate(dbc
, update
, version
, &localError
);
1446 secdebug("validupdate", "_SecRevocationDbApplyUpdate=%s, v%ld, precommit=%ld, full=%s",
1447 (ok
) ? "1" : "0", (long)version
, (long)dbc
->precommitVersion
,
1448 (dbc
->fullUpdate
) ? "1" : "0");
1450 secdebug("validupdate", "we have v%ld, skipping update to v%ld",
1451 (long)curVersion
, (long)version
);
1452 version
= -1; // invalid, so we know to skip subsequent chunks
1453 ok
= true; // this is not an error condition
1457 *outVersion
= version
;
1459 (void) CFErrorPropagate(localError
, error
);
1464 /* Database schema */
1466 /* admin table holds these key-value (or key-ival) pairs:
1467 'version' (integer) // version of database content
1468 'check_again' (double) // CFAbsoluteTime of next check (optional)
1469 'db_version' (integer) // version of database schema
1470 'db_hash' (blob) // SHA-256 database hash
1471 --> entries in admin table are unique by text key
1473 issuers table holds map of issuing CA hashes to group identifiers:
1474 groupid (integer) // associated group identifier in group ID table
1475 issuer_hash (blob) // SHA-256 hash of issuer certificate (primary key)
1476 --> entries in issuers table are unique by issuer_hash;
1477 multiple issuer entries may have the same groupid!
1479 groups table holds records with these attributes:
1480 groupid (integer) // ordinal ID associated with this group entry
1481 flags (integer) // a bitmask of the following values:
1482 kSecValidInfoComplete (0x00000001) set if we have all revocation info for this issuer group
1483 kSecValidInfoCheckOCSP (0x00000002) set if must check ocsp for certs from this issuer group
1484 kSecValidInfoKnownOnly (0x00000004) set if any CA from this issuer group must be in database
1485 kSecValidInfoRequireCT (0x00000008) set if all certs from this issuer group must have SCTs
1486 kSecValidInfoAllowlist (0x00000010) set if this entry describes valid certs (i.e. is allowed)
1487 kSecValidInfoNoCACheck (0x00000020) set if this entry does not require an OCSP check to accept (deprecated)
1488 kSecValidInfoOverridable (0x00000040) set if the trust status is recoverable and can be overridden
1489 kSecValidInfoDateConstraints (0x00000080) set if this group has not-before or not-after constraints
1490 kSecValidInfoNameConstraints (0x00000100) set if this group has name constraints in database
1491 kSecValidInfoPolicyConstraints (0x00000200) set if this group has policy constraints in database
1492 kSecValidInfoNoCAv2Check (0x00000400) set if this entry does not require an OCSP check to accept
1493 format (integer) // an integer describing format of entries:
1494 kSecValidInfoFormatUnknown (0) unknown format
1495 kSecValidInfoFormatSerial (1) serial number, not greater than 20 bytes in length
1496 kSecValidInfoFormatSHA256 (2) SHA-256 hash, 32 bytes in length
1497 kSecValidInfoFormatNto1 (3) filter data blob of arbitrary length
1498 data (blob) // Bloom filter data if format is 'nto1', otherwise NULL
1499 policies (blob) // NULL, or uint8_t count value followed by array of int8_t policy values
1500 --> entries in groups table are unique by groupid
1502 serials table holds serial number blobs with these attributes:
1503 groupid (integer) // identifier for issuer group in the groups table
1504 serial (blob) // serial number
1505 --> entries in serials table are unique by serial and groupid
1507 hashes table holds SHA-256 hashes of certificates with these attributes:
1508 groupid (integer) // identifier for issuer group in the groups table
1509 sha256 (blob) // SHA-256 hash of subject certificate
1510 --> entries in hashes table are unique by sha256 and groupid
1512 dates table holds notBefore and notAfter dates (as CFAbsoluteTime) with these attributes:
1513 groupid (integer) // identifier for issuer group in the groups table (primary key)
1514 notbefore (real) // issued certs are invalid if their notBefore is prior to this date
1515 notafter (real) // issued certs are invalid after this date (or their notAfter, if earlier)
1516 --> entries in dates table are unique by groupid, and only exist if kSecValidInfoDateConstraints is true
1519 #define createTablesSQL CFSTR("CREATE TABLE IF NOT EXISTS admin(" \
1520 "key TEXT PRIMARY KEY NOT NULL," \
1521 "ival INTEGER NOT NULL," \
1524 "CREATE TABLE IF NOT EXISTS issuers(" \
1525 "groupid INTEGER NOT NULL," \
1526 "issuer_hash BLOB PRIMARY KEY NOT NULL" \
1528 "CREATE INDEX IF NOT EXISTS issuer_idx ON issuers(issuer_hash);" \
1529 "CREATE TABLE IF NOT EXISTS groups(" \
1530 "groupid INTEGER PRIMARY KEY AUTOINCREMENT," \
1536 "CREATE TABLE IF NOT EXISTS serials(" \
1537 "groupid INTEGER NOT NULL," \
1538 "serial BLOB NOT NULL," \
1539 "UNIQUE(groupid,serial)" \
1541 "CREATE TABLE IF NOT EXISTS hashes(" \
1542 "groupid INTEGER NOT NULL," \
1543 "sha256 BLOB NOT NULL," \
1544 "UNIQUE(groupid,sha256)" \
1546 "CREATE TABLE IF NOT EXISTS dates(" \
1547 "groupid INTEGER PRIMARY KEY NOT NULL," \
1551 "CREATE TRIGGER IF NOT EXISTS group_del " \
1552 "BEFORE DELETE ON groups FOR EACH ROW " \
1554 "DELETE FROM serials WHERE groupid=OLD.groupid; " \
1555 "DELETE FROM hashes WHERE groupid=OLD.groupid; " \
1556 "DELETE FROM issuers WHERE groupid=OLD.groupid; " \
1557 "DELETE FROM dates WHERE groupid=OLD.groupid; " \
1560 #define selectGroupIdSQL CFSTR("SELECT DISTINCT groupid " \
1561 "FROM issuers WHERE issuer_hash=?")
1562 #define selectVersionSQL CFSTR("SELECT ival FROM admin " \
1563 "WHERE key='version'")
1564 #define selectDbVersionSQL CFSTR("SELECT ival FROM admin " \
1565 "WHERE key='db_version'")
1566 #define selectDbFormatSQL CFSTR("SELECT ival FROM admin " \
1567 "WHERE key='db_format'")
1568 #define selectDbHashSQL CFSTR("SELECT value FROM admin " \
1569 "WHERE key='db_hash'")
1570 #define selectDbSourceSQL CFSTR("SELECT value FROM admin " \
1571 "WHERE key='db_source'")
1572 #define selectNextUpdateSQL CFSTR("SELECT value FROM admin " \
1573 "WHERE key='check_again'")
1574 #define selectUpdateIntervalSQL CFSTR("SELECT ival FROM admin " \
1575 "WHERE key='interval'")
1576 #define selectGroupRecordSQL CFSTR("SELECT flags,format,data,policies " \
1577 "FROM groups WHERE groupid=?")
1578 #define selectSerialRecordSQL CFSTR("SELECT rowid FROM serials " \
1579 "WHERE groupid=? AND serial=?")
1580 #define selectDateRecordSQL CFSTR("SELECT notbefore,notafter FROM " \
1581 "dates WHERE groupid=?")
1582 #define selectHashRecordSQL CFSTR("SELECT rowid FROM hashes " \
1583 "WHERE groupid=? AND sha256=?")
1584 #define insertAdminRecordSQL CFSTR("INSERT OR REPLACE INTO admin " \
1585 "(key,ival,value) VALUES (?,?,?)")
1586 #define insertIssuerRecordSQL CFSTR("INSERT OR REPLACE INTO issuers " \
1587 "(groupid,issuer_hash) VALUES (?,?)")
1588 #define insertGroupRecordSQL CFSTR("INSERT OR REPLACE INTO groups " \
1589 "(groupid,flags,format,data,policies) VALUES (?,?,?,?,?)")
1590 #define insertSerialRecordSQL CFSTR("INSERT OR REPLACE INTO serials " \
1591 "(groupid,serial) VALUES (?,?)")
1592 #define deleteSerialRecordSQL CFSTR("DELETE FROM serials " \
1593 "WHERE groupid=? AND hex(serial) LIKE ?")
1594 #define insertSha256RecordSQL CFSTR("INSERT OR REPLACE INTO hashes " \
1595 "(groupid,sha256) VALUES (?,?)")
1596 #define deleteSha256RecordSQL CFSTR("DELETE FROM hashes " \
1597 "WHERE groupid=? AND hex(sha256) LIKE ?")
1598 #define insertDateRecordSQL CFSTR("INSERT OR REPLACE INTO dates " \
1599 "(groupid,notbefore,notafter) VALUES (?,?,?)")
1600 #define deleteGroupRecordSQL CFSTR("DELETE FROM groups " \
1602 #define deleteGroupIssuersSQL CFSTR("DELETE FROM issuers " \
1604 #define addPoliciesColumnSQL CFSTR("ALTER TABLE groups " \
1605 "ADD COLUMN policies BLOB")
1606 #define updateGroupPoliciesSQL CFSTR("UPDATE OR IGNORE groups " \
1607 "SET policies=? WHERE groupid=?")
1609 #define updateConstraintsTablesSQL CFSTR("" \
1610 "CREATE TABLE IF NOT EXISTS dates(" \
1611 "groupid INTEGER PRIMARY KEY NOT NULL," \
1616 #define updateGroupDeleteTriggerSQL CFSTR("" \
1617 "DROP TRIGGER IF EXISTS group_del;" \
1618 "CREATE TRIGGER group_del BEFORE DELETE ON groups FOR EACH ROW " \
1620 "DELETE FROM serials WHERE groupid=OLD.groupid; " \
1621 "DELETE FROM hashes WHERE groupid=OLD.groupid; " \
1622 "DELETE FROM issuers WHERE groupid=OLD.groupid; " \
1623 "DELETE FROM dates WHERE groupid=OLD.groupid; " \
1626 #define deleteAllEntriesSQL CFSTR("" \
1627 "DELETE FROM groups; " \
1628 "DELETE FROM admin WHERE key='version'; " \
1629 "DELETE FROM sqlite_sequence")
1632 /* Database management */
1634 static SecDbRef
SecRevocationDbCreate(CFStringRef path
) {
1635 /* only the db owner should open a read-write connection. */
1636 __block
bool readWrite
= isDbOwner();
1639 SecDbRef result
= SecDbCreate(path
, mode
, readWrite
, false, true, true, 1, ^bool (SecDbRef db
, SecDbConnectionRef dbconn
, bool didCreate
, bool *callMeAgainForNextConnection
, CFErrorRef
*error
) {
1640 __block
bool ok
= true;
1642 ok
&= SecDbTransaction(dbconn
, kSecDbExclusiveTransactionType
, error
, ^(bool *commit
) {
1643 /* Create all database tables, indexes, and triggers.
1644 * SecDbOpen will set auto_vacuum and journal_mode for us before we get called back.*/
1645 ok
= ok
&& SecDbExec(dbconn
, createTablesSQL
, error
);
1649 if (!ok
|| (error
&& *error
)) {
1650 CFIndex errCode
= errSecInternalComponent
;
1651 if (error
&& *error
) {
1652 errCode
= CFErrorGetCode(*error
);
1654 secerror("%s failed: %@", didCreate
? "Create" : "Open", error
? *error
: NULL
);
1655 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationCreate
, TAFatalError
, errCode
);
1663 static dispatch_once_t kSecRevocationDbOnce
;
1664 static SecRevocationDbRef kSecRevocationDb
= NULL
;
1666 static SecRevocationDbRef
SecRevocationDbInit(CFStringRef db_name
) {
1667 SecRevocationDbRef rdb
;
1668 dispatch_queue_attr_t attr
;
1670 require(rdb
= (SecRevocationDbRef
)malloc(sizeof(struct __SecRevocationDb
)), errOut
);
1672 rdb
->update_queue
= NULL
;
1673 rdb
->updateInProgress
= false;
1674 rdb
->unsupportedVersion
= false;
1675 rdb
->changed
= false;
1677 require(rdb
->db
= SecRevocationDbCreate(db_name
), errOut
);
1678 attr
= dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL
, QOS_CLASS_BACKGROUND
, 0);
1679 attr
= dispatch_queue_attr_make_with_autorelease_frequency(attr
, DISPATCH_AUTORELEASE_FREQUENCY_WORK_ITEM
);
1680 require(rdb
->update_queue
= dispatch_queue_create(NULL
, attr
), errOut
);
1681 require(rdb
->info_cache_list
= CFArrayCreateMutable(NULL
, 0, &kCFTypeArrayCallBacks
), errOut
);
1682 require(rdb
->info_cache
= CFDictionaryCreateMutable(NULL
, 0, &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
), errOut
);
1683 rdb
->info_cache_lock
= OS_UNFAIR_LOCK_INIT
;
1686 /* register for changes signaled by the db owner instance */
1688 notify_register_dispatch(kSecRevocationDbChanged
, &out_token
, rdb
->update_queue
, ^(int __unused token
) {
1689 secnotice("validupdate", "Got notification of database change");
1690 SecRevocationDbResetCaches();
1696 secdebug("validupdate", "Failed to create db at \"%@\"", db_name
);
1698 if (rdb
->update_queue
) {
1699 dispatch_release(rdb
->update_queue
);
1701 CFReleaseSafe(rdb
->db
);
1707 static CFStringRef
SecRevocationDbCopyPath(void) {
1708 CFURLRef revDbURL
= NULL
;
1709 CFStringRef revInfoRelPath
= NULL
;
1710 if ((revInfoRelPath
= CFStringCreateWithFormat(NULL
, NULL
, CFSTR("%s"), kSecRevocationDbFileName
)) != NULL
) {
1711 revDbURL
= SecCopyURLForFileInRevocationInfoDirectory(revInfoRelPath
);
1713 CFReleaseSafe(revInfoRelPath
);
1715 CFStringRef revDbPath
= NULL
;
1717 revDbPath
= CFURLCopyFileSystemPath(revDbURL
, kCFURLPOSIXPathStyle
);
1718 CFRelease(revDbURL
);
1723 static void SecRevocationDbWith(void(^dbJob
)(SecRevocationDbRef db
)) {
1724 dispatch_once(&kSecRevocationDbOnce
, ^{
1725 CFStringRef dbPath
= SecRevocationDbCopyPath();
1727 kSecRevocationDb
= SecRevocationDbInit(dbPath
);
1729 if (kSecRevocationDb
&& isDbOwner()) {
1730 /* check and update schema immediately after database is opened */
1731 SecRevocationDbUpdateSchema(kSecRevocationDb
);
1735 // Do pre job run work here (cancel idle timers etc.)
1736 if (kSecRevocationDb
->updateInProgress
) {
1737 return; // this would block since SecDb has an exclusive transaction lock
1739 dbJob(kSecRevocationDb
);
1740 // Do post job run work here (gc timer, etc.)
1743 static bool SecRevocationDbPerformWrite(SecRevocationDbRef rdb
, CFErrorRef
*error
,
1744 bool(^writeJob
)(SecRevocationDbConnectionRef dbc
, CFErrorRef
*blockError
)) {
1745 __block
bool ok
= true;
1746 __block CFErrorRef localError
= NULL
;
1748 ok
&= SecDbPerformWrite(rdb
->db
, &localError
, ^(SecDbConnectionRef dbconn
) {
1749 ok
&= SecDbTransaction(dbconn
, kSecDbImmediateTransactionType
, &localError
, ^(bool *commit
) {
1750 SecRevocationDbConnectionRef dbc
= SecRevocationDbConnectionInit(rdb
, dbconn
, &localError
);
1751 ok
= ok
&& writeJob(dbc
, &localError
);
1756 ok
&= CFErrorPropagate(localError
, error
);
1760 static bool SecRevocationDbPerformRead(SecRevocationDbRef rdb
, CFErrorRef
*error
,
1761 bool(^readJob
)(SecRevocationDbConnectionRef dbc
, CFErrorRef
*blockError
)) {
1762 __block CFErrorRef localError
= NULL
;
1763 __block
bool ok
= true;
1765 ok
&= SecDbPerformRead(rdb
->db
, &localError
, ^(SecDbConnectionRef dbconn
) {
1766 SecRevocationDbConnectionRef dbc
= SecRevocationDbConnectionInit(rdb
, dbconn
, &localError
);
1767 ok
= ok
&& readJob(dbc
, &localError
);
1770 ok
&= CFErrorPropagate(localError
, error
);
1774 static SecRevocationDbConnectionRef
SecRevocationDbConnectionInit(SecRevocationDbRef db
, SecDbConnectionRef dbconn
, CFErrorRef
*error
) {
1775 SecRevocationDbConnectionRef dbc
= NULL
;
1776 CFErrorRef localError
= NULL
;
1778 dbc
= (SecRevocationDbConnectionRef
)malloc(sizeof(struct __SecRevocationDbConnection
));
1781 dbc
->dbconn
= dbconn
;
1782 dbc
->precommitVersion
= (CFIndex
)_SecRevocationDbGetVersion(dbc
, &localError
);
1783 dbc
->precommitDbVersion
= (CFIndex
)_SecRevocationDbGetSchemaVersion(db
, dbc
, &localError
);
1784 dbc
->precommitInterval
= 0; /* set only if we are explicitly given a new value */
1785 dbc
->fullUpdate
= false;
1787 (void) CFErrorPropagate(localError
, error
);
1791 static CF_RETURNS_RETAINED CFDataRef
createCacheKey(CFDataRef certHash
, CFDataRef issuerHash
) {
1792 CFMutableDataRef concat
= CFDataCreateMutableCopy(NULL
, 0, certHash
);
1793 CFDataAppend(concat
, issuerHash
);
1794 CFDataRef result
= SecSHA256DigestCreateFromData(NULL
, concat
);
1795 CFReleaseNull(concat
);
1799 static CF_RETURNS_RETAINED SecValidInfoRef
SecRevocationDbCacheRead(SecRevocationDbRef db
,
1800 SecCertificateRef certificate
,
1801 CFDataRef issuerHash
) {
1805 SecValidInfoRef result
= NULL
;
1806 if (!db
|| !db
->info_cache
|| !db
->info_cache_list
) {
1809 CFIndex ix
= kCFNotFound
;
1810 CFDataRef certHash
= SecCertificateCopySHA256Digest(certificate
);
1811 CFDataRef cacheKey
= createCacheKey(certHash
, issuerHash
);
1813 os_unfair_lock_lock(&db
->info_cache_lock
); // grab the cache lock before using the cache
1814 if (0 <= (ix
= CFArrayGetFirstIndexOfValue(db
->info_cache_list
,
1815 CFRangeMake(0, CFArrayGetCount(db
->info_cache_list
)),
1817 result
= (SecValidInfoRef
)CFDictionaryGetValue(db
->info_cache
, cacheKey
);
1818 // Verify this really is the right result
1819 if (CFEqualSafe(result
->certHash
, certHash
) && CFEqualSafe(result
->issuerHash
, issuerHash
)) {
1820 // Cache hit. Move the entry to the bottom of the list.
1821 CFArrayRemoveValueAtIndex(db
->info_cache_list
, ix
);
1822 CFArrayAppendValue(db
->info_cache_list
, cacheKey
);
1823 secdebug("validcache", "cache hit: %@", cacheKey
);
1825 // Just remove this bad entry
1826 CFArrayRemoveValueAtIndex(db
->info_cache_list
, ix
);
1827 CFDictionaryRemoveValue(db
->info_cache
, cacheKey
);
1828 secdebug("validcache", "cache remove bad: %@", cacheKey
);
1829 secnotice("validcache", "found a bad valid info cache entry at %ld", (long)ix
);
1832 CFRetainSafe(result
);
1833 os_unfair_lock_unlock(&db
->info_cache_lock
);
1834 CFReleaseSafe(certHash
);
1835 CFReleaseSafe(cacheKey
);
1839 static void SecRevocationDbCacheWrite(SecRevocationDbRef db
,
1840 SecValidInfoRef validInfo
) {
1841 if (!db
|| !validInfo
|| !db
->info_cache
|| !db
->info_cache_list
) {
1845 CFDataRef cacheKey
= createCacheKey(validInfo
->certHash
, validInfo
->issuerHash
);
1847 os_unfair_lock_lock(&db
->info_cache_lock
); // grab the cache lock before using the cache
1848 // check to make sure another thread didn't add this entry to the cache already
1849 if (0 > CFArrayGetFirstIndexOfValue(db
->info_cache_list
,
1850 CFRangeMake(0, CFArrayGetCount(db
->info_cache_list
)),
1852 CFDictionaryAddValue(db
->info_cache
, cacheKey
, validInfo
);
1853 if (kSecRevocationDbCacheSize
<= CFArrayGetCount(db
->info_cache_list
)) {
1854 // Remove least recently used cache entry.
1855 secdebug("validcache", "cache remove stale: %@", CFArrayGetValueAtIndex(db
->info_cache_list
, 0));
1856 CFDictionaryRemoveValue(db
->info_cache
, CFArrayGetValueAtIndex(db
->info_cache_list
, 0));
1857 CFArrayRemoveValueAtIndex(db
->info_cache_list
, 0);
1859 CFArrayAppendValue(db
->info_cache_list
, cacheKey
);
1860 secdebug("validcache", "cache add: %@", cacheKey
);
1862 os_unfair_lock_unlock(&db
->info_cache_lock
);
1863 CFReleaseNull(cacheKey
);
1866 static void SecRevocationDbCachePurge(SecRevocationDbRef db
) {
1867 if (!db
|| !db
->info_cache
|| !db
->info_cache_list
) {
1871 /* grab the cache lock and clear all entries */
1872 os_unfair_lock_lock(&db
->info_cache_lock
);
1873 CFArrayRemoveAllValues(db
->info_cache_list
);
1874 CFDictionaryRemoveAllValues(db
->info_cache
);
1875 secdebug("validcache", "cache purge");
1876 os_unfair_lock_unlock(&db
->info_cache_lock
);
1879 static int64_t _SecRevocationDbGetUpdateInterval(SecRevocationDbConnectionRef dbc
, CFErrorRef
*error
) {
1880 /* look up interval entry in admin table; returns -1 on error */
1881 __block
int64_t interval
= -1;
1882 __block
bool ok
= true;
1883 __block CFErrorRef localError
= NULL
;
1885 ok
= ok
&& SecDbWithSQL(dbc
->dbconn
, selectUpdateIntervalSQL
, &localError
, ^bool(sqlite3_stmt
*selectInterval
) {
1886 ok
= ok
&& SecDbStep(dbc
->dbconn
, selectInterval
, &localError
, ^void(bool *stop
) {
1887 interval
= sqlite3_column_int64(selectInterval
, 0);
1892 if (!ok
|| localError
) {
1893 secerror("_SecRevocationDbGetUpdateInterval failed: %@", localError
);
1894 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationRead
, TAFatalError
,
1895 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
1897 (void) CFErrorPropagate(localError
, error
);
1901 static bool _SecRevocationDbSetUpdateInterval(SecRevocationDbConnectionRef dbc
, int64_t interval
, CFErrorRef
*error
) {
1902 secdebug("validupdate", "setting interval to %lld", interval
);
1904 __block CFErrorRef localError
= NULL
;
1905 __block
bool ok
= (dbc
!= NULL
);
1906 ok
= ok
&& SecDbWithSQL(dbc
->dbconn
, insertAdminRecordSQL
, &localError
, ^bool(sqlite3_stmt
*insertInterval
) {
1907 const char *intervalKey
= "interval";
1908 ok
= ok
&& SecDbBindText(insertInterval
, 1, intervalKey
, strlen(intervalKey
),
1909 SQLITE_TRANSIENT
, &localError
);
1910 ok
= ok
&& SecDbBindInt64(insertInterval
, 2,
1911 (sqlite3_int64
)interval
, &localError
);
1912 ok
= ok
&& SecDbStep(dbc
->dbconn
, insertInterval
, &localError
, NULL
);
1915 if (!ok
|| localError
) {
1916 secerror("_SecRevocationDbSetUpdateInterval failed: %@", localError
);
1917 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationWrite
, TAFatalError
,
1918 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
1920 (void) CFErrorPropagate(localError
, error
);
1924 static CFArrayRef
_SecRevocationDbCopyHashes(SecRevocationDbConnectionRef dbc
, CFErrorRef
*error
) {
1925 /* return a retained copy of the db_hash array stored in the admin table; or NULL on error */
1926 __block CFMutableArrayRef hashes
= CFArrayCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeArrayCallBacks
);
1927 __block
bool ok
= (dbc
&& hashes
);
1928 __block CFErrorRef localError
= NULL
;
1930 ok
= ok
&& SecDbWithSQL(dbc
->dbconn
, selectDbHashSQL
, &localError
, ^bool(sqlite3_stmt
*selectDbHash
) {
1931 ok
= ok
&& SecDbStep(dbc
->dbconn
, selectDbHash
, &localError
, ^void(bool *stop
) {
1932 uint8_t *p
= (uint8_t *)sqlite3_column_blob(selectDbHash
, 0);
1933 uint64_t len
= sqlite3_column_bytes(selectDbHash
, 0);
1934 CFIndex hashLen
= CC_SHA256_DIGEST_LENGTH
;
1935 while (p
&& len
>= (uint64_t)hashLen
) {
1936 CFDataRef hash
= CFDataCreate(kCFAllocatorDefault
, (const UInt8
*)p
, hashLen
);
1938 CFArrayAppendValue(hashes
, hash
);
1939 CFReleaseNull(hash
);
1948 if (!ok
|| localError
) {
1949 CFReleaseNull(hashes
);
1951 (void) CFErrorPropagate(localError
, error
);
1955 static bool _SecRevocationDbSetHashes(SecRevocationDbConnectionRef dbc
, CFArrayRef hashes
, CFErrorRef
*error
) {
1956 /* flatten and store db_hash array in the admin table */
1957 __block CFErrorRef localError
= NULL
;
1958 __block
bool ok
= (dbc
&& hashes
);
1960 ok
= ok
&& SecDbWithSQL(dbc
->dbconn
, insertAdminRecordSQL
, &localError
, ^bool(sqlite3_stmt
*insertHashes
) {
1961 CFIndex count
= CFArrayGetCount(hashes
);
1962 CFIndex hashLen
= CC_SHA256_DIGEST_LENGTH
, dataLen
= hashLen
* count
;
1963 uint8_t *dataPtr
= (uint8_t *)calloc(dataLen
, 1);
1964 uint8_t *p
= dataPtr
;
1965 for (CFIndex idx
= 0; idx
< count
&& p
; idx
++) {
1966 CFDataRef hash
= CFArrayGetValueAtIndex(hashes
, idx
);
1967 uint8_t *h
= (hash
) ? (uint8_t *)CFDataGetBytePtr(hash
) : NULL
;
1968 if (h
&& CFDataGetLength(hash
) == hashLen
) { memcpy(p
, h
, hashLen
); }
1971 const char *hashKey
= "db_hash";
1972 ok
= ok
&& SecDbBindText(insertHashes
, 1, hashKey
, strlen(hashKey
),
1973 SQLITE_TRANSIENT
, &localError
);
1974 ok
= ok
&& SecDbBindInt64(insertHashes
, 2,
1975 (sqlite3_int64
)0, &localError
);
1976 ok
= ok
&& SecDbBindBlob(insertHashes
, 3,
1978 SQLITE_TRANSIENT
, &localError
);
1979 ok
= ok
&& SecDbStep(dbc
->dbconn
, insertHashes
, &localError
, NULL
);
1983 if (!ok
|| localError
) {
1985 (void) CFErrorPropagate(localError
, error
);
1989 static int64_t _SecRevocationDbGetVersion(SecRevocationDbConnectionRef dbc
, CFErrorRef
*error
) {
1990 /* look up version entry in admin table; returns -1 on error */
1991 __block
int64_t version
= -1;
1992 __block
bool ok
= (dbc
!= NULL
);
1993 __block CFErrorRef localError
= NULL
;
1995 ok
= ok
&& SecDbWithSQL(dbc
->dbconn
, selectVersionSQL
, &localError
, ^bool(sqlite3_stmt
*selectVersion
) {
1996 ok
= ok
&& SecDbStep(dbc
->dbconn
, selectVersion
, &localError
, ^void(bool *stop
) {
1997 version
= sqlite3_column_int64(selectVersion
, 0);
2002 if (!ok
|| localError
) {
2003 secerror("_SecRevocationDbGetVersion failed: %@", localError
);
2004 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationRead
, TAFatalError
,
2005 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
2007 (void) CFErrorPropagate(localError
, error
);
2011 static bool _SecRevocationDbSetVersion(SecRevocationDbConnectionRef dbc
, CFIndex version
, CFErrorRef
*error
) {
2012 secdebug("validupdate", "setting version to %ld", (long)version
);
2014 __block CFErrorRef localError
= NULL
;
2015 __block
bool ok
= (dbc
!= NULL
);
2016 ok
= ok
&& SecDbWithSQL(dbc
->dbconn
, insertAdminRecordSQL
, &localError
, ^bool(sqlite3_stmt
*insertVersion
) {
2017 const char *versionKey
= "version";
2018 ok
= ok
&& SecDbBindText(insertVersion
, 1, versionKey
, strlen(versionKey
),
2019 SQLITE_TRANSIENT
, &localError
);
2020 ok
= ok
&& SecDbBindInt64(insertVersion
, 2,
2021 (sqlite3_int64
)version
, &localError
);
2022 ok
= ok
&& SecDbStep(dbc
->dbconn
, insertVersion
, &localError
, NULL
);
2025 if (!ok
|| localError
) {
2026 secerror("_SecRevocationDbSetVersion failed: %@", localError
);
2027 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationWrite
, TAFatalError
,
2028 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
2030 (void) CFErrorPropagate(localError
, error
);
2034 static int64_t _SecRevocationDbReadSchemaVersionFromDb(SecRevocationDbConnectionRef dbc
, CFErrorRef
*error
) {
2035 /* look up db_version entry in admin table; returns -1 on error */
2036 __block
int64_t db_version
= -1;
2037 __block
bool ok
= (dbc
!= NULL
);
2038 __block CFErrorRef localError
= NULL
;
2040 ok
= ok
&& SecDbWithSQL(dbc
->dbconn
, selectDbVersionSQL
, &localError
, ^bool(sqlite3_stmt
*selectDbVersion
) {
2041 ok
= ok
&& SecDbStep(dbc
->dbconn
, selectDbVersion
, &localError
, ^void(bool *stop
) {
2042 db_version
= sqlite3_column_int64(selectDbVersion
, 0);
2047 if (!ok
|| localError
) {
2048 secerror("_SecRevocationDbReadSchemaVersionFromDb failed: %@", localError
);
2049 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationRead
, TAFatalError
,
2050 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
2052 (void) CFErrorPropagate(localError
, error
);
2056 static _Atomic
int64_t gSchemaVersion
= -1;
2057 static int64_t _SecRevocationDbGetSchemaVersion(SecRevocationDbRef rdb
, SecRevocationDbConnectionRef dbc
, CFErrorRef
*error
) {
2058 static dispatch_once_t onceToken
;
2059 dispatch_once(&onceToken
, ^{
2061 atomic_init(&gSchemaVersion
, _SecRevocationDbReadSchemaVersionFromDb(dbc
, error
));
2063 (void) SecRevocationDbPerformRead(rdb
, error
, ^bool(SecRevocationDbConnectionRef dbc
, CFErrorRef
*blockError
) {
2064 atomic_init(&gSchemaVersion
, _SecRevocationDbReadSchemaVersionFromDb(dbc
, blockError
));
2069 if (atomic_load(&gSchemaVersion
) == -1) {
2070 /* Initial read(s) failed. Try to read the schema version again. */
2072 atomic_store(&gSchemaVersion
, _SecRevocationDbReadSchemaVersionFromDb(dbc
, error
));
2074 (void) SecRevocationDbPerformRead(rdb
, error
, ^bool(SecRevocationDbConnectionRef dbc
, CFErrorRef
*blockError
) {
2075 atomic_store(&gSchemaVersion
, _SecRevocationDbReadSchemaVersionFromDb(dbc
, blockError
));
2080 return atomic_load(&gSchemaVersion
);
2083 static void SecRevocationDbResetCaches(void) {
2084 SecRevocationDbWith(^(SecRevocationDbRef db
) {
2085 db
->unsupportedVersion
= false;
2086 db
->changed
= false;
2087 (void) SecRevocationDbPerformRead(db
, NULL
, ^bool(SecRevocationDbConnectionRef dbc
, CFErrorRef
*blockError
) {
2088 atomic_store(&gSchemaVersion
, _SecRevocationDbReadSchemaVersionFromDb(dbc
, blockError
));
2091 SecRevocationDbCachePurge(db
);
2095 static bool _SecRevocationDbSetSchemaVersion(SecRevocationDbConnectionRef dbc
, CFIndex dbversion
, CFErrorRef
*error
) {
2096 if (dbversion
> 0) {
2097 int64_t db_version
= (dbc
) ? dbc
->precommitDbVersion
: -1;
2098 if (db_version
>= dbversion
) {
2099 return true; /* requested schema is earlier than current schema */
2102 secdebug("validupdate", "setting db_version to %ld", (long)dbversion
);
2104 __block CFErrorRef localError
= NULL
;
2105 __block
bool ok
= (dbc
!= NULL
);
2106 ok
= ok
&& SecDbWithSQL(dbc
->dbconn
, insertAdminRecordSQL
, &localError
, ^bool(sqlite3_stmt
*insertDbVersion
) {
2107 const char *dbVersionKey
= "db_version";
2108 ok
= ok
&& SecDbBindText(insertDbVersion
, 1, dbVersionKey
, strlen(dbVersionKey
),
2109 SQLITE_TRANSIENT
, &localError
);
2110 ok
= ok
&& SecDbBindInt64(insertDbVersion
, 2,
2111 (sqlite3_int64
)dbversion
, &localError
);
2112 ok
= ok
&& SecDbStep(dbc
->dbconn
, insertDbVersion
, &localError
, NULL
);
2115 if (!ok
|| localError
) {
2116 secerror("_SecRevocationDbSetSchemaVersion failed: %@", localError
);
2117 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationWrite
, TAFatalError
,
2118 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
2120 dbc
->db
->changed
= true; /* will notify clients of this change */
2121 dbc
->db
->unsupportedVersion
= false;
2122 dbc
->precommitDbVersion
= dbversion
;
2123 atomic_store(&gSchemaVersion
, (int64_t)dbversion
);
2125 CFReleaseSafe(localError
);
2129 static bool _SecRevocationDbUpdateSchema(SecRevocationDbConnectionRef dbc
, CFErrorRef
*error
) {
2130 __block CFErrorRef localError
= NULL
;
2131 __block
bool ok
= (dbc
!= NULL
);
2132 __block
int64_t db_version
= (dbc
) ? dbc
->precommitDbVersion
: 0;
2133 if (db_version
>= kSecRevocationDbSchemaVersion
) {
2134 return ok
; /* schema version already up to date */
2136 secdebug("validupdate", "updating db schema from v%lld to v%lld",
2137 (long long)db_version
, (long long)kSecRevocationDbSchemaVersion
);
2139 if (ok
&& db_version
< 5) {
2140 /* apply v5 changes (add dates table and replace trigger) */
2141 ok
&= SecDbWithSQL(dbc
->dbconn
, updateConstraintsTablesSQL
, &localError
, ^bool(sqlite3_stmt
*updateTables
) {
2142 ok
= SecDbStep(dbc
->dbconn
, updateTables
, &localError
, NULL
);
2145 ok
&= SecDbWithSQL(dbc
->dbconn
, updateGroupDeleteTriggerSQL
, &localError
, ^bool(sqlite3_stmt
*updateTrigger
) {
2146 ok
= SecDbStep(dbc
->dbconn
, updateTrigger
, &localError
, NULL
);
2149 secdebug("validupdate", "applied schema update to v5 (%s)", (ok
) ? "ok" : "failed!");
2151 if (ok
&& db_version
< 6) {
2152 /* apply v6 changes (the SecDb layer will update autovacuum mode if needed, so we don't execute
2153 any SQL here, but we do want the database to be replaced in case transaction scope problems
2154 with earlier versions caused missing entries.) */
2155 secdebug("validupdate", "applied schema update to v6 (%s)", (ok
) ? "ok" : "failed!");
2156 if (db_version
> 0) {
2157 SecValidUpdateForceReplaceDatabase();
2160 if (ok
&& db_version
< 7) {
2161 /* apply v7 changes (add policies column in groups table) */
2162 ok
&= SecDbWithSQL(dbc
->dbconn
, addPoliciesColumnSQL
, &localError
, ^bool(sqlite3_stmt
*addPoliciesColumn
) {
2163 ok
= SecDbStep(dbc
->dbconn
, addPoliciesColumn
, &localError
, NULL
);
2166 secdebug("validupdate", "applied schema update to v7 (%s)", (ok
) ? "ok" : "failed!");
2170 secerror("_SecRevocationDbUpdateSchema failed: %@", localError
);
2172 ok
= ok
&& _SecRevocationDbSetSchemaVersion(dbc
, kSecRevocationDbSchemaVersion
, &localError
);
2174 (void) CFErrorPropagate(localError
, error
);
2178 bool SecRevocationDbUpdateSchema(SecRevocationDbRef rdb
) {
2179 /* note: this function assumes it is called only by the database owner.
2180 non-owner (read-only) clients will fail if changes to the db are needed. */
2181 if (!rdb
|| !rdb
->db
) {
2184 __block
bool ok
= true;
2185 __block CFErrorRef localError
= NULL
;
2186 ok
&= SecRevocationDbPerformWrite(rdb
, &localError
, ^bool(SecRevocationDbConnectionRef dbc
, CFErrorRef
*blockError
) {
2187 return _SecRevocationDbUpdateSchema(dbc
, blockError
);
2189 CFReleaseSafe(localError
);
2193 static int64_t _SecRevocationDbGetUpdateFormat(SecRevocationDbConnectionRef dbc
, CFErrorRef
*error
) {
2194 /* look up db_format entry in admin table; returns -1 on error */
2195 __block
int64_t db_format
= -1;
2196 __block
bool ok
= (dbc
!= NULL
);
2197 __block CFErrorRef localError
= NULL
;
2199 ok
= ok
&& SecDbWithSQL(dbc
->dbconn
, selectDbFormatSQL
, &localError
, ^bool(sqlite3_stmt
*selectDbFormat
) {
2200 ok
&= SecDbStep(dbc
->dbconn
, selectDbFormat
, &localError
, ^void(bool *stop
) {
2201 db_format
= sqlite3_column_int64(selectDbFormat
, 0);
2206 if (!ok
|| localError
) {
2207 secerror("_SecRevocationDbGetUpdateFormat failed: %@", localError
);
2208 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationRead
, TAFatalError
,
2209 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
2211 (void) CFErrorPropagate(localError
, error
);
2215 static bool _SecRevocationDbSetUpdateFormat(SecRevocationDbConnectionRef dbc
, CFIndex dbformat
, CFErrorRef
*error
) {
2216 secdebug("validupdate", "setting db_format to %ld", (long)dbformat
);
2218 __block CFErrorRef localError
= NULL
;
2219 __block
bool ok
= (dbc
!= NULL
);
2220 ok
= ok
&& SecDbWithSQL(dbc
->dbconn
, insertAdminRecordSQL
, &localError
, ^bool(sqlite3_stmt
*insertDbFormat
) {
2221 const char *dbFormatKey
= "db_format";
2222 ok
= ok
&& SecDbBindText(insertDbFormat
, 1, dbFormatKey
, strlen(dbFormatKey
),
2223 SQLITE_TRANSIENT
, &localError
);
2224 ok
= ok
&& SecDbBindInt64(insertDbFormat
, 2,
2225 (sqlite3_int64
)dbformat
, &localError
);
2226 ok
= ok
&& SecDbStep(dbc
->dbconn
, insertDbFormat
, &localError
, NULL
);
2229 if (!ok
|| localError
) {
2230 secerror("_SecRevocationDbSetUpdateFormat failed: %@", localError
);
2231 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationWrite
, TAFatalError
,
2232 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
2234 dbc
->db
->changed
= true; /* will notify clients of this change */
2235 dbc
->db
->unsupportedVersion
= false;
2237 (void) CFErrorPropagate(localError
, error
);
2241 static CFStringRef
_SecRevocationDbCopyUpdateSource(SecRevocationDbConnectionRef dbc
, CFErrorRef
*error
) {
2242 /* look up db_source entry in admin table; returns NULL on error */
2243 __block CFStringRef updateSource
= NULL
;
2244 __block
bool ok
= (dbc
!= NULL
);
2245 __block CFErrorRef localError
= NULL
;
2247 ok
= ok
&& SecDbWithSQL(dbc
->dbconn
, selectDbSourceSQL
, &localError
, ^bool(sqlite3_stmt
*selectDbSource
) {
2248 ok
&= SecDbStep(dbc
->dbconn
, selectDbSource
, &localError
, ^void(bool *stop
) {
2249 const UInt8
*p
= (const UInt8
*)sqlite3_column_blob(selectDbSource
, 0);
2251 CFIndex length
= (CFIndex
)sqlite3_column_bytes(selectDbSource
, 0);
2253 updateSource
= CFStringCreateWithBytes(kCFAllocatorDefault
, p
, length
, kCFStringEncodingUTF8
, false);
2260 if (!ok
|| localError
) {
2261 secerror("_SecRevocationDbCopyUpdateSource failed: %@", localError
);
2262 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationRead
, TAFatalError
,
2263 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
2265 (void) CFErrorPropagate(localError
, error
);
2266 return updateSource
;
2269 bool _SecRevocationDbSetUpdateSource(SecRevocationDbConnectionRef dbc
, CFStringRef updateSource
, CFErrorRef
*error
) {
2270 if (!updateSource
) {
2271 secerror("_SecRevocationDbSetUpdateSource failed: %d", errSecParam
);
2274 __block
char buffer
[256];
2275 __block
const char *updateSourceCStr
= CFStringGetCStringPtr(updateSource
, kCFStringEncodingUTF8
);
2276 if (!updateSourceCStr
) {
2277 if (CFStringGetCString(updateSource
, buffer
, 256, kCFStringEncodingUTF8
)) {
2278 updateSourceCStr
= buffer
;
2281 if (!updateSourceCStr
) {
2282 secerror("_SecRevocationDbSetUpdateSource failed: unable to get UTF-8 encoding");
2285 secdebug("validupdate", "setting update source to \"%s\"", updateSourceCStr
);
2287 __block CFErrorRef localError
= NULL
;
2288 __block
bool ok
= (dbc
!= NULL
);
2289 ok
= ok
&& SecDbWithSQL(dbc
->dbconn
, insertAdminRecordSQL
, &localError
, ^bool(sqlite3_stmt
*insertRecord
) {
2290 const char *dbSourceKey
= "db_source";
2291 ok
= ok
&& SecDbBindText(insertRecord
, 1, dbSourceKey
, strlen(dbSourceKey
),
2292 SQLITE_TRANSIENT
, &localError
);
2293 ok
= ok
&& SecDbBindInt64(insertRecord
, 2,
2294 (sqlite3_int64
)0, &localError
);
2295 ok
= ok
&& SecDbBindBlob(insertRecord
, 3,
2296 updateSourceCStr
, strlen(updateSourceCStr
),
2297 SQLITE_TRANSIENT
, &localError
);
2298 ok
= ok
&& SecDbStep(dbc
->dbconn
, insertRecord
, &localError
, NULL
);
2301 if (!ok
|| localError
) {
2302 secerror("_SecRevocationDbSetUpdateSource failed: %@", localError
);
2303 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationWrite
, TAFatalError
,
2304 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
2306 (void) CFErrorPropagate(localError
, error
);
2307 CFReleaseSafe(localError
);
2311 bool SecRevocationDbSetUpdateSource(SecRevocationDbRef rdb
, CFStringRef updateSource
) {
2312 /* note: this function assumes it is called only by the database owner.
2313 non-owner (read-only) clients will fail if changes to the db are needed. */
2314 if (!rdb
|| !rdb
->db
) {
2317 CFErrorRef localError
= NULL
;
2319 ok
&= SecRevocationDbPerformWrite(rdb
, &localError
, ^bool(SecRevocationDbConnectionRef dbc
, CFErrorRef
*error
) {
2320 return _SecRevocationDbSetUpdateSource(dbc
, updateSource
, error
);
2322 CFReleaseSafe(localError
);
2326 static CFAbsoluteTime
_SecRevocationDbGetNextUpdateTime(SecRevocationDbConnectionRef dbc
, CFErrorRef
*error
) {
2327 /* look up check_again entry in admin table; returns 0 on error */
2328 __block CFAbsoluteTime nextUpdate
= 0;
2329 __block
bool ok
= (dbc
!= NULL
);
2330 __block CFErrorRef localError
= NULL
;
2332 ok
= ok
&& SecDbWithSQL(dbc
->dbconn
, selectNextUpdateSQL
, &localError
, ^bool(sqlite3_stmt
*selectNextUpdate
) {
2333 ok
&= SecDbStep(dbc
->dbconn
, selectNextUpdate
, &localError
, ^void(bool *stop
) {
2334 CFAbsoluteTime
*p
= (CFAbsoluteTime
*)sqlite3_column_blob(selectNextUpdate
, 0);
2336 if (sizeof(CFAbsoluteTime
) == sqlite3_column_bytes(selectNextUpdate
, 0)) {
2344 if (!ok
|| localError
) {
2345 secerror("_SecRevocationDbGetNextUpdateTime failed: %@", localError
);
2346 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationRead
, TAFatalError
,
2347 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
2349 (void) CFErrorPropagate(localError
, error
);
2353 static bool _SecRevocationDbSetNextUpdateTime(SecRevocationDbConnectionRef dbc
, CFAbsoluteTime nextUpdate
, CFErrorRef
*error
){
2354 secdebug("validupdate", "setting next update to %f", (double)nextUpdate
);
2356 __block CFErrorRef localError
= NULL
;
2357 __block
bool ok
= (dbc
!= NULL
);
2358 ok
= ok
&& SecDbWithSQL(dbc
->dbconn
, insertAdminRecordSQL
, &localError
, ^bool(sqlite3_stmt
*insertRecord
) {
2359 const char *nextUpdateKey
= "check_again";
2360 ok
= ok
&& SecDbBindText(insertRecord
, 1, nextUpdateKey
, strlen(nextUpdateKey
),
2361 SQLITE_TRANSIENT
, &localError
);
2362 ok
= ok
&& SecDbBindInt64(insertRecord
, 2,
2363 (sqlite3_int64
)0, &localError
);
2364 ok
= ok
&& SecDbBindBlob(insertRecord
, 3,
2365 &nextUpdate
, sizeof(CFAbsoluteTime
),
2366 SQLITE_TRANSIENT
, &localError
);
2367 ok
= ok
&& SecDbStep(dbc
->dbconn
, insertRecord
, &localError
, NULL
);
2370 if (!ok
|| localError
) {
2371 secerror("_SecRevocationDbSetNextUpdate failed: %@", localError
);
2372 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationWrite
, TAFatalError
,
2373 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
2375 (void) CFErrorPropagate(localError
, error
);
2379 bool _SecRevocationDbRemoveAllEntries(SecRevocationDbConnectionRef dbc
, CFErrorRef
*error
) {
2380 /* clear out the contents of the database and start fresh */
2381 bool ok
= (dbc
!= NULL
);
2382 CFErrorRef localError
= NULL
;
2384 /* _SecRevocationDbUpdateSchema was called when db was opened, so no need to do it again. */
2386 /* delete all entries */
2387 ok
= ok
&& SecDbExec(dbc
->dbconn
, deleteAllEntriesSQL
, &localError
);
2388 secnotice("validupdate", "resetting database, result: %d (expected 1)", (ok
) ? 1 : 0);
2390 /* one more thing: update the schema version and format to current */
2391 ok
= ok
&& _SecRevocationDbSetSchemaVersion(dbc
, kSecRevocationDbSchemaVersion
, &localError
);
2392 ok
= ok
&& _SecRevocationDbSetUpdateFormat(dbc
, kSecRevocationDbUpdateFormat
, &localError
);
2394 if (!ok
|| localError
) {
2395 secerror("_SecRevocationDbRemoveAllEntries failed: %@", localError
);
2396 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationWrite
, TAFatalError
,
2397 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
2399 (void) CFErrorPropagate(localError
, error
);
2403 static bool _SecRevocationDbUpdateIssuers(SecRevocationDbConnectionRef dbc
, int64_t groupId
, CFArrayRef issuers
, CFErrorRef
*error
) {
2404 /* insert or replace issuer records in issuers table */
2405 if (!issuers
|| groupId
< 0) {
2406 return false; /* must have something to insert, and a group to associate with it */
2408 __block
bool ok
= (dbc
!= NULL
);
2409 __block CFErrorRef localError
= NULL
;
2410 if (isArray(issuers
)) {
2411 CFIndex issuerIX
, issuerCount
= CFArrayGetCount(issuers
);
2412 for (issuerIX
=0; issuerIX
<issuerCount
&& ok
; issuerIX
++) {
2413 CFDataRef hash
= (CFDataRef
)CFArrayGetValueAtIndex(issuers
, issuerIX
);
2414 if (!hash
) { continue; }
2415 ok
= ok
&& SecDbWithSQL(dbc
->dbconn
, insertIssuerRecordSQL
, &localError
, ^bool(sqlite3_stmt
*insertIssuer
) {
2416 ok
= ok
&& SecDbBindInt64(insertIssuer
, 1,
2417 groupId
, &localError
);
2418 ok
= ok
&& SecDbBindBlob(insertIssuer
, 2,
2419 CFDataGetBytePtr(hash
),
2420 CFDataGetLength(hash
),
2421 SQLITE_TRANSIENT
, &localError
);
2422 /* Execute the insert statement for this issuer record. */
2423 ok
= ok
&& SecDbStep(dbc
->dbconn
, insertIssuer
, &localError
, NULL
);
2428 if (!ok
|| localError
) {
2429 secerror("_SecRevocationDbUpdateIssuers failed: %@", localError
);
2430 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationWrite
, TAFatalError
,
2431 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
2433 (void) CFErrorPropagate(localError
, error
);
2437 static SecValidInfoFormat
_SecRevocationDbGetGroupFormatForData(SecRevocationDbConnectionRef dbc
, int64_t groupId
, CFDataRef data
) {
2438 /* determine existing format if groupId is supplied,
2439 otherwise return the expected format for the given data. */
2440 SecValidInfoFormat format
= kSecValidInfoFormatUnknown
;
2442 format
= _SecRevocationDbGetGroupFormat(dbc
, groupId
, NULL
, NULL
, NULL
, NULL
);
2444 if (format
== kSecValidInfoFormatUnknown
&& data
!= NULL
) {
2445 /* group doesn't exist, so determine format based on length of specified data.
2446 len <= 20 is a serial number (actually, <=37, but != 32.)
2447 len==32 is a sha256 hash. otherwise: nto1. */
2448 CFIndex length
= CFDataGetLength(data
);
2450 format
= kSecValidInfoFormatSHA256
;
2451 } else if (length
<= 37) {
2452 format
= kSecValidInfoFormatSerial
;
2453 } else if (length
> 0) {
2454 format
= kSecValidInfoFormatNto1
;
2460 static bool _SecRevocationDbUpdateIssuerData(SecRevocationDbConnectionRef dbc
, int64_t groupId
, CFDictionaryRef dict
, CFErrorRef
*error
) {
2461 /* update/delete records in serials or hashes table. */
2462 if (!dict
|| groupId
< 0) {
2463 return false; /* must have something to insert, and a group to associate with it */
2465 __block
bool ok
= (dbc
!= NULL
);
2466 __block CFErrorRef localError
= NULL
;
2467 /* process deletions */
2468 CFArrayRef deleteArray
= (CFArrayRef
)CFDictionaryGetValue(dict
, CFSTR("delete"));
2469 if (isArray(deleteArray
)) {
2470 SecValidInfoFormat format
= kSecValidInfoFormatUnknown
;
2471 CFIndex processed
=0, identifierIX
, identifierCount
= CFArrayGetCount(deleteArray
);
2472 for (identifierIX
=0; identifierIX
<identifierCount
; identifierIX
++) {
2473 CFDataRef identifierData
= (CFDataRef
)CFArrayGetValueAtIndex(deleteArray
, identifierIX
);
2474 if (!identifierData
) { continue; }
2475 if (format
== kSecValidInfoFormatUnknown
) {
2476 format
= _SecRevocationDbGetGroupFormatForData(dbc
, groupId
, identifierData
);
2478 CFStringRef sql
= NULL
;
2479 if (format
== kSecValidInfoFormatSerial
) {
2480 sql
= deleteSerialRecordSQL
;
2481 } else if (format
== kSecValidInfoFormatSHA256
) {
2482 sql
= deleteSha256RecordSQL
;
2484 if (!sql
) { continue; }
2486 ok
= ok
&& SecDbWithSQL(dbc
->dbconn
, sql
, &localError
, ^bool(sqlite3_stmt
*deleteIdentifier
) {
2487 /* (groupid,serial|sha256) */
2488 CFDataRef hexData
= cfToHexData(identifierData
, true);
2489 if (!hexData
) { return false; }
2490 ok
= ok
&& SecDbBindInt64(deleteIdentifier
, 1,
2491 groupId
, &localError
);
2492 ok
= ok
&& SecDbBindBlob(deleteIdentifier
, 2,
2493 CFDataGetBytePtr(hexData
),
2494 CFDataGetLength(hexData
),
2495 SQLITE_TRANSIENT
, &localError
);
2496 /* Execute the delete statement for the identifier record. */
2497 ok
= ok
&& SecDbStep(dbc
->dbconn
, deleteIdentifier
, &localError
, NULL
);
2498 CFReleaseSafe(hexData
);
2501 if (ok
) { ++processed
; }
2504 secdebug("validupdate", "Processed %ld of %ld deletions for group %lld, result=%s",
2505 processed
, identifierCount
, groupId
, (ok
) ? "true" : "false");
2508 /* process additions */
2509 CFArrayRef addArray
= (CFArrayRef
)CFDictionaryGetValue(dict
, CFSTR("add"));
2510 if (isArray(addArray
)) {
2511 SecValidInfoFormat format
= kSecValidInfoFormatUnknown
;
2512 CFIndex processed
=0, identifierIX
, identifierCount
= CFArrayGetCount(addArray
);
2513 for (identifierIX
=0; identifierIX
<identifierCount
; identifierIX
++) {
2514 CFDataRef identifierData
= (CFDataRef
)CFArrayGetValueAtIndex(addArray
, identifierIX
);
2515 if (!identifierData
) { continue; }
2516 if (format
== kSecValidInfoFormatUnknown
) {
2517 format
= _SecRevocationDbGetGroupFormatForData(dbc
, groupId
, identifierData
);
2519 CFStringRef sql
= NULL
;
2520 if (format
== kSecValidInfoFormatSerial
) {
2521 sql
= insertSerialRecordSQL
;
2522 } else if (format
== kSecValidInfoFormatSHA256
) {
2523 sql
= insertSha256RecordSQL
;
2525 if (!sql
) { continue; }
2527 ok
= ok
&& SecDbWithSQL(dbc
->dbconn
, sql
, &localError
, ^bool(sqlite3_stmt
*insertIdentifier
) {
2528 /* rowid,(groupid,serial|sha256) */
2529 /* rowid is autoincremented and we never set it directly */
2530 ok
= ok
&& SecDbBindInt64(insertIdentifier
, 1,
2531 groupId
, &localError
);
2532 ok
= ok
&& SecDbBindBlob(insertIdentifier
, 2,
2533 CFDataGetBytePtr(identifierData
),
2534 CFDataGetLength(identifierData
),
2535 SQLITE_TRANSIENT
, &localError
);
2536 /* Execute the insert statement for the identifier record. */
2537 ok
= ok
&& SecDbStep(dbc
->dbconn
, insertIdentifier
, &localError
, NULL
);
2540 if (ok
) { ++processed
; }
2543 secdebug("validupdate", "Processed %ld of %ld additions for group %lld, result=%s",
2544 processed
, identifierCount
, groupId
, (ok
) ? "true" : "false");
2547 if (!ok
|| localError
) {
2548 secerror("_SecRevocationDbUpdatePerIssuerData failed: %@", localError
);
2549 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationWrite
, TAFatalError
,
2550 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
2552 (void) CFErrorPropagate(localError
, error
);
2556 static bool _SecRevocationDbCopyDateConstraints(SecRevocationDbConnectionRef dbc
,
2557 int64_t groupId
, CFDateRef
*notBeforeDate
, CFDateRef
*notAfterDate
, CFErrorRef
*error
) {
2558 /* return true if one or both date constraints exist for a given groupId.
2559 the actual constraints are optionally returned in output CFDateRef parameters.
2560 caller is responsible for releasing date and error parameters, if provided.
2562 __block
bool ok
= (dbc
!= NULL
);
2563 __block CFDateRef localNotBefore
= NULL
;
2564 __block CFDateRef localNotAfter
= NULL
;
2565 __block CFErrorRef localError
= NULL
;
2567 ok
= ok
&& SecDbWithSQL(dbc
->dbconn
, selectDateRecordSQL
, &localError
, ^bool(sqlite3_stmt
*selectDates
) {
2568 /* (groupid,notbefore,notafter) */
2569 ok
&= SecDbBindInt64(selectDates
, 1, groupId
, &localError
);
2570 ok
= ok
&& SecDbStep(dbc
->dbconn
, selectDates
, &localError
, ^(bool *stop
) {
2571 /* if column has no value, its type will be SQLITE_NULL */
2572 if (SQLITE_NULL
!= sqlite3_column_type(selectDates
, 0)) {
2573 CFAbsoluteTime nb
= (CFAbsoluteTime
)sqlite3_column_double(selectDates
, 0);
2574 localNotBefore
= CFDateCreate(NULL
, nb
);
2576 if (SQLITE_NULL
!= sqlite3_column_type(selectDates
, 1)) {
2577 CFAbsoluteTime na
= (CFAbsoluteTime
)sqlite3_column_double(selectDates
, 1);
2578 localNotAfter
= CFDateCreate(NULL
, na
);
2583 /* must have at least one date constraint to return true.
2584 since date constraints are optional, not finding any should not log an error. */
2585 ok
= ok
&& !localError
&& (localNotBefore
!= NULL
|| localNotAfter
!= NULL
);
2587 secerror("_SecRevocationDbCopyDateConstraints failed: %@", localError
);
2588 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationRead
, TAFatalError
,
2589 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
2592 CFReleaseNull(localNotBefore
);
2593 CFReleaseNull(localNotAfter
);
2595 if (notBeforeDate
) {
2596 *notBeforeDate
= localNotBefore
;
2598 CFReleaseSafe(localNotBefore
);
2601 *notAfterDate
= localNotAfter
;
2603 CFReleaseSafe(localNotAfter
);
2606 (void) CFErrorPropagate(localError
, error
);
2610 static bool _SecRevocationDbUpdateDateConstraints(SecRevocationDbConnectionRef dbc
, int64_t groupId
, CFDictionaryRef dict
, CFErrorRef
*error
) {
2611 /* Called only from _SecRevocationDbUpdateIssuerConstraints.
2612 Function assumes that the caller has checked the input arguments.
2614 __block
bool ok
= true;
2615 __block CFErrorRef localError
= NULL
;
2616 __block CFAbsoluteTime notBefore
= -3155760000.0; /* default: 1901-01-01 00:00:00-0000 */
2617 __block CFAbsoluteTime notAfter
= 31556908800.0; /* default: 3001-01-01 00:00:00-0000 */
2619 CFDateRef notBeforeDate
= (CFDateRef
)CFDictionaryGetValue(dict
, CFSTR("not-before"));
2620 CFDateRef notAfterDate
= (CFDateRef
)CFDictionaryGetValue(dict
, CFSTR("not-after"));
2622 if (isDate(notBeforeDate
)) {
2623 notBefore
= CFDateGetAbsoluteTime(notBeforeDate
);
2625 notBeforeDate
= NULL
;
2627 if (isDate(notAfterDate
)) {
2628 notAfter
= CFDateGetAbsoluteTime(notAfterDate
);
2630 notAfterDate
= NULL
;
2632 if (!(notBeforeDate
|| notAfterDate
)) {
2633 return ok
; /* no dates supplied, so we have nothing to update for this issuer */
2636 if (!(notBeforeDate
&& notAfterDate
)) {
2637 /* only one date was supplied, so check for existing date constraints */
2638 CFDateRef curNotBeforeDate
= NULL
;
2639 CFDateRef curNotAfterDate
= NULL
;
2640 if (_SecRevocationDbCopyDateConstraints(dbc
, groupId
, &curNotBeforeDate
,
2641 &curNotAfterDate
, &localError
)) {
2642 if (!notBeforeDate
) {
2643 notBeforeDate
= curNotBeforeDate
;
2644 notBefore
= CFDateGetAbsoluteTime(notBeforeDate
);
2646 CFReleaseSafe(curNotBeforeDate
);
2648 if (!notAfterDate
) {
2649 notAfterDate
= curNotAfterDate
;
2650 notAfter
= CFDateGetAbsoluteTime(notAfterDate
);
2652 CFReleaseSafe(curNotAfterDate
);
2656 ok
= ok
&& SecDbWithSQL(dbc
->dbconn
, insertDateRecordSQL
, &localError
, ^bool(sqlite3_stmt
*insertDate
) {
2657 /* (groupid,notbefore,notafter) */
2658 ok
= ok
&& SecDbBindInt64(insertDate
, 1, groupId
, &localError
);
2659 ok
= ok
&& SecDbBindDouble(insertDate
, 2, notBefore
, &localError
);
2660 ok
= ok
&& SecDbBindDouble(insertDate
, 3, notAfter
, &localError
);
2661 ok
= ok
&& SecDbStep(dbc
->dbconn
, insertDate
, &localError
, NULL
);
2665 if (!ok
|| localError
) {
2666 secinfo("validupdate", "_SecRevocationDbUpdateDateConstraints failed (ok=%s, localError=%@)",
2667 (ok
) ? "1" : "0", localError
);
2668 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationWrite
, TAFatalError
,
2669 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
2671 (void) CFErrorPropagate(localError
, error
);
2675 static bool _SecRevocationDbUpdatePolicyConstraints(SecRevocationDbConnectionRef dbc
, int64_t groupId
, CFDictionaryRef dict
, CFErrorRef
*error
) {
2676 /* Called only from _SecRevocationDbUpdateIssuerConstraints.
2677 Function assumes that the caller has checked the input arguments.
2679 __block
bool ok
= true;
2680 __block CFErrorRef localError
= NULL
;
2681 CFArrayRef policies
= (CFArrayRef
)CFDictionaryGetValue(dict
, CFSTR("policies"));
2682 if (!isArray(policies
)) {
2683 return ok
; /* no policies supplied, so nothing to update for this issuer */
2686 __block CFDataRef data
= createPoliciesData(policies
);
2687 ok
= data
&& SecDbWithSQL(dbc
->dbconn
, updateGroupPoliciesSQL
, &localError
, ^bool(sqlite3_stmt
*updatePolicies
) {
2688 /* (policies,groupid) */
2689 ok
= ok
&& SecDbBindBlob(updatePolicies
, 1,
2690 CFDataGetBytePtr(data
),
2691 CFDataGetLength(data
),
2692 SQLITE_TRANSIENT
, &localError
);
2693 ok
= ok
&& SecDbBindInt64(updatePolicies
, 2, groupId
, &localError
);
2694 ok
= ok
&& SecDbStep(dbc
->dbconn
, updatePolicies
, &localError
, NULL
);
2697 CFReleaseSafe(data
);
2699 if (!ok
|| localError
) {
2700 secinfo("validupdate", "_SecRevocationDbUpdatePolicyConstraints failed (ok=%s, localError=%@)",
2701 (ok
) ? "1" : "0", localError
);
2702 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationWrite
, TAFatalError
,
2703 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
2705 (void) CFErrorPropagate(localError
, error
);
2709 static bool _SecRevocationDbUpdateNameConstraints(SecRevocationDbConnectionRef dbc
, int64_t groupId
, CFDictionaryRef dict
, CFErrorRef
*error
) {
2710 /* Called only from _SecRevocationDbUpdateIssuerConstraints.
2711 Function assumes that the caller has checked the input arguments.
2714 /* %%% (TBI:9254570) update name constraint entries here */
2718 static bool _SecRevocationDbUpdateIssuerConstraints(SecRevocationDbConnectionRef dbc
, int64_t groupId
, CFDictionaryRef dict
, CFErrorRef
*error
) {
2719 /* check input arguments */
2720 if (!dbc
|| !dict
|| groupId
< 0) {
2724 ok
= ok
&& _SecRevocationDbUpdateDateConstraints(dbc
, groupId
, dict
, error
);
2725 ok
= ok
&& _SecRevocationDbUpdateNameConstraints(dbc
, groupId
, dict
, error
);
2726 ok
= ok
&& _SecRevocationDbUpdatePolicyConstraints(dbc
, groupId
, dict
, error
);
2730 static SecValidInfoFormat
_SecRevocationDbGetGroupFormat(SecRevocationDbConnectionRef dbc
,
2731 int64_t groupId
, SecValidInfoFlags
*flags
, CFDataRef
*data
, CFDataRef
*policies
, CFErrorRef
*error
) {
2732 /* return group record fields for a given groupId.
2733 on success, returns a non-zero format type, and other field values in optional output parameters.
2734 caller is responsible for releasing data, policies, and error parameters, if provided.
2736 __block
bool ok
= (dbc
!= NULL
);
2737 __block SecValidInfoFormat format
= 0;
2738 __block CFErrorRef localError
= NULL
;
2740 /* Select the group record to determine flags and format. */
2741 ok
= ok
&& SecDbWithSQL(dbc
->dbconn
, selectGroupRecordSQL
, &localError
, ^bool(sqlite3_stmt
*selectGroup
) {
2742 ok
= ok
&& SecDbBindInt64(selectGroup
, 1, groupId
, &localError
);
2743 ok
= ok
&& SecDbStep(dbc
->dbconn
, selectGroup
, &localError
, ^(bool *stop
) {
2745 *flags
= (SecValidInfoFlags
)sqlite3_column_int(selectGroup
, 0);
2747 format
= (SecValidInfoFormat
)sqlite3_column_int(selectGroup
, 1);
2749 //%%% stream the data from the db into a streamed decompression <rdar://32142637>
2750 uint8_t *p
= (uint8_t *)sqlite3_column_blob(selectGroup
, 2);
2751 if (p
!= NULL
&& format
== kSecValidInfoFormatNto1
) {
2752 CFIndex length
= (CFIndex
)sqlite3_column_bytes(selectGroup
, 2);
2753 *data
= CFDataCreate(kCFAllocatorDefault
, p
, length
);
2757 uint8_t *p
= (uint8_t *)sqlite3_column_blob(selectGroup
, 3);
2759 CFIndex length
= (CFIndex
)sqlite3_column_bytes(selectGroup
, 3);
2760 *policies
= CFDataCreate(kCFAllocatorDefault
, p
, length
);
2766 if (!ok
|| localError
) {
2767 secdebug("validupdate", "GetGroupFormat for groupId %lu failed", (unsigned long)groupId
);
2768 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationRead
, TAFatalError
,
2769 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
2770 format
= kSecValidInfoFormatUnknown
;
2772 (void) CFErrorPropagate(localError
, error
);
2773 if (!(format
> kSecValidInfoFormatUnknown
)) {
2774 secdebug("validupdate", "GetGroupFormat: got format %d for groupId %lld", format
, (long long)groupId
);
2779 static bool _SecRevocationDbUpdateFlags(CFDictionaryRef dict
, CFStringRef key
, SecValidInfoFlags mask
, SecValidInfoFlags
*flags
) {
2780 /* If a boolean value exists in the given dictionary for the given key,
2781 or an explicit "1" or "0" is specified as the key string,
2782 set or clear the corresponding bit(s) defined by the mask argument.
2783 Function returns true if the flags value was changed, false otherwise.
2785 if (!isDictionary(dict
) || !isString(key
) || !flags
) {
2788 bool hasValue
= false, newValue
= false, result
= false;
2789 CFTypeRef value
= (CFBooleanRef
)CFDictionaryGetValue(dict
, key
);
2790 if (isBoolean(value
)) {
2791 newValue
= CFBooleanGetValue((CFBooleanRef
)value
);
2793 } else if (BOOL_STRING_KEY_LENGTH
== CFStringGetLength(key
)) {
2794 if (CFStringCompare(key
, kBoolTrueKey
, 0) == kCFCompareEqualTo
) {
2795 hasValue
= newValue
= true;
2796 } else if (CFStringCompare(key
, kBoolFalseKey
, 0) == kCFCompareEqualTo
) {
2801 SecValidInfoFlags oldFlags
= *flags
;
2807 result
= (*flags
!= oldFlags
);
2812 static bool _SecRevocationDbUpdateFilter(CFDictionaryRef dict
, CFDataRef oldData
, CFDataRef
* __nonnull CF_RETURNS_RETAINED xmlData
) {
2813 /* If xor and/or params values exist in the given dictionary, create a new
2814 property list containing the updated values, and return as a flattened
2815 data blob in the xmlData output parameter (note: caller must release.)
2816 Function returns true if there is new xmlData to save, false otherwise.
2818 bool result
= false;
2819 bool xorProvided
= false;
2820 bool paramsProvided
= false;
2821 bool missingData
= false;
2823 if (!dict
|| !xmlData
) {
2824 return result
; /* no-op if no dictionary is provided, or no way to update the data */
2827 CFDataRef xorCurrent
= NULL
;
2828 CFDataRef xorUpdate
= (CFDataRef
)CFDictionaryGetValue(dict
, CFSTR("xor"));
2829 if (isData(xorUpdate
)) {
2832 CFArrayRef paramsCurrent
= NULL
;
2833 CFArrayRef paramsUpdate
= (CFArrayRef
)CFDictionaryGetValue(dict
, CFSTR("params"));
2834 if (isArray(paramsUpdate
)) {
2835 paramsProvided
= true;
2837 if (!(xorProvided
|| paramsProvided
)) {
2838 return result
; /* nothing to update, so we can bail out here. */
2841 CFPropertyListRef nto1Current
= NULL
;
2842 CFMutableDictionaryRef nto1Update
= CFDictionaryCreateMutable(kCFAllocatorDefault
, 0,
2843 &kCFTypeDictionaryKeyCallBacks
,
2844 &kCFTypeDictionaryValueCallBacks
);
2849 /* turn old data into property list */
2850 CFDataRef data
= (CFDataRef
)CFRetainSafe(oldData
);
2851 CFDataRef inflatedData
= copyInflatedData(data
);
2853 CFReleaseSafe(data
);
2854 data
= inflatedData
;
2857 nto1Current
= CFPropertyListCreateWithData(kCFAllocatorDefault
, data
, 0, NULL
, NULL
);
2858 CFReleaseSafe(data
);
2861 xorCurrent
= (CFDataRef
)CFDictionaryGetValue((CFDictionaryRef
)nto1Current
, CFSTR("xor"));
2862 paramsCurrent
= (CFArrayRef
)CFDictionaryGetValue((CFDictionaryRef
)nto1Current
, CFSTR("params"));
2865 /* set current or updated xor data in new property list */
2867 CFDataRef xorNew
= NULL
;
2869 CFIndex xorUpdateLen
= CFDataGetLength(xorUpdate
);
2870 CFMutableDataRef
xor = CFDataCreateMutableCopy(NULL
, 0, xorCurrent
);
2871 if (xor && xorUpdateLen
> 0) {
2872 /* truncate or zero-extend data to match update size */
2873 CFDataSetLength(xor, xorUpdateLen
);
2874 /* exclusive-or update bytes over the existing data */
2875 UInt8
*xorP
= (UInt8
*)CFDataGetMutableBytePtr(xor);
2876 UInt8
*updP
= (UInt8
*)CFDataGetBytePtr(xorUpdate
);
2878 for (int idx
= 0; idx
< xorUpdateLen
; idx
++) {
2879 xorP
[idx
] = xorP
[idx
] ^ updP
[idx
];
2883 xorNew
= (CFDataRef
)xor;
2885 xorNew
= (CFDataRef
)CFRetainSafe(xorUpdate
);
2888 CFDictionaryAddValue(nto1Update
, CFSTR("xor"), xorNew
);
2889 CFReleaseSafe(xorNew
);
2891 secdebug("validupdate", "Failed to get updated filter data");
2894 } else if (xorCurrent
) {
2895 /* not provided, so use existing xor value */
2896 CFDictionaryAddValue(nto1Update
, CFSTR("xor"), xorCurrent
);
2898 secdebug("validupdate", "Failed to get current filter data");
2902 /* set current or updated params in new property list */
2903 if (paramsProvided
) {
2904 CFDictionaryAddValue(nto1Update
, CFSTR("params"), paramsUpdate
);
2905 } else if (paramsCurrent
) {
2906 /* not provided, so use existing params value */
2907 CFDictionaryAddValue(nto1Update
, CFSTR("params"), paramsCurrent
);
2909 /* missing params: neither provided nor existing */
2910 secdebug("validupdate", "Failed to get current filter params");
2914 CFReleaseSafe(nto1Current
);
2916 *xmlData
= CFPropertyListCreateData(kCFAllocatorDefault
, nto1Update
,
2917 kCFPropertyListXMLFormat_v1_0
,
2919 result
= (*xmlData
!= NULL
);
2921 CFReleaseSafe(nto1Update
);
2923 /* compress the xmlData blob, if possible */
2925 CFDataRef deflatedData
= copyDeflatedData(*xmlData
);
2927 if (CFDataGetLength(deflatedData
) < CFDataGetLength(*xmlData
)) {
2928 CFRelease(*xmlData
);
2929 *xmlData
= deflatedData
;
2931 CFRelease(deflatedData
);
2939 static int64_t _SecRevocationDbUpdateGroup(SecRevocationDbConnectionRef dbc
, int64_t groupId
, CFDictionaryRef dict
, CFErrorRef
*error
) {
2940 /* insert group record for a given groupId.
2941 if the specified groupId is < 0, a new group entry is created.
2942 returns the groupId on success, or -1 on failure.
2945 return groupId
; /* no-op if no dictionary is provided */
2948 __block
int64_t result
= -1;
2949 __block
bool ok
= (dbc
!= NULL
);
2950 __block
bool isFormatChange
= false;
2951 __block CFErrorRef localError
= NULL
;
2953 __block SecValidInfoFlags flags
= 0;
2954 __block SecValidInfoFormat format
= kSecValidInfoFormatUnknown
;
2955 __block SecValidInfoFormat formatUpdate
= kSecValidInfoFormatUnknown
;
2956 __block CFDataRef data
= NULL
;
2957 __block CFDataRef policies
= NULL
;
2960 /* fetch the flags and data for an existing group record, in case some are being changed. */
2962 format
= _SecRevocationDbGetGroupFormat(dbc
, groupId
, &flags
, &data
, &policies
, NULL
);
2964 if (format
== kSecValidInfoFormatUnknown
) {
2965 secdebug("validupdate", "existing group %lld has unknown format %d, flags=0x%lx",
2966 (long long)groupId
, format
, flags
);
2967 //%%% clean up by deleting all issuers with this groupId, then the group record,
2968 // or just force a full update? note: we can get here if we fail to bind the
2969 // format value in the prepared SQL statement below.
2973 CFTypeRef value
= (CFStringRef
)CFDictionaryGetValue(dict
, CFSTR("format"));
2974 if (isString(value
)) {
2975 if (CFStringCompare((CFStringRef
)value
, CFSTR("serial"), 0) == kCFCompareEqualTo
) {
2976 formatUpdate
= kSecValidInfoFormatSerial
;
2977 } else if (CFStringCompare((CFStringRef
)value
, CFSTR("sha256"), 0) == kCFCompareEqualTo
) {
2978 formatUpdate
= kSecValidInfoFormatSHA256
;
2979 } else if (CFStringCompare((CFStringRef
)value
, CFSTR("nto1"), 0) == kCFCompareEqualTo
) {
2980 formatUpdate
= kSecValidInfoFormatNto1
;
2983 /* if format value is explicitly supplied, then this is effectively a new group entry. */
2984 isFormatChange
= (formatUpdate
> kSecValidInfoFormatUnknown
&&
2985 formatUpdate
!= format
&&
2988 if (isFormatChange
) {
2989 secdebug("validupdate", "group %lld format change from %d to %d",
2990 (long long)groupId
, format
, formatUpdate
);
2991 /* format of an existing group is changing; delete the group first.
2992 this should ensure that all entries referencing the old groupid are deleted.
2994 ok
= ok
&& SecDbWithSQL(dbc
->dbconn
, deleteGroupRecordSQL
, &localError
, ^bool(sqlite3_stmt
*deleteResponse
) {
2995 ok
= ok
&& SecDbBindInt64(deleteResponse
, 1, groupId
, &localError
);
2996 /* Execute the delete statement. */
2997 ok
= ok
&& SecDbStep(dbc
->dbconn
, deleteResponse
, &localError
, NULL
);
3001 ok
= ok
&& SecDbWithSQL(dbc
->dbconn
, insertGroupRecordSQL
, &localError
, ^bool(sqlite3_stmt
*insertGroup
) {
3002 /* (groupid,flags,format,data,policies) */
3003 /* groups.groupid */
3004 if (ok
&& (!isFormatChange
) && (groupId
>= 0)) {
3005 /* bind to existing groupId row if known, otherwise will insert and autoincrement */
3006 ok
= SecDbBindInt64(insertGroup
, 1, groupId
, &localError
);
3008 secdebug("validupdate", "failed to set groupId %lld", (long long)groupId
);
3013 (void)_SecRevocationDbUpdateFlags(dict
, CFSTR("complete"), kSecValidInfoComplete
, &flags
);
3014 (void)_SecRevocationDbUpdateFlags(dict
, CFSTR("check-ocsp"), kSecValidInfoCheckOCSP
, &flags
);
3015 (void)_SecRevocationDbUpdateFlags(dict
, CFSTR("known-intermediates-only"), kSecValidInfoKnownOnly
, &flags
);
3016 (void)_SecRevocationDbUpdateFlags(dict
, CFSTR("require-ct"), kSecValidInfoRequireCT
, &flags
);
3017 (void)_SecRevocationDbUpdateFlags(dict
, CFSTR("valid"), kSecValidInfoAllowlist
, &flags
);
3018 (void)_SecRevocationDbUpdateFlags(dict
, CFSTR("no-ca"), kSecValidInfoNoCACheck
, &flags
);
3019 (void)_SecRevocationDbUpdateFlags(dict
, CFSTR("no-ca-v2"), kSecValidInfoNoCAv2Check
, &flags
);
3020 (void)_SecRevocationDbUpdateFlags(dict
, CFSTR("overridable"), kSecValidInfoOverridable
, &flags
);
3022 /* date constraints exist if either "not-before" or "not-after" keys are found */
3023 CFTypeRef notBeforeValue
= (CFDateRef
)CFDictionaryGetValue(dict
, CFSTR("not-before"));
3024 CFTypeRef notAfterValue
= (CFDateRef
)CFDictionaryGetValue(dict
, CFSTR("not-after"));
3025 if (isDate(notBeforeValue
) || isDate(notAfterValue
)) {
3026 (void)_SecRevocationDbUpdateFlags(dict
, kBoolTrueKey
, kSecValidInfoDateConstraints
, &flags
);
3027 /* Note that the spec defines not-before and not-after dates as optional, such that
3028 not providing one does not change the database contents. Therefore, we can never clear
3029 this flag; either a new date entry will be supplied, or a format change will cause
3030 the entire group entry to be deleted. */
3032 /* policy constraints exist if "policies" key is found */
3033 CFTypeRef policiesValue
= (CFArrayRef
)CFDictionaryGetValue(dict
, CFSTR("policies"));
3034 if (isArray(policiesValue
)) {
3035 (void)_SecRevocationDbUpdateFlags(dict
, kBoolTrueKey
, kSecValidInfoPolicyConstraints
, &flags
);
3036 /* As above, not providing this value in an update does not change the existing state,
3037 so we never need to clear this flag once it is set. */
3040 /* %%% (TBI:9254570) name constraints don't exist yet */
3041 (void)_SecRevocationDbUpdateFlags(dict
, kBoolFalseKey
, kSecValidInfoNameConstraints
, &flags
);
3043 ok
= SecDbBindInt(insertGroup
, 2, (int)flags
, &localError
);
3045 secdebug("validupdate", "failed to set flags (%lu) for groupId %lld", flags
, (long long)groupId
);
3050 SecValidInfoFormat formatValue
= format
;
3051 if (formatUpdate
> kSecValidInfoFormatUnknown
) {
3052 formatValue
= formatUpdate
;
3054 ok
= SecDbBindInt(insertGroup
, 3, (int)formatValue
, &localError
);
3056 secdebug("validupdate", "failed to set format (%d) for groupId %lld", formatValue
, (long long)groupId
);
3060 CFDataRef xmlData
= NULL
;
3062 bool hasFilter
= ((formatUpdate
== kSecValidInfoFormatNto1
) ||
3063 (formatUpdate
== kSecValidInfoFormatUnknown
&&
3064 format
== kSecValidInfoFormatNto1
));
3066 CFDataRef dataValue
= data
; /* use existing data */
3067 if (_SecRevocationDbUpdateFilter(dict
, data
, &xmlData
)) {
3068 dataValue
= xmlData
; /* use updated data */
3071 ok
= SecDbBindBlob(insertGroup
, 4,
3072 CFDataGetBytePtr(dataValue
),
3073 CFDataGetLength(dataValue
),
3074 SQLITE_TRANSIENT
, &localError
);
3077 secdebug("validupdate", "failed to set data for groupId %lld",
3078 (long long)groupId
);
3081 /* else there is no data, so NULL is implicitly bound to column 4 */
3083 /* groups.policies */
3084 CFDataRef newPoliciesData
= NULL
;
3086 CFDataRef policiesValue
= policies
; /* use existing policies */
3087 newPoliciesData
= createPoliciesData((CFArrayRef
)CFDictionaryGetValue(dict
, CFSTR("policies")));
3088 if (newPoliciesData
) {
3089 policiesValue
= newPoliciesData
; /* use updated policies */
3091 if (policiesValue
) {
3092 ok
= SecDbBindBlob(insertGroup
, 5,
3093 CFDataGetBytePtr(policiesValue
),
3094 CFDataGetLength(policiesValue
),
3095 SQLITE_TRANSIENT
, &localError
);
3097 /* else there is no policy data, so NULL is implicitly bound to column 5 */
3099 secdebug("validupdate", "failed to set policies for groupId %lld",
3100 (long long)groupId
);
3104 /* Execute the insert statement for the group record. */
3106 ok
= SecDbStep(dbc
->dbconn
, insertGroup
, &localError
, NULL
);
3108 secdebug("validupdate", "failed to execute insertGroup statement for groupId %lld",
3109 (long long)groupId
);
3111 result
= (int64_t)sqlite3_last_insert_rowid(SecDbHandle(dbc
->dbconn
));
3114 secdebug("validupdate", "failed to insert group %lld", (long long)result
);
3116 /* Clean up temporary allocations made in this block. */
3117 CFReleaseSafe(xmlData
);
3118 CFReleaseSafe(newPoliciesData
);
3122 CFReleaseSafe(data
);
3123 CFReleaseSafe(policies
);
3125 if (!ok
|| localError
) {
3126 secerror("_SecRevocationDbUpdateGroup failed: %@", localError
);
3127 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationWrite
, TAFatalError
,
3128 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
3130 (void) CFErrorPropagate(localError
, error
);
3134 static int64_t _SecRevocationDbGroupIdForIssuerHash(SecRevocationDbConnectionRef dbc
, CFDataRef hash
, CFErrorRef
*error
) {
3135 /* look up issuer hash in issuers table to get groupid, if it exists */
3136 __block
int64_t groupId
= -1;
3137 __block
bool ok
= (dbc
!= NULL
);
3138 __block CFErrorRef localError
= NULL
;
3141 secdebug("validupdate", "failed to get hash (%@)", hash
);
3143 require(hash
&& dbc
, errOut
);
3145 /* This is the starting point for any lookup; find a group id for the given issuer hash.
3146 Before we do that, need to verify the current db_version. We cannot use results from a
3147 database created with a schema version older than the minimum supported version.
3148 However, we may be able to use results from a newer version. At the next database
3149 update interval, if the existing schema is old, we'll be removing and recreating
3150 the database contents with the current schema version.
3152 int64_t db_version
= _SecRevocationDbGetSchemaVersion(dbc
->db
, dbc
, NULL
);
3153 if (db_version
< kSecRevocationDbMinSchemaVersion
) {
3154 if (!dbc
->db
->unsupportedVersion
) {
3155 secdebug("validupdate", "unsupported db_version: %lld", (long long)db_version
);
3156 dbc
->db
->unsupportedVersion
= true; /* only warn once for a given unsupported version */
3159 require_quiet(db_version
>= kSecRevocationDbMinSchemaVersion
, errOut
);
3161 /* Look up provided issuer_hash in the issuers table.
3163 ok
= ok
&& SecDbWithSQL(dbc
->dbconn
, selectGroupIdSQL
, &localError
, ^bool(sqlite3_stmt
*selectGroupId
) {
3164 ok
&= SecDbBindBlob(selectGroupId
, 1, CFDataGetBytePtr(hash
), CFDataGetLength(hash
), SQLITE_TRANSIENT
, &localError
);
3165 ok
&= SecDbStep(dbc
->dbconn
, selectGroupId
, &localError
, ^(bool *stopGroupId
) {
3166 groupId
= sqlite3_column_int64(selectGroupId
, 0);
3172 if (!ok
|| localError
) {
3173 secerror("_SecRevocationDbGroupIdForIssuerHash failed: %@", localError
);
3174 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationRead
, TAFatalError
,
3175 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
3177 (void) CFErrorPropagate(localError
, error
);
3181 static bool _SecRevocationDbApplyGroupDelete(SecRevocationDbConnectionRef dbc
, CFDataRef issuerHash
, CFErrorRef
*error
) {
3182 /* delete group associated with the given issuer;
3183 schema trigger will delete associated issuers, serials, and hashes. */
3184 __block
int64_t groupId
= -1;
3185 __block
bool ok
= (dbc
!= NULL
);
3186 __block CFErrorRef localError
= NULL
;
3189 groupId
= _SecRevocationDbGroupIdForIssuerHash(dbc
, issuerHash
, &localError
);
3193 SecError(errSecParam
, &localError
, CFSTR("group not found for issuer"));
3197 ok
= ok
&& SecDbWithSQL(dbc
->dbconn
, deleteGroupRecordSQL
, &localError
, ^bool(sqlite3_stmt
*deleteResponse
) {
3198 ok
&= SecDbBindInt64(deleteResponse
, 1, groupId
, &localError
);
3199 /* Execute the delete statement. */
3200 ok
= ok
&& SecDbStep(dbc
->dbconn
, deleteResponse
, &localError
, NULL
);
3203 if (!ok
|| localError
) {
3204 secerror("_SecRevocationDbApplyGroupDelete failed: %@", localError
);
3205 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationWrite
, TAFatalError
,
3206 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
3208 (void) CFErrorPropagate(localError
, error
);
3212 static bool _SecRevocationDbApplyGroupUpdate(SecRevocationDbConnectionRef dbc
, CFDictionaryRef dict
, CFErrorRef
*error
) {
3213 /* process one issuer group's update dictionary */
3214 __block
int64_t groupId
= -1;
3215 __block
bool ok
= (dbc
!= NULL
);
3216 __block CFErrorRef localError
= NULL
;
3218 CFArrayRef issuers
= (dict
) ? (CFArrayRef
)CFDictionaryGetValue(dict
, CFSTR("issuer-hash")) : NULL
;
3219 /* look for existing group id */
3220 if (ok
&& isArray(issuers
)) {
3221 CFIndex issuerIX
, issuerCount
= CFArrayGetCount(issuers
);
3222 /* while we have issuers and haven't found a matching group id */
3223 for (issuerIX
=0; issuerIX
<issuerCount
&& groupId
< 0; issuerIX
++) {
3224 CFDataRef hash
= (CFDataRef
)CFArrayGetValueAtIndex(issuers
, issuerIX
);
3225 if (!hash
) { continue; }
3226 groupId
= _SecRevocationDbGroupIdForIssuerHash(dbc
, hash
, &localError
);
3229 /* according to the spec, we must replace all existing issuers with
3230 the new issuers list, so delete all issuers in the group first. */
3231 ok
= ok
&& SecDbWithSQL(dbc
->dbconn
, deleteGroupIssuersSQL
, &localError
, ^bool(sqlite3_stmt
*deleteIssuers
) {
3232 ok
= ok
&& SecDbBindInt64(deleteIssuers
, 1, groupId
, &localError
);
3233 ok
= ok
&& SecDbStep(dbc
->dbconn
, deleteIssuers
, &localError
, NULL
);
3238 /* create or update the group entry */
3240 groupId
= _SecRevocationDbUpdateGroup(dbc
, groupId
, dict
, &localError
);
3243 secdebug("validupdate", "failed to get groupId");
3246 /* create or update issuer entries, now that we know the group id */
3247 ok
= ok
&& _SecRevocationDbUpdateIssuers(dbc
, groupId
, issuers
, &localError
);
3248 /* create or update entries in serials or hashes tables */
3249 ok
= ok
&& _SecRevocationDbUpdateIssuerData(dbc
, groupId
, dict
, &localError
);
3250 /* create or update entries in dates/names/policies tables */
3251 ok
= ok
&& _SecRevocationDbUpdateIssuerConstraints(dbc
, groupId
, dict
, &localError
);
3254 (void) CFErrorPropagate(localError
, error
);
3258 bool _SecRevocationDbApplyUpdate(SecRevocationDbConnectionRef dbc
, CFDictionaryRef update
, CFIndex version
, CFErrorRef
*error
) {
3259 /* process entire update dictionary */
3260 if (!dbc
|| !dbc
->db
|| !update
) {
3261 secerror("_SecRevocationDbApplyUpdate failed: invalid args");
3262 SecError(errSecParam
, error
, CFSTR("_SecRevocationDbApplyUpdate: invalid db or update parameter"));
3266 CFDictionaryRef localUpdate
= (CFDictionaryRef
)CFRetainSafe(update
);
3267 CFErrorRef localError
= NULL
;
3270 CFTypeRef value
= NULL
;
3271 CFIndex deleteCount
= 0;
3272 CFIndex updateCount
= 0;
3274 dbc
->db
->updateInProgress
= true;
3276 /* check whether this is a full update */
3277 value
= (CFBooleanRef
)CFDictionaryGetValue(update
, CFSTR("full"));
3278 if (isBoolean(value
) && CFBooleanGetValue((CFBooleanRef
)value
)) {
3279 /* clear the database before processing a full update */
3280 dbc
->fullUpdate
= true;
3281 secdebug("validupdate", "update has \"full\" attribute; clearing database");
3282 ok
= ok
&& _SecRevocationDbRemoveAllEntries(dbc
, &localError
);
3285 /* process 'delete' list */
3286 value
= (CFArrayRef
)CFDictionaryGetValue(localUpdate
, CFSTR("delete"));
3287 if (isArray(value
)) {
3288 deleteCount
= CFArrayGetCount((CFArrayRef
)value
);
3289 secdebug("validupdate", "processing %ld deletes", (long)deleteCount
);
3290 for (CFIndex deleteIX
=0; deleteIX
<deleteCount
; deleteIX
++) {
3291 CFDataRef issuerHash
= (CFDataRef
)CFArrayGetValueAtIndex((CFArrayRef
)value
, deleteIX
);
3292 if (isData(issuerHash
)) {
3293 ok
= ok
&& _SecRevocationDbApplyGroupDelete(dbc
, issuerHash
, &localError
);
3295 secdebug("validupdate", "skipping delete %ld (hash is not a data value)", (long)deleteIX
);
3300 /* process 'update' list */
3301 value
= (CFArrayRef
)CFDictionaryGetValue(localUpdate
, CFSTR("update"));
3302 if (isArray(value
)) {
3303 updateCount
= CFArrayGetCount((CFArrayRef
)value
);
3304 secdebug("validupdate", "processing %ld updates", (long)updateCount
);
3305 for (CFIndex updateIX
=0; updateIX
<updateCount
; updateIX
++) {
3306 CFDictionaryRef dict
= (CFDictionaryRef
)CFArrayGetValueAtIndex((CFArrayRef
)value
, updateIX
);
3307 if (isDictionary(dict
)) {
3308 ok
= ok
&& _SecRevocationDbApplyGroupUpdate(dbc
, dict
, &localError
);
3310 secdebug("validupdate", "skipping update %ld (not a dictionary)", (long)updateIX
);
3314 CFReleaseSafe(localUpdate
);
3317 ok
= ok
&& _SecRevocationDbSetVersion(dbc
, version
, &localError
);
3319 /* set interval if not already set, or changed */
3320 int64_t interval
= _SecRevocationDbGetUpdateInterval(dbc
, NULL
);
3321 if (interval
!= dbc
->precommitInterval
) {
3322 interval
= (dbc
->precommitInterval
> 0) ? dbc
->precommitInterval
: kSecStdUpdateInterval
;
3323 ok
= ok
&& _SecRevocationDbSetUpdateInterval(dbc
, interval
, &localError
);
3326 /* set db_version if not already set */
3327 int64_t db_version
= _SecRevocationDbGetSchemaVersion(dbc
->db
, dbc
, NULL
);
3328 if (db_version
<= 0) {
3329 ok
= ok
&& _SecRevocationDbSetSchemaVersion(dbc
, kSecRevocationDbSchemaVersion
, &localError
);
3332 /* set db_format if not already set */
3333 int64_t db_format
= _SecRevocationDbGetUpdateFormat(dbc
, NULL
);
3334 if (db_format
<= 0) {
3335 ok
= ok
&& _SecRevocationDbSetUpdateFormat(dbc
, kSecRevocationDbUpdateFormat
, &localError
);
3338 /* purge the in-memory cache */
3339 SecRevocationDbCachePurge(dbc
->db
);
3341 dbc
->db
->updateInProgress
= false;
3343 (void) CFErrorPropagate(localError
, error
);
3347 static bool _SecRevocationDbSerialInGroup(SecRevocationDbConnectionRef dbc
,
3350 CFErrorRef
*error
) {
3351 __block
bool result
= false;
3352 __block
bool ok
= true;
3353 __block CFErrorRef localError
= NULL
;
3354 require(dbc
&& serial
, errOut
);
3355 ok
&= SecDbWithSQL(dbc
->dbconn
, selectSerialRecordSQL
, &localError
, ^bool(sqlite3_stmt
*selectSerial
) {
3356 ok
&= SecDbBindInt64(selectSerial
, 1, groupId
, &localError
);
3357 ok
&= SecDbBindBlob(selectSerial
, 2, CFDataGetBytePtr(serial
),
3358 CFDataGetLength(serial
), SQLITE_TRANSIENT
, &localError
);
3359 ok
&= SecDbStep(dbc
->dbconn
, selectSerial
, &localError
, ^(bool *stop
) {
3360 int64_t foundRowId
= (int64_t)sqlite3_column_int64(selectSerial
, 0);
3361 result
= (foundRowId
> 0);
3367 if (!ok
|| localError
) {
3368 secerror("_SecRevocationDbSerialInGroup failed: %@", localError
);
3369 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationRead
, TAFatalError
,
3370 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
3372 (void) CFErrorPropagate(localError
, error
);
3376 static bool _SecRevocationDbCertHashInGroup(SecRevocationDbConnectionRef dbc
,
3379 CFErrorRef
*error
) {
3380 __block
bool result
= false;
3381 __block
bool ok
= true;
3382 __block CFErrorRef localError
= NULL
;
3383 require(dbc
&& certHash
, errOut
);
3384 ok
&= SecDbWithSQL(dbc
->dbconn
, selectHashRecordSQL
, &localError
, ^bool(sqlite3_stmt
*selectHash
) {
3385 ok
&= SecDbBindInt64(selectHash
, 1, groupId
, &localError
);
3386 ok
= SecDbBindBlob(selectHash
, 2, CFDataGetBytePtr(certHash
),
3387 CFDataGetLength(certHash
), SQLITE_TRANSIENT
, &localError
);
3388 ok
&= SecDbStep(dbc
->dbconn
, selectHash
, &localError
, ^(bool *stop
) {
3389 int64_t foundRowId
= (int64_t)sqlite3_column_int64(selectHash
, 0);
3390 result
= (foundRowId
> 0);
3396 if (!ok
|| localError
) {
3397 secerror("_SecRevocationDbCertHashInGroup failed: %@", localError
);
3398 TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb
, TAOperationRead
, TAFatalError
,
3399 localError
? CFErrorGetCode(localError
) : errSecInternalComponent
);
3401 (void) CFErrorPropagate(localError
, error
);
3405 static bool _SecRevocationDbSerialInFilter(SecRevocationDbConnectionRef dbc
,
3406 CFDataRef serialData
,
3407 CFDataRef xmlData
) {
3408 /* N-To-1 filter implementation.
3409 The 'xmlData' parameter is a flattened XML dictionary,
3410 containing 'xor' and 'params' keys. First order of
3411 business is to reconstitute the blob into components.
3413 bool result
= false;
3414 CFRetainSafe(xmlData
);
3415 CFDataRef propListData
= xmlData
;
3416 /* Expand data blob if needed */
3417 CFDataRef inflatedData
= copyInflatedData(propListData
);
3419 CFReleaseSafe(propListData
);
3420 propListData
= inflatedData
;
3422 CFDataRef
xor = NULL
;
3423 CFArrayRef params
= NULL
;
3424 CFPropertyListRef nto1
= CFPropertyListCreateWithData(kCFAllocatorDefault
, propListData
, 0, NULL
, NULL
);
3426 xor = (CFDataRef
)CFDictionaryGetValue((CFDictionaryRef
)nto1
, CFSTR("xor"));
3427 params
= (CFArrayRef
)CFDictionaryGetValue((CFDictionaryRef
)nto1
, CFSTR("params"));
3429 uint8_t *hash
= (xor) ? (uint8_t*)CFDataGetBytePtr(xor) : NULL
;
3430 CFIndex hashLen
= (hash
) ? CFDataGetLength(xor) : 0;
3431 uint8_t *serial
= (serialData
) ? (uint8_t*)CFDataGetBytePtr(serialData
) : NULL
;
3432 CFIndex serialLen
= (serial
) ? CFDataGetLength(serialData
) : 0;
3434 require(hash
&& serial
&& params
, errOut
);
3436 const uint32_t FNV_OFFSET_BASIS
= 2166136261;
3437 const uint32_t FNV_PRIME
= 16777619;
3438 bool notInHash
= false;
3439 CFIndex ix
, count
= CFArrayGetCount(params
);
3440 for (ix
= 0; ix
< count
; ix
++) {
3442 CFNumberRef cfnum
= (CFNumberRef
)CFArrayGetValueAtIndex(params
, ix
);
3443 if (!isNumber(cfnum
) ||
3444 !CFNumberGetValue(cfnum
, kCFNumberSInt32Type
, ¶m
)) {
3445 secinfo("validupdate", "error processing filter params at index %ld", (long)ix
);
3448 /* process one param */
3449 uint32_t hval
= FNV_OFFSET_BASIS
^ param
;
3450 CFIndex i
= serialLen
;
3452 hval
= ((hval
^ (serial
[--i
])) * FNV_PRIME
) & 0xFFFFFFFF;
3454 hval
= hval
% (hashLen
* 8);
3455 if ((hash
[hval
/8] & (1 << (hval
% 8))) == 0) {
3456 notInHash
= true; /* definitely not in hash */
3461 /* probabilistically might be in hash if we get here. */
3466 CFReleaseSafe(nto1
);
3467 CFReleaseSafe(propListData
);
3471 static SecValidInfoRef
_SecRevocationDbValidInfoForCertificate(SecRevocationDbConnectionRef dbc
,
3472 SecCertificateRef certificate
,
3473 CFDataRef issuerHash
,
3474 CFErrorRef
*error
) {
3475 __block CFErrorRef localError
= NULL
;
3476 __block SecValidInfoFlags flags
= 0;
3477 __block SecValidInfoFormat format
= kSecValidInfoFormatUnknown
;
3478 __block CFDataRef data
= NULL
;
3480 bool matched
= false;
3481 bool isOnList
= false;
3482 int64_t groupId
= 0;
3483 CFDataRef serial
= NULL
;
3484 CFDataRef certHash
= NULL
;
3485 CFDateRef notBeforeDate
= NULL
;
3486 CFDateRef notAfterDate
= NULL
;
3487 CFDataRef names
= NULL
;
3488 CFDataRef policies
= NULL
;
3489 SecValidInfoRef result
= NULL
;
3491 require((serial
= SecCertificateCopySerialNumberData(certificate
, NULL
)) != NULL
, errOut
);
3492 require((certHash
= SecCertificateCopySHA256Digest(certificate
)) != NULL
, errOut
);
3493 require_quiet((groupId
= _SecRevocationDbGroupIdForIssuerHash(dbc
, issuerHash
, &localError
)) > 0, errOut
);
3495 /* Look up the group record to determine flags and format. */
3496 format
= _SecRevocationDbGetGroupFormat(dbc
, groupId
, &flags
, &data
, &policies
, &localError
);
3498 if (format
== kSecValidInfoFormatUnknown
) {
3499 /* No group record found for this issuer. Don't return a SecValidInfoRef */
3502 else if (format
== kSecValidInfoFormatSerial
) {
3503 /* Look up certificate's serial number in the serials table. */
3504 matched
= _SecRevocationDbSerialInGroup(dbc
, serial
, groupId
, &localError
);
3506 else if (format
== kSecValidInfoFormatSHA256
) {
3507 /* Look up certificate's SHA-256 hash in the hashes table. */
3508 matched
= _SecRevocationDbCertHashInGroup(dbc
, certHash
, groupId
, &localError
);
3510 else if (format
== kSecValidInfoFormatNto1
) {
3511 /* Perform a Bloom filter match against the serial. If matched is false,
3512 then the cert is definitely not in the list. But if matched is true,
3513 we don't know for certain, so we would need to check OCSP. */
3514 matched
= _SecRevocationDbSerialInFilter(dbc
, serial
, data
);
3518 /* Found a specific match for this certificate. */
3519 secdebug("validupdate", "Valid db matched certificate: %@, format=%d, flags=0x%lx",
3520 certHash
, format
, flags
);
3524 /* If supplemental constraints are present for this issuer, then we always match. */
3525 if ((flags
& kSecValidInfoDateConstraints
) &&
3526 (_SecRevocationDbCopyDateConstraints(dbc
, groupId
, ¬BeforeDate
, ¬AfterDate
, &localError
))) {
3527 secdebug("validupdate", "Valid db matched supplemental date constraints for groupId %lld: nb=%@, na=%@",
3528 (long long)groupId
, notBeforeDate
, notAfterDate
);
3532 /* Return SecValidInfo for certificates for which an issuer entry is found. */
3533 result
= SecValidInfoCreate(format
, flags
, isOnList
,
3534 certHash
, issuerHash
, /*anchorHash*/ NULL
,
3535 notBeforeDate
, notAfterDate
,
3538 if (result
&& SecIsAppleTrustAnchor(certificate
, 0)) {
3539 /* Prevent a catch-22. */
3540 secdebug("validupdate", "Valid db match for Apple trust anchor: %@, format=%d, flags=0x%lx",
3541 certHash
, format
, flags
);
3542 CFReleaseNull(result
);
3546 (void) CFErrorPropagate(localError
, error
);
3547 CFReleaseSafe(data
);
3548 CFReleaseSafe(certHash
);
3549 CFReleaseSafe(serial
);
3550 CFReleaseSafe(notBeforeDate
);
3551 CFReleaseSafe(notAfterDate
);
3552 CFReleaseSafe(names
);
3553 CFReleaseSafe(policies
);
3557 static SecValidInfoRef
_SecRevocationDbCopyMatching(SecRevocationDbConnectionRef dbc
,
3558 SecCertificateRef certificate
,
3559 SecCertificateRef issuer
) {
3560 SecValidInfoRef result
= NULL
;
3561 CFErrorRef error
= NULL
;
3562 CFDataRef issuerHash
= NULL
;
3564 require_quiet(dbc
&& certificate
&& issuer
, errOut
);
3565 require(issuerHash
= SecCertificateCopySHA256Digest(issuer
), errOut
);
3567 /* Check for the result in the cache. */
3568 result
= SecRevocationDbCacheRead(dbc
->db
, certificate
, issuerHash
);
3570 /* Upon cache miss, get the result from the database and add it to the cache. */
3572 result
= _SecRevocationDbValidInfoForCertificate(dbc
, certificate
, issuerHash
, &error
);
3573 SecRevocationDbCacheWrite(dbc
->db
, result
);
3577 CFReleaseSafe(issuerHash
);
3578 CFReleaseSafe(error
);
3582 /* Return the update source as a retained CFStringRef.
3583 If the value cannot be obtained, NULL is returned.
3585 CF_RETURNS_RETAINED CFStringRef
SecRevocationDbCopyUpdateSource(void) {
3586 __block CFStringRef result
= NULL
;
3587 SecRevocationDbWith(^(SecRevocationDbRef db
) {
3588 (void) SecRevocationDbPerformRead(db
, NULL
, ^bool(SecRevocationDbConnectionRef dbc
, CFErrorRef
*blockError
) {
3589 result
= _SecRevocationDbCopyUpdateSource(dbc
, blockError
);
3590 return (bool)result
;
3596 /* Set the next update value for the revocation database.
3597 (This function is expected to be called only by the database
3598 maintainer, normally the system instance of trustd. If the
3599 caller does not have write access, this is a no-op.)
3601 bool SecRevocationDbSetNextUpdateTime(CFAbsoluteTime nextUpdate
, CFErrorRef
*error
) {
3602 __block
bool ok
= true;
3603 __block CFErrorRef localError
= NULL
;
3604 SecRevocationDbWith(^(SecRevocationDbRef rdb
) {
3605 ok
&= SecRevocationDbPerformWrite(rdb
, &localError
, ^bool(SecRevocationDbConnectionRef dbc
, CFErrorRef
*blockError
) {
3606 return _SecRevocationDbSetNextUpdateTime(dbc
, nextUpdate
, blockError
);
3609 (void) CFErrorPropagate(localError
, error
);
3613 /* Return the next update value as a CFAbsoluteTime.
3614 If the value cannot be obtained, -1 is returned.
3616 CFAbsoluteTime
SecRevocationDbGetNextUpdateTime(void) {
3617 __block CFAbsoluteTime result
= -1;
3618 SecRevocationDbWith(^(SecRevocationDbRef db
) {
3619 (void) SecRevocationDbPerformRead(db
, NULL
, ^bool(SecRevocationDbConnectionRef dbc
, CFErrorRef
*blockError
) {
3620 result
= _SecRevocationDbGetNextUpdateTime(dbc
, blockError
);
3627 /* Return the serial background queue for database updates.
3628 If the queue cannot be obtained, NULL is returned.
3630 dispatch_queue_t
SecRevocationDbGetUpdateQueue(void) {
3631 __block dispatch_queue_t result
= NULL
;
3632 SecRevocationDbWith(^(SecRevocationDbRef db
) {
3633 result
= (db
) ? db
->update_queue
: NULL
;
3638 /* Release all connections to the revocation database.
3640 void SecRevocationDbReleaseAllConnections(void) {
3641 SecRevocationDbWith(^(SecRevocationDbRef db
) {
3642 SecDbReleaseAllConnections((db
) ? db
->db
: NULL
);
3646 /* === SecRevocationDb API === */
3648 /* Given a certificate and its issuer, returns a SecValidInfoRef if the
3649 valid database contains matching info; otherwise returns NULL.
3650 Caller must release the returned SecValidInfoRef when finished.
3652 SecValidInfoRef
SecRevocationDbCopyMatching(SecCertificateRef certificate
,
3653 SecCertificateRef issuer
) {
3654 __block SecValidInfoRef result
= NULL
;
3655 SecRevocationDbWith(^(SecRevocationDbRef db
) {
3656 (void) SecRevocationDbPerformRead(db
, NULL
, ^bool(SecRevocationDbConnectionRef dbc
, CFErrorRef
*blockError
) {
3657 result
= _SecRevocationDbCopyMatching(dbc
, certificate
, issuer
);
3658 return (bool)result
;
3664 /* Given an issuer, returns true if an entry for this issuer exists in
3665 the database (i.e. a known CA). If the provided certificate is NULL,
3666 or its entry is not found, the function returns false.
3668 bool SecRevocationDbContainsIssuer(SecCertificateRef issuer
) {
3672 __block
bool result
= false;
3673 SecRevocationDbWith(^(SecRevocationDbRef db
) {
3674 (void) SecRevocationDbPerformRead(db
, NULL
, ^bool(SecRevocationDbConnectionRef dbc
, CFErrorRef
*blockError
) {
3675 CFDataRef issuerHash
= SecCertificateCopySHA256Digest(issuer
);
3676 int64_t groupId
= _SecRevocationDbGroupIdForIssuerHash(dbc
, issuerHash
, blockError
);
3677 CFReleaseSafe(issuerHash
);
3678 result
= (groupId
> 0);
3685 /* Return the current version of the revocation database.
3686 A version of 0 indicates an empty database which must be populated.
3687 If the version cannot be obtained, -1 is returned.
3689 CFIndex
SecRevocationDbGetVersion(void) {
3690 __block CFIndex result
= -1;
3691 SecRevocationDbWith(^(SecRevocationDbRef db
) {
3692 (void) SecRevocationDbPerformRead(db
, NULL
, ^bool(SecRevocationDbConnectionRef dbc
, CFErrorRef
*blockError
) {
3693 result
= (CFIndex
)_SecRevocationDbGetVersion(dbc
, blockError
);
3694 return (result
>= 0);
3700 /* Return the current schema version of the revocation database.
3701 A version of 0 indicates an empty database which must be populated.
3702 If the schema version cannot be obtained, -1 is returned.
3704 CFIndex
SecRevocationDbGetSchemaVersion(void) {
3705 __block CFIndex result
= -1;
3706 SecRevocationDbWith(^(SecRevocationDbRef db
) {
3707 result
= (CFIndex
)_SecRevocationDbGetSchemaVersion(db
, NULL
, NULL
);
3712 /* Return the current update format of the revocation database.
3713 A version of 0 indicates the format was unknown.
3714 If the update format cannot be obtained, -1 is returned.
3716 CFIndex
SecRevocationDbGetUpdateFormat(void) {
3717 __block CFIndex result
= -1;
3718 SecRevocationDbWith(^(SecRevocationDbRef db
) {
3719 (void) SecRevocationDbPerformRead(db
, NULL
, ^bool(SecRevocationDbConnectionRef dbc
, CFErrorRef
*blockError
) {
3720 result
= (CFIndex
)_SecRevocationDbGetUpdateFormat(dbc
, blockError
);
3721 return (result
>= 0);
3730 ==============================================================================
3732 ==============================================================================
3735 /* Returns array of SHA-256 hashes computed over the contents of a valid.sqlite3
3736 database, in the order specified by the valid-server-api documentation. The
3737 resulting hashes can be compared against those in the update's 'hash' array.
3739 Hash 0: full database (all fields in initial Valid specification)
3740 Hash 1: all issuer_hash arrays, plus not-after and not-before dates for each
3741 Hash 2: subset of issuer_hash arrays where the no-ca-v2 flag is set
3743 static CF_RETURNS_RETAINED CFArrayRef
SecRevocationDbComputeFullContentDigests(SecRevocationDbConnectionRef dbc
, CFErrorRef
*error
) {
3744 if (!dbc
) { return NULL
; }
3745 __block
bool ok
= true;
3746 __block CFErrorRef localError
= NULL
;
3747 __block
uint32_t N
[4]={0,0,0,0};
3748 __block CC_SHA256_CTX hash0_ctx
, hash1_ctx
, hash2_ctx
;
3749 CC_SHA256_Init(&hash0_ctx
);
3750 CC_SHA256_Init(&hash1_ctx
);
3751 CC_SHA256_Init(&hash2_ctx
);
3753 // Add version, check-again, and update (array count) fields as array of N.
3754 // (Note: 'N' is defined as "unsigned 32-bit integer in network byte order")
3755 int64_t version
= _SecRevocationDbGetVersion(dbc
, NULL
);
3756 N
[0] = OSSwapInt32(version
& 0xffffffff);
3757 int64_t interval
= _SecRevocationDbGetUpdateInterval(dbc
, NULL
);
3759 interval
= kSecStdUpdateInterval
; // if we didn't store it, assume default
3761 N
[1] = OSSwapInt32(interval
& 0xffffffff);
3762 __block
int64_t count
= 0;
3763 ok
= ok
&& SecDbWithSQL(dbc
->dbconn
, CFSTR("SELECT count(*) FROM groups"), &localError
, ^bool(sqlite3_stmt
*selectGroupsCount
) {
3764 ok
= ok
&& SecDbStep(dbc
->dbconn
, selectGroupsCount
, &localError
, ^void(bool *stop
) {
3765 count
= sqlite3_column_int64(selectGroupsCount
, 0);
3770 N
[2] = OSSwapInt32(count
& 0xffffffff);
3771 CC_SHA256_Update(&hash0_ctx
, N
, sizeof(uint32_t) * 3);
3773 // Sort the update array in order of minimum 'issuer-hash' entry.
3774 // The issuer-hash array is first sorted to determine the lowest issuer-hash,
3775 // and that value is used to sort the update entries.
3777 // For our sqlite database, recreating the update array order means fetching
3778 // the groupid column from the issuers table after sorting on issuer_hash,
3779 // using DISTINCT to remove duplicates. Then, for each returned groupid, we
3780 // obtain its list of issuers, its list of serials or hashes, and other data.
3782 ok
= ok
&& SecDbWithSQL(dbc
->dbconn
, CFSTR("SELECT DISTINCT groupid FROM issuers ORDER BY issuer_hash ASC"), &localError
, ^bool(sqlite3_stmt
*selectGroups
) {
3783 ok
= ok
&& SecDbForEach(dbc
->dbconn
, selectGroups
, &localError
, ^bool(int row_index
) {
3784 __block
int64_t groupId
= sqlite3_column_int64(selectGroups
, 0);
3785 ok
= ok
&& SecDbWithSQL(dbc
->dbconn
, CFSTR("SELECT flags,format,data FROM groups WHERE groupid=?"), &localError
, ^bool(sqlite3_stmt
*selectGroup
) {
3786 ok
= ok
&& SecDbBindInt64(selectGroup
, 1, groupId
, &localError
);
3787 ok
= ok
&& SecDbStep(dbc
->dbconn
, selectGroup
, &localError
, ^(bool *stop
) {
3788 // per-group info is hashed in the following order:
3789 // - issuer_hash array data (sorted)
3790 // - flag bytes, in order listed below
3791 // - format string [serial|sha256|nto1]
3792 // - add array data (sorted), if [serial|sha256]
3793 // - params (if present)
3794 // - xor data (if present)
3796 int64_t flags
= sqlite3_column_int64(selectGroup
, 0);
3797 bool noCAv2
= (flags
& kSecValidInfoNoCAv2Check
);
3799 // instead of recreating the issuer_hash array in memory,
3800 // hash its length (item count) followed by the data of each issuer_hash.
3801 ok
= ok
&& SecDbWithSQL(dbc
->dbconn
, CFSTR("SELECT count(*) FROM issuers WHERE groupid=?"), &localError
, ^bool(sqlite3_stmt
*selectIssuersCount
) {
3802 ok
= ok
&& SecDbBindInt64(selectIssuersCount
, 1, groupId
, &localError
);
3803 ok
= ok
&& SecDbStep(dbc
->dbconn
, selectIssuersCount
, &localError
, ^void(bool *stop
) {
3804 count
= sqlite3_column_int64(selectIssuersCount
, 0);
3809 uint32_t n
= OSSwapInt32(count
& 0xffffffff);
3810 CC_SHA256_Update(&hash0_ctx
, &n
, sizeof(uint32_t));
3811 CC_SHA256_Update(&hash1_ctx
, &n
, sizeof(uint32_t));
3813 CC_SHA256_Update(&hash2_ctx
, &n
, sizeof(uint32_t));
3816 // process issuer_hash entries for this group
3817 ok
= ok
&& SecDbWithSQL(dbc
->dbconn
, CFSTR("SELECT issuer_hash FROM issuers WHERE groupid=? ORDER BY issuer_hash ASC"), &localError
, ^bool(sqlite3_stmt
*selectIssuerHash
) {
3818 ok
= ok
&& SecDbBindInt64(selectIssuerHash
, 1, groupId
, &localError
);
3819 ok
= ok
&& SecDbForEach(dbc
->dbconn
, selectIssuerHash
, &localError
, ^bool(int row_index
) {
3820 uint8_t *p
= (uint8_t *)sqlite3_column_blob(selectIssuerHash
, 0);
3821 CFDataRef data
= NULL
;
3823 CFIndex length
= (CFIndex
)sqlite3_column_bytes(selectIssuerHash
, 0);
3824 data
= CFDataCreate(kCFAllocatorDefault
, p
, length
);
3827 hashData(data
, &hash0_ctx
);
3828 hashData(data
, &hash1_ctx
);
3830 hashData(data
, &hash2_ctx
);
3841 // process flags, converting to array of unsigned 8-bit values, either 0 or 1:
3842 // [ complete, check-ocsp, known-intermediates-only, no-ca, overridable, require-ct, valid ]
3843 uint8_t C
[8]={0,0,0,0,0,0,0,0};
3844 C
[0] = (flags
& kSecValidInfoComplete
) ? 1 : 0;
3845 C
[1] = (flags
& kSecValidInfoCheckOCSP
) ? 1 : 0;
3846 C
[2] = (flags
& kSecValidInfoKnownOnly
) ? 1 : 0;
3847 C
[3] = (flags
& kSecValidInfoNoCACheck
) ? 1 : 0;
3848 C
[4] = (flags
& kSecValidInfoOverridable
) ? 1 : 0;
3849 C
[5] = (flags
& kSecValidInfoRequireCT
) ? 1 : 0;
3850 C
[6] = (flags
& kSecValidInfoAllowlist
) ? 1 : 0;
3851 CC_SHA256_Update(&hash0_ctx
, C
, sizeof(uint8_t) * 7);
3853 // process format, converting integer to string value [serial|sha256|nto1]
3854 SecValidInfoFormat format
= (SecValidInfoFormat
)sqlite3_column_int(selectGroup
, 1);
3856 case kSecValidInfoFormatSerial
:
3857 hashString(CFSTR("serial"), &hash0_ctx
);
3859 case kSecValidInfoFormatSHA256
:
3860 hashString(CFSTR("sha256"), &hash0_ctx
);
3862 case kSecValidInfoFormatNto1
:
3863 hashString(CFSTR("nto1"), &hash0_ctx
);
3865 case kSecValidInfoFormatUnknown
:
3867 ok
= false; // unexpected format values are not allowed
3870 // process 'add' array (serial or sha256 format).
3871 // instead of recreating the 'add' array in memory,
3872 // hash its length (item count) followed by the data of each entry.
3873 CFStringRef arrayCountSql
= NULL
;
3874 if (format
== kSecValidInfoFormatSerial
) {
3875 arrayCountSql
= CFSTR("SELECT count(*) FROM serials WHERE groupid=?");
3876 } else if (format
== kSecValidInfoFormatSHA256
) {
3877 arrayCountSql
= CFSTR("SELECT count(*) FROM hashes WHERE groupid=?");
3879 if (arrayCountSql
) {
3880 ok
= ok
&& SecDbWithSQL(dbc
->dbconn
, arrayCountSql
, &localError
, ^bool(sqlite3_stmt
*selectAddCount
) {
3881 ok
= ok
&& SecDbBindInt64(selectAddCount
, 1, groupId
, &localError
);
3882 ok
= ok
&& SecDbStep(dbc
->dbconn
, selectAddCount
, &localError
, ^void(bool *stop
) {
3883 count
= sqlite3_column_int64(selectAddCount
, 0);
3888 n
= OSSwapInt32(count
& 0xffffffff);
3889 CC_SHA256_Update(&hash0_ctx
, &n
, sizeof(uint32_t));
3891 // process data entries for this group
3892 CFStringRef arrayDataSql
= NULL
;
3893 if (format
== kSecValidInfoFormatSerial
) {
3894 arrayDataSql
= CFSTR("SELECT serial FROM serials WHERE groupid=? ORDER BY serial ASC");
3895 } else if (format
== kSecValidInfoFormatSHA256
) {
3896 arrayDataSql
= CFSTR("SELECT sha256 FROM hashes WHERE groupid=? ORDER by sha256 ASC");
3899 ok
= ok
&& SecDbWithSQL(dbc
->dbconn
, arrayDataSql
, &localError
, ^bool(sqlite3_stmt
*selectAddData
) {
3900 ok
= ok
&& SecDbBindInt64(selectAddData
, 1, groupId
, &localError
);
3901 ok
= ok
&& SecDbForEach(dbc
->dbconn
, selectAddData
, &localError
, ^bool(int row_index
) {
3902 uint8_t *p
= (uint8_t *)sqlite3_column_blob(selectAddData
, 0);
3903 CFDataRef data
= NULL
;
3905 CFIndex length
= (CFIndex
)sqlite3_column_bytes(selectAddData
, 0);
3906 data
= CFDataCreate(kCFAllocatorDefault
, p
, length
);
3909 hashData(data
, &hash0_ctx
);
3920 // process params and xor data, if format is nto1
3921 if (format
== kSecValidInfoFormatNto1
) {
3922 uint8_t *p
= (uint8_t *)sqlite3_column_blob(selectGroup
, 2);
3923 CFDataRef data
= NULL
;
3925 CFIndex length
= (CFIndex
)sqlite3_column_bytes(selectGroup
, 2);
3926 data
= CFDataCreate(kCFAllocatorDefault
, p
, length
);
3929 // unpack params and xor data
3930 CFDataRef
xor = NULL
;
3931 CFArrayRef params
= NULL
;
3932 if (copyFilterComponents(data
, &xor, ¶ms
)) {
3933 hashArray(params
, &hash0_ctx
);
3934 hashData(xor, &hash0_ctx
);
3939 CFReleaseSafe(params
);
3941 CFReleaseSafe(data
);
3944 // process date constraints [not-after, not-before]
3945 CFAbsoluteTime notBefore
= -3155760000.0; /* default: 1901-01-01 00:00:00-0000 */
3946 CFAbsoluteTime notAfter
= 31556908800.0; /* default: 3001-01-01 00:00:00-0000 */
3947 CFDateRef notBeforeDate
= NULL
;
3948 CFDateRef notAfterDate
= NULL
;
3949 if (_SecRevocationDbCopyDateConstraints(dbc
, groupId
, ¬BeforeDate
, ¬AfterDate
, &localError
)) {
3950 if (notBeforeDate
) {
3951 notBefore
= CFDateGetAbsoluteTime(notBeforeDate
);
3952 CFReleaseNull(notBeforeDate
);
3955 notAfter
= CFDateGetAbsoluteTime(notAfterDate
);
3956 CFReleaseNull(notAfterDate
);
3959 double nb
= htond(notBefore
);
3960 double na
= htond(notAfter
);
3961 CC_SHA256_Update(&hash1_ctx
, &na
, sizeof(double));
3962 CC_SHA256_Update(&hash1_ctx
, &nb
, sizeof(double));
3965 }); // per-group step
3967 }); // per-group select
3969 }); // for each group in list
3971 }); // select full group list
3973 CFMutableArrayRef result
= CFArrayCreateMutable(NULL
, 0, &kCFTypeArrayCallBacks
);
3975 uint8_t digest
[CC_SHA256_DIGEST_LENGTH
];
3976 CFDataRef data
= NULL
;
3977 CC_SHA256_Final(digest
, &hash0_ctx
);
3978 if ((data
= CFDataCreate(NULL
, (const UInt8
*)digest
, CC_SHA256_DIGEST_LENGTH
)) != NULL
) {
3979 CFArrayAppendValue(result
, data
);
3980 CFReleaseNull(data
);
3982 CC_SHA256_Final(digest
, &hash1_ctx
);
3983 if ((data
= CFDataCreate(NULL
, (const UInt8
*)digest
, CC_SHA256_DIGEST_LENGTH
)) != NULL
) {
3984 CFArrayAppendValue(result
, data
);
3985 CFReleaseNull(data
);
3987 CC_SHA256_Final(digest
, &hash2_ctx
);
3988 if ((data
= CFDataCreate(NULL
, (const UInt8
*)digest
, CC_SHA256_DIGEST_LENGTH
)) != NULL
) {
3989 CFArrayAppendValue(result
, data
);
3990 CFReleaseNull(data
);
3993 (void) CFErrorPropagate(localError
, error
);
3997 static bool SecRevocationDbComputeDigests(SecRevocationDbConnectionRef dbc
, CFErrorRef
*error
) {
3998 secinfo("validupdate", "Started verifying db content");
4000 CFArrayRef expectedList
= _SecRevocationDbCopyHashes(dbc
, error
);
4001 CFIndex expectedCount
= (expectedList
) ? CFArrayGetCount(expectedList
) : 0;
4002 if (expectedCount
< 1) {
4003 secinfo("validupdate", "Unable to read db_hash values");
4004 CFReleaseNull(expectedList
);
4005 return result
; // %%%% this will happen on first update, when db_hash isn't there
4007 CFArrayRef computedList
= SecRevocationDbComputeFullContentDigests(dbc
, error
);
4008 CFIndex computedCount
= (computedList
) ? CFArrayGetCount(computedList
) : 0;
4009 for (CFIndex idx
= 0; idx
< expectedCount
; idx
++) {
4010 if (idx
>= computedCount
) {
4011 continue; // server provided additional hash value that we don't yet compute
4013 CFDataRef expectedHash
= (CFDataRef
)CFArrayGetValueAtIndex(expectedList
, idx
);
4014 CFDataRef computedHash
= (CFDataRef
)CFArrayGetValueAtIndex(computedList
, idx
);
4015 if (!CFEqualSafe(expectedHash
, computedHash
)) {
4021 secinfo("validupdate", "Expected: %@", expectedList
);
4022 secinfo("validupdate", "Computed: %@", computedList
);
4024 secinfo("validupdate", "Finished verifying db content; result=%s",
4025 (result
) ? "SUCCESS" : "FAIL");
4026 CFReleaseSafe(expectedList
);
4027 CFReleaseSafe(computedList
);