SecItemDataSource.c [plain text]
#include "keychain/securityd/SecItemDataSource.h"
#include "keychain/securityd/SecItemDb.h"
#include "keychain/securityd/SecDbItem.h"
#include "keychain/securityd/SecItemSchema.h"
#include "keychain/securityd/SecItemServer.h"
#include "keychain/securityd/SOSCloudCircleServer.h"
#include "keychain/SecureObjectSync/SOSDigestVector.h"
#include "keychain/SecureObjectSync/SOSEngine.h"
#include <Security/SecureObjectSync/SOSViews.h>
#include <Security/SecBasePriv.h>
#include <Security/SecItem.h>
#include <Security/SecItemBackup.h>
#include <Security/SecItemPriv.h>
#include <utilities/array_size.h>
#include <keychain/ckks/CKKS.h>
typedef struct SecItemDataSource *SecItemDataSourceRef;
struct SecItemDataSource {
struct SOSDataSource ds;
SecDbRef db; CFStringRef name; };
static const SecDbClass *dsSyncedClassesV0Ptrs[] = {
NULL,
NULL,
NULL,
};
static size_t dsSyncedClassesV0Size = (array_size(dsSyncedClassesV0Ptrs));
static const SecDbClass** dsSyncedClassesV0() {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
dsSyncedClassesV0Ptrs[0] = genp_class();
dsSyncedClassesV0Ptrs[1] = inet_class();
dsSyncedClassesV0Ptrs[2] = keys_class();
});
return dsSyncedClassesV0Ptrs;
}
static const SecDbClass *dsSyncedClassesPtrs[] = {
NULL,
NULL,
NULL,
NULL,
};
static const size_t dsSyncedClassesSize = array_size(dsSyncedClassesPtrs);
static const SecDbClass** dsSyncedClasses() {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
dsSyncedClassesPtrs[0] = genp_class(); dsSyncedClassesPtrs[1] = inet_class();
dsSyncedClassesPtrs[2] = keys_class();
dsSyncedClassesPtrs[3] = cert_class();
});
return dsSyncedClassesPtrs;
}
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 SOSManifestRef SecItemDataSourceCopyManifestWithQueries(SecItemDataSourceRef ds, CFArrayRef queries, CFErrorRef *error) {
__block SOSManifestRef manifest = NULL;
__block CFErrorRef localError = NULL;
if (!kc_with_custom_db(false, true, ds->db, error, ^bool(SecDbConnectionRef dbconn) {
__block struct SOSDigestVector dv = SOSDigestVectorInit;
Query *q;
bool ok = true;
CFArrayForEachC(queries, q) {
if (!(ok &= SecDbItemSelectSHA1(q, dbconn, &localError, ^bool(const SecDbAttr *attr) {
return CFDictionaryContainsKey(q->q_item, attr->name);
}, 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(&dv, digest);
}
}))) {
secerror("SecDbItemSelectSHA1 failed: %@", localError);
break;
}
}
if (ok) {
manifest = SOSManifestCreateWithDigestVector(&dv, &localError);
}
SOSDigestVectorFree(&dv);
return ok;
})) {
CFReleaseSafe(manifest);
}
CFErrorPropagate(localError, error);
return manifest;
}
static Query *SecItemDataSourceAppendQuery(CFMutableArrayRef queries, const SecDbClass *qclass, bool noTombstones, CFErrorRef *error) {
Query *q = query_create(qclass, NULL, NULL, NULL, error);
if (q) {
q->q_return_type = kSecReturnDataMask | kSecReturnAttributesMask;
q->q_limit = kSecMatchUnlimited;
q->q_keybag = KEYBAG_DEVICE;
query_add_attribute(kSecAttrSynchronizable, kCFBooleanTrue, q);
query_add_or_attribute(kSecAttrAccessible, kSecAttrAccessibleWhenUnlocked, q);
query_add_or_attribute(kSecAttrAccessible, kSecAttrAccessibleAfterFirstUnlock, q);
query_add_or_attribute(kSecAttrAccessible, kSecAttrAccessibleAlwaysPrivate, q);
query_add_or_attribute(kSecAttrAccessible, kSecAttrAccessibleWhenUnlockedThisDeviceOnly, q);
query_add_or_attribute(kSecAttrAccessible, kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly, q);
query_add_or_attribute(kSecAttrAccessible, kSecAttrAccessibleAlwaysThisDeviceOnlyPrivate, q);
if (noTombstones) {
query_add_attribute(kSecAttrTombstone, kCFBooleanFalse, q);
}
CFArrayAppendValue(queries, q);
}
return q;
}
static Query *SecItemDataSourceAppendQueryWithClassAndViewHint(CFMutableArrayRef queries, const SecDbClass *qclass, bool noTombstones, bool allowTkid, CFStringRef viewHint, CFErrorRef *error) {
Query *q = SecItemDataSourceAppendQuery(queries, qclass, noTombstones, error);
if (q) {
SecDbForEachAttr(qclass, attr) {
if (attr == &v7tkid || attr == &v7vwht) {
if (!allowTkid || attr != &v7tkid) {
if (attr == &v7vwht && viewHint) {
query_add_attribute_with_desc(attr, viewHint, q);
} else {
CFTypeRef value = SecDbAttrCopyDefaultValue(attr, &q->q_error);
if (value) {
query_add_attribute_with_desc(attr, value, q);
CFRelease(value);
}
}
}
}
}
}
return q;
}
static Query *SecItemDataSourceAppendQueryWithClass(CFMutableArrayRef queries, const SecDbClass *qclass, bool noTombstones, bool allowTkid, CFErrorRef *error) {
return SecItemDataSourceAppendQueryWithClassAndViewHint(queries, qclass, noTombstones, allowTkid, NULL, error);
}
static Query *SecItemDataSourceAppendQueryWithClassAndAgrp(CFMutableArrayRef queries, const SecDbClass *qclass, bool noTombstones, bool allowTkid, CFStringRef agrp, CFErrorRef *error) {
Query *q = SecItemDataSourceAppendQueryWithClass(queries, qclass, noTombstones, allowTkid, error);
if (q && agrp) {
query_add_attribute(kSecAttrAccessGroup, agrp, q);
}
return q;
}
static bool SecItemDataSourceAppendQueriesForViewName(SecItemDataSourceRef ds, CFMutableArrayRef queries, CFStringRef compositeViewName, CFErrorRef *error) {
bool ok = true;
CFStringRef viewName;
bool noTombstones = CFStringHasSuffix(compositeViewName, CFSTR("-tomb"));
if (noTombstones) {
viewName = CFStringCreateWithSubstring(kCFAllocatorDefault, compositeViewName, CFRangeMake(0, CFStringGetLength(compositeViewName) - 5));
} else {
viewName = CFRetain(compositeViewName);
}
if(!SOSViewInSOSSystem(viewName)) {
CFReleaseSafe(viewName);
return ok;
}
const bool noTKID = false;
const bool allowTKID = true;
if (CFEqual(viewName, kSOSViewKeychainV0)) {
for (size_t class_ix = 0; class_ix < dsSyncedClassesV0Size; ++class_ix) {
SecItemDataSourceAppendQueryWithClass(queries, dsSyncedClassesV0()[class_ix], noTombstones, noTKID, error);
}
} else if (CFEqual(viewName, kSOSViewWiFi)) {
Query *q = SecItemDataSourceAppendQueryWithClassAndAgrp(queries, genp_class(), noTombstones, allowTKID, CFSTR("apple"), error);
if (q) {
query_add_attribute(kSecAttrService, CFSTR("AirPort"), q);
}
} else if (CFEqual(viewName, kSOSViewAutofillPasswords)) {
SecItemDataSourceAppendQueryWithClassAndAgrp(queries, inet_class(), noTombstones, allowTKID, CFSTR("com.apple.cfnetwork"), error);
} else if (CFEqual(viewName, kSOSViewSafariCreditCards)) {
SecItemDataSourceAppendQueryWithClassAndAgrp(queries, genp_class(), noTombstones, allowTKID, CFSTR("com.apple.safari.credit-cards"), error);
} else if (CFEqual(viewName, kSOSViewiCloudIdentity)) {
SecItemDataSourceAppendQueryWithClassAndAgrp(queries, keys_class(), noTombstones, allowTKID, CFSTR("com.apple.security.sos"), error);
} else if (CFEqual(viewName, kSOSViewBackupBagV0)) {
SecItemDataSourceAppendQueryWithClassAndAgrp(queries, genp_class(), noTombstones, allowTKID, CFSTR("com.apple.sbd"), error);
} else if (CFEqual(viewName, kSOSViewOtherSyncable)) {
SecItemDataSourceAppendQueryWithClass(queries, cert_class(), noTombstones, allowTKID, error);
Query *q1_genp = SecItemDataSourceAppendQueryWithClassAndAgrp(queries, genp_class(), noTombstones, allowTKID, CFSTR("apple"), error);
query_add_not_attribute(kSecAttrService, CFSTR("AirPort"), q1_genp);
Query *q2_genp = SecItemDataSourceAppendQueryWithClass(queries, genp_class(), noTombstones, allowTKID, error);
query_add_not_attribute(kSecAttrAccessGroup, CFSTR("apple"), q2_genp);
query_add_not_attribute(kSecAttrAccessGroup, CFSTR("com.apple.safari.credit-cards"), q2_genp);
query_add_not_attribute(kSecAttrAccessGroup, CFSTR("com.apple.sbd"), q2_genp);
Query *q_inet = SecItemDataSourceAppendQueryWithClass(queries, inet_class(), noTombstones, allowTKID, error);
query_add_not_attribute(kSecAttrAccessGroup, CFSTR("com.apple.cfnetwork"), q_inet);
Query *q_keys = SecItemDataSourceAppendQueryWithClass(queries, keys_class(), noTombstones, allowTKID, error);
query_add_not_attribute(kSecAttrAccessGroup, CFSTR("com.apple.security.sos"), q_keys);
} else {
for (size_t class_ix = 0; class_ix < dsSyncedClassesSize; ++class_ix) {
SecItemDataSourceAppendQueryWithClassAndViewHint(queries, dsSyncedClasses()[class_ix], noTombstones, allowTKID, viewName, error);
}
}
CFReleaseSafe(viewName);
return ok;
}
static SOSManifestRef SecItemDataSourceCopyManifestWithViewNameSet(SecItemDataSourceRef ds, CFSetRef viewNames, CFErrorRef *error) {
CFMutableArrayRef queries = CFArrayCreateMutable(kCFAllocatorDefault, 0, NULL);
SOSManifestRef manifest = NULL;
__block bool ok = true;
CFSetForEach(viewNames, ^(const void *value) {
CFStringRef viewName = (CFStringRef)value;
ok &= SecItemDataSourceAppendQueriesForViewName(ds, queries, viewName, error);
});
if (ok)
manifest = SecItemDataSourceCopyManifestWithQueries(ds, queries, error);
Query *q;
CFArrayForEachC(queries, q) {
CFErrorRef localError = NULL;
if (!query_destroy(q, &localError)) {
secerror("query_destroy failed: %@", localError);
CFErrorPropagate(localError, error);
CFReleaseNull(manifest);
}
}
CFReleaseSafe(queries);
return manifest;
}
static SecDbItemRef SecItemDataSourceCopyMergedItem(SecDbItemRef item1, SecDbItemRef item2, CFErrorRef *error) {
CFErrorRef localError = NULL;
SecDbItemRef result = NULL;
CFDateRef m1, m2;
const SecDbAttr *desc = SecDbAttrWithKey(SecDbItemGetClass(item1), kSecAttrModificationDate, error);
m1 = SecDbItemGetValue(item1, desc, &localError);
m2 = SecDbItemGetValue(item2, desc, &localError);
if (m1 && m2) switch (CFDateCompare(m1, m2, NULL)) {
case kCFCompareGreaterThan:
result = item1;
break;
case kCFCompareLessThan:
result = item2;
break;
case kCFCompareEqualTo:
{
CFDataRef digest1 = SecDbItemGetSHA1(item1, &localError);
CFDataRef digest2 = SecDbItemGetSHA1(item2, &localError);
if (digest1 && digest2) switch (CFDataCompareDERData(digest1, digest2)) {
case kCFCompareGreaterThan:
case kCFCompareEqualTo:
result = item2;
break;
case kCFCompareLessThan:
result = item1;
break;
} else if (SecErrorGetOSStatus(localError) == errSecDecode) {
if (digest1) result = item1;
if (digest2) result = item2;
}
break;
}
} else if (SecErrorGetOSStatus(localError) == errSecDecode) {
if (m1) result = item1;
if (m2) result = item2;
}
if (localError) {
if (!result && error && !*error)
*error = localError;
else
CFRelease(localError);
}
if(result && item2 && result != item2) {
SecDbForEachAttr(SecDbItemGetClass(result), attr) {
if(CFEqualSafe(attr->name, v10itemuuid.name)) {
SecItemPreserveAttribute(result, item2, attr);
}
}
SecDbForEachAttrWithMask(SecDbItemGetClass(result), attr, kSecDbSyncSOSCannotSyncFlag) {
SecItemPreserveAttribute(result, item2, attr);
}
}
return CFRetainSafe(result);
}
static CFStringRef dsGetName(SOSDataSourceRef data_source) {
SecItemDataSourceRef ds = (SecItemDataSourceRef)data_source;
return ds->name;
}
static void dsAddNotifyPhaseBlock(SOSDataSourceRef data_source, SOSDataSourceNotifyBlock notifyBlock) {
SecItemDataSourceRef ds = (SecItemDataSourceRef)data_source;
SecDbAddNotifyPhaseBlock(ds->db, ^(SecDbConnectionRef dbconn, SecDbTransactionPhase phase, SecDbTransactionSource source, CFArrayRef changes)
{
notifyBlock(&ds->ds, (SOSTransactionRef)dbconn, phase, source, changes);
});
}
static SOSManifestRef dsCopyManifestWithViewNameSet(SOSDataSourceRef data_source, CFSetRef viewNameSet, CFErrorRef *error) {
struct SecItemDataSource *ds = (struct SecItemDataSource *)data_source;
return SecItemDataSourceCopyManifestWithViewNameSet(ds, viewNameSet, error);
}
static bool dsForEachObject(SOSDataSourceRef data_source, SOSTransactionRef txn, SOSManifestRef manifest, CFErrorRef *error, void (^handle_object)(CFDataRef key, SOSObjectRef object, bool *stop)) {
struct SecItemDataSource *ds = (struct SecItemDataSource *)data_source;
__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;
};
Query *select_queries[dsSyncedClassesSize] = {};
CFStringRef select_sql[dsSyncedClassesSize] = {};
sqlite3_stmt *select_stmts[dsSyncedClassesSize] = {};
__block Query **queries = select_queries;
__block CFStringRef *sqls = select_sql;
__block sqlite3_stmt **stmts = select_stmts;
bool (^readBlock)(SecDbConnectionRef dbconn) = ^bool(SecDbConnectionRef dbconn)
{
for (size_t class_ix = 0; class_ix < dsSyncedClassesSize; ++class_ix) {
result = (result
&& (queries[class_ix] = query_create(dsSyncedClasses()[class_ix], NULL, NULL, 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, bool *stop) {
__block SecDbItemRef item = NULL;
for (size_t class_ix = 0; result && !item && class_ix < dsSyncedClassesSize; ++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);
if (result) {
result &= SecDbStep(dbconn, stmts[class_ix], error, ^(bool *unused_stop) {
item = SecDbItemCreateWithStatement(kCFAllocatorDefault, queries[class_ix]->q_class, stmts[class_ix], KEYBAG_DEVICE, error, return_attr);
});
}
if (result)
result &= SecDbReset(stmts[class_ix], error);
}
handle_object(key, (SOSObjectRef)item, stop);
CFReleaseSafe(item);
});
for (size_t class_ix = 0; class_ix < dsSyncedClassesSize; ++class_ix) {
result &= SecDbReleaseCachedStmt(dbconn, sqls[class_ix], stmts[class_ix], error);
CFReleaseSafe(sqls[class_ix]);
if (queries[class_ix])
result &= query_destroy(queries[class_ix], error);
}
return true;
};
if (txn) {
readBlock((SecDbConnectionRef)txn);
} else {
result &= kc_with_custom_db(false, true, ds->db, error, readBlock);
}
return result;
}
static bool dsRelease(SOSDataSourceRef data_source, CFErrorRef *error) {
return true;
}
static SOSObjectRef objectCreateWithPropertyList(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 copyObjectDigest(SOSObjectRef object, CFErrorRef *error) {
SecDbItemRef item = (SecDbItemRef) object;
CFDataRef digest = SecDbItemGetSHA1(item, error);
CFRetainSafe(digest);
return digest;
}
static CFDateRef copyObjectModDate(SOSObjectRef object, CFErrorRef *error) {
SecDbItemRef item = (SecDbItemRef) object;
CFDateRef modDate = SecDbItemGetValueKind(item, kSecDbModificationDateAttr, NULL);
CFRetainSafe(modDate);
return modDate;
}
static CFDictionaryRef objectCopyPropertyList(SOSObjectRef object, CFErrorRef *error) {
SecDbItemRef item = (SecDbItemRef) object;
CFMutableDictionaryRef secretDataDict = SecDbItemCopyPListWithFlagAndSkip(item, kSecDbReturnDataFlag, kSecDbSyncSOSCannotSyncFlag, error);
CFMutableDictionaryRef cryptoDataDict = SecDbItemCopyPListWithFlagAndSkip(item, kSecDbInCryptoDataFlag, kSecDbSyncSOSCannotSyncFlag, error);
CFMutableDictionaryRef authDataDict = SecDbItemCopyPListWithFlagAndSkip(item, kSecDbInAuthenticatedDataFlag, kSecDbSyncSOSCannotSyncFlag, error);
if (cryptoDataDict) {
if (authDataDict) {
CFDictionaryForEach(authDataDict, ^(const void *key, const void *value) {
CFDictionarySetValue(cryptoDataDict, key, value);
});
}
if (secretDataDict) {
CFDictionaryForEach(secretDataDict, ^(const void* key, const void* value) {
CFDictionarySetValue(cryptoDataDict, key, value);
});
}
CFDictionaryAddValue(cryptoDataDict, kSecClass, SecDbItemGetClass(item)->name);
}
CFReleaseNull(secretDataDict);
CFReleaseNull(authDataDict);
return cryptoDataDict;
}
static bool dsWith(SOSDataSourceRef data_source, CFErrorRef *error, SOSDataSourceTransactionSource source, bool onCommitQueue, void(^transaction)(SOSTransactionRef txn, bool *commit)) {
SecItemDataSourceRef ds = (SecItemDataSourceRef)data_source;
__block bool ok = true;
ok &= kc_with_custom_db(true, true, ds->db, error, ^bool(SecDbConnectionRef dbconn) {
return SecDbTransaction(dbconn,
source == kSOSDataSourceAPITransaction ? kSecDbExclusiveTransactionType : kSecDbExclusiveRemoteSOSTransactionType,
error, ^(bool *commit) {
if (onCommitQueue) {
SecDbPerformOnCommitQueue(dbconn, false, ^{
transaction((SOSTransactionRef)dbconn, commit);
});
} else {
transaction((SOSTransactionRef)dbconn, commit);
}
});
});
return ok;
}
static bool dsReadWith(SOSDataSourceRef data_source, CFErrorRef *error, SOSDataSourceTransactionSource source, void(^perform)(SOSTransactionRef txn)) {
SecItemDataSourceRef ds = (SecItemDataSourceRef)data_source;
__block bool ok = true;
ok &= kc_with_custom_db(false, true, ds->db, error, ^bool(SecDbConnectionRef dbconn) {
SecDbPerformOnCommitQueue(dbconn, false, ^{
perform((SOSTransactionRef)dbconn);
});
return true;
});
return ok;
}
static SOSMergeResult dsMergeObject(SOSTransactionRef txn, SOSObjectRef peersObject, SOSObjectRef *mergedObject, CFErrorRef *error) {
SecDbConnectionRef dbconn = (SecDbConnectionRef)txn;
SecDbItemRef peersItem = (SecDbItemRef)peersObject;
__block SOSMergeResult mr = kSOSMergeFailure;
__block SecDbItemRef mergedItem = NULL;
__block SecDbItemRef replacedItem = NULL;
__block CFErrorRef localError = NULL;
__block bool attemptedMerge = false;
if (!peersItem || !dbconn)
return kSOSMergeFailure;
if (!SecDbItemSetKeybag(peersItem, KEYBAG_DEVICE, &localError)) {
secnotice("ds", "kSOSMergeFailure => SecDbItemSetKeybag: %@", localError);
CFErrorPropagate(localError, error);
return kSOSMergeFailure;
}
bool insertedOrReplaced = SecDbItemInsertOrReplace(peersItem, dbconn, &localError, ^(SecDbItemRef myItem, SecDbItemRef *replace) {
attemptedMerge = true;
mergedItem = SecItemDataSourceCopyMergedItem(peersItem, myItem, &localError);
if (!mergedItem) {
mr = kSOSMergeFailure;
return; }
if (mergedObject)
*mergedObject = (SOSObjectRef)CFRetain(mergedItem);
if (CFEqual(mergedItem, myItem)) {
if (SecDbItemIsEngineInternalState(myItem))
secdebug ("ds", "Conflict resolver chose my (local) item: %@", myItem);
else
secnotice("ds", "Conflict resolver chose my (local) item: %@", myItem);
mr = kSOSMergeLocalObject;
} else {
CFRetainAssign(replacedItem, myItem);
*replace = CFRetainSafe(mergedItem);
if (CFEqual(mergedItem, peersItem)) {
if (SecDbItemIsEngineInternalState(peersItem))
secdebug ("ds", "Conflict resolver chose peers item: %@", peersItem);
else
secnotice("ds", "Conflict resolver chose peers item: %@", peersItem);
mr = kSOSMergePeersObject;
} else {
if (SecDbItemIsEngineInternalState(mergedItem))
secdebug ("ds", "Conflict resolver created a new item; return it to our caller: %@", mergedItem);
else
secnotice("ds", "Conflict resolver created a new item; return it to our caller: %@", mergedItem);
mr = kSOSMergeCreatedObject;
}
}
});
if (insertedOrReplaced && !attemptedMerge) {
secnotice("ds", "Insert succeeded for: %@", peersItem);
mr = kSOSMergePeersObject;
if (localError) {
secnotice("ds", "kSOSMergeFailure => kSOSMergePeersObject, %@", localError);
CFReleaseSafe(localError);
}
}
if (localError && !SecErrorIsSqliteDuplicateItemError(localError)) {
secnotice("ds", "dsMergeObject failed: mr=%ld, %@", mr, localError);
if (mr == kSOSMergeFailure) {
CFErrorPropagate(localError, error);
localError = NULL;
}
}
CFReleaseSafe(mergedItem);
CFReleaseSafe(replacedItem);
CFReleaseSafe(localError);
return mr;
}
enum {
kSecBackupIndexHash = 0,
kSecBackupIndexClass,
kSecBackupIndexData,
};
static const void *kSecBackupKeys[] = {
[kSecBackupIndexHash] = kSecItemBackupHashKey,
[kSecBackupIndexClass] = kSecItemBackupClassKey,
[kSecBackupIndexData] = kSecItemBackupDataKey
};
static CFDictionaryRef objectCopyBackup(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 CFDataRef dsCopyStateWithKey(SOSDataSourceRef data_source, CFStringRef key, CFStringRef pdmn, SOSTransactionRef txn, CFErrorRef *error) {
SecItemDataSourceRef ds = (SecItemDataSourceRef)data_source;
CFStringRef dataSourceID = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("SOSDataSource-%@"), ds->name);
CFMutableDictionaryRef dict = CFDictionaryCreateMutableForCFTypesWith(kCFAllocatorDefault,
kSecAttrAccessGroup, kSOSInternalAccessGroup,
kSecAttrAccount, key,
kSecAttrService, dataSourceID,
kSecAttrAccessible, pdmn,
kSecAttrSynchronizable, kCFBooleanFalse,
NULL);
CFReleaseSafe(dataSourceID);
__block CFDataRef data = NULL;
SecDbQueryRef query = query_create(genp_class(), NULL, dict, NULL, error);
if (query) {
if (query->q_item) CFReleaseSafe(query->q_item);
query->q_item = dict;
bool (^read_it)(SecDbConnectionRef dbconn) = ^(SecDbConnectionRef dbconn) {
return SecDbItemSelect(query, dbconn, error, NULL, ^bool(const SecDbAttr *attr) {
return CFDictionaryContainsKey(dict, attr->name);
}, NULL, NULL, ^(SecDbItemRef item, bool *stop) {
secnotice("ds", "found item for key %@@%@", key, pdmn);
data = CFRetainSafe(SecDbItemGetValue(item, &v6v_Data, error));
});
};
if (txn) {
read_it((SecDbConnectionRef) txn);
} else {
kc_with_custom_db(false, true, ds->db, error, read_it);
}
query_destroy(query, error);
} else {
CFReleaseSafe(dict);
}
if (!data) secnotice("ds", "failed to load %@@%@ state: %@", key, pdmn, error ? *error : NULL);
return data;
}
static CFDataRef dsCopyItemDataWithKeys(SOSDataSourceRef data_source, CFDictionaryRef keys, CFErrorRef *error) {
SecItemDataSourceRef ds = (SecItemDataSourceRef)data_source;
CFMutableDictionaryRef dict = CFDictionaryCreateMutableCopy(kCFAllocatorDefault, 0, keys);
__block CFDataRef data = NULL;
SecDbQueryRef query = query_create(genp_class(), NULL, dict, NULL, error);
if (query) {
if (query->q_item) CFReleaseSafe(query->q_item);
query->q_item = dict;
kc_with_custom_db(false, true, ds->db, error, ^bool(SecDbConnectionRef dbconn) {
return SecDbItemSelect(query, dbconn, error, NULL, ^bool(const SecDbAttr *attr) {
return CFDictionaryContainsKey(dict, attr->name);
}, NULL, NULL, ^(SecDbItemRef item, bool *stop) {
secnotice("ds", "found item for keys %@", keys);
data = CFRetainSafe(SecDbItemGetValue(item, &v6v_Data, error));
});
});
query_destroy(query, error);
} else {
CFReleaseSafe(dict);
}
if (!data) secnotice("ds", "failed to load item %@: %@", keys, error ? *error : NULL);
return data;
}
static bool dsSetStateWithKey(SOSDataSourceRef data_source, SOSTransactionRef txn, CFStringRef key, CFStringRef pdmn, CFDataRef state, CFErrorRef *error) {
SecItemDataSourceRef ds = (SecItemDataSourceRef)data_source;
CFStringRef dataSourceID = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("SOSDataSource-%@"), ds->name);
CFMutableDictionaryRef dict = CFDictionaryCreateMutableForCFTypesWith(kCFAllocatorDefault,
kSecAttrAccessGroup, kSOSInternalAccessGroup,
kSecAttrAccount, key,
kSecAttrService, dataSourceID,
kSecAttrAccessible, pdmn,
kSecAttrSynchronizable, kCFBooleanFalse,
kSecValueData, state,
NULL);
CFReleaseSafe(dataSourceID);
SecDbItemRef item = SecDbItemCreateWithAttributes(kCFAllocatorDefault, genp_class(), dict, KEYBAG_DEVICE, error);
SOSMergeResult mr = dsMergeObject(txn, (SOSObjectRef)item, NULL, error);
if (mr == kSOSMergeFailure) secerror("failed to save %@@%@ state: %@", key, pdmn, error ? *error : NULL);
CFReleaseSafe(item);
CFReleaseSafe(dict);
return mr != kSOSMergeFailure;
}
static bool dsDeleteStateWithKey(SOSDataSourceRef data_source, CFStringRef key, CFStringRef pdmn, SOSTransactionRef txn, CFErrorRef *error) {
SecItemDataSourceRef ds = (SecItemDataSourceRef)data_source;
CFStringRef dataSourceID = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("SOSDataSource-%@"), ds->name);
CFMutableDictionaryRef dict = CFDictionaryCreateMutableForCFTypesWith(kCFAllocatorDefault,
kSecAttrAccessGroup, kSOSInternalAccessGroup,
kSecAttrAccount, key,
kSecAttrService, dataSourceID,
kSecAttrAccessible, pdmn,
kSecAttrSynchronizable, kCFBooleanFalse,
NULL);
CFReleaseSafe(dataSourceID);
SecDbItemRef item = SecDbItemCreateWithAttributes(kCFAllocatorDefault, genp_class(), dict, KEYBAG_DEVICE, error);
bool ok = SecDbItemDoDeleteSilently(item, (SecDbConnectionRef)txn, error);
CFReleaseNull(dict);
CFReleaseSafe(item);
return ok;
}
static bool dsRestoreObject(SOSTransactionRef txn, uint64_t handle, CFDictionaryRef item, CFErrorRef *error) {
CFStringRef item_class = CFDictionaryGetValue(item, kSecItemBackupClassKey);
CFDataRef data = CFDictionaryGetValue(item, kSecItemBackupDataKey);
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);
SecDbItemRef dbitem = SecDbItemCreateWithEncryptedData(kCFAllocatorDefault, dbclass, data, (keybag_handle_t)handle, error);
bool ok = dbitem && (dsMergeObject(txn, (SOSObjectRef)dbitem, NULL, error) != kSOSMergeFailure);
CFReleaseSafe(dbitem);
return ok;
}
SOSDataSourceRef SecItemDataSourceCreate(SecDbRef db, CFStringRef name, CFErrorRef *error) {
SecItemDataSourceRef ds = calloc(1, sizeof(struct SecItemDataSource));
ds->ds.dsGetName = dsGetName;
ds->ds.dsAddNotifyPhaseBlock = dsAddNotifyPhaseBlock;
ds->ds.dsCopyManifestWithViewNameSet = dsCopyManifestWithViewNameSet;
ds->ds.dsCopyStateWithKey = dsCopyStateWithKey;
ds->ds.dsCopyItemDataWithKeys = dsCopyItemDataWithKeys;
ds->ds.dsForEachObject = dsForEachObject;
ds->ds.dsWith = dsWith;
ds->ds.dsReadWith = dsReadWith;
ds->ds.dsRelease = dsRelease;
ds->ds.dsMergeObject = dsMergeObject;
ds->ds.dsSetStateWithKey = dsSetStateWithKey;
ds->ds.dsDeleteStateWithKey = dsDeleteStateWithKey;
ds->ds.dsRestoreObject = dsRestoreObject;
ds->ds.objectCopyDigest = copyObjectDigest;
ds->ds.objectCopyModDate = copyObjectModDate;
ds->ds.objectCreateWithPropertyList = objectCreateWithPropertyList;
ds->ds.objectCopyPropertyList = objectCopyPropertyList;
ds->ds.objectCopyBackup = objectCopyBackup;
ds->db = CFRetainSafe(db);
ds->name = CFRetainSafe(name);
ds->ds.engine = SOSEngineCreate(&ds->ds, error);
if (!ds->ds.engine) {
free(ds);
ds = NULL;
}
return &ds->ds;
}
static CFStringRef SecItemDataSourceFactoryCopyName(SOSDataSourceFactoryRef factory)
{
return kSecAttrAccessibleWhenUnlocked;
}
struct SecItemDataSourceFactory {
struct SOSDataSourceFactory factory;
CFMutableDictionaryRef dsCache;
dispatch_queue_t queue;
SecDbRef db;
};
static SOSDataSourceRef SecItemDataSourceFactoryCopyDataSource(SOSDataSourceFactoryRef factory, CFStringRef dataSourceName, CFErrorRef *error)
{
struct SecItemDataSourceFactory *f = (struct SecItemDataSourceFactory *)factory;
__block SOSDataSourceRef dataSource = NULL;
dispatch_sync(f->queue, ^{
dataSource = (SOSDataSourceRef)CFDictionaryGetValue(f->dsCache, dataSourceName);
if (!dataSource && f->db) {
dataSource = (SOSDataSourceRef)SecItemDataSourceCreate(f->db, dataSourceName, error);
CFDictionarySetValue(f->dsCache, dataSourceName, dataSource);
}
});
return dataSource;
}
static void SecItemDataSourceFactoryDispose(SOSDataSourceFactoryRef factory)
{
}
static bool SOSDataSourceFactoryStartYourEngines(SOSDataSourceFactoryRef factory) {
#if OCTAGON
if(!SecCKKSTestDisableSOS() && !SecCKKSTestsEnabled()) {
#endif // OCTAGON
bool ok = true;
CFStringRef dsName = SOSDataSourceFactoryCopyName(factory);
CFErrorRef localError = NULL;
SOSDataSourceRef ds = SOSDataSourceFactoryCreateDataSource(factory, dsName, &localError);
if (!ds)
secerror("create_datasource %@ failed %@", dsName, localError);
CFReleaseNull(localError);
SOSDataSourceRelease(ds, &localError);
CFReleaseNull(localError);
CFReleaseNull(dsName);
return ok;
#if OCTAGON
} else {
return false;
}
#endif // OCTAGON
}
static SOSDataSourceFactoryRef SecItemDataSourceFactoryCreate(SecDbRef db) {
struct SecItemDataSourceFactory *dsf = calloc(1, sizeof(struct SecItemDataSourceFactory));
dsf->factory.copy_name = SecItemDataSourceFactoryCopyName;
dsf->factory.create_datasource = SecItemDataSourceFactoryCopyDataSource;
dsf->factory.release = SecItemDataSourceFactoryDispose;
dsf->dsCache = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, NULL);
dsf->queue = dispatch_queue_create("dsf queue", DISPATCH_QUEUE_SERIAL);
dsf->db = CFRetainSafe(db);
if (!SOSDataSourceFactoryStartYourEngines(&dsf->factory))
secerror("Failed to start engines, gonna lose the race.");
return &dsf->factory;
}
static dispatch_once_t sDSFQueueOnce;
static dispatch_queue_t sDSFQueue;
static CFMutableDictionaryRef sDSTable = NULL;
void SecItemDataSourceFactoryReleaseAll() {
(void) SecItemDataSourceFactoryGetShared(nil);
dispatch_sync(sDSFQueue, ^{
if(sDSTable) {
CFDictionaryRemoveAllValues(sDSTable);
}
});
}
SOSDataSourceFactoryRef SecItemDataSourceFactoryGetShared(SecDbRef db) {
dispatch_once(&sDSFQueueOnce, ^{
sDSFQueue = dispatch_queue_create("dataSourceFactory queue", DISPATCH_QUEUE_SERIAL);
sDSTable = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, NULL);
});
__block SOSDataSourceFactoryRef result = NULL;
dispatch_sync(sDSFQueue, ^{
if(db) {
CFStringRef dbPath = SecDbGetPath(db);
if(dbPath) {
result = (SOSDataSourceFactoryRef) CFDictionaryGetValue(sDSTable, dbPath);
if(!result) {
result = SecItemDataSourceFactoryCreate(db);
CFDictionaryAddValue(sDSTable, dbPath, result);
}
}
}
});
return result;
}
void SecItemServerAppendItemDescription(CFMutableStringRef desc, CFDictionaryRef object) {
SOSObjectRef item = objectCreateWithPropertyList(object, NULL);
if (item) {
CFStringRef itemDesc = CFCopyDescription(item);
if (itemDesc) {
CFStringAppend(desc, itemDesc);
CFReleaseSafe(itemDesc);
}
CFRelease(item);
}
}
SOSManifestRef SOSCreateManifestWithBackup(CFDictionaryRef backup, CFErrorRef *error)
{
__block struct SOSDigestVector dv = SOSDigestVectorInit;
if (backup) {
CFDictionaryForEach(backup, ^void (const void * key, const void * value) {
if (isDictionary(value)) {
CFDataRef sha1 = CFDictionaryGetValue(value, kSecItemBackupHashKey);
if (isData(sha1) && CFDataGetLength(sha1) == CCSHA1_OUTPUT_SIZE)
SOSDigestVectorAppend(&dv, CFDataGetBytePtr(sha1));
}
});
}
SOSManifestRef manifest = SOSManifestCreateWithDigestVector(&dv, error);
SOSDigestVectorFree(&dv);
return manifest;
}