#include <securityd/SecItemServer.h>
#include <securityd/SecDbItem.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 <utilities/SecIOFormat.h>
#include <utilities/SecCFWrappers.h>
#include <utilities/SecCFError.h>
#include <utilities/der_plist.h>
#include <limits.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/CFArray.h>
#include <CoreFoundation/CFDictionary.h>
#include <CoreFoundation/CFNumber.h>
#include <CoreFoundation/CFString.h>
#include <CoreFoundation/CFURL.h>
#include <CommonCrypto/CommonDigest.h>
#include <CommonCrypto/CommonDigestSPI.h>
#include <CommonCrypto/CommonCryptor.h>
#include <CommonCrypto/CommonCryptorSPI.h>
#include <libkern/OSByteOrder.h>
#include <utilities/debugging.h>
#include <assert.h>
#include <Security/SecInternal.h>
#include "securityd_client.h"
#include "utilities/sqlutils.h"
#include "utilities/SecIOFormat.h"
#include "utilities/SecFileLocations.h"
#include <utilities/iCloudKeychainTrace.h>
#include <AssertMacros.h>
#include <asl.h>
#include <inttypes.h>
#include <utilities/array_size.h>
#include <utilities/SecDb.h>
#include <securityd/SOSCloudCircleServer.h>
#include <notify.h>
#include "OTATrustUtilities.h"
#if USE_KEYSTORE
#include <IOKit/IOKitLib.h>
#include <libaks.h>
#if TARGET_OS_EMBEDDED
#include <MobileKeyBag/MobileKeyBag.h>
#endif
#endif
#if USE_KEYSTORE
#if TARGET_OS_MAC && !TARGET_OS_EMBEDDED
static keybag_handle_t g_keychain_keybag = session_keybag_handle;
#else
static keybag_handle_t g_keychain_keybag = device_keybag_handle;
#endif
#else
static int32_t g_keychain_keybag = 0;
#endif
void SecItemServerSetKeychainKeybag(int32_t keybag)
{
g_keychain_keybag=keybag;
}
void SecItemServerResetKeychainKeybag(void)
{
#if USE_KEYSTORE
#if TARGET_OS_MAC && !TARGET_OS_EMBEDDED
g_keychain_keybag = session_keybag_handle;
#else
g_keychain_keybag = device_keybag_handle;
#endif
#else
g_keychain_keybag = 0;
#endif
}
#define KEYBAG_NONE (-1)
#define KEYBAG_DEVICE (g_keychain_keybag)
static const char *g_keychain_changed_notification = kSecServerKeychainChangedNotification;
void SecItemServerSetKeychainChangedNotification(const char *notification_name)
{
g_keychain_changed_notification = notification_name;
}
#define CERTIFICATE_DATA_COLUMN_LABEL "certdata"
#define CURRENT_DB_VERSION 6
#define CLOSE_DB 0
enum SecItemFilter {
kSecNoItemFilter,
kSecSysBoundItemFilter,
kSecBackupableItemFilter,
};
static CF_RETURNS_RETAINED CFDictionaryRef SecServerExportKeychainPlist(SecDbConnectionRef dbt,
keybag_handle_t src_keybag, keybag_handle_t dest_keybag,
enum SecItemFilter filter, CFErrorRef *error);
static bool SecServerImportKeychainInPlist(SecDbConnectionRef dbt,
keybag_handle_t src_keybag, keybag_handle_t dest_keybag,
CFDictionaryRef keychain, enum SecItemFilter filter, CFErrorRef *error);
#if USE_KEYSTORE
static bool hwaes_key_available(void)
{
keybag_handle_t handle = bad_keybag_handle;
keybag_handle_t special_handle = bad_keybag_handle;
#if TARGET_OS_MAC && !TARGET_OS_EMBEDDED
special_handle = session_keybag_handle;
#elif TARGET_OS_EMBEDDED
special_handle = device_keybag_handle;
#endif
kern_return_t kr = aks_get_system(special_handle, &handle);
if (kr != kIOReturnSuccess) {
#if TARGET_OS_EMBEDDED
int kb_state = MKBGetDeviceLockState(NULL);
asl_log(NULL, NULL, ASL_LEVEL_INFO, "AppleKeyStore lock state: %d", kb_state);
#endif
}
return true;
}
#else
static bool hwaes_key_available(void)
{
return false;
}
#endif
#define __FLAGS(ARG, ...) SECDBFLAGS(__VA_ARGS__)
#define SECDBFLAGS(ARG, ...) __FLAGS_##ARG | __FLAGS(__VA_ARGS__)
#define SecDbFlags(P,L,I,S,A,D,R,C,H,B,Z,E,N) (__FLAGS_##P|__FLAGS_##L|__FLAGS_##I|__FLAGS_##S|__FLAGS_##A|__FLAGS_##D|__FLAGS_##R|__FLAGS_##C|__FLAGS_##H|__FLAGS_##B|__FLAGS_##Z|__FLAGS_##E|__FLAGS_##N)
#define __FLAGS_ 0
#define __FLAGS_P kSecDbPrimaryKeyFlag
#define __FLAGS_L kSecDbInFlag
#define __FLAGS_I kSecDbIndexFlag
#define __FLAGS_S kSecDbSHA1ValueInFlag
#define __FLAGS_A kSecDbReturnAttrFlag
#define __FLAGS_D kSecDbReturnDataFlag
#define __FLAGS_R kSecDbReturnRefFlag
#define __FLAGS_C kSecDbInCryptoDataFlag
#define __FLAGS_H kSecDbInHashFlag
#define __FLAGS_B kSecDbInBackupFlag
#define __FLAGS_Z kSecDbDefault0Flag
#define __FLAGS_E kSecDbDefaultEmptyFlag
#define __FLAGS_N kSecDbNotNullFlag
SECDB_ATTR(v6rowid, "rowid", RowId, SecDbFlags( ,L, , , , ,R, , ,B, , , ));
SECDB_ATTR(v6cdat, "cdat", CreationDate, SecDbFlags( ,L, , ,A, , ,C,H, , , , ));
SECDB_ATTR(v6mdat, "mdat",ModificationDate,SecDbFlags( ,L, , ,A, , ,C,H, , , , ));
SECDB_ATTR(v6labl, "labl", Blob, SecDbFlags( ,L, ,S,A, , ,C,H, , , , ));
SECDB_ATTR(v6data, "data", EncryptedData, SecDbFlags( ,L, , , , , , , ,B, , , ));
SECDB_ATTR(v6agrp, "agrp", String, SecDbFlags(P,L, , ,A, , ,C,H, , , , ));
SECDB_ATTR(v6pdmn, "pdmn", Access, SecDbFlags( ,L, , ,A, , ,C,H, , , , ));
SECDB_ATTR(v6sync, "sync", Sync, SecDbFlags(P,L,I, ,A, , ,C,H, ,Z, ,N));
SECDB_ATTR(v6tomb, "tomb", Tomb, SecDbFlags( ,L, , , , , ,C,H, ,Z, ,N));
SECDB_ATTR(v6sha1, "sha1", SHA1, SecDbFlags( ,L,I, ,A, ,R, , , , , , ));
SECDB_ATTR(v6v_Data, "v_Data", Data, SecDbFlags( , , , , ,D, ,C,H, , , , ));
SECDB_ATTR(v6v_pk, "v_pk", PrimaryKey, SecDbFlags( , , , , , , , , , , , , ));
SECDB_ATTR(v6crtr, "crtr", Number, SecDbFlags( ,L, , ,A, , ,C,H, , , , ));
SECDB_ATTR(v6alis, "alis", Blob, SecDbFlags( ,L, ,S,A, , ,C,H, , , , ));
SECDB_ATTR(v6desc, "desc", Blob, SecDbFlags( ,L, ,S,A, , ,C,H, , , , ));
SECDB_ATTR(v6icmt, "icmt", Blob, SecDbFlags( ,L, ,S,A, , ,C,H, , , , ));
SECDB_ATTR(v6type, "type", Number, SecDbFlags( ,L, , ,A, , ,C,H, , , , ));
SECDB_ATTR(v6invi, "invi", Number, SecDbFlags( ,L, , ,A, , ,C,H, , , , ));
SECDB_ATTR(v6nega, "nega", Number, SecDbFlags( ,L, , ,A, , ,C,H, , , , ));
SECDB_ATTR(v6cusi, "cusi", Number, SecDbFlags( ,L, , ,A, , ,C,H, , , , ));
SECDB_ATTR(v6prot, "prot", Blob, SecDbFlags( ,L, ,S,A, , ,C,H, , , , ));
SECDB_ATTR(v6scrp, "scrp", Number, SecDbFlags( ,L, , ,A, , ,C,H, , , , ));
SECDB_ATTR(v6acct, "acct", Blob, SecDbFlags(P,L, ,S,A, , ,C,H, , ,E,N));
SECDB_ATTR(v6svce, "svce", Blob, SecDbFlags(P,L, ,S,A, , ,C,H, , ,E,N));
SECDB_ATTR(v6gena, "gena", Blob, SecDbFlags( ,L, ,S,A, , ,C,H, , , , ));
SECDB_ATTR(v6sdmn, "sdmn", Blob, SecDbFlags(P,L, ,S,A, , ,C,H, , ,E,N));
SECDB_ATTR(v6srvr, "srvr", Blob, SecDbFlags(P,L, ,S,A, , ,C,H, , ,E,N));
SECDB_ATTR(v6ptcl, "ptcl", Number, SecDbFlags(P,L, , ,A, , ,C,H, ,Z, ,N));
SECDB_ATTR(v6atyp, "atyp", Blob, SecDbFlags(P,L, ,S,A, , ,C,H, , ,E,N));
SECDB_ATTR(v6port, "port", Number, SecDbFlags(P,L, , ,A, , ,C,H, ,Z, ,N));
SECDB_ATTR(v6path, "path", Blob, SecDbFlags(P,L, ,S,A, , ,C,H, , ,E,N));
SECDB_ATTR(v6ctyp, "ctyp", Number, SecDbFlags(P,L, , ,A, , ,C,H, ,Z, ,N));
SECDB_ATTR(v6cenc, "cenc", Number, SecDbFlags( ,L, , ,A, , ,C,H, , , , ));
SECDB_ATTR(v6subj, "subj", Data, SecDbFlags( ,L,I,S,A, , ,C,H, , , , ));
SECDB_ATTR(v6issr, "issr", Data, SecDbFlags(P,L, ,S,A, , ,C,H, , ,E,N));
SECDB_ATTR(v6slnr, "slnr", Data, SecDbFlags(P,L, ,S,A, , ,C,H, , ,E,N));
SECDB_ATTR(v6skid, "skid", Data, SecDbFlags( ,L,I,S,A, , ,C,H, , , , ));
SECDB_ATTR(v6pkhh, "pkhh", Data, SecDbFlags( ,L,I, ,A, , ,C,H, , , , ));
SECDB_ATTR(v6certalis, "alis", Blob, SecDbFlags( ,L,I,S,A, , ,C,H, , , , ));
SECDB_ATTR(v6kcls, "kcls", Number, SecDbFlags(P,L,I,S,A, , ,C,H, ,Z, ,N));
SECDB_ATTR(v6perm, "perm", Number, SecDbFlags( ,L, , ,A, , ,C,H, , , , ));
SECDB_ATTR(v6priv, "priv", Number, SecDbFlags( ,L, , ,A, , ,C,H, , , , ));
SECDB_ATTR(v6modi, "modi", Number, SecDbFlags( ,L, , ,A, , ,C,H, , , , ));
SECDB_ATTR(v6klbl, "klbl", Data, SecDbFlags(P,L,I, ,A, , ,C,H, , ,E,N));
SECDB_ATTR(v6atag, "atag", Blob, SecDbFlags(P,L, ,S,A, , ,C,H, , ,E,N));
SECDB_ATTR(v6bsiz, "bsiz", Number, SecDbFlags(P,L, , ,A, , ,C,H, ,Z, ,N));
SECDB_ATTR(v6esiz, "esiz", Number, SecDbFlags(P,L, , ,A, , ,C,H, ,Z, ,N));
SECDB_ATTR(v6sdat, "sdat", Date, SecDbFlags(P,L, , ,A, , ,C,H, ,Z, ,N));
SECDB_ATTR(v6edat, "edat", Date, SecDbFlags(P,L, , ,A, , ,C,H, ,Z, ,N));
SECDB_ATTR(v6sens, "sens", Number, SecDbFlags( ,L, , ,A, , ,C,H, , , , ));
SECDB_ATTR(v6asen, "asen", Number, SecDbFlags( ,L, , ,A, , ,C,H, , , , ));
SECDB_ATTR(v6extr, "extr", Number, SecDbFlags( ,L, , ,A, , ,C,H, , , , ));
SECDB_ATTR(v6next, "next", Number, SecDbFlags( ,L, , ,A, , ,C,H, , , , ));
SECDB_ATTR(v6encr, "encr", Number, SecDbFlags( ,L,I, ,A, , ,C,H, , , , ));
SECDB_ATTR(v6decr, "decr", Number, SecDbFlags( ,L,I, ,A, , ,C,H, , , , ));
SECDB_ATTR(v6drve, "drve", Number, SecDbFlags( ,L,I, ,A, , ,C,H, , , , ));
SECDB_ATTR(v6sign, "sign", Number, SecDbFlags( ,L,I, ,A, , ,C,H, , , , ));
SECDB_ATTR(v6vrfy, "vrfy", Number, SecDbFlags( ,L,I, ,A, , ,C,H, , , , ));
SECDB_ATTR(v6snrc, "snrc", Number, SecDbFlags( ,L, , ,A, , ,C,H, , , , ));
SECDB_ATTR(v6vyrc, "vyrc", Number, SecDbFlags( ,L, , ,A, , ,C,H, , , , ));
SECDB_ATTR(v6wrap, "wrap", Number, SecDbFlags( ,L,I, ,A, , ,C,H, , , , ));
SECDB_ATTR(v6unwp, "unwp", Number, SecDbFlags( ,L,I, ,A, , ,C,H, , , , ));
SECDB_ATTR(v6keytype, "type", Number, SecDbFlags(P,L, , ,A, , ,C,H, ,Z, ,N));
SECDB_ATTR(v6keycrtr, "crtr", Number, SecDbFlags(P,L, , ,A, , ,C,H, ,Z, ,N));
static const SecDbClass genp_class = {
.name = CFSTR("genp"),
.attrs = {
&v6rowid,
&v6cdat,
&v6mdat,
&v6desc,
&v6icmt,
&v6crtr,
&v6type,
&v6scrp,
&v6labl,
&v6alis,
&v6invi,
&v6nega,
&v6cusi,
&v6prot,
&v6acct,
&v6svce,
&v6gena,
&v6data,
&v6agrp,
&v6pdmn,
&v6sync,
&v6tomb,
&v6sha1,
&v6v_Data,
&v6v_pk,
NULL
},
};
static const SecDbClass inet_class = {
.name = CFSTR("inet"),
.attrs = {
&v6rowid,
&v6cdat,
&v6mdat,
&v6desc,
&v6icmt,
&v6crtr,
&v6type,
&v6scrp,
&v6labl,
&v6alis,
&v6invi,
&v6nega,
&v6cusi,
&v6prot,
&v6acct,
&v6sdmn,
&v6srvr,
&v6ptcl,
&v6atyp,
&v6port,
&v6path,
&v6data,
&v6agrp,
&v6pdmn,
&v6sync,
&v6tomb,
&v6sha1,
&v6v_Data,
&v6v_pk,
0
},
};
static const SecDbClass cert_class = {
.name = CFSTR("cert"),
.attrs = {
&v6rowid,
&v6cdat,
&v6mdat,
&v6ctyp,
&v6cenc,
&v6labl,
&v6certalis,
&v6subj,
&v6issr,
&v6slnr,
&v6skid,
&v6pkhh,
&v6data,
&v6agrp,
&v6pdmn,
&v6sync,
&v6tomb,
&v6sha1,
&v6v_Data,
&v6v_pk,
0
},
};
static const SecDbClass keys_class = {
.name = CFSTR("keys"),
.attrs = {
&v6rowid,
&v6cdat,
&v6mdat,
&v6kcls,
&v6labl,
&v6alis,
&v6perm,
&v6priv,
&v6modi,
&v6klbl,
&v6atag,
&v6keycrtr,
&v6keytype,
&v6bsiz,
&v6esiz,
&v6sdat,
&v6edat,
&v6sens,
&v6asen,
&v6extr,
&v6next,
&v6encr,
&v6decr,
&v6drve,
&v6sign,
&v6vrfy,
&v6snrc,
&v6vyrc,
&v6wrap,
&v6unwp,
&v6data,
&v6agrp,
&v6pdmn,
&v6sync,
&v6tomb,
&v6sha1,
&v6v_Data,
&v6v_pk,
0
}
};
static const SecDbClass identity_class = {
.name = CFSTR("idnt"),
.attrs = {
0
},
};
static const SecDbAttr *SecDbAttrWithKey(const SecDbClass *c,
CFTypeRef key,
CFErrorRef *error) {
if (c == &identity_class) {
const SecDbAttr *desc;
if (!(desc = SecDbAttrWithKey(&cert_class, key, 0)))
desc = SecDbAttrWithKey(&keys_class, key, error);
return desc;
}
if (isString(key)) {
SecDbForEachAttr(c, a) {
if (CFEqual(a->name, key))
return a;
}
}
SecError(errSecNoSuchAttr, error, CFSTR("attribute %@ not found in class %@"), key, c->name);
return NULL;
}
static bool kc_transaction(SecDbConnectionRef dbt, CFErrorRef *error, bool(^perform)()) {
__block bool ok = true;
return ok && SecDbTransaction(dbt, kSecDbExclusiveTransactionType, error, ^(bool *commit) {
ok = *commit = perform();
});
}
static CFStringRef SecDbGetKindSQL(SecDbAttrKind kind) {
switch (kind) {
case kSecDbBlobAttr:
case kSecDbDataAttr:
case kSecDbSHA1Attr:
case kSecDbPrimaryKeyAttr:
case kSecDbEncryptedDataAttr:
return CFSTR("BLOB");
case kSecDbAccessAttr:
case kSecDbStringAttr:
return CFSTR("TEXT");
case kSecDbNumberAttr:
case kSecDbSyncAttr:
case kSecDbTombAttr:
return CFSTR("INTEGER");
case kSecDbDateAttr:
case kSecDbCreationDateAttr:
case kSecDbModificationDateAttr:
return CFSTR("REAL");
case kSecDbRowIdAttr:
return CFSTR("INTEGER PRIMARY KEY AUTOINCREMENT");
}
}
static void SecDbAppendUnqiue(CFMutableStringRef sql, CFStringRef value, bool *haveUnique) {
assert(haveUnique);
if (!*haveUnique)
CFStringAppend(sql, CFSTR("UNIQUE("));
SecDbAppendElement(sql, value, haveUnique);
}
static void SecDbAppendCreateTableWithClass(CFMutableStringRef sql, const SecDbClass *c) {
CFStringAppendFormat(sql, 0, CFSTR("CREATE TABLE %@("), c->name);
SecDbForEachAttrWithMask(c,desc,kSecDbInFlag) {
CFStringAppendFormat(sql, 0, CFSTR("%@ %@"), desc->name, SecDbGetKindSQL(desc->kind));
if (desc->flags & kSecDbNotNullFlag)
CFStringAppend(sql, CFSTR(" NOT NULL"));
if (desc->flags & kSecDbDefault0Flag)
CFStringAppend(sql, CFSTR(" DEFAULT 0"));
if (desc->flags & kSecDbDefaultEmptyFlag)
CFStringAppend(sql, CFSTR(" DEFAULT ''"));
CFStringAppend(sql, CFSTR(","));
}
bool haveUnique = false;
SecDbForEachAttrWithMask(c,desc,kSecDbPrimaryKeyFlag | kSecDbInFlag) {
SecDbAppendUnqiue(sql, desc->name, &haveUnique);
}
if (haveUnique)
CFStringAppend(sql, CFSTR(")"));
CFStringAppend(sql, CFSTR(");"));
}
static const char * const s3dl_upgrade_sql[] = {
"",
"CREATE INDEX igsha ON genp(sha1);"
"CREATE INDEX iisha ON inet(sha1);"
"CREATE INDEX icsha ON cert(sha1);"
"CREATE INDEX iksha ON keys(sha1);"
"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;"
"DROP TABLE tversion;",
"",
"",
"",
"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,cdat,mdat,ctyp,cenc,labl,alis,subj,issr,slnr,skid,pkhh,data,agrp,pdmn) SELECT rowid,cdat,mdat,ctyp,cenc,labl,alis,subj,issr,slnr,skid,pkhh,data,agrp,pdmn from ocert;"
"INSERT INTO keys (rowid,cdat,mdat,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,cdat,mdat,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;"
"CREATE INDEX igsha ON genp(sha1);"
"CREATE INDEX iisha ON inet(sha1);"
"CREATE INDEX icsha ON cert(sha1);"
"CREATE INDEX iksha ON keys(sha1);",
};
struct sql_stages {
int pre;
int main;
int post;
bool init_pdmn; };
static struct sql_stages s3dl_upgrade_script[] = {
{ -1, 0, 1, false },
{},
{},
{},
{},
{ 3, 0, 7, true },
};
static bool sql_run_script(SecDbConnectionRef dbt, int number, CFErrorRef *error)
{
if (number < 0)
return true;
if ((size_t)number >= array_size(s3dl_upgrade_sql))
return SecDbError(SQLITE_CORRUPT, error, CFSTR("script %d exceeds maximum %d"),
number, (int)(array_size(s3dl_upgrade_sql)));
__block bool ok = true;
if (number == 0) {
CFMutableStringRef sql = CFStringCreateMutable(0, 0);
SecDbAppendCreateTableWithClass(sql, &genp_class);
SecDbAppendCreateTableWithClass(sql, &inet_class);
SecDbAppendCreateTableWithClass(sql, &cert_class);
SecDbAppendCreateTableWithClass(sql, &keys_class);
CFStringAppend(sql, CFSTR("CREATE TABLE tversion(version INTEGER);INSERT INTO tversion(version) VALUES(6);"));
CFStringPerformWithCString(sql, ^(const char *sql_string) {
ok = SecDbErrorWithDb(sqlite3_exec(SecDbHandle(dbt), sql_string, NULL, NULL, NULL),
SecDbHandle(dbt), error, CFSTR("sqlite3_exec: %s"), sql_string);
});
CFReleaseSafe(sql);
} else {
ok = SecDbErrorWithDb(sqlite3_exec(SecDbHandle(dbt), s3dl_upgrade_sql[number], NULL, NULL, NULL),
SecDbHandle(dbt), error, CFSTR("sqlite3_exec: %s"), s3dl_upgrade_sql[number]);
}
return ok;
}
static bool s3dl_dbt_get_version(SecDbConnectionRef dbt, int *version, CFErrorRef *error)
{
CFStringRef sql = CFSTR("SELECT version FROM tversion LIMIT 1");
return SecDbWithSQL(dbt, sql, error, ^(sqlite3_stmt *stmt) {
__block bool found_version = false;
bool step_ok = SecDbForEach(stmt, error, ^(int row_index __unused) {
if (!found_version) {
*version = sqlite3_column_int(stmt, 0);
found_version = true;
}
return found_version;
});
if (!found_version) {
step_ok = SecDbError(SQLITE_CORRUPT, error, CFSTR("Failed to read version: database corrupt"));
secwarning("SELECT version step: %@", error ? *error : NULL);
}
return step_ok;
});
}
static bool s3dl_dbt_upgrade_from_version(SecDbConnectionRef dbt, int version, CFErrorRef *error)
{
__block bool ok = true;
if (version == CURRENT_DB_VERSION)
return ok;
if (ok && version < 6) {
ok = (SecDbExec(dbt, CFSTR("PRAGMA auto_vacuum = FULL"), error) &&
SecDbExec(dbt, CFSTR("PRAGMA journal_mode = WAL"), error));
}
if (ok) { ok = SecDbTransaction(dbt, kSecDbExclusiveTransactionType, error, ^(bool *commit) {
int cur_version = version;
s3dl_dbt_get_version(dbt, &cur_version, NULL);
if (ok && (cur_version < 0 ||
(size_t)cur_version >= array_size(s3dl_upgrade_script))) {
ok = SecDbError(SQLITE_CORRUPT, error, CFSTR("no upgrade script for version: %d"), cur_version);
secerror("no upgrade script for version %d", cur_version);
}
struct sql_stages *script;
if (ok) {
script = &s3dl_upgrade_script[cur_version];
if (script->pre == 0)
ok = SecDbError(SQLITE_CORRUPT, error, CFSTR("unsupported db version %d"), cur_version);
}
if (ok)
ok = sql_run_script(dbt, script->pre, error);
if (ok)
ok = sql_run_script(dbt, script->main, error);
if (ok)
ok = sql_run_script(dbt, script->post, error);
if (ok && script->init_pdmn) {
CFErrorRef localError = NULL;
CFDictionaryRef backup = SecServerExportKeychainPlist(dbt,
KEYBAG_DEVICE, KEYBAG_NONE, kSecNoItemFilter, &localError);
if (backup) {
if (localError) {
secerror("Ignoring export error: %@ during upgrade export", localError);
CFReleaseNull(localError);
}
ok = SecServerImportKeychainInPlist(dbt, KEYBAG_NONE,
KEYBAG_DEVICE, backup, kSecNoItemFilter, &localError);
CFRelease(backup);
} else {
ok = false;
if (localError && CFErrorGetCode(localError) == errSecInteractionNotAllowed) {
SecError(errSecUpgradePending, error,
CFSTR("unable to complete upgrade due to device lock state"));
secerror("unable to complete upgrade due to device lock state");
} else {
secerror("unable to complete upgrade for unknown reason, marking DB as corrupt: %@", localError);
SecDbCorrupt(dbt);
}
}
if (localError) {
if (error && !*error)
*error = localError;
else
CFRelease(localError);
}
} else if (!ok) {
secerror("unable to complete upgrade scripts, marking DB as corrupt: %@", error ? *error : NULL);
SecDbCorrupt(dbt);
}
*commit = ok;
}); } else {
secerror("unable to complete upgrade scripts, marking DB as corrupt: %@", error ? *error : NULL);
SecDbCorrupt(dbt);
}
return ok;
}
static bool s3dl_dbt_upgrade(SecDbConnectionRef dbt, CFErrorRef *error)
{
int version = 0; s3dl_dbt_get_version(dbt, &version, NULL);
return s3dl_dbt_upgrade_from_version(dbt, version, error);
}
const uint32_t v0KeyWrapOverHead = 8;
static bool 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, CFErrorRef *error) {
#if USE_KEYSTORE
kern_return_t kernResult = kIOReturnBadArgument;
if (selector == kAppleKeyStoreKeyWrap) {
kernResult = aks_wrap_key(source, textLength, keyclass, keybag, dest, (int*)dest_len);
} else if (selector == kAppleKeyStoreKeyUnwrap) {
kernResult = aks_unwrap_key(source, textLength, keyclass, keybag, dest, (int*)dest_len);
}
if (kernResult != KERN_SUCCESS) {
if ((kernResult == kIOReturnNotPermitted) || (kernResult == kIOReturnNotPrivileged)) {
return SecError(errSecInteractionNotAllowed, error, CFSTR("ks_crypt: %x failed to %s item (class %"PRId32", bag: %"PRId32") Access to item attempted while keychain is locked."),
kernResult, (selector == kAppleKeyStoreKeyWrap ? "wrap" : "unwrap"), keyclass, keybag);
} else if (kernResult == kIOReturnError) {
return SecError(errSecDecode, error, CFSTR("ks_crypt: %x failed to %s item (class %"PRId32", bag: %"PRId32") Item can't be decrypted on this device, ever, so drop the item."),
kernResult, (selector == kAppleKeyStoreKeyWrap ? "wrap" : "unwrap"), keyclass, keybag);
} else {
return SecError(errSecNotAvailable, error, CFSTR("ks_crypt: %x failed to %s item (class %"PRId32", bag: %"PRId32")"),
kernResult, (selector == kAppleKeyStoreKeyWrap ? "wrap" : "unwrap"), keyclass, keybag);
}
}
return true;
#else
if (selector == kAppleKeyStoreKeyWrap) {
if (*dest_len >= textLength + 8) {
memcpy(dest, source, textLength);
memset(dest + textLength, 8, 8);
*dest_len = textLength + 8;
} else
return SecError(errSecNotAvailable, error, CFSTR("ks_crypt: failed to wrap item (class %"PRId32")"), keyclass);
} else if (selector == kAppleKeyStoreKeyUnwrap) {
if (*dest_len + 8 >= textLength) {
memcpy(dest, source, textLength - 8);
*dest_len = textLength - 8;
} else
return SecError(errSecNotAvailable, error, CFSTR("ks_crypt: failed to unwrap item (class %"PRId32")"), keyclass);
}
return true;
#endif
}
CFDataRef kc_plist_copy_der(CFPropertyListRef plist, CFErrorRef *error) {
size_t len = der_sizeof_plist(plist, error);
CFMutableDataRef encoded = CFDataCreateMutable(0, len);
CFDataSetLength(encoded, len);
uint8_t *der_end = CFDataGetMutableBytePtr(encoded);
const uint8_t *der = der_end;
der_end += len;
der_end = der_encode_plist(plist, error, der, der_end);
if (!der_end) {
CFReleaseNull(encoded);
} else {
assert(!der_end || der_end == der);
}
return encoded;
}
static CFDataRef kc_copy_digest(const struct ccdigest_info *di, size_t len,
const void *data, CFErrorRef *error) {
CFMutableDataRef digest = CFDataCreateMutable(0, di->output_size);
CFDataSetLength(digest, di->output_size);
ccdigest(di, len, data, CFDataGetMutableBytePtr(digest));
return digest;
}
CFDataRef kc_copy_sha1(size_t len, const void *data, CFErrorRef *error) {
return kc_copy_digest(ccsha1_di(), len, data, error);
}
CFDataRef kc_copy_plist_sha1(CFPropertyListRef plist, CFErrorRef *error) {
CFDataRef der = kc_plist_copy_der(plist, error);
CFDataRef digest = NULL;
if (der) {
digest = kc_copy_sha1(CFDataGetLength(der), CFDataGetBytePtr(der), error);
CFRelease(der);
}
return digest;
}
bool ks_encrypt_data(keybag_handle_t keybag,
keyclass_t keyclass, CFDataRef plainText, CFDataRef *pBlob, CFErrorRef *error) {
CFMutableDataRef blob = NULL;
bool ok = true;
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;
if (!plainText || CFGetTypeID(plainText) != CFDataGetTypeID()
|| keyclass == 0) {
ok = SecError(errSecParam, error, CFSTR("ks_encrypt_data: invalid plain text"));
goto out;
}
size_t ptLen = CFDataGetLength(plainText);
size_t ctLen = ptLen;
size_t tagLen = 16;
uint32_t version = 3;
if (SecRandomCopyBytes(kSecRandomDefault, bulkKeySize, bulkKey)) {
ok = SecError(errSecAllocate, error, CFSTR("ks_encrypt_data: SecRandomCopyBytes failed"));
goto out;
}
require_quiet(ok = ks_crypt(kAppleKeyStoreKeyWrap, keybag, keyclass,
bulkKeySize, bulkKey, bulkKeyWrapped,
&bulkKeyWrappedSize, error), 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) {
ok = SecError(errSecInternal, error, CFSTR("ks_encrypt_data: CCCryptorGCM failed: %d"), ccerr);
goto out;
}
if (tagLen != 16) {
ok = SecError(errSecInternal, error, CFSTR("ks_encrypt_data: CCCryptorGCM expected: 16 got: %ld byte tag"), tagLen);
goto out;
}
out:
memset(bulkKey, 0, sizeof(bulkKey));
if (!ok) {
CFReleaseSafe(blob);
} else {
*pBlob = blob;
}
return ok;
}
bool ks_decrypt_data(keybag_handle_t keybag,
keyclass_t *pkeyclass, CFDataRef blob, CFDataRef *pPlainText,
uint32_t *version_p, CFErrorRef *error) {
const uint32_t bulkKeySize = 32;
uint8_t bulkKey[bulkKeySize];
size_t bulkKeyCapacity = sizeof(bulkKey);
bool ok = true;
CFMutableDataRef plainText = NULL;
#if USE_KEYSTORE
#if TARGET_OS_IPHONE
check(keybag >= 0);
#else
check((keybag >= 0) || (keybag == session_keybag_handle));
#endif
#endif
if (!blob) {
ok = SecError(errSecParam, error, CFSTR("ks_decrypt_data: invalid blob"));
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) {
ok = SecError(errSecDecode, error, CFSTR("ks_decrypt_data: Check for underflow"));
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:
case 3:
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:
ok = SecError(errSecDecode, error, CFSTR("ks_decrypt_data: invalid version %d"), version);
goto out;
}
require(blobLen - minimum_blob_len - tagLen >= wrapped_key_size, out);
ctLen -= wrapped_key_size;
if (version < 2 && (ctLen & 0xF) != 0) {
ok = SecError(errSecDecode, error, CFSTR("ks_decrypt_data: invalid version"));
goto out;
}
require_quiet(ok = ks_crypt(kAppleKeyStoreKeyUnwrap, keybag,
keyclass, wrapped_key_size, cursor, bulkKey, &bulkKeyCapacity, error), out);
cursor += wrapped_key_size;
plainText = CFDataCreateMutable(NULL, ctLen);
if (!plainText) {
ok = SecError(errSecDecode, error, CFSTR("ks_decrypt_data: failed to allocate data for plain text"));
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) {
ok = SecError(errSecDecode, error, CFSTR("ks_decrypt_data: CCCryptorGCM failed: %d"), ccerr);
goto out;
}
if (tagLen != 16) {
ok = SecError(errSecInternal, error, CFSTR("ks_decrypt_data: CCCryptorGCM expected: 16 got: %ld byte tag"), tagLen);
goto out;
}
cursor += ctLen;
if (memcmp(tag, cursor, tagLen)) {
ok = SecError(errSecDecode, error, CFSTR("ks_decrypt_data: CCCryptorGCM computed tag not same as tag in blob"));
goto out;
}
} else {
size_t ptLen;
ccerr = CCCrypt(kCCDecrypt, kCCAlgorithmAES128, kCCOptionPKCS7Padding,
bulkKey, bulkKeySize, NULL, cursor, ctLen,
CFDataGetMutableBytePtr(plainText), ctLen, &ptLen);
if (ccerr) {
ok = SecError(errSecDecode, error, CFSTR("ks_decrypt_data: CCCrypt failed: %d"), ccerr);
goto out;
}
CFDataSetLength(plainText, ptLen);
}
if (version_p) *version_p = version;
out:
memset(bulkKey, 0, bulkKeySize);
if (!ok) {
CFReleaseSafe(plainText);
} else {
*pPlainText = plainText;
}
return ok;
}
static bool use_hwaes() {
static bool use_hwaes;
static dispatch_once_t check_once;
dispatch_once(&check_once, ^{
use_hwaes = hwaes_key_available();
if (use_hwaes) {
asl_log(NULL, NULL, ASL_LEVEL_INFO, "using hwaes key");
} else {
asl_log(NULL, NULL, ASL_LEVEL_ERR, "unable to access hwaes key");
}
});
return use_hwaes;
}
#define QUERY_KEY_LIMIT_BASE (128)
#ifdef NO_SERVER
#define QUERY_KEY_LIMIT (31 + QUERY_KEY_LIMIT_BASE)
#else
#define QUERY_KEY_LIMIT QUERY_KEY_LIMIT_BASE
#endif
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)) {
SecError(errSecParam, &q->q_error, CFSTR("accessible attribute %@ not a string"), value);
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 {
SecError(errSecParam, &q->q_error, CFSTR("accessible attribute %@ unknown"), value);
return;
}
}
static const SecDbClass *kc_class_with_name(CFStringRef name) {
if (isString(name)) {
#if 0
static const void *kc_db_classes[] = {
&genp_class,
&inet_class,
&cert_class,
&keys_class,
&identity_class
};
#endif
if (CFEqual(name, kSecClassGenericPassword))
return &genp_class;
else if (CFEqual(name, kSecClassInternetPassword))
return &inet_class;
else if (CFEqual(name, kSecClassCertificate))
return &cert_class;
else if (CFEqual(name, kSecClassKey))
return &keys_class;
else if (CFEqual(name, kSecClassIdentity))
return &identity_class;
}
return NULL;
}
static void query_add_attribute_with_desc(const SecDbAttr *desc, const void *value, Query *q)
{
if (CFEqual(desc->name, kSecAttrSynchronizable)) {
q->q_sync = true;
if (CFEqual(value, kSecAttrSynchronizableAny))
return;
}
CFTypeRef attr = NULL;
switch (desc->kind) {
case kSecDbDataAttr:
attr = copyData(value);
break;
case kSecDbBlobAttr:
attr = copyBlob(value);
break;
case kSecDbDateAttr:
case kSecDbCreationDateAttr:
case kSecDbModificationDateAttr:
attr = copyDate(value);
break;
case kSecDbNumberAttr:
case kSecDbSyncAttr:
case kSecDbTombAttr:
attr = copyNumber(value);
break;
case kSecDbAccessAttr:
case kSecDbStringAttr:
attr = copyString(value);
break;
case kSecDbSHA1Attr:
attr = copySHA1(value);
break;
case kSecDbRowIdAttr:
case kSecDbPrimaryKeyAttr:
case kSecDbEncryptedDataAttr:
break;
}
if (!attr) {
SecError(errSecItemInvalidValue, &q->q_error, CFSTR("attribute %@: value: %@ failed to convert"), desc->name, value);
return;
}
if (q->q_item && desc->kind != kSecDbSHA1Attr) {
CFDictionarySetValue(q->q_item, desc->name, attr);
}
if (CFEqual(desc->name, kSecAttrAccessible)) {
query_parse_keyclass(attr, q);
}
if (desc->flags & kSecDbSHA1ValueInFlag) {
CFDataRef data = copyData(attr);
CFRelease(attr);
if (!data) {
SecError(errSecInternal, &q->q_error, CFSTR("failed to get attribute %@ data"), desc->name);
return;
}
CFMutableDataRef digest = CFDataCreateMutable(0, CC_SHA1_DIGEST_LENGTH);
CFDataSetLength(digest, CC_SHA1_DIGEST_LENGTH);
assert((unsigned long)CFDataGetLength(data)<UINT32_MAX);
CCDigest(kCCDigestSHA1, 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_add_attribute(const void *key, const void *value, Query *q)
{
const SecDbAttr *desc = SecDbAttrWithKey(q->q_class, key, &q->q_error);
if (desc)
query_add_attribute_with_desc(desc, value, q);
}
static void query_set_attribute_with_desc(const SecDbAttr *desc, const void *value, Query *q) {
if (CFDictionaryContainsKey(q->q_item, desc->name)) {
CFIndex ix;
for (ix = 0; ix < q->q_attr_end; ++ix) {
if (CFEqual(desc->name, 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, desc->name);
break;
}
}
}
query_add_attribute_with_desc(desc, 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))
SecError(errSecItemInvalidValue, &q->q_error, CFSTR("failed to convert match limit %@ to CFIndex"), value);
} else if (CFEqual(kSecMatchLimitAll, value)) {
q->q_limit = kSecMatchUnlimited;
} else if (CFEqual(kSecMatchLimitOne, value)) {
q->q_limit = 1;
} else {
SecError(errSecItemInvalidValue, &q->q_error, CFSTR("unsupported match limit %@"), value);
}
} else if (CFEqual(kSecMatchIssuers, key) &&
(CFGetTypeID(value) == CFArrayGetTypeID()))
{
CFMutableArrayRef canonical_issuers = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
if (canonical_issuers) {
CFIndex i, count = CFArrayGetCount(value);
for (i = 0; i < count; i++) {
CFTypeRef issuer_data = CFArrayGetValueAtIndex(value, i);
CFDataRef issuer_canonical = NULL;
if (CFDataGetTypeID() == CFGetTypeID(issuer_data))
issuer_canonical = SecDistinguishedNameCopyNormalizedContent((CFDataRef)issuer_data);
if (issuer_canonical) {
CFArrayAppendValue(canonical_issuers, issuer_canonical);
CFRelease(issuer_canonical);
}
}
if (CFArrayGetCount(canonical_issuers) > 0) {
q->q_match_issuer = canonical_issuers;
} else
CFRelease(canonical_issuers);
}
}
}
static bool query_set_class(Query *q, CFStringRef c_name, CFErrorRef *error) {
const SecDbClass *value;
if (c_name && CFGetTypeID(c_name) == CFStringGetTypeID() &&
(value = kc_class_with_name(c_name)) &&
(q->q_class == 0 || q->q_class == value)) {
q->q_class = value;
return true;
}
if (error && !*error)
SecError((c_name ? errSecNoSuchClass : errSecItemClassMissing), error, CFSTR("can find class named: %@"), c_name);
return false;
}
static const SecDbClass *query_get_class(CFDictionaryRef query, CFErrorRef *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 = kc_class_with_name(c_name))) {
return value;
} else {
if (c_name)
SecError(errSecNoSuchClass, error, CFSTR("can't find class named: %@"), c_name);
else
SecError(errSecItemClassMissing, error, CFSTR("query missing class name"));
return NULL;
}
}
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 {
SecError(errSecItemInvalidKey, &q->q_error, CFSTR("add_class: key %@ is not %@"), key, kSecClass);
}
}
static void query_add_return(const void *key, const void *value, Query *q)
{
ReturnTypeMask mask;
if (CFGetTypeID(value) != CFBooleanGetTypeID()) {
SecError(errSecItemInvalidValue, &q->q_error, CFSTR("add_return: value %@ is not CFBoolean"), value);
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 {
SecError(errSecItemInvalidKey, &q->q_error, CFSTR("add_return: unknown key %@"), key);
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;
} else if (CFEqual(key, kSecUseTombstones)) {
if (CFGetTypeID(value) == CFBooleanGetTypeID()) {
q->q_use_tomb = value;
} else if (CFGetTypeID(value) == CFNumberGetTypeID()) {
q->q_use_tomb = CFBooleanGetValue(value) ? kCFBooleanTrue : kCFBooleanFalse;
} else if (CFGetTypeID(value) == CFStringGetTypeID()) {
q->q_use_tomb = CFStringGetIntValue(value) ? kCFBooleanTrue : kCFBooleanFalse;
} else {
SecError(errSecItemInvalidValue, &q->q_error, CFSTR("add_use: value %@ for key %@ is neither CFBoolean nor CFNumber"), value, key);
return;
}
#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 {
SecError(errSecItemInvalidKey, &q->q_error, CFSTR("add_use: unknown key %@"), key);
return;
}
}
static void query_set_data(const void *value, Query *q) {
if (!isData(value)) {
SecError(errSecItemInvalidValue, &q->q_error, CFSTR("set_data: value %@ is not type data"), value);
} 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);
#ifdef 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
SecError(errSecItemInvalidValue, &q->q_error, CFSTR("add_value: value %@ is not a valid persitent ref"), value);
} else {
SecError(errSecItemInvalidKey, &q->q_error, CFSTR("add_value: unknown key %@"), key);
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)) {
SecError(errSecItemInvalidKeyType, &q->q_error, CFSTR("update_applier: unknown key type %@"), key);
return;
}
if (!value) {
SecError(errSecItemInvalidValue, &q->q_error, CFSTR("update_applier: key %@ has NULL value"), key);
return;
}
if (CFEqual(key, kSecValueData)) {
query_set_data(value, q);
} else {
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) {
SecError(errSecItemInvalidKeyType, &q->q_error, CFSTR("applier: NULL key"));
return;
}
if (!value) {
SecError(errSecItemInvalidValue, &q->q_error, CFSTR("applier: key %@ has NULL value"), key);
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:
SecError(errSecItemInvalidKey, &q->q_error, CFSTR("applier: key %@ invalid"), key);
break;
}
} else {
SecError(errSecItemInvalidKey, &q->q_error, CFSTR("applier: key %@ invalid length"), key);
}
} else if (key_id == CFNumberGetTypeID()) {
query_add_attribute(key, value, q);
} else {
SecError(errSecItemInvalidKeyType, &q->q_error, CFSTR("applier: key %@ neither string nor number"), key);
}
}
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 bool query_error(Query *q, CFErrorRef *error) {
if (q->q_error) {
CFErrorRef tmp = q->q_error;
q->q_error = NULL;
if (error && !*error) {
*error = tmp;
} else {
CFRelease(tmp);
}
return false;
}
return true;
}
bool query_destroy(Query *q, CFErrorRef *error) {
bool ok = query_error(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);
CFReleaseSafe(q->q_primary_key_digest);
CFReleaseSafe(q->q_match_issuer);
free(q);
return ok;
}
static void SecKeychainChanged(bool syncWithPeers) {
uint32_t result = notify_post(g_keychain_changed_notification);
if (syncWithPeers)
SOSCCSyncWithAllPeers();
if (result == NOTIFY_STATUS_OK)
secnotice("item", "Sent %s%s", syncWithPeers ? "SyncWithAllPeers and " : "", g_keychain_changed_notification);
else
secerror("%snotify_post %s returned: %" PRIu32, syncWithPeers ? "Sent SyncWithAllPeers, " : "", g_keychain_changed_notification, result);
}
static bool query_notify_and_destroy(Query *q, bool ok, CFErrorRef *error) {
if (ok && !q->q_error && q->q_sync_changed) {
SecKeychainChanged(true);
}
return query_destroy(q, error) && ok;
}
Query *query_create(const SecDbClass *qclass, CFDictionaryRef query,
CFErrorRef *error)
{
if (!qclass) {
if (error && !*error)
SecError(errSecItemClassMissing, error, CFSTR("Missing class"));
return NULL;
}
CFIndex key_count = SecDbClassAttrCount(qclass);
if (key_count == 0) {
key_count = SecDbClassAttrCount(&cert_class) + SecDbClassAttrCount(&keys_class);
}
if (query) {
key_count += CFDictionaryGetCount(query);
SecDbForEachAttr(qclass, attr) {
if (CFDictionaryContainsKey(query, attr->name))
--key_count;
}
}
if (key_count > QUERY_KEY_LIMIT) {
if (error && !*error)
{
secerror("key_count: %ld, QUERY_KEY_LIMIT: %d", (long)key_count, QUERY_KEY_LIMIT);
SecError(errSecItemIllegalQuery, error, CFSTR("Past query key limit"));
}
return NULL;
}
Query *q = calloc(1, sizeof(Query) + sizeof(Pair) * key_count);
if (q == NULL) {
if (error && !*error)
SecError(errSecAllocate, error, CFSTR("Out of memory"));
return NULL;
}
q->q_keybag = KEYBAG_DEVICE;
q->q_class = qclass;
q->q_match_begin = q->q_match_end = key_count;
q->q_item = CFDictionaryCreateMutable(0, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
return q;
}
static bool query_parse_with_applier(Query *q, CFDictionaryRef query,
CFDictionaryApplierFunction applier,
CFErrorRef *error) {
CFDictionaryApplyFunction(query, applier, q);
return query_error(q, error);
}
static bool query_parse(Query *q, CFDictionaryRef query,
CFErrorRef *error) {
return query_parse_with_applier(q, query, query_applier, error);
}
static bool query_update_parse(Query *q, CFDictionaryRef update,
CFErrorRef *error) {
return query_parse_with_applier(q, update, query_update_applier, error);
}
static Query *query_create_with_limit(CFDictionaryRef query, CFIndex limit,
CFErrorRef *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;
}
if (!q->q_sync && !q->q_row_id) {
query_add_attribute(kSecAttrSynchronizable, kCFBooleanFalse, q);
}
}
return q;
}
static void
query_pre_add(Query *q, bool force_date) {
CFDateRef now = CFDateCreate(0, CFAbsoluteTimeGetCurrent());
SecDbForEachAttrWithMask(q->q_class, desc, kSecDbInFlag) {
if (desc->kind == kSecDbCreationDateAttr ||
desc->kind == kSecDbModificationDateAttr) {
if (force_date) {
query_set_attribute_with_desc(desc, now, q);
} else if (!CFDictionaryContainsKey(q->q_item, desc->name)) {
query_add_attribute_with_desc(desc, now, q);
}
} else if ((desc->flags & kSecDbNotNullFlag) &&
!CFDictionaryContainsKey(q->q_item, desc->name)) {
CFTypeRef value = NULL;
if (desc->flags & kSecDbDefault0Flag) {
if (desc->kind == kSecDbDateAttr)
value = CFDateCreate(kCFAllocatorDefault, 0.0);
else {
SInt32 vzero = 0;
value = CFNumberCreate(0, kCFNumberSInt32Type, &vzero);
}
} else if (desc->flags & kSecDbDefaultEmptyFlag) {
if (desc->kind == kSecDbDataAttr)
value = CFDataCreate(kCFAllocatorDefault, NULL, 0);
else {
value = CFSTR("");
CFRetain(value);
}
}
if (value) {
query_add_attribute_with_desc(desc, value, q);
CFRelease(value);
}
}
}
CFReleaseSafe(now);
}
static void
query_pre_update(Query *q) {
SecDbForEachAttr(q->q_class, desc) {
if (desc->kind == kSecDbModificationDateAttr) {
CFDateRef now = CFDateCreate(0, CFAbsoluteTimeGetCurrent());
query_set_attribute_with_desc(desc, 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 CFDataRef SecDbItemMakePersistentRef(SecDbItemRef item, CFErrorRef *error) {
sqlite3_int64 row_id = SecDbItemGetRowId(item, error);
if (row_id)
return _SecItemMakePersistentRef(SecDbItemGetClass(item)->name, row_id);
return NULL;
}
static CFTypeRef SecDbItemCopyResult(SecDbItemRef item, ReturnTypeMask return_type, CFErrorRef *error) {
CFTypeRef a_result;
if (return_type == 0) {
a_result = kCFNull;
} else if (return_type == kSecReturnDataMask) {
a_result = SecDbItemGetCachedValueWithName(item, kSecValueData);
if (a_result) {
CFRetainSafe(a_result);
} else {
a_result = CFDataCreate(kCFAllocatorDefault, NULL, 0);
}
} else if (return_type == kSecReturnPersistentRefMask) {
a_result = SecDbItemMakePersistentRef(item, error);
} else {
CFMutableDictionaryRef dict = CFDictionaryCreateMutableForCFTypes(CFGetAllocator(item));
if (return_type & kSecReturnRefMask) {
CFDictionarySetValue(dict, kSecClass, SecDbItemGetClass(item)->name);
}
CFOptionFlags mask = (((return_type & kSecReturnDataMask || return_type & kSecReturnRefMask) ? kSecDbReturnDataFlag : 0) |
((return_type & kSecReturnAttributesMask || return_type & kSecReturnRefMask) ? kSecDbReturnAttrFlag : 0));
SecDbForEachAttr(SecDbItemGetClass(item), desc) {
if ((desc->flags & mask) != 0) {
CFTypeRef value = SecDbItemGetValue(item, desc, error);
if (value && !CFEqual(kCFNull, value)) {
CFDictionarySetValue(dict, desc->name, value);
} else if (value == NULL) {
CFReleaseNull(dict);
break;
}
}
}
if (return_type & kSecReturnPersistentRefMask) {
CFDataRef pref = SecDbItemMakePersistentRef(item, error);
CFDictionarySetValue(dict, kSecValuePersistentRef, pref);
CFRelease(pref);
}
a_result = dict;
}
return a_result;
}
static CFMutableDictionaryRef
s3dl_item_from_col(sqlite3_stmt *stmt, Query *q, int col,
CFArrayRef accessGroups, keyclass_t *keyclass, CFErrorRef *error);
static bool
s3dl_query_add(SecDbConnectionRef dbt, Query *q, CFTypeRef *result, CFErrorRef *error)
{
if (query_match_count(q) != 0)
return errSecItemMatchUnsupported;
if (q->q_use_item_list)
return errSecUseItemListUnsupported;
SecDbItemRef item = SecDbItemCreateWithAttributes(kCFAllocatorDefault, q->q_class, q->q_item, KEYBAG_DEVICE, error);
if (!item)
return false;
bool ok = true;
if (q->q_data)
ok = SecDbItemSetValueWithName(item, CFSTR("v_Data"), q->q_data, error);
if (q->q_row_id)
ok = SecDbItemSetRowId(item, q->q_row_id, error);
if (ok)
ok = SecDbItemInsert(item, dbt, error);
if (ok) {
if (result && q->q_return_type) {
*result = SecDbItemCopyResult(item, q->q_return_type, error);
}
}
if (!ok && error && *error) {
if (CFEqual(CFErrorGetDomain(*error), kSecDbErrorDomain) && CFErrorGetCode(*error) == SQLITE_CONSTRAINT) {
CFReleaseNull(*error);
SecError(errSecDuplicateItem, error, CFSTR("duplicate item %@"), item);
}
}
if (ok) {
q->q_changed = true;
if (SecDbItemIsSyncable(item))
q->q_sync_changed = true;
}
secdebug("dbitem", "inserting item %@%s%@", item, ok ? "" : "failed: ", ok || error == NULL ? (CFErrorRef)CFSTR("") : *error);
CFRelease(item);
return ok;
}
typedef void (*s3dl_handle_row)(sqlite3_stmt *stmt, void *context);
static CFMutableDictionaryRef dictionaryFromPlist(CFPropertyListRef plist, CFErrorRef *error) {
if (plist && !isDictionary(plist)) {
CFStringRef typeName = CFCopyTypeIDDescription(CFGetTypeID((CFTypeRef)plist));
SecError(errSecDecode, error, CFSTR("plist is a %@, expecting a dictionary"), typeName);
CFReleaseSafe(typeName);
CFReleaseNull(plist);
}
return (CFMutableDictionaryRef)plist;
}
static CFMutableDictionaryRef s3dl_item_v2_decode(CFDataRef plain, CFErrorRef *error) {
CFPropertyListRef item;
item = CFPropertyListCreateWithData(0, plain, kCFPropertyListMutableContainers, NULL, error);
return dictionaryFromPlist(item, error);
}
static CFMutableDictionaryRef s3dl_item_v3_decode(CFDataRef plain, CFErrorRef *error) {
CFPropertyListRef item = NULL;
const uint8_t *der = CFDataGetBytePtr(plain);
const uint8_t *der_end = der + CFDataGetLength(plain);
der = der_decode_plist(0, kCFPropertyListMutableContainers, &item, error, der, der_end);
if (der && der != der_end) {
SecCFCreateError(errSecDecode, kSecErrorDomain, CFSTR("trailing garbage at end of decrypted item"), NULL, error);
CFReleaseNull(item);
}
return dictionaryFromPlist(item, error);
}
static CFMutableDictionaryRef
s3dl_item_from_data(CFDataRef edata, Query *q, CFArrayRef accessGroups, keyclass_t *keyclass, CFErrorRef *error) {
CFMutableDictionaryRef item = NULL;
CFDataRef plain = NULL;
uint32_t version;
require_quiet((ks_decrypt_data(q->q_keybag, keyclass, edata, &plain, &version, error)), out);
if (version < 2) {
goto out;
}
if (version < 3) {
item = s3dl_item_v2_decode(plain, error);
} else {
item = s3dl_item_v3_decode(plain, error);
}
if (!item && plain) {
secerror("decode v%d failed: %@ [item: %@]", version, error ? *error : NULL, plain);
}
if (item && !itemInAccessGroup(item, accessGroups)) {
secerror("items accessGroup %@ not in %@",
CFDictionaryGetValue(item, kSecAttrAccessGroup),
accessGroups);
CFReleaseNull(item);
}
out:
CFReleaseSafe(plain);
return item;
}
static CFDataRef
s3dl_copy_data_from_col(sqlite3_stmt *stmt, int col, CFErrorRef *error) {
return CFDataCreateWithBytesNoCopy(0, sqlite3_column_blob(stmt, col),
sqlite3_column_bytes(stmt, col),
kCFAllocatorNull);
}
static CFMutableDictionaryRef
s3dl_item_from_col(sqlite3_stmt *stmt, Query *q, int col,
CFArrayRef accessGroups, keyclass_t *keyclass, CFErrorRef *error) {
CFMutableDictionaryRef item = NULL;
CFDataRef edata = NULL;
require(edata = s3dl_copy_data_from_col(stmt, col, error), out);
item = s3dl_item_from_data(edata, q, accessGroups, keyclass, error);
out:
CFReleaseSafe(edata);
return item;
}
struct s3dl_query_ctx {
Query *q;
CFArrayRef accessGroups;
CFTypeRef result;
int found;
};
static bool match_item(Query *q, CFArrayRef accessGroups, CFDictionaryRef item);
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, &q->q_error);
sqlite_int64 rowid = sqlite3_column_int64(stmt, 0);
if (!item) {
secerror("decode %@,rowid=%" PRId64 " failed (%ld): %@", q->q_class->name, rowid, CFErrorGetCode(q->q_error), q->q_error);
if(CFErrorGetCode(q->q_error)==errSecDecode)
{
secerror("We should attempt to delete this row (%lld)", rowid);
{
CFDataRef edata = s3dl_copy_data_from_col(stmt, 1, NULL);
CFMutableStringRef edatastring = CFStringCreateMutable(kCFAllocatorDefault, 0);
if(edatastring) {
CFStringAppendEncryptedData(edatastring, edata);
secnotice("item", "corrupted edata=%@", edatastring);
}
CFReleaseSafe(edata);
CFReleaseSafe(edatastring);
}
if(q->corrupted_rows==NULL) {
q->corrupted_rows=CFArrayCreateMutableForCFTypes(kCFAllocatorDefault);
}
if(q->corrupted_rows==NULL) {
secerror("Could not create a mutable array to store corrupted row! No memory ?");
} else {
long long row=rowid;
CFNumberRef number = CFNumberCreate(kCFAllocatorDefault, kCFNumberLongLongType, &row);
if(number==NULL) {
secerror("Could not create a CFNumber to store corrupted row! No memory ?");
} else {
CFArrayAppendValue(q->corrupted_rows, number);
CFReleaseNull(number);
CFReleaseNull(q->q_error);
}
}
}
return;
}
if (q->q_class == &identity_class) {
CFMutableDictionaryRef key = s3dl_item_from_col(stmt, q, 3,
c->accessGroups, NULL, &q->q_error);
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;
}
if (!match_item(q, c->accessGroups, item))
goto out;
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);
}
static void
SecDbAppendWhereROWID(CFMutableStringRef sql,
CFStringRef col, sqlite_int64 row_id,
bool *needWhere) {
if (row_id > 0) {
SecDbAppendWhereOrAnd(sql, needWhere);
CFStringAppendFormat(sql, NULL, CFSTR("%@=%lld"), col, row_id);
}
}
static void
SecDbAppendWhereAttrs(CFMutableStringRef sql, const Query *q, bool *needWhere) {
CFIndex ix, attr_count = query_attr_count(q);
for (ix = 0; ix < attr_count; ++ix) {
SecDbAppendWhereOrAndEquals(sql, query_attr_at(q, ix).key, needWhere);
}
}
static void
SecDbAppendWhereAccessGroups(CFMutableStringRef sql,
CFStringRef col,
CFArrayRef accessGroups,
bool *needWhere) {
CFIndex ix, ag_count;
if (!accessGroups || 0 == (ag_count = CFArrayGetCount(accessGroups))) {
return;
}
SecDbAppendWhereOrAnd(sql, needWhere);
CFStringAppend(sql, col);
CFStringAppend(sql, CFSTR(" IN (?"));
for (ix = 1; ix < ag_count; ++ix) {
CFStringAppend(sql, CFSTR(",?"));
}
CFStringAppend(sql, CFSTR(")"));
}
static void SecDbAppendWhereClause(CFMutableStringRef sql, const Query *q,
CFArrayRef accessGroups) {
bool needWhere = true;
SecDbAppendWhereROWID(sql, CFSTR("ROWID"), q->q_row_id, &needWhere);
SecDbAppendWhereAttrs(sql, q, &needWhere);
SecDbAppendWhereAccessGroups(sql, CFSTR("agrp"), accessGroups, &needWhere);
}
static void SecDbAppendLimit(CFMutableStringRef sql, CFIndex limit) {
if (limit != kSecMatchUnlimited)
CFStringAppendFormat(sql, NULL, CFSTR(" LIMIT %" PRIdCFIndex), limit);
}
static CFStringRef s3dl_select_sql(Query *q, 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"));
SecDbAppendWhereAccessGroups(sql, CFSTR("cert.agrp"), accessGroups, 0);
SecDbAppendWhereROWID(sql, CFSTR("crowid"), q->q_row_id, 0);
CFStringAppend(sql, CFSTR(")"));
bool needWhere = true;
SecDbAppendWhereAttrs(sql, q, &needWhere);
SecDbAppendWhereAccessGroups(sql, CFSTR("agrp"), accessGroups, &needWhere);
} else {
CFStringAppend(sql, CFSTR("SELECT rowid, data FROM "));
CFStringAppend(sql, q->q_class->name);
SecDbAppendWhereClause(sql, q, accessGroups);
}
SecDbAppendLimit(sql, q->q_limit);
return sql;
}
static bool sqlBindAccessGroups(sqlite3_stmt *stmt, CFArrayRef accessGroups,
int *pParam, CFErrorRef *error) {
bool result = true;
int param = *pParam;
CFIndex ix, count = accessGroups ? CFArrayGetCount(accessGroups) : 0;
for (ix = 0; ix < count; ++ix) {
result = SecDbBindObject(stmt, param++,
CFArrayGetValueAtIndex(accessGroups, ix),
error);
if (!result)
break;
}
*pParam = param;
return result;
}
static bool sqlBindWhereClause(sqlite3_stmt *stmt, const Query *q,
CFArrayRef accessGroups, int *pParam, CFErrorRef *error) {
bool result = true;
int param = *pParam;
CFIndex ix, attr_count = query_attr_count(q);
for (ix = 0; ix < attr_count; ++ix) {
result = SecDbBindObject(stmt, param++, query_attr_at(q, ix).value, error);
if (!result)
break;
}
if (result) {
result = sqlBindAccessGroups(stmt, accessGroups, ¶m, error);
}
*pParam = param;
return result;
}
static bool SecDbItemQuery(SecDbQueryRef query, CFArrayRef accessGroups, SecDbConnectionRef dbconn, CFErrorRef *error,
void (^handle_row)(SecDbItemRef item, bool *stop)) {
__block bool ok = true;
if (query->q_ref)
return SecError(errSecValueRefUnsupported, error, CFSTR("value ref not supported by queries"));
bool (^return_attr)(const SecDbAttr *attr) = ^bool (const SecDbAttr * attr) {
return attr->kind == kSecDbRowIdAttr || attr->kind == kSecDbEncryptedDataAttr;
};
CFStringRef sql = s3dl_select_sql(query, accessGroups);
ok = sql;
if (sql) {
ok &= SecDbPrepare(dbconn, sql, error, ^(sqlite3_stmt *stmt) {
int param = 1;
if (query->q_class == &identity_class) {
ok &= sqlBindAccessGroups(stmt, accessGroups, ¶m, error);
}
if (ok)
ok &= sqlBindWhereClause(stmt, query, accessGroups, ¶m, error);
if (ok) {
SecDbStep(dbconn, stmt, error, ^(bool *stop) {
SecDbItemRef item = SecDbItemCreateWithStatement(kCFAllocatorDefault, query->q_class, stmt, query->q_keybag, error, return_attr);
if (item) {
if (match_item(query, accessGroups, item->attributes))
handle_row(item, stop);
CFRelease(item);
} else {
secerror("failed to create item from stmt: %@", error ? *error : (CFErrorRef)"no error");
if (error) {
CFReleaseNull(*error);
}
}
});
}
});
CFRelease(sql);
}
return ok;
}
static bool
s3dl_query(SecDbConnectionRef dbt, s3dl_handle_row handle_row,
void *context, CFErrorRef *error)
{
struct s3dl_query_ctx *c = context;
Query *q = c->q;
CFArrayRef accessGroups = c->accessGroups;
if (q->q_ref)
return SecError(errSecValueRefUnsupported, error, CFSTR("value ref not supported by queries"));
if (q->q_limit == 1) {
c->result = NULL;
} else {
c->result = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
}
CFStringRef sql = s3dl_select_sql(q, accessGroups);
bool ok = SecDbWithSQL(dbt, sql, error, ^(sqlite3_stmt *stmt) {
bool sql_ok = true;
int param = 1;
if (q->q_class == &identity_class) {
sql_ok = sqlBindAccessGroups(stmt, accessGroups, ¶m, error);
}
if (sql_ok)
sql_ok = sqlBindWhereClause(stmt, q, accessGroups, ¶m, error);
if (sql_ok) {
SecDbForEach(stmt, error, ^bool (int row_index) {
handle_row(stmt, context);
return (!q->q_error) && (q->q_limit == kSecMatchUnlimited || c->found < q->q_limit);
});
}
return sql_ok;
});
CFRelease(sql);
if (!query_error(q, error))
ok = false;
if (ok && c->found == 0)
ok = SecError(errSecItemNotFound, error, CFSTR("no matching items found"));
return ok;
}
#if 0
static void
s3dl_cleanup_corrupted(SecDbConnectionRef dbt, Query *q, CFErrorRef *error)
{
if(q->corrupted_rows==NULL)
return;
__security_simulatecrash(CFSTR("Corrupted items found in keychain"));
if (q->q_class == &identity_class) {
secerror("Cleaning up corrupted identities is not implemented yet");
goto out;
}
CFArrayForEach(q->corrupted_rows, ^(const void *value) {
CFMutableStringRef sql=CFStringCreateMutable(kCFAllocatorDefault, 0);
if(sql==NULL) {
secerror("Could not allocate CFString for sql, out of memory ?");
} else {
CFStringAppend(sql, CFSTR("DELETE FROM "));
CFStringAppend(sql, q->q_class->name);
CFStringAppendFormat(sql, NULL, CFSTR(" WHERE rowid=%@"), value);
secerror("Attempting cleanup with %@", sql);
if(!SecDbExec(dbt, sql, error)) {
secerror("Cleanup Failed using %@, error: %@", sql, error?NULL:*error);
} else {
secerror("Cleanup Succeeded using %@", sql);
}
CFReleaseSafe(sql);
}
});
out:
CFReleaseNull(q->corrupted_rows);
}
#endif
static bool
s3dl_copy_matching(SecDbConnectionRef dbt, Query *q, CFTypeRef *result,
CFArrayRef accessGroups, CFErrorRef *error)
{
struct s3dl_query_ctx ctx = {
.q = q, .accessGroups = accessGroups,
};
if (q->q_row_id && query_attr_count(q))
return SecError(errSecItemIllegalQuery, error,
CFSTR("attributes to query illegal; both row_id and other attributes can't be searched at the same time"));
if (!CFDictionaryContainsKey(q->q_item, kSecAttrTombstone))
query_add_attribute(kSecAttrTombstone, kCFBooleanFalse, q);
bool ok = s3dl_query(dbt, s3dl_query_row, &ctx, error);
if (ok && result)
*result = ctx.result;
else
CFReleaseSafe(ctx.result);
return ok;
}
static bool
s3dl_query_update(SecDbConnectionRef dbt, Query *q,
CFDictionaryRef attributesToUpdate, CFArrayRef accessGroups, CFErrorRef *error)
{
if (query_match_count(q) != 0)
return SecError(errSecItemMatchUnsupported, error, CFSTR("match not supported in attributes to update"));
if (q->q_ref)
return SecError(errSecValueRefUnsupported, error, CFSTR("value ref not supported in attributes to update"));
if (q->q_row_id && query_attr_count(q))
return SecError(errSecItemIllegalQuery, error, CFSTR("attributes to update illegal; both row_id and other attributes can't be updated at the same time"));
__block bool result = true;
Query *u = query_create(q->q_class, attributesToUpdate, error);
if (u == NULL) return false;
require_action_quiet(query_update_parse(u, attributesToUpdate, error), errOut, result = false);
query_pre_update(u);
result &= SecDbTransaction(dbt, kSecDbExclusiveTransactionType, error, ^(bool *commit) {
if (!CFDictionaryContainsKey(q->q_item, kSecAttrTombstone))
query_add_attribute(kSecAttrTombstone, kCFBooleanFalse, q);
result &= SecDbItemQuery(q, accessGroups, dbt, error, ^(SecDbItemRef item, bool *stop) {
CFErrorRef localError = NULL;
SecDbItemRef new_item = SecDbItemCopyWithUpdates(item, u->q_item, &localError);
if(SecErrorGetOSStatus(localError)==errSecDecode) {
secerror("Trying to update to a corrupted item");
CFReleaseSafe(localError);
return;
}
if (error && *error == NULL) {
*error = localError;
localError = NULL;
}
CFReleaseSafe(localError);
result = new_item;
if (new_item) {
bool item_is_sync = SecDbItemIsSyncable(item);
bool makeTombstone = q->q_use_tomb ? CFBooleanGetValue(q->q_use_tomb) : (item_is_sync && !SecDbItemIsTombstone(item));
result = SecDbItemUpdate(item, new_item, dbt, makeTombstone, error);
if (result) {
q->q_changed = true;
if (item_is_sync || SecDbItemIsSyncable(new_item))
q->q_sync_changed = true;
}
CFRelease(new_item);
}
if (!result)
*stop = true;
});
if (!result)
*commit = false;
});
if (result && !q->q_changed)
result = SecError(errSecItemNotFound, error, CFSTR("No items updated"));
errOut:
if (!query_destroy(u, error))
result = false;
return result;
}
static bool
s3dl_query_delete(SecDbConnectionRef dbt, Query *q, CFArrayRef accessGroups, CFErrorRef *error)
{
__block bool ok = true;
if (!CFDictionaryContainsKey(q->q_item, kSecAttrTombstone))
query_add_attribute(kSecAttrTombstone, kCFBooleanFalse, q);
ok &= SecDbItemSelect(q, dbt, error, ^bool(const SecDbAttr *attr) {
return false;
},^bool(CFMutableStringRef sql, bool *needWhere) {
SecDbAppendWhereClause(sql, q, accessGroups);
return true;
},^bool(sqlite3_stmt * stmt, int col) {
return sqlBindWhereClause(stmt, q, accessGroups, &col, error);
}, ^(SecDbItemRef item, bool *stop) {
bool item_is_sync = SecDbItemIsSyncable(item);
bool makeTombstone = q->q_use_tomb ? CFBooleanGetValue(q->q_use_tomb) : (item_is_sync && !SecDbItemIsTombstone(item));
ok = SecDbItemDelete(item, dbt, makeTombstone, error);
if (ok) {
q->q_changed = true;
if (item_is_sync)
q->q_sync_changed = true;
}
});
if (ok && !q->q_changed) {
ok = SecError(errSecItemNotFound, error, CFSTR("Delete failed to delete anything"));
}
return ok;
}
static bool SecItemIsSystemBound(CFDictionaryRef item, const SecDbClass *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 bool SecServerDeleteAll(SecDbConnectionRef dbt, CFErrorRef *error) {
return kc_transaction(dbt, error, ^{
bool ok = (SecDbExec(dbt, CFSTR("DELETE from genp;"), error) &&
SecDbExec(dbt, CFSTR("DELETE from inet;"), error) &&
SecDbExec(dbt, CFSTR("DELETE from cert;"), error) &&
SecDbExec(dbt, CFSTR("DELETE from keys;"), error));
return ok;
});
}
struct s3dl_export_row_ctx {
struct s3dl_query_ctx qc;
keybag_handle_t dest_keybag;
enum SecItemFilter filter;
SecDbConnectionRef dbt;
};
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;
CFErrorRef localError = NULL;
sqlite_int64 rowid = sqlite3_column_int64(stmt, 0);
CFMutableDictionaryRef item = s3dl_item_from_col(stmt, q, 1, c->qc.accessGroups, &keyclass, &localError);
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 = kc_plist_copy_der(item, &q->q_error);
CFDictionaryRemoveAllValues(item);
if (plain) {
CFDataRef edata = NULL;
if (ks_encrypt_data(c->dest_keybag, keyclass, plain, &edata, &q->q_error)) {
CFDictionarySetValue(item, kSecValueData, edata);
CFReleaseSafe(edata);
} else {
seccritical("ks_encrypt_data %@,rowid=%" PRId64 ": failed: %@", q->q_class->name, rowid, q->q_error);
CFReleaseNull(q->q_error);
}
CFRelease(plain);
}
}
if (CFDictionaryGetCount(item)) {
CFDictionarySetValue(item, kSecValuePersistentRef, pref);
CFArrayAppendValue((CFMutableArrayRef)c->qc.result, item);
c->qc.found++;
}
CFReleaseSafe(pref);
}
}
CFRelease(item);
} else {
secnotice("item","Could not export item for rowid %llu: %@", rowid, localError);
if(SecErrorGetOSStatus(localError)==errSecDecode) {
CFReleaseNull(localError);
} else {
CFReleaseSafe(q->q_error);
q->q_error=localError;
}
}
}
static CF_RETURNS_RETAINED CFDictionaryRef SecServerExportKeychainPlist(SecDbConnectionRef dbt,
keybag_handle_t src_keybag, keybag_handle_t dest_keybag,
enum SecItemFilter filter, CFErrorRef *error) {
CFMutableDictionaryRef keychain;
keychain = CFDictionaryCreateMutable(kCFAllocatorDefault, 0,
&kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
if (!keychain) {
if (error && !*error)
SecError(errSecAllocate, error, CFSTR("Can't create keychain dictionary"));
goto errOut;
}
unsigned class_ix;
Query q = { .q_keybag = src_keybag };
q.q_return_type = kSecReturnDataMask | kSecReturnAttributesMask | \
kSecReturnPersistentRefMask;
q.q_limit = kSecMatchUnlimited;
const SecDbClass *SecDbClasses[] = {
&genp_class,
&inet_class,
&cert_class,
&keys_class
};
for (class_ix = 0; class_ix < array_size(SecDbClasses);
++class_ix) {
q.q_class = SecDbClasses[class_ix];
struct s3dl_export_row_ctx ctx = {
.qc = { .q = &q, },
.dest_keybag = dest_keybag, .filter = filter,
.dbt = dbt,
};
secnotice("item", "exporting class '%@'", q.q_class->name);
CFErrorRef localError = NULL;
if (s3dl_query(dbt, s3dl_export_row, &ctx, &localError)) {
if (CFArrayGetCount(ctx.qc.result))
CFDictionaryAddValue(keychain, q.q_class->name, ctx.qc.result);
} else {
OSStatus status = (OSStatus)CFErrorGetCode(localError);
if (status == errSecItemNotFound) {
CFRelease(localError);
} else {
secerror("Export failed: %@", localError);
if (error) {
CFReleaseSafe(*error);
*error = localError;
} else {
CFRelease(localError);
}
CFReleaseNull(keychain);
break;
}
}
CFReleaseNull(ctx.qc.result);
}
errOut:
return keychain;
}
static CF_RETURNS_RETAINED CFDataRef SecServerExportKeychain(SecDbConnectionRef dbt,
keybag_handle_t src_keybag, keybag_handle_t dest_keybag, CFErrorRef *error) {
CFDataRef data_out = NULL;
CFDictionaryRef keychain = SecServerExportKeychainPlist(dbt,
src_keybag, dest_keybag, kSecBackupableItemFilter,
error);
if (keychain) {
data_out = CFPropertyListCreateData(kCFAllocatorDefault, keychain,
kCFPropertyListBinaryFormat_v1_0,
0, error);
CFRelease(keychain);
}
return data_out;
}
struct SecServerImportClassState {
SecDbConnectionRef dbt;
CFErrorRef error;
keybag_handle_t src_keybag;
keybag_handle_t dest_keybag;
enum SecItemFilter filter;
};
struct SecServerImportItemState {
const SecDbClass *class;
struct SecServerImportClassState *s;
};
static bool SecDbItemImportMigrate(SecDbItemRef item, CFErrorRef *error) {
bool ok = true;
CFStringRef agrp = SecDbItemGetCachedValueWithName(item, kSecAttrAccessGroup);
CFStringRef accessible = SecDbItemGetCachedValueWithName(item, kSecAttrAccessible);
if (!isString(agrp) || !isString(accessible))
return ok;
if (SecDbItemGetClass(item) == &genp_class && CFEqual(accessible, kSecAttrAccessibleAlways)) {
CFStringRef svce = SecDbItemGetCachedValueWithName(item, kSecAttrService);
if (!isString(svce)) return ok;
if (CFEqual(agrp, CFSTR("apple"))) {
if (CFEqual(svce, CFSTR("AirPort"))) {
ok = SecDbItemSetValueWithName(item, kSecAttrAccessible, kSecAttrAccessibleAfterFirstUnlock, error);
} else if (CFEqual(svce, CFSTR("com.apple.airplay.password"))) {
ok = SecDbItemSetValueWithName(item, kSecAttrAccessible, kSecAttrAccessibleWhenUnlocked, error);
} else if (CFEqual(svce, CFSTR("YouTube"))) {
ok = (SecDbItemSetValueWithName(item, kSecAttrAccessible, kSecAttrAccessibleWhenUnlocked, error) &&
SecDbItemSetValueWithName(item, kSecAttrAccessGroup, CFSTR("com.apple.youtube.credentials"), error));
} else {
CFStringRef desc = SecDbItemGetCachedValueWithName(item, kSecAttrDescription);
if (!isString(desc)) return ok;
if (CFEqual(desc, CFSTR("IPSec Shared Secret")) || CFEqual(desc, CFSTR("PPP Password"))) {
ok = SecDbItemSetValueWithName(item, kSecAttrAccessible, kSecAttrAccessibleAfterFirstUnlock, error);
}
}
}
} else if (SecDbItemGetClass(item) == &inet_class && CFEqual(accessible, kSecAttrAccessibleAlways)) {
if (CFEqual(agrp, CFSTR("PrintKitAccessGroup"))) {
ok = SecDbItemSetValueWithName(item, kSecAttrAccessible, kSecAttrAccessibleWhenUnlocked, error);
} else if (CFEqual(agrp, CFSTR("apple"))) {
CFTypeRef ptcl = SecDbItemGetCachedValueWithName(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)
ok = SecDbItemSetValueWithName(item, kSecAttrAccessible, kSecAttrAccessibleWhenUnlocked, error);
}
}
return ok;
}
bool SecDbItemDecrypt(SecDbItemRef item, CFDataRef edata, CFErrorRef *error) {
bool ok = true;
CFDataRef pdata = NULL;
keyclass_t keyclass;
uint32_t version;
ok = ks_decrypt_data(SecDbItemGetKeybag(item), &keyclass, edata, &pdata, &version, error);
if (!ok)
return ok;
if (version < 2) {
ok = SecDbItemSetValueWithName(item, CFSTR("v_Data"), pdata, error) &&
SecDbItemImportMigrate(item, error);
} else {
CFDictionaryRef dict;
if (version < 3) {
dict = s3dl_item_v2_decode(pdata, error);
} else {
dict = s3dl_item_v3_decode(pdata, error);
}
ok = dict && SecDbItemSetValues(item, dict, error);
CFReleaseSafe(dict);
}
CFReleaseSafe(pdata);
keyclass_t my_keyclass = SecDbItemGetKeyclass(item, NULL);
if (!my_keyclass) {
ok = ok && SecDbItemSetKeyclass(item, keyclass, error);
} else {
if (my_keyclass != keyclass) {
ok = SecError(errSecDecode, error, CFSTR("keyclass attribute %d doesn't match keyclass in blob %d"), my_keyclass, keyclass);
}
}
return ok;
}
static bool SecDbItemInferSyncable(SecDbItemRef item, CFErrorRef *error)
{
CFStringRef agrp = SecDbItemGetCachedValueWithName(item, kSecAttrAccessGroup);
if (!isString(agrp))
return true;
if (CFEqual(agrp, CFSTR("com.apple.cfnetwork")) && SecDbItemGetClass(item) == &inet_class) {
CFTypeRef srvr = SecDbItemGetCachedValueWithName(item, kSecAttrServer);
CFTypeRef ptcl = SecDbItemGetCachedValueWithName(item, kSecAttrProtocol);
CFTypeRef atyp = SecDbItemGetCachedValueWithName(item, kSecAttrAuthenticationType);
if (isString(srvr) && isString(ptcl) && isString(atyp)) {
secnotice("item", "Make this item syncable: %@", item);
return SecDbItemSetSyncable(item, true, error);
}
}
return true;
}
static SecDbItemRef SecDbItemCreateWithBackupDictionary(CFAllocatorRef allocator, const SecDbClass *dbclass, CFDictionaryRef dict, keybag_handle_t src_keybag, keybag_handle_t dst_keybag, CFErrorRef *error)
{
CFDataRef edata = CFDictionaryGetValue(dict, CFSTR("v_Data"));
SecDbItemRef item = NULL;
if (edata) {
item = SecDbItemCreateWithEncryptedData(kCFAllocatorDefault, dbclass, edata, src_keybag, error);
if (item)
if (!SecDbItemSetKeybag(item, dst_keybag, error))
CFReleaseNull(item);
} else {
SecError(errSecDecode, error, CFSTR("No v_Data in backup dictionary %@"), dict);
}
return item;
}
static bool SecDbItemExtractRowIdFromBackupDictionary(SecDbItemRef item, CFDictionaryRef dict, CFErrorRef *error) {
CFDataRef ref = CFDictionaryGetValue(dict, CFSTR("v_PersistentRef"));
if (!ref)
return SecError(errSecDecode, error, CFSTR("No v_PersistentRef in backup dictionary %@"), dict);
CFStringRef className;
sqlite3_int64 rowid;
if (!_SecItemParsePersistentRef(ref, &className, &rowid))
return SecError(errSecDecode, error, CFSTR("v_PersistentRef %@ failed to decode"), ref);
if (!CFEqual(SecDbItemGetClass(item)->name, className))
return SecError(errSecDecode, error, CFSTR("v_PersistentRef has unexpected class %@"), className);
return SecDbItemSetRowId(item, rowid, error);
}
static void SecServerImportItem(const void *value, void *context) {
struct SecServerImportItemState *state =
(struct SecServerImportItemState *)context;
if (state->s->error)
return;
if (!isDictionary(value)) {
SecError(errSecParam, &state->s->error, CFSTR("value %@ is not a dictionary"), value);
return;
}
CFDictionaryRef dict = (CFDictionaryRef)value;
secdebug("item", "Import Item : %@", dict);
if (state->s->filter == kSecBackupableItemFilter &&
SecItemIsSystemBound(dict, state->class))
return;
SecDbItemRef item;
if (state->s->src_keybag == KEYBAG_NONE) {
item = SecDbItemCreateWithAttributes(kCFAllocatorDefault, state->class, dict, state->s->dest_keybag, &state->s->error);
} else {
item = SecDbItemCreateWithBackupDictionary(kCFAllocatorDefault, state->class, dict, state->s->src_keybag, state->s->dest_keybag, &state->s->error);
}
if (item) {
if(state->s->filter != kSecSysBoundItemFilter) {
SecDbItemExtractRowIdFromBackupDictionary(item, dict, &state->s->error);
}
SecDbItemInferSyncable(item, &state->s->error);
SecDbItemInsert(item, state->s->dbt, &state->s->error);
}
if (state->s->error) {
secwarning("Failed to import an item (%@) of class '%@': %@ - ignoring error.",
item, state->class->name, state->s->error);
CFReleaseNull(state->s->error);
}
CFReleaseSafe(item);
}
static void SecServerImportClass(const void *key, const void *value,
void *context) {
struct SecServerImportClassState *state =
(struct SecServerImportClassState *)context;
if (state->error)
return;
if (!isString(key)) {
SecError(errSecParam, &state->error, CFSTR("class name %@ is not a string"), key);
return;
}
const SecDbClass *class = kc_class_with_name(key);
if (!class || class == &identity_class) {
SecError(errSecParam, &state->error, CFSTR("attempt to import an identity"));
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 bool SecServerImportKeychainInPlist(SecDbConnectionRef dbt,
keybag_handle_t src_keybag, keybag_handle_t dest_keybag,
CFDictionaryRef keychain, enum SecItemFilter filter, CFErrorRef *error) {
bool ok = true;
CFDictionaryRef sys_bound = NULL;
if (filter == kSecBackupableItemFilter) {
require(sys_bound = SecServerExportKeychainPlist(dbt, KEYBAG_DEVICE,
KEYBAG_NONE, kSecSysBoundItemFilter,
error), errOut);
}
require(ok = SecServerDeleteAll(dbt, error), 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);
}
if (state.error) {
if (error) {
CFReleaseSafe(*error);
*error = state.error;
} else {
CFRelease(state.error);
}
ok = false;
}
errOut:
return ok;
}
static bool SecServerImportKeychain(SecDbConnectionRef dbt,
keybag_handle_t src_keybag,
keybag_handle_t dest_keybag, CFDataRef data, CFErrorRef *error) {
return kc_transaction(dbt, error, ^{
bool ok = false;
CFDictionaryRef keychain;
keychain = CFPropertyListCreateWithData(kCFAllocatorDefault, data,
kCFPropertyListImmutable, NULL,
error);
if (keychain) {
if (isDictionary(keychain)) {
ok = SecServerImportKeychainInPlist(dbt, src_keybag,
dest_keybag, keychain,
kSecBackupableItemFilter,
error);
} else {
ok = SecError(errSecParam, error, CFSTR("import: keychain is not a dictionary"));
}
CFRelease(keychain);
}
return ok;
});
}
static bool ks_open_keybag(CFDataRef keybag, CFDataRef password, keybag_handle_t *handle, CFErrorRef *error) {
#if USE_KEYSTORE
kern_return_t kernResult;
kernResult = aks_load_bag(CFDataGetBytePtr(keybag), (int)CFDataGetLength(keybag), handle);
if (kernResult)
return SecKernError(kernResult, error, CFSTR("aks_load_bag failed: %@"), keybag);
if (password) {
kernResult = aks_unlock_bag(*handle, CFDataGetBytePtr(password), (int)CFDataGetLength(password));
if (kernResult) {
aks_unload_bag(*handle);
return SecKernError(kernResult, error, CFSTR("aks_unlock_bag failed"));
}
}
return true;
#else
*handle = KEYBAG_NONE;
return true;
#endif
}
static bool ks_close_keybag(keybag_handle_t keybag, CFErrorRef *error) {
#if USE_KEYSTORE
IOReturn kernResult = aks_unload_bag(keybag);
if (kernResult) {
return SecKernError(kernResult, error, CFSTR("aks_unload_bag failed"));
}
#endif
return true;
}
static CF_RETURNS_RETAINED CFDataRef SecServerKeychainBackup(SecDbConnectionRef dbt, CFDataRef keybag,
CFDataRef password, CFErrorRef *error) {
CFDataRef backup = NULL;
keybag_handle_t backup_keybag;
if (ks_open_keybag(keybag, password, &backup_keybag, error)) {
backup = SecServerExportKeychain(dbt, KEYBAG_DEVICE, backup_keybag, error);
if (!ks_close_keybag(backup_keybag, error)) {
CFReleaseNull(backup);
}
}
return backup;
}
static bool SecServerKeychainRestore(SecDbConnectionRef dbt, CFDataRef backup,
CFDataRef keybag, CFDataRef password, CFErrorRef *error) {
keybag_handle_t backup_keybag;
if (!ks_open_keybag(keybag, password, &backup_keybag, error))
return false;
bool ok = SecServerImportKeychain(dbt, backup_keybag, KEYBAG_DEVICE,
backup, error);
ok &= ks_close_keybag(backup_keybag, error);
return ok;
}
CFStringRef __SecKeychainCopyPath(void) {
CFStringRef kcRelPath = NULL;
if (use_hwaes()) {
kcRelPath = CFSTR("keychain-2.db");
} else {
kcRelPath = CFSTR("keychain-2-debug.db");
}
CFStringRef kcPath = NULL;
CFURLRef kcURL = SecCopyURLForFileInKeychainDirectory(kcRelPath);
if (kcURL) {
kcPath = CFURLCopyFileSystemPath(kcURL, kCFURLPOSIXPathStyle);
CFRelease(kcURL);
}
return kcPath;
}
static SecDbRef SecKeychainDbCreate(CFStringRef path) {
return SecDbCreate(path, ^bool (SecDbConnectionRef dbconn, bool didCreate, CFErrorRef *localError) {
bool ok;
if (didCreate)
ok = s3dl_dbt_upgrade_from_version(dbconn, 0, localError);
else
ok = s3dl_dbt_upgrade(dbconn, localError);
if (!ok)
secerror("Upgrade %sfailed: %@", didCreate ? "from v0 " : "", localError ? *localError : NULL);
return ok;
});
}
static SecDbRef _kc_dbhandle = NULL;
static void kc_dbhandle_init(void) {
SecDbRef oldHandle = _kc_dbhandle;
_kc_dbhandle = NULL;
CFStringRef dbPath = __SecKeychainCopyPath();
if (dbPath) {
_kc_dbhandle = SecKeychainDbCreate(dbPath);
CFRelease(dbPath);
}
if (oldHandle) {
secerror("replaced %@ with %@", oldHandle, _kc_dbhandle);
CFRelease(oldHandle);
}
}
static dispatch_once_t _kc_dbhandle_once;
static SecDbRef kc_dbhandle(void) {
dispatch_once(&_kc_dbhandle_once, ^{
kc_dbhandle_init();
});
return _kc_dbhandle;
}
void kc_dbhandle_reset(void);
void kc_dbhandle_reset(void)
{
__block bool done = false;
dispatch_once(&_kc_dbhandle_once, ^{
kc_dbhandle_init();
done = true;
});
if (!done)
kc_dbhandle_init();
}
static SecDbConnectionRef kc_aquire_dbt(bool writeAndRead, CFErrorRef *error) {
return SecDbConnectionAquire(kc_dbhandle(), !writeAndRead, error);
}
static bool kc_with_dbt(bool writeAndRead, CFErrorRef *error, bool (^perform)(SecDbConnectionRef dbt))
{
bool ok = false;
SecDbConnectionRef dbt = kc_aquire_dbt(writeAndRead, error);
if (dbt) {
ok = perform(dbt);
SecDbConnectionRelease(dbt);
}
return ok;
}
static bool
items_matching_issuer_parent(CFArrayRef accessGroups,
CFDataRef issuer, CFArrayRef issuers, int recurse)
{
Query *q;
CFArrayRef results = NULL;
SecDbConnectionRef dbt = NULL;
CFIndex i, count;
bool found = false;
if (CFArrayContainsValue(issuers, CFRangeMake(0, CFArrayGetCount(issuers)), issuer))
return true;
const void *keys[] = { kSecClass, kSecReturnRef, kSecAttrSubject };
const void *vals[] = { kSecClassCertificate, kCFBooleanTrue, issuer };
CFDictionaryRef query = CFDictionaryCreate(kCFAllocatorDefault, keys, vals, array_size(keys), NULL, NULL);
if (!query)
return false;
CFErrorRef localError = NULL;
q = query_create_with_limit(query, kSecMatchUnlimited, &localError);
CFRelease(query);
if (q) {
if ((dbt = SecDbConnectionAquire(kc_dbhandle(), true, &localError))) {
s3dl_copy_matching(dbt, q, (CFTypeRef*)&results, accessGroups, &localError);
SecDbConnectionRelease(dbt);
}
query_destroy(q, &localError);
}
if (localError) {
secerror("items matching issuer parent: %@", localError);
CFReleaseNull(localError);
return false;
}
count = CFArrayGetCount(results);
for (i = 0; (i < count) && !found; i++) {
CFDictionaryRef cert_dict = (CFDictionaryRef)CFArrayGetValueAtIndex(results, i);
CFDataRef cert_issuer = CFDictionaryGetValue(cert_dict, kSecAttrIssuer);
if (CFEqual(cert_issuer, issuer))
continue;
if (recurse-- > 0)
found = items_matching_issuer_parent(accessGroups, cert_issuer, issuers, recurse);
}
CFRelease(results);
return found;
}
static bool match_item(Query *q, CFArrayRef accessGroups, CFDictionaryRef item)
{
if (q->q_match_issuer) {
CFDataRef issuer = CFDictionaryGetValue(item, kSecAttrIssuer);
if (!items_matching_issuer_parent(accessGroups, issuer, q->q_match_issuer, 10 ))
return false;
}
return true;
}
#if 0
static bool SecErrorWith(CFErrorRef *in_error, bool (^perform)(CFErrorRef *error)) {
CFErrorRef error = in_error ? *in_error : NULL;
bool ok;
if ((ok = perform(&error))) {
assert(error == NULL);
if (error)
secerror("error + success: %@", error);
} else {
assert(error);
OSStatus status = SecErrorGetOSStatus(error);
if (status != errSecItemNotFound) secerror("error:[%" PRIdOSStatus "] %@", status, error);
if (in_error) {
*in_error = error;
} else {
CFReleaseNull(error);
}
}
return ok;
}
#endif
static bool
SecItemServerCopyMatching(CFDictionaryRef query, CFTypeRef *result,
CFArrayRef accessGroups, CFErrorRef *error)
{
CFIndex ag_count;
if (!accessGroups || 0 == (ag_count = CFArrayGetCount(accessGroups))) {
return SecError(errSecMissingEntitlement, error,
CFSTR("client has neither application-identifier nor keychain-access-groups entitlements"));
}
if (CFArrayContainsValue(accessGroups, CFRangeMake(0, ag_count), CFSTR("*"))) {
accessGroups = NULL;
}
bool ok = false;
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) {
ok = SecError(errSecUseItemListUnsupported, error, CFSTR("use item list unsupported"));
#if defined(MULTIPLE_KEYCHAINS)
} else if (q->q_use_keychain) {
ok = SecError(errSecUseKeychainUnsupported, error, CFSTR("use keychain list unsupported"));
#endif
} else if (q->q_match_issuer && ((q->q_class != &cert_class) &&
(q->q_class != &identity_class))) {
ok = SecError(errSecUnsupportedOperation, error, CFSTR("unsupported match attribute"));
} else if (q->q_return_type != 0 && result == NULL) {
ok = SecError(errSecReturnMissingPointer, error, CFSTR("missing pointer"));
} else if (!q->q_error) {
ok = kc_with_dbt(false, error, ^(SecDbConnectionRef dbt) {
return s3dl_copy_matching(dbt, q, result, accessGroups, error);
});
}
CFReleaseSafe(accessGroups);
if (!query_destroy(q, error))
ok = false;
}
return ok;
}
bool
_SecItemCopyMatching(CFDictionaryRef query, CFArrayRef accessGroups, CFTypeRef *result, CFErrorRef *error) {
return SecItemServerCopyMatching(query, result, accessGroups, error);
}
bool
_SecItemAdd(CFDictionaryRef attributes, CFArrayRef accessGroups,
CFTypeRef *result, CFErrorRef *error)
{
bool ok = true;
CFIndex ag_count;
if (!accessGroups || 0 == (ag_count = CFArrayGetCount(accessGroups)))
return SecError(errSecMissingEntitlement, error,
CFSTR("client has neither application-identifier nor keychain-access-groups entitlements"));
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 SecError(errSecNoAccessForItem, error, CFSTR("NoAccessForItem"));
} else {
agrp = (CFStringRef)CFArrayGetValueAtIndex(ag, 0);
query_add_attribute(kSecAttrAccessGroup, agrp, q);
}
query_ensure_keyclass(q, agrp);
if (q->q_row_id)
ok = SecError(errSecValuePersistentRefUnsupported, error, CFSTR("q_row_id")); #if defined(MULTIPLE_KEYCHAINS)
else if (q->q_use_keychain_list)
ok = SecError(errSecUseKeychainListUnsupported, error, CFSTR("q_use_keychain_list")); #endif
else if (!q->q_error) {
ok = kc_with_dbt(true, error, ^(SecDbConnectionRef dbt){
return kc_transaction(dbt, error, ^{
query_pre_add(q, true);
return s3dl_query_add(dbt, q, result, error);
});
});
}
ok = query_notify_and_destroy(q, ok, error);
} else {
ok = false;
}
return ok;
}
bool
_SecItemUpdate(CFDictionaryRef query, CFDictionaryRef attributesToUpdate,
CFArrayRef accessGroups, CFErrorRef *error)
{
CFIndex ag_count;
if (!accessGroups || 0 == (ag_count = CFArrayGetCount(accessGroups))) {
return SecError(errSecMissingEntitlement, error,
CFSTR("client has neither application-identifier nor keychain-access-groups entitlements"));
}
if (CFArrayContainsValue(accessGroups, CFRangeMake(0, ag_count), CFSTR("*"))) {
accessGroups = NULL;
}
bool ok = true;
Query *q = query_create_with_limit(query, kSecMatchUnlimited, error);
if (!q) {
ok = false;
}
if (ok) {
if (q->q_use_item_list) {
ok = SecError(errSecUseItemListUnsupported, error, CFSTR("use item list not supported"));
} else if (q->q_return_type & kSecReturnDataMask) {
ok = SecError(errSecReturnDataUnsupported, error, CFSTR("return data not supported by update"));
} else if (q->q_return_type & kSecReturnAttributesMask) {
ok = SecError(errSecReturnAttributesUnsupported, error, CFSTR("return attributes not supported by update"));
} else if (q->q_return_type & kSecReturnRefMask) {
ok = SecError(errSecReturnRefUnsupported, error, CFSTR("return ref not supported by update"));
} else if (q->q_return_type & kSecReturnPersistentRefMask) {
ok = SecError(errSecReturnPersitentRefUnsupported, error, CFSTR("return persistent ref not supported by update"));
} else {
CFStringRef agrp = (CFStringRef)CFDictionaryGetValue(attributesToUpdate,
kSecAttrAccessGroup);
if (agrp) {
if (!accessGroupsAllows(accessGroups, agrp)) {
ok = SecError(errSecNoAccessForItem, error, CFSTR("accessGroup %@ not in %@"), agrp, accessGroups);
}
}
}
}
if (ok) {
if (!q->q_use_tomb && SOSCCThisDeviceDefinitelyNotActiveInCircle()) {
q->q_use_tomb = kCFBooleanFalse;
}
ok = kc_with_dbt(true, error, ^(SecDbConnectionRef dbt) {
return s3dl_query_update(dbt, q, attributesToUpdate, accessGroups, error);
});
}
if (q) {
ok = query_notify_and_destroy(q, ok, error);
}
return ok;
}
bool
_SecItemDelete(CFDictionaryRef query, CFArrayRef accessGroups, CFErrorRef *error)
{
CFIndex ag_count;
if (!accessGroups || 0 == (ag_count = CFArrayGetCount(accessGroups))) {
return SecError(errSecMissingEntitlement, error,
CFSTR("client has neither application-identifier nor keychain-access-groups entitlements"));
}
if (CFArrayContainsValue(accessGroups, CFRangeMake(0, ag_count), CFSTR("*"))) {
accessGroups = NULL;
}
Query *q = query_create_with_limit(query, kSecMatchUnlimited, error);
bool ok;
if (q) {
if (q->q_limit != kSecMatchUnlimited)
ok = SecError(errSecMatchLimitUnsupported, error, CFSTR("match limit not supported by delete"));
else if (query_match_count(q) != 0)
ok = SecError(errSecItemMatchUnsupported, error, CFSTR("match not supported by delete"));
else if (q->q_ref)
ok = SecError(errSecValueRefUnsupported, error, CFSTR("value ref not supported by delete"));
else if (q->q_row_id && query_attr_count(q))
ok = SecError(errSecItemIllegalQuery, error, CFSTR("rowid and other attributes are mutually exclusive"));
else {
if (!q->q_use_tomb && SOSCCThisDeviceDefinitelyNotActiveInCircle()) {
q->q_use_tomb = kCFBooleanFalse;
}
ok = kc_with_dbt(true, error, ^(SecDbConnectionRef dbt) {
return s3dl_query_delete(dbt, q, accessGroups, error);
});
}
ok = query_notify_and_destroy(q, ok, error);
} else {
ok = false;
}
return ok;
}
static bool
SecItemServerDeleteAll(CFErrorRef *error) {
return kc_with_dbt(true, error, ^bool (SecDbConnectionRef dbt) {
return (kc_transaction(dbt, error, ^bool {
return (SecDbExec(dbt, CFSTR("DELETE from genp;"), error) &&
SecDbExec(dbt, CFSTR("DELETE from inet;"), error) &&
SecDbExec(dbt, CFSTR("DELETE from cert;"), error) &&
SecDbExec(dbt, CFSTR("DELETE from keys;"), error));
}) && SecDbExec(dbt, CFSTR("VACUUM;"), error));
});
}
bool
_SecItemDeleteAll(CFErrorRef *error) {
return SecItemServerDeleteAll(error);
}
CFDataRef
_SecServerKeychainBackup(CFDataRef keybag, CFDataRef passcode, CFErrorRef *error) {
CFDataRef backup;
SecDbConnectionRef dbt = SecDbConnectionAquire(kc_dbhandle(), false, error);
if (!dbt)
return NULL;
if (keybag == NULL && passcode == NULL) {
#if USE_KEYSTORE
backup = SecServerExportKeychain(dbt, KEYBAG_DEVICE, backup_keybag_handle, error);
#else
SecError(errSecParam, error, CFSTR("Why are you doing this?"));
backup = NULL;
#endif
} else {
backup = SecServerKeychainBackup(dbt, keybag, passcode, error);
}
SecDbConnectionRelease(dbt);
return backup;
}
bool
_SecServerKeychainRestore(CFDataRef backup, CFDataRef keybag, CFDataRef passcode, CFErrorRef *error) {
if (backup == NULL || keybag == NULL)
return SecError(errSecParam, error, CFSTR("backup or keybag missing"));
__block bool ok = true;
ok &= SecDbPerformWrite(kc_dbhandle(), error, ^(SecDbConnectionRef dbconn) {
ok = SecServerKeychainRestore(dbconn, backup, keybag, passcode, error);
});
if (ok) {
SecKeychainChanged(true);
}
return ok;
}
static CFStringRef kSecItemDataSourceErrorDomain = CFSTR("com.apple.secitem.datasource");
enum {
kSecObjectMallocFailed = 1,
kSecAddDuplicateEntry,
kSecObjectNotFoundError,
kSOSAccountCreationFailed,
};
typedef struct SecItemDataSource *SecItemDataSourceRef;
struct SecItemDataSource {
struct SOSDataSource ds;
SecDbRef db;
bool readOnly;
SecDbConnectionRef _dbconn;
unsigned gm_count;
unsigned cm_count;
unsigned co_count;
bool dv_loaded;
struct SOSDigestVector dv;
struct SOSDigestVector toadd;
struct SOSDigestVector todel;
SOSManifestRef manifest;
uint8_t manifest_digest[SOSDigestSize];
bool changed;
bool syncWithPeersWhenDone;
};
static SecDbConnectionRef SecItemDataSourceGetConnection(SecItemDataSourceRef ds, CFErrorRef *error) {
if (!ds->_dbconn) {
ds->_dbconn = SecDbConnectionAquire(ds->db, ds->readOnly, error);
if (ds->_dbconn) {
ds->changed = false;
} else {
secerror("SecDbConnectionAquire failed: %@", error ? *error : NULL);
}
}
return ds->_dbconn;
}
static bool SecItemDataSourceRecordUpdate(SecItemDataSourceRef ds, SecDbItemRef deleted, SecDbItemRef inserted, CFErrorRef *error) {
bool ok = true;
CFDataRef digest;
if (ds->dv_loaded) {
if (inserted) {
ok = digest = SecDbItemGetSHA1(inserted, error);
if (ok) SOSDigestVectorAppend(&ds->toadd, CFDataGetBytePtr(digest));
}
if (ok && deleted) {
ok = digest = SecDbItemGetSHA1(deleted, error);
if (ok) SOSDigestVectorAppend(&ds->todel, CFDataGetBytePtr(digest));
}
if (inserted || deleted) {
CFReleaseNull(ds->manifest);
ds->changed = true;
}
if (!ok) {
ds->dv_loaded = false;
}
}
return ok;
}
static bool SecItemDataSourceRecordAdd(SecItemDataSourceRef ds, SecDbItemRef inserted, CFErrorRef *error) {
return SecItemDataSourceRecordUpdate(ds, NULL, inserted, error);
}
static bool SecDbItemSelectSHA1(SecDbQueryRef query, SecDbConnectionRef dbconn, CFErrorRef *error,
bool (^use_attr_in_where)(const SecDbAttr *attr),
bool (^add_where_sql)(CFMutableStringRef sql, bool *needWhere),
bool (^bind_added_where)(sqlite3_stmt *stmt, int col),
void (^row)(sqlite3_stmt *stmt, bool *stop)) {
__block bool ok = true;
bool (^return_attr)(const SecDbAttr *attr) = ^bool (const SecDbAttr * attr) {
return attr->kind == kSecDbSHA1Attr;
};
CFStringRef sql = SecDbItemCopySelectSQL(query, return_attr, use_attr_in_where, add_where_sql);
if (sql) {
ok &= SecDbPrepare(dbconn, sql, error, ^(sqlite3_stmt *stmt) {
ok = (SecDbItemSelectBind(query, stmt, error, use_attr_in_where, bind_added_where) &&
SecDbStep(dbconn, stmt, error, ^(bool *stop){ row(stmt, stop); }));
});
CFRelease(sql);
} else {
ok = false;
}
return ok;
}
static bool SecItemDataSourceLoadManifest(SecItemDataSourceRef ds, CFErrorRef *error) {
bool ok = true;
SecDbConnectionRef dbconn;
if (!(dbconn = SecItemDataSourceGetConnection(ds, error))) return false;
const SecDbClass *synced_classes[] = {
&genp_class,
&inet_class,
&keys_class,
};
ds->dv.count = 0; CFErrorRef localError = NULL;
for (size_t class_ix = 0; class_ix < array_size(synced_classes);
++class_ix) {
Query *q = query_create(synced_classes[class_ix], NULL, &localError);
if (q) {
q->q_return_type = kSecReturnDataMask | kSecReturnAttributesMask;
q->q_limit = kSecMatchUnlimited;
q->q_keybag = KEYBAG_DEVICE;
query_add_attribute(kSecAttrSynchronizable, kCFBooleanTrue, q);
if (!SecDbItemSelectSHA1(q, dbconn, &localError, ^bool(const SecDbAttr *attr) {
return attr->kind == kSecDbSyncAttr;
}, NULL, NULL, ^(sqlite3_stmt *stmt, bool *stop) {
const uint8_t *digest = sqlite3_column_blob(stmt, 0);
size_t digestLen = sqlite3_column_bytes(stmt, 0);
if (digestLen != SOSDigestSize) {
secerror("digest %zu bytes", digestLen);
} else {
SOSDigestVectorAppend(&ds->dv, digest);
}
})) {
secerror("SecDbItemSelect failed: %@", localError);
CFReleaseNull(localError);
}
query_destroy(q, &localError);
if (localError) {
secerror("query_destroy failed: %@", localError);
CFReleaseNull(localError);
}
} else if (localError) {
secerror("query_create failed: %@", localError);
CFReleaseNull(localError);
}
}
SOSDigestVectorSort(&ds->dv);
return ok;
}
static bool SecItemDataSourceEnsureFreshManifest(SecItemDataSourceRef ds, CFErrorRef *error) {
bool ok = true;
if (ds->dv_loaded && (ds->toadd.count || ds->todel.count)) {
CFErrorRef patchError = NULL;
struct SOSDigestVector new_dv = SOSDigestVectorInit;
ok = SOSDigestVectorPatch(&ds->dv, &ds->todel, &ds->toadd, &new_dv, &patchError);
if (!ok) secerror("patch failed %@ manifest: %@ toadd: %@ todel: %@", patchError, &ds->dv, &ds->todel, &ds->toadd);
CFReleaseSafe(patchError);
SOSDigestVectorFree(&ds->dv);
SOSDigestVectorFree(&ds->toadd);
SOSDigestVectorFree(&ds->todel);
ds->dv = new_dv;
}
return (ok && ds->dv_loaded) || (ds->dv_loaded = SecItemDataSourceLoadManifest(ds, error));
}
static bool ds_get_manifest_digest(SOSDataSourceRef data_source, uint8_t *out_digest, CFErrorRef *error) {
struct SecItemDataSource *ds = (struct SecItemDataSource *)data_source;
if (!ds->manifest) {
SOSManifestRef mf = data_source->copy_manifest(data_source, error);
if (mf) {
CFRelease(mf);
} else {
return false;
}
}
memcpy(out_digest, ds->manifest_digest, SOSDigestSize);
ds->gm_count++;
return true;
}
static SOSManifestRef ds_copy_manifest(SOSDataSourceRef data_source, CFErrorRef *error) {
struct SecItemDataSource *ds = (struct SecItemDataSource *)data_source;
ds->cm_count++;
if (ds->manifest) {
CFRetain(ds->manifest);
return ds->manifest;
}
if (!SecItemDataSourceEnsureFreshManifest(ds, error)) return NULL;
ds->manifest = SOSManifestCreateWithBytes((const uint8_t *)ds->dv.digest, ds->dv.count * SOSDigestSize, error);
ccdigest(ccsha1_di(), SOSManifestGetSize(ds->manifest), SOSManifestGetBytePtr(ds->manifest), ds->manifest_digest);
return (SOSManifestRef)CFRetain(ds->manifest);
}
static bool ds_foreach_object(SOSDataSourceRef data_source, SOSManifestRef manifest, CFErrorRef *error, bool (^handle_object)(SOSObjectRef object, CFErrorRef *error)) {
struct SecItemDataSource *ds = (struct SecItemDataSource *)data_source;
ds->co_count++;
__block bool result = true;
const SecDbAttr *sha1Attr = SecDbClassAttrWithKind(&genp_class, kSecDbSHA1Attr, error);
if (!sha1Attr) return false;
bool (^return_attr)(const SecDbAttr *attr) = ^bool (const SecDbAttr * attr) {
return attr->kind == kSecDbRowIdAttr || attr->kind == kSecDbEncryptedDataAttr;
};
bool (^use_attr_in_where)(const SecDbAttr *attr) = ^bool (const SecDbAttr * attr) {
return attr->kind == kSecDbSHA1Attr;
};
const SecDbClass *synced_classes[] = {
&genp_class,
&inet_class,
&keys_class,
};
Query *select_queries[array_size(synced_classes)];
CFStringRef select_sql[array_size(synced_classes)];
sqlite3_stmt *select_stmts[array_size(synced_classes)];
__block Query **queries = select_queries;
__block CFStringRef *sqls = select_sql;
__block sqlite3_stmt **stmts = select_stmts;
SecDbConnectionRef dbconn;
result = dbconn = SecItemDataSourceGetConnection(ds, error);
for (size_t class_ix = 0; class_ix < array_size(synced_classes); ++class_ix) {
result = (result
&& (queries[class_ix] = query_create(synced_classes[class_ix], NULL, error))
&& (sqls[class_ix] = SecDbItemCopySelectSQL(queries[class_ix], return_attr, use_attr_in_where, NULL))
&& (stmts[class_ix] = SecDbCopyStmt(dbconn, sqls[class_ix], NULL, error)));
}
if (result) SOSManifestForEach(manifest, ^(CFDataRef key) {
__block bool gotItem = false;
for (size_t class_ix = 0; result && !gotItem && class_ix < array_size(synced_classes); ++class_ix) {
CFDictionarySetValue(queries[class_ix]->q_item, sha1Attr->name, key);
result &= (SecDbItemSelectBind(queries[class_ix], stmts[class_ix], error, use_attr_in_where, NULL) && SecDbStep(dbconn, stmts[class_ix], error, ^(bool *stop) {
SecDbItemRef item = SecDbItemCreateWithStatement(kCFAllocatorDefault, queries[class_ix]->q_class, stmts[class_ix], KEYBAG_DEVICE, error, return_attr);
if (item) {
CFErrorRef localError=NULL;
gotItem = true;
if(!(result=handle_object((SOSObjectRef)item, &localError))){
if (SecErrorGetOSStatus(localError) == errSecDecode) {
const uint8_t *p=CFDataGetBytePtr(key);
secnotice("item", "Found corrupted item, removing from manifest key=%02X%02X%02X%02X, item=%@", p[0],p[1],p[2],p[3], item);
SOSDigestVectorAppend(&ds->todel, p);
CFReleaseNull(ds->manifest);
} else {
*stop=true;
}
if(error && *error == NULL) {
*error = localError;
localError = NULL;
}
}
CFRelease(item);
CFReleaseSafe(localError);
}
})) && SecDbReset(stmts[class_ix], error);
}
if (!gotItem) {
result = false;
if (error && !*error) {
SecCFCreateErrorWithFormat(kSecObjectNotFoundError, kSecItemDataSourceErrorDomain, NULL, error, 0, CFSTR("key %@ not in database"), key);
}
}
});
for (size_t class_ix = 0; class_ix < array_size(synced_classes); ++class_ix) {
result &= SecDbReleaseCachedStmt(dbconn, sqls[class_ix], stmts[class_ix], error);
CFReleaseSafe(sqls[class_ix]);
result &= query_destroy(queries[class_ix], error);
}
return result;
}
static void ds_dispose(SOSDataSourceRef data_source) {
struct SecItemDataSource *ds = (struct SecItemDataSource *)data_source;
if (ds->_dbconn)
SecDbConnectionRelease(ds->_dbconn);
if (ds->changed)
SecKeychainChanged(ds->syncWithPeersWhenDone);
CFReleaseSafe(ds->manifest);
SOSDigestVectorFree(&ds->dv);
free(ds);
}
static SOSObjectRef ds_create_with_property_list(SOSDataSourceRef ds, CFDictionaryRef plist, CFErrorRef *error) {
SecDbItemRef item = NULL;
const SecDbClass *class = NULL;
CFTypeRef cname = CFDictionaryGetValue(plist, kSecClass);
if (cname) {
class = kc_class_with_name(cname);
if (class) {
item = SecDbItemCreateWithAttributes(kCFAllocatorDefault, class, plist, KEYBAG_DEVICE, error);
} else {
SecError(errSecNoSuchClass, error, CFSTR("can find class named: %@"), cname);
}
} else {
SecError(errSecItemClassMissing, error, CFSTR("query missing %@ attribute"), kSecClass);
}
return (SOSObjectRef)item;
}
static CFDataRef ds_copy_digest(SOSObjectRef object, CFErrorRef *error) {
SecDbItemRef item = (SecDbItemRef) object;
CFDataRef digest = SecDbItemGetSHA1(item, error);
CFRetainSafe(digest);
return digest;
}
static CFDataRef ds_copy_primary_key(SOSObjectRef object, CFErrorRef *error) {
SecDbItemRef item = (SecDbItemRef) object;
CFDataRef pk = SecDbItemGetPrimaryKey(item, error);
CFRetainSafe(pk);
return pk;
}
static CFDictionaryRef ds_copy_property_list(SOSObjectRef object, CFErrorRef *error) {
SecDbItemRef item = (SecDbItemRef) object;
CFMutableDictionaryRef plist = SecDbItemCopyPListWithMask(item, kSecDbInCryptoDataFlag, error);
if (plist)
CFDictionaryAddValue(plist, kSecClass, SecDbItemGetClass(item)->name);
return plist;
}
static SOSObjectRef ds_copy_merged_object(SOSObjectRef object1, SOSObjectRef object2, CFErrorRef *error) {
SecDbItemRef item1 = (SecDbItemRef) object1;
SecDbItemRef item2 = (SecDbItemRef) object2;
SOSObjectRef result = NULL;
CFDateRef m1, m2;
const SecDbAttr *desc = SecDbAttrWithKey(SecDbItemGetClass(item1), kSecAttrModificationDate, error);
m1 = SecDbItemGetValue(item1, desc, error);
if (!m1)
return NULL;
m2 = SecDbItemGetValue(item2, desc, error);
if (!m2)
return NULL;
switch (CFDateCompare(m1, m2, NULL)) {
case kCFCompareGreaterThan:
result = (SOSObjectRef)item1;
break;
case kCFCompareLessThan:
result = (SOSObjectRef)item2;
break;
case kCFCompareEqualTo:
{
CFDataRef digest1 = ds_copy_digest(object1, error);
CFDataRef digest2 = ds_copy_digest(object2, error);
if (digest1 && digest2) switch (CFDataCompare(digest1, digest2)) {
case kCFCompareGreaterThan:
case kCFCompareEqualTo:
result = (SOSObjectRef)item2;
break;
case kCFCompareLessThan:
result = (SOSObjectRef)item1;
break;
}
CFReleaseSafe(digest2);
CFReleaseSafe(digest1);
break;
}
}
CFRetainSafe(result);
return result;
}
static SOSMergeResult dsMergeObject(SOSDataSourceRef data_source, SOSObjectRef peersObject, CFErrorRef *error) {
struct SecItemDataSource *ds = (struct SecItemDataSource *)data_source;
SecDbItemRef peersItem = (SecDbItemRef)peersObject;
SecDbConnectionRef dbconn = SecItemDataSourceGetConnection(ds, error);
__block SOSMergeResult mr = kSOSMergeFailure;
__block SecDbItemRef mergedItem = NULL;
__block SecDbItemRef replacedItem = NULL;
if (!peersItem || !dbconn || !SecDbItemSetKeybag(peersItem, KEYBAG_DEVICE, error)) return mr;
if (SecDbItemInsertOrReplace(peersItem, dbconn, error, ^(SecDbItemRef myItem, SecDbItemRef *replace) {
mergedItem = (SecDbItemRef)ds_copy_merged_object(peersObject, (SOSObjectRef)myItem, error);
if (!mergedItem) return;
if (CFEqual(mergedItem, myItem)) {
mr = kSOSMergeLocalObject;
} else {
CFRetainSafe(myItem);
replacedItem = myItem;
CFRetainSafe(mergedItem);
*replace = mergedItem;
if (CFEqual(mergedItem, peersItem)) {
mr = kSOSMergePeersObject;
} else {
mr = kSOSMergeCreatedObject;
}
}
})) {
if (mr == kSOSMergeFailure) {
mr = kSOSMergePeersObject;
SecItemDataSourceRecordAdd(ds, peersItem, error);
} else if (mr != kSOSMergeLocalObject) {
SecItemDataSourceRecordUpdate(ds, replacedItem, mergedItem, error);
}
}
if (error && *error && mr != kSOSMergeFailure)
CFReleaseNull(*error);
CFReleaseSafe(mergedItem);
CFReleaseSafe(replacedItem);
return mr;
}
enum {
kSecBackupIndexHash = 0,
kSecBackupIndexClass,
kSecBackupIndexData,
};
static const void *kSecBackupKeys[] = {
[kSecBackupIndexHash] = CFSTR("hash"),
[kSecBackupIndexClass] = CFSTR("class"),
[kSecBackupIndexData] = CFSTR("data"),
};
#define kSecBackupHash kSecBackupKeys[kSecBackupIndexHash]
#define kSecBackupClass kSecBackupKeys[kSecBackupIndexClass]
#define kSecBackupData kSecBackupKeys[kSecBackupIndexData]
static CFDictionaryRef ds_backup_object(SOSObjectRef object, uint64_t handle, CFErrorRef *error) {
const void *values[array_size(kSecBackupKeys)];
SecDbItemRef item = (SecDbItemRef)object;
CFDictionaryRef backup_item = NULL;
if ((values[kSecBackupIndexHash] = SecDbItemGetSHA1(item, error))) {
if ((values[kSecBackupIndexData] = SecDbItemCopyEncryptedDataToBackup(item, handle, error))) {
values[kSecBackupIndexClass] = SecDbItemGetClass(item)->name;
backup_item = CFDictionaryCreate(kCFAllocatorDefault, kSecBackupKeys, values, array_size(kSecBackupKeys), &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
CFRelease(values[kSecBackupIndexData]);
}
}
return backup_item;
}
static bool ds_restore_object(SOSDataSourceRef data_source, uint64_t handle, CFDictionaryRef item, CFErrorRef *error) {
struct SecItemDataSource *ds = (struct SecItemDataSource *)data_source;
SecDbConnectionRef dbconn = SecItemDataSourceGetConnection(ds, error);
if (!dbconn) return false;
CFStringRef item_class = CFDictionaryGetValue(item, kSecBackupClass);
CFDataRef data = CFDictionaryGetValue(item, kSecBackupData);
const SecDbClass *dbclass = NULL;
if (!item_class || !data)
return SecError(errSecDecode, error, CFSTR("no class or data in object"));
dbclass = kc_class_with_name(item_class);
if (!dbclass)
return SecError(errSecDecode, error, CFSTR("no such class %@; update kc_class_with_name "), item_class);
__block SecDbItemRef dbitem = SecDbItemCreateWithEncryptedData(kCFAllocatorDefault, dbclass, data, (keybag_handle_t)handle, error);
if (!dbitem)
return false;
__block bool ok = SecDbItemSetKeybag(dbitem, KEYBAG_DEVICE, error);
if (ok) {
__block SecDbItemRef replaced_item = NULL;
ok &= SecDbItemInsertOrReplace(dbitem, dbconn, error, ^(SecDbItemRef old_item, SecDbItemRef *replace) {
SecDbItemRef chosen_item = (SecDbItemRef)ds_copy_merged_object((SOSObjectRef)dbitem, (SOSObjectRef)old_item, error);
if (chosen_item) {
if (CFEqual(chosen_item, old_item)) {
CFRelease(chosen_item);
CFReleaseNull(dbitem);
} else {
CFRelease(dbitem); CFRetain(chosen_item); *replace = dbitem = chosen_item;
CFRetain(old_item);
replaced_item = old_item;
}
} else {
ok = false;
}
})
&& SecItemDataSourceRecordUpdate(ds, replaced_item, dbitem, error);
CFReleaseSafe(replaced_item);
}
CFReleaseSafe(dbitem);
return ok;
}
static SOSDataSourceRef SecItemDataSourceCreate(SecDbRef db, bool readOnly, bool syncWithPeersWhenDone, CFErrorRef *error) {
__block SecItemDataSourceRef ds = calloc(1, sizeof(struct SecItemDataSource));
ds->ds.get_manifest_digest = ds_get_manifest_digest;
ds->ds.copy_manifest = ds_copy_manifest;
ds->ds.foreach_object = ds_foreach_object;
ds->ds.release = ds_dispose;
ds->ds.add = dsMergeObject;
ds->ds.createWithPropertyList = ds_create_with_property_list;
ds->ds.copyDigest = ds_copy_digest;
ds->ds.copyPrimaryKey = ds_copy_primary_key;
ds->ds.copyPropertyList = ds_copy_property_list;
ds->ds.copyMergedObject = ds_copy_merged_object;
ds->ds.backupObject = ds_backup_object;
ds->ds.restoreObject = ds_restore_object;
ds->syncWithPeersWhenDone = syncWithPeersWhenDone;
ds->db = (SecDbRef)CFRetain(db);
ds->readOnly = readOnly;
ds->changed = false;
struct SOSDigestVector dv = SOSDigestVectorInit;
ds->dv = dv;
return (SOSDataSourceRef)ds;
}
static CFArrayRef SecItemDataSourceFactoryCopyNames(SOSDataSourceFactoryRef factory)
{
return CFArrayCreateForCFTypes(kCFAllocatorDefault,
kSecAttrAccessibleWhenUnlocked,
NULL);
}
struct SecItemDataSourceFactory {
struct SOSDataSourceFactory factory;
SecDbRef db;
};
static SOSDataSourceRef SecItemDataSourceFactoryCopyDataSource(SOSDataSourceFactoryRef factory, CFStringRef dataSourceName, bool readOnly, CFErrorRef *error)
{
struct SecItemDataSourceFactory *f = (struct SecItemDataSourceFactory *)factory;
return SecItemDataSourceCreate(f->db, readOnly, false, error);
}
static void SecItemDataSourceFactoryDispose(SOSDataSourceFactoryRef factory)
{
struct SecItemDataSourceFactory *f = (struct SecItemDataSourceFactory *)factory;
CFReleaseSafe(f->db);
free(f);
}
SOSDataSourceFactoryRef SecItemDataSourceFactoryCreate(SecDbRef db) {
struct SecItemDataSourceFactory *dsf = calloc(1, sizeof(struct SecItemDataSourceFactory));
dsf->factory.copy_names = SecItemDataSourceFactoryCopyNames;
dsf->factory.create_datasource = SecItemDataSourceFactoryCopyDataSource;
dsf->factory.release = SecItemDataSourceFactoryDispose;
CFRetainSafe(db);
dsf->db = db;
return &dsf->factory;
}
SOSDataSourceFactoryRef SecItemDataSourceFactoryCreateDefault(void) {
return SecItemDataSourceFactoryCreate(kc_dbhandle());
}
void SecItemServerAppendItemDescription(CFMutableStringRef desc, CFDictionaryRef object) {
SOSObjectRef item = ds_create_with_property_list(NULL, object, NULL);
if (item) {
CFStringRef itemDesc = CFCopyDescription(item);
if (itemDesc) {
CFStringAppend(desc, itemDesc);
CFReleaseSafe(itemDesc);
}
CFRelease(item);
}
}
bool
_SecServerKeychainSyncUpdate(CFDictionaryRef updates, CFErrorRef *error) {
CFRetainSafe(updates);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
SOSCCHandleUpdate(updates);
CFReleaseSafe(updates);
});
return true;
}
static CFStringRef SOSCopyItemKey(SOSDataSourceRef ds, SOSObjectRef object, CFErrorRef *error)
{
CFStringRef item_key = NULL;
CFDataRef digest_data = ds->copyDigest(object, error);
if (digest_data) {
item_key = CFDataCopyHexString(digest_data);
CFRelease(digest_data);
}
return item_key;
}
static SOSManifestRef SOSCopyManifestFromBackup(CFDictionaryRef backup)
{
CFMutableDataRef manifest = CFDataCreateMutable(kCFAllocatorDefault, 0);
if (backup) {
CFDictionaryForEach(backup, ^void (const void * key, const void * value) {
if (isDictionary(value)) {
CFDataRef sha1 = CFDictionaryGetValue(value, kSecBackupHash);
if (isData(sha1))
CFDataAppend(manifest, sha1);
}
});
}
return (SOSManifestRef)manifest;
}
static CFDictionaryRef
_SecServerCopyTruthInTheCloud(CFDataRef keybag, CFDataRef password,
CFDictionaryRef backup, CFErrorRef *error)
{
SOSManifestRef mold = NULL, mnow = NULL, mdelete = NULL, madd = NULL;
CFErrorRef foreachError = NULL;
CFDictionaryRef backup_out = NULL;
keybag_handle_t bag_handle;
if (!ks_open_keybag(keybag, password, &bag_handle, error))
return NULL;
CFMutableDictionaryRef backup_new = NULL;
SOSDataSourceRef ds = SecItemDataSourceCreate(kc_dbhandle(), true, false, error);
if (ds) {
backup_new = backup ? CFDictionaryCreateMutableCopy(kCFAllocatorDefault, 0, backup) : CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
mold = SOSCopyManifestFromBackup(backup);
mnow = ds->copy_manifest(ds, error);
SOSManifestDiff(mold, mnow, &mdelete, &madd, error);
SOSManifestForEach(mdelete, ^(CFDataRef digest_data) {
CFStringRef deleted_item_key = CFDataCopyHexString(digest_data);
CFDictionaryRemoveValue(backup_new, deleted_item_key);
CFRelease(deleted_item_key);
});
if(!ds->foreach_object(ds, madd, &foreachError, ^bool(SOSObjectRef object, CFErrorRef *localError) {
bool ok = true;
CFStringRef key = SOSCopyItemKey(ds, object, localError);
CFTypeRef value = ds->backupObject(object, bag_handle, localError);
if (!key || !value) {
ok = false;
} else {
CFDictionarySetValue(backup_new, key, value);
}
CFReleaseSafe(key);
CFReleaseSafe(value);
return ok;
})) {
if(!SecErrorGetOSStatus(foreachError)==errSecDecode) {
if(error && *error==NULL) {
*error = foreachError;
foreachError = NULL;
}
goto out;
}
}
backup_out = backup_new;
backup_new = NULL;
}
out:
if(ds)
ds->release(ds);
CFReleaseSafe(foreachError);
CFReleaseSafe(mold);
CFReleaseSafe(mnow);
CFReleaseSafe(madd);
CFReleaseSafe(mdelete);
CFReleaseSafe(backup_new);
if (!ks_close_keybag(bag_handle, error))
CFReleaseNull(backup_out);
return backup_out;
}
static bool
_SecServerRestoreTruthInTheCloud(CFDataRef keybag, CFDataRef password, CFDictionaryRef backup_in, CFErrorRef *error) {
__block bool ok = true;
keybag_handle_t bag_handle;
if (!ks_open_keybag(keybag, password, &bag_handle, error))
return false;
SOSManifestRef mbackup = SOSCopyManifestFromBackup(backup_in);
if (mbackup) {
SOSDataSourceRef ds = SecItemDataSourceCreate(kc_dbhandle(), false, true, error);
if (ds) {
SOSManifestRef mnow = ds->copy_manifest(ds, error);
SOSManifestRef mdelete = NULL, madd = NULL;
SOSManifestDiff(mnow, mbackup, &mdelete, &madd, error);
SOSManifestForEach(madd, ^void(CFDataRef e) {
CFDictionaryRef item = NULL;
CFStringRef sha1 = CFDataCopyHexString(e);
if (sha1) {
item = CFDictionaryGetValue(backup_in, sha1);
CFRelease(sha1);
}
if (item) {
CFErrorRef localError = NULL;
if (!ds->restoreObject(ds, bag_handle, item, &localError)) {
if (SecErrorGetOSStatus(localError) == errSecDuplicateItem) {
secnotice("titc", "restore %@ not replacing existing item", item);
} else {
secerror("restore %@ failed %@", item, localError);
ok = false;
if (error && !*error) {
*error = localError;
localError = NULL;
}
}
CFReleaseSafe(localError);
}
}
});
ds->release(ds);
CFReleaseNull(mdelete);
CFReleaseNull(madd);
CFReleaseNull(mnow);
} else {
ok = false;
}
CFRelease(mbackup);
}
ok &= ks_close_keybag(bag_handle, error);
return ok;
}
CF_RETURNS_RETAINED CFDictionaryRef
_SecServerBackupSyncable(CFDictionaryRef backup, CFDataRef keybag, CFDataRef password, CFErrorRef *error) {
require_action_quiet(isData(keybag), errOut, SecError(errSecParam, error, CFSTR("keybag %@ not a data"), keybag));
require_action_quiet(!backup || isDictionary(backup), errOut, SecError(errSecParam, error, CFSTR("backup %@ not a dictionary"), backup));
require_action_quiet(!password || isData(password), errOut, SecError(errSecParam, error, CFSTR("password %@ not a data"), password));
return _SecServerCopyTruthInTheCloud(keybag, password, backup, error);
errOut:
return NULL;
}
bool
_SecServerRestoreSyncable(CFDictionaryRef backup, CFDataRef keybag, CFDataRef password, CFErrorRef *error) {
bool ok;
require_action_quiet(isData(keybag), errOut, ok = SecError(errSecParam, error, CFSTR("keybag %@ not a data"), keybag));
require_action_quiet(isDictionary(backup), errOut, ok = SecError(errSecParam, error, CFSTR("backup %@ not a dictionary"), backup));
if (password) {
require_action_quiet(isData(password), errOut, ok = SecError(errSecParam, error, CFSTR("password not a data")));
}
ok = _SecServerRestoreTruthInTheCloud(keybag, password, backup, error);
errOut:
return ok;
}