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