1// 2// SOSTestDataSource.c 3// sec 4// 5// Created by Michael Brouwer on 9/28/12. 6// 7// 8 9#include "SOSTestDataSource.h" 10 11#include <corecrypto/ccder.h> 12#include <SecureObjectSync/SOSEngine.h> 13#include <utilities/array_size.h> 14#include <utilities/der_plist.h> 15#include <utilities/SecCFError.h> 16#include <utilities/SecCFWrappers.h> 17#include <Security/SecItemPriv.h> 18 19static CFStringRef sErrorDomain = CFSTR("com.apple.testdatasource"); 20 21enum { 22 kSOSObjectMallocFailed = 1, 23 kAddDuplicateEntry, 24 kSOSObjectNotFouncError = 1, 25}; 26 27typedef struct SOSTestDataSource *SOSTestDataSourceRef; 28 29struct SOSTestDataSource { 30 struct SOSDataSource ds; 31 unsigned gm_count; 32 unsigned cm_count; 33 unsigned co_count; 34 CFMutableDictionaryRef database; 35 uint8_t manifest_digest[SOSDigestSize]; 36 bool clean; 37}; 38 39typedef struct SOSTestDataSourceFactory *SOSTestDataSourceFactoryRef; 40 41struct SOSTestDataSourceFactory { 42 struct SOSDataSourceFactory dsf; 43 CFMutableDictionaryRef data_sources; 44}; 45 46 47/* DataSource protocol. */ 48static bool get_manifest_digest(SOSDataSourceRef data_source, uint8_t *out_digest, CFErrorRef *error) { 49 struct SOSTestDataSource *ds = (struct SOSTestDataSource *)data_source; 50 if (!ds->clean) { 51 SOSManifestRef mf = data_source->copy_manifest(data_source, error); 52 if (mf) { 53 CFRelease(mf); 54 } else { 55 return false; 56 } 57 } 58 memcpy(out_digest, ds->manifest_digest, SOSDigestSize); 59 ds->gm_count++; 60 return true; 61} 62 63static SOSManifestRef copy_manifest(SOSDataSourceRef data_source, CFErrorRef *error) { 64 struct SOSTestDataSource *ds = (struct SOSTestDataSource *)data_source; 65 ds->cm_count++; 66 __block struct SOSDigestVector dv = SOSDigestVectorInit; 67 CFDictionaryForEach(ds->database, ^(const void *key, const void *value) { 68 SOSDigestVectorAppend(&dv, CFDataGetBytePtr((CFDataRef)key)); 69 }); 70 SOSDigestVectorSort(&dv); 71 SOSManifestRef manifest = SOSManifestCreateWithBytes((const uint8_t *)dv.digest, dv.count * SOSDigestSize, error); 72 SOSDigestVectorFree(&dv); 73 ccdigest(ccsha1_di(), SOSManifestGetSize(manifest), SOSManifestGetBytePtr(manifest), ds->manifest_digest); 74 ds->clean = true; 75 76 return manifest; 77} 78 79static bool foreach_object(SOSDataSourceRef data_source, SOSManifestRef manifest, CFErrorRef *error, bool (^handle_object)(SOSObjectRef object, CFErrorRef *error)) { 80 struct SOSTestDataSource *ds = (struct SOSTestDataSource *)data_source; 81 ds->co_count++; 82 __block bool result = true; 83 SOSManifestForEach(manifest, ^(CFDataRef key) { 84 CFDictionaryRef dict = (CFDictionaryRef)CFDictionaryGetValue(ds->database, key); 85 if (dict) { 86 result = result && handle_object((SOSObjectRef)dict, error); 87 } else { 88 result = false; 89 if (error) { 90 // TODO: Collect all missing keys in an array and return an single error at the end with all collected keys 91 // Collect all errors as chained errors. 92 CFErrorRef old_error = *error; 93 *error = NULL; 94 SecCFCreateErrorWithFormat(kSOSObjectNotFouncError, sErrorDomain, old_error, error, 0, CFSTR("key %@ not in database"), key); 95 } 96 } 97 }); 98 return result; 99} 100 101static void dispose(SOSDataSourceRef data_source) { 102 struct SOSTestDataSource *ds = (struct SOSTestDataSource *)data_source; 103 free(ds); 104} 105 106static SOSObjectRef createWithPropertyList(SOSDataSourceRef ds, CFDictionaryRef plist, CFErrorRef *error) { 107 return (SOSObjectRef)CFDictionaryCreateCopy(kCFAllocatorDefault, plist); 108} 109 110static CFDataRef SOSObjectCopyDER(SOSObjectRef object, CFErrorRef *error) { 111 CFDictionaryRef dict = (CFDictionaryRef)object; 112 size_t size = der_sizeof_plist(dict, error); 113 CFMutableDataRef data = CFDataCreateMutable(0, size); 114 if (data) { 115 CFDataSetLength(data, size); 116 uint8_t *der = (uint8_t *)CFDataGetMutableBytePtr(data); 117 uint8_t *der_end = der + size; 118 der_end = der_encode_plist(dict, error, der, der_end); 119 assert(der_end == der); 120 } else if (error && *error == NULL) { 121 *error = CFErrorCreate(0, sErrorDomain, kSOSObjectMallocFailed, NULL); 122 } 123 return data; 124} 125 126static CFDataRef ccdigest_copy_data(const struct ccdigest_info *di, size_t len, 127 const void *data, CFErrorRef *error) { 128 CFMutableDataRef digest = CFDataCreateMutable(0, di->output_size); 129 if (digest) { 130 CFDataSetLength(digest, di->output_size); 131 ccdigest(di, len, data, CFDataGetMutableBytePtr(digest)); 132 } else if (error && *error == NULL) { 133 *error = CFErrorCreate(0, sErrorDomain, kSOSObjectMallocFailed, NULL); 134 } 135 return digest; 136} 137 138static CFDataRef copyDigest(SOSObjectRef object, CFErrorRef *error) { 139 CFMutableDictionaryRef ocopy = CFDictionaryCreateMutableCopy(kCFAllocatorDefault, 0, (CFDictionaryRef)object); 140 CFDictionaryRemoveValue(ocopy, kSecClass); 141 CFDataRef der = SOSObjectCopyDER((SOSObjectRef)ocopy, error); 142 CFRelease(ocopy); 143 CFDataRef digest = NULL; 144 if (der) { 145 digest = ccdigest_copy_data(ccsha1_di(), CFDataGetLength(der), CFDataGetBytePtr(der), error); 146 CFRelease(der); 147 } 148 return digest; 149} 150 151static CFDataRef copyPrimaryKey(SOSObjectRef object, CFErrorRef *error) { 152 CFMutableDictionaryRef ocopy = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault); 153 CFTypeRef pkNames[] = { 154 CFSTR("acct"), 155 CFSTR("agrp"), 156 CFSTR("svce"), 157 CFSTR("sync"), 158 CFSTR("sdmn"), 159 CFSTR("srvr"), 160 CFSTR("ptcl"), 161 CFSTR("atyp"), 162 CFSTR("port"), 163 CFSTR("path"), 164 CFSTR("ctyp"), 165 CFSTR("issr"), 166 CFSTR("slnr"), 167 CFSTR("kcls"), 168 CFSTR("klbl"), 169 CFSTR("atag"), 170 CFSTR("crtr"), 171 CFSTR("type"), 172 CFSTR("bsiz"), 173 CFSTR("esiz"), 174 CFSTR("sdat"), 175 CFSTR("edat"), 176 }; 177 CFSetRef pkAttrs = CFSetCreate(kCFAllocatorDefault, pkNames, array_size(pkNames), &kCFTypeSetCallBacks); 178 CFDictionaryForEach((CFDictionaryRef)object, ^(const void *key, const void *value) { 179 if (CFSetContainsValue(pkAttrs, key)) 180 CFDictionaryAddValue(ocopy, key, value); 181 }); 182 CFRelease(pkAttrs); 183 CFDataRef der = SOSObjectCopyDER((SOSObjectRef)ocopy, error); 184 CFRelease(ocopy); 185 CFDataRef digest = NULL; 186 if (der) { 187 digest = ccdigest_copy_data(ccsha1_di(), CFDataGetLength(der), CFDataGetBytePtr(der), error); 188 CFRelease(der); 189 } 190 return digest; 191} 192 193static CFDictionaryRef copyPropertyList(SOSObjectRef object, CFErrorRef *error) { 194 return (CFDictionaryRef) CFRetain(object); 195} 196 197// Return the newest object 198static SOSObjectRef copyMergedObject(SOSObjectRef object1, SOSObjectRef object2, CFErrorRef *error) { 199 CFDictionaryRef dict1 = (CFDictionaryRef)object1; 200 CFDictionaryRef dict2 = (CFDictionaryRef)object2; 201 SOSObjectRef result = NULL; 202 CFDateRef m1, m2; 203 m1 = CFDictionaryGetValue(dict1, kSecAttrModificationDate); 204 m2 = CFDictionaryGetValue(dict2, kSecAttrModificationDate); 205 switch (CFDateCompare(m1, m2, NULL)) { 206 case kCFCompareGreaterThan: 207 result = (SOSObjectRef)dict1; 208 break; 209 case kCFCompareLessThan: 210 result = (SOSObjectRef)dict2; 211 break; 212 case kCFCompareEqualTo: 213 { 214 // Return the item with the smallest digest. 215 CFDataRef digest1 = copyDigest(object1, error); 216 CFDataRef digest2 = copyDigest(object2, error); 217 if (digest1 && digest2) switch (CFDataCompare(digest1, digest2)) { 218 case kCFCompareGreaterThan: 219 case kCFCompareEqualTo: 220 result = (SOSObjectRef)dict2; 221 break; 222 case kCFCompareLessThan: 223 result = (SOSObjectRef)dict1; 224 break; 225 } 226 CFReleaseSafe(digest2); 227 CFReleaseSafe(digest1); 228 break; 229 } 230 } 231 CFRetainSafe(result); 232 return result; 233} 234 235SOSDataSourceRef SOSTestDataSourceCreate(void) { 236 SOSTestDataSourceRef ds = calloc(1, sizeof(struct SOSTestDataSource)); 237 238 ds->ds.get_manifest_digest = get_manifest_digest; 239 ds->ds.copy_manifest = copy_manifest; 240 ds->ds.foreach_object = foreach_object; 241 ds->ds.release = dispose; 242 ds->ds.add = SOSTestDataSourceAddObject; 243 244 ds->ds.createWithPropertyList = createWithPropertyList; 245 ds->ds.copyDigest = copyDigest; 246 ds->ds.copyPrimaryKey = copyPrimaryKey; 247 ds->ds.copyPropertyList = copyPropertyList; 248 ds->ds.copyMergedObject = copyMergedObject; 249 250 ds->database = CFDictionaryCreateMutable(0, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); 251 ds->clean = false; 252 253 return (SOSDataSourceRef)ds; 254} 255 256static CFArrayRef SOSTestDataSourceFactoryCopyNames(SOSDataSourceFactoryRef factory) 257{ 258 SOSTestDataSourceFactoryRef dsf = (SOSTestDataSourceFactoryRef) factory; 259 CFMutableArrayRef result = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault); 260 261 CFDictionaryForEach(dsf->data_sources, ^(const void*key, const void*value) { CFArrayAppendValue(result, key); }); 262 263 return result; 264} 265 266static SOSDataSourceRef SOSTestDataSourceFactoryCreateDataSource(SOSDataSourceFactoryRef factory, CFStringRef dataSourceName, bool readOnly __unused, CFErrorRef *error) 267{ 268 SOSTestDataSourceFactoryRef dsf = (SOSTestDataSourceFactoryRef) factory; 269 270 return (SOSDataSourceRef) CFDictionaryGetValue(dsf->data_sources, dataSourceName); 271} 272 273static void SOSTestDataSourceFactoryDispose(SOSDataSourceFactoryRef factory) 274{ 275 SOSTestDataSourceFactoryRef dsf = (SOSTestDataSourceFactoryRef) factory; 276 277 CFReleaseNull(dsf->data_sources); 278 free(dsf); 279} 280 281SOSDataSourceFactoryRef SOSTestDataSourceFactoryCreate() { 282 SOSTestDataSourceFactoryRef dsf = calloc(1, sizeof(struct SOSTestDataSourceFactory)); 283 284 dsf->dsf.copy_names = SOSTestDataSourceFactoryCopyNames; 285 dsf->dsf.create_datasource = SOSTestDataSourceFactoryCreateDataSource; 286 dsf->dsf.release = SOSTestDataSourceFactoryDispose; 287 dsf->data_sources = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, NULL); 288 289 return &(dsf->dsf); 290} 291 292static void do_nothing(SOSDataSourceRef ds) 293{ 294} 295 296void SOSTestDataSourceFactoryAddDataSource(SOSDataSourceFactoryRef factory, CFStringRef name, SOSDataSourceRef ds) 297{ 298 SOSTestDataSourceFactoryRef dsf = (SOSTestDataSourceFactoryRef) factory; 299 300 // TODO This hack sucks. It leaks now. 301 ds->release = do_nothing; 302 303 CFDictionarySetValue(dsf->data_sources, name, ds); 304 305} 306 307SOSMergeResult SOSTestDataSourceAddObject(SOSDataSourceRef data_source, SOSObjectRef object, CFErrorRef *error) { 308 struct SOSTestDataSource *ds = (struct SOSTestDataSource *)data_source; 309 bool result = false; 310 CFDataRef key = copyDigest(object, error); 311 if (key) { 312 SOSObjectRef myObject = (SOSObjectRef)CFDictionaryGetValue(ds->database, key); 313 SOSObjectRef merged = NULL; 314 if (myObject) { 315 merged = copyMergedObject(object, myObject, error); 316 } else { 317 merged = object; 318 CFRetain(merged); 319 } 320 if (merged) { 321 result = true; 322 if (!CFEqualSafe(merged, myObject)) { 323 CFDictionarySetValue(ds->database, key, merged); 324 ds->clean = false; 325 } 326 CFRelease(merged); 327 } 328 CFRelease(key); 329 } 330 return result; 331} 332 333bool SOSTestDataSourceDeleteObject(SOSDataSourceRef data_source, CFDataRef key, CFErrorRef *error) { 334 //struct SOSTestDataSource *ds = (struct SOSTestDataSource *)data_source; 335 return false; 336} 337 338CFMutableDictionaryRef SOSTestDataSourceGetDatabase(SOSDataSourceRef data_source) { 339 struct SOSTestDataSource *ds = (struct SOSTestDataSource *)data_source; 340 return ds->database; 341} 342 343// This works for any datasource, not just the test one, but it's only used in testcases, so it's here for now. 344SOSObjectRef SOSDataSourceCreateGenericItemWithData(SOSDataSourceRef ds, CFStringRef account, CFStringRef service, bool is_tomb, CFDataRef data) { 345#if (TARGET_OS_MAC && !(TARGET_OS_EMBEDDED || TARGET_OS_IPHONE)) 346 abort(); 347#else 348 int32_t value = 0; 349 CFNumberRef zero = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &value); 350 value = 1; 351 CFNumberRef one = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &value); 352 CFAbsoluteTime timestamp = 3700000; 353 CFDateRef now = CFDateCreate(kCFAllocatorDefault, timestamp); 354 CFDictionaryRef dict = CFDictionaryCreateForCFTypes(kCFAllocatorDefault, 355 kSecClass, kSecClassGenericPassword, 356 kSecAttrSynchronizable, one, 357 kSecAttrTombstone, is_tomb ? one : zero, 358 kSecAttrAccount, account, 359 kSecAttrService, service, 360 kSecAttrCreationDate, now, 361 kSecAttrModificationDate, now, 362 kSecAttrAccessGroup, CFSTR("test"), 363 kSecAttrAccessible, kSecAttrAccessibleWhenUnlocked, 364 !is_tomb && data ? kSecValueData : NULL,data, 365 NULL); 366 CFRelease(one); 367 CFRelease(zero); 368 CFReleaseSafe(now); 369 CFErrorRef localError = NULL; 370 SOSObjectRef object = ds->createWithPropertyList(ds, dict, &localError); 371 if (!object) { 372 secerror("createWithPropertyList: %@ failed: %@", dict, localError); 373 CFRelease(localError); 374 } 375 CFRelease(dict); 376 return object; 377#endif 378} 379 380SOSObjectRef SOSDataSourceCreateGenericItem(SOSDataSourceRef ds, CFStringRef account, CFStringRef service) { 381 return SOSDataSourceCreateGenericItemWithData(ds, account, service, false, NULL); 382} 383