#include "trust/trustd/SecRevocationDb.h"
#include "trust/trustd/OTATrustUtilities.h"
#include "trust/trustd/SecRevocationNetworking.h"
#include "trust/trustd/SecTrustLoggingServer.h"
#include "trust/trustd/trustdFileLocations.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 <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>
static bool hashCFThing(CFTypeRef thing, CC_SHA256_CTX* hash_ctx);
static int compareCFStrings(const void *p, const void *q) {
CFStringRef str1 = *(CFStringRef *)p;
CFStringRef str2 = *(CFStringRef *)q;
if (!(isString(str1) && isString(str2))) {
return -1;
}
CFComparisonResult result = CFStringCompare(str1, str2, 0);
if (result == kCFCompareLessThan) {
return -1;
} else if (result == kCFCompareGreaterThan) {
return 1;
}
return 0;
}
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) {
uint32_t c = (uint32_t)(strlen(strbuf) & 0xFFFFFFFF);
uint32_t n = OSSwapInt32(c);
CC_SHA256_Update(hash_ctx, &n, sizeof(uint32_t));
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) {
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;
}
static uint32_t SecSwapInt32Ptr(const void* P) {
uint32_t _i=0; memcpy(&_i,P,sizeof(_i)); return OSSwapInt32(_i);
}
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");
#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,
};
#define kSecMinUpdateInterval (60.0 * 5)
#define kSecStdUpdateInterval (60.0 * 60 * 3)
#define kSecMaxUpdateInterval (60.0 * 60 * 24 * 7)
#define kSecRevocationCurUpdateFile "update-current"
#define kSecRevocationDbFileName "valid.sqlite3"
#define kSecRevocationDbReplaceFile ".valid_replace"
#define isDbOwner SecOTAPKIIsSystemTrustd
#define kSecRevocationDbChanged "com.apple.trustd.valid.db-changed"
#define kSecRevocationDbSchemaVersion 7
#define kSecRevocationDbMinSchemaVersion 7
CF_ENUM(CFIndex) {
kSecValidUpdateFormatG1 = 1,
kSecValidUpdateFormatG2 = 2,
kSecValidUpdateFormatG3 = 3
};
#define kSecRevocationDbUpdateFormat 3
#define kSecRevocationDbMinUpdateFormat 2
#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);
CF_RETURNS_RETAINED 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));
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;
}
}
} while (rc == Z_OK && zs.avail_in);
deflateEnd(&zs);
free(buf);
if (rc != Z_STREAM_END) {
CFReleaseSafe(outData);
return NULL;
}
return (CFDataRef)outData;
}
int readValidFile(const char *fileName,
CFDataRef *bytes) { 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; } else {
secnotice("validupdate", "remove (%s): %s", path, strerror(error));
}
} else {
result = true;
}
free(path);
}
return result;
}
static CFDataRef CF_RETURNS_RETAINED createPoliciesData(CFArrayRef policies) {
CFIndex count = (policies) ? CFArrayGetCount(policies) : -1;
if (count < 0 || count > 127) {
return NULL;
}
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) {
bool result = false;
CFRetainSafe(xmlData);
CFDataRef propListData = xmlData;
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;
}
CFAbsoluteTime gUpdateStarted = 0.0;
CFAbsoluteTime gNextUpdate = 0.0;
static CFIndex gUpdateInterval = 0;
static CFIndex gLastVersion = 0;
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;
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;
}
uint32_t dataLength = SecSwapInt32Ptr(p);
bytesRemaining -= sizeof(uint32_t);
p += sizeof(uint32_t);
uint32_t plistCount = 1;
uint32_t plistTotal = 1;
if (format > kSecValidUpdateFormatG2) {
plistCount = SecSwapInt32Ptr(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;
}
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 = SecSwapInt32Ptr(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;
CFTypeRef value = (CFNumberRef)CFDictionaryGetValue((CFDictionaryRef)propertyList,
CFSTR("check-again"));
if (isNumber(value)) {
CFNumberGetValue((CFNumberRef)value, kCFNumberCFIndexType, &interval);
}
value = (CFArrayRef)CFDictionaryGetValue((CFDictionaryRef)propertyList,
CFSTR("hash"));
ok = _SecRevocationDbSetHashes(dbc, (CFArrayRef)value, &localError);
}
if (ok && curVersion < 0) {
plistCount = 0; 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);
}
}
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;
}
if (!SecRevocationDbVerifyUpdate((void *)CFDataGetBytePtr(updateData), CFDataGetLength(updateData))) {
secerror("failed to verify valid update");
TrustdHealthAnalyticsLogErrorCode(TAEventValidUpdate, TAFatalError, errSecVerifyFailed);
return;
}
CFStringRef dbSource = SecRevocationDbCopyUpdateSource();
if (dbSource && updateServer && (kCFCompareEqualTo != CFStringCompare(dbSource, updateServer,
kCFCompareCaseInsensitive))) {
secnotice("validupdate", "switching db source from \"%@\" to \"%@\"", dbSource, updateServer);
}
CFReleaseNull(dbSource);
__block bool ok = true;
__block CFErrorRef localError = NULL;
SecRevocationDbWith(^(SecRevocationDbRef rdb) {
ok &= SecRevocationDbPerformWrite(rdb, &localError, ^bool(SecRevocationDbConnectionRef dbc, CFErrorRef *blockError) {
if (fullUpdate) {
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) {
ok = ok && SecRevocationDbPerformRead(rdb, &localError, ^bool(SecRevocationDbConnectionRef dbc, CFErrorRef *blockError) {
ok = ok && SecRevocationDbComputeDigests(dbc, blockError);
if (!ok) {
(void) SecValidUpdateForceReplaceDatabase();
}
return ok;
});
}
notify_post(kSecRevocationDbChanged);
}
});
(void) SecRevocationDbSetNextUpdateTime(gNextUpdate, NULL);
CFReleaseSafe(localError);
}
static bool SecValidUpdateForceReplaceDatabase(void) {
__block bool result = false;
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) {
CFErrorRef error = NULL;
if (!TrustdChangeFileProtectionToClassD(utf8String, &error)) {
secerror("failed to change replace valid db flag protection class: %@", error);
CFReleaseNull(error);
}
close(fd);
}
});
if (result) {
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 (sNumLocalUpdates > 1) {
secdebug("validupdate", "%d consecutive db resets, ignoring local asset", sNumLocalUpdates);
goto updateExit;
}
if (kCFCompareEqualTo != CFStringCompare(server, kValidUpdateProdServer,
kCFCompareCaseInsensitive)) {
secdebug("validupdate", "non-production server specified, ignoring local asset");
goto updateExit;
}
otapkiRef = SecOTAPKICopyCurrentOTAPKIRef();
if (!otapkiRef) {
goto updateExit;
}
CFIndex assetVersion = SecOTAPKIGetValidSnapshotVersion(otapkiRef);
CFIndex assetFormat = SecOTAPKIGetValidSnapshotFormat(otapkiRef);
if (assetVersion <= version || assetFormat < kSecValidUpdateFormatG3) {
goto updateExit;
}
if (!safeToReplace) {
relaunching = SecValidUpdateForceReplaceDatabase();
goto updateExit;
}
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 {
CFErrorRef localError = NULL;
result = TrustdChangeFileProtectionToClassD(path, &localError);
if (!result) {
secerror("failed to change protection class of copied valid snapshot: %@", localError);
CFReleaseNull(localError);
}
}
});
}
updateExit:
CFReleaseNull(otapkiRef);
if (result) {
sNumLocalUpdates++;
gLastVersion = SecRevocationDbGetVersion();
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; }
if (relaunching) {
result = true;
}
return result;
}
static bool SecValidUpdateSchedule(bool updateEnabled, CFStringRef server, CFIndex version) {
if (SecValidUpdateSatisfiedLocally(server, version, false)) {
return true;
}
if (!updateEnabled) {
secnotice("validupdate", "skipping update");
return false;
}
#if !TARGET_OS_BRIDGE
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 CFStringRef defaultServer = kValidUpdateProdServer;
#endif if (os_variant_has_internal_diagnostics("com.apple.security")) {
defaultServer = kValidUpdateCarryServer;
}
return defaultServer;
#else // TARGET_OS_WATCH || TARGET_OS_BRIDGE
return kValidUpdateProdServer;
#endif
}
static CF_RETURNS_RETAINED CFStringRef SecRevocationDbCopyServer(void) {
CFTypeRef value = CFPreferencesCopyAppValue(kUpdateServerKey, kSecPrefsDomain);
if (!value) {
value = CFPreferencesCopyValue(kUpdateServerKey, kSecPrefsDomain, kCFPreferencesAnyUser, kCFPreferencesCurrentHost);
}
CFStringRef server = NULL;
if (isString(value)) {
server = (CFStringRef)value;
} else {
CFReleaseNull(value);
server = CFRetainSafe(SecRevocationDbGetDefaultServer());
}
return server;
}
void SecRevocationDbInitialize() {
if (!isDbOwner()) { return; }
os_transaction_t transaction = os_transaction_create("com.apple.trustd.valid.initialize");
__block bool initializeDb = false;
WithPathInRevocationInfoDirectory(NULL, ^(const char *utf8String) {
(void)mkpath_np(utf8String, 0755);
});
WithPathInRevocationInfoDirectory(CFSTR(kSecRevocationDbReplaceFile), ^(const char *path) {
struct stat sb;
if (stat(path, &sb) == 0) {
initializeDb = true;
if (remove(path) == -1) {
int error = errno;
secnotice("validupdate", "remove (%s): %s", path, strerror(error));
}
}
});
WithPathInRevocationInfoDirectory(CFSTR(kSecRevocationDbFileName), ^(const char *path) {
if (initializeDb) {
(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;
}
}
});
if (!initializeDb) {
os_release(transaction);
return;
}
CFStringRef server = SecRevocationDbCopyServer();
CFIndex version = 0;
secnotice("validupdate", "initializing database");
if (!SecValidUpdateSatisfiedLocally(server, version, true)) {
#if !TARGET_OS_BRIDGE
(void)SecValidUpdateRequest(SecRevocationDbGetUpdateQueue(), server, version);
#endif
}
CFReleaseSafe(server);
os_release(transaction);
}
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);
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;
}
static CFIndex _SecRevocationDbGetUpdateVersion(CFStringRef server) {
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;
}
CFStringRef db_source = SecRevocationDbCopyUpdateSource();
if (!db_source) {
db_source = (CFStringRef) CFRetain(kValidUpdateProdServer);
}
CFIndex db_version = SecRevocationDbGetSchemaVersion();
CFIndex db_format = SecRevocationDbGetUpdateFormat();
if (db_version < kSecRevocationDbSchemaVersion ||
db_format < kSecRevocationDbUpdateFormat ||
kCFCompareEqualTo != CFStringCompare(server, db_source, kCFCompareCaseInsensitive)) {
version = gLastVersion = 0;
}
CFReleaseNull(db_source);
return version;
}
static bool _SecRevocationDbIsUpdateEnabled(void) {
CFTypeRef value = NULL;
#if !TARGET_OS_WATCH && !TARGET_OS_BRIDGE
bool updateEnabled = true;
#else
bool updateEnabled = false;
#endif
value = (CFBooleanRef)CFPreferencesCopyValue(kUpdateEnabledKey, kSecPrefsDomain, kCFPreferencesAnyUser, kCFPreferencesCurrentHost);
if (isBoolean(value)) {
updateEnabled = CFBooleanGetValue((CFBooleanRef)value);
}
CFReleaseNull(value);
return updateEnabled;
}
static bool _SecRevocationDbCheckNextUpdate(void) {
if (!isDbOwner()) {
return false;
}
CFTypeRef value = NULL;
CFAbsoluteTime now = CFAbsoluteTimeGetCurrent();
CFAbsoluteTime minNextUpdate = now + gUpdateInterval;
gUpdateStarted = now;
if (0 == gNextUpdate) {
gNextUpdate = SecRevocationDbGetNextUpdateTime();
minNextUpdate = now;
if (gNextUpdate < minNextUpdate) {
gNextUpdate = minNextUpdate;
}
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;
}
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");
gNextUpdate = minNextUpdate;
CFStringRef server = SecRevocationDbCopyServer();
CFIndex version = _SecRevocationDbGetUpdateVersion(server);
bool updateEnabled = _SecRevocationDbIsUpdateEnabled();
bool result = SecValidUpdateSchedule(updateEnabled, server, version);
CFReleaseNull(server);
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);
}
bool SecRevocationDbUpdate(CFErrorRef *error)
{
if (!isDbOwner()) {
return SecError(errSecWrPerm, error, CFSTR("Unable to update Valid DB from user agent"));
}
if (!_SecRevocationDbIsUpdateEnabled()) {
return SecError(errSecWrPerm, error, CFSTR("Valid updates not enabled on this device"));
}
CFStringRef server = SecRevocationDbCopyServer();
CFIndex version = _SecRevocationDbGetUpdateVersion(server);
secdebug("validupdate", "will fetch v%lu from \"%@\" now", (unsigned long)version, server);
bool result = SecValidUpdateUpdateNow(SecRevocationDbGetUpdateQueue(), server, version);
CFReleaseNull(server);
return result;
}
bool SecRevocationDbVerifyUpdate(void *update, CFIndex length) {
if (!update || length <= (CFIndex)sizeof(uint32_t)) {
return false;
}
uint32_t plistLength = SecSwapInt32Ptr(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 = SecSwapInt32Ptr(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"), CFSTR("1.2.840.113635.100.6.2.10"), CFSTR("1.2.840.113635.100.6.51"));
if ((status = CMSDecoderCopySignerStatus(cms, 0, policy,
false, &signerStatus, &trust, NULL)) != errSecSuccess) {
secdebug("validupdate", "CMSDecoderCopySignerStatus failed with error %d\n", (int)status);
goto verifyExit;
}
if (signerStatus != kCMSSignerValid) {
secdebug("validupdate", "ERROR: signature did not verify (signer status %d)\n", (int)signerStatus);
status = errSecInvalidSignature;
goto verifyExit;
}
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;
CFTypeRef value = (CFNumberRef)CFPreferencesCopyValue(kUpdateIntervalKey, kSecPrefsDomain, kCFPreferencesAnyUser, kCFPreferencesCurrentHost);
if (isNumber(value)) {
CFNumberGetValue((CFNumberRef)value, kCFNumberCFIndexType, &interval);
}
CFReleaseNull(value);
if (interval <= 0) {
interval = kSecStdUpdateInterval;
}
if (interval < kSecMinUpdateInterval) {
interval = kSecMinUpdateInterval;
} else if (interval > kSecMaxUpdateInterval) {
interval = kSecMaxUpdateInterval;
}
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;
}
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) {
version = chunkVersion;
}
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; ok = true; }
setVersionAndExit:
if (outVersion) {
*outVersion = version;
}
(void) CFErrorPropagate(localError, error);
return ok;
}
#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")
static SecDbRef SecRevocationDbCreate(CFStringRef path) {
__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) {
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()) {
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()) {
SecRevocationDbUpdateSchema(kSecRevocationDb);
}
}
});
if (kSecRevocationDb->updateInProgress) {
return; }
dbJob(kSecRevocationDb);
}
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;
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); if (0 <= (ix = CFArrayGetFirstIndexOfValue(db->info_cache_list,
CFRangeMake(0, CFArrayGetCount(db->info_cache_list)),
cacheKey))) {
result = (SecValidInfoRef)CFDictionaryGetValue(db->info_cache, cacheKey);
if (CFEqualSafe(result->certHash, certHash) && CFEqualSafe(result->issuerHash, issuerHash)) {
CFArrayRemoveValueAtIndex(db->info_cache_list, ix);
CFArrayAppendValue(db->info_cache_list, cacheKey);
secdebug("validcache", "cache hit: %@", cacheKey);
} else {
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); 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)) {
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;
}
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) {
__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) {
__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) {
__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) {
__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) {
__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) {
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;
}
}
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;
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;
}
secdebug("validupdate", "updating db schema from v%lld to v%lld",
(long long)db_version, (long long)kSecRevocationDbSchemaVersion);
if (ok && db_version < 5) {
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) {
secdebug("validupdate", "applied schema update to v6 (%s)", (ok) ? "ok" : "failed!");
if (db_version > 0) {
SecValidUpdateForceReplaceDatabase();
}
}
if (ok && db_version < 7) {
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) {
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) {
__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;
dbc->db->unsupportedVersion = false;
}
(void) CFErrorPropagate(localError, error);
return ok;
}
static CFStringRef _SecRevocationDbCopyUpdateSource(SecRevocationDbConnectionRef dbc, CFErrorRef *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) {
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) {
__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) {
bool ok = (dbc != NULL);
CFErrorRef localError = NULL;
ok = ok && SecDbExec(dbc->dbconn, deleteAllEntriesSQL, &localError);
secnotice("validupdate", "resetting database, result: %d (expected 1)", (ok) ? 1 : 0);
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) {
if (!issuers || groupId < 0) {
return false;
}
__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);
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) {
SecValidInfoFormat format = kSecValidInfoFormatUnknown;
if (groupId >= 0) {
format = _SecRevocationDbGetGroupFormat(dbc, groupId, NULL, NULL, NULL, NULL);
}
if (format == kSecValidInfoFormatUnknown && data != NULL) {
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) {
if (!dict || groupId < 0) {
return false;
}
__block bool ok = (dbc != NULL);
__block CFErrorRef localError = NULL;
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) {
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);
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
}
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) {
ok = ok && SecDbBindInt64(insertIdentifier, 1,
groupId, &localError);
ok = ok && SecDbBindBlob(insertIdentifier, 2,
CFDataGetBytePtr(identifierData),
CFDataGetLength(identifierData),
SQLITE_TRANSIENT, &localError);
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) {
__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) {
ok &= SecDbBindInt64(selectDates, 1, groupId, &localError);
ok = ok && SecDbStep(dbc->dbconn, selectDates, &localError, ^(bool *stop) {
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;
});
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) {
__block bool ok = true;
__block CFErrorRef localError = NULL;
__block CFAbsoluteTime notBefore = -3155760000.0;
__block CFAbsoluteTime notAfter = 31556908800.0;
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;
}
if (!(notBeforeDate && notAfterDate)) {
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) {
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) {
__block bool ok = true;
__block CFErrorRef localError = NULL;
CFArrayRef policies = (CFArrayRef)CFDictionaryGetValue(dict, CFSTR("policies"));
if (!isArray(policies)) {
return ok;
}
__block CFDataRef data = createPoliciesData(policies);
ok = data && SecDbWithSQL(dbc->dbconn, updateGroupPoliciesSQL, &localError, ^bool(sqlite3_stmt *updatePolicies) {
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) {
return true;
}
static bool _SecRevocationDbUpdateIssuerConstraints(SecRevocationDbConnectionRef dbc, int64_t groupId, CFDictionaryRef dict, CFErrorRef *error) {
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) {
__block bool ok = (dbc != NULL);
__block SecValidInfoFormat format = 0;
__block CFErrorRef localError = NULL;
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) {
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 (!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) {
bool result = false;
bool xorProvided = false;
bool paramsProvided = false;
bool missingData = false;
if (!dict || !xmlData) {
return result;
}
*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;
}
CFPropertyListRef nto1Current = NULL;
CFMutableDictionaryRef nto1Update = CFDictionaryCreateMutable(kCFAllocatorDefault, 0,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
if (!nto1Update) {
return result;
}
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"));
}
if (xorProvided) {
CFDataRef xorNew = NULL;
if (xorCurrent) {
CFIndex xorUpdateLen = CFDataGetLength(xorUpdate);
CFMutableDataRef xor = CFDataCreateMutableCopy(NULL, 0, xorCurrent);
if (xor && xorUpdateLen > 0) {
CFDataSetLength(xor, xorUpdateLen);
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) {
CFDictionaryAddValue(nto1Update, CFSTR("xor"), xorCurrent);
} else {
secdebug("validupdate", "Failed to get current filter data");
missingData = true;
}
if (paramsProvided) {
CFDictionaryAddValue(nto1Update, CFSTR("params"), paramsUpdate);
} else if (paramsCurrent) {
CFDictionaryAddValue(nto1Update, CFSTR("params"), paramsCurrent);
} else {
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);
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) {
if (!dict) {
return groupId;
}
__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) {
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);
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;
}
}
isFormatChange = (formatUpdate > kSecValidInfoFormatUnknown &&
formatUpdate != format &&
groupId >= 0);
if (isFormatChange) {
secdebug("validupdate", "group %lld format change from %d to %d",
(long long)groupId, format, formatUpdate);
ok = ok && SecDbWithSQL(dbc->dbconn, deleteGroupRecordSQL, &localError, ^bool(sqlite3_stmt *deleteResponse) {
ok = ok && SecDbBindInt64(deleteResponse, 1, groupId, &localError);
ok = ok && SecDbStep(dbc->dbconn, deleteResponse, &localError, NULL);
return ok;
});
}
ok = ok && SecDbWithSQL(dbc->dbconn, insertGroupRecordSQL, &localError, ^bool(sqlite3_stmt *insertGroup) {
if (ok && (!isFormatChange) && (groupId >= 0)) {
ok = SecDbBindInt64(insertGroup, 1, groupId, &localError);
if (!ok) {
secdebug("validupdate", "failed to set groupId %lld", (long long)groupId);
}
}
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);
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);
}
CFTypeRef policiesValue = (CFArrayRef)CFDictionaryGetValue(dict, CFSTR("policies"));
if (isArray(policiesValue)) {
(void)_SecRevocationDbUpdateFlags(dict, kBoolTrueKey, kSecValidInfoPolicyConstraints, &flags);
}
(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);
}
}
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);
}
}
CFDataRef xmlData = NULL;
if (ok) {
bool hasFilter = ((formatUpdate == kSecValidInfoFormatNto1) ||
(formatUpdate == kSecValidInfoFormatUnknown &&
format == kSecValidInfoFormatNto1));
if (hasFilter) {
CFDataRef dataValue = data;
if (_SecRevocationDbUpdateFilter(dict, data, &xmlData)) {
dataValue = xmlData;
}
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);
}
}
}
CFDataRef newPoliciesData = NULL;
if (ok) {
CFDataRef policiesValue = policies;
newPoliciesData = createPoliciesData((CFArrayRef)CFDictionaryGetValue(dict, CFSTR("policies")));
if (newPoliciesData) {
policiesValue = newPoliciesData;
}
if (policiesValue) {
ok = SecDbBindBlob(insertGroup, 5,
CFDataGetBytePtr(policiesValue),
CFDataGetLength(policiesValue),
SQLITE_TRANSIENT, &localError);
}
if (!ok) {
secdebug("validupdate", "failed to set policies for groupId %lld",
(long long)groupId);
}
}
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);
}
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) {
__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);
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;
}
}
require_quiet(db_version >= kSecRevocationDbMinSchemaVersion, errOut);
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) {
__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);
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) {
__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 (ok && isArray(issuers)) {
CFIndex issuerIX, issuerCount = CFArrayGetCount(issuers);
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) {
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;
});
}
}
if (ok) {
groupId = _SecRevocationDbUpdateGroup(dbc, groupId, dict, &localError);
}
if (groupId < 0) {
secdebug("validupdate", "failed to get groupId");
ok = false;
} else {
ok = ok && _SecRevocationDbUpdateIssuers(dbc, groupId, issuers, &localError);
ok = ok && _SecRevocationDbUpdateIssuerData(dbc, groupId, dict, &localError);
ok = ok && _SecRevocationDbUpdateIssuerConstraints(dbc, groupId, dict, &localError);
}
(void) CFErrorPropagate(localError, error);
return ok;
}
bool _SecRevocationDbApplyUpdate(SecRevocationDbConnectionRef dbc, CFDictionaryRef update, CFIndex version, CFErrorRef *error) {
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;
value = (CFBooleanRef)CFDictionaryGetValue(update, CFSTR("full"));
if (isBoolean(value) && CFBooleanGetValue((CFBooleanRef)value)) {
dbc->fullUpdate = true;
secdebug("validupdate", "update has \"full\" attribute; clearing database");
ok = ok && _SecRevocationDbRemoveAllEntries(dbc, &localError);
}
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);
}
}
}
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);
ok = ok && _SecRevocationDbSetVersion(dbc, version, &localError);
int64_t interval = _SecRevocationDbGetUpdateInterval(dbc, NULL);
if (interval != dbc->precommitInterval) {
interval = (dbc->precommitInterval > 0) ? dbc->precommitInterval : kSecStdUpdateInterval;
ok = ok && _SecRevocationDbSetUpdateInterval(dbc, interval, &localError);
}
int64_t db_version = _SecRevocationDbGetSchemaVersion(dbc->db, dbc, NULL);
if (db_version <= 0) {
ok = ok && _SecRevocationDbSetSchemaVersion(dbc, kSecRevocationDbSchemaVersion, &localError);
}
int64_t db_format = _SecRevocationDbGetUpdateFormat(dbc, NULL);
if (db_format <= 0) {
ok = ok && _SecRevocationDbSetUpdateFormat(dbc, kSecRevocationDbUpdateFormat, &localError);
}
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) {
bool result = false;
CFRetainSafe(xmlData);
CFDataRef propListData = xmlData;
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;
}
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;
break;
}
}
if (!notInHash) {
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);
format = _SecRevocationDbGetGroupFormat(dbc, groupId, &flags, &data, &policies, &localError);
if (format == kSecValidInfoFormatUnknown) {
goto errOut;
}
else if (format == kSecValidInfoFormatSerial) {
matched = _SecRevocationDbSerialInGroup(dbc, serial, groupId, &localError);
}
else if (format == kSecValidInfoFormatSHA256) {
matched = _SecRevocationDbCertHashInGroup(dbc, certHash, groupId, &localError);
}
else if (format == kSecValidInfoFormatNto1) {
matched = _SecRevocationDbSerialInFilter(dbc, serial, data);
}
if (matched) {
secdebug("validupdate", "Valid db matched certificate: %@, format=%d, flags=0x%lx",
certHash, format, flags);
isOnList = true;
}
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);
}
result = SecValidInfoCreate(format, flags, isOnList,
certHash, issuerHash, NULL,
notBeforeDate, notAfterDate,
names, policies);
if (result && SecIsAppleTrustAnchor(certificate, 0)) {
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_quiet(dbc && certificate && issuer, errOut);
require(issuerHash = SecCertificateCopySHA256Digest(issuer), errOut);
result = SecRevocationDbCacheRead(dbc->db, certificate, issuerHash);
if (!result) {
result = _SecRevocationDbValidInfoForCertificate(dbc, certificate, issuerHash, &error);
SecRevocationDbCacheWrite(dbc->db, result);
}
errOut:
CFReleaseSafe(issuerHash);
CFReleaseSafe(error);
return result;
}
CF_RETURNS_RETAINED 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;
}
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;
}
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;
}
dispatch_queue_t SecRevocationDbGetUpdateQueue(void) {
__block dispatch_queue_t result = NULL;
SecRevocationDbWith(^(SecRevocationDbRef db) {
result = (db) ? db->update_queue : NULL;
});
return result;
}
void SecRevocationDbReleaseAllConnections(void) {
SecRevocationDbWith(^(SecRevocationDbRef db) {
SecDbReleaseAllConnections((db) ? db->db : NULL);
});
}
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;
}
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;
}
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;
}
CFIndex SecRevocationDbGetSchemaVersion(void) {
__block CFIndex result = -1;
SecRevocationDbWith(^(SecRevocationDbRef db) {
result = (CFIndex)_SecRevocationDbGetSchemaVersion(db, NULL, NULL);
});
return result;
}
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;
}
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);
int64_t version = _SecRevocationDbGetVersion(dbc, NULL);
N[0] = OSSwapInt32(version & 0xffffffff);
int64_t interval = _SecRevocationDbGetUpdateInterval(dbc, NULL);
if (interval < 0) {
interval = kSecStdUpdateInterval; }
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);
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) {
int64_t flags = sqlite3_column_int64(selectGroup, 0);
bool noCAv2 = (flags & kSecValidInfoNoCAv2Check);
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));
}
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;
});
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);
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; break;
}
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));
}
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;
});
}
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) {
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);
}
CFAbsoluteTime notBefore = -3155760000.0;
CFAbsoluteTime notAfter = 31556908800.0;
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;
}); return ok;
}); return ok;
}); return ok;
});
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; }
CFArrayRef computedList = SecRevocationDbComputeFullContentDigests(dbc, error);
CFIndex computedCount = (computedList) ? CFArrayGetCount(computedList) : 0;
for (CFIndex idx = 0; idx < expectedCount; idx++) {
if (idx >= computedCount) {
continue; }
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;
}