#include <securityd/SecItemServer.h>
#include <Security/SecItem.h>
#include <Security/SecItemPriv.h>
#include <Security/SecItemInternal.h>
#include <Security/SecKey.h>
#include <Security/SecKeyPriv.h>
#include <Security/SecCertificateInternal.h>
#include <Security/SecIdentity.h>
#include <Security/SecIdentityPriv.h>
#include <Security/SecFramework.h>
#include <Security/SecRandom.h>
#include <Security/SecBasePriv.h>
#include <errno.h>
#include <limits.h>
#include <pthread.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <sys/param.h>
#include <sys/stat.h>
#include <Security/SecBase.h>
#include <CoreFoundation/CFData.h>
#include <CoreFoundation/CFDate.h>
#include <CoreFoundation/CFDictionary.h>
#include <CoreFoundation/CFNumber.h>
#include <CoreFoundation/CFString.h>
#include <CoreFoundation/CFURL.h>
#include <CommonCrypto/CommonDigest.h>
#include <CommonCrypto/CommonCryptor.h>
#include <CommonCrypto/CommonCryptorSPI.h>
#include <libkern/OSByteOrder.h>
#include <security_utilities/debugging.h>
#include <assert.h>
#include <Security/SecInternal.h>
#include <TargetConditionals.h>
#include "securityd_client.h"
#include "securityd_server.h"
#include "sqlutils.h"
#include <AssertMacros.h>
#include <asl.h>
#include <inttypes.h>
#if TARGET_OS_EMBEDDED && !TARGET_IPHONE_SIMULATOR
#define USE_HWAES 1
#define USE_KEYSTORE 1
#else
#define USE_HWAES 0
#define USE_KEYSTORE 0
#endif
#if USE_HWAES
#include <IOKit/IOKitLib.h>
#include <Kernel/IOKit/crypto/IOAESTypes.h>
#endif
#if USE_KEYSTORE
#include <Kernel/IOKit/crypto/AppleKeyStoreDefs.h>
#include <MobileKeyBag/MobileKeyBag.h>
typedef int32_t keyclass_t;
#else
#define kAppleKeyStoreKeyWrap 0
#define kAppleKeyStoreKeyUnwrap 1
typedef int32_t keyclass_t;
typedef int32_t key_handle_t;
typedef int32_t keybag_handle_t;
enum key_classes {
key_class_ak = 6,
key_class_ck,
key_class_dk,
key_class_aku,
key_class_cku,
key_class_dku
};
#endif
enum {
KEYBAG_LEGACY = -3,
KEYBAG_BACKUP = -2,
KEYBAG_NONE = -1,
KEYBAG_DEVICE = 0,
};
#if 0
#include <CoreFoundation/CFPriv.h>
#else
CF_EXPORT
CFURLRef CFCopyHomeDirectoryURLForUser(CFStringRef uName);
#endif
#define CERTIFICATE_DATA_COLUMN_LABEL "certdata"
#define CURRENT_DB_VERSION 5
#define CLOSE_DB 0
static bool isArray(CFTypeRef cfType) {
return cfType && CFGetTypeID(cfType) == CFArrayGetTypeID();
}
static bool isData(CFTypeRef cfType) {
return cfType && CFGetTypeID(cfType) == CFDataGetTypeID();
}
static bool isDictionary(CFTypeRef cfType) {
return cfType && CFGetTypeID(cfType) == CFDictionaryGetTypeID();
}
static bool isNumber(CFTypeRef cfType) {
return cfType && CFGetTypeID(cfType) == CFNumberGetTypeID();
}
static bool isNumberOfType(CFTypeRef cfType, CFNumberType number) {
return isNumber(cfType) && CFNumberGetType(cfType) == number;
}
static bool isString(CFTypeRef cfType) {
return cfType && CFGetTypeID(cfType) == CFStringGetTypeID();
}
typedef struct s3dl_db
{
char *db_name;
pthread_key_t key;
pthread_mutex_t mutex;
struct s3dl_db_thread *dbt_head;
bool use_hwaes;
} s3dl_db;
typedef s3dl_db *db_handle;
typedef struct s3dl_db_thread
{
struct s3dl_db_thread *dbt_next;
s3dl_db *db;
sqlite3 *s3_handle;
bool autocommit;
bool in_transaction;
} s3dl_db_thread;
typedef struct s3dl_results_handle
{
uint32_t recordid;
sqlite3_stmt *stmt;
} s3dl_results_handle;
static CFDictionaryRef gClasses;
enum SecItemFilter {
kSecNoItemFilter,
kSecSysBoundItemFilter,
kSecBackupableItemFilter,
};
static CFDictionaryRef SecServerExportKeychainPlist(s3dl_db_thread *dbt,
keybag_handle_t src_keybag, keybag_handle_t dest_keybag,
enum SecItemFilter filter, int version, OSStatus *error);
static OSStatus SecServerImportKeychainInPlist(s3dl_db_thread *dbt,
keybag_handle_t src_keybag, keybag_handle_t dest_keybag,
CFDictionaryRef keychain, enum SecItemFilter filter);
#if USE_HWAES || USE_KEYSTORE
static pthread_once_t hwcrypto_init_once = PTHREAD_ONCE_INIT;
static io_connect_t hwaes_codec = MACH_PORT_NULL;
static io_connect_t keystore = MACH_PORT_NULL;
static void service_matching_callback(void *refcon, io_iterator_t iterator)
{
io_object_t obj = IO_OBJECT_NULL;
while ((obj = IOIteratorNext(iterator))) {
kern_return_t ret = IOServiceOpen(obj, mach_task_self(), 0,
(io_connect_t*)refcon);
if (ret) {
asl_log(NULL, NULL, ASL_LEVEL_ERR,
"IOServiceOpen() failed: %x", ret);
}
IOObjectRelease(obj);
}
}
static void connect_to_service(const char *className, io_connect_t *connect)
{
kern_return_t kernResult;
io_iterator_t iterator = MACH_PORT_NULL;
IONotificationPortRef port = MACH_PORT_NULL;
CFDictionaryRef classToMatch;
if ((classToMatch = IOServiceMatching(className)) == NULL) {
asl_log(NULL, NULL, ASL_LEVEL_ERR,
"IOServiceMatching failed for '%s'", className);
return;
}
CFRetain(classToMatch);
kernResult = IOServiceGetMatchingServices(kIOMasterPortDefault,
classToMatch, &iterator);
if (kernResult == KERN_SUCCESS) {
CFRelease(classToMatch);
} else {
asl_log(NULL, NULL, ASL_LEVEL_WARNING,
"IOServiceGetMatchingServices() failed %x using notifiction",
kernResult);
port = IONotificationPortCreate(kIOMasterPortDefault);
if (!port) {
asl_log(NULL, NULL, ASL_LEVEL_ERR,
"IONotificationPortCreate() failed");
return;
}
kernResult = IOServiceAddMatchingNotification(port,
kIOFirstMatchNotification, classToMatch, service_matching_callback,
connect, &iterator);
if (kernResult) {
asl_log(NULL, NULL, ASL_LEVEL_ERR,
"IOServiceAddMatchingNotification() failed: %x", kernResult);
return;
}
}
service_matching_callback(connect, iterator);
if (port) {
if (*connect == MACH_PORT_NULL) {
asl_log(NULL, NULL, ASL_LEVEL_ERR,
"Waiting for %s to show up.", className);
CFStringRef mode = CFSTR("WaitForCryptoService");
CFRunLoopAddSource(CFRunLoopGetCurrent(),
IONotificationPortGetRunLoopSource(port), mode);
CFRunLoopRunInMode(mode, 30.0, true);
if (hwaes_codec == MACH_PORT_NULL)
asl_log(NULL, NULL, ASL_LEVEL_ERR, "Cannot find AES driver");
}
IONotificationPortDestroy(port);
}
IOObjectRelease(iterator);
if (*connect) {
asl_log(NULL, NULL, ASL_LEVEL_INFO, "Obtained connection %d for %s",
*connect, className);
}
return;
}
static void hwcrypto_init(void)
{
connect_to_service(kIOAESAcceleratorClass, &hwaes_codec);
connect_to_service(kAppleKeyStoreServiceName, &keystore);
if (keystore != MACH_PORT_NULL) {
IOReturn kernResult = IOConnectCallMethod(keystore,
kAppleKeyStoreUserClientOpen, NULL, 0, NULL, 0, NULL, NULL,
NULL, NULL);
if (kernResult) {
asl_log(NULL, NULL, ASL_LEVEL_ERR,
"Failed to open AppleKeyStore: %x", kernResult);
} else {
asl_log(NULL, NULL, ASL_LEVEL_INFO, "Opened AppleKeyStore");
}
}
int kb_state = MKBGetDeviceLockState(NULL);
asl_log(NULL, NULL, ASL_LEVEL_INFO, "AppleKeyStore lock state: %d",
kb_state);
}
static bool hwaes_crypt(IOAESOperation operation, UInt32 keyHandle,
UInt32 keySizeInBits, const UInt8 *keyBits, const UInt8 *iv,
UInt32 textLength, UInt8 *plainText, UInt8 *cipherText)
{
struct IOAESAcceleratorRequest aesRequest;
kern_return_t kernResult;
IOByteCount outSize;
if (!hwaes_codec) {
asl_log(NULL, NULL, ASL_LEVEL_ERR, "aes codec not initialized");
return false;
}
aesRequest.plainText = plainText;
aesRequest.cipherText = cipherText;
aesRequest.textLength = textLength;
memcpy(aesRequest.iv.ivBytes, iv, 16);
aesRequest.operation = operation;
aesRequest.keyData.key.keyLength = keySizeInBits;
aesRequest.keyData.keyHandle = keyHandle;
if (keyBits) {
memcpy(aesRequest.keyData.key.keyBytes, keyBits, keySizeInBits / 8);
} else {
bzero(aesRequest.keyData.key.keyBytes, keySizeInBits / 8);
}
outSize = sizeof(aesRequest);
kernResult = IOConnectCallStructMethod(hwaes_codec,
kIOAESAcceleratorPerformAES, &aesRequest, outSize,
&aesRequest, &outSize);
if (kernResult != KERN_SUCCESS) {
asl_log(NULL, NULL, ASL_LEVEL_ERR, "kIOAESAcceleratorPerformAES: %x",
kernResult);
return false;
}
asl_log(NULL, NULL, ASL_LEVEL_INFO,
"kIOAESAcceleratorPerformAES processed: %lu bytes", textLength);
return true;
}
static bool hwaes_key_available(void)
{
UInt8 buf[32] = {};
UInt8 *bufp = (UInt8*)(((intptr_t)&buf[15]) & ~15);
pthread_once(&hwcrypto_init_once, hwcrypto_init);
return hwaes_crypt(IOAESOperationEncrypt,
kIOAESAcceleratorKeyHandleKeychain, 128, NULL, bufp, 16, bufp, bufp);
}
#else
static bool hwaes_key_available(void)
{
return false;
}
#endif
static int s3dl_create_path(const char *path)
{
char pathbuf[PATH_MAX];
size_t pos, len = strlen(path);
if (len == 0 || len > PATH_MAX)
return SQLITE_CANTOPEN;
memcpy(pathbuf, path, len);
for (pos = len; --pos > 0;)
{
if (pathbuf[pos] == '/')
{
pathbuf[pos] = '\0';
if (!mkdir(pathbuf, 0777))
break;
else
{
int err = errno;
if (err == EEXIST)
return 0;
if (err == ENOTDIR)
return SQLITE_CANTOPEN;
if (err == EROFS)
return SQLITE_READONLY;
if (err == EACCES)
return SQLITE_PERM;
if (err == ENOSPC || err == EDQUOT)
return SQLITE_FULL;
if (err == EIO)
return SQLITE_IOERR;
return SQLITE_INTERNAL;
}
}
}
return 0;
}
static int s3dl_begin_transaction(s3dl_db_thread *dbt)
{
if (dbt->in_transaction)
return dbt->autocommit ? SQLITE_INTERNAL : SQLITE_OK;
int s3e = sqlite3_exec(dbt->s3_handle, "BEGIN EXCLUSIVE TRANSACTION",
NULL, NULL, NULL);
if (s3e == SQLITE_OK)
dbt->in_transaction = true;
return s3e;
}
static int s3dl_commit_transaction(s3dl_db_thread *dbt)
{
if (!dbt->in_transaction)
return SQLITE_OK;
int s3e = sqlite3_exec(dbt->s3_handle, "COMMIT TRANSACTION",
NULL, NULL, NULL);
if (s3e == SQLITE_OK)
dbt->in_transaction = false;
return s3e;
}
static int s3dl_rollback_transaction(s3dl_db_thread *dbt)
{
if (!dbt->in_transaction)
return SQLITE_OK;
int s3e = sqlite3_exec(dbt->s3_handle, "ROLLBACK TRANSACTION",
NULL, NULL, NULL);
if (s3e == SQLITE_OK)
dbt->in_transaction = false;
return s3e;
}
static int s3dl_end_transaction(s3dl_db_thread *dbt, int s3e)
{
if (dbt->autocommit)
{
if (s3e == SQLITE_OK)
return s3dl_commit_transaction(dbt);
else
s3dl_rollback_transaction(dbt);
}
return s3e;
}
static int s3dl_close_dbt(s3dl_db_thread *dbt)
{
int s3e = sqlite3_close(dbt->s3_handle);
free(dbt);
return s3e;
}
typedef enum {
kc_blob_attr, kc_data_attr,
kc_string_attr,
kc_number_attr,
kc_date_attr,
kc_creation_date_attr,
kc_modification_date_attr
} kc_attr_kind;
enum {
kc_constrain_not_null = (1 << 0), kc_constrain_default_0 = (1 << 1), kc_constrain_default_empty = (1 << 2), kc_digest_attr = (1 << 3), };
typedef struct kc_attr_desc {
CFStringRef name;
kc_attr_kind kind;
CFOptionFlags flags;
} kc_attr_desc;
typedef struct kc_class {
CFStringRef name;
CFIndex n_attrs;
const kc_attr_desc *attrs;
} kc_class;
#if 0
typedef struct kc_item {
kc_class *c;
CFMutableDictionaryRef a;
} kc_item;
typedef CFIndex kc_attr_id;
#endif
#define KC_ATTR(name, kind, flags) { CFSTR(name), kc_ ## kind ## _attr, flags }
static const kc_attr_desc genp_attrs[] = {
KC_ATTR("pdmn", string, 0),
KC_ATTR("agrp", string, 0),
KC_ATTR("cdat", creation_date, 0),
KC_ATTR("mdat", modification_date, 0),
KC_ATTR("desc", blob, kc_digest_attr),
KC_ATTR("icmt", blob, kc_digest_attr),
KC_ATTR("crtr", number, 0),
KC_ATTR("type", number, 0),
KC_ATTR("scrp", number, 0),
KC_ATTR("labl", blob, kc_digest_attr),
KC_ATTR("alis", blob, kc_digest_attr),
KC_ATTR("invi", number, 0),
KC_ATTR("nega", number, 0),
KC_ATTR("cusi", number, 0),
KC_ATTR("prot", blob, kc_digest_attr),
KC_ATTR("acct", blob, kc_constrain_not_null | kc_constrain_default_empty | kc_digest_attr),
KC_ATTR("svce", blob, kc_constrain_not_null | kc_constrain_default_empty | kc_digest_attr),
KC_ATTR("gena", blob, kc_digest_attr),
};
#if 0
static const kc_attr_id genp_unique[] = {
15, 16, 1
};
static kc_class_constraint genp_constraints[] = {
{ kc_unique_constraint, sizeof(genp_unique) / sizeof(*genp_unique), genp_unique },
};
#endif
static const kc_class genp_class = {
.name = CFSTR("genp"),
.n_attrs = sizeof(genp_attrs) / sizeof(*genp_attrs),
.attrs = genp_attrs,
};
static const kc_attr_desc inet_attrs[] = {
KC_ATTR("pdmn", string, 0),
KC_ATTR("agrp", string, 0),
KC_ATTR("cdat", creation_date, 0),
KC_ATTR("mdat", modification_date, 0),
KC_ATTR("desc", blob, kc_digest_attr),
KC_ATTR("icmt", blob, kc_digest_attr),
KC_ATTR("crtr", number, 0),
KC_ATTR("type", number, 0),
KC_ATTR("scrp", number, 0),
KC_ATTR("labl", blob, kc_digest_attr),
KC_ATTR("alis", blob, kc_digest_attr),
KC_ATTR("invi", number, 0),
KC_ATTR("nega", number, 0),
KC_ATTR("cusi", number, 0),
KC_ATTR("prot", blob, kc_digest_attr),
KC_ATTR("acct", blob, kc_constrain_not_null | kc_constrain_default_empty | kc_digest_attr),
KC_ATTR("sdmn", blob, kc_constrain_not_null | kc_constrain_default_empty | kc_digest_attr),
KC_ATTR("srvr", blob, kc_constrain_not_null | kc_constrain_default_empty | kc_digest_attr),
KC_ATTR("ptcl", number, kc_constrain_not_null | kc_constrain_default_0),
KC_ATTR("atyp", blob, kc_digest_attr),
KC_ATTR("port", number, kc_constrain_not_null | kc_constrain_default_0),
KC_ATTR("path", blob, kc_constrain_not_null | kc_constrain_default_empty | kc_digest_attr),
};
#if 0
static const kc_attr_id inet_unique[] = {
15, 16, 17, 18, 19, 20, 21, 1
};
static kc_class_constraint inet_constraints[] = {
{ kc_unique_constraint, sizeof(inet_unique) / sizeof(*inet_unique), inet_unique },
};
#endif
static const kc_class inet_class = {
.name = CFSTR("inet"),
.n_attrs = sizeof(inet_attrs) / sizeof(*inet_attrs),
.attrs = inet_attrs,
};
static const kc_attr_desc cert_attrs[] = {
KC_ATTR("pdmn", string, 0),
KC_ATTR("agrp", string, 0),
KC_ATTR("cdat", creation_date, 0),
KC_ATTR("mdat", modification_date, 0),
KC_ATTR("ctyp", number, kc_constrain_not_null | kc_constrain_default_0),
KC_ATTR("cenc", number, 0),
KC_ATTR("labl", blob, kc_digest_attr),
KC_ATTR("alis", blob, kc_digest_attr),
KC_ATTR("subj", data, kc_digest_attr),
KC_ATTR("issr", data, kc_constrain_not_null | kc_constrain_default_empty | kc_digest_attr),
KC_ATTR("slnr", data, kc_constrain_not_null | kc_constrain_default_empty | kc_digest_attr),
KC_ATTR("skid", data, kc_digest_attr),
KC_ATTR("pkhh", data, 0),
};
#if 0
static const kc_attr_id cert_unique[] = {
2, 7, 8, 1
};
static kc_class_constraint cert_constraints[] = {
{ kc_unique_constraint, sizeof(cert_unique) / sizeof(*cert_unique), cert_unique, },
};
#endif
static const kc_class cert_class = {
.name = CFSTR("cert"),
.n_attrs = sizeof(cert_attrs) / sizeof(*cert_attrs),
.attrs = cert_attrs,
};
static const kc_attr_desc keys_attrs[] = {
KC_ATTR("pdmn", string, 0),
KC_ATTR("agrp", string, 0),
KC_ATTR("cdat", creation_date, 0),
KC_ATTR("mdat", modification_date, 0),
KC_ATTR("kcls", number, kc_constrain_not_null | kc_constrain_default_0),
KC_ATTR("labl", blob, kc_digest_attr),
KC_ATTR("alis", blob, kc_digest_attr),
KC_ATTR("perm", number, 0),
KC_ATTR("priv", number, 0),
KC_ATTR("modi", number, 0),
KC_ATTR("klbl", data, kc_constrain_not_null | kc_constrain_default_empty),
KC_ATTR("atag", blob, kc_constrain_not_null | kc_constrain_default_empty | kc_digest_attr),
KC_ATTR("crtr", number, kc_constrain_not_null | kc_constrain_default_0),
KC_ATTR("type", number, kc_constrain_not_null | kc_constrain_default_0),
KC_ATTR("bsiz", number, kc_constrain_not_null | kc_constrain_default_0),
KC_ATTR("esiz", number, kc_constrain_not_null | kc_constrain_default_0),
KC_ATTR("sdat", date, kc_constrain_not_null | kc_constrain_default_0),
KC_ATTR("edat", date, kc_constrain_not_null | kc_constrain_default_0),
KC_ATTR("sens", number, 0),
KC_ATTR("asen", number, 0),
KC_ATTR("extr", number, 0),
KC_ATTR("next", number, 0),
KC_ATTR("encr", number, 0),
KC_ATTR("decr", number, 0),
KC_ATTR("drve", number, 0),
KC_ATTR("sign", number, 0),
KC_ATTR("vrfy", number, 0),
KC_ATTR("snrc", number, 0),
KC_ATTR("vyrc", number, 0),
KC_ATTR("wrap", number, 0),
KC_ATTR("unwp", number, 0),
};
#if 0
static const kc_attr_id keys_unique[] = {
2, 8, 9, 10, 11, 12, 13, 14, 15, 1
};
static kc_class_constraint keys_constraints[] = {
{ kc_unique_constraint, sizeof(keys_unique) / sizeof(*keys_unique), keys_unique, },
};
#endif
static const kc_class keys_class = {
.name = CFSTR("keys"),
.n_attrs = sizeof(keys_attrs) / sizeof(*keys_attrs),
.attrs = keys_attrs,
};
static const kc_class identity_class = {
.name = CFSTR("idnt"),
.n_attrs = 0,
.attrs = NULL,
};
static const kc_attr_desc *kc_attr_desc_with_key(const kc_class *c,
CFTypeRef key,
OSStatus *error) {
if (c == &identity_class) {
const kc_attr_desc *desc;
if (!(desc = kc_attr_desc_with_key(&cert_class, key, 0)))
desc = kc_attr_desc_with_key(&keys_class, key, error);
return desc;
}
if (isString(key)) {
CFIndex ix;
for (ix = 0; ix < c->n_attrs; ++ix) {
if (CFEqual(c->attrs[ix].name, key)) {
return &c->attrs[ix];
}
}
if (CFEqual(key, kSecAttrSynchronizable))
return NULL;
}
if (error && !*error)
*error = errSecNoSuchAttr;
return NULL;
}
static const char * const s3dl_upgrade_sql[] = {
"CREATE TABLE genp("
"rowid INTEGER PRIMARY KEY AUTOINCREMENT,"
"cdat REAL,"
"mdat REAL,"
"desc BLOB,"
"icmt BLOB,"
"crtr INTEGER,"
"type INTEGER,"
"scrp INTEGER,"
"labl BLOB,"
"alis BLOB,"
"invi INTEGER,"
"nega INTEGER,"
"cusi INTEGER,"
"prot BLOB,"
"acct BLOB NOT NULL DEFAULT '',"
"svce BLOB NOT NULL DEFAULT '',"
"gena BLOB,"
"data BLOB,"
"agrp TEXT,"
"pdmn TEXT,"
"UNIQUE("
"acct,svce,agrp"
"));"
"CREATE TABLE inet("
"rowid INTEGER PRIMARY KEY AUTOINCREMENT,"
"cdat REAL,"
"mdat REAL,"
"desc BLOB,"
"icmt BLOB,"
"crtr INTEGER,"
"type INTEGER,"
"scrp INTEGER,"
"labl BLOB,"
"alis BLOB,"
"invi INTEGER,"
"nega INTEGER,"
"cusi INTEGER,"
"prot BLOB,"
"acct BLOB NOT NULL DEFAULT '',"
"sdmn BLOB NOT NULL DEFAULT '',"
"srvr BLOB NOT NULL DEFAULT '',"
"ptcl INTEGER NOT NULL DEFAULT 0,"
"atyp BLOB NOT NULL DEFAULT '',"
"port INTEGER NOT NULL DEFAULT 0,"
"path BLOB NOT NULL DEFAULT '',"
"data BLOB,"
"agrp TEXT,"
"pdmn TEXT,"
"UNIQUE("
"acct,sdmn,srvr,ptcl,atyp,port,path,agrp"
"));"
"CREATE TABLE cert("
"rowid INTEGER PRIMARY KEY AUTOINCREMENT,"
"cdat REAL,"
"mdat REAL,"
"ctyp INTEGER NOT NULL DEFAULT 0,"
"cenc INTEGER,"
"labl BLOB,"
"alis BLOB,"
"subj BLOB,"
"issr BLOB NOT NULL DEFAULT '',"
"slnr BLOB NOT NULL DEFAULT '',"
"skid BLOB,"
"pkhh BLOB,"
"data BLOB,"
"agrp TEXT,"
"pdmn TEXT,"
"UNIQUE("
"ctyp,issr,slnr,agrp"
"));"
"CREATE TABLE keys("
"rowid INTEGER PRIMARY KEY AUTOINCREMENT,"
"cdat REAL,"
"mdat REAL,"
"kcls INTEGER NOT NULL DEFAULT 0,"
"labl BLOB,"
"alis BLOB,"
"perm INTEGER,"
"priv INTEGER,"
"modi INTEGER,"
"klbl BLOB NOT NULL DEFAULT '',"
"atag BLOB NOT NULL DEFAULT '',"
"crtr INTEGER NOT NULL DEFAULT 0,"
"type INTEGER NOT NULL DEFAULT 0,"
"bsiz INTEGER NOT NULL DEFAULT 0,"
"esiz INTEGER NOT NULL DEFAULT 0,"
"sdat REAL NOT NULL DEFAULT 0,"
"edat REAL NOT NULL DEFAULT 0,"
"sens INTEGER,"
"asen INTEGER,"
"extr INTEGER,"
"next INTEGER,"
"encr INTEGER,"
"decr INTEGER,"
"drve INTEGER,"
"sign INTEGER,"
"vrfy INTEGER,"
"snrc INTEGER,"
"vyrc INTEGER,"
"wrap INTEGER,"
"unwp INTEGER,"
"data BLOB,"
"agrp TEXT,"
"pdmn TEXT,"
"UNIQUE("
"kcls,klbl,atag,crtr,type,bsiz,esiz,sdat,edat,agrp"
"));"
"CREATE TABLE tversion(version INTEGER);"
"INSERT INTO tversion(version) VALUES(5);",
"CREATE INDEX ialis ON cert(alis);"
"CREATE INDEX isubj ON cert(subj);"
"CREATE INDEX iskid ON cert(skid);"
"CREATE INDEX ipkhh ON cert(pkhh);"
"CREATE INDEX ikcls ON keys(kcls);"
"CREATE INDEX iklbl ON keys(klbl);"
"CREATE INDEX iencr ON keys(encr);"
"CREATE INDEX idecr ON keys(decr);"
"CREATE INDEX idrve ON keys(drve);"
"CREATE INDEX isign ON keys(sign);"
"CREATE INDEX ivrfy ON keys(vrfy);"
"CREATE INDEX iwrap ON keys(wrap);"
"CREATE INDEX iunwp ON keys(unwp);",
"ALTER TABLE genp RENAME TO ogenp;"
"ALTER TABLE inet RENAME TO oinet;"
"ALTER TABLE cert RENAME TO ocert;"
"ALTER TABLE keys RENAME TO okeys;",
"ALTER TABLE genp RENAME TO ogenp;"
"ALTER TABLE inet RENAME TO oinet;"
"ALTER TABLE cert RENAME TO ocert;"
"ALTER TABLE keys RENAME TO okeys;"
"DROP TABLE tversion;",
"INSERT INTO genp (rowid,cdat,mdat,desc,icmt,crtr,type,scrp,labl,alis,invi,nega,cusi,prot,acct,svce,gena,data) SELECT rowid,cdat,mdat,desc,icmt,crtr,type,scrp,labl,alis,invi,nega,cusi,prot,acct,svce,gena,data from ogenp;"
"INSERT INTO inet (rowid,cdat,mdat,desc,icmt,crtr,type,scrp,labl,alis,invi,nega,cusi,prot,acct,sdmn,srvr,ptcl,atyp,port,path,data) SELECT rowid,cdat,mdat,desc,icmt,crtr,type,scrp,labl,alis,invi,nega,cusi,prot,acct,sdmn,srvr,ptcl,atyp,port,path,data from oinet;"
"INSERT INTO cert (rowid,ctyp,cenc,labl,alis,subj,issr,slnr,skid,pkhh,data) SELECT rowid,ctyp,cenc,labl,alis,subj,issr,slnr,skid,pkhh,data from ocert;"
"INSERT INTO keys (rowid,kcls,labl,alis,perm,priv,modi,klbl,atag,crtr,type,bsiz,esiz,sdat,edat,sens,asen,extr,next,encr,decr,drve,sign,vrfy,snrc,vyrc,wrap,unwp,data) SELECT rowid,kcls,labl,alis,perm,priv,modi,klbl,atag,crtr,type,bsiz,esiz,sdat,edat,sens,asen,extr,next,encr,decr,drve,sign,vrfy,snrc,vyrc,wrap,unwp,data from okeys;"
"UPDATE genp SET agrp='apple';"
"UPDATE inet SET agrp='apple';"
"UPDATE cert SET agrp='apple';"
"UPDATE keys SET agrp='apple';"
"DROP TABLE ogenp;"
"DROP TABLE oinet;"
"DROP TABLE ocert;"
"DROP TABLE okeys;",
"INSERT INTO genp (rowid,cdat,mdat,desc,icmt,crtr,type,scrp,labl,alis,invi,nega,cusi,prot,acct,svce,gena,data,agrp) SELECT rowid,cdat,mdat,desc,icmt,crtr,type,scrp,labl,alis,invi,nega,cusi,prot,acct,svce,gena,data,agrp from ogenp;"
"INSERT INTO inet (rowid,cdat,mdat,desc,icmt,crtr,type,scrp,labl,alis,invi,nega,cusi,prot,acct,sdmn,srvr,ptcl,atyp,port,path,data,agrp) SELECT rowid,cdat,mdat,desc,icmt,crtr,type,scrp,labl,alis,invi,nega,cusi,prot,acct,sdmn,srvr,ptcl,atyp,port,path,data,agrp from oinet;"
"INSERT INTO cert (rowid,ctyp,cenc,labl,alis,subj,issr,slnr,skid,pkhh,data,agrp) SELECT rowid,ctyp,cenc,labl,alis,subj,issr,slnr,skid,pkhh,data,agrp from ocert;"
"INSERT INTO keys (rowid,kcls,labl,alis,perm,priv,modi,klbl,atag,crtr,type,bsiz,esiz,sdat,edat,sens,asen,extr,next,encr,decr,drve,sign,vrfy,snrc,vyrc,wrap,unwp,data,agrp) SELECT rowid,kcls,labl,alis,perm,priv,modi,klbl,atag,crtr,type,bsiz,esiz,sdat,edat,sens,asen,extr,next,encr,decr,drve,sign,vrfy,snrc,vyrc,wrap,unwp,data,agrp from okeys;"
"DROP TABLE ogenp;"
"DROP TABLE oinet;"
"DROP TABLE ocert;"
"DROP TABLE okeys;",
"INSERT INTO genp (rowid,cdat,mdat,desc,icmt,crtr,type,scrp,labl,alis,invi,nega,cusi,prot,acct,svce,gena,data,agrp,pdmn) SELECT rowid,cdat,mdat,desc,icmt,crtr,type,scrp,labl,alis,invi,nega,cusi,prot,acct,svce,gena,data,agrp,pdmn from ogenp;"
"INSERT INTO inet (rowid,cdat,mdat,desc,icmt,crtr,type,scrp,labl,alis,invi,nega,cusi,prot,acct,sdmn,srvr,ptcl,atyp,port,path,data,agrp,pdmn) SELECT rowid,cdat,mdat,desc,icmt,crtr,type,scrp,labl,alis,invi,nega,cusi,prot,acct,sdmn,srvr,ptcl,atyp,port,path,data,agrp,pdmn from oinet;"
"INSERT INTO cert (rowid,ctyp,cenc,labl,alis,subj,issr,slnr,skid,pkhh,data,agrp,pdmn) SELECT rowid,ctyp,cenc,labl,alis,subj,issr,slnr,skid,pkhh,data,agrp,pdmn from ocert;"
"INSERT INTO keys (rowid,kcls,labl,alis,perm,priv,modi,klbl,atag,crtr,type,bsiz,esiz,sdat,edat,sens,asen,extr,next,encr,decr,drve,sign,vrfy,snrc,vyrc,wrap,unwp,data,agrp,pdmn) SELECT rowid,kcls,labl,alis,perm,priv,modi,klbl,atag,crtr,type,bsiz,esiz,sdat,edat,sens,asen,extr,next,encr,decr,drve,sign,vrfy,snrc,vyrc,wrap,unwp,data,agrp,pdmn from okeys;"
"DROP TABLE ogenp;"
"DROP TABLE oinet;"
"DROP TABLE ocert;"
"DROP TABLE okeys;",
};
struct sql_stages {
int pre;
int main;
int post;
bool init_pdmn;
};
static struct sql_stages s3dl_upgrade_script[] = {
{ -1, 0, 1, false },
{ 2, 0, 4, true },
{ 3, 0, 4, true },
{ 3, 0, 5, true },
{ 3, 0, 6, true },
};
static int sql_run_script(s3dl_db_thread *dbt, int number)
{
int s3e;
if (number < 0)
return SQLITE_OK;
if ((size_t)number >= sizeof(s3dl_upgrade_sql) / sizeof(*s3dl_upgrade_sql))
return SQLITE_CORRUPT;
char *errmsg = NULL;
s3e = sqlite3_exec(dbt->s3_handle, s3dl_upgrade_sql[number],
NULL, NULL, &errmsg);
if (errmsg) {
secwarning("script %d: %s", number, errmsg);
sqlite3_free(errmsg);
}
return s3e;
}
static int s3dl_dbt_upgrade_from_version(s3dl_db_thread *dbt, int version)
{
int s3e;
if (version < 0 ||
(size_t)version >= sizeof(s3dl_upgrade_script) / sizeof(*s3dl_upgrade_script))
return SQLITE_CORRUPT;
struct sql_stages *script = &s3dl_upgrade_script[version];
s3e = sql_run_script(dbt, script->pre);
if (s3e == SQLITE_OK)
s3e = sql_run_script(dbt, script->main);
if (s3e == SQLITE_OK)
s3e = sql_run_script(dbt, script->post);
if (script->init_pdmn) {
OSStatus status = s3e;
CFDictionaryRef backup = SecServerExportKeychainPlist(dbt,
version < 4 ? KEYBAG_LEGACY : KEYBAG_DEVICE,
KEYBAG_NONE, kSecNoItemFilter, version, &status);
if (backup) {
if (status) {
secerror("Ignoring export error: %d during upgrade", status);
}
status = SecServerImportKeychainInPlist(dbt, KEYBAG_NONE,
KEYBAG_DEVICE, backup, kSecNoItemFilter);
CFRelease(backup);
} else if (status == errSecInteractionNotAllowed){
status = errSecUpgradePending;
}
s3e = status;
}
return s3e;
}
static int s3dl_dbt_create_or_upgrade(s3dl_db_thread *dbt)
{
sqlite3_stmt *stmt = NULL;
int s3e;
s3e = sqlite3_prepare(dbt->s3_handle, "SELECT cdat FROM genp", -1, &stmt, NULL);
if (stmt)
sqlite3_finalize(stmt);
return s3dl_dbt_upgrade_from_version(dbt, s3e ? 0 : 1);
}
static int s3dl_dbt_get_version(s3dl_db_thread *dbt, int *version)
{
sqlite3 *s3h = dbt->s3_handle;
int s3e;
sqlite3_stmt *stmt = NULL;
static const char sql[] = "SELECT version FROM tversion LIMIT 1;";
s3e = sqlite3_prepare(s3h, sql, sizeof(sql) - 1, &stmt, NULL);
if (s3e)
goto errOut;
s3e = sqlite3_step(stmt);
if (s3e == SQLITE_ROW) {
*version = sqlite3_column_int(stmt, 0);
s3e = SQLITE_OK;
} else if (s3e) {
secwarning("SELECT version step: %s", sqlite3_errmsg(s3h));
} else {
s3e = SQLITE_CORRUPT;
}
errOut:
if (stmt) {
sqlite3_finalize(stmt);
}
return s3e;
}
static int s3dl_dbt_upgrade(s3dl_db_thread *dbt)
{
int version;
int s3e;
require_noerr(s3e = s3dl_begin_transaction(dbt), errOut);
s3e = s3dl_dbt_get_version(dbt, &version);
if (!s3e) {
s3e = s3dl_dbt_upgrade_from_version(dbt, version);
} else {
require_noerr(s3e = s3dl_dbt_create_or_upgrade(dbt), errOut);
}
errOut:
return s3dl_end_transaction(dbt, s3e);
}
static int s3dl_create_dbt(s3dl_db *db, s3dl_db_thread **pdbt, int create)
{
sqlite3 *s3h;
int retries, s3e;
for (retries = 0; retries < 2; ++retries) {
s3e = sqlite3_open(db->db_name, &s3h);
if (s3e == SQLITE_CANTOPEN && create)
{
s3dl_create_path(db->db_name);
s3e = sqlite3_open(db->db_name, &s3h);
}
if (!s3e) {
s3dl_db_thread *dbt = (s3dl_db_thread *)malloc(sizeof(s3dl_db_thread));
dbt->dbt_next = NULL;
dbt->db = db;
dbt->s3_handle = s3h;
dbt->autocommit = true;
dbt->in_transaction = false;
int version;
s3e = s3dl_dbt_get_version(dbt, &version);
if (s3e == SQLITE_EMPTY || s3e == SQLITE_ERROR || (!s3e && version < CURRENT_DB_VERSION)) {
s3e = s3dl_dbt_upgrade(dbt);
if (s3e) {
asl_log(NULL, NULL, ASL_LEVEL_CRIT,
"failed to upgrade keychain %s: %d", db->db_name, s3e);
if (s3e != errSecUpgradePending) {
s3e = SQLITE_CORRUPT;
}
}
} else if (s3e) {
asl_log(NULL, NULL, ASL_LEVEL_ERR,
"failed to obtain database version for %s: %d",
db->db_name, s3e);
} else if (version > CURRENT_DB_VERSION) {
asl_log(NULL, NULL, ASL_LEVEL_ERR,
"found keychain %s with version: %d which is newer than %d marking as corrupted",
db->db_name, version, CURRENT_DB_VERSION);
s3e = SQLITE_CORRUPT;
}
if (s3e) {
s3dl_close_dbt(dbt);
} else {
*pdbt = dbt;
break;
}
}
if (s3e == SQLITE_CORRUPT || s3e == SQLITE_NOTADB ||
s3e == SQLITE_CANTOPEN || s3e == SQLITE_PERM ||
s3e == SQLITE_CONSTRAINT) {
size_t len = strlen(db->db_name);
char *old_db_name = malloc(len + 9);
memcpy(old_db_name, db->db_name, len);
strcpy(old_db_name + len, ".corrupt");
if (rename(db->db_name, old_db_name)) {
asl_log(NULL, NULL, ASL_LEVEL_CRIT,
"unable to rename corrupt keychain %s -> %s: %s",
db->db_name, old_db_name, strerror(errno));
free(old_db_name);
break;
} else {
asl_log(NULL, NULL, ASL_LEVEL_ERR,
"renamed corrupt keychain %s -> %s (%d)",
db->db_name, old_db_name, s3e);
}
free(old_db_name);
} else if (s3e) {
asl_log(NULL, NULL, ASL_LEVEL_CRIT,
"failed to open keychain %s: %d", db->db_name, s3e);
break;
}
}
return s3e;
}
static void s3dl_dbt_destructor(void *data)
{
s3dl_db_thread *dbt = (s3dl_db_thread *)data;
int found = 0;
pthread_mutex_lock(&dbt->db->mutex);
s3dl_db_thread **pdbt = &dbt->db->dbt_head;
for (;*pdbt; pdbt = &(*pdbt)->dbt_next)
{
if (*pdbt == dbt)
{
*pdbt = dbt->dbt_next;
found = 1;
break;
}
}
pthread_mutex_unlock(&dbt->db->mutex);
if (found)
s3dl_close_dbt(dbt);
}
static int s3dl_create_db_handle(const char *db_name, db_handle *pdbHandle,
s3dl_db_thread **pdbt, bool autocommit, bool create, bool use_hwaes)
{
void *mem = malloc(sizeof(s3dl_db) + strlen(db_name) + 1);
s3dl_db *db = (s3dl_db *)mem;
db->db_name = ((char *)mem) + sizeof(*db);
strcpy(db->db_name, db_name);
db->use_hwaes = use_hwaes;
s3dl_db_thread *dbt;
int s3e = s3dl_create_dbt(db, &dbt, create);
if (s3e != SQLITE_OK)
{
if (s3e == errSecUpgradePending) {
secerror("Device locked during initial open + upgrade attempt");
dbt = NULL;
} else {
free(mem);
return s3e;
}
} else {
dbt->autocommit = autocommit;
}
db->dbt_head = dbt;
int err = pthread_key_create(&db->key, s3dl_dbt_destructor);
if (!err)
err = pthread_mutex_init(&db->mutex, NULL);
if (!err && dbt)
err = pthread_setspecific(db->key, dbt);
if (err)
{
if (dbt)
s3e = s3dl_close_dbt(dbt);
if (s3e == SQLITE_OK)
s3e = SQLITE_INTERNAL;
free(mem);
} else {
if (pdbt)
*pdbt = dbt;
*pdbHandle = (db_handle)db;
}
return s3e;
}
static int s3dl_close_db_handle(db_handle dbHandle)
{
s3dl_db *db = (s3dl_db *)dbHandle;
int s3e = SQLITE_OK;
s3dl_db_thread *next_dbt = db->dbt_head;
while (next_dbt)
{
s3dl_db_thread *dbt = next_dbt;
next_dbt = next_dbt->dbt_next;
int s3e2 = s3dl_close_dbt(dbt);
if (s3e2 != SQLITE_OK && s3e == SQLITE_OK)
s3e = s3e2;
}
pthread_key_delete(db->key);
free(db);
return s3e;
}
static int s3dl_get_dbt(db_handle dbHandle, s3dl_db_thread **pdbt)
{
if (!dbHandle)
return SQLITE_ERROR;
s3dl_db *db = (s3dl_db *)dbHandle;
int s3e = SQLITE_OK;
s3dl_db_thread *dbt = pthread_getspecific(db->key);
if (!dbt)
{
s3e = s3dl_create_dbt(db, &dbt, false);
if (s3e == SQLITE_OK)
{
int err = pthread_mutex_lock(&db->mutex);
if (!err)
{
dbt->dbt_next = db->dbt_head;
db->dbt_head = dbt;
err = pthread_mutex_unlock(&db->mutex);
}
if (!err)
err = pthread_setspecific(db->key, dbt);
if (err)
{
s3e = s3dl_close_dbt(dbt);
if (s3e == SQLITE_OK)
s3e = SQLITE_INTERNAL;
}
}
}
*pdbt = dbt;
return s3e;
}
static OSStatus osstatus_for_s3e(int s3e)
{
if (s3e > 0 && s3e <= SQLITE_DONE) switch (s3e)
{
case SQLITE_OK:
return 0;
case SQLITE_ERROR:
return errSecNotAvailable;
case SQLITE_FULL:
return errSecNotAvailable;
case SQLITE_PERM:
case SQLITE_READONLY:
return errSecNotAvailable;
case SQLITE_CANTOPEN:
return errSecNotAvailable;
case SQLITE_EMPTY:
return errSecNotAvailable;
case SQLITE_CONSTRAINT:
return errSecDuplicateItem;
case SQLITE_ABORT:
return -1;
case SQLITE_MISMATCH:
return errSecNoSuchAttr;
case SQLITE_AUTH:
return errSecNotAvailable;
case SQLITE_NOMEM:
return -2;
case SQLITE_INTERNAL:
default:
return errSecNotAvailable;
}
return s3e;
}
const uint32_t v0KeyWrapOverHead = 8;
static int ks_crypt(uint32_t selector, keybag_handle_t keybag,
keyclass_t keyclass, uint32_t textLength, const uint8_t *source, uint8_t *dest, size_t *dest_len) {
#if USE_KEYSTORE
kern_return_t kernResult;
if (keystore == MACH_PORT_NULL) {
asl_log(NULL, NULL, ASL_LEVEL_ERR, "No AppleKeyStore connection");
return errSecNotAvailable;
}
uint64_t inputs[] = { keybag, keyclass };
uint32_t num_inputs = sizeof(inputs)/sizeof(*inputs);
kernResult = IOConnectCallMethod(keystore, selector, inputs,
num_inputs, source, textLength, NULL, NULL, dest, dest_len);
if (kernResult != KERN_SUCCESS) {
if (kernResult == kIOReturnNotPermitted) {
asl_log(NULL, NULL, ASL_LEVEL_INFO,
"%s sel: %d bag: %d cls: %d src: %p len: %"PRIu32" err: kIOReturnNotPermitted",
(selector == kAppleKeyStoreKeyWrap ? "kAppleKeyStoreKeyWrap"
: "kAppleKeyStoreKeyUnwrap"),
selector, keybag, keyclass, source, textLength);
return errSecInteractionNotAllowed;
} else if (kernResult == kIOReturnError) {
secerror("%s sel: %d bag: %d cls: %d src: %p len: %lu err: kIOReturnError",
(selector == kAppleKeyStoreKeyWrap ? "kAppleKeyStoreKeyWrap"
: "kAppleKeyStoreKeyUnwrap"),
selector, keybag, keyclass, source, textLength);
return errSecDecode;
} else {
secerror("%s sel: %d bag: %d cls: %d src: %p len: %lu err: %x",
(selector == kAppleKeyStoreKeyWrap ? "kAppleKeyStoreKeyWrap"
: "kAppleKeyStoreKeyUnwrap"),
selector, keybag, keyclass, source, textLength, kernResult);
return errSecNotAvailable;
}
}
return errSecSuccess;
#else
if (selector == kAppleKeyStoreKeyWrap) {
if (*dest_len >= textLength + 8) {
memcpy(dest, source, textLength);
memset(dest + textLength, 8, 8);
*dest_len = textLength + 8;
} else
return errSecNotAvailable;
} else if (selector == kAppleKeyStoreKeyUnwrap) {
if (*dest_len + 8 >= textLength) {
memcpy(dest, source, textLength - 8);
*dest_len = textLength - 8;
} else
return errSecNotAvailable;
}
return errSecSuccess;
#endif
}
#if 0
typedef struct kc_item {
CFMutableDictionaryRef item;
CFIndex n_attrs;
CFStringRef *attrs;
void *values[];
} kc_item;
#define kc_item_size(n) sizeof(kc_item) + 2 * sizeof(void *)
#define kc_item_init(i, n) do { \
kc_item *_kc_item_a = (i); \
_kc_item_a->item = NULL; \
_kc_item_a->n_attrs = (n); \
_kc_item_a->attrs = (CFStringRef *)&_kc_item_a->values[_kc_item_a->n_attrs]; \
} while(0)
static kc_item *kc_item_create(CFIndex n_attrs) {
kc_item *item = malloc(kc_item_size(n_attrs));
kc_item_init(item, n_attrs);
return item;
}
static void kc_item_destroy(kc_item *item) {
CFReleaseSafe(item->item);
free(item);
}
static kc_item *kc_item_init_with_data() {
}
static CFDataRef kc_item_encode(const kc_item *item) {
CFDictionaryRef attrs = CFDictionaryCreate(0, (const void **)item->attrs,
(const void **)item->values,
item->n_attrs, 0, 0);
CFDataRef encoded = CFPropertyListCreateData(0, attrs,
kCFPropertyListBinaryFormat_v1_0, 0, 0);
CFRelease(attrs);
return encoded;
}
struct kc_item_set_attr {
CFIndex ix;
kc_item *item;
OSStatus error;
};
static void kc_item_set_attr(CFStringRef key, void *value,
void *context) {
struct kc_item_set_attr *c = context;
if (CFGetTypeID(key) != CFStringGetTypeID()) {
c->error = 1;
return;
}
c->item->attrs[c->ix] = key;
c->item->values[c->ix++] = value;
}
static CFDictionaryRef kc_item_decode(CFDataRef encoded_item) {
CFPropertyListFormat format;
CFPropertyListRef attrs;
attrs = CFPropertyListCreateWithData(0, encoded_item,
kCFPropertyListImmutable, &format, 0);
if (!attrs)
return NULL;
kc_item *item = NULL;
if (CFGetTypeID(attrs) != CFDictionaryGetTypeID())
goto errOut;
item = kc_item_create(CFDictionaryGetCount(attrs));
int failed = 0;
CFDictionaryApplyFunction(attrs,
(CFDictionaryApplierFunction)kc_item_set_attr,
&failed);
errOut:
#if 0
CFRelease(attrs);
return item;
#else
kc_item_destroy(item);
return attrs;
#endif
}
#endif
static int ks_encrypt_data(keybag_handle_t keybag,
keyclass_t keyclass, CFDataRef plainText, CFDataRef *pBlob) {
CFMutableDataRef blob = NULL;
const uint32_t bulkKeySize = 32;
const uint32_t maxKeyWrapOverHead = 8 + 32;
uint8_t bulkKey[bulkKeySize];
uint8_t bulkKeyWrapped[bulkKeySize + maxKeyWrapOverHead];
size_t bulkKeyWrappedSize = sizeof(bulkKeyWrapped);
uint32_t key_wrapped_size;
int s3e = errSecAllocate;
if (!plainText || CFGetTypeID(plainText) != CFDataGetTypeID()
|| keyclass == 0) {
s3e = errSecParam;
goto out;
}
size_t ptLen = CFDataGetLength(plainText);
size_t ctLen = ptLen;
size_t tagLen = 16;
uint32_t version = 2;
if (SecRandomCopyBytes(kSecRandomDefault, bulkKeySize, bulkKey))
goto out;
require_noerr_quiet(s3e = ks_crypt(kAppleKeyStoreKeyWrap, keybag, keyclass,
bulkKeySize, bulkKey, bulkKeyWrapped,
&bulkKeyWrappedSize), out);
key_wrapped_size = (uint32_t)bulkKeyWrappedSize;
size_t blobLen = sizeof(version) + sizeof(keyclass) +
sizeof(key_wrapped_size) + key_wrapped_size + ctLen + tagLen;
require_quiet(blob = CFDataCreateMutable(NULL, blobLen), out);
CFDataSetLength(blob, blobLen);
UInt8 *cursor = CFDataGetMutableBytePtr(blob);
*((uint32_t *)cursor) = version;
cursor += sizeof(version);
*((keyclass_t *)cursor) = keyclass;
cursor += sizeof(keyclass);
*((uint32_t *)cursor) = key_wrapped_size;
cursor += sizeof(key_wrapped_size);
memcpy(cursor, bulkKeyWrapped, key_wrapped_size);
cursor += key_wrapped_size;
CCCryptorStatus ccerr = CCCryptorGCM(kCCEncrypt, kCCAlgorithmAES128,
bulkKey, bulkKeySize,
NULL, 0,
NULL, 0,
CFDataGetBytePtr(plainText), ptLen,
cursor,
cursor + ctLen, &tagLen);
if (ccerr) {
asl_log(NULL, NULL, ASL_LEVEL_ERR, "CCCryptorGCM failed: %d", ccerr);
s3e = errSecInternal;
goto out;
}
if (tagLen != 16) {
asl_log(NULL, NULL, ASL_LEVEL_ERR,
"CCCryptorGCM expected: 16 got: %ld byte tag", tagLen);
s3e = errSecInternal;
goto out;
}
out:
memset(bulkKey, 0, sizeof(bulkKey));
if (s3e) {
CFReleaseSafe(blob);
} else {
*pBlob = blob;
}
return s3e;
}
static int ks_decrypt_data(keybag_handle_t keybag,
keyclass_t *pkeyclass, CFDataRef blob, CFDataRef *pPlainText,
uint32_t *version_p) {
const uint32_t bulkKeySize = 32;
uint8_t bulkKey[bulkKeySize];
size_t bulkKeyCapacity = sizeof(bulkKey);
CFMutableDataRef plainText = NULL;
check(keybag >= 0);
int s3e = errSecDecode;
if (!blob) {
s3e = errSecParam;
goto out;
}
size_t blobLen = CFDataGetLength(blob);
const uint8_t *cursor = CFDataGetBytePtr(blob);
uint32_t version;
keyclass_t keyclass;
uint32_t wrapped_key_size;
if (blobLen < sizeof(version) + sizeof(keyclass) +
bulkKeySize + v0KeyWrapOverHead + 16)
goto out;
version = *((uint32_t *)cursor);
cursor += sizeof(version);
keyclass = *((keyclass_t *)cursor);
if (pkeyclass)
*pkeyclass = keyclass;
cursor += sizeof(keyclass);
size_t minimum_blob_len = sizeof(version) + sizeof(keyclass) + 16;
size_t ctLen = blobLen - sizeof(version) - sizeof(keyclass);
size_t tagLen = 0;
switch (version) {
case 0:
wrapped_key_size = bulkKeySize + v0KeyWrapOverHead;
break;
case 2:
tagLen = 16;
minimum_blob_len -= 16; ctLen -= tagLen;
case 1:
wrapped_key_size = *((uint32_t *)cursor);
cursor += sizeof(wrapped_key_size);
minimum_blob_len += sizeof(wrapped_key_size);
ctLen -= sizeof(wrapped_key_size);
break;
default:
goto out;
}
require(blobLen - minimum_blob_len - tagLen >= wrapped_key_size, out);
ctLen -= wrapped_key_size;
if (version < 2 && (ctLen & 0xF) != 0)
goto out;
require_noerr_quiet(s3e = ks_crypt(kAppleKeyStoreKeyUnwrap, keybag,
keyclass, wrapped_key_size, cursor, bulkKey, &bulkKeyCapacity), out);
cursor += wrapped_key_size;
plainText = CFDataCreateMutable(NULL, ctLen);
if (!plainText)
goto out;
CFDataSetLength(plainText, ctLen);
CCCryptorStatus ccerr;
if (tagLen) {
uint8_t tag[tagLen];
ccerr = CCCryptorGCM(kCCDecrypt, kCCAlgorithmAES128,
bulkKey, bulkKeySize,
NULL, 0,
NULL, 0,
cursor, ctLen,
CFDataGetMutableBytePtr(plainText),
tag, &tagLen);
if (ccerr) {
secerror("CCCryptorGCM failed: %d", ccerr);
s3e = errSecDecode;
goto out;
}
if (tagLen != 16) {
secerror("CCCryptorGCM expected: 16 got: %ld byte tag", tagLen);
s3e = errSecInternal;
goto out;
}
cursor += ctLen;
if (memcmp(tag, cursor, tagLen)) {
secerror("CCCryptorGCM computed tag not same as tag in blob");
s3e = errSecDecode;
goto out;
}
} else {
size_t ptLen;
ccerr = CCCrypt(kCCDecrypt, kCCAlgorithmAES128, kCCOptionPKCS7Padding,
bulkKey, bulkKeySize, NULL, cursor, ctLen,
CFDataGetMutableBytePtr(plainText), ctLen, &ptLen);
if (ccerr) {
secerror("CCCrypt failed: %d", ccerr);
s3e = errSecDecode;
goto out;
}
CFDataSetLength(plainText, ptLen);
}
s3e = errSecSuccess;
if (version_p) *version_p = version;
out:
memset(bulkKey, 0, bulkKeySize);
if (s3e) {
CFReleaseSafe(plainText);
} else {
*pPlainText = plainText;
}
return s3e;
}
static bool kc_aes_crypt(s3dl_db_thread *dbt, bool dir_encrypt, const UInt8 *iv,
UInt32 textLength, const UInt8 *source, UInt8 *dest) {
#if USE_HWAES
if (dbt->db->use_hwaes) {
IOAESOperation operation;
UInt8 *plainText;
UInt8 *cipherText;
if (dir_encrypt) {
operation = IOAESOperationEncrypt;
plainText = (UInt8 *)source;
cipherText = dest;
} else {
operation = IOAESOperationDecrypt;
plainText = dest;
cipherText = (UInt8 *)source;
}
return hwaes_crypt(operation,
kIOAESAcceleratorKeyHandleKeychain, 128, NULL, iv, textLength,
plainText, cipherText);
}
else
#endif
{
memcpy(dest, source, textLength);
return true;
}
}
#if 0
static int kc_encrypt_data(s3dl_db_thread *dbt, CFDataRef plainText,
CFDataRef *pCipherText) {
CFMutableDataRef cipherText = NULL;
int s3e = SQLITE_AUTH;
if (!plainText || CFGetTypeID(plainText) != CFDataGetTypeID()) {
s3e = SQLITE_MISMATCH;
goto out;
}
CFIndex ptLen = CFDataGetLength(plainText);
CFIndex ctLen = ptLen + CC_SHA1_DIGEST_LENGTH;
CFIndex padLen = 16 - (ctLen & 15);
CFIndex paddedTotalLength = (16 + ctLen + padLen + 0x1F) & ~0x1F;
cipherText = CFDataCreateMutable(NULL, paddedTotalLength);
if (!cipherText)
goto out;
CFDataSetLength(cipherText, 16 + ctLen + padLen);
UInt8 *iv = CFDataGetMutableBytePtr(cipherText);
if (SecRandomCopyBytes(kSecRandomDefault, 16, iv))
goto out;
UInt8 *ct = iv + 16;
const UInt8 *pt = CFDataGetBytePtr(plainText);
memcpy(ct, pt, ptLen);
CC_SHA1(pt, ptLen, ct + ptLen);
memset(ct + ctLen, padLen, padLen);
if (!kc_aes_crypt(dbt, true, iv, ctLen + padLen, ct, ct)) {
goto out;
}
s3e = SQLITE_OK;
out:
if (s3e) {
CFReleaseSafe(cipherText);
} else {
*pCipherText = cipherText;
}
return s3e;
}
#endif
static int kc_decrypt_data(s3dl_db_thread *dbt, CFDataRef cipherText,
CFDataRef *pPlainText) {
CFMutableDataRef plainText = NULL;
int s3e = SQLITE_AUTH;
if (!cipherText)
goto out;
CFIndex ctLen = CFDataGetLength(cipherText);
if (ctLen < 48 || (ctLen & 0xF) != 0)
goto out;
const UInt8 *iv = CFDataGetBytePtr(cipherText);
const UInt8 *ct = iv + 16;
CFIndex ptLen = ctLen - 16;
assert((unsigned long)ptLen<UINT32_MAX);
CFIndex paddedLength = (ptLen + 0x1F) & ~0x1F;
plainText = CFDataCreateMutable(NULL, paddedLength);
if (!plainText)
goto out;
CFDataSetLength(plainText, ptLen);
UInt8 *pt = CFDataGetMutableBytePtr(plainText);
if (!kc_aes_crypt(dbt, false, iv, (uint32_t)ptLen, ct, pt)) {
goto out;
}
UInt8 pad = pt[ptLen - 1];
if (pad < 1 || pad > 16) {
asl_log(NULL, NULL, ASL_LEVEL_ERR,
"kc_decrypt_data: bad padding bytecount: 0x%02X", pad);
goto out;
}
CFIndex ix;
ptLen -= pad;
for (ix = 0; ix < pad - 1; ++ix) {
if (pt[ptLen + ix] != pad) {
asl_log(NULL, NULL, ASL_LEVEL_ERR,
"kc_decrypt_data: bad padding byte: %lu: 0x%02X",
ix, pt[ptLen + ix]);
goto out;
}
}
UInt8 sha1[CC_SHA1_DIGEST_LENGTH];
ptLen -= CC_SHA1_DIGEST_LENGTH;
CC_SHA1(pt, (CC_LONG)ptLen, sha1);
if (memcmp(sha1, pt + ptLen, CC_SHA1_DIGEST_LENGTH)) {
asl_log(NULL, NULL, ASL_LEVEL_ERR, "kc_decrypt_data: digest mismatch");
goto out;
}
CFDataSetLength(plainText, ptLen);
s3e = SQLITE_OK;
out:
if (s3e) {
CFReleaseSafe(plainText);
} else {
*pPlainText = plainText;
}
return s3e;
}
static int kc_bind_paramter(sqlite3_stmt *stmt, int param, CFTypeRef value)
{
CFTypeID valueId;
int s3e;
if (!value || (valueId = CFGetTypeID(value)) == CFNullGetTypeID()) {
#if 1
s3e = SQLITE_OK;
#else
s3e = sqlite3_bind_null(stmt, param);
#endif
secdebug("bind", "bind_null: %d", s3e);
} else if (valueId == CFStringGetTypeID()) {
const char *cstr = CFStringGetCStringPtr(value, kCFStringEncodingUTF8);
if (cstr) {
s3e = sqlite3_bind_text_wrapper(stmt, param, cstr, strlen(cstr),
SQLITE_TRANSIENT);
secdebug("bind", "quick bind_text: %s: %d", cstr, s3e);
} else {
CFIndex len = 0;
CFRange range = { 0, CFStringGetLength(value) };
CFStringGetBytes(value, range, kCFStringEncodingUTF8,
0, FALSE, NULL, 0, &len);
{
CFIndex usedlen = 0;
char buf[len];
CFStringGetBytes(value, range, kCFStringEncodingUTF8,
0, FALSE, (UInt8 *)buf, len, &usedlen);
s3e = sqlite3_bind_text_wrapper(stmt, param, buf, usedlen,
SQLITE_TRANSIENT);
secdebug("bind", "slow bind_text: %.*s: %d", usedlen, buf, s3e);
}
}
} else if (valueId == CFDataGetTypeID()) {
CFIndex len = CFDataGetLength(value);
if (len) {
s3e = sqlite3_bind_blob_wrapper(stmt, param, CFDataGetBytePtr(value),
len, SQLITE_TRANSIENT);
secdebug("bind", "bind_blob: %.*s: %d",
CFDataGetLength(value), CFDataGetBytePtr(value), s3e);
} else {
s3e = sqlite3_bind_text(stmt, param, "", 0, SQLITE_TRANSIENT);
}
} else if (valueId == CFDateGetTypeID()) {
CFAbsoluteTime abs_time = CFDateGetAbsoluteTime(value);
s3e = sqlite3_bind_double(stmt, param, abs_time);
secdebug("bind", "bind_double: %f: %d", abs_time, s3e);
} else if (valueId == CFBooleanGetTypeID()) {
int bval = CFBooleanGetValue(value);
s3e = sqlite3_bind_int(stmt, param, bval);
secdebug("bind", "bind_int: %d: %d", bval, s3e);
} else if (valueId == CFNumberGetTypeID()) {
Boolean convertOk;
if (CFNumberIsFloatType(value)) {
double nval;
convertOk = CFNumberGetValue(value, kCFNumberDoubleType, &nval);
s3e = sqlite3_bind_double(stmt, param, nval);
secdebug("bind", "bind_double: %f: %d", nval, s3e);
} else {
CFIndex len = CFNumberGetByteSize(value);
if (len <= (CFIndex)sizeof(int)) {
int nval;
convertOk = CFNumberGetValue(value, kCFNumberIntType, &nval);
s3e = sqlite3_bind_int(stmt, param, nval);
secdebug("bind", "bind_int: %d: %d", nval, s3e);
} else {
sqlite_int64 nval;
convertOk = CFNumberGetValue(value, kCFNumberLongLongType,
&nval);
s3e = sqlite3_bind_int64(stmt, param, nval);
secdebug("bind", "bind_int64: %lld: %d", nval, s3e);
}
}
if (!convertOk) {
s3e = SQLITE_INTERNAL;
}
} else {
s3e = SQLITE_MISMATCH;
}
return s3e;
}
static int kc_prepare_statement(sqlite3 *s3h, CFStringRef sql,
sqlite3_stmt **stmt)
{
int s3e;
const char *cstr = CFStringGetCStringPtr(sql, kCFStringEncodingUTF8);
if (cstr) {
secdebug("sql", "quick prepare: %s", cstr);
s3e = sqlite3_prepare_wrapper(s3h, cstr, strlen(cstr), stmt, NULL);
} else {
CFIndex len = 0;
CFRange range = { 0, CFStringGetLength(sql) };
CFStringGetBytes(sql, range, kCFStringEncodingUTF8,
0, FALSE, NULL, 0, &len);
{
CFIndex usedlen = 0;
char buf[len];
CFStringGetBytes(sql, range, kCFStringEncodingUTF8,
0, FALSE, (UInt8 *)buf, len, &usedlen);
secdebug("sql", "slow prepare: %.*s", usedlen, buf);
s3e = sqlite3_prepare_wrapper(s3h, buf, usedlen, stmt, NULL);
}
}
if (s3e == SQLITE_ERROR) {
secdebug("sql", "sqlite3_prepare: %s", sqlite3_errmsg(s3h));
s3e = errSecParam;
}
return s3e;
}
typedef uint32_t ReturnTypeMask;
enum
{
kSecReturnDataMask = 1 << 0,
kSecReturnAttributesMask = 1 << 1,
kSecReturnRefMask = 1 << 2,
kSecReturnPersistentRefMask = 1 << 3,
};
enum
{
kSecMatchUnlimited = kCFNotFound
};
#ifdef NO_SERVER
#define QUERY_KEY_LIMIT (31 + 53)
#else
#define QUERY_KEY_LIMIT (53)
#endif
typedef struct Pair
{
const void *key;
const void *value;
} Pair;
typedef struct Query
{
const kc_class *q_class;
CFMutableDictionaryRef q_item;
CFIndex q_match_begin;
CFIndex q_match_end;
CFIndex q_attr_end;
OSStatus q_error;
ReturnTypeMask q_return_type;
CFDataRef q_data;
CFTypeRef q_ref;
sqlite_int64 q_row_id;
CFArrayRef q_use_item_list;
#if defined(MULTIPLE_KEYCHAINS)
CFArrayRef q_use_keychain;
CFArrayRef q_use_keychain_list;
#endif
CFIndex q_limit;
keybag_handle_t q_keybag;
keyclass_t q_keyclass;
Pair q_pairs[];
} Query;
static inline CFIndex query_attr_count(const Query *q)
{
return q->q_attr_end;
}
static inline Pair query_attr_at(const Query *q, CFIndex ix)
{
return q->q_pairs[ix];
}
static inline CFIndex query_match_count(const Query *q)
{
return q->q_match_end - q->q_match_begin;
}
static inline Pair query_match_at(const Query *q, CFIndex ix)
{
return q->q_pairs[q->q_match_begin + ix];
}
static void query_parse_keyclass(const void *value, Query *q) {
if (!isString(value)) {
q->q_error = errSecParam;
return;
} else if (CFEqual(value, kSecAttrAccessibleWhenUnlocked)) {
q->q_keyclass = key_class_ak;
} else if (CFEqual(value, kSecAttrAccessibleAfterFirstUnlock)) {
q->q_keyclass = key_class_ck;
} else if (CFEqual(value, kSecAttrAccessibleAlways)) {
q->q_keyclass = key_class_dk;
} else if (CFEqual(value, kSecAttrAccessibleWhenUnlockedThisDeviceOnly)) {
q->q_keyclass = key_class_aku;
} else if (CFEqual(value, kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly)) {
q->q_keyclass = key_class_cku;
} else if (CFEqual(value, kSecAttrAccessibleAlwaysThisDeviceOnly)) {
q->q_keyclass = key_class_dku;
} else {
q->q_error = errSecParam;
return;
}
}
static CFStringRef copyString(CFTypeRef obj) {
CFTypeID tid = CFGetTypeID(obj);
if (tid == CFStringGetTypeID())
return CFStringCreateCopy(0, obj);
else if (tid == CFDataGetTypeID())
return CFStringCreateFromExternalRepresentation(0, obj, kCFStringEncodingUTF8);
else
return NULL;
}
static CFDataRef copyData(CFTypeRef obj) {
CFTypeID tid = CFGetTypeID(obj);
if (tid == CFDataGetTypeID()) {
return CFDataCreateCopy(0, obj);
} else if (tid == CFStringGetTypeID()) {
return CFStringCreateExternalRepresentation(0, obj, kCFStringEncodingUTF8, 0);
} else if (tid == CFNumberGetTypeID()) {
SInt32 value;
CFNumberGetValue(obj, kCFNumberSInt32Type, &value);
return CFDataCreate(0, (const UInt8 *)&value, sizeof(value));
} else {
return NULL;
}
}
static CFTypeRef copyBlob(CFTypeRef obj) {
CFTypeID tid = CFGetTypeID(obj);
if (tid == CFDataGetTypeID()) {
return CFDataCreateCopy(0, obj);
} else if (tid == CFStringGetTypeID()) {
return CFStringCreateCopy(0, obj);
} else if (tid == CFNumberGetTypeID()) {
CFRetain(obj);
return obj;
} else {
return NULL;
}
}
static CFTypeRef copyNumber(CFTypeRef obj) {
CFTypeID tid = CFGetTypeID(obj);
if (tid == CFNumberGetTypeID()) {
CFRetain(obj);
return obj;
} else if (tid == CFBooleanGetTypeID()) {
SInt32 value = CFBooleanGetValue(obj);
return CFNumberCreate(0, kCFNumberSInt32Type, &value);
} else if (tid == CFStringGetTypeID()) {
SInt32 value = CFStringGetIntValue(obj);
CFStringRef t = CFStringCreateWithFormat(0, 0, CFSTR("%ld"), value);
if (!CFEqual(t, obj)) {
CFRelease(t);
return CFStringCreateCopy(0, obj);
}
CFRelease(t);
return CFNumberCreate(0, kCFNumberSInt32Type, &value);
} else
return NULL;
}
static CFDateRef copyDate(CFTypeRef obj) {
CFTypeID tid = CFGetTypeID(obj);
if (tid == CFDateGetTypeID()) {
CFRetain(obj);
return obj;
} else
return NULL;
}
static void query_add_attribute(const void *key, const void *value, Query *q)
{
const kc_attr_desc *desc;
if (!(desc = kc_attr_desc_with_key(q->q_class, key, &q->q_error)))
return;
CFTypeRef attr = NULL;
switch (desc->kind) {
case kc_data_attr:
attr = copyData(value);
break;
case kc_blob_attr:
attr = copyBlob(value);
break;
case kc_date_attr:
case kc_creation_date_attr:
case kc_modification_date_attr:
attr = copyDate(value);
break;
case kc_number_attr:
attr = copyNumber(value);
break;
case kc_string_attr:
attr = copyString(value);
break;
}
if (!attr) {
q->q_error = errSecItemInvalidValue;
return;
}
if (q->q_item) {
CFDictionarySetValue(q->q_item, desc->name, attr);
}
if (CFEqual(desc->name, kSecAttrAccessible)) {
query_parse_keyclass(attr, q);
}
if (desc->flags & kc_digest_attr) {
CFDataRef data = copyData(attr);
CFRelease(attr);
if (!data) {
q->q_error = errSecInternal;
return;
}
CFMutableDataRef digest = CFDataCreateMutable(0, CC_SHA1_DIGEST_LENGTH);
CFDataSetLength(digest, CC_SHA1_DIGEST_LENGTH);
assert((unsigned long)CFDataGetLength(data)<UINT32_MAX);
CC_SHA1(CFDataGetBytePtr(data), (CC_LONG)CFDataGetLength(data),
CFDataGetMutableBytePtr(digest));
CFRelease(data);
attr = digest;
}
q->q_pairs[q->q_attr_end].key = desc->name;
q->q_pairs[q->q_attr_end++].value = attr;
}
static void query_set_attribute(const void *key, const void *value, Query *q) {
if (CFDictionaryContainsKey(q->q_item, key)) {
CFIndex ix;
for (ix = 0; ix < q->q_attr_end; ++ix) {
if (CFEqual(key, q->q_pairs[ix].key)) {
CFReleaseSafe(q->q_pairs[ix].value);
--q->q_attr_end;
for (; ix < q->q_attr_end; ++ix) {
q->q_pairs[ix] = q->q_pairs[ix + 1];
}
CFDictionaryRemoveValue(q->q_item, key);
break;
}
}
}
query_add_attribute(key, value, q);
}
static void query_add_match(const void *key, const void *value, Query *q)
{
--(q->q_match_begin);
q->q_pairs[q->q_match_begin].key = key;
q->q_pairs[q->q_match_begin].value = value;
if (CFEqual(kSecMatchLimit, key)) {
if (CFGetTypeID(value) == CFNumberGetTypeID()) {
if (!CFNumberGetValue(value, kCFNumberCFIndexType, &q->q_limit))
q->q_error = errSecItemInvalidValue;
} else if (CFEqual(kSecMatchLimitAll, value)) {
q->q_limit = kSecMatchUnlimited;
} else if (CFEqual(kSecMatchLimitOne, value)) {
q->q_limit = 1;
} else {
q->q_error = errSecItemInvalidValue;
}
}
}
static bool query_set_class(Query *q, CFStringRef c_name, OSStatus *error) {
const void *value;
if (c_name && CFGetTypeID(c_name) == CFStringGetTypeID() &&
(value = CFDictionaryGetValue(gClasses, c_name)) &&
(q->q_class == 0 || q->q_class == value)) {
q->q_class = value;
return true;
}
if (error && !*error)
*error = c_name ? errSecNoSuchClass : errSecItemClassMissing;
return false;
}
static const kc_class *query_get_class(CFDictionaryRef query, OSStatus *error) {
CFStringRef c_name = NULL;
const void *value = CFDictionaryGetValue(query, kSecClass);
if (isString(value)) {
c_name = value;
} else {
value = CFDictionaryGetValue(query, kSecValuePersistentRef);
if (isData(value)) {
CFDataRef pref = value;
_SecItemParsePersistentRef(pref, &c_name, 0);
}
}
if (c_name && (value = CFDictionaryGetValue(gClasses, c_name))) {
return value;
} else {
if (error && !*error)
*error = c_name ? errSecNoSuchClass : errSecItemClassMissing;
return false;
}
}
static void query_add_class(const void *key, const void *value, Query *q)
{
if (CFEqual(key, kSecClass)) {
query_set_class(q, value, &q->q_error);
} else {
q->q_error = errSecItemInvalidKey;
}
}
static void query_add_return(const void *key, const void *value, Query *q)
{
ReturnTypeMask mask;
if (CFGetTypeID(value) != CFBooleanGetTypeID()) {
q->q_error = errSecItemInvalidValue;
return;
}
int set_it = CFEqual(value, kCFBooleanTrue);
if (CFEqual(key, kSecReturnData))
mask = kSecReturnDataMask;
else if (CFEqual(key, kSecReturnAttributes))
mask = kSecReturnAttributesMask;
else if (CFEqual(key, kSecReturnRef))
mask = kSecReturnRefMask;
else if (CFEqual(key, kSecReturnPersistentRef))
mask = kSecReturnPersistentRefMask;
else {
q->q_error = errSecItemInvalidKey;
return;
}
if ((q->q_return_type & mask) && !set_it) {
q->q_return_type ^= mask;
} else if (!(q->q_return_type & mask) && set_it) {
q->q_return_type |= mask;
}
}
static void query_add_use(const void *key, const void *value, Query *q)
{
if (CFEqual(key, kSecUseItemList)) {
q->q_use_item_list = value;
}
#if defined(MULTIPLE_KEYCHAINS)
else if (CFEqual(key, kSecUseKeychain))
q->q_use_keychain = value;
else if (CFEqual(key, kSecUseKeychainList))
q->q_use_keychain_list = value;
#endif
else {
q->q_error = errSecItemInvalidKey;
return;
}
}
static void query_set_data(const void *value, Query *q) {
if (!isData(value)) {
q->q_error = errSecItemInvalidValue;
} else {
q->q_data = value;
if (q->q_item)
CFDictionarySetValue(q->q_item, kSecValueData, value);
}
}
static void query_add_value(const void *key, const void *value, Query *q)
{
if (CFEqual(key, kSecValueData)) {
query_set_data(value, q);
#if NO_SERVER
} else if (CFEqual(key, kSecValueRef)) {
q->q_ref = value;
#endif
} else if (CFEqual(key, kSecValuePersistentRef)) {
CFStringRef c_name;
if (_SecItemParsePersistentRef(value, &c_name, &q->q_row_id))
query_set_class(q, c_name, &q->q_error);
else
q->q_error = errSecItemInvalidValue;
} else {
q->q_error = errSecItemInvalidKey;
return;
}
}
static void query_update_applier(const void *key, const void *value,
void *context)
{
Query *q = (Query *)context;
if (q->q_error)
return;
if (!isString(key)) {
q->q_error = errSecItemInvalidKeyType;
return;
}
if (!value) {
q->q_error = errSecItemInvalidValue;
return;
}
if (CFEqual(key, kSecValueData)) {
query_set_data(value, q);
} else {
if (!value) {
q->q_error = errSecItemInvalidValue;
return;
}
query_add_attribute(key, value, q);
}
}
static void query_applier(const void *key, const void *value, void *context)
{
Query *q = (Query *)context;
if (q->q_error)
return;
if (!key) {
q->q_error = errSecItemInvalidKeyType;
return;
}
if (!value) {
q->q_error = errSecItemInvalidValue;
return;
}
CFTypeID key_id = CFGetTypeID(key);
if (key_id == CFStringGetTypeID()) {
CFIndex key_len = CFStringGetLength(key);
if (key_len == 4) {
query_add_attribute(key, value, q);
} else if (key_len > 1) {
UniChar k_first_char = CFStringGetCharacterAtIndex(key, 0);
switch (k_first_char)
{
case 'c':
query_add_class(key, value, q);
break;
case 'm':
query_add_match(key, value, q);
break;
case 'r':
query_add_return(key, value, q);
break;
case 'u':
query_add_use(key, value, q);
break;
case 'v':
query_add_value(key, value, q);
break;
default:
q->q_error = errSecItemInvalidKey;
break;
}
} else {
q->q_error = errSecItemInvalidKey;
}
} else if (key_id == CFNumberGetTypeID()) {
query_add_attribute(key, value, q);
} else {
q->q_error = errSecItemInvalidKeyType;
}
}
static CFStringRef query_infer_keyclass(Query *q, CFStringRef agrp) {
if (CFEqual(agrp, CFSTR("com.apple.apsd"))
|| CFEqual(agrp, CFSTR("lockdown-identities"))) {
return kSecAttrAccessibleAlwaysThisDeviceOnly;
}
if (q->q_class == &cert_class) {
return kSecAttrAccessibleAlways;
}
return kSecAttrAccessibleWhenUnlocked;
}
static void query_ensure_keyclass(Query *q, CFStringRef agrp) {
if (q->q_keyclass == 0) {
CFStringRef accessible = query_infer_keyclass(q, agrp);
query_add_attribute(kSecAttrAccessible, accessible, q);
}
}
static void query_destroy(Query *q, OSStatus *status)
{
if (status && *status == 0 && q->q_error)
*status = q->q_error;
CFIndex ix, attr_count = query_attr_count(q);
for (ix = 0; ix < attr_count; ++ix) {
CFReleaseSafe(query_attr_at(q, ix).value);
}
CFReleaseSafe(q->q_item);
free(q);
}
static Query *query_create(const kc_class *qclass, CFDictionaryRef query,
OSStatus *error)
{
if (!qclass) {
if (error && !*error)
*error = errSecItemClassMissing;
return NULL;
}
CFIndex key_count = qclass->n_attrs;
if (query) {
key_count += CFDictionaryGetCount(query);
CFIndex ix;
for (ix = 0; ix < qclass->n_attrs; ++ix) {
if (CFDictionaryContainsKey(query, qclass->attrs[ix].name))
--key_count;
}
}
if (key_count > QUERY_KEY_LIMIT) {
if (error && !*error)
*error = errSecItemIllegalQuery;
return NULL;
}
Query *q = calloc(1, sizeof(Query) + sizeof(Pair) * key_count);
if (q == NULL) {
if (error && !*error)
*error = errSecAllocate;
return NULL;
}
q->q_class = qclass;
q->q_match_begin = q->q_match_end = key_count;
return q;
}
static bool query_parse_with_applier(Query *q, CFDictionaryRef query,
CFDictionaryApplierFunction applier,
OSStatus *error) {
if (q->q_item == NULL) {
q->q_item = CFDictionaryCreateMutable(0, 0,
&kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
}
CFDictionaryApplyFunction(query, applier, q);
if (q->q_error) {
if (error && !*error)
*error = q->q_error;
return false;
}
return true;
}
static bool query_parse(Query *q, CFDictionaryRef query,
OSStatus *error) {
return query_parse_with_applier(q, query, query_applier, error);
}
static bool query_update_parse(Query *q, CFDictionaryRef update,
OSStatus *error) {
return query_parse_with_applier(q, update, query_update_applier, error);
}
static Query *query_create_with_limit(CFDictionaryRef query, CFIndex limit,
OSStatus *error) {
Query *q;
q = query_create(query_get_class(query, error), query, error);
if (q) {
q->q_limit = limit;
if (!query_parse(q, query, error)) {
query_destroy(q, error);
return NULL;
}
}
return q;
}
static void
query_pre_add(Query *q, bool force_date) {
CFDateRef now = CFDateCreate(0, CFAbsoluteTimeGetCurrent());
CFIndex ix;
for (ix = 0; ix < q->q_class->n_attrs; ++ix) {
const kc_attr_desc *desc = &q->q_class->attrs[ix];
if (desc->kind == kc_creation_date_attr ||
desc->kind == kc_modification_date_attr) {
if (force_date) {
query_set_attribute(desc->name, now, q);
} else if (!CFDictionaryContainsKey(q->q_item, desc->name)) {
query_add_attribute(desc->name, now, q);
}
} else if ((desc->flags & kc_constrain_not_null) &&
!CFDictionaryContainsKey(q->q_item, desc->name)) {
CFTypeRef value = NULL;
if (desc->flags & kc_constrain_default_0) {
if (desc->kind == kc_date_attr)
value = CFDateCreate(kCFAllocatorDefault, 0.0);
else {
SInt32 vzero = 0;
value = CFNumberCreate(0, kCFNumberSInt32Type, &vzero);
}
} else if (desc->flags & kc_constrain_default_empty) {
if (desc->kind == kc_data_attr)
value = CFDataCreate(kCFAllocatorDefault, NULL, 0);
else {
value = CFSTR("");
CFRetain(value);
}
}
if (value) {
query_add_attribute(desc->name, value, q);
CFRelease(value);
}
}
}
CFReleaseSafe(now);
}
static void
query_pre_update(Query *q) {
CFIndex ix;
for (ix = 0; ix < q->q_class->n_attrs; ++ix) {
const kc_attr_desc *desc = &q->q_class->attrs[ix];
if (desc->kind == kc_modification_date_attr) {
CFDateRef now = CFDateCreate(0, CFAbsoluteTimeGetCurrent());
query_set_attribute(desc->name, now, q);
CFReleaseSafe(now);
}
}
}
static bool accessGroupsAllows(CFArrayRef accessGroups,
CFStringRef accessGroup) {
if (!accessGroups)
return true;
if (!isString(accessGroup))
return false;
CFRange range = { 0, CFArrayGetCount(accessGroups) };
if (range.length &&
(CFArrayContainsValue(accessGroups, range, accessGroup) ||
CFArrayContainsValue(accessGroups, range, CFSTR("*"))))
return true;
return false;
}
static bool itemInAccessGroup(CFDictionaryRef item, CFArrayRef accessGroups) {
return accessGroupsAllows(accessGroups,
CFDictionaryGetValue(item, kSecAttrAccessGroup));
}
static void s3dl_merge_into_dict(const void *key, const void *value, void *context) {
CFDictionarySetValue(context, key, value);
}
static CFTypeRef handle_result(Query *q, CFMutableDictionaryRef item,
sqlite_int64 rowid) {
CFTypeRef a_result;
CFDataRef data;
data = CFDictionaryGetValue(item, kSecValueData);
if (q->q_return_type == 0) {
a_result = kCFNull;
} else if (q->q_return_type == kSecReturnDataMask) {
if (data) {
a_result = data;
CFRetain(a_result);
} else {
a_result = CFDataCreate(kCFAllocatorDefault, NULL, 0);
}
} else if (q->q_return_type == kSecReturnPersistentRefMask) {
a_result = _SecItemMakePersistentRef(q->q_class->name, rowid);
} else {
if (q->q_return_type & kSecReturnRefMask) {
CFDictionarySetValue(item, kSecClass, q->q_class->name);
} else if ((q->q_return_type & kSecReturnAttributesMask)) {
if (!(q->q_return_type & kSecReturnDataMask)) {
CFDictionaryRemoveValue(item, kSecValueData);
}
} else {
if (data)
CFRetain(data);
CFDictionaryRemoveAllValues(item);
if ((q->q_return_type & kSecReturnDataMask) && data) {
CFDictionarySetValue(item, kSecValueData, data);
CFRelease(data);
}
}
if (q->q_return_type & kSecReturnPersistentRefMask) {
CFDataRef pref = _SecItemMakePersistentRef(q->q_class->name, rowid);
CFDictionarySetValue(item, kSecValuePersistentRef, pref);
CFRelease(pref);
}
a_result = item;
CFRetain(item);
}
return a_result;
}
static CFStringRef s3dl_insert_sql(Query *q) {
CFMutableStringRef sql = CFStringCreateMutable(NULL, 0);
CFStringAppendFormat(sql, NULL, CFSTR("INSERT INTO %@(data"), q->q_class->name);
CFIndex ix, attr_count = query_attr_count(q);
for (ix = 0; ix < attr_count; ++ix) {
CFStringAppendFormat(sql, NULL, CFSTR(",%@"),
query_attr_at(q, ix).key);
}
if (q->q_row_id) {
CFStringAppendFormat(sql, NULL, CFSTR(",rowid"));
}
CFStringAppend(sql, CFSTR(")VALUES(?"));
for (ix = 0; ix < attr_count; ++ix) {
CFStringAppend(sql, CFSTR(",?"));
}
if (q->q_row_id) {
CFStringAppendFormat(sql, NULL, CFSTR(",%qd"), q->q_row_id);
}
CFStringAppend(sql, CFSTR(");"));
return sql;
}
static CFDataRef s3dl_encode_item(keybag_handle_t keybag, keyclass_t keyclass,
CFDictionaryRef item, OSStatus *error) {
CFDataRef plain = CFPropertyListCreateData(0, item,
kCFPropertyListBinaryFormat_v1_0, 0, 0);
CFDataRef edata = NULL;
if (plain) {
int s3e = ks_encrypt_data(keybag, keyclass, plain, &edata);
if (s3e) {
asl_log(NULL, NULL, ASL_LEVEL_CRIT,
"ks_encrypt_data: failed: %d", s3e);
if (error && !*error)
*error = s3e;
}
CFRelease(plain);
} else {
if (error && !*error)
*error = errSecAllocate;
}
return edata;
}
static int s3dl_insert_bind(Query *q, sqlite3_stmt *stmt) {
int s3e = SQLITE_OK;
int param = 1;
OSStatus error = 0;
CFDataRef edata = s3dl_encode_item(q->q_keybag, q->q_keyclass, q->q_item, &error);
if (edata) {
s3e = sqlite3_bind_blob_wrapper(stmt, param, CFDataGetBytePtr(edata),
CFDataGetLength(edata), SQLITE_TRANSIENT);
secdebug("bind", "bind_blob: %.*s: %d",
CFDataGetLength(edata), CFDataGetBytePtr(edata), s3e);
CFRelease(edata);
} else {
s3e = error;
}
param++;
CFIndex ix, attr_count = query_attr_count(q);
for (ix = 0; s3e == SQLITE_OK && ix < attr_count; ++ix) {
s3e = kc_bind_paramter(stmt, param++, query_attr_at(q, ix).value);
}
return s3e;
}
static OSStatus
s3dl_query_add(s3dl_db_thread *dbt, Query *q, CFTypeRef *result)
{
int s3e;
if (query_match_count(q) != 0)
return errSecItemMatchUnsupported;
if (q->q_use_item_list)
return errSecUseItemListUnsupported;
sqlite3 *s3h = dbt->s3_handle;
CFStringRef sql = s3dl_insert_sql(q);
sqlite3_stmt *stmt = NULL;
s3e = kc_prepare_statement(s3h, sql, &stmt);
CFRelease(sql);
if (s3e == SQLITE_OK)
s3e = s3dl_insert_bind(q, stmt);
if (s3e == SQLITE_OK) {
s3e = sqlite3_step(stmt);
if (s3e == SQLITE_DONE) {
s3e = SQLITE_OK;
if (q->q_return_type) {
*result = handle_result(q, q->q_item, sqlite3_last_insert_rowid(s3h));
}
} else if (s3e == SQLITE_ERROR) {
secdebug("sql", "insert: %s", sqlite3_errmsg(s3h));
s3e = errSecDuplicateItem;
}
}
if (stmt) {
int s3e2 = sqlite3_finalize(stmt);
if (s3e2 != SQLITE_OK && s3e == SQLITE_OK)
s3e = s3e2;
}
return s3e == SQLITE_OK ? 0 : osstatus_for_s3e(s3e);
}
typedef void (*s3dl_handle_row)(sqlite3_stmt *stmt, void *context);
static CFMutableDictionaryRef
s3dl_item_from_col(sqlite3_stmt *stmt, Query *q, int col,
CFArrayRef accessGroups, keyclass_t *keyclass) {
CFMutableDictionaryRef item = NULL;
CFErrorRef error = NULL;
CFDataRef edata = NULL;
CFDataRef plain = NULL;
require_action(edata = CFDataCreateWithBytesNoCopy(0, sqlite3_column_blob(stmt, col),
sqlite3_column_bytes(stmt, col),
kCFAllocatorNull),
out, q->q_error = errSecDecode);
uint32_t version;
require_noerr((q->q_error = ks_decrypt_data(q->q_keybag, keyclass, edata, &plain, &version)), out);
if (version < 2) {
goto out;
}
CFPropertyListFormat format;
item = (CFMutableDictionaryRef)CFPropertyListCreateWithData(0, plain,
kCFPropertyListMutableContainers, &format, &error);
if (!item) {
secerror("decode failed: %@ [item: %@]", error, plain);
q->q_error = (OSStatus)CFErrorGetCode(error);
CFRelease(error);
} else if (!isDictionary(item)) {
CFRelease(item);
item = NULL;
} else if (!itemInAccessGroup(item, accessGroups)) {
secerror("items accessGroup %@ not in %@",
CFDictionaryGetValue(item, kSecAttrAccessGroup),
accessGroups);
CFRelease(item);
item = NULL;
}
out:
CFReleaseSafe(edata);
CFReleaseSafe(plain);
return item;
}
struct s3dl_query_ctx {
Query *q;
CFArrayRef accessGroups;
CFTypeRef result;
int found;
};
static void s3dl_query_row(sqlite3_stmt *stmt, void *context) {
struct s3dl_query_ctx *c = context;
Query *q = c->q;
CFMutableDictionaryRef item = s3dl_item_from_col(stmt, q, 1,
c->accessGroups, NULL);
if (!item)
return;
if (q->q_class == &identity_class) {
CFMutableDictionaryRef key = s3dl_item_from_col(stmt, q, 3,
c->accessGroups, NULL);
if (!key)
goto out;
CFDataRef certData = CFDictionaryGetValue(item, kSecValueData);
if (certData) {
CFDictionarySetValue(key, CFSTR(CERTIFICATE_DATA_COLUMN_LABEL),
certData);
CFDictionaryRemoveValue(item, kSecValueData);
}
CFDictionaryApplyFunction(item, s3dl_merge_into_dict, key);
CFRelease(item);
item = key;
}
sqlite_int64 rowid = sqlite3_column_int64(stmt, 0);
CFTypeRef a_result = handle_result(q, item, rowid);
if (a_result) {
if (a_result == kCFNull) {
} else if (q->q_limit == 1) {
c->result = a_result;
} else {
CFArrayAppendValue((CFMutableArrayRef)c->result, a_result);
CFRelease(a_result);
}
c->found++;
}
out:
CFRelease(item);
}
struct s3dl_update_row_ctx {
struct s3dl_query_ctx qc;
Query *u;
sqlite3_stmt *update_stmt;
};
static void s3dl_update_row(sqlite3_stmt *stmt, void *context)
{
struct s3dl_update_row_ctx *c = context;
Query *q = c->qc.q;
int s3e = SQLITE_OK;
CFDataRef edata = NULL;
keyclass_t keyclass;
sqlite_int64 rowid = sqlite3_column_int64(stmt, 0);
CFMutableDictionaryRef item;
require(item = s3dl_item_from_col(stmt, q, 1, c->qc.accessGroups,
&keyclass), out);
Query *u = c->u;
CFDictionaryApplyFunction(u->q_item, s3dl_merge_into_dict, item);
if (u->q_keyclass) {
keyclass = u->q_keyclass;
}
require(edata = s3dl_encode_item(q->q_keybag, keyclass, item, &q->q_error),
out);
CFIndex count = 1 + query_attr_count(u);
assert(count < INT_MAX);
int param = (int)count;
s3e = sqlite3_bind_blob_wrapper(c->update_stmt, param++,
CFDataGetBytePtr(edata), CFDataGetLength(edata),
SQLITE_TRANSIENT);
if (s3e == SQLITE_OK)
s3e = sqlite3_bind_int64(c->update_stmt, param++, rowid);
if (s3e == SQLITE_OK) {
s3e = sqlite3_step(c->update_stmt);
if (s3e == SQLITE_DONE) {
s3e = SQLITE_OK;
c->qc.found++;
secdebug("sql", "updated row: %llu", rowid);
} else if (s3e == SQLITE_ERROR) {
s3e = SQLITE_OK;
}
}
out:
if (s3e) q->q_error = osstatus_for_s3e(s3e);
s3e = sqlite3_reset(c->update_stmt);
if (s3e && !q->q_error) q->q_error = osstatus_for_s3e(s3e);
if (q->q_error) { secdebug("sql", "update failed: %d", q->q_error); }
CFReleaseSafe(item);
CFReleaseSafe(edata);
}
static void
sqlAppendWhereOrAnd(CFMutableStringRef sql, bool *needWhere) {
if (!needWhere || !*needWhere) {
CFStringAppend(sql, CFSTR(" AND "));
} else {
CFStringAppend(sql, CFSTR(" WHERE "));
*needWhere = false;
}
}
static void
sqlAppendWhereBind(CFMutableStringRef sql, CFStringRef col, bool *needWhere) {
sqlAppendWhereOrAnd(sql, needWhere);
CFStringAppend(sql, col);
CFStringAppend(sql, CFSTR("=?"));
}
static void
sqlAppendWhereROWID(CFMutableStringRef sql,
CFStringRef col, sqlite_int64 row_id,
bool *needWhere) {
if (row_id > 0) {
sqlAppendWhereOrAnd(sql, needWhere);
CFStringAppendFormat(sql, NULL, CFSTR("%@=%lld"), col, row_id);
}
}
static void
sqlAppendWhereAttrs(CFMutableStringRef sql, const Query *q, bool *needWhere) {
CFIndex ix, attr_count = query_attr_count(q);
for (ix = 0; ix < attr_count; ++ix) {
sqlAppendWhereBind(sql, query_attr_at(q, ix).key, needWhere);
}
}
static void
sqlAppendWhereAccessGroups(CFMutableStringRef sql,
CFStringRef col,
CFArrayRef accessGroups,
bool *needWhere) {
CFIndex ix, ag_count;
if (!accessGroups || 0 == (ag_count = CFArrayGetCount(accessGroups))) {
return;
}
sqlAppendWhereOrAnd(sql, needWhere);
#if 1
CFStringAppend(sql, col);
CFStringAppend(sql, CFSTR(" IN (?"));
for (ix = 1; ix < ag_count; ++ix) {
CFStringAppend(sql, CFSTR(",?"));
}
CFStringAppend(sql, CFSTR(")"));
#else
CFStringAppendFormat(sql, 0, CFSTR("(%@=?"), col);
for (ix = 1; ix < ag_count; ++ix) {
CFStringAppendFormat(sql, 0, CFSTR(" OR %@=?"), col);
}
CFStringAppend(sql, CFSTR(")"));
#endif
}
static void sqlAppendWhereClause(CFMutableStringRef sql, const Query *q,
CFArrayRef accessGroups) {
bool needWhere = true;
sqlAppendWhereROWID(sql, CFSTR("ROWID"), q->q_row_id, &needWhere);
sqlAppendWhereAttrs(sql, q, &needWhere);
sqlAppendWhereAccessGroups(sql, CFSTR("agrp"), accessGroups, &needWhere);
}
static void sqlAppendLimit(CFMutableStringRef sql, CFIndex limit) {
if (limit != kSecMatchUnlimited)
CFStringAppendFormat(sql, NULL, CFSTR(" LIMIT %d;"), limit);
else
CFStringAppend(sql, CFSTR(";"));
}
static CFStringRef s3dl_select_sql(Query *q, int version, CFArrayRef accessGroups) {
CFMutableStringRef sql = CFStringCreateMutable(NULL, 0);
if (q->q_class == &identity_class) {
CFStringAppendFormat(sql, NULL, CFSTR("SELECT crowid, "
CERTIFICATE_DATA_COLUMN_LABEL ", rowid, data FROM "
"(SELECT cert.rowid AS crowid, cert.labl AS labl,"
" cert.issr AS issr, cert.slnr AS slnr, cert.skid AS skid,"
" keys.*, cert.data AS " CERTIFICATE_DATA_COLUMN_LABEL
" FROM keys, cert"
" WHERE keys.priv == 1 AND cert.pkhh == keys.klbl"));
sqlAppendWhereAccessGroups(sql, CFSTR("cert.agrp"), accessGroups, 0);
sqlAppendWhereROWID(sql, CFSTR("crowid"), q->q_row_id, 0);
CFStringAppend(sql, CFSTR(")"));
bool needWhere = true;
sqlAppendWhereAttrs(sql, q, &needWhere);
sqlAppendWhereAccessGroups(sql, CFSTR("agrp"), accessGroups, &needWhere);
} else {
CFStringAppend(sql, (version < 5 ? CFSTR("SELECT * FROM ") :
CFSTR("SELECT rowid, data FROM ")));
CFStringAppend(sql, q->q_class->name);
sqlAppendWhereClause(sql, q, accessGroups);
}
sqlAppendLimit(sql, q->q_limit);
return sql;
}
static int sqlBindAccessGroups(sqlite3_stmt *stmt, CFArrayRef accessGroups,
int *pParam) {
int s3e = SQLITE_OK;
int param = *pParam;
CFIndex ix, count = accessGroups ? CFArrayGetCount(accessGroups) : 0;
for (ix = 0; ix < count; ++ix) {
s3e = kc_bind_paramter(stmt, param++,
CFArrayGetValueAtIndex(accessGroups, ix));
if (s3e)
break;
}
*pParam = param;
return s3e;
}
static int sqlBindWhereClause(sqlite3_stmt *stmt, const Query *q,
CFArrayRef accessGroups, int *pParam) {
int s3e = SQLITE_OK;
int param = *pParam;
CFIndex ix, attr_count = query_attr_count(q);
for (ix = 0; ix < attr_count; ++ix) {
s3e = kc_bind_paramter(stmt, param++, query_attr_at(q, ix).value);
if (s3e)
break;
}
if (s3e == SQLITE_OK) {
s3e = sqlBindAccessGroups(stmt, accessGroups, ¶m);
}
*pParam = param;
return s3e;
}
static OSStatus
s3dl_query(s3dl_db_thread *dbt, s3dl_handle_row handle_row, int version,
void *context)
{
struct s3dl_query_ctx *c = context;
Query *q = c->q;
CFArrayRef accessGroups = c->accessGroups;
int s3e;
if (q->q_ref)
return errSecValueRefUnsupported;
if (q->q_row_id && query_attr_count(q))
return errSecItemIllegalQuery;
sqlite3 *s3h = dbt->s3_handle;
CFStringRef sql = s3dl_select_sql(q, version, accessGroups);
sqlite3_stmt *stmt = NULL;
s3e = kc_prepare_statement(s3h, sql, &stmt);
CFRelease(sql);
if (s3e == SQLITE_OK) {
int param = 1;
if (q->q_class == &identity_class) {
s3e = sqlBindAccessGroups(stmt, accessGroups, ¶m);
}
if (s3e == SQLITE_OK)
s3e = sqlBindWhereClause(stmt, q, accessGroups, ¶m);
}
if (q->q_limit == 1) {
c->result = NULL;
} else {
c->result = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
}
while (s3e == SQLITE_OK &&
(q->q_limit == kSecMatchUnlimited || c->found < q->q_limit)) {
s3e = sqlite3_step(stmt);
if (s3e == SQLITE_ROW) {
handle_row(stmt, context);
s3e = q->q_error;
if (s3e == errSecDecode) {
secerror("Ignoring undecryptable %@ item: ", q->q_class->name);
s3e = SQLITE_OK;
}
} else if (s3e == SQLITE_DONE) {
if (c->found == 0) {
s3e = errSecItemNotFound;
} else {
s3e = SQLITE_OK;
}
break;
} else if (s3e != SQLITE_OK) {
secdebug("sql", "select: %d: %s", s3e, sqlite3_errmsg(s3h));
}
}
if (stmt) {
int s3e2 = sqlite3_finalize(stmt);
if (s3e2 != SQLITE_OK && s3e == SQLITE_OK)
s3e = s3e2;
}
OSStatus status;
if (s3e) status = osstatus_for_s3e(s3e);
else if (q->q_error) status = q->q_error;
else return 0;
CFReleaseNull(c->result);
return status;
}
static OSStatus
s3dl_copy_matching(s3dl_db_thread *dbt, Query *q, CFTypeRef *result,
CFArrayRef accessGroups)
{
struct s3dl_query_ctx ctx = {
.q = q, .accessGroups = accessGroups,
};
OSStatus status = s3dl_query(dbt, s3dl_query_row, CURRENT_DB_VERSION, &ctx);
if (result)
*result = ctx.result;
else
CFReleaseSafe(ctx.result);
return status;
}
static CFStringRef s3dl_update_sql(Query *q) {
CFMutableStringRef sql = CFStringCreateMutable(NULL, 0);
CFStringAppendFormat(sql, NULL, CFSTR("UPDATE %@ SET"), q->q_class->name);
CFIndex ix, attr_count = query_attr_count(q);
for (ix = 0; ix < attr_count; ++ix) {
CFStringAppendFormat(sql, NULL, CFSTR(" %@=?,"),
query_attr_at(q, ix).key);
}
CFStringAppend(sql, CFSTR(" data=? WHERE ROWID=?;"));
return sql;
}
static int s3dl_update_bind(Query *q, sqlite3_stmt *stmt) {
int s3e = SQLITE_OK;
int param = 1;
CFIndex ix, attr_count = query_attr_count(q);
for (ix = 0; s3e == SQLITE_OK && ix < attr_count; ++ix) {
s3e = kc_bind_paramter(stmt, param++, query_attr_at(q, ix).value);
}
return s3e;
}
static OSStatus
s3dl_query_update(s3dl_db_thread *dbt, Query *q,
CFDictionaryRef attributesToUpdate, CFArrayRef accessGroups)
{
if (query_match_count(q) != 0)
return errSecItemMatchUnsupported;
if (q->q_ref)
return errSecValueRefUnsupported;
if (q->q_row_id && query_attr_count(q))
return errSecItemIllegalQuery;
int s3e = SQLITE_OK;
Query *u = query_create(q->q_class, attributesToUpdate, &q->q_error);
if (u == NULL) return q->q_error;
if (!query_update_parse(u, attributesToUpdate, &q->q_error))
goto errOut;
query_pre_update(u);
sqlite3 *s3h = dbt->s3_handle;
CFStringRef sql = s3dl_update_sql(u);
sqlite3_stmt *stmt = NULL;
s3e = kc_prepare_statement(s3h, sql, &stmt);
CFRelease(sql);
if (s3e == SQLITE_OK)
s3e = s3dl_update_bind(u, stmt);
if (s3e == SQLITE_OK) {
s3e = s3dl_begin_transaction(dbt);
q->q_return_type = 0;
struct s3dl_update_row_ctx ctx = {
.qc = {
.q = q, .accessGroups = accessGroups,
},
.u = u,
.update_stmt = stmt
};
u->q_error = s3dl_query(dbt, s3dl_update_row, CURRENT_DB_VERSION, &ctx);
s3e = s3dl_end_transaction(dbt, s3e);
}
if (stmt) {
int s3e2 = sqlite3_finalize(stmt);
if (s3e2 != SQLITE_OK && s3e == SQLITE_OK)
s3e = s3e2;
}
errOut:
query_destroy(u, &q->q_error);
if (s3e) return osstatus_for_s3e(s3e);
if (q->q_error) return q->q_error;
return 0;
}
static OSStatus
s3dl_query_delete(s3dl_db_thread *dbt, Query *q, CFArrayRef accessGroups)
{
sqlite3 *s3h = dbt->s3_handle;
sqlite3_stmt *stmt = NULL;
CFMutableStringRef sql;
int s3e;
sql = CFStringCreateMutable(NULL, 0);
CFStringAppendFormat(sql, NULL, CFSTR("DELETE FROM %@"), q->q_class->name);
sqlAppendWhereClause(sql, q, accessGroups);
CFStringAppend(sql, CFSTR(";"));
s3e = kc_prepare_statement(s3h, sql, &stmt);
CFRelease(sql);
if (s3e == SQLITE_OK) {
int param = 1;
s3e = sqlBindWhereClause(stmt, q, accessGroups, ¶m);
}
if (s3e == SQLITE_OK) {
s3e = sqlite3_step(stmt);
if (s3e == SQLITE_DONE) {
int changes = sqlite3_changes(s3h);
if (changes == 0 && query_attr_count(q) > 0) {
s3e = errSecItemNotFound;
} else {
s3e = SQLITE_OK;
secdebug("sql", "deleted: %d records", changes);
}
} else if (s3e != SQLITE_OK) {
secdebug("sql", "delete: %d: %s", s3e, sqlite3_errmsg(s3h));
}
}
if (stmt) {
int s3e2 = sqlite3_finalize(stmt);
if (s3e2 != SQLITE_OK && s3e == SQLITE_OK)
s3e = s3e2;
}
return s3e;
}
static bool SecItemIsSystemBound(CFDictionaryRef item, const kc_class *class) {
CFStringRef agrp = CFDictionaryGetValue(item, kSecAttrAccessGroup);
if (!isString(agrp))
return false;
if (CFEqual(agrp, CFSTR("lockdown-identities"))) {
secdebug("backup", "found sys_bound item: %@", item);
return true;
}
if (CFEqual(agrp, CFSTR("apple")) && class == &genp_class) {
CFStringRef service = CFDictionaryGetValue(item, kSecAttrService);
CFStringRef account = CFDictionaryGetValue(item, kSecAttrAccount);
if (isString(service) && isString(account) &&
CFEqual(service, CFSTR("com.apple.managedconfiguration")) &&
(CFEqual(account, CFSTR("Public")) ||
CFEqual(account, CFSTR("Private")))) {
secdebug("backup", "found sys_bound item: %@", item);
return true;
}
}
secdebug("backup", "found non sys_bound item: %@", item);
return false;
}
static OSStatus SecServerDeleteAll(s3dl_db_thread *dbt) {
int s3e = sqlite3_exec(dbt->s3_handle,
"DELETE from genp;"
"DELETE from inet;"
"DELETE FROM cert;"
"DELETE FROM keys;",
NULL, NULL, NULL);
return s3e == SQLITE_OK ? 0 : osstatus_for_s3e(s3e);
}
struct s3dl_export_row_ctx {
struct s3dl_query_ctx qc;
keybag_handle_t dest_keybag;
enum SecItemFilter filter;
int version;
s3dl_db_thread *dbt;
};
static CFMutableDictionaryRef
s3dl_item_from_pre_v5(sqlite3_stmt *stmt, Query *q, s3dl_db_thread *dbt,
keyclass_t *keyclass, sqlite_int64 *rowid) {
int cix = 0, cc = sqlite3_column_count(stmt);
CFMutableDictionaryRef item = CFDictionaryCreateMutable(0, 0,
&kCFTypeDictionaryKeyCallBacks, & kCFTypeDictionaryValueCallBacks);
for (cix = 0; cix < cc; ++cix) {
const char *cname = sqlite3_column_name(stmt, cix);
CFStringRef key = NULL;
CFTypeRef value = NULL;
int ctype = sqlite3_column_type(stmt, cix);
switch (ctype) {
case SQLITE_INTEGER:
{
sqlite_int64 i64Value = sqlite3_column_int64(stmt, cix);
if (!strcmp(cname, "rowid")) {
*rowid = i64Value;
continue;
} else if (i64Value > INT_MAX || i64Value < INT_MIN) {
value = CFNumberCreate(0, kCFNumberLongLongType, &i64Value);
} else {
int iValue = (int)i64Value;
value = CFNumberCreate(0, kCFNumberIntType, &iValue);
}
break;
}
case SQLITE_FLOAT:
value = CFDateCreate(0, sqlite3_column_double(stmt, cix));
break;
case SQLITE_TEXT:
value = CFStringCreateWithCString(0,
(const char *)sqlite3_column_text(stmt, cix),
kCFStringEncodingUTF8);
break;
case SQLITE_BLOB:
value = CFDataCreate(0, sqlite3_column_blob(stmt, cix),
sqlite3_column_bytes(stmt, cix));
if (value && !strcmp(cname, "data")) {
CFDataRef plain;
if (q->q_keybag == KEYBAG_LEGACY) {
q->q_error = kc_decrypt_data(dbt, value, &plain);
} else {
q->q_error = ks_decrypt_data(q->q_keybag, keyclass,
value, &plain, 0);
}
CFRelease(value);
if (q->q_error) {
secerror("failed to decrypt data: %d", q->q_error);
goto out;
}
value = plain;
key = kSecValueData;
}
break;
default:
secwarning("Unsupported column type: %d", ctype);
case SQLITE_NULL:
continue;
}
if (!value)
continue;
if (key) {
CFDictionarySetValue(item, key, value);
} else {
key = CFStringCreateWithCString(0, cname, kCFStringEncodingUTF8);
if (key) {
CFDictionarySetValue(item, key, value);
CFRelease(key);
}
}
CFRelease(value);
}
return item;
out:
CFReleaseSafe(item);
return NULL;
}
static void s3dl_export_row(sqlite3_stmt *stmt, void *context) {
struct s3dl_export_row_ctx *c = context;
Query *q = c->qc.q;
keyclass_t keyclass = 0;
CFMutableDictionaryRef item;
sqlite_int64 rowid = -1;
if (c->version < 5) {
item = s3dl_item_from_pre_v5(stmt, q, c->dbt, &keyclass, &rowid);
} else {
item = s3dl_item_from_col(stmt, q, 1, c->qc.accessGroups, &keyclass);
rowid = sqlite3_column_int64(stmt, 0);
}
if (item) {
bool do_sys_bound = c->filter == kSecSysBoundItemFilter;
if (c->filter == kSecNoItemFilter ||
SecItemIsSystemBound(item, q->q_class) == do_sys_bound) {
secdebug("item", "export rowid %llu item: %@", rowid, item);
CFDataRef pref = _SecItemMakePersistentRef(q->q_class->name, rowid);
if (pref) {
if (c->dest_keybag != KEYBAG_NONE) {
CFDataRef plain = CFPropertyListCreateData(0, item, kCFPropertyListBinaryFormat_v1_0, 0, 0);
CFDictionaryRemoveAllValues(item);
if (plain) {
CFDataRef edata = NULL;
int s3e = ks_encrypt_data(c->dest_keybag, keyclass, plain, &edata);
if (s3e)
q->q_error = osstatus_for_s3e(s3e);
if (edata) {
CFDictionarySetValue(item, kSecValueData, edata);
CFRelease(edata);
}
CFRelease(plain);
}
}
if (CFDictionaryGetCount(item)) {
CFDictionarySetValue(item, kSecValuePersistentRef, pref);
CFArrayAppendValue((CFMutableArrayRef)c->qc.result, item);
c->qc.found++;
}
CFRelease(pref);
}
}
CFRelease(item);
}
}
static CFDictionaryRef SecServerExportKeychainPlist(s3dl_db_thread *dbt,
keybag_handle_t src_keybag, keybag_handle_t dest_keybag,
enum SecItemFilter filter, int version, OSStatus *error) {
CFMutableDictionaryRef keychain;
keychain = CFDictionaryCreateMutable(kCFAllocatorDefault, 0,
&kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
if (!keychain) {
if (error && !*error)
*error = errSecAllocate;
goto errOut;
}
unsigned class_ix;
Query q = { .q_keybag = src_keybag };
q.q_return_type = kSecReturnDataMask | kSecReturnAttributesMask | \
kSecReturnPersistentRefMask;
q.q_limit = kSecMatchUnlimited;
const kc_class *kc_classes[] = {
&genp_class,
&inet_class,
&cert_class,
&keys_class
};
for (class_ix = 0; class_ix < sizeof(kc_classes) / sizeof(*kc_classes);
++class_ix) {
q.q_class = kc_classes[class_ix];
struct s3dl_export_row_ctx ctx = {
.qc = { .q = &q, },
.dest_keybag = dest_keybag, .filter = filter, .version = version,
.dbt = dbt,
};
ctx.qc.result = CFArrayCreateMutable(kCFAllocatorDefault, 0,
&kCFTypeArrayCallBacks);
if (ctx.qc.result) {
OSStatus status = s3dl_query(dbt, s3dl_export_row, version, &ctx);
if (status == noErr) {
if (CFArrayGetCount(ctx.qc.result))
CFDictionaryAddValue(keychain, q.q_class->name, ctx.qc.result);
CFRelease(ctx.qc.result);
} else if (status != errSecItemNotFound) {
if (error && !*error)
*error = status;
if (status == errSecInteractionNotAllowed) {
secerror("Device locked during attempted keychain upgrade");
CFReleaseNull(keychain);
break;
}
}
}
}
errOut:
return keychain;
}
static OSStatus SecServerExportKeychain(s3dl_db_thread *dbt,
keybag_handle_t src_keybag, keybag_handle_t dest_keybag,
CFDataRef *data_out) {
OSStatus status = noErr;
CFDictionaryRef keychain = SecServerExportKeychainPlist(dbt,
src_keybag, dest_keybag, kSecBackupableItemFilter, CURRENT_DB_VERSION,
&status);
if (keychain) {
CFErrorRef error = NULL;
*data_out = CFPropertyListCreateData(kCFAllocatorDefault, keychain,
kCFPropertyListBinaryFormat_v1_0,
0, &error);
CFRelease(keychain);
if (error) {
secerror("Error encoding keychain: %@", error);
status = (OSStatus)CFErrorGetCode(error);
CFRelease(error);
}
}
return status;
}
struct SecServerImportClassState {
s3dl_db_thread *dbt;
OSStatus status;
keybag_handle_t src_keybag;
keybag_handle_t dest_keybag;
enum SecItemFilter filter;
};
struct SecServerImportItemState {
const kc_class *class;
struct SecServerImportClassState *s;
};
static void SecItemImportInferAccessible(Query *q) {
CFStringRef agrp = CFDictionaryGetValue(q->q_item, kSecAttrAccessGroup);
CFStringRef accessible = NULL;
if (isString(agrp)) {
if (CFEqual(agrp, CFSTR("apple"))) {
if (q->q_class == &cert_class) {
accessible = kSecAttrAccessibleAlways;
} else if (q->q_class == &genp_class) {
CFStringRef svce = CFDictionaryGetValue(q->q_item, kSecAttrService);
if (isString(svce)) {
if (CFEqual(svce, CFSTR("iTools"))) {
accessible = kSecAttrAccessibleAlways;
} else if (CFEqual(svce, CFSTR("BackupAgent"))) {
accessible = kSecAttrAccessibleWhenUnlockedThisDeviceOnly;
} else if (CFEqual(svce, CFSTR("MobileBluetooth"))) {
accessible = kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly;
}
}
} else if (q->q_class == &inet_class) {
CFStringRef ptcl = CFDictionaryGetValue(q->q_item, kSecAttrProtocol);
if (isString(ptcl)) {
if (CFEqual(ptcl, kSecAttrProtocolLDAP) ||
CFEqual(ptcl, kSecAttrProtocolLDAPS)) {
accessible = kSecAttrAccessibleWhenUnlocked;
}
}
}
if (accessible == NULL) {
accessible = kSecAttrAccessibleAfterFirstUnlock;
}
} else if (CFEqual(agrp, CFSTR("com.apple.apsd"))
|| CFEqual(agrp, CFSTR("lockdown-identities"))) {
accessible = kSecAttrAccessibleAlwaysThisDeviceOnly;
}
}
if (accessible == NULL) {
if (q->q_class == &cert_class) {
accessible = kSecAttrAccessibleAlways;
} else {
accessible = kSecAttrAccessibleWhenUnlocked;
}
}
query_add_attribute(kSecAttrAccessible, accessible, q);
}
static void SecItemImportMigrate(Query *q) {
CFStringRef agrp = CFDictionaryGetValue(q->q_item, kSecAttrAccessGroup);
CFStringRef accessible = CFDictionaryGetValue(q->q_item, kSecAttrAccessible);
if (!isString(agrp) || !isString(accessible))
return;
if (q->q_class == &genp_class && CFEqual(accessible, kSecAttrAccessibleAlways)) {
CFStringRef svce = CFDictionaryGetValue(q->q_item, kSecAttrService);
if (!isString(svce)) return;
if (CFEqual(agrp, CFSTR("apple"))) {
if (CFEqual(svce, CFSTR("AirPort"))) {
query_set_attribute(kSecAttrAccessible, kSecAttrAccessibleAfterFirstUnlock, q);
} else if (CFEqual(svce, CFSTR("com.apple.airplay.password"))) {
query_set_attribute(kSecAttrAccessible, kSecAttrAccessibleWhenUnlocked, q);
} else if (CFEqual(svce, CFSTR("YouTube"))) {
query_set_attribute(kSecAttrAccessible, kSecAttrAccessibleWhenUnlocked, q);
query_set_attribute(kSecAttrAccessGroup, CFSTR("com.apple.youtube.credentials"), q);
} else {
CFStringRef desc = CFDictionaryGetValue(q->q_item, kSecAttrDescription);
if (!isString(desc)) return;
if (CFEqual(desc, CFSTR("IPSec Shared Secret")) || CFEqual(desc, CFSTR("PPP Password"))) {
query_set_attribute(kSecAttrAccessible, kSecAttrAccessibleAfterFirstUnlock, q);
}
}
}
} else if (q->q_class == &inet_class && CFEqual(accessible, kSecAttrAccessibleAlways)) {
if (CFEqual(agrp, CFSTR("PrintKitAccessGroup"))) {
query_set_attribute(kSecAttrAccessible, kSecAttrAccessibleWhenUnlocked, q);
} else if (CFEqual(agrp, CFSTR("apple"))) {
CFTypeRef ptcl = CFDictionaryGetValue(q->q_item, kSecAttrProtocol);
bool is_proxy = false;
if (isNumber(ptcl)) {
SInt32 iptcl;
CFNumberGetValue(ptcl, kCFNumberSInt32Type, &iptcl);
is_proxy = (iptcl == FOUR_CHAR_CODE('htpx') ||
iptcl == FOUR_CHAR_CODE('htsx') ||
iptcl == FOUR_CHAR_CODE('ftpx') ||
iptcl == FOUR_CHAR_CODE('rtsx') ||
iptcl == FOUR_CHAR_CODE('xpth') ||
iptcl == FOUR_CHAR_CODE('xsth') ||
iptcl == FOUR_CHAR_CODE('xptf') ||
iptcl == FOUR_CHAR_CODE('xstr'));
} else if (isString(ptcl)) {
is_proxy = (CFEqual(ptcl, kSecAttrProtocolHTTPProxy) ||
CFEqual(ptcl, kSecAttrProtocolHTTPSProxy) ||
CFEqual(ptcl, kSecAttrProtocolRTSPProxy) ||
CFEqual(ptcl, kSecAttrProtocolFTPProxy));
}
if (is_proxy)
query_set_attribute(kSecAttrAccessible, kSecAttrAccessibleWhenUnlocked, q);
}
}
}
static void SecServerImportItem(const void *value, void *context) {
struct SecServerImportItemState *state =
(struct SecServerImportItemState *)context;
if (state->s->status)
return;
if (!isDictionary(value)) {
state->s->status = errSecParam;
return;
}
CFDictionaryRef item = (CFDictionaryRef)value;
if (state->s->filter == kSecBackupableItemFilter &&
SecItemIsSystemBound(item, state->class))
return;
Query *q = query_create(state->class, item, &state->s->status);
if (!q)
return;
query_parse(q, item, &state->s->status);
CFDataRef pdata = NULL;
if (state->s->src_keybag == KEYBAG_NONE) {
SecItemImportMigrate(q);
} else {
if (q->q_data) {
keyclass_t keyclass;
uint32_t version;
q->q_error = ks_decrypt_data(state->s->src_keybag,
&keyclass, q->q_data, &pdata, &version);
if (q->q_error) {
q->q_error = errSecDecode;
asl_log(NULL, NULL, ASL_LEVEL_ERR,
"keyclass attribute %d doesn't match keyclass in blob %d",
q->q_keyclass, keyclass);
}
if (version < 2) {
if (q->q_keyclass && q->q_keyclass != keyclass) {
q->q_error = errSecDecode;
asl_log(NULL, NULL, ASL_LEVEL_ERR,
"keyclass attribute %d doesn't match keyclass in blob %d",
q->q_keyclass, keyclass);
} else {
query_set_data(pdata, q);
SecItemImportMigrate(q);
}
} else {
CFErrorRef error = NULL;
CFPropertyListFormat format;
item = CFPropertyListCreateWithData(0, pdata, kCFPropertyListImmutable, &format, &error);
if (item) {
query_update_parse(q, item, &state->s->status);
secdebug("item", "importing status: %ld %@",
state->s->status, item);
CFRelease(item);
} else if (error) {
secerror("failed to decode v%d item data: %@",
version, error);
CFRelease(error);
}
}
}
}
if (q->q_keyclass == 0)
SecItemImportInferAccessible(q);
if (q->q_error) {
state->s->status = q->q_error;
} else {
if (q->q_keyclass == 0) {
state->s->status = errSecParam;
} else {
if (state->s->filter == kSecSysBoundItemFilter) {
q->q_row_id = 0;
}
query_pre_add(q, false);
state->s->status = s3dl_query_add(state->s->dbt, q, NULL);
}
}
CFReleaseSafe(pdata);
query_destroy(q, &state->s->status);
if (state->s->status) {
secerror("Failed to import %@ item %ld ignoring error",
state->class->name, state->s->status);
state->s->status = 0;
}
}
static void SecServerImportClass(const void *key, const void *value,
void *context) {
struct SecServerImportClassState *state =
(struct SecServerImportClassState *)context;
if (state->status)
return;
if (!isString(key)) {
state->status = errSecParam;
return;
}
const void *desc = CFDictionaryGetValue(gClasses, key);
const kc_class *class = desc;
if (!class || class == &identity_class) {
state->status = errSecParam;
return;
}
struct SecServerImportItemState item_state = {
.class = class, .s = state
};
if (isArray(value)) {
CFArrayRef items = (CFArrayRef)value;
CFArrayApplyFunction(items, CFRangeMake(0, CFArrayGetCount(items)),
SecServerImportItem, &item_state);
} else {
CFDictionaryRef item = (CFDictionaryRef)value;
SecServerImportItem(item, &item_state);
}
}
static OSStatus SecServerImportKeychainInPlist(s3dl_db_thread *dbt,
keybag_handle_t src_keybag, keybag_handle_t dest_keybag,
CFDictionaryRef keychain, enum SecItemFilter filter) {
OSStatus status = errSecSuccess;
CFDictionaryRef sys_bound = NULL;
if (filter == kSecBackupableItemFilter) {
require(sys_bound = SecServerExportKeychainPlist(dbt, KEYBAG_DEVICE,
KEYBAG_NONE, kSecSysBoundItemFilter, CURRENT_DB_VERSION,
&status), errOut);
}
require_noerr(status = SecServerDeleteAll(dbt), errOut);
struct SecServerImportClassState state = {
.dbt = dbt,
.src_keybag = src_keybag,
.dest_keybag = dest_keybag,
.filter = filter
};
CFDictionaryApplyFunction(keychain, SecServerImportClass, &state);
if (sys_bound) {
state.src_keybag = KEYBAG_NONE;
state.filter = kSecSysBoundItemFilter;
CFDictionaryApplyFunction(sys_bound, SecServerImportClass, &state);
CFRelease(sys_bound);
}
status = state.status;
errOut:
return status;
}
static OSStatus SecServerImportKeychain(s3dl_db_thread *dbt,
keybag_handle_t src_keybag,
keybag_handle_t dest_keybag, CFDataRef data) {
int s3e = s3dl_begin_transaction(dbt);
OSStatus status = errSecSuccess;
if (s3e != SQLITE_OK) {
status = osstatus_for_s3e(s3e);
} else {
CFDictionaryRef keychain;
CFPropertyListFormat format;
CFErrorRef error = NULL;
keychain = CFPropertyListCreateWithData(kCFAllocatorDefault, data,
kCFPropertyListImmutable, &format,
&error);
if (keychain) {
if (isDictionary(keychain)) {
status = SecServerImportKeychainInPlist(dbt, src_keybag,
dest_keybag, keychain,
kSecBackupableItemFilter);
} else {
status = errSecParam;
}
CFRelease(keychain);
} else {
secerror("Error decoding keychain: %@", error);
status = (OSStatus)CFErrorGetCode(error);
CFRelease(error);
}
}
s3e = s3dl_end_transaction(dbt, status);
return status ? status : osstatus_for_s3e(s3e);
}
static OSStatus
SecServerMigrateKeychain(s3dl_db_thread *dbt,
int32_t handle_in, CFDataRef data_in,
int32_t *handle_out, CFDataRef *data_out) {
OSStatus status;
if (handle_in == kSecMigrateKeychainImport) {
if (data_in == NULL) {
return errSecParam;
}
status = SecServerImportKeychain(dbt, KEYBAG_NONE, KEYBAG_DEVICE, data_in);
*data_out = NULL;
*handle_out = 0;
} else if (handle_in == kSecMigrateKeychainExport) {
if (data_in != NULL) {
return errSecParam;
}
status = SecServerExportKeychain(dbt, KEYBAG_DEVICE, KEYBAG_NONE, data_out);
*handle_out = 0;
} else {
status = errSecParam;
}
return status;
}
static keybag_handle_t ks_open_keybag(CFDataRef keybag, CFDataRef password) {
#if USE_KEYSTORE
uint64_t outputs[] = { KEYBAG_NONE };
uint32_t num_outputs = sizeof(outputs) / sizeof(*outputs);
IOReturn kernResult;
kernResult = IOConnectCallMethod(keystore,
kAppleKeyStoreKeyBagCreateWithData, NULL, 0, CFDataGetBytePtr(keybag),
CFDataGetLength(keybag), outputs, &num_outputs, NULL, 0);
if (kernResult) {
asl_log(NULL, NULL, ASL_LEVEL_ERR,
"kAppleKeyStoreKeyBagCreateWithData: %x", kernResult);
goto errOut;
}
if (password) {
kernResult = IOConnectCallMethod(keystore, kAppleKeyStoreKeyBagUnlock,
outputs, 1, CFDataGetBytePtr(password), CFDataGetLength(password),
NULL, 0, NULL, NULL);
if (kernResult) {
asl_log(NULL, NULL, ASL_LEVEL_ERR,
"kAppleKeyStoreKeyBagCreateWithData: %x", kernResult);
goto errOut;
}
}
return (keybag_handle_t)outputs[0];
errOut:
return -3;
#else
return KEYBAG_NONE;
#endif
}
static void ks_close_keybag(keybag_handle_t keybag) {
#if USE_KEYSTORE
uint64_t inputs[] = { keybag };
IOReturn kernResult = IOConnectCallMethod(keystore,
kAppleKeyStoreKeyBagRelease, inputs, 1, NULL, 0, NULL, NULL, NULL, 0);
if (kernResult) {
asl_log(NULL, NULL, ASL_LEVEL_ERR,
"kAppleKeyStoreKeyBagRelease: %d: %x", keybag, kernResult);
}
#endif
}
static OSStatus SecServerKeychainBackup(s3dl_db_thread *dbt, CFDataRef keybag,
CFDataRef password, CFDataRef *backup) {
OSStatus status;
keybag_handle_t backup_keybag = ks_open_keybag(keybag, password);
status = SecServerExportKeychain(dbt, KEYBAG_DEVICE, backup_keybag,
backup);
ks_close_keybag(backup_keybag);
return status;
}
static OSStatus SecServerKeychainRestore(s3dl_db_thread *dbt, CFDataRef backup,
CFDataRef keybag, CFDataRef password) {
OSStatus status;
keybag_handle_t backup_keybag = ks_open_keybag(keybag, password);
status = SecServerImportKeychain(dbt, backup_keybag, KEYBAG_DEVICE,
backup);
ks_close_keybag(backup_keybag);
return status;
}
static pthread_once_t kc_dbhandle_init_once = PTHREAD_ONCE_INIT;
static db_handle kc_dbhandle = NULL;
static void kc_dbhandle_init(void)
{
#if 0
CFTypeRef kc_attributes[] = {
kSecAttrAccessible,
kSecAttrAccessGroup,
kSecAttrCreationDate,
kSecAttrModificationDate,
kSecAttrDescription,
kSecAttrComment,
kSecAttrCreator,
kSecAttrType,
kSecAttrLabel,
kSecAttrIsInvisible,
kSecAttrIsNegative,
kSecAttrAccount,
kSecAttrService,
kSecAttrGeneric,
kSecAttrSecurityDomain,
kSecAttrServer,
kSecAttrProtocol,
kSecAttrAuthenticationType,
kSecAttrPort,
kSecAttrPath,
kSecAttrSubject,
kSecAttrIssuer,
kSecAttrSerialNumber,
kSecAttrSubjectKeyID,
kSecAttrPublicKeyHash,
kSecAttrCertificateType,
kSecAttrCertificateEncoding,
kSecAttrKeyClass,
kSecAttrApplicationLabel,
kSecAttrIsPermanent,
kSecAttrApplicationTag,
kSecAttrKeyType,
kSecAttrKeySizeInBits,
kSecAttrEffectiveKeySize,
kSecAttrCanEncrypt,
kSecAttrCanDecrypt,
kSecAttrCanDerive,
kSecAttrCanSign,
kSecAttrCanVerify,
kSecAttrCanWrap,
kSecAttrCanUnwrap,
kSecAttrScriptCode,
kSecAttrAlias,
kSecAttrHasCustomIcon,
kSecAttrVolume,
kSecAttrAddress,
kSecAttrAFPServerSignature,
kSecAttrCRLType,
kSecAttrCRLEncoding,
kSecAttrKeyCreator,
kSecAttrIsPrivate,
kSecAttrIsModifiable,
kSecAttrStartDate,
kSecAttrEndDate,
kSecAttrIsSensitive,
kSecAttrWasAlwaysSensitive,
kSecAttrIsExtractable,
kSecAttrWasNeverExtractable,
kSecAttrCanSignRecover,
kSecAttrCanVerifyRecover
};
#endif
CFTypeRef kc_class_names[] = {
kSecClassGenericPassword,
kSecClassInternetPassword,
kSecClassCertificate,
kSecClassKey,
kSecClassIdentity
};
const void *kc_classes[] = {
&genp_class,
&inet_class,
&cert_class,
&keys_class,
&identity_class
};
#if 0
gAttributes = CFSetCreate(kCFAllocatorDefault, kc_attributes,
sizeof(kc_attributes) / sizeof(*kc_attributes), &kCFTypeSetCallBacks);
#endif
gClasses = CFDictionaryCreate(kCFAllocatorDefault, kc_class_names,
kc_classes,
sizeof(kc_classes) / sizeof(*kc_classes),
&kCFTypeDictionaryKeyCallBacks, 0);
const char *kcRelPath;
bool use_hwaes = hwaes_key_available();
if (use_hwaes) {
asl_log(NULL, NULL, ASL_LEVEL_INFO, "using hwaes key");
kcRelPath = "/Library/Keychains/keychain-2.db";
} else {
asl_log(NULL, NULL, ASL_LEVEL_ERR, "unable to access hwaes key");
kcRelPath = "/Library/Keychains/keychain-2-debug.db";
}
bool autocommit = true;
bool create = true;
#if NO_SERVER
const char *home = getenv("HOME");
char path[PATH_MAX];
size_t homeLen = strlen(home);
size_t kcRelPathLen = strlen(kcRelPath);
if (homeLen + kcRelPathLen > sizeof(path))
return;
strlcpy(path, home, sizeof(path));
strlcat(path, kcRelPath, sizeof(path));
kcRelPath = path;
#endif
s3dl_create_db_handle(kcRelPath, &kc_dbhandle, NULL , autocommit,
create, use_hwaes);
}
#if NO_SERVER
void kc_dbhandle_reset(void);
void kc_dbhandle_reset(void)
{
s3dl_close_db_handle(kc_dbhandle);
kc_dbhandle_init();
}
#endif
static int kc_get_dbt(s3dl_db_thread **dbt, bool create)
{
return s3dl_get_dbt(kc_dbhandle, dbt);
}
static int kc_release_dbt(s3dl_db_thread *dbt)
{
#if CLOSE_DB
s3dl_dbt_destructor(dbt);
pthread_setspecific(dbt->db->key, NULL);
#endif
return SQLITE_OK;
}
OSStatus
_SecItemCopyMatching(CFDictionaryRef query, CFTypeRef *result,
CFArrayRef accessGroups)
{
pthread_once(&kc_dbhandle_init_once, kc_dbhandle_init);
CFIndex ag_count;
if (!accessGroups || 0 == (ag_count = CFArrayGetCount(accessGroups)))
return errSecMissingEntitlement;
if (CFArrayContainsValue(accessGroups, CFRangeMake(0, ag_count), CFSTR("*")))
accessGroups = NULL;
OSStatus error = 0;
Query *q = query_create_with_limit(query, 1, &error);
if (q) {
CFStringRef agrp = CFDictionaryGetValue(q->q_item, kSecAttrAccessGroup);
if (agrp && accessGroupsAllows(accessGroups, agrp)) {
const void *val = agrp;
accessGroups = CFArrayCreate(0, &val, 1, &kCFTypeArrayCallBacks);
} else {
CFRetainSafe(accessGroups);
}
if (q->q_use_item_list) {
error = errSecUseItemListUnsupported;
#if defined(MULTIPLE_KEYCHAINS)
} else if (q->q_use_keychain) {
error = errSecUseKeychainUnsupported;
#endif
} else if (q->q_return_type != 0 && result == NULL) {
error = errSecReturnMissingPointer;
} else if (!q->q_error) {
s3dl_db_thread *dbt;
int s3e = s3dl_get_dbt(kc_dbhandle, &dbt);
if (s3e == SQLITE_OK) {
s3e = s3dl_copy_matching(dbt, q, result, accessGroups);
kc_release_dbt(dbt);
}
if (s3e)
error = osstatus_for_s3e(s3e);
}
CFReleaseSafe(accessGroups);
query_destroy(q, &error);
}
return error;
}
OSStatus
_SecItemAdd(CFDictionaryRef attributes, CFTypeRef *result,
CFArrayRef accessGroups)
{
pthread_once(&kc_dbhandle_init_once, kc_dbhandle_init);
CFIndex ag_count;
if (!accessGroups || 0 == (ag_count = CFArrayGetCount(accessGroups)))
return errSecMissingEntitlement;
OSStatus error = 0;
Query *q = query_create_with_limit(attributes, 0, &error);
if (q) {
CFStringRef agrp = (CFStringRef)CFDictionaryGetValue(attributes,
kSecAttrAccessGroup);
CFArrayRef ag = accessGroups;
if (CFArrayContainsValue(accessGroups, CFRangeMake(0, ag_count), CFSTR("*")))
accessGroups = NULL;
if (agrp) {
if (!accessGroupsAllows(accessGroups, agrp))
return errSecNoAccessForItem;
} else {
agrp = (CFStringRef)CFArrayGetValueAtIndex(ag, 0);
query_add_attribute(kSecAttrAccessGroup, agrp, q);
}
query_ensure_keyclass(q, agrp);
if (q->q_row_id)
error = errSecValuePersistentRefUnsupported;
#if defined(MULTIPLE_KEYCHAINS)
else if (q->q_use_keychain_list)
error = errSecUseKeychainListUnsupported;
#endif
else if (!q->q_error) {
s3dl_db_thread *dbt;
int s3e = s3dl_get_dbt(kc_dbhandle, &dbt);
if (s3e == SQLITE_OK) {
s3e = s3dl_begin_transaction(dbt);
if (s3e == SQLITE_OK) {
query_pre_add(q, true);
s3e = s3dl_query_add(dbt, q, result);
}
s3e = s3dl_end_transaction(dbt, s3e);
}
kc_release_dbt(dbt);
if (s3e)
error = osstatus_for_s3e(s3e);
}
query_destroy(q, &error);
}
return error;
}
OSStatus
_SecItemUpdate(CFDictionaryRef query,
CFDictionaryRef attributesToUpdate, CFArrayRef accessGroups)
{
pthread_once(&kc_dbhandle_init_once, kc_dbhandle_init);
CFIndex ag_count;
if (!accessGroups || 0 == (ag_count = CFArrayGetCount(accessGroups)))
return errSecMissingEntitlement;
if (CFArrayContainsValue(accessGroups, CFRangeMake(0, ag_count), CFSTR("*")))
accessGroups = NULL;
OSStatus error = 0;
Query *q = query_create_with_limit(query, kSecMatchUnlimited, &error);
if (q) {
if (q->q_use_item_list) {
error = errSecUseItemListUnsupported;
} else if (q->q_return_type & kSecReturnDataMask) {
error = errSecReturnDataUnsupported;
} else if (q->q_return_type & kSecReturnAttributesMask) {
error = errSecReturnAttributesUnsupported;
} else if (q->q_return_type & kSecReturnRefMask) {
error = errSecReturnRefUnsupported;
} else {
CFStringRef agrp = (CFStringRef)CFDictionaryGetValue(attributesToUpdate,
kSecAttrAccessGroup);
if (agrp) {
if (!accessGroupsAllows(accessGroups, agrp))
error = errSecNoAccessForItem;
}
if (!error) {
s3dl_db_thread *dbt;
int s3e = s3dl_get_dbt(kc_dbhandle, &dbt);
if (s3e == SQLITE_OK) {
s3e = s3dl_query_update(dbt, q, attributesToUpdate, accessGroups);
kc_release_dbt(dbt);
}
if (s3e)
error = osstatus_for_s3e(s3e);
}
}
query_destroy(q, &error);
}
return error;
}
OSStatus
_SecItemDelete(CFDictionaryRef query, CFArrayRef accessGroups)
{
pthread_once(&kc_dbhandle_init_once, kc_dbhandle_init);
CFIndex ag_count;
if (!accessGroups || 0 == (ag_count = CFArrayGetCount(accessGroups)))
return errSecMissingEntitlement;
if (CFArrayContainsValue(accessGroups, CFRangeMake(0, ag_count), CFSTR("*")))
accessGroups = NULL;
OSStatus error = 0;
Query *q = query_create_with_limit(query, kSecMatchUnlimited, &error);
if (q) {
if (q->q_limit != kSecMatchUnlimited)
error = errSecMatchLimitUnsupported;
else if (query_match_count(q) != 0)
error = errSecItemMatchUnsupported;
else if (q->q_ref)
error = errSecValueRefUnsupported;
else if (q->q_row_id && query_attr_count(q))
error = errSecItemIllegalQuery;
else {
s3dl_db_thread *dbt;
int s3e = s3dl_get_dbt(kc_dbhandle, &dbt);
if (s3e == SQLITE_OK) {
s3e = s3dl_query_delete(dbt, q, accessGroups);
kc_release_dbt(dbt);
}
if (s3e)
error = osstatus_for_s3e(s3e);
}
query_destroy(q, &error);
}
return error;
}
bool
_SecItemDeleteAll(void)
{
pthread_once(&kc_dbhandle_init_once, kc_dbhandle_init);
static const char deleteAllSQL[] = "BEGIN EXCLUSIVE TRANSACTION; "
"DELETE from inet; DELETE from cert; DELETE from keys; DELETE from genp; "
"COMMIT TRANSACTION; VACUUM;";
s3dl_db_thread *dbt;
int s3e = kc_get_dbt(&dbt, true);
if (s3e == SQLITE_OK) {
s3e = sqlite3_exec(dbt->s3_handle, deleteAllSQL, NULL, NULL, NULL);
kc_release_dbt(dbt);
}
return (s3e == SQLITE_OK);
}
static const char *restore_keychain_location = "/Library/Keychains/keychain.restoring";
OSStatus
_SecServerRestoreKeychain(void)
{
static db_handle restore_dbhandle = NULL;
s3dl_db_thread *restore_dbt = NULL, *dbt = NULL;
CFDataRef backup = NULL;
OSStatus status = errSecSuccess;
int s3e;
pthread_once(&kc_dbhandle_init_once, kc_dbhandle_init);
require_noerr(s3e = s3dl_get_dbt(kc_dbhandle, &dbt), errOut);
bool use_hwaes = hwaes_key_available();
require_noerr(s3e = s3dl_create_db_handle(restore_keychain_location,
&restore_dbhandle, &restore_dbt, true, false, use_hwaes), errOut);
require_noerr(status = SecServerExportKeychain(restore_dbt, KEYBAG_DEVICE,
KEYBAG_NONE, &backup), errOut);
require_noerr(status = SecServerImportKeychain(dbt, KEYBAG_NONE,
KEYBAG_DEVICE, backup), errOut);
errOut:
s3e = s3dl_close_db_handle(restore_dbhandle);
CFReleaseSafe(backup);
kc_release_dbt(restore_dbt);
kc_release_dbt(dbt);
if (s3e != SQLITE_OK)
return osstatus_for_s3e(s3e);
return status;
}
OSStatus
_SecServerMigrateKeychain(CFArrayRef args_in, CFTypeRef *args_out)
{
pthread_once(&kc_dbhandle_init_once, kc_dbhandle_init);
CFMutableArrayRef args = NULL;
CFNumberRef hin = NULL, hout = NULL;
int32_t handle_in, handle_out = 0;
CFDataRef data_in, data_out = NULL;
OSStatus status = errSecParam;
CFIndex argc = CFArrayGetCount(args_in);
s3dl_db_thread *dbt;
int s3e = s3dl_get_dbt(kc_dbhandle, &dbt);
if (s3e != SQLITE_OK)
return osstatus_for_s3e(s3e);
require_quiet(argc == 1 || argc == 2, errOut);
hin = (CFNumberRef)CFArrayGetValueAtIndex(args_in, 0);
require_quiet(isNumberOfType(hin, kCFNumberSInt32Type), errOut);
require_quiet(CFNumberGetValue(hin, kCFNumberSInt32Type, &handle_in), errOut);
if (argc > 1) {
data_in = (CFDataRef)CFArrayGetValueAtIndex(args_in, 1);
require_quiet(data_in, errOut);
require_quiet(CFGetTypeID(data_in) == CFDataGetTypeID(), errOut);
} else {
data_in = NULL;
}
secdebug("migrate", "migrate: %d %d", handle_in, data_in);
status = SecServerMigrateKeychain(dbt, handle_in, data_in, &handle_out, &data_out);
require_quiet(args = CFArrayCreateMutable(kCFAllocatorDefault, 2, &kCFTypeArrayCallBacks), errOut);
require_quiet(hout = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &handle_out), errOut);
CFArrayAppendValue(args, hout);
if (data_out)
CFArrayAppendValue(args, data_out);
*args_out = args;
args = NULL;
errOut:
kc_release_dbt(dbt);
CFReleaseSafe(args);
CFReleaseSafe(hout);
CFReleaseSafe(data_out);
return status;
}
OSStatus
_SecServerKeychainBackup(CFArrayRef args_in, CFTypeRef *args_out) {
pthread_once(&kc_dbhandle_init_once, kc_dbhandle_init);
OSStatus status = errSecParam;
CFIndex argc = args_in ? CFArrayGetCount(args_in) : 0;
CFDataRef backup = NULL;
s3dl_db_thread *dbt;
int s3e = s3dl_get_dbt(kc_dbhandle, &dbt);
if (s3e != SQLITE_OK)
return osstatus_for_s3e(s3e);
require_quiet(args_out != NULL, errOut);
if (argc == 0) {
#if USE_KEYSTORE
require_noerr_quiet(status = SecServerExportKeychain(dbt, KEYBAG_DEVICE, backup_keybag_handle, &backup), errOut);
#else
goto errOut;
#endif
}
else if (argc == 1 || argc == 2) {
CFDataRef keybag = (CFDataRef)CFArrayGetValueAtIndex(args_in, 0);
require_quiet(isData(keybag), errOut);
CFDataRef password;
if (argc > 1) {
password = (CFDataRef)CFArrayGetValueAtIndex(args_in, 1);
require_quiet(isData(password), errOut);
} else {
password = NULL;
}
require_noerr_quiet(status = SecServerKeychainBackup(dbt, keybag, password, &backup), errOut);
}
*args_out = backup;
errOut:
kc_release_dbt(dbt);
return status;
}
OSStatus
_SecServerKeychainRestore(CFArrayRef args_in, CFTypeRef *dummy) {
pthread_once(&kc_dbhandle_init_once, kc_dbhandle_init);
OSStatus status = errSecParam;
CFIndex argc = CFArrayGetCount(args_in);
s3dl_db_thread *dbt;
int s3e = s3dl_get_dbt(kc_dbhandle, &dbt);
if (s3e != SQLITE_OK)
return osstatus_for_s3e(s3e);
require_quiet(argc == 2 || argc == 3, errOut);
CFDataRef backup = (CFDataRef)CFArrayGetValueAtIndex(args_in, 0);
require_quiet(isData(backup), errOut);
CFDataRef keybag = (CFDataRef)CFArrayGetValueAtIndex(args_in, 1);
require_quiet(isData(keybag), errOut);
CFDataRef password;
if (argc > 2) {
password = (CFDataRef)CFArrayGetValueAtIndex(args_in, 2);
require_quiet(isData(password), errOut);
} else {
password = NULL;
}
status = SecServerKeychainRestore(dbt, backup, keybag, password);
if (!backup) {
}
if (dummy) {
*dummy = NULL;
}
status = errSecSuccess;
errOut:
kc_release_dbt(dbt);
return status;
}