#include <securityd/SecRevocationDb.h>
#include <securityd/asynchttp.h>
#include <securityd/OTATrustUtilities.h>
#include <securityd/SecRevocationNetworking.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 <limits.h>
#include <string.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <errno.h>
#include <dispatch/dispatch.h>
#include <asl.h>
#include <copyfile.h>
#include "utilities/debugging.h"
#include "utilities/sec_action.h"
#include "utilities/sqlutils.h"
#include "utilities/SecAppleAnchorPriv.h"
#include "utilities/iOSforOSX.h"
#include <utilities/SecCFError.h>
#include <utilities/SecCFRelease.h>
#include <utilities/SecCFWrappers.h>
#include <utilities/SecDb.h>
#include <utilities/SecFileLocations.h>
#include <sqlite3.h>
#include <zlib.h>
#include <malloc/malloc.h>
#include <xpc/activity.h>
#include <xpc/private.h>
#include <os/transaction_private.h>
#include <CFNetwork/CFHTTPMessage.h>
#include <CoreFoundation/CFURL.h>
#include <CoreFoundation/CFUtilities.h>
static CFStringRef kValidUpdateServer = CFSTR("valid.apple.com");
static CFStringRef kSecPrefsDomain = CFSTR("com.apple.security");
static CFStringRef kUpdateServerKey = CFSTR("ValidUpdateServer");
static CFStringRef kUpdateEnabledKey = CFSTR("ValidUpdateEnabled");
static CFStringRef kUpdateIntervalKey = CFSTR("ValidUpdateInterval");
typedef CF_OPTIONS(CFOptionFlags, SecValidInfoFlags) {
kSecValidInfoComplete = 1u << 0,
kSecValidInfoCheckOCSP = 1u << 1,
kSecValidInfoKnownOnly = 1u << 2,
kSecValidInfoRequireCT = 1u << 3,
kSecValidInfoAllowlist = 1u << 4,
kSecValidInfoNoCACheck = 1u << 5
};
#define kSecMinUpdateInterval (60.0 * 5)
#define kSecStdUpdateInterval (60.0 * 60)
#define kSecMaxUpdateInterval (60.0 * 60 * 24 * 7)
#define kSecRevocationBasePath "/Library/Keychains/crls"
#define kSecRevocationCurUpdateFile "update-current"
#define kSecRevocationDbFileName "valid.sqlite3"
#define kSecRevocationDbReplaceFile ".valid_replace"
#define kSecRevocationDbSchemaVersion 4
#define kSecRevocationDbMinSchemaVersion 3
CF_ENUM(CFIndex) {
kSecValidUpdateFormatG1 = 1,
kSecValidUpdateFormatG2 = 2,
kSecValidUpdateFormatG3 = 3
};
#define kSecRevocationDbUpdateFormat 3
#define kSecRevocationDbMinUpdateFormat 2
bool SecRevocationDbVerifyUpdate(void *update, CFIndex length);
CFIndex SecRevocationDbIngestUpdate(CFDictionaryRef update, CFIndex chunkVersion);
void SecRevocationDbApplyUpdate(CFDictionaryRef update, CFIndex version);
CFAbsoluteTime SecRevocationDbComputeNextUpdateTime(CFIndex updateInterval);
void SecRevocationDbSetSchemaVersion(CFIndex dbversion);
CFIndex SecRevocationDbGetUpdateFormat(void);
void SecRevocationDbSetUpdateFormat(CFIndex dbformat);
void SecRevocationDbSetUpdateSource(CFStringRef source);
CFStringRef SecRevocationDbCopyUpdateSource(void);
void SecRevocationDbSetNextUpdateTime(CFAbsoluteTime nextUpdate);
CFAbsoluteTime SecRevocationDbGetNextUpdateTime(void);
dispatch_queue_t SecRevocationDbGetUpdateQueue(void);
void SecRevocationDbRemoveAllEntries(void);
void SecRevocationDbReleaseAllConnections(void);
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);
if (buf) {
free(buf);
}
if (rc != Z_STREAM_END) {
CFReleaseSafe(outData);
return NULL;
}
return (CFDataRef)outData;
}
static CFDataRef copyInflatedDataToFile(CFDataRef data, char *fileName) {
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);
(void)remove(fileName);
int fd;
off_t off;
fd = open(fileName, O_RDWR | O_CREAT | O_TRUNC, 0644);
if (fd < 0 || (off = lseek(fd, 0, SEEK_SET)) < 0) {
secerror("unable to open %s (errno %d)", fileName, errno);
if (fd >= 0) {
close(fd);
}
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);
if (off < (int64_t)zs.total_out) {
off = write(fd, buf, (int64_t)zs.total_out - off);
}
} while (rc == Z_OK);
close(fd);
inflateEnd(&zs);
if (buf) {
free(buf);
}
if (rc != Z_STREAM_END) {
(void)remove(fileName);
return NULL;
}
CFDataRef outData = NULL;
if ((rc = readValidFile(fileName, &outData)) != 0) {
secerror("unable to read and map %s (errno %d)", fileName, rc);
CFReleaseNull(outData);
}
return 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);
if (buf) {
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 void unmapData(CFDataRef CF_CONSUMED data) {
if (data) {
int rtn = munmap((void *)CFDataGetBytePtr(data), CFDataGetLength(data));
if (rtn != 0) {
secerror("unable to unmap %ld bytes at %p (error %d)", CFDataGetLength(data), CFDataGetBytePtr(data), rtn);
}
}
CFReleaseNull(data);
}
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 bool isDbOwner() {
#if TARGET_OS_EMBEDDED
if (getuid() == 64) #else
if (getuid() == 0)
#endif
{
return true;
}
return false;
}
CFAbsoluteTime gUpdateStarted = 0.0;
CFAbsoluteTime gNextUpdate = 0.0;
static CFIndex gUpdateInterval = 0;
static CFIndex gLastVersion = 0;
static bool SecValidUpdateProcessData(CFIndex format, CFDataRef updateData) {
if (!updateData || format < 2) {
return false;
}
bool result = false;
CFIndex version = 0;
CFIndex interval = 0;
const UInt8* p = CFDataGetBytePtr(updateData);
CFIndex bytesRemaining = (p) ? CFDataGetLength(updateData) : 0;
if (bytesRemaining < ((CFIndex)sizeof(uint32_t) * 2)) {
secinfo("validupdate", "Skipping property list creation (length %ld is too short)", (long)bytesRemaining);
return result;
}
uint32_t dataLength = OSSwapInt32(*((uint32_t *)p));
bytesRemaining -= sizeof(uint32_t);
p += sizeof(uint32_t);
uint32_t plistCount = 1;
uint32_t plistTotal = 1;
if (format > kSecValidUpdateFormatG2) {
plistCount = OSSwapInt32(*((uint32_t *)p));
plistTotal = plistCount;
bytesRemaining -= sizeof(uint32_t);
p += sizeof(uint32_t);
}
if (dataLength > bytesRemaining) {
secinfo("validupdate", "Skipping property list creation (dataLength=%ld, bytesRemaining=%ld)",
(long)dataLength, (long)bytesRemaining);
return result;
}
uint32_t plistProcessed = 0;
while (plistCount > 0 && bytesRemaining > 0) {
CFPropertyListRef propertyList = NULL;
uint32_t plistLength = dataLength;
if (format > kSecValidUpdateFormatG2) {
plistLength = OSSwapInt32(*((uint32_t *)p));
bytesRemaining -= sizeof(uint32_t);
p += sizeof(uint32_t);
}
--plistCount;
++plistProcessed;
os_transaction_t transaction;
transaction = os_transaction_create("com.apple.trustd.valid");
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 = SecRevocationDbIngestUpdate((CFDictionaryRef)propertyList, version);
if (plistProcessed == 1) {
version = curVersion;
CFTypeRef value = (CFNumberRef)CFDictionaryGetValue((CFDictionaryRef)propertyList,
CFSTR("check-again"));
if (isNumber(value)) {
CFNumberGetValue((CFNumberRef)value, kCFNumberCFIndexType, &interval);
}
}
if (curVersion < 0) {
plistCount = 0; result = true;
}
} else {
secinfo("validupdate", "Failed to deserialize update chunk %u of %u",
plistProcessed, plistTotal);
if (plistProcessed == 1) {
gNextUpdate = SecRevocationDbComputeNextUpdateTime(0);
}
}
CFReleaseSafe(propertyList);
os_release(transaction);
bytesRemaining -= plistLength;
p += plistLength;
}
if (version > 0) {
secdebug("validupdate", "Update received: v%lu", (unsigned long)version);
gLastVersion = version;
gNextUpdate = SecRevocationDbComputeNextUpdateTime(interval);
secdebug("validupdate", "Next update time: %f", gNextUpdate);
result = true;
}
SecRevocationDbSetNextUpdateTime(gNextUpdate);
return result;
}
void SecValidUpdateVerifyAndIngest(CFDataRef updateData) {
if (!updateData) {
secnotice("validupdate", "invalid update data");
return;
}
if (SecRevocationDbVerifyUpdate((void *)CFDataGetBytePtr(updateData), CFDataGetLength(updateData))) {
bool result = SecValidUpdateProcessData(kSecValidUpdateFormatG3, updateData);
if (!result) {
result = SecValidUpdateProcessData(kSecValidUpdateFormatG2, updateData);
}
if (!result) {
secerror("failed to process valid update");
}
} else {
secerror("failed to verify valid update");
}
}
static bool SecValidUpdateFromCompressed(CFDataRef CF_CONSUMED data) {
if (!data) { return false; }
os_transaction_t transaction;
transaction = os_transaction_create("com.apple.trustd.valid");
__block CFDataRef inflatedData = NULL;
WithPathInRevocationInfoDirectory(CFSTR(kSecRevocationCurUpdateFile), ^(const char *curUpdatePath) {
inflatedData = copyInflatedDataToFile(data, (char *)curUpdatePath);
secdebug("validupdate", "data expanded: %ld bytes", (long)CFDataGetLength(inflatedData));
});
unmapData(data);
os_release(transaction);
if (inflatedData) {
SecValidUpdateVerifyAndIngest(inflatedData);
unmapData(inflatedData);
}
WithPathInRevocationInfoDirectory(CFSTR(kSecRevocationCurUpdateFile), ^(const char *curUpdatePath) {
(void)removeFileWithSuffix(curUpdatePath, "");
});
return true;
}
static bool SecValidDatabaseFromCompressed(CFDataRef CF_CONSUMED data) {
if (!data) { return false; }
secdebug("validupdate", "read %ld bytes from file", (long)CFDataGetLength(data));
os_transaction_t transaction;
transaction = os_transaction_create("com.apple.trustd.valid");
__block CFDataRef inflatedData = NULL;
WithPathInRevocationInfoDirectory(CFSTR(kSecRevocationDbFileName), ^(const char *dbPath) {
inflatedData = copyInflatedDataToFile(data, (char *)dbPath);
secdebug("validupdate", "data expanded: %ld bytes", (long)CFDataGetLength(inflatedData));
});
unmapData(data);
os_release(transaction);
if (inflatedData) {
unmapData(inflatedData);
}
return true;
}
static bool SecValidUpdateSatisfiedLocally(CFStringRef server, CFIndex version, bool safeToReplace) {
__block bool result = false;
CFDataRef data = NULL;
SecOTAPKIRef otapkiRef = NULL;
int rtn = 0;
static int sNumLocalUpdates = 0;
if (sNumLocalUpdates > 1) {
secdebug("validupdate", "%d consecutive db resets, ignoring local asset", sNumLocalUpdates);
goto updateExit;
}
if (kCFCompareEqualTo != CFStringCompare(server, kValidUpdateServer,
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) {
char *semPathBuf = NULL;
asprintf(&semPathBuf, "%s/%s", kSecRevocationBasePath, kSecRevocationDbReplaceFile);
if (semPathBuf) {
struct stat sb;
int fd = open(semPathBuf, O_WRONLY | O_CREAT, DEFFILEMODE);
if (fd == -1 || fstat(fd, &sb) || close(fd)) {
secnotice("validupdate", "unable to write %s", semPathBuf);
}
free(semPathBuf);
}
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();
});
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 {
result = true;
}
});
}
if (result) {
goto updateExit;
}
if (validDbPathBuf) {
char *validDbCmpPathBuf = NULL;
asprintf(&validDbCmpPathBuf, "%s%s", validDbPathBuf, ".gz");
if (validDbCmpPathBuf) {
secdebug("validupdate", "will read data from \"%s\"", validDbCmpPathBuf);
if ((rtn = readValidFile(validDbCmpPathBuf, &data)) != 0) {
unmapData(data);
data = NULL;
secnotice("validupdate", "readValidFile error %d", rtn);
}
free(validDbCmpPathBuf);
}
}
result = SecValidDatabaseFromCompressed(data);
if (result) {
goto updateExit;
}
const char *validUpdatePathBuf = SecOTAPKIGetValidUpdateSnapshot(otapkiRef);
if (validUpdatePathBuf) {
secdebug("validupdate", "will read data from \"%s\"", validUpdatePathBuf);
if ((rtn = readValidFile(validUpdatePathBuf, &data)) != 0) {
unmapData(data);
data = NULL;
secnotice("validupdate", "readValidFile error %d", rtn);
}
}
result = SecValidUpdateFromCompressed(data);
updateExit:
CFReleaseNull(otapkiRef);
if (result) {
sNumLocalUpdates++;
SecRevocationDbSetUpdateSource(server);
gLastVersion = SecRevocationDbGetVersion();
gUpdateStarted = 0;
secdebug("validupdate", "local update to g%ld/v%ld complete at %f",
(long)SecRevocationDbGetUpdateFormat(), (long)gLastVersion,
(double)CFAbsoluteTimeGetCurrent());
} else {
sNumLocalUpdates = 0; }
return result;
}
static bool SecValidUpdateSchedule(bool updateEnabled, CFStringRef server, CFIndex version) {
if (SecValidUpdateSatisfiedLocally(server, version, false)) {
return true;
}
if (!updateEnabled) {
return false;
}
#if !TARGET_OS_BRIDGE
return SecValidUpdateRequest(SecRevocationDbGetUpdateQueue(), server, version);
#else
return false;
#endif
}
void SecRevocationDbInitialize() {
if (!isDbOwner()) { return; }
__block bool initializeDb = false;
(void)mkpath_np(kSecRevocationBasePath, 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) {
return;
}
CFTypeRef value = (CFStringRef)CFPreferencesCopyValue(kUpdateServerKey, kSecPrefsDomain, kCFPreferencesAnyUser, kCFPreferencesCurrentHost);
CFStringRef server = (isString(value)) ? (CFStringRef)value : (CFStringRef)kValidUpdateServer;
CFIndex version = 0;
secnotice("validupdate", "initializing database");
if (!SecValidUpdateSatisfiedLocally(server, version, true)) {
#if !TARGET_OS_BRIDGE
(void)SecValidUpdateRequest(SecRevocationDbGetUpdateQueue(), server, version);
#endif
}
CFReleaseSafe(value);
}
static SecValidInfoRef SecValidInfoCreate(SecValidInfoFormat format,
CFOptionFlags flags,
bool isOnList,
CFDataRef certHash,
CFDataRef issuerHash,
CFDataRef anchorHash) {
SecValidInfoRef validInfo;
validInfo = (SecValidInfoRef)calloc(1, sizeof(struct __SecValidInfo));
if (!validInfo) { return NULL; }
CFRetainSafe(certHash);
CFRetainSafe(issuerHash);
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 & kSecValidInfoNoCACheck);
return validInfo;
}
void SecValidInfoRelease(SecValidInfoRef validInfo) {
if (validInfo) {
CFReleaseSafe(validInfo->certHash);
CFReleaseSafe(validInfo->issuerHash);
CFReleaseSafe(validInfo->anchorHash);
free(validInfo);
}
}
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 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;
value = (CFStringRef)CFPreferencesCopyValue(kUpdateServerKey, kSecPrefsDomain, kCFPreferencesAnyUser, kCFPreferencesCurrentHost);
if (isString(value)) {
server = (CFStringRef) CFRetain(value);
} else {
server = (CFStringRef) CFRetain(kValidUpdateServer);
}
CFReleaseNull(value);
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(kValidUpdateServer);
}
CFIndex db_version = SecRevocationDbGetSchemaVersion();
CFIndex db_format = SecRevocationDbGetUpdateFormat();
if (db_version < kSecRevocationDbSchemaVersion ||
db_format < kSecRevocationDbUpdateFormat ||
kCFCompareEqualTo != CFStringCompare(server, db_source, kCFCompareCaseInsensitive)) {
SecRevocationDbRemoveAllEntries();
version = gLastVersion = 0;
}
#if (__MAC_OS_X_VERSION_MIN_REQUIRED >= 101300 || __IPHONE_OS_VERSION_MIN_REQUIRED >= 110000)
bool updateEnabled = true; #else
bool updateEnabled = false;
#endif
value = (CFBooleanRef)CFPreferencesCopyValue(kUpdateEnabledKey, kSecPrefsDomain, kCFPreferencesAnyUser, kCFPreferencesCurrentHost);
if (isBoolean(value)) {
updateEnabled = CFBooleanGetValue((CFBooleanRef)value);
}
CFReleaseNull(value);
bool result = SecValidUpdateSchedule(updateEnabled, server, version);
CFReleaseNull(server);
CFReleaseNull(db_source);
return result;
}
void SecRevocationDbCheckNextUpdate(void) {
static dispatch_once_t once;
static sec_action_t action;
dispatch_once(&once, ^{
dispatch_queue_t update_queue = SecRevocationDbGetUpdateQueue();
action = sec_action_create_with_queue(update_queue, "update_check", kSecMinUpdateInterval);
sec_action_set_handler(action, ^{
(void)_SecRevocationDbCheckNextUpdate();
});
});
sec_action_perform(action);
}
bool SecRevocationDbVerifyUpdate(void *update, CFIndex length) {
if (!update || length <= (CFIndex)sizeof(uint32_t)) {
return false;
}
uint32_t plistLength = OSSwapInt32(*((uint32_t *)update));
if ((plistLength + (CFIndex)(sizeof(uint32_t)*2)) > (uint64_t) length) {
secdebug("validupdate", "ERROR: reported plist length (%lu)+%lu exceeds total length (%lu)\n",
(unsigned long)plistLength, (unsigned long)sizeof(uint32_t)*2, (unsigned long)length);
return false;
}
uint8_t *plistData = (uint8_t *)update + sizeof(uint32_t);
uint8_t *sigData = (uint8_t *)plistData + plistLength;
uint32_t sigLength = OSSwapInt32(*((uint32_t *)sigData));
sigData += sizeof(uint32_t);
if ((plistLength + sigLength + (CFIndex)(sizeof(uint32_t) * 2)) != (uint64_t) length) {
secdebug("validupdate", "ERROR: reported lengths do not add up to total length\n");
return false;
}
OSStatus status = 0;
CMSSignerStatus signerStatus;
CMSDecoderRef cms = NULL;
SecPolicyRef policy = NULL;
SecTrustRef trust = NULL;
CFDataRef content = NULL;
if ((content = CFDataCreateWithBytesNoCopy(kCFAllocatorDefault,
(const UInt8 *)plistData, (CFIndex)plistLength, kCFAllocatorNull)) == NULL) {
secdebug("validupdate", "CFDataCreateWithBytesNoCopy failed (%ld bytes)\n", (long)plistLength);
return false;
}
if ((status = CMSDecoderCreate(&cms)) != errSecSuccess) {
secdebug("validupdate", "CMSDecoderCreate failed with error %d\n", (int)status);
goto verifyExit;
}
if ((status = CMSDecoderUpdateMessage(cms, sigData, sigLength)) != errSecSuccess) {
secdebug("validupdate", "CMSDecoderUpdateMessage failed with error %d\n", (int)status);
goto verifyExit;
}
if ((status = CMSDecoderSetDetachedContent(cms, content)) != errSecSuccess) {
secdebug("validupdate", "CMSDecoderSetDetachedContent failed with error %d\n", (int)status);
goto verifyExit;
}
if ((status = CMSDecoderFinalizeMessage(cms)) != errSecSuccess) {
secdebug("validupdate", "CMSDecoderFinalizeMessage failed with error %d\n", (int)status);
goto verifyExit;
}
policy = SecPolicyCreateApplePinned(CFSTR("ValidUpdate"), 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);
SecRevocationDbSetNextUpdateTime(gNextUpdate);
gUpdateStarted = 0;
}
CFIndex SecRevocationDbIngestUpdate(CFDictionaryRef update, CFIndex chunkVersion) {
CFIndex version = 0;
if (!update) {
return version;
}
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 = SecRevocationDbGetVersion();
if (version > curVersion || chunkVersion > 0) {
SecRevocationDbApplyUpdate(update, version);
} else {
secdebug("validupdate", "we have v%ld, skipping update to v%ld",
(long)curVersion, (long)version);
version = -1; }
return version;
}
#define createTablesSQL CFSTR("CREATE TABLE admin(" \
"key TEXT PRIMARY KEY NOT NULL," \
"ival INTEGER NOT NULL," \
"value BLOB" \
");" \
"CREATE TABLE issuers(" \
"groupid INTEGER NOT NULL," \
"issuer_hash BLOB PRIMARY KEY NOT NULL" \
");" \
"CREATE INDEX issuer_idx ON issuers(issuer_hash);" \
"CREATE TABLE groups(" \
"groupid INTEGER PRIMARY KEY AUTOINCREMENT," \
"flags INTEGER," \
"format INTEGER," \
"data BLOB" \
");" \
"CREATE TABLE serials(" \
"rowid INTEGER PRIMARY KEY AUTOINCREMENT," \
"groupid INTEGER NOT NULL," \
"serial BLOB NOT NULL," \
"UNIQUE(groupid,serial)" \
");" \
"CREATE TABLE hashes(" \
"rowid INTEGER PRIMARY KEY AUTOINCREMENT," \
"groupid INTEGER NOT NULL," \
"sha256 BLOB NOT NULL," \
"UNIQUE(groupid,sha256)" \
");" \
"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; " \
"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 selectGroupRecordSQL CFSTR("SELECT flags,format,data FROM " \
"groups WHERE groupid=?")
#define selectSerialRecordSQL CFSTR("SELECT rowid FROM serials " \
"WHERE groupid=? AND serial=?")
#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) VALUES (?,?,?,?)")
#define insertSerialRecordSQL CFSTR("INSERT OR REPLACE INTO serials " \
"(groupid,serial) VALUES (?,?)")
#define insertSha256RecordSQL CFSTR("INSERT OR REPLACE INTO hashes " \
"(groupid,sha256) VALUES (?,?)")
#define deleteGroupRecordSQL CFSTR("DELETE FROM groups WHERE groupid=?")
#define deleteAllEntriesSQL CFSTR("DELETE from hashes; " \
"DELETE from serials; DELETE from issuers; DELETE from groups; " \
"DELETE from admin; DELETE from sqlite_sequence")
#define deleteTablesSQL CFSTR("DROP TABLE hashes; " \
"DROP TABLE serials; DROP TABLE issuers; DROP TABLE groups; " \
"DROP TABLE admin; DELETE from sqlite_sequence")
static SecDbRef SecRevocationDbCreate(CFStringRef path) {
bool readWrite = isDbOwner();
mode_t mode = 0644;
SecDbRef result = SecDbCreateWithOptions(path, mode, readWrite, false, false, ^bool (SecDbRef db, SecDbConnectionRef dbconn, bool didCreate, bool *callMeAgainForNextConnection, CFErrorRef *error) {
__block bool ok = true;
CFErrorRef localError = NULL;
if (ok && !SecDbWithSQL(dbconn, selectGroupIdSQL, &localError, NULL) && CFErrorGetCode(localError) == SQLITE_ERROR) {
ok &= SecDbTransaction(dbconn, kSecDbExclusiveTransactionType, error, ^(bool *commit) {
ok = SecDbExec(dbconn, createTablesSQL, error);
*commit = ok;
});
}
CFReleaseSafe(localError);
if (!ok)
secerror("%s failed: %@", didCreate ? "Create" : "Open", error ? *error : NULL);
return ok;
});
return result;
}
typedef struct __SecRevocationDb *SecRevocationDbRef;
struct __SecRevocationDb {
SecDbRef db;
dispatch_queue_t update_queue;
bool updateInProgress;
bool unsupportedVersion;
};
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;
require(rdb->db = SecRevocationDbCreate(db_name), errOut);
attr = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_BACKGROUND, 0);
require(rdb->update_queue = dispatch_queue_create(NULL, attr), errOut);
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->updateInProgress) {
return; }
dbJob(kSecRevocationDb);
}
static int64_t _SecRevocationDbGetVersion(SecRevocationDbRef rdb, CFErrorRef *error) {
__block int64_t version = -1;
__block bool ok = true;
__block CFErrorRef localError = NULL;
ok &= SecDbPerformRead(rdb->db, &localError, ^(SecDbConnectionRef dbconn) {
if (ok) ok &= SecDbWithSQL(dbconn, selectVersionSQL, &localError, ^bool(sqlite3_stmt *selectVersion) {
ok = SecDbStep(dbconn, selectVersion, &localError, NULL);
version = sqlite3_column_int64(selectVersion, 0);
return ok;
});
});
(void) CFErrorPropagate(localError, error);
return version;
}
static void _SecRevocationDbSetVersion(SecRevocationDbRef rdb, CFIndex version){
secdebug("validupdate", "setting version to %ld", (long)version);
__block CFErrorRef localError = NULL;
__block bool ok = true;
ok &= SecDbPerformWrite(rdb->db, &localError, ^(SecDbConnectionRef dbconn) {
ok &= SecDbTransaction(dbconn, kSecDbExclusiveTransactionType, &localError, ^(bool *commit) {
if (ok) ok = SecDbWithSQL(dbconn, insertAdminRecordSQL, &localError, ^bool(sqlite3_stmt *insertVersion) {
if (ok) {
const char *versionKey = "version";
ok = SecDbBindText(insertVersion, 1, versionKey, strlen(versionKey),
SQLITE_TRANSIENT, &localError);
}
if (ok) {
ok = SecDbBindInt64(insertVersion, 2,
(sqlite3_int64)version, &localError);
}
if (ok) {
ok = SecDbStep(dbconn, insertVersion, &localError, NULL);
}
return ok;
});
});
});
if (!ok) {
secerror("_SecRevocationDbSetVersion failed: %@", localError);
}
CFReleaseSafe(localError);
}
static int64_t _SecRevocationDbGetSchemaVersion(SecRevocationDbRef rdb, CFErrorRef *error) {
__block int64_t db_version = -1;
__block bool ok = true;
__block CFErrorRef localError = NULL;
ok &= SecDbPerformRead(rdb->db, &localError, ^(SecDbConnectionRef dbconn) {
if (ok) ok &= SecDbWithSQL(dbconn, selectDbVersionSQL, &localError, ^bool(sqlite3_stmt *selectDbVersion) {
ok = SecDbStep(dbconn, selectDbVersion, &localError, NULL);
db_version = sqlite3_column_int64(selectDbVersion, 0);
return ok;
});
});
(void) CFErrorPropagate(localError, error);
return db_version;
}
static void _SecRevocationDbSetSchemaVersion(SecRevocationDbRef rdb, CFIndex dbversion) {
secdebug("validupdate", "setting db_version to %ld", (long)dbversion);
__block CFErrorRef localError = NULL;
__block bool ok = true;
ok &= SecDbPerformWrite(rdb->db, &localError, ^(SecDbConnectionRef dbconn) {
ok &= SecDbTransaction(dbconn, kSecDbExclusiveTransactionType, &localError, ^(bool *commit) {
if (ok) ok = SecDbWithSQL(dbconn, insertAdminRecordSQL, &localError, ^bool(sqlite3_stmt *insertDbVersion) {
if (ok) {
const char *dbVersionKey = "db_version";
ok = SecDbBindText(insertDbVersion, 1, dbVersionKey, strlen(dbVersionKey),
SQLITE_TRANSIENT, &localError);
}
if (ok) {
ok = SecDbBindInt64(insertDbVersion, 2,
(sqlite3_int64)dbversion, &localError);
}
if (ok) {
ok = SecDbStep(dbconn, insertDbVersion, &localError, NULL);
}
return ok;
});
});
});
if (!ok) {
secerror("_SecRevocationDbSetSchemaVersion failed: %@", localError);
} else {
rdb->unsupportedVersion = false;
}
CFReleaseSafe(localError);
}
static int64_t _SecRevocationDbGetUpdateFormat(SecRevocationDbRef rdb, CFErrorRef *error) {
__block int64_t db_format = -1;
__block bool ok = true;
__block CFErrorRef localError = NULL;
ok &= SecDbPerformRead(rdb->db, &localError, ^(SecDbConnectionRef dbconn) {
if (ok) ok &= SecDbWithSQL(dbconn, selectDbFormatSQL, &localError, ^bool(sqlite3_stmt *selectDbFormat) {
ok = SecDbStep(dbconn, selectDbFormat, &localError, NULL);
db_format = sqlite3_column_int64(selectDbFormat, 0);
return ok;
});
});
(void) CFErrorPropagate(localError, error);
return db_format;
}
static void _SecRevocationDbSetUpdateFormat(SecRevocationDbRef rdb, CFIndex dbformat) {
secdebug("validupdate", "setting db_format to %ld", (long)dbformat);
__block CFErrorRef localError = NULL;
__block bool ok = true;
ok &= SecDbPerformWrite(rdb->db, &localError, ^(SecDbConnectionRef dbconn) {
ok &= SecDbTransaction(dbconn, kSecDbExclusiveTransactionType, &localError, ^(bool *commit) {
if (ok) ok = SecDbWithSQL(dbconn, insertAdminRecordSQL, &localError, ^bool(sqlite3_stmt *insertDbFormat) {
if (ok) {
const char *dbFormatKey = "db_format";
ok = SecDbBindText(insertDbFormat, 1, dbFormatKey, strlen(dbFormatKey),
SQLITE_TRANSIENT, &localError);
}
if (ok) {
ok = SecDbBindInt64(insertDbFormat, 2,
(sqlite3_int64)dbformat, &localError);
}
if (ok) {
ok = SecDbStep(dbconn, insertDbFormat, &localError, NULL);
}
return ok;
});
});
});
if (!ok) {
secerror("_SecRevocationDbSetUpdateFormat failed: %@", localError);
} else {
rdb->unsupportedVersion = false;
}
CFReleaseSafe(localError);
}
static CFStringRef _SecRevocationDbCopyUpdateSource(SecRevocationDbRef rdb, CFErrorRef *error) {
__block CFStringRef updateSource = NULL;
__block bool ok = true;
__block CFErrorRef localError = NULL;
ok &= SecDbPerformRead(rdb->db, &localError, ^(SecDbConnectionRef dbconn) {
if (ok) ok &= SecDbWithSQL(dbconn, selectDbSourceSQL, &localError, ^bool(sqlite3_stmt *selectDbSource) {
ok = SecDbStep(dbconn, selectDbSource, &localError, NULL);
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);
}
}
return ok;
});
});
(void) CFErrorPropagate(localError, error);
return updateSource;
}
static void _SecRevocationDbSetUpdateSource(SecRevocationDbRef rdb, CFStringRef updateSource) {
if (!updateSource) {
secerror("_SecRevocationDbSetUpdateSource failed: %d", errSecParam);
return;
}
__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;
}
secdebug("validupdate", "setting update source to \"%s\"", updateSourceCStr);
__block CFErrorRef localError = NULL;
__block bool ok = true;
ok &= SecDbPerformWrite(rdb->db, &localError, ^(SecDbConnectionRef dbconn) {
ok &= SecDbTransaction(dbconn, kSecDbExclusiveTransactionType, &localError, ^(bool *commit) {
if (ok) ok = SecDbWithSQL(dbconn, insertAdminRecordSQL, &localError, ^bool(sqlite3_stmt *insertRecord) {
if (ok) {
const char *dbSourceKey = "db_source";
ok = SecDbBindText(insertRecord, 1, dbSourceKey, strlen(dbSourceKey),
SQLITE_TRANSIENT, &localError);
}
if (ok) {
ok = SecDbBindInt64(insertRecord, 2,
(sqlite3_int64)0, &localError);
}
if (ok) {
ok = SecDbBindBlob(insertRecord, 3,
updateSourceCStr, strlen(updateSourceCStr),
SQLITE_TRANSIENT, &localError);
}
if (ok) {
ok = SecDbStep(dbconn, insertRecord, &localError, NULL);
}
return ok;
});
});
});
if (!ok) {
secerror("_SecRevocationDbSetUpdateSource failed: %@", localError);
}
CFReleaseSafe(localError);
}
static CFAbsoluteTime _SecRevocationDbGetNextUpdateTime(SecRevocationDbRef rdb, CFErrorRef *error) {
__block CFAbsoluteTime nextUpdate = 0;
__block bool ok = true;
__block CFErrorRef localError = NULL;
ok &= SecDbPerformRead(rdb->db, &localError, ^(SecDbConnectionRef dbconn) {
if (ok) ok &= SecDbWithSQL(dbconn, selectNextUpdateSQL, &localError, ^bool(sqlite3_stmt *selectNextUpdate) {
ok = SecDbStep(dbconn, selectNextUpdate, &localError, NULL);
CFAbsoluteTime *p = (CFAbsoluteTime *)sqlite3_column_blob(selectNextUpdate, 0);
if (p != NULL) {
if (sizeof(CFAbsoluteTime) == sqlite3_column_bytes(selectNextUpdate, 0)) {
nextUpdate = *p;
}
}
return ok;
});
});
(void) CFErrorPropagate(localError, error);
return nextUpdate;
}
static void _SecRevocationDbSetNextUpdateTime(SecRevocationDbRef rdb, CFAbsoluteTime nextUpdate){
secdebug("validupdate", "setting next update to %f", (double)nextUpdate);
__block CFErrorRef localError = NULL;
__block bool ok = true;
ok &= SecDbPerformWrite(rdb->db, &localError, ^(SecDbConnectionRef dbconn) {
ok &= SecDbTransaction(dbconn, kSecDbExclusiveTransactionType, &localError, ^(bool *commit) {
if (ok) ok = SecDbWithSQL(dbconn, insertAdminRecordSQL, &localError, ^bool(sqlite3_stmt *insertRecord) {
if (ok) {
const char *nextUpdateKey = "check_again";
ok = SecDbBindText(insertRecord, 1, nextUpdateKey, strlen(nextUpdateKey),
SQLITE_TRANSIENT, &localError);
}
if (ok) {
ok = SecDbBindInt64(insertRecord, 2,
(sqlite3_int64)0, &localError);
}
if (ok) {
ok = SecDbBindBlob(insertRecord, 3,
&nextUpdate, sizeof(CFAbsoluteTime),
SQLITE_TRANSIENT, &localError);
}
if (ok) {
ok = SecDbStep(dbconn, insertRecord, &localError, NULL);
}
return ok;
});
});
});
if (!ok) {
secerror("_SecRevocationDbSetNextUpdate failed: %@", localError);
}
CFReleaseSafe(localError);
}
static bool _SecRevocationDbRemoveAllEntries(SecRevocationDbRef rdb) {
__block bool ok = true;
__block CFErrorRef localError = NULL;
ok &= SecDbPerformWrite(rdb->db, &localError, ^(SecDbConnectionRef dbconn) {
ok &= SecDbTransaction(dbconn, kSecDbExclusiveTransactionType, &localError, ^(bool *commit) {
ok &= SecDbExec(dbconn, deleteTablesSQL, &localError);
ok &= SecDbExec(dbconn, createTablesSQL, &localError);
secdebug("validupdate", "resetting database, result: %d", (ok) ? 1 : 0);
*commit = ok;
});
SecDbExec(dbconn, CFSTR("VACUUM"), &localError);
});
_SecRevocationDbSetSchemaVersion(rdb, kSecRevocationDbSchemaVersion);
_SecRevocationDbSetUpdateFormat(rdb, kSecRevocationDbUpdateFormat);
CFReleaseSafe(localError);
return ok;
}
static bool _SecRevocationDbUpdateIssuers(SecRevocationDbRef rdb, int64_t groupId, CFArrayRef issuers, CFErrorRef *error) {
if (!issuers || groupId < 0) {
return false;
}
__block bool ok = true;
__block CFErrorRef localError = NULL;
ok &= SecDbPerformWrite(rdb->db, &localError, ^(SecDbConnectionRef dbconn) {
ok &= SecDbTransaction(dbconn, kSecDbExclusiveTransactionType, &localError, ^(bool *commit) {
if (isArray(issuers)) {
CFIndex issuerIX, issuerCount = CFArrayGetCount(issuers);
for (issuerIX=0; issuerIX<issuerCount && ok; issuerIX++) {
CFDataRef hash = (CFDataRef)CFArrayGetValueAtIndex(issuers, issuerIX);
if (!hash) { continue; }
if (ok) ok = SecDbWithSQL(dbconn, insertIssuerRecordSQL, &localError, ^bool(sqlite3_stmt *insertIssuer) {
if (ok) {
ok = SecDbBindInt64(insertIssuer, 1,
groupId, &localError);
}
if (ok) {
ok = SecDbBindBlob(insertIssuer, 2,
CFDataGetBytePtr(hash),
CFDataGetLength(hash),
SQLITE_TRANSIENT, &localError);
}
if (ok) {
ok = SecDbStep(dbconn, insertIssuer, &localError, NULL);
}
return ok;
});
}
}
});
});
(void) CFErrorPropagate(localError, error);
return ok;
}
static bool _SecRevocationDbUpdatePerIssuerData(SecRevocationDbRef rdb, int64_t groupId, CFDictionaryRef dict, CFErrorRef *error) {
if (!dict || groupId < 0) {
return false;
}
__block bool ok = true;
__block CFErrorRef localError = NULL;
ok &= SecDbPerformWrite(rdb->db, &localError, ^(SecDbConnectionRef dbconn) {
ok &= SecDbTransaction(dbconn, kSecDbExclusiveTransactionType, &localError, ^(bool *commit) {
CFArrayRef deleteArray = (CFArrayRef)CFDictionaryGetValue(dict, CFSTR("delete"));
if (isArray(deleteArray)) {
}
CFArrayRef addArray = (CFArrayRef)CFDictionaryGetValue(dict, CFSTR("add"));
if (isArray(addArray)) {
CFIndex identifierIX, identifierCount = CFArrayGetCount(addArray);
for (identifierIX=0; identifierIX<identifierCount; identifierIX++) {
CFDataRef identifierData = (CFDataRef)CFArrayGetValueAtIndex(addArray, identifierIX);
if (!identifierData) { continue; }
CFIndex length = CFDataGetLength(identifierData);
CFStringRef sql = NULL;
if (length <= 20) {
sql = insertSerialRecordSQL;
} else if (length == 32) {
sql = insertSha256RecordSQL;
}
if (!sql) { continue; }
if (ok) ok = SecDbWithSQL(dbconn, sql, &localError, ^bool(sqlite3_stmt *insertIdentifier) {
if (ok) {
ok = SecDbBindInt64(insertIdentifier, 1,
groupId, &localError);
}
if (ok) {
ok = SecDbBindBlob(insertIdentifier, 2,
CFDataGetBytePtr(identifierData),
CFDataGetLength(identifierData),
SQLITE_TRANSIENT, &localError);
}
if (ok) {
ok = SecDbStep(dbconn, insertIdentifier, &localError, NULL);
}
return ok;
});
}
}
});
});
(void) CFErrorPropagate(localError, error);
return ok;
}
static SecValidInfoFormat _SecRevocationDbGetGroupFormat(SecRevocationDbRef rdb,
int64_t groupId, SecValidInfoFlags *flags, CFDataRef *data, CFErrorRef *error) {
__block bool ok = true;
__block SecValidInfoFormat format = 0;
__block CFErrorRef localError = NULL;
ok &= SecDbPerformRead(rdb->db, &localError, ^(SecDbConnectionRef dbconn) {
ok &= SecDbWithSQL(dbconn, selectGroupRecordSQL, &localError, ^bool(sqlite3_stmt *selectGroup) {
ok = SecDbBindInt64(selectGroup, 1, groupId, &localError);
ok &= SecDbStep(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);
}
}
});
return ok;
});
});
if (!ok) {
secdebug("validupdate", "GetGroupFormat for groupId %lu failed", (unsigned long)groupId);
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) {
bool result = false;
CFTypeRef value = (CFBooleanRef)CFDictionaryGetValue(dict, key);
if (isBoolean(value) && flags) {
SecValidInfoFlags oldFlags = *flags;
if (CFBooleanGetValue((CFBooleanRef)value)) {
*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(SecRevocationDbRef rdb, int64_t groupId, CFDictionaryRef dict, CFErrorRef *error) {
if (!dict) {
return groupId;
}
__block int64_t result = -1;
__block bool ok = true;
__block bool isFormatChange = false;
__block CFErrorRef localError = NULL;
__block SecValidInfoFlags flags = 0;
__block SecValidInfoFormat format = kSecValidInfoFormatUnknown;
__block SecValidInfoFormat formatUpdate = kSecValidInfoFormatUnknown;
__block CFDataRef data = NULL;
if (groupId >= 0) {
format = _SecRevocationDbGetGroupFormat(rdb, groupId, &flags, &data, NULL);
if (format == kSecValidInfoFormatUnknown) {
secdebug("validupdate", "existing group %lld has unknown format %d, flags=%lu",
(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);
ok &= SecDbPerformWrite(rdb->db, &localError, ^(SecDbConnectionRef dbconn) {
ok &= SecDbTransaction(dbconn, kSecDbExclusiveTransactionType, &localError, ^(bool *commit) {
if (isFormatChange) {
secdebug("validupdate", "group %lld format change from %d to %d",
(long long)groupId, format, formatUpdate);
ok &= SecDbWithSQL(dbconn, deleteGroupRecordSQL, &localError, ^bool(sqlite3_stmt *deleteResponse) {
ok = SecDbBindInt64(deleteResponse, 1, groupId, &localError);
if (ok) {
ok = SecDbStep(dbconn, deleteResponse, &localError, NULL);
}
return ok;
});
}
ok &= SecDbWithSQL(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);
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);
}
}
}
if (ok) {
ok = SecDbStep(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(dbconn));
}
if (!ok) {
secdebug("validupdate", "failed to insert group %lld", (long long)result);
}
CFReleaseSafe(xmlData);
CFReleaseSafe(data);
return ok;
});
});
});
(void) CFErrorPropagate(localError, error);
return result;
}
static int64_t _SecRevocationDbGroupIdForIssuerHash(SecRevocationDbRef rdb, CFDataRef hash, CFErrorRef *error) {
__block int64_t groupId = -1;
__block bool ok = true;
__block CFErrorRef localError = NULL;
if (!hash) {
secdebug("validupdate", "failed to get hash (%@)", hash);
}
require(hash, errOut);
int64_t db_version = _SecRevocationDbGetSchemaVersion(rdb, NULL);
if (db_version < kSecRevocationDbMinSchemaVersion) {
if (!rdb->unsupportedVersion) {
secdebug("validupdate", "unsupported db_version: %lld", (long long)db_version);
rdb->unsupportedVersion = true;
}
}
require_quiet(db_version >= kSecRevocationDbMinSchemaVersion, errOut);
ok &= SecDbPerformRead(rdb->db, &localError, ^(SecDbConnectionRef dbconn) {
ok &= SecDbWithSQL(dbconn, selectGroupIdSQL, &localError, ^bool(sqlite3_stmt *selectGroupId) {
ok = SecDbBindBlob(selectGroupId, 1, CFDataGetBytePtr(hash), CFDataGetLength(hash), SQLITE_TRANSIENT, &localError);
ok &= SecDbStep(dbconn, selectGroupId, &localError, ^(bool *stopGroupId) {
groupId = sqlite3_column_int64(selectGroupId, 0);
});
return ok;
});
});
errOut:
(void) CFErrorPropagate(localError, error);
return groupId;
}
static bool _SecRevocationDbApplyGroupDelete(SecRevocationDbRef rdb, CFDataRef issuerHash, CFErrorRef *error) {
__block int64_t groupId = -1;
__block bool ok = true;
__block CFErrorRef localError = NULL;
groupId = _SecRevocationDbGroupIdForIssuerHash(rdb, issuerHash, &localError);
require(!(groupId < 0), errOut);
ok &= SecDbPerformWrite(rdb->db, &localError, ^(SecDbConnectionRef dbconn) {
ok &= SecDbTransaction(dbconn, kSecDbExclusiveTransactionType, &localError, ^(bool *commit) {
ok = SecDbWithSQL(dbconn, deleteGroupRecordSQL, &localError, ^bool(sqlite3_stmt *deleteResponse) {
ok = SecDbBindInt64(deleteResponse, 1, groupId, &localError);
if (ok) {
ok = SecDbStep(dbconn, deleteResponse, &localError, NULL);
}
return ok;
});
});
});
errOut:
(void) CFErrorPropagate(localError, error);
return (groupId < 0) ? false : true;
}
static bool _SecRevocationDbApplyGroupUpdate(SecRevocationDbRef rdb, CFDictionaryRef dict, CFErrorRef *error) {
int64_t groupId = -1;
CFErrorRef localError = NULL;
CFArrayRef issuers = (dict) ? (CFArrayRef)CFDictionaryGetValue(dict, CFSTR("issuer-hash")) : NULL;
if (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(rdb, hash, &localError);
}
}
groupId = _SecRevocationDbUpdateGroup(rdb, groupId, dict, &localError);
if (groupId < 0) {
secdebug("validupdate", "failed to get groupId");
} else {
_SecRevocationDbUpdateIssuers(rdb, groupId, issuers, &localError);
_SecRevocationDbUpdatePerIssuerData(rdb, groupId, dict, &localError);
}
(void) CFErrorPropagate(localError, error);
return (groupId > 0) ? true : false;
}
static void _SecRevocationDbApplyUpdate(SecRevocationDbRef rdb, CFDictionaryRef update, CFIndex version) {
if (!rdb || !update) {
secerror("_SecRevocationDbApplyUpdate failed: invalid args");
return;
}
__block CFDictionaryRef localUpdate = (CFDictionaryRef)CFRetainSafe(update);
__block CFErrorRef localError = NULL;
CFTypeRef value = NULL;
CFIndex deleteCount = 0;
CFIndex updateCount = 0;
rdb->updateInProgress = true;
value = (CFBooleanRef)CFDictionaryGetValue(update, CFSTR("full"));
if (isBoolean(value) && CFBooleanGetValue((CFBooleanRef)value)) {
SecRevocationDbRemoveAllEntries();
}
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)) {
(void)_SecRevocationDbApplyGroupDelete(rdb, issuerHash, &localError);
CFReleaseNull(localError);
}
}
}
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)) {
(void)_SecRevocationDbApplyGroupUpdate(rdb, dict, &localError);
CFReleaseNull(localError);
}
}
}
CFRelease(localUpdate);
_SecRevocationDbSetVersion(rdb, version);
int64_t db_version = _SecRevocationDbGetSchemaVersion(rdb, NULL);
if (db_version <= 0) {
_SecRevocationDbSetSchemaVersion(rdb, kSecRevocationDbSchemaVersion);
}
int64_t db_format = _SecRevocationDbGetUpdateFormat(rdb, NULL);
if (db_format <= 0) {
_SecRevocationDbSetUpdateFormat(rdb, kSecRevocationDbUpdateFormat);
}
(void)SecDbPerformWrite(rdb->db, &localError, ^(SecDbConnectionRef dbconn) {
SecDbExec(dbconn, CFSTR("VACUUM"), &localError);
CFReleaseNull(localError);
});
rdb->updateInProgress = false;
}
static bool _SecRevocationDbSerialInGroup(SecRevocationDbRef rdb,
CFDataRef serial,
int64_t groupId,
CFErrorRef *error) {
__block bool result = false;
__block bool ok = true;
__block CFErrorRef localError = NULL;
require(rdb && serial, errOut);
ok &= SecDbPerformRead(rdb->db, &localError, ^(SecDbConnectionRef dbconn) {
ok &= SecDbWithSQL(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(dbconn, selectSerial, &localError, ^(bool *stop) {
int64_t foundRowId = (int64_t)sqlite3_column_int64(selectSerial, 0);
result = (foundRowId > 0);
});
return ok;
});
});
errOut:
(void) CFErrorPropagate(localError, error);
return result;
}
static bool _SecRevocationDbCertHashInGroup(SecRevocationDbRef rdb,
CFDataRef certHash,
int64_t groupId,
CFErrorRef *error) {
__block bool result = false;
__block bool ok = true;
__block CFErrorRef localError = NULL;
require(rdb && certHash, errOut);
ok &= SecDbPerformRead(rdb->db, &localError, ^(SecDbConnectionRef dbconn) {
ok &= SecDbWithSQL(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(dbconn, selectHash, &localError, ^(bool *stop) {
int64_t foundRowId = (int64_t)sqlite3_column_int64(selectHash, 0);
result = (foundRowId > 0);
});
return ok;
});
});
errOut:
(void) CFErrorPropagate(localError, error);
return result;
}
static bool _SecRevocationDbSerialInFilter(SecRevocationDbRef rdb,
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(SecRevocationDbRef rdb,
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;
SecValidInfoRef result = NULL;
require((serial = SecCertificateCopySerialNumberData(certificate, NULL)) != NULL, errOut);
require((certHash = SecCertificateCopySHA256Digest(certificate)) != NULL, errOut);
require((groupId = _SecRevocationDbGroupIdForIssuerHash(rdb, issuerHash, &localError)) > 0, errOut);
format = _SecRevocationDbGetGroupFormat(rdb, groupId, &flags, &data, &localError);
if (format == kSecValidInfoFormatUnknown) {
}
else if (format == kSecValidInfoFormatSerial) {
matched = _SecRevocationDbSerialInGroup(rdb, serial, groupId, &localError);
}
else if (format == kSecValidInfoFormatSHA256) {
matched = _SecRevocationDbCertHashInGroup(rdb, certHash, groupId, &localError);
}
else if (format == kSecValidInfoFormatNto1) {
matched = _SecRevocationDbSerialInFilter(rdb, serial, data);
}
if (matched) {
secdebug("validupdate", "Valid db matched certificate: %@, format=%d, flags=%lu",
certHash, format, flags);
isOnList = true;
}
else if ((flags & kSecValidInfoComplete) && (flags & kSecValidInfoAllowlist)) {
secdebug("validupdate", "Valid db did NOT match certificate on allowlist: %@, format=%d, flags=%lu",
certHash, format, flags);
matched = true;
}
else if ((!(flags & kSecValidInfoComplete)) && (format > kSecValidInfoFormatUnknown)) {
secdebug("validupdate", "Valid db did not find certificate on incomplete list: %@, format=%d, flags=%lu",
certHash, format, flags);
matched = true;
}
if (matched) {
result = SecValidInfoCreate(format, flags, isOnList, certHash, issuerHash, NULL);
}
if (result && SecIsAppleTrustAnchor(certificate, 0)) {
secdebug("validupdate", "Valid db match for Apple trust anchor: %@, format=%d, flags=%lu",
certHash, format, flags);
SecValidInfoRelease(result);
result = NULL;
}
errOut:
(void) CFErrorPropagate(localError, error);
CFReleaseSafe(data);
CFReleaseSafe(certHash);
CFReleaseSafe(serial);
return result;
}
static SecValidInfoRef _SecRevocationDbCopyMatching(SecRevocationDbRef db,
SecCertificateRef certificate,
SecCertificateRef issuer) {
SecValidInfoRef result = NULL;
CFErrorRef error = NULL;
CFDataRef issuerHash = NULL;
require(certificate && issuer, errOut);
require(issuerHash = SecCertificateCopySHA256Digest(issuer), errOut);
result = _SecRevocationDbValidInfoForCertificate(db, certificate, issuerHash, &error);
errOut:
CFReleaseSafe(issuerHash);
CFReleaseSafe(error);
return result;
}
static dispatch_queue_t _SecRevocationDbGetUpdateQueue(SecRevocationDbRef rdb) {
return (rdb) ? rdb->update_queue : NULL;
}
void SecRevocationDbApplyUpdate(CFDictionaryRef update, CFIndex version) {
SecRevocationDbWith(^(SecRevocationDbRef db) {
_SecRevocationDbApplyUpdate(db, update, version);
});
}
void SecRevocationDbSetSchemaVersion(CFIndex db_version) {
SecRevocationDbWith(^(SecRevocationDbRef db) {
_SecRevocationDbSetSchemaVersion(db, db_version);
});
}
void SecRevocationDbSetUpdateFormat(CFIndex db_format) {
SecRevocationDbWith(^(SecRevocationDbRef db) {
_SecRevocationDbSetUpdateFormat(db, db_format);
});
}
void SecRevocationDbSetUpdateSource(CFStringRef updateSource) {
SecRevocationDbWith(^(SecRevocationDbRef db) {
_SecRevocationDbSetUpdateSource(db, updateSource);
});
}
CFStringRef SecRevocationDbCopyUpdateSource(void) {
__block CFStringRef result = NULL;
SecRevocationDbWith(^(SecRevocationDbRef db) {
result = _SecRevocationDbCopyUpdateSource(db, NULL);
});
return result;
}
void SecRevocationDbSetNextUpdateTime(CFAbsoluteTime nextUpdate) {
SecRevocationDbWith(^(SecRevocationDbRef db) {
_SecRevocationDbSetNextUpdateTime(db, nextUpdate);
});
}
CFAbsoluteTime SecRevocationDbGetNextUpdateTime(void) {
__block CFAbsoluteTime result = -1;
SecRevocationDbWith(^(SecRevocationDbRef db) {
result = _SecRevocationDbGetNextUpdateTime(db, NULL);
});
return result;
}
dispatch_queue_t SecRevocationDbGetUpdateQueue(void) {
__block dispatch_queue_t result = NULL;
SecRevocationDbWith(^(SecRevocationDbRef db) {
result = _SecRevocationDbGetUpdateQueue(db);
});
return result;
}
void SecRevocationDbRemoveAllEntries(void) {
SecRevocationDbWith(^(SecRevocationDbRef db) {
_SecRevocationDbRemoveAllEntries(db);
});
}
void SecRevocationDbReleaseAllConnections(void) {
SecRevocationDbWith(^(SecRevocationDbRef db) {
SecDbReleaseAllConnections((db) ? db->db : NULL);
});
}
SecValidInfoRef SecRevocationDbCopyMatching(SecCertificateRef certificate,
SecCertificateRef issuer) {
__block SecValidInfoRef result = NULL;
SecRevocationDbWith(^(SecRevocationDbRef db) {
result = _SecRevocationDbCopyMatching(db, certificate, issuer);
});
return result;
}
CFIndex SecRevocationDbGetVersion(void) {
__block CFIndex result = -1;
SecRevocationDbWith(^(SecRevocationDbRef db) {
result = (CFIndex)_SecRevocationDbGetVersion(db, NULL);
});
return result;
}
CFIndex SecRevocationDbGetSchemaVersion(void) {
__block CFIndex result = -1;
SecRevocationDbWith(^(SecRevocationDbRef db) {
result = (CFIndex)_SecRevocationDbGetSchemaVersion(db, NULL);
});
return result;
}
CFIndex SecRevocationDbGetUpdateFormat(void) {
__block CFIndex result = -1;
SecRevocationDbWith(^(SecRevocationDbRef db) {
result = (CFIndex)_SecRevocationDbGetUpdateFormat(db, NULL);
});
return result;
}