+++ /dev/null
-/*
- * Copyright (c) 2016-2019 Apple Inc. All Rights Reserved.
- *
- * @APPLE_LICENSE_HEADER_START@
- *
- * This file contains Original Code and/or Modifications of Original Code
- * as defined in and that are subject to the Apple Public Source License
- * Version 2.0 (the 'License'). You may not use this file except in
- * compliance with the License. Please obtain a copy of the License at
- * http://www.opensource.apple.com/apsl/ and read it before using this
- * file.
- *
- * The Original Code and all software distributed under the License are
- * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
- * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
- * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
- * Please see the License for the specific language governing rights and
- * limitations under the License.
- *
- * @APPLE_LICENSE_HEADER_END@
- *
- */
-
-/*
- * SecRevocationDb.c
- */
-
-#include <securityd/SecRevocationDb.h>
-#include <securityd/OTATrustUtilities.h>
-#include <securityd/SecRevocationNetworking.h>
-#include <securityd/SecTrustLoggingServer.h>
-#include <Security/SecCertificateInternal.h>
-#include <Security/SecCMS.h>
-#include <Security/CMSDecoder.h>
-#include <Security/SecFramework.h>
-#include <Security/SecInternal.h>
-#include <Security/SecPolicyPriv.h>
-#include <AssertMacros.h>
-#include <stdlib.h>
-#include <stdatomic.h>
-#include <limits.h>
-#include <string.h>
-#include <fcntl.h>
-#include <sys/stat.h>
-#include <errno.h>
-#include <dispatch/dispatch.h>
-#include <notify.h>
-#include <asl.h>
-#include <copyfile.h>
-#include "utilities/debugging.h"
-#include "utilities/sec_action.h"
-#include "utilities/sqlutils.h"
-#include "utilities/SecAppleAnchorPriv.h"
-#include "utilities/iOSforOSX.h"
-#include <utilities/SecCFError.h>
-#include <utilities/SecCFRelease.h>
-#include <utilities/SecCFWrappers.h>
-#include <utilities/SecDb.h>
-#include <utilities/SecFileLocations.h>
-#include <sqlite3.h>
-#include <zlib.h>
-#include <malloc/malloc.h>
-#include <xpc/activity.h>
-#include <xpc/private.h>
-#include <os/transaction_private.h>
-#include <os/variant_private.h>
-#include <os/lock.h>
-
-#include <CommonCrypto/CommonDigest.h>
-#include <CFNetwork/CFHTTPMessage.h>
-#include <CoreFoundation/CFURL.h>
-#include <CoreFoundation/CFUtilities.h>
-
-/*
- ==============================================================================
- CoreFoundation utilities
- ==============================================================================
-*/
-
-static bool hashCFThing(CFTypeRef thing, CC_SHA256_CTX* hash_ctx);
-
-/* comparison function for sorting dictionary keys alphabetically */
-static int compareCFStrings(const void *p, const void *q) {
- CFStringRef str1 = *(CFStringRef *)p;
- CFStringRef str2 = *(CFStringRef *)q;
- if (!(isString(str1) && isString(str2))) {
- return -1; /* can't compare non-string types */
- }
- CFComparisonResult result = CFStringCompare(str1, str2, 0);
- if (result == kCFCompareLessThan) {
- return -1;
- } else if (result == kCFCompareGreaterThan) {
- return 1;
- }
- return 0; /* (result == kCFCompareEqualTo) */
-}
-
-static bool hashData(CFDataRef data, CC_SHA256_CTX* hash_ctx) {
- if (!isData(data)) { return false; }
- uint32_t length = (uint32_t)(CFDataGetLength(data) & 0xFFFFFFFF);
- uint32_t n = OSSwapInt32(length);
- CC_SHA256_Update(hash_ctx, &n, sizeof(uint32_t));
- const uint8_t *p = (uint8_t*)CFDataGetBytePtr(data);
- if (p) { CC_SHA256_Update(hash_ctx, p, length); }
- return (p != NULL);
-}
-
-static bool hashString(CFStringRef str, CC_SHA256_CTX* hash_ctx) {
- if (!isString(str)) { return false; }
- __block bool ok = false;
- CFStringPerformWithCString(str, ^(const char *strbuf) {
- // hash string length (in bytes, not counting null terminator)
- uint32_t c = (uint32_t)(strlen(strbuf) & 0xFFFFFFFF);
- uint32_t n = OSSwapInt32(c);
- CC_SHA256_Update(hash_ctx, &n, sizeof(uint32_t));
- // hash string bytes
- CC_SHA256_Update(hash_ctx, strbuf, c);
- ok = true;
- });
- return ok;
-}
-
-static bool hashNumber(CFNumberRef num, CC_SHA256_CTX* hash_ctx) {
- uint32_t n = 0;
- if (!isNumber(num) || !CFNumberGetValue(num, kCFNumberSInt32Type, &n)) {
- return false;
- }
- n = OSSwapInt32(n);
- CC_SHA256_Update(hash_ctx, &n, sizeof(uint32_t));
- return true;
-}
-
-static bool hashBoolean(CFBooleanRef value, CC_SHA256_CTX* hash_ctx) {
- if (!isBoolean(value)) { return false; }
- uint8_t c = CFBooleanGetValue(value) ? 1 : 0;
- CC_SHA256_Update(hash_ctx, &c, sizeof(uint8_t));
- return true;
-}
-
-static bool hashArray(CFArrayRef array, CC_SHA256_CTX* hash_ctx) {
- if (!isArray(array)) { return false; }
- CFIndex count = CFArrayGetCount(array);
- uint32_t n = OSSwapInt32(count & 0xFFFFFFFF);
- CC_SHA256_Update(hash_ctx, &n, sizeof(uint32_t));
- __block bool ok = true;
- CFArrayForEach(array, ^(const void *thing) {
- ok &= hashCFThing(thing, hash_ctx);
- });
- return ok;
-}
-
-static bool hashDictionary(CFDictionaryRef dictionary, CC_SHA256_CTX* hash_ctx) {
- if (!isDictionary(dictionary)) { return false; }
- CFIndex count = CFDictionaryGetCount(dictionary);
- const void **keys = (const void **)malloc(sizeof(void*) * count);
- const void **vals = (const void **)malloc(sizeof(void*) * count);
- bool ok = (keys && vals);
- if (ok) {
- CFDictionaryGetKeysAndValues(dictionary, keys, vals);
- qsort(keys, count, sizeof(CFStringRef), compareCFStrings);
- uint32_t n = OSSwapInt32(count & 0xFFFFFFFF);
- CC_SHA256_Update(hash_ctx, &n, sizeof(uint32_t));
- }
- for (CFIndex idx = 0; ok && idx < count; idx++) {
- CFStringRef key = (CFStringRef)keys[idx];
- CFTypeRef value = (CFTypeRef)CFDictionaryGetValue(dictionary, key);
- ok &= hashString(key, hash_ctx);
- ok &= hashCFThing(value, hash_ctx);
- }
- free(keys);
- free(vals);
- return ok;
-}
-
-static bool hashCFThing(CFTypeRef thing, CC_SHA256_CTX* hash_ctx) {
- if (isArray(thing)) {
- return hashArray(thing, hash_ctx);
- } else if (isDictionary(thing)) {
- return hashDictionary(thing, hash_ctx);
- } else if (isData(thing)) {
- return hashData(thing, hash_ctx);
- } else if (isString(thing)) {
- return hashString(thing, hash_ctx);
- } else if (isNumber(thing)) {
- return hashNumber(thing, hash_ctx);
- } else if (isBoolean(thing)) {
- return hashBoolean(thing, hash_ctx);
- }
- return false;
-}
-
-static double htond(double h) {
- /* no-op if big endian */
- if (OSHostByteOrder() == OSBigEndian) {
- return h;
- }
- double n;
- size_t i=0;
- char *hp = (char*)&h;
- char *np = (char*)&n;
- while (i < sizeof(h)) { np[i] = hp[(sizeof(h)-1)-i]; ++i; }
- return n;
-}
-
-
-// MARK: -
-// MARK: Valid definitions
-/*
- ==============================================================================
- Valid definitions
- ==============================================================================
-*/
-
-const CFStringRef kValidUpdateProdServer = CFSTR("valid.apple.com");
-const CFStringRef kValidUpdateSeedServer = CFSTR("valid.apple.com/seed");
-const CFStringRef kValidUpdateCarryServer = CFSTR("valid.apple.com/carry");
-
-static CFStringRef kSecPrefsDomain = CFSTR("com.apple.security");
-static CFStringRef kUpdateServerKey = CFSTR("ValidUpdateServer");
-static CFStringRef kUpdateEnabledKey = CFSTR("ValidUpdateEnabled");
-static CFStringRef kVerifyEnabledKey = CFSTR("ValidVerifyEnabled");
-static CFStringRef kUpdateIntervalKey = CFSTR("ValidUpdateInterval");
-static CFStringRef kBoolTrueKey = CFSTR("1");
-static CFStringRef kBoolFalseKey = CFSTR("0");
-
-/* constant length of boolean string keys */
-#define BOOL_STRING_KEY_LENGTH 1
-
-typedef CF_OPTIONS(CFOptionFlags, SecValidInfoFlags) {
- kSecValidInfoComplete = 1u << 0,
- kSecValidInfoCheckOCSP = 1u << 1,
- kSecValidInfoKnownOnly = 1u << 2,
- kSecValidInfoRequireCT = 1u << 3,
- kSecValidInfoAllowlist = 1u << 4,
- kSecValidInfoNoCACheck = 1u << 5,
- kSecValidInfoOverridable = 1u << 6,
- kSecValidInfoDateConstraints = 1u << 7,
- kSecValidInfoNameConstraints = 1u << 8,
- kSecValidInfoPolicyConstraints = 1u << 9,
- kSecValidInfoNoCAv2Check = 1u << 10,
-};
-
-/* minimum update interval */
-#define kSecMinUpdateInterval (60.0 * 5)
-
-/* standard update interval */
-#define kSecStdUpdateInterval (60.0 * 60 * 3)
-
-/* maximum allowed interval */
-#define kSecMaxUpdateInterval (60.0 * 60 * 24 * 7)
-
-#define kSecRevocationBasePath "/Library/Keychains/crls"
-#define kSecRevocationCurUpdateFile "update-current"
-#define kSecRevocationDbFileName "valid.sqlite3"
-#define kSecRevocationDbReplaceFile ".valid_replace"
-
-#define isDbOwner SecOTAPKIIsSystemTrustd
-
-#define kSecRevocationDbChanged "com.apple.trustd.valid.db-changed"
-
-/* database schema version
- v1 = initial version
- v2 = fix for group entry transitions
- v3 = handle optional entries in update dictionaries
- v4 = add db_format and db_source entries
- v5 = add date constraints table, with updated group flags
- v6 = explicitly set autovacuum and journal modes at db creation
- v7 = add policies column to groups table (policy constraints)
-
- Note: kSecRevocationDbMinSchemaVersion is the lowest version whose
- results can be used. This allows revocation results to be obtained
- from an existing db before the next update interval occurs, at which
- time we'll update to the current version (kSecRevocationDbSchemaVersion).
-*/
-#define kSecRevocationDbSchemaVersion 7 /* current version we support */
-#define kSecRevocationDbMinSchemaVersion 7 /* minimum version we can use */
-
-/* update file format
-*/
-CF_ENUM(CFIndex) {
- kSecValidUpdateFormatG1 = 1, /* initial version */
- kSecValidUpdateFormatG2 = 2, /* signed content, single plist */
- kSecValidUpdateFormatG3 = 3 /* signed content, multiple plists */
-};
-
-#define kSecRevocationDbUpdateFormat 3 /* current version we support */
-#define kSecRevocationDbMinUpdateFormat 2 /* minimum version we can use */
-
-#define kSecRevocationDbCacheSize 100
-
-typedef struct __SecRevocationDb *SecRevocationDbRef;
-struct __SecRevocationDb {
- SecDbRef db;
- dispatch_queue_t update_queue;
- bool updateInProgress;
- bool unsupportedVersion;
- bool changed;
- CFMutableArrayRef info_cache_list;
- CFMutableDictionaryRef info_cache;
- os_unfair_lock info_cache_lock;
-};
-
-typedef struct __SecRevocationDbConnection *SecRevocationDbConnectionRef;
-struct __SecRevocationDbConnection {
- SecRevocationDbRef db;
- SecDbConnectionRef dbconn;
- CFIndex precommitVersion;
- CFIndex precommitDbVersion;
- CFIndex precommitInterval;
- bool fullUpdate;
-};
-
-bool SecRevocationDbVerifyUpdate(void *update, CFIndex length);
-bool SecRevocationDbIngestUpdate(SecRevocationDbConnectionRef dbc, CFDictionaryRef update, CFIndex chunkVersion, CFIndex *outVersion, CFErrorRef *error);
-bool _SecRevocationDbApplyUpdate(SecRevocationDbConnectionRef dbc, CFDictionaryRef update, CFIndex version, CFErrorRef *error);
-CFAbsoluteTime SecRevocationDbComputeNextUpdateTime(CFIndex updateInterval);
-bool SecRevocationDbUpdateSchema(SecRevocationDbRef rdb);
-CFIndex SecRevocationDbGetUpdateFormat(void);
-bool _SecRevocationDbSetUpdateSource(SecRevocationDbConnectionRef dbc, CFStringRef source, CFErrorRef *error);
-bool SecRevocationDbSetUpdateSource(SecRevocationDbRef rdb, CFStringRef source);
-CFStringRef SecRevocationDbCopyUpdateSource(void);
-bool SecRevocationDbSetNextUpdateTime(CFAbsoluteTime nextUpdate, CFErrorRef *error);
-CFAbsoluteTime SecRevocationDbGetNextUpdateTime(void);
-dispatch_queue_t SecRevocationDbGetUpdateQueue(void);
-bool _SecRevocationDbRemoveAllEntries(SecRevocationDbConnectionRef dbc, CFErrorRef *error);
-void SecRevocationDbReleaseAllConnections(void);
-static bool SecValidUpdateForceReplaceDatabase(void);
-static void SecRevocationDbWith(void(^dbJob)(SecRevocationDbRef db));
-static bool SecRevocationDbPerformWrite(SecRevocationDbRef rdb, CFErrorRef *error, bool(^writeJob)(SecRevocationDbConnectionRef dbc, CFErrorRef *blockError));
-static bool SecRevocationDbPerformRead(SecRevocationDbRef rdb, CFErrorRef *error, bool(^readJob)(SecRevocationDbConnectionRef dbc, CFErrorRef *blockError));
-static SecValidInfoFormat _SecRevocationDbGetGroupFormat(SecRevocationDbConnectionRef dbc, int64_t groupId, SecValidInfoFlags *flags, CFDataRef *data, CFDataRef *policies, CFErrorRef *error);
-static bool _SecRevocationDbSetUpdateInterval(SecRevocationDbConnectionRef dbc, int64_t interval, CFErrorRef *error);
-static int64_t _SecRevocationDbGetUpdateInterval(SecRevocationDbConnectionRef dbc, CFErrorRef *error);
-static bool _SecRevocationDbSetVersion(SecRevocationDbConnectionRef dbc, CFIndex version, CFErrorRef *error);
-static int64_t _SecRevocationDbGetVersion(SecRevocationDbConnectionRef dbc, CFErrorRef *error);
-static int64_t _SecRevocationDbGetSchemaVersion(SecRevocationDbRef rdb, SecRevocationDbConnectionRef dbc, CFErrorRef *error);
-static CFArrayRef _SecRevocationDbCopyHashes(SecRevocationDbConnectionRef dbc, CFErrorRef *error);
-static bool _SecRevocationDbSetHashes(SecRevocationDbConnectionRef dbc, CFArrayRef hashes, CFErrorRef *error);
-static void SecRevocationDbResetCaches(void);
-static SecRevocationDbConnectionRef SecRevocationDbConnectionInit(SecRevocationDbRef db, SecDbConnectionRef dbconn, CFErrorRef *error);
-static bool SecRevocationDbComputeDigests(SecRevocationDbConnectionRef dbc, CFErrorRef *error);
-
-
-static CFDataRef copyInflatedData(CFDataRef data) {
- if (!data) {
- return NULL;
- }
- z_stream zs;
- memset(&zs, 0, sizeof(zs));
- /* 32 is a magic value which enables automatic header detection
- of gzip or zlib compressed data. */
- if (inflateInit2(&zs, 32+MAX_WBITS) != Z_OK) {
- return NULL;
- }
- zs.next_in = (UInt8 *)(CFDataGetBytePtr(data));
- zs.avail_in = (uInt)CFDataGetLength(data);
-
- CFMutableDataRef outData = CFDataCreateMutable(NULL, 0);
- if (!outData) {
- return NULL;
- }
- CFIndex buf_sz = malloc_good_size(zs.avail_in ? zs.avail_in : 1024 * 4);
- unsigned char *buf = malloc(buf_sz);
- int rc;
- do {
- zs.next_out = (Bytef*)buf;
- zs.avail_out = (uInt)buf_sz;
- rc = inflate(&zs, 0);
- CFIndex outLen = CFDataGetLength(outData);
- if (outLen < (CFIndex)zs.total_out) {
- CFDataAppendBytes(outData, (const UInt8*)buf, (CFIndex)zs.total_out - outLen);
- }
- } while (rc == Z_OK);
-
- inflateEnd(&zs);
- free(buf);
- if (rc != Z_STREAM_END) {
- CFReleaseSafe(outData);
- return NULL;
- }
- return (CFDataRef)outData;
-}
-
-static CFDataRef copyDeflatedData(CFDataRef data) {
- if (!data) {
- return NULL;
- }
- z_stream zs;
- memset(&zs, 0, sizeof(zs));
- if (deflateInit(&zs, Z_BEST_COMPRESSION) != Z_OK) {
- return NULL;
- }
- zs.next_in = (UInt8 *)(CFDataGetBytePtr(data));
- zs.avail_in = (uInt)CFDataGetLength(data);
-
- CFMutableDataRef outData = CFDataCreateMutable(NULL, 0);
- if (!outData) {
- return NULL;
- }
- CFIndex buf_sz = malloc_good_size(zs.avail_in ? zs.avail_in : 1024 * 4);
- unsigned char *buf = malloc(buf_sz);
- int rc = Z_BUF_ERROR;
- do {
- zs.next_out = (Bytef*)buf;
- zs.avail_out = (uInt)buf_sz;
- rc = deflate(&zs, Z_FINISH);
-
- if (rc == Z_OK || rc == Z_STREAM_END) {
- CFIndex buf_used = buf_sz - zs.avail_out;
- CFDataAppendBytes(outData, (const UInt8*)buf, buf_used);
- }
- else if (rc == Z_BUF_ERROR) {
- free(buf);
- buf_sz = malloc_good_size(buf_sz * 2);
- buf = malloc(buf_sz);
- if (buf) {
- rc = Z_OK; /* try again with larger buffer */
- }
- }
- } while (rc == Z_OK && zs.avail_in);
-
- deflateEnd(&zs);
- free(buf);
- if (rc != Z_STREAM_END) {
- CFReleaseSafe(outData);
- return NULL;
- }
- return (CFDataRef)outData;
-}
-
-/* Read file opens the file, mmaps it and then closes the file. */
-int readValidFile(const char *fileName,
- CFDataRef *bytes) { // mmapped and returned -- must be munmapped!
- int rtn, fd;
- const uint8_t *buf = NULL;
- struct stat sb;
- size_t size = 0;
-
- *bytes = NULL;
- fd = open(fileName, O_RDONLY);
- if (fd < 0) { return errno; }
- rtn = fstat(fd, &sb);
- if (rtn) { goto errOut; }
- if (sb.st_size > (off_t) ((UINT32_MAX >> 1)-1)) {
- rtn = EFBIG;
- goto errOut;
- }
- size = (size_t)sb.st_size;
-
- buf = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0);
- if (!buf || buf == MAP_FAILED) {
- rtn = errno;
- secerror("unable to map %s (errno %d)", fileName, rtn);
- goto errOut;
- }
-
- *bytes = CFDataCreateWithBytesNoCopy(NULL, buf, size, kCFAllocatorNull);
-
-errOut:
- close(fd);
- if(rtn) {
- CFReleaseNull(*bytes);
- if (buf) {
- int unmap_err = munmap((void *)buf, size);
- if (unmap_err != 0) {
- secerror("unable to unmap %ld bytes at %p (error %d)", (long)size, buf, rtn);
- }
- }
- }
- return rtn;
-}
-
-static bool removeFileWithSuffix(const char *basepath, const char *suffix) {
- bool result = false;
- char *path = NULL;
- asprintf(&path, "%s%s", basepath, suffix);
- if (path) {
- if (remove(path) == -1) {
- int error = errno;
- if (error == ENOENT) {
- result = true; // not an error if the file did not exist
- } else {
- secnotice("validupdate", "remove (%s): %s", path, strerror(error));
- }
- } else {
- result = true;
- }
- free(path);
- }
- return result;
-}
-
-static CFDataRef CF_RETURNS_RETAINED createPoliciesData(CFArrayRef policies) {
- /*
- * Given an array of CFNumber values (in the range 0..127),
- * allocate and return a CFDataRef representation. Per Valid specification,
- * a zero-length array is allowed, meaning no policies are permitted.
- */
- CFIndex count = (policies) ? CFArrayGetCount(policies) : -1;
- if (count < 0 || count > 127) {
- return NULL; /* either no constraints, or far more than we expect. */
- }
- CFDataRef data = NULL;
- CFIndex length = 1 + (sizeof(int8_t) * count);
- int8_t *bytes = malloc(length);
- if (bytes) {
- int8_t *p = bytes;
- *p++ = (int8_t)(count & 0xFF);
- for (CFIndex idx = 0; idx < count; idx++) {
- int8_t pval = 0;
- CFNumberRef value = (CFNumberRef)CFArrayGetValueAtIndex(policies, idx);
- if (isNumber(value)) {
- (void)CFNumberGetValue(value, kCFNumberSInt8Type, &pval);
- }
- *p++ = pval;
- }
- data = CFDataCreate(kCFAllocatorDefault, (const UInt8*)bytes, length);
- }
- free(bytes);
- return data;
-}
-
-static CFDataRef CF_RETURNS_RETAINED cfToHexData(CFDataRef data, bool prependWildcard) {
- if (!isData(data)) { return NULL; }
- CFIndex len = CFDataGetLength(data) * 2;
- CFMutableStringRef hex = CFStringCreateMutable(NULL, len+1);
- static const char* digits[]={
- "0","1","2","3","4","5","6","7","8","9","A","B","C","D","E","F"};
- if (prependWildcard) {
- CFStringAppendCString(hex, "%", 1);
- }
- const uint8_t* p = CFDataGetBytePtr(data);
- for (CFIndex i = 0; i < CFDataGetLength(data); i++) {
- CFStringAppendCString(hex, digits[p[i] >> 4], 1);
- CFStringAppendCString(hex, digits[p[i] & 0xf], 1);
- }
- CFDataRef result = CFStringCreateExternalRepresentation(NULL, hex, kCFStringEncodingUTF8, 0);
- CFReleaseSafe(hex);
- return result;
-}
-
-static bool copyFilterComponents(CFDataRef xmlData, CFDataRef * CF_RETURNS_RETAINED xor,
- CFArrayRef * CF_RETURNS_RETAINED params) {
- /*
- The 'xmlData' parameter is a flattened XML dictionary,
- containing 'xor' and 'params' keys. First order of
- business is to reconstitute the blob into components.
- */
- bool result = false;
- CFRetainSafe(xmlData);
- CFDataRef propListData = xmlData;
- /* Expand data blob if needed */
- CFDataRef inflatedData = copyInflatedData(propListData);
- if (inflatedData) {
- CFReleaseSafe(propListData);
- propListData = inflatedData;
- }
- CFDataRef xorData = NULL;
- CFArrayRef paramsArray = NULL;
- CFPropertyListRef nto1 = CFPropertyListCreateWithData(kCFAllocatorDefault, propListData, 0, NULL, NULL);
- CFReleaseSafe(propListData);
- if (nto1) {
- xorData = (CFDataRef)CFDictionaryGetValue((CFDictionaryRef)nto1, CFSTR("xor"));
- CFRetainSafe(xorData);
- paramsArray = (CFArrayRef)CFDictionaryGetValue((CFDictionaryRef)nto1, CFSTR("params"));
- CFRetainSafe(paramsArray);
- CFReleaseSafe(nto1);
- }
- result = (xorData && paramsArray);
- if (xor) {
- *xor = xorData;
- } else {
- CFReleaseSafe(xorData);
- }
- if (params) {
- *params = paramsArray;
- } else {
- CFReleaseSafe(paramsArray);
- }
- return result;
-}
-
-// MARK: -
-// MARK: SecValidUpdate
-/*
- ==============================================================================
- SecValidUpdate
- ==============================================================================
-*/
-
-CFAbsoluteTime gUpdateStarted = 0.0;
-CFAbsoluteTime gNextUpdate = 0.0;
-static CFIndex gUpdateInterval = 0;
-static CFIndex gLastVersion = 0;
-
-/* Update Format:
- 1. The length of the signed data, as a 4-byte integer in network byte order.
- 2. The signed data, which consists of:
- a. A 4-byte integer in network byte order, the count of plists to follow; and then for each plist:
- i. A 4-byte integer, the length of each plist
- ii. A plist, in binary form
- b. There may be other data after the plists in the signed data, described by a future version of this specification.
- 3. The length of the following CMS blob, as a 4-byte integer in network byte order.
- 4. A detached CMS signature of the signed data described above.
- 5. There may be additional data after the CMS blob, described by a future version of this specification.
-
- Note: the difference between g2 and g3 format is the addition of the 4-byte count in (2a).
-*/
-static bool SecValidUpdateProcessData(SecRevocationDbConnectionRef dbc, CFIndex format, CFDataRef updateData, CFErrorRef *error) {
- bool result = false;
- if (!updateData || format < 2) {
- SecError(errSecParam, error, CFSTR("SecValidUpdateProcessData: invalid update format"));
- return result;
- }
- CFIndex version = 0;
- CFIndex interval = 0;
- const UInt8* p = CFDataGetBytePtr(updateData);
- size_t bytesRemaining = (p) ? (size_t)CFDataGetLength(updateData) : 0;
- /* make sure there is enough data to contain length and count */
- if (bytesRemaining < ((CFIndex)sizeof(uint32_t) * 2)) {
- secinfo("validupdate", "Skipping property list creation (length %ld is too short)", (long)bytesRemaining);
- SecError(errSecParam, error, CFSTR("SecValidUpdateProcessData: data length is too short"));
- return result;
- }
- /* get length of signed data */
- uint32_t dataLength = OSSwapInt32(*((uint32_t *)p));
- bytesRemaining -= sizeof(uint32_t);
- p += sizeof(uint32_t);
-
- /* get plist count (G3 format and later) */
- uint32_t plistCount = 1;
- uint32_t plistTotal = 1;
- if (format > kSecValidUpdateFormatG2) {
- plistCount = OSSwapInt32(*((uint32_t *)p));
- plistTotal = plistCount;
- bytesRemaining -= sizeof(uint32_t);
- p += sizeof(uint32_t);
- }
- if (dataLength > bytesRemaining) {
- secinfo("validupdate", "Skipping property list creation (dataLength=%ld, bytesRemaining=%ld)",
- (long)dataLength, (long)bytesRemaining);
- SecError(errSecParam, error, CFSTR("SecValidUpdateProcessData: data longer than expected"));
- return result;
- }
-
- /* process each chunked plist */
- bool ok = true;
- CFErrorRef localError = NULL;
- uint32_t plistProcessed = 0;
- while (plistCount > 0 && bytesRemaining > 0) {
- CFPropertyListRef propertyList = NULL;
- uint32_t plistLength = dataLength;
- if (format > kSecValidUpdateFormatG2) {
- plistLength = OSSwapInt32(*((uint32_t *)p));
- bytesRemaining -= sizeof(uint32_t);
- p += sizeof(uint32_t);
- }
- --plistCount;
- ++plistProcessed;
-
- if (plistLength <= bytesRemaining) {
- CFDataRef data = CFDataCreateWithBytesNoCopy(NULL, p, plistLength, kCFAllocatorNull);
- propertyList = CFPropertyListCreateWithData(NULL, data, kCFPropertyListImmutable, NULL, NULL);
- CFReleaseNull(data);
- }
- if (isDictionary(propertyList)) {
- secdebug("validupdate", "Ingesting plist chunk %u of %u, length: %u",
- plistProcessed, plistTotal, plistLength);
- CFIndex curVersion = -1;
- ok = ok && SecRevocationDbIngestUpdate(dbc, (CFDictionaryRef)propertyList, version, &curVersion, &localError);
- if (plistProcessed == 1) {
- dbc->precommitVersion = version = curVersion;
- // get server-provided interval
- CFTypeRef value = (CFNumberRef)CFDictionaryGetValue((CFDictionaryRef)propertyList,
- CFSTR("check-again"));
- if (isNumber(value)) {
- CFNumberGetValue((CFNumberRef)value, kCFNumberCFIndexType, &interval);
- }
- // get server-provided hash list
- value = (CFArrayRef)CFDictionaryGetValue((CFDictionaryRef)propertyList,
- CFSTR("hash"));
- ok = _SecRevocationDbSetHashes(dbc, (CFArrayRef)value, &localError);
- }
- if (ok && curVersion < 0) {
- plistCount = 0; // we already had this version; skip remaining plists
- result = true;
- }
- } else {
- secinfo("validupdate", "Failed to deserialize update chunk %u of %u",
- plistProcessed, plistTotal);
- SecError(errSecParam, error, CFSTR("SecValidUpdateProcessData: failed to get update chunk"));
- if (plistProcessed == 1) {
- gNextUpdate = SecRevocationDbComputeNextUpdateTime(0);
- }
- }
- /* All finished with this property list */
- CFReleaseSafe(propertyList);
-
- bytesRemaining -= plistLength;
- p += plistLength;
- }
-
- if (ok && version > 0) {
- secdebug("validupdate", "Update received: v%ld", (long)version);
- gLastVersion = version;
- gNextUpdate = SecRevocationDbComputeNextUpdateTime(interval);
- secdebug("validupdate", "Next update time: %f", gNextUpdate);
- result = true;
- }
-
- (void) CFErrorPropagate(localError, error);
- return result;
-}
-
-void SecValidUpdateVerifyAndIngest(CFDataRef updateData, CFStringRef updateServer, bool fullUpdate) {
- if (!updateData) {
- secnotice("validupdate", "invalid update data");
- return;
- }
- /* Verify CMS signature on signed data */
- if (!SecRevocationDbVerifyUpdate((void *)CFDataGetBytePtr(updateData), CFDataGetLength(updateData))) {
- secerror("failed to verify valid update");
- TrustdHealthAnalyticsLogErrorCode(TAEventValidUpdate, TAFatalError, errSecVerifyFailed);
- return;
- }
- /* Read current update source from database. */
- CFStringRef dbSource = SecRevocationDbCopyUpdateSource();
- if (dbSource && updateServer && (kCFCompareEqualTo != CFStringCompare(dbSource, updateServer,
- kCFCompareCaseInsensitive))) {
- secnotice("validupdate", "switching db source from \"%@\" to \"%@\"", dbSource, updateServer);
- }
- CFReleaseNull(dbSource);
-
- /* Ingest the update. This is now performed under a single immediate write transaction,
- so other writers are blocked (but not other readers), and the changes can be rolled back
- in their entirety if any error occurs. */
- __block bool ok = true;
- __block CFErrorRef localError = NULL;
- SecRevocationDbWith(^(SecRevocationDbRef rdb) {
- ok &= SecRevocationDbPerformWrite(rdb, &localError, ^bool(SecRevocationDbConnectionRef dbc, CFErrorRef *blockError) {
- if (fullUpdate) {
- /* Must completely replace existing database contents */
- secdebug("validupdate", "starting to process full update; clearing database");
- ok = ok && _SecRevocationDbRemoveAllEntries(dbc, blockError);
- ok = ok && _SecRevocationDbSetUpdateSource(dbc, updateServer, blockError);
- dbc->precommitVersion = 0;
- dbc->fullUpdate = true;
- }
- CFIndex startingVersion = dbc->precommitVersion;
- ok = ok && SecValidUpdateProcessData(dbc, kSecValidUpdateFormatG3, updateData, blockError);
- rdb->changed = ok && (startingVersion < dbc->precommitVersion);
- if (!ok) {
- secerror("failed to process valid update: %@", blockError ? *blockError : NULL);
- TrustdHealthAnalyticsLogErrorCode(TAEventValidUpdate, TAFatalError, errSecDecode);
- } else {
- TrustdHealthAnalyticsLogSuccess(TAEventValidUpdate);
- }
- return ok;
- });
- if (rdb->changed) {
- rdb->changed = false;
- bool verifyEnabled = false;
- CFBooleanRef value = (CFBooleanRef)CFPreferencesCopyValue(kVerifyEnabledKey,
- kSecPrefsDomain, kCFPreferencesAnyUser, kCFPreferencesCurrentHost);
- if (isBoolean(value)) {
- verifyEnabled = CFBooleanGetValue(value);
- }
- CFReleaseNull(value);
- if (verifyEnabled) {
- /* compute and verify database content hashes */
- ok = ok && SecRevocationDbPerformRead(rdb, &localError, ^bool(SecRevocationDbConnectionRef dbc, CFErrorRef *blockError) {
- ok = ok && SecRevocationDbComputeDigests(dbc, blockError);
- if (!ok) {
- /* digests failed to verify, so roll back to known-good snapshot */
- (void) SecValidUpdateForceReplaceDatabase();
- }
- return ok;
- });
- }
- /* signal other trustd instances that the database has been updated */
- notify_post(kSecRevocationDbChanged);
- }
- });
-
- /* remember next update time in case of restart (separate write transaction) */
- (void) SecRevocationDbSetNextUpdateTime(gNextUpdate, NULL);
-
- CFReleaseSafe(localError);
-}
-
-static bool SecValidUpdateForceReplaceDatabase(void) {
- __block bool result = false;
-
- // write semaphore file that we will pick up when we next launch
- WithPathInRevocationInfoDirectory(CFSTR(kSecRevocationDbReplaceFile), ^(const char *utf8String) {
- struct stat sb;
- int fd = open(utf8String, O_WRONLY | O_CREAT, DEFFILEMODE);
- if (fd == -1 || fstat(fd, &sb)) {
- secnotice("validupdate", "unable to write %s (error %d)", utf8String, errno);
- } else {
- result = true;
- }
- if (fd >= 0) {
- close(fd);
- }
- });
- if (result) {
- // exit as gracefully as possible so we can replace the database
- secnotice("validupdate", "process exiting to replace db file");
- dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 3ull*NSEC_PER_SEC), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
- xpc_transaction_exit_clean();
- });
- }
- return result;
-}
-
-static bool SecValidUpdateSatisfiedLocally(CFStringRef server, CFIndex version, bool safeToReplace) {
- __block bool result = false;
- SecOTAPKIRef otapkiRef = NULL;
- bool relaunching = false;
- static int sNumLocalUpdates = 0;
-
- // if we've replaced the database with a local asset twice in a row,
- // something is wrong with it. Get this update from the server.
- if (sNumLocalUpdates > 1) {
- secdebug("validupdate", "%d consecutive db resets, ignoring local asset", sNumLocalUpdates);
- goto updateExit;
- }
-
- // if a non-production server is specified, we will not be able to use a
- // local production asset since its update sequence will be different.
- if (kCFCompareEqualTo != CFStringCompare(server, kValidUpdateProdServer,
- kCFCompareCaseInsensitive)) {
- secdebug("validupdate", "non-production server specified, ignoring local asset");
- goto updateExit;
- }
-
- // check static database asset(s)
- otapkiRef = SecOTAPKICopyCurrentOTAPKIRef();
- if (!otapkiRef) {
- goto updateExit;
- }
- CFIndex assetVersion = SecOTAPKIGetValidSnapshotVersion(otapkiRef);
- CFIndex assetFormat = SecOTAPKIGetValidSnapshotFormat(otapkiRef);
- // version <= 0 means the database is invalid or empty.
- // version > 0 means we have some version, but we need to see if a
- // newer version is available as a local asset.
- if (assetVersion <= version || assetFormat < kSecValidUpdateFormatG3) {
- // asset is not newer than ours, or its version is unknown
- goto updateExit;
- }
-
- // replace database only if safe to do so (i.e. called at startup)
- if (!safeToReplace) {
- relaunching = SecValidUpdateForceReplaceDatabase();
- goto updateExit;
- }
-
- // try to copy uncompressed database asset, if available
- const char *validDbPathBuf = SecOTAPKIGetValidDatabaseSnapshot(otapkiRef);
- if (validDbPathBuf) {
- WithPathInRevocationInfoDirectory(CFSTR(kSecRevocationDbFileName), ^(const char *path) {
- secdebug("validupdate", "will copy data from \"%s\"", validDbPathBuf);
- copyfile_state_t state = copyfile_state_alloc();
- int retval = copyfile(validDbPathBuf, path, state, COPYFILE_DATA);
- copyfile_state_free(state);
- if (retval < 0) {
- secnotice("validupdate", "copyfile error %d", retval);
- } else {
- result = true;
- }
- });
- }
-
-updateExit:
- CFReleaseNull(otapkiRef);
- if (result) {
- sNumLocalUpdates++;
- gLastVersion = SecRevocationDbGetVersion();
- // note: snapshot should already have latest schema and production source,
- // but set it here anyway so we don't keep trying to replace the db.
- SecRevocationDbWith(^(SecRevocationDbRef db) {
- (void)SecRevocationDbSetUpdateSource(db, server);
- (void)SecRevocationDbUpdateSchema(db);
- });
- gUpdateStarted = 0;
- secdebug("validupdate", "local update to g%ld/v%ld complete at %f",
- (long)SecRevocationDbGetUpdateFormat(), (long)gLastVersion,
- (double)CFAbsoluteTimeGetCurrent());
- } else {
- sNumLocalUpdates = 0; // reset counter
- }
- if (relaunching) {
- // request is locally satisfied; don't schedule a network update
- result = true;
- }
- return result;
-}
-
-static bool SecValidUpdateSchedule(bool updateEnabled, CFStringRef server, CFIndex version) {
- /* Check if we have a later version available locally */
- if (SecValidUpdateSatisfiedLocally(server, version, false)) {
- return true;
- }
-
- /* If update not permitted return */
- if (!updateEnabled) {
- return false;
- }
-
-#if !TARGET_OS_BRIDGE
- /* Schedule as a maintenance task */
- secdebug("validupdate", "will fetch v%lu from \"%@\"", (unsigned long)version, server);
- return SecValidUpdateRequest(SecRevocationDbGetUpdateQueue(), server, version);
-#else
- return false;
-#endif
-}
-
-static CFStringRef SecRevocationDbGetDefaultServer(void) {
-#if !TARGET_OS_WATCH && !TARGET_OS_BRIDGE
- #if RC_SEED_BUILD
- CFStringRef defaultServer = kValidUpdateSeedServer;
- #else // !RC_SEED_BUILD
- CFStringRef defaultServer = kValidUpdateProdServer;
- #endif // !RC_SEED_BUILD
- if (os_variant_has_internal_diagnostics("com.apple.security")) {
- defaultServer = kValidUpdateCarryServer;
- }
- return defaultServer;
-#else // TARGET_OS_WATCH || TARGET_OS_BRIDGE
- /* Because watchOS and bridgeOS can't update over the air, we should
- * always use the prod server so that the valid database built into the
- * image is used. */
- return kValidUpdateProdServer;
-#endif
-}
-
-void SecRevocationDbInitialize() {
- if (!isDbOwner()) { return; }
- os_transaction_t transaction = os_transaction_create("com.apple.trustd.valid.initialize");
- __block bool initializeDb = false;
-
- /* create base path if it doesn't exist */
- WithPathInRevocationInfoDirectory(NULL, ^(const char *utf8String) {
- (void)mkpath_np(utf8String, 0755);
- });
-
- /* check semaphore file */
- WithPathInRevocationInfoDirectory(CFSTR(kSecRevocationDbReplaceFile), ^(const char *path) {
- struct stat sb;
- if (stat(path, &sb) == 0) {
- initializeDb = true; /* file was found, so we will replace the database */
- if (remove(path) == -1) {
- int error = errno;
- secnotice("validupdate", "remove (%s): %s", path, strerror(error));
- }
- }
- });
-
- /* check database */
- WithPathInRevocationInfoDirectory(CFSTR(kSecRevocationDbFileName), ^(const char *path) {
- if (initializeDb) {
- /* remove old database file(s) */
- (void)removeFileWithSuffix(path, "");
- (void)removeFileWithSuffix(path, "-journal");
- (void)removeFileWithSuffix(path, "-shm");
- (void)removeFileWithSuffix(path, "-wal");
- }
- else {
- struct stat sb;
- if (stat(path, &sb) == -1) {
- initializeDb = true; /* file not found, so we will create the database */
- }
- }
- });
-
- if (!initializeDb) {
- os_release(transaction);
- return; /* database exists and doesn't need replacing */
- }
-
- /* initialize database from local asset */
- CFTypeRef value = (CFStringRef)CFPreferencesCopyValue(kUpdateServerKey, kSecPrefsDomain, kCFPreferencesAnyUser, kCFPreferencesCurrentHost);
- CFStringRef server = (isString(value)) ? (CFStringRef)value : (CFStringRef)SecRevocationDbGetDefaultServer();
- CFIndex version = 0;
- secnotice("validupdate", "initializing database");
- if (!SecValidUpdateSatisfiedLocally(server, version, true)) {
-#if !TARGET_OS_BRIDGE
- /* Schedule full update as a maintenance task */
- (void)SecValidUpdateRequest(SecRevocationDbGetUpdateQueue(), server, version);
-#endif
- }
- CFReleaseSafe(value);
- os_release(transaction);
-}
-
-
-// MARK: -
-// MARK: SecValidInfoRef
-/*
- ==============================================================================
- SecValidInfoRef
- ==============================================================================
-*/
-
-CFGiblisWithCompareFor(SecValidInfo);
-
-static SecValidInfoRef SecValidInfoCreate(SecValidInfoFormat format,
- CFOptionFlags flags,
- bool isOnList,
- CFDataRef certHash,
- CFDataRef issuerHash,
- CFDataRef anchorHash,
- CFDateRef notBeforeDate,
- CFDateRef notAfterDate,
- CFDataRef nameConstraints,
- CFDataRef policyConstraints) {
- SecValidInfoRef validInfo;
- validInfo = CFTypeAllocate(SecValidInfo, struct __SecValidInfo, kCFAllocatorDefault);
- if (!validInfo) { return NULL; }
-
- CFRetainSafe(certHash);
- CFRetainSafe(issuerHash);
- CFRetainSafe(anchorHash);
- CFRetainSafe(notBeforeDate);
- CFRetainSafe(notAfterDate);
- CFRetainSafe(nameConstraints);
- CFRetainSafe(policyConstraints);
-
- validInfo->format = format;
- validInfo->certHash = certHash;
- validInfo->issuerHash = issuerHash;
- validInfo->anchorHash = anchorHash;
- validInfo->isOnList = isOnList;
- validInfo->valid = (flags & kSecValidInfoAllowlist);
- validInfo->complete = (flags & kSecValidInfoComplete);
- validInfo->checkOCSP = (flags & kSecValidInfoCheckOCSP);
- validInfo->knownOnly = (flags & kSecValidInfoKnownOnly);
- validInfo->requireCT = (flags & kSecValidInfoRequireCT);
- validInfo->noCACheck = (flags & kSecValidInfoNoCAv2Check);
- validInfo->overridable = (flags & kSecValidInfoOverridable);
- validInfo->hasDateConstraints = (flags & kSecValidInfoDateConstraints);
- validInfo->hasNameConstraints = (flags & kSecValidInfoNameConstraints);
- validInfo->hasPolicyConstraints = (flags & kSecValidInfoPolicyConstraints);
- validInfo->notBeforeDate = notBeforeDate;
- validInfo->notAfterDate = notAfterDate;
- validInfo->nameConstraints = nameConstraints;
- validInfo->policyConstraints = policyConstraints;
-
- return validInfo;
-}
-
-static void SecValidInfoDestroy(CFTypeRef cf) {
- SecValidInfoRef validInfo = (SecValidInfoRef)cf;
- if (validInfo) {
- CFReleaseNull(validInfo->certHash);
- CFReleaseNull(validInfo->issuerHash);
- CFReleaseNull(validInfo->anchorHash);
- CFReleaseNull(validInfo->notBeforeDate);
- CFReleaseNull(validInfo->notAfterDate);
- CFReleaseNull(validInfo->nameConstraints);
- CFReleaseNull(validInfo->policyConstraints);
- }
-}
-
-void SecValidInfoSetAnchor(SecValidInfoRef validInfo, SecCertificateRef anchor) {
- if (!validInfo) {
- return;
- }
- CFDataRef anchorHash = NULL;
- if (anchor) {
- anchorHash = SecCertificateCopySHA256Digest(anchor);
-
- /* clear no-ca flag for anchors where we want OCSP checked [32523118] */
- if (SecIsAppleTrustAnchor(anchor, 0)) {
- validInfo->noCACheck = false;
- }
- }
- CFReleaseNull(validInfo->anchorHash);
- validInfo->anchorHash = anchorHash;
-}
-
-static Boolean SecValidInfoCompare(CFTypeRef a, CFTypeRef b) {
- SecValidInfoRef validInfoA = (SecValidInfoRef)a;
- SecValidInfoRef validInfoB = (SecValidInfoRef)b;
- if (validInfoA == validInfoB) {
- return true;
- }
- if (!validInfoA || !validInfoB ||
- (CFGetTypeID(a) != SecValidInfoGetTypeID()) ||
- (CFGetTypeID(b) != SecValidInfoGetTypeID())) {
- return false;
- }
- return CFEqualSafe(validInfoA->certHash, validInfoB->certHash) && CFEqualSafe(validInfoA->issuerHash, validInfoB->issuerHash);
-}
-
-static CFStringRef SecValidInfoCopyFormatDescription(CFTypeRef cf, CFDictionaryRef formatOptions) {
- SecValidInfoRef validInfo = (SecValidInfoRef)cf;
- CFStringRef certHash = CFDataCopyHexString(validInfo->certHash);
- CFStringRef issuerHash = CFDataCopyHexString(validInfo->issuerHash);
- CFStringRef desc = CFStringCreateWithFormat(NULL, formatOptions, CFSTR("validInfo certHash: %@ issuerHash: %@"), certHash, issuerHash);
- CFReleaseNull(certHash);
- CFReleaseNull(issuerHash);
- return desc;
-}
-
-
-// MARK: -
-// MARK: SecRevocationDb
-/*
- ==============================================================================
- SecRevocationDb
- ==============================================================================
-*/
-
-/* SecRevocationDbCheckNextUpdate returns true if we dispatched an
- update request, otherwise false.
-*/
-static bool _SecRevocationDbCheckNextUpdate(void) {
- // are we the db owner instance?
- if (!isDbOwner()) {
- return false;
- }
- CFTypeRef value = NULL;
-
- // is it time to check?
- CFAbsoluteTime now = CFAbsoluteTimeGetCurrent();
- CFAbsoluteTime minNextUpdate = now + gUpdateInterval;
- gUpdateStarted = now;
-
- if (0 == gNextUpdate) {
- // first time we're called, check if we have a saved nextUpdate value
- gNextUpdate = SecRevocationDbGetNextUpdateTime();
- minNextUpdate = now;
- if (gNextUpdate < minNextUpdate) {
- gNextUpdate = minNextUpdate;
- }
- // allow pref to override update interval, if it exists
- CFIndex interval = -1;
- value = (CFNumberRef)CFPreferencesCopyValue(kUpdateIntervalKey, kSecPrefsDomain, kCFPreferencesAnyUser, kCFPreferencesCurrentHost);
- if (isNumber(value)) {
- if (CFNumberGetValue((CFNumberRef)value, kCFNumberCFIndexType, &interval)) {
- if (interval < kSecMinUpdateInterval) {
- interval = kSecMinUpdateInterval;
- } else if (interval > kSecMaxUpdateInterval) {
- interval = kSecMaxUpdateInterval;
- }
- }
- }
- CFReleaseNull(value);
- gUpdateInterval = kSecStdUpdateInterval;
- if (interval > 0) {
- gUpdateInterval = interval;
- }
- // pin next update time to the preferred update interval
- if (gNextUpdate > (gUpdateStarted + gUpdateInterval)) {
- gNextUpdate = gUpdateStarted + gUpdateInterval;
- }
- secdebug("validupdate", "next update at %f (in %f seconds)",
- (double)gUpdateStarted, (double)gNextUpdate-gUpdateStarted);
- }
- if (gNextUpdate > now) {
- gUpdateStarted = 0;
- return false;
- }
- secnotice("validupdate", "starting update");
-
- // set minimum next update time here in case we can't get an update
- gNextUpdate = minNextUpdate;
-
- // determine which server to query
- CFStringRef server;
- value = (CFStringRef)CFPreferencesCopyValue(kUpdateServerKey, kSecPrefsDomain, kCFPreferencesAnyUser, kCFPreferencesCurrentHost);
- if (isString(value)) {
- server = (CFStringRef) CFRetain(value);
- } else {
- server = (CFStringRef) CFRetain(SecRevocationDbGetDefaultServer());
- }
- CFReleaseNull(value);
-
- // determine version of our current database
- CFIndex version = SecRevocationDbGetVersion();
- secdebug("validupdate", "got version %ld from db", (long)version);
- if (version <= 0) {
- if (gLastVersion > 0) {
- secdebug("validupdate", "error getting version; using last good version: %ld", (long)gLastVersion);
- }
- version = gLastVersion;
- }
-
- // determine source of our current database
- // (if this ever changes, we will need to reload the db)
- CFStringRef db_source = SecRevocationDbCopyUpdateSource();
- if (!db_source) {
- db_source = (CFStringRef) CFRetain(kValidUpdateProdServer);
- }
-
- // determine whether we need to recreate the database
- CFIndex db_version = SecRevocationDbGetSchemaVersion();
- CFIndex db_format = SecRevocationDbGetUpdateFormat();
- if (db_version < kSecRevocationDbSchemaVersion ||
- db_format < kSecRevocationDbUpdateFormat ||
- kCFCompareEqualTo != CFStringCompare(server, db_source, kCFCompareCaseInsensitive)) {
- // we need to fully rebuild the db contents, so we set our version to 0.
- version = gLastVersion = 0;
- }
-
- // determine whether update fetching is enabled
-#if (__MAC_OS_X_VERSION_MIN_REQUIRED >= 101300 || __IPHONE_OS_VERSION_MIN_REQUIRED >= 110000)
- bool updateEnabled = true; // macOS 10.13 or iOS 11.0
-#else
- bool updateEnabled = false;
-#endif
- value = (CFBooleanRef)CFPreferencesCopyValue(kUpdateEnabledKey, kSecPrefsDomain, kCFPreferencesAnyUser, kCFPreferencesCurrentHost);
- if (isBoolean(value)) {
- updateEnabled = CFBooleanGetValue((CFBooleanRef)value);
- }
- CFReleaseNull(value);
-
- // Schedule maintenance work
- bool result = SecValidUpdateSchedule(updateEnabled, server, version);
- CFReleaseNull(server);
- CFReleaseNull(db_source);
- return result;
-}
-
-void SecRevocationDbCheckNextUpdate(void) {
- static dispatch_once_t once;
- static sec_action_t action;
-
- dispatch_once(&once, ^{
- dispatch_queue_t update_queue = SecRevocationDbGetUpdateQueue();
- action = sec_action_create_with_queue(update_queue, "update_check", kSecMinUpdateInterval);
- sec_action_set_handler(action, ^{
- os_transaction_t transaction = os_transaction_create("com.apple.trustd.valid.checkNextUpdate");
- (void)_SecRevocationDbCheckNextUpdate();
- os_release(transaction);
- });
- });
- sec_action_perform(action);
-}
-
-/* This function verifies an update, in this format:
- 1) unsigned 32-bit network-byte-order length of binary plist
- 2) binary plist data
- 3) unsigned 32-bit network-byte-order length of CMS message
- 4) CMS message (containing certificates and signature over binary plist)
-
- The length argument is the total size of the packed update data.
-*/
-bool SecRevocationDbVerifyUpdate(void *update, CFIndex length) {
- if (!update || length <= (CFIndex)sizeof(uint32_t)) {
- return false;
- }
- uint32_t plistLength = OSSwapInt32(*((uint32_t *)update));
- if ((plistLength + (CFIndex)(sizeof(uint32_t)*2)) > (uint64_t) length) {
- secdebug("validupdate", "ERROR: reported plist length (%lu)+%lu exceeds total length (%lu)\n",
- (unsigned long)plistLength, (unsigned long)sizeof(uint32_t)*2, (unsigned long)length);
- return false;
- }
- uint8_t *plistData = (uint8_t *)update + sizeof(uint32_t);
- uint8_t *sigData = (uint8_t *)plistData + plistLength;
- uint32_t sigLength = OSSwapInt32(*((uint32_t *)sigData));
- sigData += sizeof(uint32_t);
- if ((plistLength + sigLength + (CFIndex)(sizeof(uint32_t) * 2)) != (uint64_t) length) {
- secdebug("validupdate", "ERROR: reported lengths do not add up to total length\n");
- return false;
- }
-
- OSStatus status = 0;
- CMSSignerStatus signerStatus;
- CMSDecoderRef cms = NULL;
- SecPolicyRef policy = NULL;
- SecTrustRef trust = NULL;
- CFDataRef content = NULL;
-
- if ((content = CFDataCreateWithBytesNoCopy(kCFAllocatorDefault,
- (const UInt8 *)plistData, (CFIndex)plistLength, kCFAllocatorNull)) == NULL) {
- secdebug("validupdate", "CFDataCreateWithBytesNoCopy failed (%ld bytes)\n", (long)plistLength);
- return false;
- }
-
- if ((status = CMSDecoderCreate(&cms)) != errSecSuccess) {
- secdebug("validupdate", "CMSDecoderCreate failed with error %d\n", (int)status);
- goto verifyExit;
- }
- if ((status = CMSDecoderUpdateMessage(cms, sigData, sigLength)) != errSecSuccess) {
- secdebug("validupdate", "CMSDecoderUpdateMessage failed with error %d\n", (int)status);
- goto verifyExit;
- }
- if ((status = CMSDecoderSetDetachedContent(cms, content)) != errSecSuccess) {
- secdebug("validupdate", "CMSDecoderSetDetachedContent failed with error %d\n", (int)status);
- goto verifyExit;
- }
- if ((status = CMSDecoderFinalizeMessage(cms)) != errSecSuccess) {
- secdebug("validupdate", "CMSDecoderFinalizeMessage failed with error %d\n", (int)status);
- goto verifyExit;
- }
-
- policy = SecPolicyCreateApplePinned(CFSTR("ValidUpdate"), // kSecPolicyNameAppleValidUpdate
- CFSTR("1.2.840.113635.100.6.2.10"), // System Integration 2 Intermediate Certificate
- CFSTR("1.2.840.113635.100.6.51")); // Valid update signing OID
-
- // Check that the first signer actually signed this message.
- if ((status = CMSDecoderCopySignerStatus(cms, 0, policy,
- false, &signerStatus, &trust, NULL)) != errSecSuccess) {
- secdebug("validupdate", "CMSDecoderCopySignerStatus failed with error %d\n", (int)status);
- goto verifyExit;
- }
- // Make sure the signature verifies against the detached content
- if (signerStatus != kCMSSignerValid) {
- secdebug("validupdate", "ERROR: signature did not verify (signer status %d)\n", (int)signerStatus);
- status = errSecInvalidSignature;
- goto verifyExit;
- }
- // Make sure the signing certificate is valid for the specified policy
- SecTrustResultType trustResult = kSecTrustResultInvalid;
- status = SecTrustEvaluate(trust, &trustResult);
- if (status != errSecSuccess) {
- secdebug("validupdate", "SecTrustEvaluate failed with error %d (trust=%p)\n", (int)status, (void *)trust);
- } else if (!(trustResult == kSecTrustResultUnspecified || trustResult == kSecTrustResultProceed)) {
- secdebug("validupdate", "SecTrustEvaluate failed with trust result %d\n", (int)trustResult);
- status = errSecVerificationFailure;
- goto verifyExit;
- }
-
-verifyExit:
- CFReleaseSafe(content);
- CFReleaseSafe(trust);
- CFReleaseSafe(policy);
- CFReleaseSafe(cms);
-
- return (status == errSecSuccess);
-}
-
-CFAbsoluteTime SecRevocationDbComputeNextUpdateTime(CFIndex updateInterval) {
- CFIndex interval = updateInterval;
- // try to use interval preference if it exists
- CFTypeRef value = (CFNumberRef)CFPreferencesCopyValue(kUpdateIntervalKey, kSecPrefsDomain, kCFPreferencesAnyUser, kCFPreferencesCurrentHost);
- if (isNumber(value)) {
- CFNumberGetValue((CFNumberRef)value, kCFNumberCFIndexType, &interval);
- }
- CFReleaseNull(value);
-
- if (interval <= 0) {
- interval = kSecStdUpdateInterval;
- }
-
- // sanity check
- if (interval < kSecMinUpdateInterval) {
- interval = kSecMinUpdateInterval;
- } else if (interval > kSecMaxUpdateInterval) {
- interval = kSecMaxUpdateInterval;
- }
-
- // compute randomization factor, between 0 and 50% of the interval
- CFIndex fuzz = arc4random() % (long)(interval/2.0);
- CFAbsoluteTime nextUpdate = CFAbsoluteTimeGetCurrent() + interval + fuzz;
- secdebug("validupdate", "next update in %ld seconds", (long)(interval + fuzz));
- return nextUpdate;
-}
-
-void SecRevocationDbComputeAndSetNextUpdateTime(void) {
- gNextUpdate = SecRevocationDbComputeNextUpdateTime(0);
- (void) SecRevocationDbSetNextUpdateTime(gNextUpdate, NULL);
- gUpdateStarted = 0; /* no update is currently in progress */
-}
-
-bool SecRevocationDbIngestUpdate(SecRevocationDbConnectionRef dbc, CFDictionaryRef update, CFIndex chunkVersion, CFIndex *outVersion, CFErrorRef *error) {
- bool ok = false;
- CFIndex version = 0;
- CFErrorRef localError = NULL;
- if (!update) {
- SecError(errSecParam, &localError, CFSTR("SecRevocationDbIngestUpdate: invalid update parameter"));
- goto setVersionAndExit;
- }
- CFTypeRef value = (CFNumberRef)CFDictionaryGetValue(update, CFSTR("version"));
- if (isNumber(value)) {
- if (!CFNumberGetValue((CFNumberRef)value, kCFNumberCFIndexType, &version)) {
- version = 0;
- }
- }
- if (version == 0) {
- // only the first chunk will have a version, so the second and
- // subsequent chunks will need to pass it in chunkVersion.
- version = chunkVersion;
- }
- // check precommitted version since update hasn't been committed yet
- CFIndex curVersion = dbc->precommitVersion;
- if (version > curVersion || chunkVersion > 0) {
- ok = _SecRevocationDbApplyUpdate(dbc, update, version, &localError);
- secdebug("validupdate", "_SecRevocationDbApplyUpdate=%s, v%ld, precommit=%ld, full=%s",
- (ok) ? "1" : "0", (long)version, (long)dbc->precommitVersion,
- (dbc->fullUpdate) ? "1" : "0");
- } else {
- secdebug("validupdate", "we have v%ld, skipping update to v%ld",
- (long)curVersion, (long)version);
- version = -1; // invalid, so we know to skip subsequent chunks
- ok = true; // this is not an error condition
- }
-setVersionAndExit:
- if (outVersion) {
- *outVersion = version;
- }
- (void) CFErrorPropagate(localError, error);
- return ok;
-}
-
-
-/* Database schema */
-
-/* admin table holds these key-value (or key-ival) pairs:
- 'version' (integer) // version of database content
- 'check_again' (double) // CFAbsoluteTime of next check (optional)
- 'db_version' (integer) // version of database schema
- 'db_hash' (blob) // SHA-256 database hash
- --> entries in admin table are unique by text key
-
- issuers table holds map of issuing CA hashes to group identifiers:
- groupid (integer) // associated group identifier in group ID table
- issuer_hash (blob) // SHA-256 hash of issuer certificate (primary key)
- --> entries in issuers table are unique by issuer_hash;
- multiple issuer entries may have the same groupid!
-
- groups table holds records with these attributes:
- groupid (integer) // ordinal ID associated with this group entry
- flags (integer) // a bitmask of the following values:
- kSecValidInfoComplete (0x00000001) set if we have all revocation info for this issuer group
- kSecValidInfoCheckOCSP (0x00000002) set if must check ocsp for certs from this issuer group
- kSecValidInfoKnownOnly (0x00000004) set if any CA from this issuer group must be in database
- kSecValidInfoRequireCT (0x00000008) set if all certs from this issuer group must have SCTs
- kSecValidInfoAllowlist (0x00000010) set if this entry describes valid certs (i.e. is allowed)
- kSecValidInfoNoCACheck (0x00000020) set if this entry does not require an OCSP check to accept (deprecated)
- kSecValidInfoOverridable (0x00000040) set if the trust status is recoverable and can be overridden
- kSecValidInfoDateConstraints (0x00000080) set if this group has not-before or not-after constraints
- kSecValidInfoNameConstraints (0x00000100) set if this group has name constraints in database
- kSecValidInfoPolicyConstraints (0x00000200) set if this group has policy constraints in database
- kSecValidInfoNoCAv2Check (0x00000400) set if this entry does not require an OCSP check to accept
- format (integer) // an integer describing format of entries:
- kSecValidInfoFormatUnknown (0) unknown format
- kSecValidInfoFormatSerial (1) serial number, not greater than 20 bytes in length
- kSecValidInfoFormatSHA256 (2) SHA-256 hash, 32 bytes in length
- kSecValidInfoFormatNto1 (3) filter data blob of arbitrary length
- data (blob) // Bloom filter data if format is 'nto1', otherwise NULL
- policies (blob) // NULL, or uint8_t count value followed by array of int8_t policy values
- --> entries in groups table are unique by groupid
-
- serials table holds serial number blobs with these attributes:
- groupid (integer) // identifier for issuer group in the groups table
- serial (blob) // serial number
- --> entries in serials table are unique by serial and groupid
-
- hashes table holds SHA-256 hashes of certificates with these attributes:
- groupid (integer) // identifier for issuer group in the groups table
- sha256 (blob) // SHA-256 hash of subject certificate
- --> entries in hashes table are unique by sha256 and groupid
-
- dates table holds notBefore and notAfter dates (as CFAbsoluteTime) with these attributes:
- groupid (integer) // identifier for issuer group in the groups table (primary key)
- notbefore (real) // issued certs are invalid if their notBefore is prior to this date
- notafter (real) // issued certs are invalid after this date (or their notAfter, if earlier)
- --> entries in dates table are unique by groupid, and only exist if kSecValidInfoDateConstraints is true
-
- */
-#define createTablesSQL CFSTR("CREATE TABLE IF NOT EXISTS admin(" \
- "key TEXT PRIMARY KEY NOT NULL," \
- "ival INTEGER NOT NULL," \
- "value BLOB" \
- ");" \
- "CREATE TABLE IF NOT EXISTS issuers(" \
- "groupid INTEGER NOT NULL," \
- "issuer_hash BLOB PRIMARY KEY NOT NULL" \
- ");" \
- "CREATE INDEX IF NOT EXISTS issuer_idx ON issuers(issuer_hash);" \
- "CREATE TABLE IF NOT EXISTS groups(" \
- "groupid INTEGER PRIMARY KEY AUTOINCREMENT," \
- "flags INTEGER," \
- "format INTEGER," \
- "data BLOB," \
- "policies BLOB" \
- ");" \
- "CREATE TABLE IF NOT EXISTS serials(" \
- "groupid INTEGER NOT NULL," \
- "serial BLOB NOT NULL," \
- "UNIQUE(groupid,serial)" \
- ");" \
- "CREATE TABLE IF NOT EXISTS hashes(" \
- "groupid INTEGER NOT NULL," \
- "sha256 BLOB NOT NULL," \
- "UNIQUE(groupid,sha256)" \
- ");" \
- "CREATE TABLE IF NOT EXISTS dates(" \
- "groupid INTEGER PRIMARY KEY NOT NULL," \
- "notbefore REAL," \
- "notafter REAL" \
- ");" \
- "CREATE TRIGGER IF NOT EXISTS group_del " \
- "BEFORE DELETE ON groups FOR EACH ROW " \
- "BEGIN " \
- "DELETE FROM serials WHERE groupid=OLD.groupid; " \
- "DELETE FROM hashes WHERE groupid=OLD.groupid; " \
- "DELETE FROM issuers WHERE groupid=OLD.groupid; " \
- "DELETE FROM dates WHERE groupid=OLD.groupid; " \
- "END;")
-
-#define selectGroupIdSQL CFSTR("SELECT DISTINCT groupid " \
- "FROM issuers WHERE issuer_hash=?")
-#define selectVersionSQL CFSTR("SELECT ival FROM admin " \
- "WHERE key='version'")
-#define selectDbVersionSQL CFSTR("SELECT ival FROM admin " \
- "WHERE key='db_version'")
-#define selectDbFormatSQL CFSTR("SELECT ival FROM admin " \
- "WHERE key='db_format'")
-#define selectDbHashSQL CFSTR("SELECT value FROM admin " \
- "WHERE key='db_hash'")
-#define selectDbSourceSQL CFSTR("SELECT value FROM admin " \
- "WHERE key='db_source'")
-#define selectNextUpdateSQL CFSTR("SELECT value FROM admin " \
- "WHERE key='check_again'")
-#define selectUpdateIntervalSQL CFSTR("SELECT ival FROM admin " \
- "WHERE key='interval'")
-#define selectGroupRecordSQL CFSTR("SELECT flags,format,data,policies " \
- "FROM groups WHERE groupid=?")
-#define selectSerialRecordSQL CFSTR("SELECT rowid FROM serials " \
- "WHERE groupid=? AND serial=?")
-#define selectDateRecordSQL CFSTR("SELECT notbefore,notafter FROM " \
- "dates WHERE groupid=?")
-#define selectHashRecordSQL CFSTR("SELECT rowid FROM hashes " \
- "WHERE groupid=? AND sha256=?")
-#define insertAdminRecordSQL CFSTR("INSERT OR REPLACE INTO admin " \
- "(key,ival,value) VALUES (?,?,?)")
-#define insertIssuerRecordSQL CFSTR("INSERT OR REPLACE INTO issuers " \
- "(groupid,issuer_hash) VALUES (?,?)")
-#define insertGroupRecordSQL CFSTR("INSERT OR REPLACE INTO groups " \
- "(groupid,flags,format,data,policies) VALUES (?,?,?,?,?)")
-#define insertSerialRecordSQL CFSTR("INSERT OR REPLACE INTO serials " \
- "(groupid,serial) VALUES (?,?)")
-#define deleteSerialRecordSQL CFSTR("DELETE FROM serials " \
- "WHERE groupid=? AND hex(serial) LIKE ?")
-#define insertSha256RecordSQL CFSTR("INSERT OR REPLACE INTO hashes " \
- "(groupid,sha256) VALUES (?,?)")
-#define deleteSha256RecordSQL CFSTR("DELETE FROM hashes " \
- "WHERE groupid=? AND hex(sha256) LIKE ?")
-#define insertDateRecordSQL CFSTR("INSERT OR REPLACE INTO dates " \
- "(groupid,notbefore,notafter) VALUES (?,?,?)")
-#define deleteGroupRecordSQL CFSTR("DELETE FROM groups " \
- "WHERE groupid=?")
-#define deleteGroupIssuersSQL CFSTR("DELETE FROM issuers " \
- "WHERE groupid=?")
-#define addPoliciesColumnSQL CFSTR("ALTER TABLE groups " \
- "ADD COLUMN policies BLOB")
-#define updateGroupPoliciesSQL CFSTR("UPDATE OR IGNORE groups " \
- "SET policies=? WHERE groupid=?")
-
-#define updateConstraintsTablesSQL CFSTR("" \
-"CREATE TABLE IF NOT EXISTS dates(" \
- "groupid INTEGER PRIMARY KEY NOT NULL," \
- "notbefore REAL," \
- "notafter REAL" \
-");")
-
-#define updateGroupDeleteTriggerSQL CFSTR("" \
- "DROP TRIGGER IF EXISTS group_del;" \
- "CREATE TRIGGER group_del BEFORE DELETE ON groups FOR EACH ROW " \
- "BEGIN " \
- "DELETE FROM serials WHERE groupid=OLD.groupid; " \
- "DELETE FROM hashes WHERE groupid=OLD.groupid; " \
- "DELETE FROM issuers WHERE groupid=OLD.groupid; " \
- "DELETE FROM dates WHERE groupid=OLD.groupid; " \
- "END;")
-
-#define deleteAllEntriesSQL CFSTR("" \
- "DELETE FROM groups; " \
- "DELETE FROM admin WHERE key='version'; " \
- "DELETE FROM sqlite_sequence")
-
-
-/* Database management */
-
-static SecDbRef SecRevocationDbCreate(CFStringRef path) {
- /* only the db owner should open a read-write connection. */
- __block bool readWrite = isDbOwner();
- mode_t mode = 0644;
-
- SecDbRef result = SecDbCreate(path, mode, readWrite, false, true, true, 1, ^bool (SecDbRef db, SecDbConnectionRef dbconn, bool didCreate, bool *callMeAgainForNextConnection, CFErrorRef *error) {
- __block bool ok = true;
- if (readWrite) {
- ok &= SecDbTransaction(dbconn, kSecDbExclusiveTransactionType, error, ^(bool *commit) {
- /* Create all database tables, indexes, and triggers.
- * SecDbOpen will set auto_vacuum and journal_mode for us before we get called back.*/
- ok = ok && SecDbExec(dbconn, createTablesSQL, error);
- *commit = ok;
- });
- }
- if (!ok || (error && *error)) {
- CFIndex errCode = errSecInternalComponent;
- if (error && *error) {
- errCode = CFErrorGetCode(*error);
- }
- secerror("%s failed: %@", didCreate ? "Create" : "Open", error ? *error : NULL);
- TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb, TAOperationCreate, TAFatalError, errCode);
- }
- return ok;
- });
-
- return result;
-}
-
-static dispatch_once_t kSecRevocationDbOnce;
-static SecRevocationDbRef kSecRevocationDb = NULL;
-
-static SecRevocationDbRef SecRevocationDbInit(CFStringRef db_name) {
- SecRevocationDbRef rdb;
- dispatch_queue_attr_t attr;
-
- require(rdb = (SecRevocationDbRef)malloc(sizeof(struct __SecRevocationDb)), errOut);
- rdb->db = NULL;
- rdb->update_queue = NULL;
- rdb->updateInProgress = false;
- rdb->unsupportedVersion = false;
- rdb->changed = false;
-
- require(rdb->db = SecRevocationDbCreate(db_name), errOut);
- attr = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_BACKGROUND, 0);
- attr = dispatch_queue_attr_make_with_autorelease_frequency(attr, DISPATCH_AUTORELEASE_FREQUENCY_WORK_ITEM);
- require(rdb->update_queue = dispatch_queue_create(NULL, attr), errOut);
- require(rdb->info_cache_list = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks), errOut);
- require(rdb->info_cache = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks), errOut);
- rdb->info_cache_lock = OS_UNFAIR_LOCK_INIT;
-
- if (!isDbOwner()) {
- /* register for changes signaled by the db owner instance */
- int out_token = 0;
- notify_register_dispatch(kSecRevocationDbChanged, &out_token, rdb->update_queue, ^(int __unused token) {
- secnotice("validupdate", "Got notification of database change");
- SecRevocationDbResetCaches();
- });
- }
- return rdb;
-
-errOut:
- secdebug("validupdate", "Failed to create db at \"%@\"", db_name);
- if (rdb) {
- if (rdb->update_queue) {
- dispatch_release(rdb->update_queue);
- }
- CFReleaseSafe(rdb->db);
- free(rdb);
- }
- return NULL;
-}
-
-static CFStringRef SecRevocationDbCopyPath(void) {
- CFURLRef revDbURL = NULL;
- CFStringRef revInfoRelPath = NULL;
- if ((revInfoRelPath = CFStringCreateWithFormat(NULL, NULL, CFSTR("%s"), kSecRevocationDbFileName)) != NULL) {
- revDbURL = SecCopyURLForFileInRevocationInfoDirectory(revInfoRelPath);
- }
- CFReleaseSafe(revInfoRelPath);
-
- CFStringRef revDbPath = NULL;
- if (revDbURL) {
- revDbPath = CFURLCopyFileSystemPath(revDbURL, kCFURLPOSIXPathStyle);
- CFRelease(revDbURL);
- }
- return revDbPath;
-}
-
-static void SecRevocationDbWith(void(^dbJob)(SecRevocationDbRef db)) {
- dispatch_once(&kSecRevocationDbOnce, ^{
- CFStringRef dbPath = SecRevocationDbCopyPath();
- if (dbPath) {
- kSecRevocationDb = SecRevocationDbInit(dbPath);
- CFRelease(dbPath);
- if (kSecRevocationDb && isDbOwner()) {
- /* check and update schema immediately after database is opened */
- SecRevocationDbUpdateSchema(kSecRevocationDb);
- }
- }
- });
- // Do pre job run work here (cancel idle timers etc.)
- if (kSecRevocationDb->updateInProgress) {
- return; // this would block since SecDb has an exclusive transaction lock
- }
- dbJob(kSecRevocationDb);
- // Do post job run work here (gc timer, etc.)
-}
-
-static bool SecRevocationDbPerformWrite(SecRevocationDbRef rdb, CFErrorRef *error,
- bool(^writeJob)(SecRevocationDbConnectionRef dbc, CFErrorRef *blockError)) {
- __block bool ok = true;
- __block CFErrorRef localError = NULL;
-
- ok &= SecDbPerformWrite(rdb->db, &localError, ^(SecDbConnectionRef dbconn) {
- ok &= SecDbTransaction(dbconn, kSecDbImmediateTransactionType, &localError, ^(bool *commit) {
- SecRevocationDbConnectionRef dbc = SecRevocationDbConnectionInit(rdb, dbconn, &localError);
- ok = ok && writeJob(dbc, &localError);
- *commit = ok;
- free(dbc);
- });
- });
- ok &= CFErrorPropagate(localError, error);
- return ok;
-}
-
-static bool SecRevocationDbPerformRead(SecRevocationDbRef rdb, CFErrorRef *error,
- bool(^readJob)(SecRevocationDbConnectionRef dbc, CFErrorRef *blockError)) {
- __block CFErrorRef localError = NULL;
- __block bool ok = true;
-
- ok &= SecDbPerformRead(rdb->db, &localError, ^(SecDbConnectionRef dbconn) {
- SecRevocationDbConnectionRef dbc = SecRevocationDbConnectionInit(rdb, dbconn, &localError);
- ok = ok && readJob(dbc, &localError);
- free(dbc);
- });
- ok &= CFErrorPropagate(localError, error);
- return ok;
-}
-
-static SecRevocationDbConnectionRef SecRevocationDbConnectionInit(SecRevocationDbRef db, SecDbConnectionRef dbconn, CFErrorRef *error) {
- SecRevocationDbConnectionRef dbc = NULL;
- CFErrorRef localError = NULL;
-
- dbc = (SecRevocationDbConnectionRef)malloc(sizeof(struct __SecRevocationDbConnection));
- if (dbc) {
- dbc->db = db;
- dbc->dbconn = dbconn;
- dbc->precommitVersion = (CFIndex)_SecRevocationDbGetVersion(dbc, &localError);
- dbc->precommitDbVersion = (CFIndex)_SecRevocationDbGetSchemaVersion(db, dbc, &localError);
- dbc->precommitInterval = 0; /* set only if we are explicitly given a new value */
- dbc->fullUpdate = false;
- }
- (void) CFErrorPropagate(localError, error);
- return dbc;
-}
-
-static CF_RETURNS_RETAINED CFDataRef createCacheKey(CFDataRef certHash, CFDataRef issuerHash) {
- CFMutableDataRef concat = CFDataCreateMutableCopy(NULL, 0, certHash);
- CFDataAppend(concat, issuerHash);
- CFDataRef result = SecSHA256DigestCreateFromData(NULL, concat);
- CFReleaseNull(concat);
- return result;
-}
-
-static CF_RETURNS_RETAINED SecValidInfoRef SecRevocationDbCacheRead(SecRevocationDbRef db,
- SecCertificateRef certificate,
- CFDataRef issuerHash) {
- if (!db) {
- return NULL;
- }
- SecValidInfoRef result = NULL;
- if (!db || !db->info_cache || !db->info_cache_list) {
- return result;
- }
- CFIndex ix = kCFNotFound;
- CFDataRef certHash = SecCertificateCopySHA256Digest(certificate);
- CFDataRef cacheKey = createCacheKey(certHash, issuerHash);
-
- os_unfair_lock_lock(&db->info_cache_lock); // grab the cache lock before using the cache
- if (0 <= (ix = CFArrayGetFirstIndexOfValue(db->info_cache_list,
- CFRangeMake(0, CFArrayGetCount(db->info_cache_list)),
- cacheKey))) {
- result = (SecValidInfoRef)CFDictionaryGetValue(db->info_cache, cacheKey);
- // Verify this really is the right result
- if (CFEqualSafe(result->certHash, certHash) && CFEqualSafe(result->issuerHash, issuerHash)) {
- // Cache hit. Move the entry to the bottom of the list.
- CFArrayRemoveValueAtIndex(db->info_cache_list, ix);
- CFArrayAppendValue(db->info_cache_list, cacheKey);
- secdebug("validcache", "cache hit: %@", cacheKey);
- } else {
- // Just remove this bad entry
- CFArrayRemoveValueAtIndex(db->info_cache_list, ix);
- CFDictionaryRemoveValue(db->info_cache, cacheKey);
- secdebug("validcache", "cache remove bad: %@", cacheKey);
- secnotice("validcache", "found a bad valid info cache entry at %ld", (long)ix);
- }
- }
- CFRetainSafe(result);
- os_unfair_lock_unlock(&db->info_cache_lock);
- CFReleaseSafe(certHash);
- CFReleaseSafe(cacheKey);
- return result;
-}
-
-static void SecRevocationDbCacheWrite(SecRevocationDbRef db,
- SecValidInfoRef validInfo) {
- if (!db || !validInfo || !db->info_cache || !db->info_cache_list) {
- return;
- }
-
- CFDataRef cacheKey = createCacheKey(validInfo->certHash, validInfo->issuerHash);
-
- os_unfair_lock_lock(&db->info_cache_lock); // grab the cache lock before using the cache
- // check to make sure another thread didn't add this entry to the cache already
- if (0 > CFArrayGetFirstIndexOfValue(db->info_cache_list,
- CFRangeMake(0, CFArrayGetCount(db->info_cache_list)),
- cacheKey)) {
- CFDictionaryAddValue(db->info_cache, cacheKey, validInfo);
- if (kSecRevocationDbCacheSize <= CFArrayGetCount(db->info_cache_list)) {
- // Remove least recently used cache entry.
- secdebug("validcache", "cache remove stale: %@", CFArrayGetValueAtIndex(db->info_cache_list, 0));
- CFDictionaryRemoveValue(db->info_cache, CFArrayGetValueAtIndex(db->info_cache_list, 0));
- CFArrayRemoveValueAtIndex(db->info_cache_list, 0);
- }
- CFArrayAppendValue(db->info_cache_list, cacheKey);
- secdebug("validcache", "cache add: %@", cacheKey);
- }
- os_unfair_lock_unlock(&db->info_cache_lock);
- CFReleaseNull(cacheKey);
-}
-
-static void SecRevocationDbCachePurge(SecRevocationDbRef db) {
- if (!db || !db->info_cache || !db->info_cache_list) {
- return;
- }
-
- /* grab the cache lock and clear all entries */
- os_unfair_lock_lock(&db->info_cache_lock);
- CFArrayRemoveAllValues(db->info_cache_list);
- CFDictionaryRemoveAllValues(db->info_cache);
- secdebug("validcache", "cache purge");
- os_unfair_lock_unlock(&db->info_cache_lock);
-}
-
-static int64_t _SecRevocationDbGetUpdateInterval(SecRevocationDbConnectionRef dbc, CFErrorRef *error) {
- /* look up interval entry in admin table; returns -1 on error */
- __block int64_t interval = -1;
- __block bool ok = true;
- __block CFErrorRef localError = NULL;
-
- ok = ok && SecDbWithSQL(dbc->dbconn, selectUpdateIntervalSQL, &localError, ^bool(sqlite3_stmt *selectInterval) {
- ok = ok && SecDbStep(dbc->dbconn, selectInterval, &localError, ^void(bool *stop) {
- interval = sqlite3_column_int64(selectInterval, 0);
- *stop = true;
- });
- return ok;
- });
- if (!ok || localError) {
- secerror("_SecRevocationDbGetUpdateInterval failed: %@", localError);
- TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb, TAOperationRead, TAFatalError,
- localError ? CFErrorGetCode(localError) : errSecInternalComponent);
- }
- (void) CFErrorPropagate(localError, error);
- return interval;
-}
-
-static bool _SecRevocationDbSetUpdateInterval(SecRevocationDbConnectionRef dbc, int64_t interval, CFErrorRef *error) {
- secdebug("validupdate", "setting interval to %lld", interval);
-
- __block CFErrorRef localError = NULL;
- __block bool ok = (dbc != NULL);
- ok = ok && SecDbWithSQL(dbc->dbconn, insertAdminRecordSQL, &localError, ^bool(sqlite3_stmt *insertInterval) {
- const char *intervalKey = "interval";
- ok = ok && SecDbBindText(insertInterval, 1, intervalKey, strlen(intervalKey),
- SQLITE_TRANSIENT, &localError);
- ok = ok && SecDbBindInt64(insertInterval, 2,
- (sqlite3_int64)interval, &localError);
- ok = ok && SecDbStep(dbc->dbconn, insertInterval, &localError, NULL);
- return ok;
- });
- if (!ok || localError) {
- secerror("_SecRevocationDbSetUpdateInterval failed: %@", localError);
- TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb, TAOperationWrite, TAFatalError,
- localError ? CFErrorGetCode(localError) : errSecInternalComponent);
- }
- (void) CFErrorPropagate(localError, error);
- return ok;
-}
-
-static CFArrayRef _SecRevocationDbCopyHashes(SecRevocationDbConnectionRef dbc, CFErrorRef *error) {
- /* return a retained copy of the db_hash array stored in the admin table; or NULL on error */
- __block CFMutableArrayRef hashes = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
- __block bool ok = (dbc && hashes);
- __block CFErrorRef localError = NULL;
-
- ok = ok && SecDbWithSQL(dbc->dbconn, selectDbHashSQL, &localError, ^bool(sqlite3_stmt *selectDbHash) {
- ok = ok && SecDbStep(dbc->dbconn, selectDbHash, &localError, ^void(bool *stop) {
- uint8_t *p = (uint8_t *)sqlite3_column_blob(selectDbHash, 0);
- uint64_t len = sqlite3_column_bytes(selectDbHash, 0);
- CFIndex hashLen = CC_SHA256_DIGEST_LENGTH;
- while (p && len >= (uint64_t)hashLen) {
- CFDataRef hash = CFDataCreate(kCFAllocatorDefault, (const UInt8 *)p, hashLen);
- if (hash) {
- CFArrayAppendValue(hashes, hash);
- CFReleaseNull(hash);
- }
- len -= hashLen;
- p += hashLen;
- }
- *stop = true;
- });
- return ok;
- });
- if (!ok || localError) {
- CFReleaseNull(hashes);
- }
- (void) CFErrorPropagate(localError, error);
- return hashes;
-}
-
-static bool _SecRevocationDbSetHashes(SecRevocationDbConnectionRef dbc, CFArrayRef hashes, CFErrorRef *error) {
- /* flatten and store db_hash array in the admin table */
- __block CFErrorRef localError = NULL;
- __block bool ok = (dbc && hashes);
-
- ok = ok && SecDbWithSQL(dbc->dbconn, insertAdminRecordSQL, &localError, ^bool(sqlite3_stmt *insertHashes) {
- CFIndex count = CFArrayGetCount(hashes);
- CFIndex hashLen = CC_SHA256_DIGEST_LENGTH, dataLen = hashLen * count;
- uint8_t *dataPtr = (uint8_t *)calloc(dataLen, 1);
- uint8_t *p = dataPtr;
- for (CFIndex idx = 0; idx < count && p; idx++) {
- CFDataRef hash = CFArrayGetValueAtIndex(hashes, idx);
- uint8_t *h = (hash) ? (uint8_t *)CFDataGetBytePtr(hash) : NULL;
- if (h && CFDataGetLength(hash) == hashLen) { memcpy(p, h, hashLen); }
- p += hashLen;
- }
- const char *hashKey = "db_hash";
- ok = ok && SecDbBindText(insertHashes, 1, hashKey, strlen(hashKey),
- SQLITE_TRANSIENT, &localError);
- ok = ok && SecDbBindInt64(insertHashes, 2,
- (sqlite3_int64)0, &localError);
- ok = ok && SecDbBindBlob(insertHashes, 3,
- dataPtr, dataLen,
- SQLITE_TRANSIENT, &localError);
- ok = ok && SecDbStep(dbc->dbconn, insertHashes, &localError, NULL);
- free(dataPtr);
- return ok;
- });
- if (!ok || localError) {
- }
- (void) CFErrorPropagate(localError, error);
- return ok;
-}
-
-static int64_t _SecRevocationDbGetVersion(SecRevocationDbConnectionRef dbc, CFErrorRef *error) {
- /* look up version entry in admin table; returns -1 on error */
- __block int64_t version = -1;
- __block bool ok = (dbc != NULL);
- __block CFErrorRef localError = NULL;
-
- ok = ok && SecDbWithSQL(dbc->dbconn, selectVersionSQL, &localError, ^bool(sqlite3_stmt *selectVersion) {
- ok = ok && SecDbStep(dbc->dbconn, selectVersion, &localError, ^void(bool *stop) {
- version = sqlite3_column_int64(selectVersion, 0);
- *stop = true;
- });
- return ok;
- });
- if (!ok || localError) {
- secerror("_SecRevocationDbGetVersion failed: %@", localError);
- TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb, TAOperationRead, TAFatalError,
- localError ? CFErrorGetCode(localError) : errSecInternalComponent);
- }
- (void) CFErrorPropagate(localError, error);
- return version;
-}
-
-static bool _SecRevocationDbSetVersion(SecRevocationDbConnectionRef dbc, CFIndex version, CFErrorRef *error) {
- secdebug("validupdate", "setting version to %ld", (long)version);
-
- __block CFErrorRef localError = NULL;
- __block bool ok = (dbc != NULL);
- ok = ok && SecDbWithSQL(dbc->dbconn, insertAdminRecordSQL, &localError, ^bool(sqlite3_stmt *insertVersion) {
- const char *versionKey = "version";
- ok = ok && SecDbBindText(insertVersion, 1, versionKey, strlen(versionKey),
- SQLITE_TRANSIENT, &localError);
- ok = ok && SecDbBindInt64(insertVersion, 2,
- (sqlite3_int64)version, &localError);
- ok = ok && SecDbStep(dbc->dbconn, insertVersion, &localError, NULL);
- return ok;
- });
- if (!ok || localError) {
- secerror("_SecRevocationDbSetVersion failed: %@", localError);
- TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb, TAOperationWrite, TAFatalError,
- localError ? CFErrorGetCode(localError) : errSecInternalComponent);
- }
- (void) CFErrorPropagate(localError, error);
- return ok;
-}
-
-static int64_t _SecRevocationDbReadSchemaVersionFromDb(SecRevocationDbConnectionRef dbc, CFErrorRef *error) {
- /* look up db_version entry in admin table; returns -1 on error */
- __block int64_t db_version = -1;
- __block bool ok = (dbc != NULL);
- __block CFErrorRef localError = NULL;
-
- ok = ok && SecDbWithSQL(dbc->dbconn, selectDbVersionSQL, &localError, ^bool(sqlite3_stmt *selectDbVersion) {
- ok = ok && SecDbStep(dbc->dbconn, selectDbVersion, &localError, ^void(bool *stop) {
- db_version = sqlite3_column_int64(selectDbVersion, 0);
- *stop = true;
- });
- return ok;
- });
- if (!ok || localError) {
- secerror("_SecRevocationDbReadSchemaVersionFromDb failed: %@", localError);
- TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb, TAOperationRead, TAFatalError,
- localError ? CFErrorGetCode(localError) : errSecInternalComponent);
- }
- (void) CFErrorPropagate(localError, error);
- return db_version;
-}
-
-static _Atomic int64_t gSchemaVersion = -1;
-static int64_t _SecRevocationDbGetSchemaVersion(SecRevocationDbRef rdb, SecRevocationDbConnectionRef dbc, CFErrorRef *error) {
- static dispatch_once_t onceToken;
- dispatch_once(&onceToken, ^{
- if (dbc) {
- atomic_init(&gSchemaVersion, _SecRevocationDbReadSchemaVersionFromDb(dbc, error));
- } else {
- (void) SecRevocationDbPerformRead(rdb, error, ^bool(SecRevocationDbConnectionRef dbc, CFErrorRef *blockError) {
- atomic_init(&gSchemaVersion, _SecRevocationDbReadSchemaVersionFromDb(dbc, blockError));
- return true;
- });
- }
- });
- if (atomic_load(&gSchemaVersion) == -1) {
- /* Initial read(s) failed. Try to read the schema version again. */
- if (dbc) {
- atomic_store(&gSchemaVersion, _SecRevocationDbReadSchemaVersionFromDb(dbc, error));
- } else {
- (void) SecRevocationDbPerformRead(rdb, error, ^bool(SecRevocationDbConnectionRef dbc, CFErrorRef *blockError) {
- atomic_store(&gSchemaVersion, _SecRevocationDbReadSchemaVersionFromDb(dbc, blockError));
- return true;
- });
- }
- }
- return atomic_load(&gSchemaVersion);
-}
-
-static void SecRevocationDbResetCaches(void) {
- SecRevocationDbWith(^(SecRevocationDbRef db) {
- db->unsupportedVersion = false;
- db->changed = false;
- (void) SecRevocationDbPerformRead(db, NULL, ^bool(SecRevocationDbConnectionRef dbc, CFErrorRef *blockError) {
- atomic_store(&gSchemaVersion, _SecRevocationDbReadSchemaVersionFromDb(dbc, blockError));
- return true;
- });
- SecRevocationDbCachePurge(db);
- });
-}
-
-static bool _SecRevocationDbSetSchemaVersion(SecRevocationDbConnectionRef dbc, CFIndex dbversion, CFErrorRef *error) {
- if (dbversion > 0) {
- int64_t db_version = (dbc) ? dbc->precommitDbVersion : -1;
- if (db_version >= dbversion) {
- return true; /* requested schema is earlier than current schema */
- }
- }
- secdebug("validupdate", "setting db_version to %ld", (long)dbversion);
-
- __block CFErrorRef localError = NULL;
- __block bool ok = (dbc != NULL);
- ok = ok && SecDbWithSQL(dbc->dbconn, insertAdminRecordSQL, &localError, ^bool(sqlite3_stmt *insertDbVersion) {
- const char *dbVersionKey = "db_version";
- ok = ok && SecDbBindText(insertDbVersion, 1, dbVersionKey, strlen(dbVersionKey),
- SQLITE_TRANSIENT, &localError);
- ok = ok && SecDbBindInt64(insertDbVersion, 2,
- (sqlite3_int64)dbversion, &localError);
- ok = ok && SecDbStep(dbc->dbconn, insertDbVersion, &localError, NULL);
- return ok;
- });
- if (!ok || localError) {
- secerror("_SecRevocationDbSetSchemaVersion failed: %@", localError);
- TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb, TAOperationWrite, TAFatalError,
- localError ? CFErrorGetCode(localError) : errSecInternalComponent);
- } else {
- dbc->db->changed = true; /* will notify clients of this change */
- dbc->db->unsupportedVersion = false;
- dbc->precommitDbVersion = dbversion;
- atomic_store(&gSchemaVersion, (int64_t)dbversion);
- }
- CFReleaseSafe(localError);
- return ok;
-}
-
-static bool _SecRevocationDbUpdateSchema(SecRevocationDbConnectionRef dbc, CFErrorRef *error) {
- __block CFErrorRef localError = NULL;
- __block bool ok = (dbc != NULL);
- __block int64_t db_version = (dbc) ? dbc->precommitDbVersion : 0;
- if (db_version >= kSecRevocationDbSchemaVersion) {
- return ok; /* schema version already up to date */
- }
- secdebug("validupdate", "updating db schema from v%lld to v%lld",
- (long long)db_version, (long long)kSecRevocationDbSchemaVersion);
-
- if (ok && db_version < 5) {
- /* apply v5 changes (add dates table and replace trigger) */
- ok &= SecDbWithSQL(dbc->dbconn, updateConstraintsTablesSQL, &localError, ^bool(sqlite3_stmt *updateTables) {
- ok = SecDbStep(dbc->dbconn, updateTables, &localError, NULL);
- return ok;
- });
- ok &= SecDbWithSQL(dbc->dbconn, updateGroupDeleteTriggerSQL, &localError, ^bool(sqlite3_stmt *updateTrigger) {
- ok = SecDbStep(dbc->dbconn, updateTrigger, &localError, NULL);
- return ok;
- });
- secdebug("validupdate", "applied schema update to v5 (%s)", (ok) ? "ok" : "failed!");
- }
- if (ok && db_version < 6) {
- /* apply v6 changes (the SecDb layer will update autovacuum mode if needed, so we don't execute
- any SQL here, but we do want the database to be replaced in case transaction scope problems
- with earlier versions caused missing entries.) */
- secdebug("validupdate", "applied schema update to v6 (%s)", (ok) ? "ok" : "failed!");
- if (db_version > 0) {
- SecValidUpdateForceReplaceDatabase();
- }
- }
- if (ok && db_version < 7) {
- /* apply v7 changes (add policies column in groups table) */
- ok &= SecDbWithSQL(dbc->dbconn, addPoliciesColumnSQL, &localError, ^bool(sqlite3_stmt *addPoliciesColumn) {
- ok = SecDbStep(dbc->dbconn, addPoliciesColumn, &localError, NULL);
- return ok;
- });
- secdebug("validupdate", "applied schema update to v7 (%s)", (ok) ? "ok" : "failed!");
- }
-
- if (!ok) {
- secerror("_SecRevocationDbUpdateSchema failed: %@", localError);
- } else {
- ok = ok && _SecRevocationDbSetSchemaVersion(dbc, kSecRevocationDbSchemaVersion, &localError);
- }
- (void) CFErrorPropagate(localError, error);
- return ok;
-}
-
-bool SecRevocationDbUpdateSchema(SecRevocationDbRef rdb) {
- /* note: this function assumes it is called only by the database owner.
- non-owner (read-only) clients will fail if changes to the db are needed. */
- if (!rdb || !rdb->db) {
- return false;
- }
- __block bool ok = true;
- __block CFErrorRef localError = NULL;
- ok &= SecRevocationDbPerformWrite(rdb, &localError, ^bool(SecRevocationDbConnectionRef dbc, CFErrorRef *blockError) {
- return _SecRevocationDbUpdateSchema(dbc, blockError);
- });
- CFReleaseSafe(localError);
- return ok;
-}
-
-static int64_t _SecRevocationDbGetUpdateFormat(SecRevocationDbConnectionRef dbc, CFErrorRef *error) {
- /* look up db_format entry in admin table; returns -1 on error */
- __block int64_t db_format = -1;
- __block bool ok = (dbc != NULL);
- __block CFErrorRef localError = NULL;
-
- ok = ok && SecDbWithSQL(dbc->dbconn, selectDbFormatSQL, &localError, ^bool(sqlite3_stmt *selectDbFormat) {
- ok &= SecDbStep(dbc->dbconn, selectDbFormat, &localError, ^void(bool *stop) {
- db_format = sqlite3_column_int64(selectDbFormat, 0);
- *stop = true;
- });
- return ok;
- });
- if (!ok || localError) {
- secerror("_SecRevocationDbGetUpdateFormat failed: %@", localError);
- TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb, TAOperationRead, TAFatalError,
- localError ? CFErrorGetCode(localError) : errSecInternalComponent);
- }
- (void) CFErrorPropagate(localError, error);
- return db_format;
-}
-
-static bool _SecRevocationDbSetUpdateFormat(SecRevocationDbConnectionRef dbc, CFIndex dbformat, CFErrorRef *error) {
- secdebug("validupdate", "setting db_format to %ld", (long)dbformat);
-
- __block CFErrorRef localError = NULL;
- __block bool ok = (dbc != NULL);
- ok = ok && SecDbWithSQL(dbc->dbconn, insertAdminRecordSQL, &localError, ^bool(sqlite3_stmt *insertDbFormat) {
- const char *dbFormatKey = "db_format";
- ok = ok && SecDbBindText(insertDbFormat, 1, dbFormatKey, strlen(dbFormatKey),
- SQLITE_TRANSIENT, &localError);
- ok = ok && SecDbBindInt64(insertDbFormat, 2,
- (sqlite3_int64)dbformat, &localError);
- ok = ok && SecDbStep(dbc->dbconn, insertDbFormat, &localError, NULL);
- return ok;
- });
- if (!ok || localError) {
- secerror("_SecRevocationDbSetUpdateFormat failed: %@", localError);
- TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb, TAOperationWrite, TAFatalError,
- localError ? CFErrorGetCode(localError) : errSecInternalComponent);
- } else {
- dbc->db->changed = true; /* will notify clients of this change */
- dbc->db->unsupportedVersion = false;
- }
- (void) CFErrorPropagate(localError, error);
- return ok;
-}
-
-static CFStringRef _SecRevocationDbCopyUpdateSource(SecRevocationDbConnectionRef dbc, CFErrorRef *error) {
- /* look up db_source entry in admin table; returns NULL on error */
- __block CFStringRef updateSource = NULL;
- __block bool ok = (dbc != NULL);
- __block CFErrorRef localError = NULL;
-
- ok = ok && SecDbWithSQL(dbc->dbconn, selectDbSourceSQL, &localError, ^bool(sqlite3_stmt *selectDbSource) {
- ok &= SecDbStep(dbc->dbconn, selectDbSource, &localError, ^void(bool *stop) {
- const UInt8 *p = (const UInt8 *)sqlite3_column_blob(selectDbSource, 0);
- if (p != NULL) {
- CFIndex length = (CFIndex)sqlite3_column_bytes(selectDbSource, 0);
- if (length > 0) {
- updateSource = CFStringCreateWithBytes(kCFAllocatorDefault, p, length, kCFStringEncodingUTF8, false);
- }
- }
- *stop = true;
- });
- return ok;
- });
- if (!ok || localError) {
- secerror("_SecRevocationDbCopyUpdateSource failed: %@", localError);
- TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb, TAOperationRead, TAFatalError,
- localError ? CFErrorGetCode(localError) : errSecInternalComponent);
- }
- (void) CFErrorPropagate(localError, error);
- return updateSource;
-}
-
-bool _SecRevocationDbSetUpdateSource(SecRevocationDbConnectionRef dbc, CFStringRef updateSource, CFErrorRef *error) {
- if (!updateSource) {
- secerror("_SecRevocationDbSetUpdateSource failed: %d", errSecParam);
- return false;
- }
- __block char buffer[256];
- __block const char *updateSourceCStr = CFStringGetCStringPtr(updateSource, kCFStringEncodingUTF8);
- if (!updateSourceCStr) {
- if (CFStringGetCString(updateSource, buffer, 256, kCFStringEncodingUTF8)) {
- updateSourceCStr = buffer;
- }
- }
- if (!updateSourceCStr) {
- secerror("_SecRevocationDbSetUpdateSource failed: unable to get UTF-8 encoding");
- return false;
- }
- secdebug("validupdate", "setting update source to \"%s\"", updateSourceCStr);
-
- __block CFErrorRef localError = NULL;
- __block bool ok = (dbc != NULL);
- ok = ok && SecDbWithSQL(dbc->dbconn, insertAdminRecordSQL, &localError, ^bool(sqlite3_stmt *insertRecord) {
- const char *dbSourceKey = "db_source";
- ok = ok && SecDbBindText(insertRecord, 1, dbSourceKey, strlen(dbSourceKey),
- SQLITE_TRANSIENT, &localError);
- ok = ok && SecDbBindInt64(insertRecord, 2,
- (sqlite3_int64)0, &localError);
- ok = ok && SecDbBindBlob(insertRecord, 3,
- updateSourceCStr, strlen(updateSourceCStr),
- SQLITE_TRANSIENT, &localError);
- ok = ok && SecDbStep(dbc->dbconn, insertRecord, &localError, NULL);
- return ok;
- });
- if (!ok || localError) {
- secerror("_SecRevocationDbSetUpdateSource failed: %@", localError);
- TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb, TAOperationWrite, TAFatalError,
- localError ? CFErrorGetCode(localError) : errSecInternalComponent);
- }
- (void) CFErrorPropagate(localError, error);
- CFReleaseSafe(localError);
- return ok;
-}
-
-bool SecRevocationDbSetUpdateSource(SecRevocationDbRef rdb, CFStringRef updateSource) {
- /* note: this function assumes it is called only by the database owner.
- non-owner (read-only) clients will fail if changes to the db are needed. */
- if (!rdb || !rdb->db) {
- return false;
- }
- CFErrorRef localError = NULL;
- bool ok = true;
- ok &= SecRevocationDbPerformWrite(rdb, &localError, ^bool(SecRevocationDbConnectionRef dbc, CFErrorRef *error) {
- return _SecRevocationDbSetUpdateSource(dbc, updateSource, error);
- });
- CFReleaseSafe(localError);
- return ok;
-}
-
-static CFAbsoluteTime _SecRevocationDbGetNextUpdateTime(SecRevocationDbConnectionRef dbc, CFErrorRef *error) {
- /* look up check_again entry in admin table; returns 0 on error */
- __block CFAbsoluteTime nextUpdate = 0;
- __block bool ok = (dbc != NULL);
- __block CFErrorRef localError = NULL;
-
- ok = ok && SecDbWithSQL(dbc->dbconn, selectNextUpdateSQL, &localError, ^bool(sqlite3_stmt *selectNextUpdate) {
- ok &= SecDbStep(dbc->dbconn, selectNextUpdate, &localError, ^void(bool *stop) {
- CFAbsoluteTime *p = (CFAbsoluteTime *)sqlite3_column_blob(selectNextUpdate, 0);
- if (p != NULL) {
- if (sizeof(CFAbsoluteTime) == sqlite3_column_bytes(selectNextUpdate, 0)) {
- nextUpdate = *p;
- }
- }
- *stop = true;
- });
- return ok;
- });
- if (!ok || localError) {
- secerror("_SecRevocationDbGetNextUpdateTime failed: %@", localError);
- TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb, TAOperationRead, TAFatalError,
- localError ? CFErrorGetCode(localError) : errSecInternalComponent);
- }
- (void) CFErrorPropagate(localError, error);
- return nextUpdate;
-}
-
-static bool _SecRevocationDbSetNextUpdateTime(SecRevocationDbConnectionRef dbc, CFAbsoluteTime nextUpdate, CFErrorRef *error){
- secdebug("validupdate", "setting next update to %f", (double)nextUpdate);
-
- __block CFErrorRef localError = NULL;
- __block bool ok = (dbc != NULL);
- ok = ok && SecDbWithSQL(dbc->dbconn, insertAdminRecordSQL, &localError, ^bool(sqlite3_stmt *insertRecord) {
- const char *nextUpdateKey = "check_again";
- ok = ok && SecDbBindText(insertRecord, 1, nextUpdateKey, strlen(nextUpdateKey),
- SQLITE_TRANSIENT, &localError);
- ok = ok && SecDbBindInt64(insertRecord, 2,
- (sqlite3_int64)0, &localError);
- ok = ok && SecDbBindBlob(insertRecord, 3,
- &nextUpdate, sizeof(CFAbsoluteTime),
- SQLITE_TRANSIENT, &localError);
- ok = ok && SecDbStep(dbc->dbconn, insertRecord, &localError, NULL);
- return ok;
- });
- if (!ok || localError) {
- secerror("_SecRevocationDbSetNextUpdate failed: %@", localError);
- TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb, TAOperationWrite, TAFatalError,
- localError ? CFErrorGetCode(localError) : errSecInternalComponent);
- }
- (void) CFErrorPropagate(localError, error);
- return ok;
-}
-
-bool _SecRevocationDbRemoveAllEntries(SecRevocationDbConnectionRef dbc, CFErrorRef *error) {
- /* clear out the contents of the database and start fresh */
- bool ok = (dbc != NULL);
- CFErrorRef localError = NULL;
-
- /* _SecRevocationDbUpdateSchema was called when db was opened, so no need to do it again. */
-
- /* delete all entries */
- ok = ok && SecDbExec(dbc->dbconn, deleteAllEntriesSQL, &localError);
- secnotice("validupdate", "resetting database, result: %d (expected 1)", (ok) ? 1 : 0);
-
- /* one more thing: update the schema version and format to current */
- ok = ok && _SecRevocationDbSetSchemaVersion(dbc, kSecRevocationDbSchemaVersion, &localError);
- ok = ok && _SecRevocationDbSetUpdateFormat(dbc, kSecRevocationDbUpdateFormat, &localError);
-
- if (!ok || localError) {
- secerror("_SecRevocationDbRemoveAllEntries failed: %@", localError);
- TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb, TAOperationWrite, TAFatalError,
- localError ? CFErrorGetCode(localError) : errSecInternalComponent);
- }
- (void) CFErrorPropagate(localError, error);
- return ok;
-}
-
-static bool _SecRevocationDbUpdateIssuers(SecRevocationDbConnectionRef dbc, int64_t groupId, CFArrayRef issuers, CFErrorRef *error) {
- /* insert or replace issuer records in issuers table */
- if (!issuers || groupId < 0) {
- return false; /* must have something to insert, and a group to associate with it */
- }
- __block bool ok = (dbc != NULL);
- __block CFErrorRef localError = NULL;
- if (isArray(issuers)) {
- CFIndex issuerIX, issuerCount = CFArrayGetCount(issuers);
- for (issuerIX=0; issuerIX<issuerCount && ok; issuerIX++) {
- CFDataRef hash = (CFDataRef)CFArrayGetValueAtIndex(issuers, issuerIX);
- if (!hash) { continue; }
- ok = ok && SecDbWithSQL(dbc->dbconn, insertIssuerRecordSQL, &localError, ^bool(sqlite3_stmt *insertIssuer) {
- ok = ok && SecDbBindInt64(insertIssuer, 1,
- groupId, &localError);
- ok = ok && SecDbBindBlob(insertIssuer, 2,
- CFDataGetBytePtr(hash),
- CFDataGetLength(hash),
- SQLITE_TRANSIENT, &localError);
- /* Execute the insert statement for this issuer record. */
- ok = ok && SecDbStep(dbc->dbconn, insertIssuer, &localError, NULL);
- return ok;
- });
- }
- }
- if (!ok || localError) {
- secerror("_SecRevocationDbUpdateIssuers failed: %@", localError);
- TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb, TAOperationWrite, TAFatalError,
- localError ? CFErrorGetCode(localError) : errSecInternalComponent);
- }
- (void) CFErrorPropagate(localError, error);
- return ok;
-}
-
-static SecValidInfoFormat _SecRevocationDbGetGroupFormatForData(SecRevocationDbConnectionRef dbc, int64_t groupId, CFDataRef data) {
- /* determine existing format if groupId is supplied and this is a partial update,
- otherwise return the expected format for the given data. */
- SecValidInfoFormat format = kSecValidInfoFormatUnknown;
- if (groupId >= 0 && !dbc->fullUpdate) {
- format = _SecRevocationDbGetGroupFormat(dbc, groupId, NULL, NULL, NULL, NULL);
- }
- if (format == kSecValidInfoFormatUnknown && data != NULL) {
- /* group doesn't exist, so determine format based on length of specified data.
- len <= 20 is a serial number (actually, <=37, but != 32.)
- len==32 is a sha256 hash. otherwise: nto1. */
- CFIndex length = CFDataGetLength(data);
- if (length == 32) {
- format = kSecValidInfoFormatSHA256;
- } else if (length <= 37) {
- format = kSecValidInfoFormatSerial;
- } else if (length > 0) {
- format = kSecValidInfoFormatNto1;
- }
- }
- return format;
-}
-
-static bool _SecRevocationDbUpdateIssuerData(SecRevocationDbConnectionRef dbc, int64_t groupId, CFDictionaryRef dict, CFErrorRef *error) {
- /* update/delete records in serials or hashes table. */
- if (!dict || groupId < 0) {
- return false; /* must have something to insert, and a group to associate with it */
- }
- __block bool ok = (dbc != NULL);
- __block CFErrorRef localError = NULL;
- /* process deletions */
- CFArrayRef deleteArray = (CFArrayRef)CFDictionaryGetValue(dict, CFSTR("delete"));
- if (isArray(deleteArray)) {
- SecValidInfoFormat format = kSecValidInfoFormatUnknown;
- CFIndex processed=0, identifierIX, identifierCount = CFArrayGetCount(deleteArray);
- for (identifierIX=0; identifierIX<identifierCount; identifierIX++) {
- CFDataRef identifierData = (CFDataRef)CFArrayGetValueAtIndex(deleteArray, identifierIX);
- if (!identifierData) { continue; }
- if (format == kSecValidInfoFormatUnknown) {
- format = _SecRevocationDbGetGroupFormatForData(dbc, groupId, identifierData);
- }
- CFStringRef sql = NULL;
- if (format == kSecValidInfoFormatSerial) {
- sql = deleteSerialRecordSQL;
- } else if (format == kSecValidInfoFormatSHA256) {
- sql = deleteSha256RecordSQL;
- }
- if (!sql) { continue; }
-
- ok = ok && SecDbWithSQL(dbc->dbconn, sql, &localError, ^bool(sqlite3_stmt *deleteIdentifier) {
- /* (groupid,serial|sha256) */
- CFDataRef hexData = cfToHexData(identifierData, true);
- if (!hexData) { return false; }
- ok = ok && SecDbBindInt64(deleteIdentifier, 1,
- groupId, &localError);
- ok = ok && SecDbBindBlob(deleteIdentifier, 2,
- CFDataGetBytePtr(hexData),
- CFDataGetLength(hexData),
- SQLITE_TRANSIENT, &localError);
- /* Execute the delete statement for the identifier record. */
- ok = ok && SecDbStep(dbc->dbconn, deleteIdentifier, &localError, NULL);
- CFReleaseSafe(hexData);
- return ok;
- });
- if (ok) { ++processed; }
- }
-#if VERBOSE_LOGGING
- secdebug("validupdate", "Processed %ld of %ld deletions for group %lld, result=%s",
- processed, identifierCount, groupId, (ok) ? "true" : "false");
-#endif
- }
- /* process additions */
- CFArrayRef addArray = (CFArrayRef)CFDictionaryGetValue(dict, CFSTR("add"));
- if (isArray(addArray)) {
- SecValidInfoFormat format = kSecValidInfoFormatUnknown;
- CFIndex processed=0, identifierIX, identifierCount = CFArrayGetCount(addArray);
- for (identifierIX=0; identifierIX<identifierCount; identifierIX++) {
- CFDataRef identifierData = (CFDataRef)CFArrayGetValueAtIndex(addArray, identifierIX);
- if (!identifierData) { continue; }
- if (format == kSecValidInfoFormatUnknown) {
- format = _SecRevocationDbGetGroupFormatForData(dbc, groupId, identifierData);
- }
- CFStringRef sql = NULL;
- if (format == kSecValidInfoFormatSerial) {
- sql = insertSerialRecordSQL;
- } else if (format == kSecValidInfoFormatSHA256) {
- sql = insertSha256RecordSQL;
- }
- if (!sql) { continue; }
-
- ok = ok && SecDbWithSQL(dbc->dbconn, sql, &localError, ^bool(sqlite3_stmt *insertIdentifier) {
- /* rowid,(groupid,serial|sha256) */
- /* rowid is autoincremented and we never set it directly */
- ok = ok && SecDbBindInt64(insertIdentifier, 1,
- groupId, &localError);
- ok = ok && SecDbBindBlob(insertIdentifier, 2,
- CFDataGetBytePtr(identifierData),
- CFDataGetLength(identifierData),
- SQLITE_TRANSIENT, &localError);
- /* Execute the insert statement for the identifier record. */
- ok = ok && SecDbStep(dbc->dbconn, insertIdentifier, &localError, NULL);
- return ok;
- });
- if (ok) { ++processed; }
- }
-#if VERBOSE_LOGGING
- secdebug("validupdate", "Processed %ld of %ld additions for group %lld, result=%s",
- processed, identifierCount, groupId, (ok) ? "true" : "false");
-#endif
- }
- if (!ok || localError) {
- secerror("_SecRevocationDbUpdatePerIssuerData failed: %@", localError);
- TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb, TAOperationWrite, TAFatalError,
- localError ? CFErrorGetCode(localError) : errSecInternalComponent);
- }
- (void) CFErrorPropagate(localError, error);
- return ok;
-}
-
-static bool _SecRevocationDbCopyDateConstraints(SecRevocationDbConnectionRef dbc,
- int64_t groupId, CFDateRef *notBeforeDate, CFDateRef *notAfterDate, CFErrorRef *error) {
- /* return true if one or both date constraints exist for a given groupId.
- the actual constraints are optionally returned in output CFDateRef parameters.
- caller is responsible for releasing date and error parameters, if provided.
- */
- __block bool ok = (dbc != NULL);
- __block CFDateRef localNotBefore = NULL;
- __block CFDateRef localNotAfter = NULL;
- __block CFErrorRef localError = NULL;
-
- ok = ok && SecDbWithSQL(dbc->dbconn, selectDateRecordSQL, &localError, ^bool(sqlite3_stmt *selectDates) {
- /* (groupid,notbefore,notafter) */
- ok &= SecDbBindInt64(selectDates, 1, groupId, &localError);
- ok = ok && SecDbStep(dbc->dbconn, selectDates, &localError, ^(bool *stop) {
- /* if column has no value, its type will be SQLITE_NULL */
- if (SQLITE_NULL != sqlite3_column_type(selectDates, 0)) {
- CFAbsoluteTime nb = (CFAbsoluteTime)sqlite3_column_double(selectDates, 0);
- localNotBefore = CFDateCreate(NULL, nb);
- }
- if (SQLITE_NULL != sqlite3_column_type(selectDates, 1)) {
- CFAbsoluteTime na = (CFAbsoluteTime)sqlite3_column_double(selectDates, 1);
- localNotAfter = CFDateCreate(NULL, na);
- }
- });
- return ok;
- });
- /* must have at least one date constraint to return true.
- since date constraints are optional, not finding any should not log an error. */
- ok = ok && !localError && (localNotBefore != NULL || localNotAfter != NULL);
- if (localError) {
- secerror("_SecRevocationDbCopyDateConstraints failed: %@", localError);
- TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb, TAOperationRead, TAFatalError,
- localError ? CFErrorGetCode(localError) : errSecInternalComponent);
- }
- if (!ok) {
- CFReleaseNull(localNotBefore);
- CFReleaseNull(localNotAfter);
- }
- if (notBeforeDate) {
- *notBeforeDate = localNotBefore;
- } else {
- CFReleaseSafe(localNotBefore);
- }
- if (notAfterDate) {
- *notAfterDate = localNotAfter;
- } else {
- CFReleaseSafe(localNotAfter);
- }
-
- (void) CFErrorPropagate(localError, error);
- return ok;
-}
-
-static bool _SecRevocationDbUpdateDateConstraints(SecRevocationDbConnectionRef dbc, int64_t groupId, CFDictionaryRef dict, CFErrorRef *error) {
- /* Called only from _SecRevocationDbUpdateIssuerConstraints.
- Function assumes that the caller has checked the input arguments.
- */
- __block bool ok = true;
- __block CFErrorRef localError = NULL;
- __block CFAbsoluteTime notBefore = -3155760000.0; /* default: 1901-01-01 00:00:00-0000 */
- __block CFAbsoluteTime notAfter = 31556908800.0; /* default: 3001-01-01 00:00:00-0000 */
-
- CFDateRef notBeforeDate = (CFDateRef)CFDictionaryGetValue(dict, CFSTR("not-before"));
- CFDateRef notAfterDate = (CFDateRef)CFDictionaryGetValue(dict, CFSTR("not-after"));
-
- if (isDate(notBeforeDate)) {
- notBefore = CFDateGetAbsoluteTime(notBeforeDate);
- } else {
- notBeforeDate = NULL;
- }
- if (isDate(notAfterDate)) {
- notAfter = CFDateGetAbsoluteTime(notAfterDate);
- } else {
- notAfterDate = NULL;
- }
- if (!(notBeforeDate || notAfterDate)) {
- return ok; /* no dates supplied, so we have nothing to update for this issuer */
- }
-
- if (!(notBeforeDate && notAfterDate) && !dbc->fullUpdate) {
- /* only one date was supplied, so check for existing date constraints */
- CFDateRef curNotBeforeDate = NULL;
- CFDateRef curNotAfterDate = NULL;
- if (_SecRevocationDbCopyDateConstraints(dbc, groupId, &curNotBeforeDate,
- &curNotAfterDate, &localError)) {
- if (!notBeforeDate) {
- notBeforeDate = curNotBeforeDate;
- notBefore = CFDateGetAbsoluteTime(notBeforeDate);
- } else {
- CFReleaseSafe(curNotBeforeDate);
- }
- if (!notAfterDate) {
- notAfterDate = curNotAfterDate;
- notAfter = CFDateGetAbsoluteTime(notAfterDate);
- } else {
- CFReleaseSafe(curNotAfterDate);
- }
- }
- }
- ok = ok && SecDbWithSQL(dbc->dbconn, insertDateRecordSQL, &localError, ^bool(sqlite3_stmt *insertDate) {
- /* (groupid,notbefore,notafter) */
- ok = ok && SecDbBindInt64(insertDate, 1, groupId, &localError);
- ok = ok && SecDbBindDouble(insertDate, 2, notBefore, &localError);
- ok = ok && SecDbBindDouble(insertDate, 3, notAfter, &localError);
- ok = ok && SecDbStep(dbc->dbconn, insertDate, &localError, NULL);
- return ok;
- });
-
- if (!ok || localError) {
- secinfo("validupdate", "_SecRevocationDbUpdateDateConstraints failed (ok=%s, localError=%@)",
- (ok) ? "1" : "0", localError);
- TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb, TAOperationWrite, TAFatalError,
- localError ? CFErrorGetCode(localError) : errSecInternalComponent);
- }
- (void) CFErrorPropagate(localError, error);
- return ok;
-}
-
-static bool _SecRevocationDbUpdatePolicyConstraints(SecRevocationDbConnectionRef dbc, int64_t groupId, CFDictionaryRef dict, CFErrorRef *error) {
- /* Called only from _SecRevocationDbUpdateIssuerConstraints.
- Function assumes that the caller has checked the input arguments.
- */
- __block bool ok = true;
- __block CFErrorRef localError = NULL;
- CFArrayRef policies = (CFArrayRef)CFDictionaryGetValue(dict, CFSTR("policies"));
- if (!isArray(policies)) {
- return ok; /* no policies supplied, so nothing to update for this issuer */
- }
-
- __block CFDataRef data = createPoliciesData(policies);
- ok = data && SecDbWithSQL(dbc->dbconn, updateGroupPoliciesSQL, &localError, ^bool(sqlite3_stmt *updatePolicies) {
- /* (policies,groupid) */
- ok = ok && SecDbBindBlob(updatePolicies, 1,
- CFDataGetBytePtr(data),
- CFDataGetLength(data),
- SQLITE_TRANSIENT, &localError);
- ok = ok && SecDbBindInt64(updatePolicies, 2, groupId, &localError);
- ok = ok && SecDbStep(dbc->dbconn, updatePolicies, &localError, NULL);
- return ok;
- });
- CFReleaseSafe(data);
-
- if (!ok || localError) {
- secinfo("validupdate", "_SecRevocationDbUpdatePolicyConstraints failed (ok=%s, localError=%@)",
- (ok) ? "1" : "0", localError);
- TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb, TAOperationWrite, TAFatalError,
- localError ? CFErrorGetCode(localError) : errSecInternalComponent);
- }
- (void) CFErrorPropagate(localError, error);
- return ok;
-}
-
-static bool _SecRevocationDbUpdateNameConstraints(SecRevocationDbConnectionRef dbc, int64_t groupId, CFDictionaryRef dict, CFErrorRef *error) {
- /* Called only from _SecRevocationDbUpdateIssuerConstraints.
- Function assumes that the caller has checked the input arguments.
- */
-
- /* %%% (TBI:9254570) update name constraint entries here */
- return true;
-}
-
-static bool _SecRevocationDbUpdateIssuerConstraints(SecRevocationDbConnectionRef dbc, int64_t groupId, CFDictionaryRef dict, CFErrorRef *error) {
- /* check input arguments */
- if (!dbc || !dict || groupId < 0) {
- return false;
- }
- bool ok = true;
- ok = ok && _SecRevocationDbUpdateDateConstraints(dbc, groupId, dict, error);
- ok = ok && _SecRevocationDbUpdateNameConstraints(dbc, groupId, dict, error);
- ok = ok && _SecRevocationDbUpdatePolicyConstraints(dbc, groupId, dict, error);
- return ok;
-}
-
-static SecValidInfoFormat _SecRevocationDbGetGroupFormat(SecRevocationDbConnectionRef dbc,
- int64_t groupId, SecValidInfoFlags *flags, CFDataRef *data, CFDataRef *policies, CFErrorRef *error) {
- /* return group record fields for a given groupId.
- on success, returns a non-zero format type, and other field values in optional output parameters.
- caller is responsible for releasing data, policies, and error parameters, if provided.
- */
- __block bool ok = (dbc != NULL);
- __block SecValidInfoFormat format = 0;
- __block CFErrorRef localError = NULL;
-
- /* Select the group record to determine flags and format. */
- ok = ok && SecDbWithSQL(dbc->dbconn, selectGroupRecordSQL, &localError, ^bool(sqlite3_stmt *selectGroup) {
- ok = ok && SecDbBindInt64(selectGroup, 1, groupId, &localError);
- ok = ok && SecDbStep(dbc->dbconn, selectGroup, &localError, ^(bool *stop) {
- if (flags) {
- *flags = (SecValidInfoFlags)sqlite3_column_int(selectGroup, 0);
- }
- format = (SecValidInfoFormat)sqlite3_column_int(selectGroup, 1);
- if (data) {
- //%%% stream the data from the db into a streamed decompression <rdar://32142637>
- uint8_t *p = (uint8_t *)sqlite3_column_blob(selectGroup, 2);
- if (p != NULL && format == kSecValidInfoFormatNto1) {
- CFIndex length = (CFIndex)sqlite3_column_bytes(selectGroup, 2);
- *data = CFDataCreate(kCFAllocatorDefault, p, length);
- }
- }
- if (policies) {
- uint8_t *p = (uint8_t *)sqlite3_column_blob(selectGroup, 3);
- if (p != NULL) {
- CFIndex length = (CFIndex)sqlite3_column_bytes(selectGroup, 3);
- *policies = CFDataCreate(kCFAllocatorDefault, p, length);
- }
- }
- });
- return ok;
- });
- if (!ok || localError) {
- secdebug("validupdate", "GetGroupFormat for groupId %lu failed", (unsigned long)groupId);
- TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb, TAOperationRead, TAFatalError,
- localError ? CFErrorGetCode(localError) : errSecInternalComponent);
- format = kSecValidInfoFormatUnknown;
- }
- (void) CFErrorPropagate(localError, error);
- if (!(format > kSecValidInfoFormatUnknown)) {
- secdebug("validupdate", "GetGroupFormat: got format %d for groupId %lld", format, (long long)groupId);
- }
- return format;
-}
-
-static bool _SecRevocationDbUpdateFlags(CFDictionaryRef dict, CFStringRef key, SecValidInfoFlags mask, SecValidInfoFlags *flags) {
- /* If a boolean value exists in the given dictionary for the given key,
- or an explicit "1" or "0" is specified as the key string,
- set or clear the corresponding bit(s) defined by the mask argument.
- Function returns true if the flags value was changed, false otherwise.
- */
- if (!isDictionary(dict) || !isString(key) || !flags) {
- return false;
- }
- bool hasValue = false, newValue = false, result = false;
- CFTypeRef value = (CFBooleanRef)CFDictionaryGetValue(dict, key);
- if (isBoolean(value)) {
- newValue = CFBooleanGetValue((CFBooleanRef)value);
- hasValue = true;
- } else if (BOOL_STRING_KEY_LENGTH == CFStringGetLength(key)) {
- if (CFStringCompare(key, kBoolTrueKey, 0) == kCFCompareEqualTo) {
- hasValue = newValue = true;
- } else if (CFStringCompare(key, kBoolFalseKey, 0) == kCFCompareEqualTo) {
- hasValue = true;
- }
- }
- if (hasValue) {
- SecValidInfoFlags oldFlags = *flags;
- if (newValue) {
- *flags |= mask;
- } else {
- *flags &= ~(mask);
- }
- result = (*flags != oldFlags);
- }
- return result;
-}
-
-static bool _SecRevocationDbUpdateFilter(CFDictionaryRef dict, CFDataRef oldData, CFDataRef * __nonnull CF_RETURNS_RETAINED xmlData) {
- /* If xor and/or params values exist in the given dictionary, create a new
- property list containing the updated values, and return as a flattened
- data blob in the xmlData output parameter (note: caller must release.)
- Function returns true if there is new xmlData to save, false otherwise.
- */
- bool result = false;
- bool xorProvided = false;
- bool paramsProvided = false;
- bool missingData = false;
-
- if (!dict || !xmlData) {
- return result; /* no-op if no dictionary is provided, or no way to update the data */
- }
- *xmlData = NULL;
- CFDataRef xorCurrent = NULL;
- CFDataRef xorUpdate = (CFDataRef)CFDictionaryGetValue(dict, CFSTR("xor"));
- if (isData(xorUpdate)) {
- xorProvided = true;
- }
- CFArrayRef paramsCurrent = NULL;
- CFArrayRef paramsUpdate = (CFArrayRef)CFDictionaryGetValue(dict, CFSTR("params"));
- if (isArray(paramsUpdate)) {
- paramsProvided = true;
- }
- if (!(xorProvided || paramsProvided)) {
- return result; /* nothing to update, so we can bail out here. */
- }
-
- CFPropertyListRef nto1Current = NULL;
- CFMutableDictionaryRef nto1Update = CFDictionaryCreateMutable(kCFAllocatorDefault, 0,
- &kCFTypeDictionaryKeyCallBacks,
- &kCFTypeDictionaryValueCallBacks);
- if (!nto1Update) {
- return result;
- }
-
- /* turn old data into property list */
- CFDataRef data = (CFDataRef)CFRetainSafe(oldData);
- CFDataRef inflatedData = copyInflatedData(data);
- if (inflatedData) {
- CFReleaseSafe(data);
- data = inflatedData;
- }
- if (data) {
- nto1Current = CFPropertyListCreateWithData(kCFAllocatorDefault, data, 0, NULL, NULL);
- CFReleaseSafe(data);
- }
- if (nto1Current) {
- xorCurrent = (CFDataRef)CFDictionaryGetValue((CFDictionaryRef)nto1Current, CFSTR("xor"));
- paramsCurrent = (CFArrayRef)CFDictionaryGetValue((CFDictionaryRef)nto1Current, CFSTR("params"));
- }
-
- /* set current or updated xor data in new property list */
- if (xorProvided) {
- CFDataRef xorNew = NULL;
- if (xorCurrent) {
- CFIndex xorUpdateLen = CFDataGetLength(xorUpdate);
- CFMutableDataRef xor = CFDataCreateMutableCopy(NULL, 0, xorCurrent);
- if (xor && xorUpdateLen > 0) {
- /* truncate or zero-extend data to match update size */
- CFDataSetLength(xor, xorUpdateLen);
- /* exclusive-or update bytes over the existing data */
- UInt8 *xorP = (UInt8 *)CFDataGetMutableBytePtr(xor);
- UInt8 *updP = (UInt8 *)CFDataGetBytePtr(xorUpdate);
- if (xorP && updP) {
- for (int idx = 0; idx < xorUpdateLen; idx++) {
- xorP[idx] = xorP[idx] ^ updP[idx];
- }
- }
- }
- xorNew = (CFDataRef)xor;
- } else {
- xorNew = (CFDataRef)CFRetainSafe(xorUpdate);
- }
- if (xorNew) {
- CFDictionaryAddValue(nto1Update, CFSTR("xor"), xorNew);
- CFReleaseSafe(xorNew);
- } else {
- secdebug("validupdate", "Failed to get updated filter data");
- missingData = true;
- }
- } else if (xorCurrent) {
- /* not provided, so use existing xor value */
- CFDictionaryAddValue(nto1Update, CFSTR("xor"), xorCurrent);
- } else {
- secdebug("validupdate", "Failed to get current filter data");
- missingData = true;
- }
-
- /* set current or updated params in new property list */
- if (paramsProvided) {
- CFDictionaryAddValue(nto1Update, CFSTR("params"), paramsUpdate);
- } else if (paramsCurrent) {
- /* not provided, so use existing params value */
- CFDictionaryAddValue(nto1Update, CFSTR("params"), paramsCurrent);
- } else {
- /* missing params: neither provided nor existing */
- secdebug("validupdate", "Failed to get current filter params");
- missingData = true;
- }
-
- CFReleaseSafe(nto1Current);
- if (!missingData) {
- *xmlData = CFPropertyListCreateData(kCFAllocatorDefault, nto1Update,
- kCFPropertyListXMLFormat_v1_0,
- 0, NULL);
- result = (*xmlData != NULL);
- }
- CFReleaseSafe(nto1Update);
-
- /* compress the xmlData blob, if possible */
- if (result) {
- CFDataRef deflatedData = copyDeflatedData(*xmlData);
- if (deflatedData) {
- if (CFDataGetLength(deflatedData) < CFDataGetLength(*xmlData)) {
- CFRelease(*xmlData);
- *xmlData = deflatedData;
- } else {
- CFRelease(deflatedData);
- }
- }
- }
- return result;
-}
-
-
-static int64_t _SecRevocationDbUpdateGroup(SecRevocationDbConnectionRef dbc, int64_t groupId, CFDictionaryRef dict, CFErrorRef *error) {
- /* insert group record for a given groupId.
- if the specified groupId is < 0, a new group entry is created.
- returns the groupId on success, or -1 on failure.
- */
- if (!dict) {
- return groupId; /* no-op if no dictionary is provided */
- }
-
- __block int64_t result = -1;
- __block bool ok = (dbc != NULL);
- __block bool isFormatChange = false;
- __block CFErrorRef localError = NULL;
-
- __block SecValidInfoFlags flags = 0;
- __block SecValidInfoFormat format = kSecValidInfoFormatUnknown;
- __block SecValidInfoFormat formatUpdate = kSecValidInfoFormatUnknown;
- __block CFDataRef data = NULL;
- __block CFDataRef policies = NULL;
-
- if (groupId >= 0) {
- /* fetch the flags and data for an existing group record, in case some are being changed. */
- if (ok) {
- format = _SecRevocationDbGetGroupFormat(dbc, groupId, &flags, &data, &policies, NULL);
- }
- if (format == kSecValidInfoFormatUnknown) {
- secdebug("validupdate", "existing group %lld has unknown format %d, flags=0x%lx",
- (long long)groupId, format, flags);
- //%%% clean up by deleting all issuers with this groupId, then the group record,
- // or just force a full update? note: we can get here if we fail to bind the
- // format value in the prepared SQL statement below.
- return -1;
- }
- }
- CFTypeRef value = (CFStringRef)CFDictionaryGetValue(dict, CFSTR("format"));
- if (isString(value)) {
- if (CFStringCompare((CFStringRef)value, CFSTR("serial"), 0) == kCFCompareEqualTo) {
- formatUpdate = kSecValidInfoFormatSerial;
- } else if (CFStringCompare((CFStringRef)value, CFSTR("sha256"), 0) == kCFCompareEqualTo) {
- formatUpdate = kSecValidInfoFormatSHA256;
- } else if (CFStringCompare((CFStringRef)value, CFSTR("nto1"), 0) == kCFCompareEqualTo) {
- formatUpdate = kSecValidInfoFormatNto1;
- }
- }
- /* if format value is explicitly supplied, then this is effectively a new group entry. */
- isFormatChange = (formatUpdate > kSecValidInfoFormatUnknown &&
- formatUpdate != format &&
- groupId >= 0);
-
- if (isFormatChange) {
- secdebug("validupdate", "group %lld format change from %d to %d",
- (long long)groupId, format, formatUpdate);
- /* format of an existing group is changing; delete the group first.
- this should ensure that all entries referencing the old groupid are deleted.
- */
- ok = ok && SecDbWithSQL(dbc->dbconn, deleteGroupRecordSQL, &localError, ^bool(sqlite3_stmt *deleteResponse) {
- ok = ok && SecDbBindInt64(deleteResponse, 1, groupId, &localError);
- /* Execute the delete statement. */
- ok = ok && SecDbStep(dbc->dbconn, deleteResponse, &localError, NULL);
- return ok;
- });
- }
- ok = ok && SecDbWithSQL(dbc->dbconn, insertGroupRecordSQL, &localError, ^bool(sqlite3_stmt *insertGroup) {
- /* (groupid,flags,format,data,policies) */
- /* groups.groupid */
- if (ok && (!isFormatChange) && (groupId >= 0)) {
- /* bind to existing groupId row if known, otherwise will insert and autoincrement */
- ok = SecDbBindInt64(insertGroup, 1, groupId, &localError);
- if (!ok) {
- secdebug("validupdate", "failed to set groupId %lld", (long long)groupId);
- }
- }
- /* groups.flags */
- if (ok) {
- (void)_SecRevocationDbUpdateFlags(dict, CFSTR("complete"), kSecValidInfoComplete, &flags);
- (void)_SecRevocationDbUpdateFlags(dict, CFSTR("check-ocsp"), kSecValidInfoCheckOCSP, &flags);
- (void)_SecRevocationDbUpdateFlags(dict, CFSTR("known-intermediates-only"), kSecValidInfoKnownOnly, &flags);
- (void)_SecRevocationDbUpdateFlags(dict, CFSTR("require-ct"), kSecValidInfoRequireCT, &flags);
- (void)_SecRevocationDbUpdateFlags(dict, CFSTR("valid"), kSecValidInfoAllowlist, &flags);
- (void)_SecRevocationDbUpdateFlags(dict, CFSTR("no-ca"), kSecValidInfoNoCACheck, &flags);
- (void)_SecRevocationDbUpdateFlags(dict, CFSTR("no-ca-v2"), kSecValidInfoNoCAv2Check, &flags);
- (void)_SecRevocationDbUpdateFlags(dict, CFSTR("overridable"), kSecValidInfoOverridable, &flags);
-
- /* date constraints exist if either "not-before" or "not-after" keys are found */
- CFTypeRef notBeforeValue = (CFDateRef)CFDictionaryGetValue(dict, CFSTR("not-before"));
- CFTypeRef notAfterValue = (CFDateRef)CFDictionaryGetValue(dict, CFSTR("not-after"));
- if (isDate(notBeforeValue) || isDate(notAfterValue)) {
- (void)_SecRevocationDbUpdateFlags(dict, kBoolTrueKey, kSecValidInfoDateConstraints, &flags);
- /* Note that the spec defines not-before and not-after dates as optional, such that
- not providing one does not change the database contents. Therefore, we can never clear
- this flag; either a new date entry will be supplied, or a format change will cause
- the entire group entry to be deleted. */
- }
- /* policy constraints exist if "policies" key is found */
- CFTypeRef policiesValue = (CFArrayRef)CFDictionaryGetValue(dict, CFSTR("policies"));
- if (isArray(policiesValue)) {
- (void)_SecRevocationDbUpdateFlags(dict, kBoolTrueKey, kSecValidInfoPolicyConstraints, &flags);
- /* As above, not providing this value in an update does not change the existing state,
- so we never need to clear this flag once it is set. */
- }
-
- /* %%% (TBI:9254570) name constraints don't exist yet */
- (void)_SecRevocationDbUpdateFlags(dict, kBoolFalseKey, kSecValidInfoNameConstraints, &flags);
-
- ok = SecDbBindInt(insertGroup, 2, (int)flags, &localError);
- if (!ok) {
- secdebug("validupdate", "failed to set flags (%lu) for groupId %lld", flags, (long long)groupId);
- }
- }
- /* groups.format */
- if (ok) {
- SecValidInfoFormat formatValue = format;
- if (formatUpdate > kSecValidInfoFormatUnknown) {
- formatValue = formatUpdate;
- }
- ok = SecDbBindInt(insertGroup, 3, (int)formatValue, &localError);
- if (!ok) {
- secdebug("validupdate", "failed to set format (%d) for groupId %lld", formatValue, (long long)groupId);
- }
- }
- /* groups.data */
- CFDataRef xmlData = NULL;
- if (ok) {
- bool hasFilter = ((formatUpdate == kSecValidInfoFormatNto1) ||
- (formatUpdate == kSecValidInfoFormatUnknown &&
- format == kSecValidInfoFormatNto1));
- if (hasFilter) {
- CFDataRef dataValue = data; /* use existing data */
- if (_SecRevocationDbUpdateFilter(dict, data, &xmlData)) {
- dataValue = xmlData; /* use updated data */
- }
- if (dataValue) {
- ok = SecDbBindBlob(insertGroup, 4,
- CFDataGetBytePtr(dataValue),
- CFDataGetLength(dataValue),
- SQLITE_TRANSIENT, &localError);
- }
- if (!ok) {
- secdebug("validupdate", "failed to set data for groupId %lld",
- (long long)groupId);
- }
- }
- /* else there is no data, so NULL is implicitly bound to column 4 */
- }
- /* groups.policies */
- CFDataRef newPoliciesData = NULL;
- if (ok) {
- CFDataRef policiesValue = policies; /* use existing policies */
- newPoliciesData = createPoliciesData((CFArrayRef)CFDictionaryGetValue(dict, CFSTR("policies")));
- if (newPoliciesData) {
- policiesValue = newPoliciesData; /* use updated policies */
- }
- if (policiesValue) {
- ok = SecDbBindBlob(insertGroup, 5,
- CFDataGetBytePtr(policiesValue),
- CFDataGetLength(policiesValue),
- SQLITE_TRANSIENT, &localError);
- }
- /* else there is no policy data, so NULL is implicitly bound to column 5 */
- if (!ok) {
- secdebug("validupdate", "failed to set policies for groupId %lld",
- (long long)groupId);
- }
- }
-
- /* Execute the insert statement for the group record. */
- if (ok) {
- ok = SecDbStep(dbc->dbconn, insertGroup, &localError, NULL);
- if (!ok) {
- secdebug("validupdate", "failed to execute insertGroup statement for groupId %lld",
- (long long)groupId);
- }
- result = (int64_t)sqlite3_last_insert_rowid(SecDbHandle(dbc->dbconn));
- }
- if (!ok) {
- secdebug("validupdate", "failed to insert group %lld", (long long)result);
- }
- /* Clean up temporary allocations made in this block. */
- CFReleaseSafe(xmlData);
- CFReleaseSafe(newPoliciesData);
- return ok;
- });
-
- CFReleaseSafe(data);
- CFReleaseSafe(policies);
-
- if (!ok || localError) {
- secerror("_SecRevocationDbUpdateGroup failed: %@", localError);
- TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb, TAOperationWrite, TAFatalError,
- localError ? CFErrorGetCode(localError) : errSecInternalComponent);
- }
- (void) CFErrorPropagate(localError, error);
- return result;
-}
-
-static int64_t _SecRevocationDbGroupIdForIssuerHash(SecRevocationDbConnectionRef dbc, CFDataRef hash, CFErrorRef *error) {
- /* look up issuer hash in issuers table to get groupid, if it exists */
- __block int64_t groupId = -1;
- __block bool ok = (dbc != NULL);
- __block CFErrorRef localError = NULL;
-
- if (!hash) {
- secdebug("validupdate", "failed to get hash (%@)", hash);
- }
- require(hash && dbc, errOut);
-
- /* This is the starting point for any lookup; find a group id for the given issuer hash.
- Before we do that, need to verify the current db_version. We cannot use results from a
- database created with a schema version older than the minimum supported version.
- However, we may be able to use results from a newer version. At the next database
- update interval, if the existing schema is old, we'll be removing and recreating
- the database contents with the current schema version.
- */
- int64_t db_version = _SecRevocationDbGetSchemaVersion(dbc->db, dbc, NULL);
- if (db_version < kSecRevocationDbMinSchemaVersion) {
- if (!dbc->db->unsupportedVersion) {
- secdebug("validupdate", "unsupported db_version: %lld", (long long)db_version);
- dbc->db->unsupportedVersion = true; /* only warn once for a given unsupported version */
- }
- }
- require_quiet(db_version >= kSecRevocationDbMinSchemaVersion, errOut);
-
- /* Look up provided issuer_hash in the issuers table.
- */
- ok = ok && SecDbWithSQL(dbc->dbconn, selectGroupIdSQL, &localError, ^bool(sqlite3_stmt *selectGroupId) {
- ok &= SecDbBindBlob(selectGroupId, 1, CFDataGetBytePtr(hash), CFDataGetLength(hash), SQLITE_TRANSIENT, &localError);
- ok &= SecDbStep(dbc->dbconn, selectGroupId, &localError, ^(bool *stopGroupId) {
- groupId = sqlite3_column_int64(selectGroupId, 0);
- });
- return ok;
- });
-
-errOut:
- if (!ok || localError) {
- secerror("_SecRevocationDbGroupIdForIssuerHash failed: %@", localError);
- TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb, TAOperationRead, TAFatalError,
- localError ? CFErrorGetCode(localError) : errSecInternalComponent);
- }
- (void) CFErrorPropagate(localError, error);
- return groupId;
-}
-
-static bool _SecRevocationDbApplyGroupDelete(SecRevocationDbConnectionRef dbc, CFDataRef issuerHash, CFErrorRef *error) {
- /* delete group associated with the given issuer;
- schema trigger will delete associated issuers, serials, and hashes. */
- __block int64_t groupId = -1;
- __block bool ok = (dbc != NULL);
- __block CFErrorRef localError = NULL;
-
- if (ok) {
- groupId = _SecRevocationDbGroupIdForIssuerHash(dbc, issuerHash, &localError);
- }
- if (groupId < 0) {
- if (!localError) {
- SecError(errSecParam, &localError, CFSTR("group not found for issuer"));
- }
- ok = false;
- }
- ok = ok && SecDbWithSQL(dbc->dbconn, deleteGroupRecordSQL, &localError, ^bool(sqlite3_stmt *deleteResponse) {
- ok &= SecDbBindInt64(deleteResponse, 1, groupId, &localError);
- /* Execute the delete statement. */
- ok = ok && SecDbStep(dbc->dbconn, deleteResponse, &localError, NULL);
- return ok;
- });
- if (!ok || localError) {
- secerror("_SecRevocationDbApplyGroupDelete failed: %@", localError);
- TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb, TAOperationWrite, TAFatalError,
- localError ? CFErrorGetCode(localError) : errSecInternalComponent);
- }
- (void) CFErrorPropagate(localError, error);
- return ok;
-}
-
-static bool _SecRevocationDbApplyGroupUpdate(SecRevocationDbConnectionRef dbc, CFDictionaryRef dict, CFErrorRef *error) {
- /* process one issuer group's update dictionary */
- __block int64_t groupId = -1;
- __block bool ok = (dbc != NULL);
- __block CFErrorRef localError = NULL;
-
- CFArrayRef issuers = (dict) ? (CFArrayRef)CFDictionaryGetValue(dict, CFSTR("issuer-hash")) : NULL;
- /* if this is not a full update, then look for existing group id */
- if (ok && isArray(issuers) && !dbc->fullUpdate) {
- CFIndex issuerIX, issuerCount = CFArrayGetCount(issuers);
- /* while we have issuers and haven't found a matching group id */
- for (issuerIX=0; issuerIX<issuerCount && groupId < 0; issuerIX++) {
- CFDataRef hash = (CFDataRef)CFArrayGetValueAtIndex(issuers, issuerIX);
- if (!hash) { continue; }
- groupId = _SecRevocationDbGroupIdForIssuerHash(dbc, hash, &localError);
- }
- if (groupId >= 0) {
- /* according to the spec, we must replace all existing issuers with
- the new issuers list, so delete all issuers in the group first. */
- ok = ok && SecDbWithSQL(dbc->dbconn, deleteGroupIssuersSQL, &localError, ^bool(sqlite3_stmt *deleteIssuers) {
- ok = ok && SecDbBindInt64(deleteIssuers, 1, groupId, &localError);
- ok = ok && SecDbStep(dbc->dbconn, deleteIssuers, &localError, NULL);
- return ok;
- });
- }
- }
- /* create or update the group entry */
- if (ok) {
- groupId = _SecRevocationDbUpdateGroup(dbc, groupId, dict, &localError);
- }
- if (groupId < 0) {
- secdebug("validupdate", "failed to get groupId");
- ok = false;
- } else {
- /* create or update issuer entries, now that we know the group id */
- ok = ok && _SecRevocationDbUpdateIssuers(dbc, groupId, issuers, &localError);
- /* create or update entries in serials or hashes tables */
- ok = ok && _SecRevocationDbUpdateIssuerData(dbc, groupId, dict, &localError);
- /* create or update entries in dates/names/policies tables */
- ok = ok && _SecRevocationDbUpdateIssuerConstraints(dbc, groupId, dict, &localError);
- }
-
- (void) CFErrorPropagate(localError, error);
- return ok;
-}
-
-bool _SecRevocationDbApplyUpdate(SecRevocationDbConnectionRef dbc, CFDictionaryRef update, CFIndex version, CFErrorRef *error) {
- /* process entire update dictionary */
- if (!dbc || !dbc->db || !update) {
- secerror("_SecRevocationDbApplyUpdate failed: invalid args");
- SecError(errSecParam, error, CFSTR("_SecRevocationDbApplyUpdate: invalid db or update parameter"));
- return false;
- }
-
- CFDictionaryRef localUpdate = (CFDictionaryRef)CFRetainSafe(update);
- CFErrorRef localError = NULL;
- bool ok = true;
-
- CFTypeRef value = NULL;
- CFIndex deleteCount = 0;
- CFIndex updateCount = 0;
-
- dbc->db->updateInProgress = true;
-
- /* check whether this is a full update */
- value = (CFBooleanRef)CFDictionaryGetValue(update, CFSTR("full"));
- if (isBoolean(value) && CFBooleanGetValue((CFBooleanRef)value)) {
- /* clear the database before processing a full update */
- dbc->fullUpdate = true;
- secdebug("validupdate", "update has \"full\" attribute; clearing database");
- ok = ok && _SecRevocationDbRemoveAllEntries(dbc, &localError);
- }
-
- /* process 'delete' list */
- value = (CFArrayRef)CFDictionaryGetValue(localUpdate, CFSTR("delete"));
- if (isArray(value)) {
- deleteCount = CFArrayGetCount((CFArrayRef)value);
- secdebug("validupdate", "processing %ld deletes", (long)deleteCount);
- for (CFIndex deleteIX=0; deleteIX<deleteCount; deleteIX++) {
- CFDataRef issuerHash = (CFDataRef)CFArrayGetValueAtIndex((CFArrayRef)value, deleteIX);
- if (isData(issuerHash)) {
- ok = ok && _SecRevocationDbApplyGroupDelete(dbc, issuerHash, &localError);
- } else {
- secdebug("validupdate", "skipping delete %ld (hash is not a data value)", (long)deleteIX);
- }
- }
- }
-
- /* process 'update' list */
- value = (CFArrayRef)CFDictionaryGetValue(localUpdate, CFSTR("update"));
- if (isArray(value)) {
- updateCount = CFArrayGetCount((CFArrayRef)value);
- secdebug("validupdate", "processing %ld updates", (long)updateCount);
- for (CFIndex updateIX=0; updateIX<updateCount; updateIX++) {
- CFDictionaryRef dict = (CFDictionaryRef)CFArrayGetValueAtIndex((CFArrayRef)value, updateIX);
- if (isDictionary(dict)) {
- ok = ok && _SecRevocationDbApplyGroupUpdate(dbc, dict, &localError);
- } else {
- secdebug("validupdate", "skipping update %ld (not a dictionary)", (long)updateIX);
- }
- }
- }
- CFReleaseSafe(localUpdate);
-
- /* set version */
- ok = ok && _SecRevocationDbSetVersion(dbc, version, &localError);
-
- /* set interval if not already set, or changed */
- int64_t interval = _SecRevocationDbGetUpdateInterval(dbc, NULL);
- if (interval != dbc->precommitInterval) {
- interval = (dbc->precommitInterval > 0) ? dbc->precommitInterval : kSecStdUpdateInterval;
- ok = ok && _SecRevocationDbSetUpdateInterval(dbc, interval, &localError);
- }
-
- /* set db_version if not already set */
- int64_t db_version = _SecRevocationDbGetSchemaVersion(dbc->db, dbc, NULL);
- if (db_version <= 0) {
- ok = ok && _SecRevocationDbSetSchemaVersion(dbc, kSecRevocationDbSchemaVersion, &localError);
- }
-
- /* set db_format if not already set */
- int64_t db_format = _SecRevocationDbGetUpdateFormat(dbc, NULL);
- if (db_format <= 0) {
- ok = ok && _SecRevocationDbSetUpdateFormat(dbc, kSecRevocationDbUpdateFormat, &localError);
- }
-
- /* purge the in-memory cache */
- SecRevocationDbCachePurge(dbc->db);
-
- dbc->db->updateInProgress = false;
-
- (void) CFErrorPropagate(localError, error);
- return ok;
-}
-
-static bool _SecRevocationDbSerialInGroup(SecRevocationDbConnectionRef dbc,
- CFDataRef serial,
- int64_t groupId,
- CFErrorRef *error) {
- __block bool result = false;
- __block bool ok = true;
- __block CFErrorRef localError = NULL;
- require(dbc && serial, errOut);
- ok &= SecDbWithSQL(dbc->dbconn, selectSerialRecordSQL, &localError, ^bool(sqlite3_stmt *selectSerial) {
- ok &= SecDbBindInt64(selectSerial, 1, groupId, &localError);
- ok &= SecDbBindBlob(selectSerial, 2, CFDataGetBytePtr(serial),
- CFDataGetLength(serial), SQLITE_TRANSIENT, &localError);
- ok &= SecDbStep(dbc->dbconn, selectSerial, &localError, ^(bool *stop) {
- int64_t foundRowId = (int64_t)sqlite3_column_int64(selectSerial, 0);
- result = (foundRowId > 0);
- });
- return ok;
- });
-
-errOut:
- if (!ok || localError) {
- secerror("_SecRevocationDbSerialInGroup failed: %@", localError);
- TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb, TAOperationRead, TAFatalError,
- localError ? CFErrorGetCode(localError) : errSecInternalComponent);
- }
- (void) CFErrorPropagate(localError, error);
- return result;
-}
-
-static bool _SecRevocationDbCertHashInGroup(SecRevocationDbConnectionRef dbc,
- CFDataRef certHash,
- int64_t groupId,
- CFErrorRef *error) {
- __block bool result = false;
- __block bool ok = true;
- __block CFErrorRef localError = NULL;
- require(dbc && certHash, errOut);
- ok &= SecDbWithSQL(dbc->dbconn, selectHashRecordSQL, &localError, ^bool(sqlite3_stmt *selectHash) {
- ok &= SecDbBindInt64(selectHash, 1, groupId, &localError);
- ok = SecDbBindBlob(selectHash, 2, CFDataGetBytePtr(certHash),
- CFDataGetLength(certHash), SQLITE_TRANSIENT, &localError);
- ok &= SecDbStep(dbc->dbconn, selectHash, &localError, ^(bool *stop) {
- int64_t foundRowId = (int64_t)sqlite3_column_int64(selectHash, 0);
- result = (foundRowId > 0);
- });
- return ok;
- });
-
-errOut:
- if (!ok || localError) {
- secerror("_SecRevocationDbCertHashInGroup failed: %@", localError);
- TrustdHealthAnalyticsLogErrorCodeForDatabase(TARevocationDb, TAOperationRead, TAFatalError,
- localError ? CFErrorGetCode(localError) : errSecInternalComponent);
- }
- (void) CFErrorPropagate(localError, error);
- return result;
-}
-
-static bool _SecRevocationDbSerialInFilter(SecRevocationDbConnectionRef dbc,
- CFDataRef serialData,
- CFDataRef xmlData) {
- /* N-To-1 filter implementation.
- The 'xmlData' parameter is a flattened XML dictionary,
- containing 'xor' and 'params' keys. First order of
- business is to reconstitute the blob into components.
- */
- bool result = false;
- CFRetainSafe(xmlData);
- CFDataRef propListData = xmlData;
- /* Expand data blob if needed */
- CFDataRef inflatedData = copyInflatedData(propListData);
- if (inflatedData) {
- CFReleaseSafe(propListData);
- propListData = inflatedData;
- }
- CFDataRef xor = NULL;
- CFArrayRef params = NULL;
- CFPropertyListRef nto1 = CFPropertyListCreateWithData(kCFAllocatorDefault, propListData, 0, NULL, NULL);
- if (nto1) {
- xor = (CFDataRef)CFDictionaryGetValue((CFDictionaryRef)nto1, CFSTR("xor"));
- params = (CFArrayRef)CFDictionaryGetValue((CFDictionaryRef)nto1, CFSTR("params"));
- }
- uint8_t *hash = (xor) ? (uint8_t*)CFDataGetBytePtr(xor) : NULL;
- CFIndex hashLen = (hash) ? CFDataGetLength(xor) : 0;
- uint8_t *serial = (serialData) ? (uint8_t*)CFDataGetBytePtr(serialData) : NULL;
- CFIndex serialLen = (serial) ? CFDataGetLength(serialData) : 0;
-
- require(hash && serial && params, errOut);
-
- const uint32_t FNV_OFFSET_BASIS = 2166136261;
- const uint32_t FNV_PRIME = 16777619;
- bool notInHash = false;
- CFIndex ix, count = CFArrayGetCount(params);
- for (ix = 0; ix < count; ix++) {
- int32_t param;
- CFNumberRef cfnum = (CFNumberRef)CFArrayGetValueAtIndex(params, ix);
- if (!isNumber(cfnum) ||
- !CFNumberGetValue(cfnum, kCFNumberSInt32Type, ¶m)) {
- secinfo("validupdate", "error processing filter params at index %ld", (long)ix);
- continue;
- }
- /* process one param */
- uint32_t hval = FNV_OFFSET_BASIS ^ param;
- CFIndex i = serialLen;
- while (i > 0) {
- hval = ((hval ^ (serial[--i])) * FNV_PRIME) & 0xFFFFFFFF;
- }
- hval = hval % (hashLen * 8);
- if ((hash[hval/8] & (1 << (hval % 8))) == 0) {
- notInHash = true; /* definitely not in hash */
- break;
- }
- }
- if (!notInHash) {
- /* probabilistically might be in hash if we get here. */
- result = true;
- }
-
-errOut:
- CFReleaseSafe(nto1);
- CFReleaseSafe(propListData);
- return result;
-}
-
-static SecValidInfoRef _SecRevocationDbValidInfoForCertificate(SecRevocationDbConnectionRef dbc,
- SecCertificateRef certificate,
- CFDataRef issuerHash,
- CFErrorRef *error) {
- __block CFErrorRef localError = NULL;
- __block SecValidInfoFlags flags = 0;
- __block SecValidInfoFormat format = kSecValidInfoFormatUnknown;
- __block CFDataRef data = NULL;
-
- bool matched = false;
- bool isOnList = false;
- int64_t groupId = 0;
- CFDataRef serial = NULL;
- CFDataRef certHash = NULL;
- CFDateRef notBeforeDate = NULL;
- CFDateRef notAfterDate = NULL;
- CFDataRef names = NULL;
- CFDataRef policies = NULL;
- SecValidInfoRef result = NULL;
-
- require((serial = SecCertificateCopySerialNumberData(certificate, NULL)) != NULL, errOut);
- require((certHash = SecCertificateCopySHA256Digest(certificate)) != NULL, errOut);
- require_quiet((groupId = _SecRevocationDbGroupIdForIssuerHash(dbc, issuerHash, &localError)) > 0, errOut);
-
- /* Look up the group record to determine flags and format. */
- format = _SecRevocationDbGetGroupFormat(dbc, groupId, &flags, &data, &policies, &localError);
-
- if (format == kSecValidInfoFormatUnknown) {
- /* No group record found for this issuer. Don't return a SecValidInfoRef */
- goto errOut;
- }
- else if (format == kSecValidInfoFormatSerial) {
- /* Look up certificate's serial number in the serials table. */
- matched = _SecRevocationDbSerialInGroup(dbc, serial, groupId, &localError);
- }
- else if (format == kSecValidInfoFormatSHA256) {
- /* Look up certificate's SHA-256 hash in the hashes table. */
- matched = _SecRevocationDbCertHashInGroup(dbc, certHash, groupId, &localError);
- }
- else if (format == kSecValidInfoFormatNto1) {
- /* Perform a Bloom filter match against the serial. If matched is false,
- then the cert is definitely not in the list. But if matched is true,
- we don't know for certain, so we would need to check OCSP. */
- matched = _SecRevocationDbSerialInFilter(dbc, serial, data);
- }
-
- if (matched) {
- /* Found a specific match for this certificate. */
- secdebug("validupdate", "Valid db matched certificate: %@, format=%d, flags=0x%lx",
- certHash, format, flags);
- isOnList = true;
- }
-
- /* If supplemental constraints are present for this issuer, then we always match. */
- if ((flags & kSecValidInfoDateConstraints) &&
- (_SecRevocationDbCopyDateConstraints(dbc, groupId, ¬BeforeDate, ¬AfterDate, &localError))) {
- secdebug("validupdate", "Valid db matched supplemental date constraints for groupId %lld: nb=%@, na=%@",
- (long long)groupId, notBeforeDate, notAfterDate);
- }
-
-
- /* Return SecValidInfo for certificates for which an issuer entry is found. */
- result = SecValidInfoCreate(format, flags, isOnList,
- certHash, issuerHash, /*anchorHash*/ NULL,
- notBeforeDate, notAfterDate,
- names, policies);
-
- if (result && SecIsAppleTrustAnchor(certificate, 0)) {
- /* Prevent a catch-22. */
- secdebug("validupdate", "Valid db match for Apple trust anchor: %@, format=%d, flags=0x%lx",
- certHash, format, flags);
- CFReleaseNull(result);
- }
-
-errOut:
- (void) CFErrorPropagate(localError, error);
- CFReleaseSafe(data);
- CFReleaseSafe(certHash);
- CFReleaseSafe(serial);
- CFReleaseSafe(notBeforeDate);
- CFReleaseSafe(notAfterDate);
- CFReleaseSafe(names);
- CFReleaseSafe(policies);
- return result;
-}
-
-static SecValidInfoRef _SecRevocationDbCopyMatching(SecRevocationDbConnectionRef dbc,
- SecCertificateRef certificate,
- SecCertificateRef issuer) {
- SecValidInfoRef result = NULL;
- CFErrorRef error = NULL;
- CFDataRef issuerHash = NULL;
-
- require(dbc && certificate && issuer, errOut);
- require(issuerHash = SecCertificateCopySHA256Digest(issuer), errOut);
-
- /* Check for the result in the cache. */
- result = SecRevocationDbCacheRead(dbc->db, certificate, issuerHash);
-
- /* Upon cache miss, get the result from the database and add it to the cache. */
- if (!result) {
- result = _SecRevocationDbValidInfoForCertificate(dbc, certificate, issuerHash, &error);
- SecRevocationDbCacheWrite(dbc->db, result);
- }
-
-errOut:
- CFReleaseSafe(issuerHash);
- CFReleaseSafe(error);
- return result;
-}
-
-/* Return the update source as a retained CFStringRef.
- If the value cannot be obtained, NULL is returned.
-*/
-CFStringRef SecRevocationDbCopyUpdateSource(void) {
- __block CFStringRef result = NULL;
- SecRevocationDbWith(^(SecRevocationDbRef db) {
- (void) SecRevocationDbPerformRead(db, NULL, ^bool(SecRevocationDbConnectionRef dbc, CFErrorRef *blockError) {
- result = _SecRevocationDbCopyUpdateSource(dbc, blockError);
- return (bool)result;
- });
- });
- return result;
-}
-
-/* Set the next update value for the revocation database.
- (This function is expected to be called only by the database
- maintainer, normally the system instance of trustd. If the
- caller does not have write access, this is a no-op.)
-*/
-bool SecRevocationDbSetNextUpdateTime(CFAbsoluteTime nextUpdate, CFErrorRef *error) {
- __block bool ok = true;
- __block CFErrorRef localError = NULL;
- SecRevocationDbWith(^(SecRevocationDbRef rdb) {
- ok &= SecRevocationDbPerformWrite(rdb, &localError, ^bool(SecRevocationDbConnectionRef dbc, CFErrorRef *blockError) {
- return _SecRevocationDbSetNextUpdateTime(dbc, nextUpdate, blockError);
- });
- });
- (void) CFErrorPropagate(localError, error);
- return ok;
-}
-
-/* Return the next update value as a CFAbsoluteTime.
- If the value cannot be obtained, -1 is returned.
-*/
-CFAbsoluteTime SecRevocationDbGetNextUpdateTime(void) {
- __block CFAbsoluteTime result = -1;
- SecRevocationDbWith(^(SecRevocationDbRef db) {
- (void) SecRevocationDbPerformRead(db, NULL, ^bool(SecRevocationDbConnectionRef dbc, CFErrorRef *blockError) {
- result = _SecRevocationDbGetNextUpdateTime(dbc, blockError);
- return true;
- });
- });
- return result;
-}
-
-/* Return the serial background queue for database updates.
- If the queue cannot be obtained, NULL is returned.
-*/
-dispatch_queue_t SecRevocationDbGetUpdateQueue(void) {
- __block dispatch_queue_t result = NULL;
- SecRevocationDbWith(^(SecRevocationDbRef db) {
- result = (db) ? db->update_queue : NULL;
- });
- return result;
-}
-
-/* Release all connections to the revocation database.
-*/
-void SecRevocationDbReleaseAllConnections(void) {
- SecRevocationDbWith(^(SecRevocationDbRef db) {
- SecDbReleaseAllConnections((db) ? db->db : NULL);
- });
-}
-
-/* === SecRevocationDb API === */
-
-/* Given a certificate and its issuer, returns a SecValidInfoRef if the
- valid database contains matching info; otherwise returns NULL.
- Caller must release the returned SecValidInfoRef when finished.
-*/
-SecValidInfoRef SecRevocationDbCopyMatching(SecCertificateRef certificate,
- SecCertificateRef issuer) {
- __block SecValidInfoRef result = NULL;
- SecRevocationDbWith(^(SecRevocationDbRef db) {
- (void) SecRevocationDbPerformRead(db, NULL, ^bool(SecRevocationDbConnectionRef dbc, CFErrorRef *blockError) {
- result = _SecRevocationDbCopyMatching(dbc, certificate, issuer);
- return (bool)result;
- });
- });
- return result;
-}
-
-/* Given an issuer, returns true if an entry for this issuer exists in
- the database (i.e. a known CA). If the provided certificate is NULL,
- or its entry is not found, the function returns false.
-*/
-bool SecRevocationDbContainsIssuer(SecCertificateRef issuer) {
- if (!issuer) {
- return false;
- }
- __block bool result = false;
- SecRevocationDbWith(^(SecRevocationDbRef db) {
- (void) SecRevocationDbPerformRead(db, NULL, ^bool(SecRevocationDbConnectionRef dbc, CFErrorRef *blockError) {
- CFDataRef issuerHash = SecCertificateCopySHA256Digest(issuer);
- int64_t groupId = _SecRevocationDbGroupIdForIssuerHash(dbc, issuerHash, blockError);
- CFReleaseSafe(issuerHash);
- result = (groupId > 0);
- return result;
- });
- });
- return result;
-}
-
-/* Return the current version of the revocation database.
- A version of 0 indicates an empty database which must be populated.
- If the version cannot be obtained, -1 is returned.
-*/
-CFIndex SecRevocationDbGetVersion(void) {
- __block CFIndex result = -1;
- SecRevocationDbWith(^(SecRevocationDbRef db) {
- (void) SecRevocationDbPerformRead(db, NULL, ^bool(SecRevocationDbConnectionRef dbc, CFErrorRef *blockError) {
- result = (CFIndex)_SecRevocationDbGetVersion(dbc, blockError);
- return (result >= 0);
- });
- });
- return result;
-}
-
-/* Return the current schema version of the revocation database.
- A version of 0 indicates an empty database which must be populated.
- If the schema version cannot be obtained, -1 is returned.
- */
-CFIndex SecRevocationDbGetSchemaVersion(void) {
- __block CFIndex result = -1;
- SecRevocationDbWith(^(SecRevocationDbRef db) {
- result = (CFIndex)_SecRevocationDbGetSchemaVersion(db, NULL, NULL);
- });
- return result;
-}
-
-/* Return the current update format of the revocation database.
- A version of 0 indicates the format was unknown.
- If the update format cannot be obtained, -1 is returned.
- */
-CFIndex SecRevocationDbGetUpdateFormat(void) {
- __block CFIndex result = -1;
- SecRevocationDbWith(^(SecRevocationDbRef db) {
- (void) SecRevocationDbPerformRead(db, NULL, ^bool(SecRevocationDbConnectionRef dbc, CFErrorRef *blockError) {
- result = (CFIndex)_SecRevocationDbGetUpdateFormat(dbc, blockError);
- return (result >= 0);
- });
- });
- return result;
-}
-
-// MARK: -
-// MARK: Digests
-/*
- ==============================================================================
- Digest computation
- ==============================================================================
-*/
-
-/* Returns array of SHA-256 hashes computed over the contents of a valid.sqlite3
- database, in the order specified by the valid-server-api documentation. The
- resulting hashes can be compared against those in the update's 'hash' array.
-
- Hash 0: full database (all fields in initial Valid specification)
- Hash 1: all issuer_hash arrays, plus not-after and not-before dates for each
- Hash 2: subset of issuer_hash arrays where the no-ca-v2 flag is set
-*/
-static CF_RETURNS_RETAINED CFArrayRef SecRevocationDbComputeFullContentDigests(SecRevocationDbConnectionRef dbc, CFErrorRef *error) {
- if (!dbc) { return NULL; }
- __block bool ok = true;
- __block CFErrorRef localError = NULL;
- __block uint32_t N[4]={0,0,0,0};
- __block CC_SHA256_CTX hash0_ctx, hash1_ctx, hash2_ctx;
- CC_SHA256_Init(&hash0_ctx);
- CC_SHA256_Init(&hash1_ctx);
- CC_SHA256_Init(&hash2_ctx);
-
- // Add version, check-again, and update (array count) fields as array of N.
- // (Note: 'N' is defined as "unsigned 32-bit integer in network byte order")
- int64_t version = _SecRevocationDbGetVersion(dbc, NULL);
- N[0] = OSSwapInt32(version & 0xffffffff);
- int64_t interval = _SecRevocationDbGetUpdateInterval(dbc, NULL);
- if (interval < 0) {
- interval = kSecStdUpdateInterval; // if we didn't store it, assume default
- }
- N[1] = OSSwapInt32(interval & 0xffffffff);
- __block int64_t count = 0;
- ok = ok && SecDbWithSQL(dbc->dbconn, CFSTR("SELECT count(*) FROM groups"), &localError, ^bool(sqlite3_stmt *selectGroupsCount) {
- ok = ok && SecDbStep(dbc->dbconn, selectGroupsCount, &localError, ^void(bool *stop) {
- count = sqlite3_column_int64(selectGroupsCount, 0);
- *stop = true;
- });
- return ok;
- });
- N[2] = OSSwapInt32(count & 0xffffffff);
- CC_SHA256_Update(&hash0_ctx, N, sizeof(uint32_t) * 3);
-
- // Sort the update array in order of minimum 'issuer-hash' entry.
- // The issuer-hash array is first sorted to determine the lowest issuer-hash,
- // and that value is used to sort the update entries.
- //
- // For our sqlite database, recreating the update array order means fetching
- // the groupid column from the issuers table after sorting on issuer_hash,
- // using DISTINCT to remove duplicates. Then, for each returned groupid, we
- // obtain its list of issuers, its list of serials or hashes, and other data.
-
- ok = ok && SecDbWithSQL(dbc->dbconn, CFSTR("SELECT DISTINCT groupid FROM issuers ORDER BY issuer_hash ASC"), &localError, ^bool(sqlite3_stmt *selectGroups) {
- ok = ok && SecDbForEach(dbc->dbconn, selectGroups, &localError, ^bool(int row_index) {
- __block int64_t groupId = sqlite3_column_int64(selectGroups, 0);
- ok = ok && SecDbWithSQL(dbc->dbconn, CFSTR("SELECT flags,format,data FROM groups WHERE groupid=?"), &localError, ^bool(sqlite3_stmt *selectGroup) {
- ok = ok && SecDbBindInt64(selectGroup, 1, groupId, &localError);
- ok = ok && SecDbStep(dbc->dbconn, selectGroup, &localError, ^(bool *stop) {
- // per-group info is hashed in the following order:
- // - issuer_hash array data (sorted)
- // - flag bytes, in order listed below
- // - format string [serial|sha256|nto1]
- // - add array data (sorted), if [serial|sha256]
- // - params (if present)
- // - xor data (if present)
-
- int64_t flags = sqlite3_column_int64(selectGroup, 0);
- bool noCAv2 = (flags & kSecValidInfoNoCAv2Check);
-
- // instead of recreating the issuer_hash array in memory,
- // hash its length (item count) followed by the data of each issuer_hash.
- ok = ok && SecDbWithSQL(dbc->dbconn, CFSTR("SELECT count(*) FROM issuers WHERE groupid=?"), &localError, ^bool(sqlite3_stmt *selectIssuersCount) {
- ok = ok && SecDbBindInt64(selectIssuersCount, 1, groupId, &localError);
- ok = ok && SecDbStep(dbc->dbconn, selectIssuersCount, &localError, ^void(bool *stop) {
- count = sqlite3_column_int64(selectIssuersCount, 0);
- *stop = true;
- });
- return ok;
- });
- uint32_t n = OSSwapInt32(count & 0xffffffff);
- CC_SHA256_Update(&hash0_ctx, &n, sizeof(uint32_t));
- CC_SHA256_Update(&hash1_ctx, &n, sizeof(uint32_t));
- if (noCAv2) {
- CC_SHA256_Update(&hash2_ctx, &n, sizeof(uint32_t));
- }
-
- // process issuer_hash entries for this group
- ok = ok && SecDbWithSQL(dbc->dbconn, CFSTR("SELECT issuer_hash FROM issuers WHERE groupid=? ORDER BY issuer_hash ASC"), &localError, ^bool(sqlite3_stmt *selectIssuerHash) {
- ok = ok && SecDbBindInt64(selectIssuerHash, 1, groupId, &localError);
- ok = ok && SecDbForEach(dbc->dbconn, selectIssuerHash, &localError, ^bool(int row_index) {
- uint8_t *p = (uint8_t *)sqlite3_column_blob(selectIssuerHash, 0);
- CFDataRef data = NULL;
- if (p != NULL) {
- CFIndex length = (CFIndex)sqlite3_column_bytes(selectIssuerHash, 0);
- data = CFDataCreate(kCFAllocatorDefault, p, length);
- }
- if (data != NULL) {
- hashData(data, &hash0_ctx);
- hashData(data, &hash1_ctx);
- if (noCAv2) {
- hashData(data, &hash2_ctx);
- }
- CFRelease(data);
- } else {
- ok = false;
- }
- return ok;
- });
- return ok;
- });
-
- // process flags, converting to array of unsigned 8-bit values, either 0 or 1:
- // [ complete, check-ocsp, known-intermediates-only, no-ca, overridable, require-ct, valid ]
- uint8_t C[8]={0,0,0,0,0,0,0,0};
- C[0] = (flags & kSecValidInfoComplete) ? 1 : 0;
- C[1] = (flags & kSecValidInfoCheckOCSP) ? 1 : 0;
- C[2] = (flags & kSecValidInfoKnownOnly) ? 1 : 0;
- C[3] = (flags & kSecValidInfoNoCACheck) ? 1 : 0;
- C[4] = (flags & kSecValidInfoOverridable) ? 1 : 0;
- C[5] = (flags & kSecValidInfoRequireCT) ? 1 : 0;
- C[6] = (flags & kSecValidInfoAllowlist) ? 1 : 0;
- CC_SHA256_Update(&hash0_ctx, C, sizeof(uint8_t) * 7);
-
- // process format, converting integer to string value [serial|sha256|nto1]
- SecValidInfoFormat format = (SecValidInfoFormat)sqlite3_column_int(selectGroup, 1);
- switch (format) {
- case kSecValidInfoFormatSerial:
- hashString(CFSTR("serial"), &hash0_ctx);
- break;
- case kSecValidInfoFormatSHA256:
- hashString(CFSTR("sha256"), &hash0_ctx);
- break;
- case kSecValidInfoFormatNto1:
- hashString(CFSTR("nto1"), &hash0_ctx);
- break;
- case kSecValidInfoFormatUnknown:
- default:
- ok = false; // unexpected format values are not allowed
- break;
- }
- // process 'add' array (serial or sha256 format).
- // instead of recreating the 'add' array in memory,
- // hash its length (item count) followed by the data of each entry.
- CFStringRef arrayCountSql = NULL;
- if (format == kSecValidInfoFormatSerial) {
- arrayCountSql = CFSTR("SELECT count(*) FROM serials WHERE groupid=?");
- } else if (format == kSecValidInfoFormatSHA256) {
- arrayCountSql = CFSTR("SELECT count(*) FROM hashes WHERE groupid=?");
- }
- if (arrayCountSql) {
- ok = ok && SecDbWithSQL(dbc->dbconn, arrayCountSql, &localError, ^bool(sqlite3_stmt *selectAddCount) {
- ok = ok && SecDbBindInt64(selectAddCount, 1, groupId, &localError);
- ok = ok && SecDbStep(dbc->dbconn, selectAddCount, &localError, ^void(bool *stop) {
- count = sqlite3_column_int64(selectAddCount, 0);
- *stop = true;
- });
- return ok;
- });
- n = OSSwapInt32(count & 0xffffffff);
- CC_SHA256_Update(&hash0_ctx, &n, sizeof(uint32_t));
- }
- // process data entries for this group
- CFStringRef arrayDataSql = NULL;
- if (format == kSecValidInfoFormatSerial) {
- arrayDataSql = CFSTR("SELECT serial FROM serials WHERE groupid=? ORDER BY serial ASC");
- } else if (format == kSecValidInfoFormatSHA256) {
- arrayDataSql = CFSTR("SELECT sha256 FROM hashes WHERE groupid=? ORDER by sha256 ASC");
- }
- if (arrayDataSql) {
- ok = ok && SecDbWithSQL(dbc->dbconn, arrayDataSql, &localError, ^bool(sqlite3_stmt *selectAddData) {
- ok = ok && SecDbBindInt64(selectAddData, 1, groupId, &localError);
- ok = ok && SecDbForEach(dbc->dbconn, selectAddData, &localError, ^bool(int row_index) {
- uint8_t *p = (uint8_t *)sqlite3_column_blob(selectAddData, 0);
- CFDataRef data = NULL;
- if (p != NULL) {
- CFIndex length = (CFIndex)sqlite3_column_bytes(selectAddData, 0);
- data = CFDataCreate(kCFAllocatorDefault, p, length);
- }
- if (data != NULL) {
- hashData(data, &hash0_ctx);
- CFRelease(data);
- } else {
- ok = false;
- }
- return ok;
- });
- return ok;
- });
- }
-
- // process params and xor data, if format is nto1
- if (format == kSecValidInfoFormatNto1) {
- uint8_t *p = (uint8_t *)sqlite3_column_blob(selectGroup, 2);
- CFDataRef data = NULL;
- if (p != NULL) {
- CFIndex length = (CFIndex)sqlite3_column_bytes(selectGroup, 2);
- data = CFDataCreate(kCFAllocatorDefault, p, length);
- }
- if (data != NULL) {
- // unpack params and xor data
- CFDataRef xor = NULL;
- CFArrayRef params = NULL;
- if (copyFilterComponents(data, &xor, ¶ms)) {
- hashArray(params, &hash0_ctx);
- hashData(xor, &hash0_ctx);
- } else {
- ok = false;
- }
- CFReleaseSafe(xor);
- CFReleaseSafe(params);
- }
- CFReleaseSafe(data);
- }
-
- // process date constraints [not-after, not-before]
- CFAbsoluteTime notBefore = -3155760000.0; /* default: 1901-01-01 00:00:00-0000 */
- CFAbsoluteTime notAfter = 31556908800.0; /* default: 3001-01-01 00:00:00-0000 */
- CFDateRef notBeforeDate = NULL;
- CFDateRef notAfterDate = NULL;
- if (_SecRevocationDbCopyDateConstraints(dbc, groupId, ¬BeforeDate, ¬AfterDate, &localError)) {
- if (notBeforeDate) {
- notBefore = CFDateGetAbsoluteTime(notBeforeDate);
- CFReleaseNull(notBeforeDate);
- }
- if (notAfterDate) {
- notAfter = CFDateGetAbsoluteTime(notAfterDate);
- CFReleaseNull(notAfterDate);
- }
- }
- double nb = htond(notBefore);
- double na = htond(notAfter);
- CC_SHA256_Update(&hash1_ctx, &na, sizeof(double));
- CC_SHA256_Update(&hash1_ctx, &nb, sizeof(double));
-
- *stop = true;
- }); // per-group step
- return ok;
- }); // per-group select
- return ok;
- }); // for each group in list
- return ok;
- }); // select full group list
-
- CFMutableArrayRef result = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
- if (result) {
- uint8_t digest[CC_SHA256_DIGEST_LENGTH];
- CFDataRef data = NULL;
- CC_SHA256_Final(digest, &hash0_ctx);
- if ((data = CFDataCreate(NULL, (const UInt8 *)digest, CC_SHA256_DIGEST_LENGTH)) != NULL) {
- CFArrayAppendValue(result, data);
- CFReleaseNull(data);
- }
- CC_SHA256_Final(digest, &hash1_ctx);
- if ((data = CFDataCreate(NULL, (const UInt8 *)digest, CC_SHA256_DIGEST_LENGTH)) != NULL) {
- CFArrayAppendValue(result, data);
- CFReleaseNull(data);
- }
- CC_SHA256_Final(digest, &hash2_ctx);
- if ((data = CFDataCreate(NULL, (const UInt8 *)digest, CC_SHA256_DIGEST_LENGTH)) != NULL) {
- CFArrayAppendValue(result, data);
- CFReleaseNull(data);
- }
- }
- (void) CFErrorPropagate(localError, error);
- return result;
-}
-
-static bool SecRevocationDbComputeDigests(SecRevocationDbConnectionRef dbc, CFErrorRef *error) {
- secinfo("validupdate", "Started verifying db content");
- bool result = true;
- CFArrayRef expectedList = _SecRevocationDbCopyHashes(dbc, error);
- CFIndex expectedCount = (expectedList) ? CFArrayGetCount(expectedList) : 0;
- if (expectedCount < 1) {
- secinfo("validupdate", "Unable to read db_hash values");
- CFReleaseNull(expectedList);
- return result; // %%%% this will happen on first update, when db_hash isn't there
- }
- CFArrayRef computedList = SecRevocationDbComputeFullContentDigests(dbc, error);
- CFIndex computedCount = (computedList) ? CFArrayGetCount(computedList) : 0;
- for (CFIndex idx = 0; idx < expectedCount; idx++) {
- if (idx >= computedCount) {
- continue; // server provided additional hash value that we don't yet compute
- }
- CFDataRef expectedHash = (CFDataRef)CFArrayGetValueAtIndex(expectedList, idx);
- CFDataRef computedHash = (CFDataRef)CFArrayGetValueAtIndex(computedList, idx);
- if (!CFEqualSafe(expectedHash, computedHash)) {
- result = false;
- break;
- }
- }
- if (!result) {
- secinfo("validupdate", "Expected: %@", expectedList);
- secinfo("validupdate", "Computed: %@", computedList);
- }
- secinfo("validupdate", "Finished verifying db content; result=%s",
- (result) ? "SUCCESS" : "FAIL");
- CFReleaseSafe(expectedList);
- CFReleaseSafe(computedList);
- return result;
-}
-