SOSTestDataSource.c [plain text]
#include "SOSTestDataSource.h"
#include <corecrypto/ccder.h>
#include "keychain/SecureObjectSync/SOSDataSource.h"
#include "keychain/SecureObjectSync/SOSDigestVector.h"
#include <Security/SecureObjectSync/SOSViews.h>
#include <utilities/array_size.h>
#include <utilities/der_plist.h>
#include <utilities/SecCFError.h>
#include <utilities/SecCFWrappers.h>
#include <Security/SecItem.h>
#include <Security/SecItemPriv.h>
#include <AssertMacros.h>
CFStringRef sSOSDataSourceErrorDomain = CFSTR("com.apple.datasource");
typedef struct SOSTestDataSource *SOSTestDataSourceRef;
struct SOSTestDataSource {
struct SOSDataSource ds;
unsigned gm_count;
unsigned cm_count;
unsigned co_count;
CFMutableDictionaryRef d2database;
CFMutableDictionaryRef p2database;
CFMutableDictionaryRef statedb;
uint8_t manifest_digest[SOSDigestSize];
bool clean;
CFMutableArrayRef changes;
SOSDataSourceNotifyBlock notifyBlock;
};
typedef struct SOSTestDataSourceFactory *SOSTestDataSourceFactoryRef;
struct SOSTestDataSourceFactory {
struct SOSDataSourceFactory dsf;
CFMutableDictionaryRef data_sources;
};
static SOSManifestRef dsCopyManifestWithViewNameSet(SOSDataSourceRef data_source, CFSetRef viewNameSet, CFErrorRef *error) {
if (!CFSetContainsValue(viewNameSet, kSOSViewKeychainV0))
return SOSManifestCreateWithData(NULL, error);
struct SOSTestDataSource *ds = (struct SOSTestDataSource *)data_source;
ds->cm_count++;
__block struct SOSDigestVector dv = SOSDigestVectorInit;
CFDictionaryForEach(ds->d2database, ^(const void *key, const void *value) {
SOSDigestVectorAppend(&dv, CFDataGetBytePtr((CFDataRef)key));
});
SOSDigestVectorSort(&dv);
SOSManifestRef manifest = SOSManifestCreateWithDigestVector(&dv, error);
SOSDigestVectorFree(&dv);
ccdigest(ccsha1_di(), SOSManifestGetSize(manifest), SOSManifestGetBytePtr(manifest), ds->manifest_digest);
ds->clean = true;
return manifest;
}
static bool foreach_object(SOSDataSourceRef data_source, SOSTransactionRef txn, SOSManifestRef manifest, CFErrorRef *error, void (^handle_object)(CFDataRef key, SOSObjectRef object, bool *stop)) {
struct SOSTestDataSource *ds = (struct SOSTestDataSource *)data_source;
ds->co_count++;
__block bool result = true;
SOSManifestForEach(manifest, ^(CFDataRef key, bool *stop) {
handle_object(key, (SOSObjectRef)CFDictionaryGetValue(ds->d2database, key), stop);
});
return result;
}
static bool dispose(SOSDataSourceRef data_source, CFErrorRef *error) {
struct SOSTestDataSource *ds = (struct SOSTestDataSource *)data_source;
CFReleaseSafe(ds->d2database);
CFReleaseSafe(ds->p2database);
CFReleaseSafe(ds->statedb);
CFReleaseSafe(ds->changes);
free(ds);
return true;
}
static SOSObjectRef createWithPropertyList(CFDictionaryRef plist, CFErrorRef *error) {
return (SOSObjectRef)CFDictionaryCreateCopy(kCFAllocatorDefault, plist);
}
static CFDataRef SOSObjectCopyDER(SOSObjectRef object, CFErrorRef *error) {
CFDictionaryRef dict = (CFDictionaryRef)object;
size_t size = der_sizeof_plist(dict, error);
CFMutableDataRef data = CFDataCreateMutable(0, size);
if (data) {
CFDataSetLength(data, size);
uint8_t *der = (uint8_t *)CFDataGetMutableBytePtr(data);
uint8_t *der_end = der + size;
der_end = der_encode_plist(dict, error, der, der_end);
assert(der_end == der);
(void)der_end;
} else if (error && *error == NULL) {
*error = CFErrorCreate(0, sSOSDataSourceErrorDomain, kSOSDataSourceObjectMallocFailed, NULL);
}
return data;
}
static CFDataRef ccdigest_copy_data(const struct ccdigest_info *di, size_t len,
const void *data, CFErrorRef *error) {
CFMutableDataRef digest = CFDataCreateMutable(0, di->output_size);
if (digest) {
CFDataSetLength(digest, di->output_size);
ccdigest(di, len, data, CFDataGetMutableBytePtr(digest));
} else if (error && *error == NULL) {
*error = CFErrorCreate(0, sSOSDataSourceErrorDomain, kSOSDataSourceObjectMallocFailed, NULL);
}
return digest;
}
static CFDataRef copyDigest(SOSObjectRef object, CFErrorRef *error) {
CFMutableDictionaryRef ocopy = CFDictionaryCreateMutableCopy(kCFAllocatorDefault, 0, (CFDictionaryRef)object);
CFDictionaryRemoveValue(ocopy, kSecClass);
CFDataRef der = SOSObjectCopyDER((SOSObjectRef)ocopy, error);
CFRelease(ocopy);
CFDataRef digest = NULL;
if (der) {
digest = ccdigest_copy_data(ccsha1_di(), CFDataGetLength(der), CFDataGetBytePtr(der), error);
CFRelease(der);
}
return digest;
}
static CFDateRef copyModDate(SOSObjectRef object, CFErrorRef *error) {
return CFRetainSafe(asDate(CFDictionaryGetValue((CFDictionaryRef) object, kSecAttrModificationDate), NULL));
}
static CFDataRef copyPrimaryKey(SOSObjectRef object, CFErrorRef *error) {
CFMutableDictionaryRef ocopy = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault);
CFTypeRef pkNames[] = {
CFSTR("acct"),
CFSTR("agrp"),
CFSTR("svce"),
CFSTR("sync"),
CFSTR("sdmn"),
CFSTR("srvr"),
CFSTR("ptcl"),
CFSTR("atyp"),
CFSTR("port"),
CFSTR("path"),
CFSTR("ctyp"),
CFSTR("issr"),
CFSTR("slnr"),
CFSTR("kcls"),
CFSTR("klbl"),
CFSTR("atag"),
CFSTR("crtr"),
CFSTR("type"),
CFSTR("bsiz"),
CFSTR("esiz"),
CFSTR("sdat"),
CFSTR("edat"),
};
CFSetRef pkAttrs = CFSetCreate(kCFAllocatorDefault, pkNames, array_size(pkNames), &kCFTypeSetCallBacks);
CFDictionaryForEach((CFDictionaryRef)object, ^(const void *key, const void *value) {
if (CFSetContainsValue(pkAttrs, key))
CFDictionaryAddValue(ocopy, key, value);
});
CFRelease(pkAttrs);
CFDataRef der = SOSObjectCopyDER((SOSObjectRef)ocopy, error);
CFRelease(ocopy);
CFDataRef digest = NULL;
if (der) {
digest = ccdigest_copy_data(ccsha1_di(), CFDataGetLength(der), CFDataGetBytePtr(der), error);
CFRelease(der);
}
return digest;
}
static CFDictionaryRef copyPropertyList(SOSObjectRef object, CFErrorRef *error) {
return (CFDictionaryRef) CFRetain(object);
}
static SOSObjectRef copyMergedObject(SOSObjectRef object1, SOSObjectRef object2, CFErrorRef *error) {
CFDictionaryRef dict1 = (CFDictionaryRef)object1;
CFDictionaryRef dict2 = (CFDictionaryRef)object2;
SOSObjectRef result = NULL;
CFDateRef m1, m2;
m1 = CFDictionaryGetValue(dict1, kSecAttrModificationDate);
m2 = CFDictionaryGetValue(dict2, kSecAttrModificationDate);
switch (CFDateCompare(m1, m2, NULL)) {
case kCFCompareGreaterThan:
result = (SOSObjectRef)dict1;
break;
case kCFCompareLessThan:
result = (SOSObjectRef)dict2;
break;
case kCFCompareEqualTo:
{
CFDataRef digest1 = copyDigest(object1, error);
CFDataRef digest2 = copyDigest(object2, error);
if (digest1 && digest2) switch (CFDataCompare(digest1, digest2)) {
case kCFCompareGreaterThan:
case kCFCompareEqualTo:
result = (SOSObjectRef)dict2;
break;
case kCFCompareLessThan:
result = (SOSObjectRef)dict1;
break;
}
CFReleaseSafe(digest2);
CFReleaseSafe(digest1);
break;
}
}
CFRetainSafe(result);
return result;
}
static SOSMergeResult mergeObject(SOSTransactionRef txn, SOSObjectRef object, SOSObjectRef *mergedObject, CFErrorRef *error) {
SOSTestDataSourceRef ds = (SOSTestDataSourceRef)txn;
SOSMergeResult mr = kSOSMergeFailure;
CFDataRef pk = copyPrimaryKey(object, error);
if (!pk) return mr;
SOSObjectRef myObject = (SOSObjectRef)CFDictionaryGetValue(ds->p2database, pk);
if (myObject) {
SOSObjectRef merged = copyMergedObject(object, myObject, error);
if (mergedObject) *mergedObject = CFRetainSafe(merged);
if (CFEqualSafe(merged, myObject)) {
mr = kSOSMergeLocalObject;
} else if (CFEqualSafe(merged, object)) {
mr = kSOSMergePeersObject;
} else {
mr = kSOSMergeCreatedObject;
}
if (mr != kSOSMergeLocalObject) {
CFDataRef myKey = copyDigest(myObject, error);
CFDictionaryRemoveValue(ds->d2database, myKey);
CFReleaseSafe(myKey);
CFDataRef key = copyDigest(merged, error);
CFDictionarySetValue(ds->d2database, key, merged);
const void *values[2] = { myObject, merged };
CFTypeRef entry = CFArrayCreate(kCFAllocatorDefault, values, 2, &kCFTypeArrayCallBacks);
if (entry) {
CFArrayAppendValue(ds->changes, entry);
CFRelease(entry);
}
CFReleaseSafe(key);
CFDictionarySetValue(ds->p2database, pk, merged);
}
CFReleaseSafe(merged);
} else {
SOSTestDataSourceAddObject((SOSDataSourceRef)ds, object, error);
mr = kSOSMergePeersObject;
}
CFReleaseSafe(pk);
return mr;
}
static CFStringRef dsGetName(SOSDataSourceRef ds) {
return CFSTR("The sky is made of butterflies");
}
static void dsAddNotifyPhaseBlock(SOSDataSourceRef ds, SOSDataSourceNotifyBlock notifyBlock) {
SOSTestDataSourceRef tds = (SOSTestDataSourceRef)ds;
assert(tds->notifyBlock == NULL);
tds->notifyBlock = Block_copy(notifyBlock);
}
static CFDataRef dsCopyStateWithKey(SOSDataSourceRef ds, CFStringRef key, CFStringRef pdmn, SOSTransactionRef txn, CFErrorRef *error) {
SOSTestDataSourceRef tds = (SOSTestDataSourceRef)ds;
CFStringRef dbkey = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%@-%@"), pdmn, key);
CFDataRef state = CFDictionaryGetValue(tds->statedb, dbkey);
CFReleaseSafe(dbkey);
return CFRetainSafe(state);
}
static CFDataRef dsCopyItemDataWithKeys(SOSDataSourceRef data_source, CFDictionaryRef keys, CFErrorRef *error) {
SecError(errSecUnimplemented, error, CFSTR("dsCopyItemDataWithKeys on test data source not implemented"));
return NULL;
}
static bool dsWith(SOSDataSourceRef ds, CFErrorRef *error, SOSDataSourceTransactionSource source, bool onCommitQueue, void(^transaction)(SOSTransactionRef txn, bool *commit)) {
SOSTestDataSourceRef tds = (SOSTestDataSourceRef)ds;
bool commit = true;
transaction((SOSTransactionRef)ds, &commit);
if (commit && ((SOSTestDataSourceRef)ds)->notifyBlock && (CFArrayGetCount(tds->changes))) {
((SOSTestDataSourceRef)ds)->notifyBlock(ds, (SOSTransactionRef)ds, kSOSDataSourceTransactionWillCommit, source, tds->changes);
CFArrayRemoveAllValues(tds->changes);
}
return true;
}
static bool dsReadWith(SOSDataSourceRef ds, CFErrorRef *error, SOSDataSourceTransactionSource source, void(^perform)(SOSTransactionRef txn)) {
SOSTestDataSourceRef tds = (SOSTestDataSourceRef)ds;
perform((SOSTransactionRef)tds);
return true;
}
static bool dsSetStateWithKey(SOSDataSourceRef ds, SOSTransactionRef txn, CFStringRef key, CFStringRef pdmn, CFDataRef state, CFErrorRef *error) {
SOSTestDataSourceRef tds = (SOSTestDataSourceRef)ds;
CFStringRef dbkey = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%@-%@"), pdmn, key);
CFDictionarySetValue(tds->statedb, dbkey, state);
CFReleaseSafe(dbkey);
return true;
}
static bool dsRestoreObject(SOSTransactionRef txn, uint64_t handle, CFDictionaryRef item, CFErrorRef *error) {
assert(false);
return true;
}
static CFDictionaryRef objectCopyBackup(SOSObjectRef object, uint64_t handle, CFErrorRef *error) {
assert(false);
return NULL;
}
SOSDataSourceRef SOSTestDataSourceCreate(void) {
SOSTestDataSourceRef ds = calloc(1, sizeof(struct SOSTestDataSource));
ds->ds.engine = NULL;
ds->ds.dsGetName = dsGetName;
ds->ds.dsAddNotifyPhaseBlock = dsAddNotifyPhaseBlock;
ds->ds.dsCopyManifestWithViewNameSet = dsCopyManifestWithViewNameSet;
ds->ds.dsForEachObject = foreach_object;
ds->ds.dsCopyStateWithKey = dsCopyStateWithKey;
ds->ds.dsCopyItemDataWithKeys = dsCopyItemDataWithKeys;
ds->ds.dsWith = dsWith;
ds->ds.dsRelease = dispose;
ds->ds.dsReadWith = dsReadWith;
ds->ds.dsMergeObject = mergeObject;
ds->ds.dsSetStateWithKey = dsSetStateWithKey;
ds->ds.dsRestoreObject = dsRestoreObject;
ds->ds.objectCopyDigest = copyDigest;
ds->ds.objectCopyModDate = copyModDate;
ds->ds.objectCreateWithPropertyList = createWithPropertyList;
ds->ds.objectCopyPropertyList = copyPropertyList;
ds->ds.objectCopyBackup = objectCopyBackup;
ds->d2database = CFDictionaryCreateMutable(0, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
ds->p2database = CFDictionaryCreateMutable(0, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
ds->statedb = CFDictionaryCreateMutable(0, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
ds->clean = false;
ds->changes = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault);
return (SOSDataSourceRef)ds;
}
static CFStringRef SOSTestDataSourceFactoryCopyName(SOSDataSourceFactoryRef factory)
{
SOSTestDataSourceFactoryRef dsf = (SOSTestDataSourceFactoryRef) factory;
__block CFStringRef result = NULL;
CFDictionaryForEach(dsf->data_sources, ^(const void*key, const void*value) { if (isString(key)) result = key; });
return CFRetainSafe(result);
}
static SOSDataSourceRef SOSTestDataSourceFactoryCreateDataSource(SOSDataSourceFactoryRef factory, CFStringRef dataSourceName, CFErrorRef *error)
{
SOSTestDataSourceFactoryRef dsf = (SOSTestDataSourceFactoryRef) factory;
return (SOSDataSourceRef) CFDictionaryGetValue(dsf->data_sources, dataSourceName);
}
static void SOSTestDataSourceFactoryDispose(SOSDataSourceFactoryRef factory)
{
SOSTestDataSourceFactoryRef dsf = (SOSTestDataSourceFactoryRef) factory;
CFReleaseNull(dsf->data_sources);
free(dsf);
}
SOSDataSourceFactoryRef SOSTestDataSourceFactoryCreate() {
SOSTestDataSourceFactoryRef dsf = calloc(1, sizeof(struct SOSTestDataSourceFactory));
dsf->dsf.copy_name = SOSTestDataSourceFactoryCopyName;
dsf->dsf.create_datasource = SOSTestDataSourceFactoryCreateDataSource;
dsf->dsf.release = SOSTestDataSourceFactoryDispose;
dsf->data_sources = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, NULL);
return &(dsf->dsf);
}
static bool do_nothing(SOSDataSourceRef ds, CFErrorRef *error) {
return true;
}
void SOSTestDataSourceFactorySetDataSource(SOSDataSourceFactoryRef factory, CFStringRef name, SOSDataSourceRef ds)
{
SOSTestDataSourceFactoryRef dsf = (SOSTestDataSourceFactoryRef) factory;
ds->dsRelease = do_nothing;
CFDictionaryRemoveAllValues(dsf->data_sources);
CFDictionarySetValue(dsf->data_sources, name, ds);
}
SOSMergeResult SOSTestDataSourceAddObject(SOSDataSourceRef data_source, SOSObjectRef object, CFErrorRef *error) {
struct SOSTestDataSource *ds = (struct SOSTestDataSource *)data_source;
bool result = false;
CFDataRef key = copyDigest(object, error);
CFDataRef pk = copyPrimaryKey(object, error);
if (key && pk) {
SOSObjectRef myObject = (SOSObjectRef)CFDictionaryGetValue(ds->p2database, pk);
SOSObjectRef merged = NULL;
if (myObject) {
merged = copyMergedObject(object, myObject, error);
} else {
merged = object;
CFRetain(merged);
}
if (merged) {
result = true;
if (!CFEqualSafe(merged, myObject)) {
if (myObject) {
CFDataRef myKey = copyDigest(myObject, error);
CFDictionaryRemoveValue(ds->d2database, myKey);
CFReleaseSafe(myKey);
const void *values[2] = { myObject, merged };
CFTypeRef entry = CFArrayCreate(kCFAllocatorDefault, values, 2, &kCFTypeArrayCallBacks);
if (entry) {
CFArrayAppendValue(ds->changes, entry);
CFRelease(entry);
}
} else {
CFArrayAppendValue(ds->changes, merged);
}
CFDictionarySetValue(ds->d2database, key, merged);
CFDictionarySetValue(ds->p2database, pk, merged);
ds->clean = false;
}
CFRelease(merged);
}
}
CFReleaseSafe(pk);
CFReleaseSafe(key);
return result;
}
bool SOSTestDataSourceDeleteObject(SOSDataSourceRef data_source, CFDataRef key, CFErrorRef *error) {
return false;
}
CFMutableDictionaryRef SOSTestDataSourceGetDatabase(SOSDataSourceRef data_source) {
struct SOSTestDataSource *ds = (struct SOSTestDataSource *)data_source;
return ds->d2database;
}
SOSObjectRef SOSDataSourceCreateGenericItemWithData(SOSDataSourceRef ds, CFStringRef account, CFStringRef service, bool is_tomb, CFDataRef data) {
int32_t value = 0;
CFNumberRef zero = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &value);
value = 1;
CFNumberRef one = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &value);
CFAbsoluteTime timestamp = 3700000 + (is_tomb ? 1 : 0);
CFDateRef now = CFDateCreate(kCFAllocatorDefault, timestamp);
CFDataRef defaultData = NULL;
if (!is_tomb && !data) {
defaultData = CFDataCreate(NULL, (UInt8*)"some data", 9);
data = defaultData;
}
CFDictionaryRef dict = CFDictionaryCreateForCFTypes(kCFAllocatorDefault,
kSecClass, kSecClassGenericPassword,
kSecAttrSynchronizable, one,
kSecAttrTombstone, is_tomb ? one : zero,
kSecAttrAccount, account,
kSecAttrService, service,
kSecAttrCreationDate, now,
kSecAttrModificationDate, now,
kSecAttrAccessGroup, CFSTR("test"),
kSecAttrAccessible, kSecAttrAccessibleWhenUnlocked,
!is_tomb && data ? kSecValueData : NULL,data,
NULL);
CFRelease(one);
CFRelease(zero);
CFReleaseSafe(now);
CFReleaseNull(defaultData);
CFErrorRef localError = NULL;
SOSObjectRef object = ds->objectCreateWithPropertyList(dict, &localError);
if (!object) {
secerror("createWithPropertyList: %@ failed: %@", dict, localError);
CFRelease(localError);
}
CFRelease(dict);
return object;
}
SOSObjectRef SOSDataSourceCreateGenericItem(SOSDataSourceRef ds, CFStringRef account, CFStringRef service) {
return SOSDataSourceCreateGenericItemWithData(ds, account, service, false, NULL);
}
SOSObjectRef SOSDataSourceCreateV0EngineStateWithData(SOSDataSourceRef ds, CFDataRef engineStateData) {
CFAbsoluteTime timestamp = 3700000;
CFDateRef now = CFDateCreate(kCFAllocatorDefault, timestamp);
CFDictionaryRef item = CFDictionaryCreateForCFTypes(kCFAllocatorDefault,
kSecClass, kSecClassGenericPassword,
kSecAttrSynchronizable, kCFBooleanFalse,
kSecAttrTombstone, kCFBooleanFalse,
kSecAttrAccount, CFSTR("engine-state"),
kSecAttrService, CFSTR("SOSDataSource-ak"),
kSecAttrCreationDate, now,
kSecAttrModificationDate, now,
kSecAttrAccessGroup, CFSTR("com.apple.security.sos"),
kSecAttrAccessible, kSecAttrAccessibleAlwaysPrivate,
engineStateData ? kSecValueData : NULL, engineStateData,
NULL);
CFReleaseSafe(now);
CFErrorRef localError = NULL;
SOSObjectRef object = ds->objectCreateWithPropertyList(item, &localError);
if (!object) {
secerror("createWithPropertyList: %@ failed: %@", item, localError);
CFRelease(localError);
}
CFRelease(item);
return object;
}
SOSObjectRef SOSDataSourceCopyObject(SOSDataSourceRef ds, SOSObjectRef match, CFErrorRef *error)
{
__block SOSObjectRef result = NULL;
CFDataRef digest = SOSObjectCopyDigest(ds, match, error);
SOSManifestRef manifest = NULL;
require(digest, exit);
manifest = SOSManifestCreateWithData(digest, error);
SOSDataSourceForEachObject(ds, NULL, manifest, error, ^void (CFDataRef key, SOSObjectRef object, bool *stop) {
if (object == NULL) {
if (error && !*error) {
SecCFCreateErrorWithFormat(kSOSDataSourceObjectNotFoundError, sSOSDataSourceErrorDomain, NULL, error, 0, CFSTR("key %@ not in database"), key);
}
} else if (result == NULL) {
result = CFRetainSafe(object);
}
});
exit:
CFReleaseNull(manifest);
CFReleaseNull(digest);
return result;
}