2 * Copyright (c) 2012-2014 Apple Inc. All Rights Reserved.
4 * @APPLE_LICENSE_HEADER_START@
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
21 * @APPLE_LICENSE_HEADER_END@
25 #include "SOSTestDataSource.h"
27 #include <corecrypto/ccder.h>
28 #include <Security/SecureObjectSync/SOSDataSource.h>
29 #include <Security/SecureObjectSync/SOSDigestVector.h>
30 #include <utilities/array_size.h>
31 #include <utilities/der_plist.h>
32 #include <utilities/SecCFError.h>
33 #include <utilities/SecCFWrappers.h>
34 #include <Security/SecItem.h>
35 #include <Security/SecItemPriv.h>
36 #include <AssertMacros.h>
38 CFStringRef sSOSDataSourceErrorDomain
= CFSTR("com.apple.datasource");
40 typedef struct SOSTestDataSource
*SOSTestDataSourceRef
;
42 struct SOSTestDataSource
{
43 struct SOSDataSource ds
;
47 CFMutableDictionaryRef d2database
;
48 CFMutableDictionaryRef p2database
;
49 CFMutableDictionaryRef statedb
;
50 uint8_t manifest_digest
[SOSDigestSize
];
53 CFMutableArrayRef changes
;
54 SOSDataSourceNotifyBlock notifyBlock
;
57 typedef struct SOSTestDataSourceFactory
*SOSTestDataSourceFactoryRef
;
59 struct SOSTestDataSourceFactory
{
60 struct SOSDataSourceFactory dsf
;
61 CFMutableDictionaryRef data_sources
;
65 /* DataSource protocol. */
66 static SOSManifestRef
dsCopyManifestWithViewNameSet(SOSDataSourceRef data_source
, CFSetRef viewNameSet
, CFErrorRef
*error
) {
67 if (!CFSetContainsValue(viewNameSet
, kSOSViewKeychainV0
))
68 return SOSManifestCreateWithData(NULL
, error
);
70 struct SOSTestDataSource
*ds
= (struct SOSTestDataSource
*)data_source
;
72 __block
struct SOSDigestVector dv
= SOSDigestVectorInit
;
73 CFDictionaryForEach(ds
->d2database
, ^(const void *key
, const void *value
) {
74 SOSDigestVectorAppend(&dv
, CFDataGetBytePtr((CFDataRef
)key
));
76 SOSDigestVectorSort(&dv
);
77 SOSManifestRef manifest
= SOSManifestCreateWithDigestVector(&dv
, error
);
78 SOSDigestVectorFree(&dv
);
79 ccdigest(ccsha1_di(), SOSManifestGetSize(manifest
), SOSManifestGetBytePtr(manifest
), ds
->manifest_digest
);
85 static bool foreach_object(SOSDataSourceRef data_source
, SOSManifestRef manifest
, CFErrorRef
*error
, void (^handle_object
)(CFDataRef key
, SOSObjectRef object
, bool *stop
)) {
86 struct SOSTestDataSource
*ds
= (struct SOSTestDataSource
*)data_source
;
88 __block
bool result
= true;
89 SOSManifestForEach(manifest
, ^(CFDataRef key
, bool *stop
) {
90 handle_object(key
, (SOSObjectRef
)CFDictionaryGetValue(ds
->d2database
, key
), stop
);
95 static bool dispose(SOSDataSourceRef data_source
, CFErrorRef
*error
) {
96 struct SOSTestDataSource
*ds
= (struct SOSTestDataSource
*)data_source
;
97 CFReleaseSafe(ds
->d2database
);
98 CFReleaseSafe(ds
->p2database
);
99 CFReleaseSafe(ds
->statedb
);
100 CFReleaseSafe(ds
->changes
);
105 static SOSObjectRef
createWithPropertyList(CFDictionaryRef plist
, CFErrorRef
*error
) {
106 return (SOSObjectRef
)CFDictionaryCreateCopy(kCFAllocatorDefault
, plist
);
109 static CFDataRef
SOSObjectCopyDER(SOSObjectRef object
, CFErrorRef
*error
) {
110 CFDictionaryRef dict
= (CFDictionaryRef
)object
;
111 size_t size
= der_sizeof_plist(dict
, error
);
112 CFMutableDataRef data
= CFDataCreateMutable(0, size
);
114 CFDataSetLength(data
, size
);
115 uint8_t *der
= (uint8_t *)CFDataGetMutableBytePtr(data
);
116 uint8_t *der_end
= der
+ size
;
117 der_end
= der_encode_plist(dict
, error
, der
, der_end
);
118 assert(der_end
== der
);
120 } else if (error
&& *error
== NULL
) {
121 *error
= CFErrorCreate(0, sSOSDataSourceErrorDomain
, kSOSDataSourceObjectMallocFailed
, NULL
);
126 static 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
);
130 CFDataSetLength(digest
, di
->output_size
);
131 ccdigest(di
, len
, data
, CFDataGetMutableBytePtr(digest
));
132 } else if (error
&& *error
== NULL
) {
133 *error
= CFErrorCreate(0, sSOSDataSourceErrorDomain
, kSOSDataSourceObjectMallocFailed
, NULL
);
138 static 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
);
143 CFDataRef digest
= NULL
;
145 digest
= ccdigest_copy_data(ccsha1_di(), CFDataGetLength(der
), CFDataGetBytePtr(der
), error
);
151 static CFDataRef
copyPrimaryKey(SOSObjectRef object
, CFErrorRef
*error
) {
152 CFMutableDictionaryRef ocopy
= CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault
);
153 CFTypeRef pkNames
[] = {
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
);
183 CFDataRef der
= SOSObjectCopyDER((SOSObjectRef
)ocopy
, error
);
185 CFDataRef digest
= NULL
;
187 digest
= ccdigest_copy_data(ccsha1_di(), CFDataGetLength(der
), CFDataGetBytePtr(der
), error
);
193 static CFDictionaryRef
copyPropertyList(SOSObjectRef object
, CFErrorRef
*error
) {
194 return (CFDictionaryRef
) CFRetain(object
);
197 // Return the newest object
198 static SOSObjectRef
copyMergedObject(SOSObjectRef object1
, SOSObjectRef object2
, CFErrorRef
*error
) {
199 CFDictionaryRef dict1
= (CFDictionaryRef
)object1
;
200 CFDictionaryRef dict2
= (CFDictionaryRef
)object2
;
201 SOSObjectRef result
= NULL
;
203 m1
= CFDictionaryGetValue(dict1
, kSecAttrModificationDate
);
204 m2
= CFDictionaryGetValue(dict2
, kSecAttrModificationDate
);
205 switch (CFDateCompare(m1
, m2
, NULL
)) {
206 case kCFCompareGreaterThan
:
207 result
= (SOSObjectRef
)dict1
;
209 case kCFCompareLessThan
:
210 result
= (SOSObjectRef
)dict2
;
212 case kCFCompareEqualTo
:
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
;
222 case kCFCompareLessThan
:
223 result
= (SOSObjectRef
)dict1
;
226 CFReleaseSafe(digest2
);
227 CFReleaseSafe(digest1
);
231 CFRetainSafe(result
);
235 static SOSMergeResult
mergeObject(SOSTransactionRef txn
, SOSObjectRef object
, SOSObjectRef
*mergedObject
, CFErrorRef
*error
) {
236 SOSTestDataSourceRef ds
= (SOSTestDataSourceRef
)txn
;
237 SOSMergeResult mr
= kSOSMergeFailure
;
238 CFDataRef pk
= copyPrimaryKey(object
, error
);
240 SOSObjectRef myObject
= (SOSObjectRef
)CFDictionaryGetValue(ds
->p2database
, pk
);
242 SOSObjectRef merged
= copyMergedObject(object
, myObject
, error
);
243 if (mergedObject
) *mergedObject
= CFRetainSafe(merged
);
244 if (CFEqualSafe(merged
, myObject
)) {
245 mr
= kSOSMergeLocalObject
;
246 } else if (CFEqualSafe(merged
, object
)) {
247 mr
= kSOSMergePeersObject
;
249 mr
= kSOSMergeCreatedObject
;
251 if (mr
!= kSOSMergeLocalObject
) {
252 CFDataRef myKey
= copyDigest(myObject
, error
);
253 CFDictionaryRemoveValue(ds
->d2database
, myKey
);
254 CFReleaseSafe(myKey
);
255 CFDataRef key
= copyDigest(merged
, error
);
256 CFDictionarySetValue(ds
->d2database
, key
, merged
);
257 const void *values
[2] = { myObject
, merged
};
258 CFTypeRef entry
= CFArrayCreate(kCFAllocatorDefault
, values
, 2, &kCFTypeArrayCallBacks
);
260 CFArrayAppendValue(ds
->changes
, entry
);
264 CFDictionarySetValue(ds
->p2database
, pk
, merged
);
266 CFReleaseSafe(merged
);
268 SOSTestDataSourceAddObject((SOSDataSourceRef
)ds
, object
, error
);
269 mr
= kSOSMergePeersObject
;
275 static CFStringRef
dsGetName(SOSDataSourceRef ds
) {
276 return CFSTR("The sky is made of butterflies");
279 static void dsSetNotifyPhaseBlock(SOSDataSourceRef ds
, dispatch_queue_t queue
, SOSDataSourceNotifyBlock notifyBlock
) {
280 ((SOSTestDataSourceRef
)ds
)->notifyBlock
= Block_copy(notifyBlock
);
283 static CFDataRef
dsCopyStateWithKey(SOSDataSourceRef ds
, CFStringRef key
, CFStringRef pdmn
, CFErrorRef
*error
) {
284 SOSTestDataSourceRef tds
= (SOSTestDataSourceRef
)ds
;
285 CFStringRef dbkey
= CFStringCreateWithFormat(kCFAllocatorDefault
, NULL
, CFSTR("%@-%@"), pdmn
, key
);
286 CFDataRef state
= CFDictionaryGetValue(tds
->statedb
, dbkey
);
287 CFReleaseSafe(dbkey
);
288 return CFRetainSafe(state
);
291 static CFDataRef
dsCopyItemDataWithKeys(SOSDataSourceRef data_source
, CFDictionaryRef keys
, CFErrorRef
*error
) {
292 SecError(errSecUnimplemented
, error
, CFSTR("dsCopyItemDataWithKeys on test data source not implemented"));
296 static bool dsWith(SOSDataSourceRef ds
, CFErrorRef
*error
, SOSDataSourceTransactionSource source
, void(^transaction
)(SOSTransactionRef txn
, bool *commit
)) {
297 SOSTestDataSourceRef tds
= (SOSTestDataSourceRef
)ds
;
299 transaction((SOSTransactionRef
)ds
, &commit
);
300 if (commit
&& ((SOSTestDataSourceRef
)ds
)->notifyBlock
&& (CFArrayGetCount(tds
->changes
))) {
301 ((SOSTestDataSourceRef
)ds
)->notifyBlock(ds
, (SOSTransactionRef
)ds
, kSOSDataSourceTransactionWillCommit
, source
, tds
->changes
);
302 CFArrayRemoveAllValues(tds
->changes
);
307 static bool dsSetStateWithKey(SOSDataSourceRef ds
, SOSTransactionRef txn
, CFStringRef pdmn
, CFStringRef key
, CFDataRef state
, CFErrorRef
*error
) {
308 SOSTestDataSourceRef tds
= (SOSTestDataSourceRef
)ds
;
309 CFStringRef dbkey
= CFStringCreateWithFormat(kCFAllocatorDefault
, NULL
, CFSTR("%@-%@"), pdmn
, key
);
310 CFDictionarySetValue(tds
->statedb
, dbkey
, state
);
311 CFReleaseSafe(dbkey
);
315 static bool dsRestoreObject(SOSTransactionRef txn
, uint64_t handle
, CFDictionaryRef item
, CFErrorRef
*error
) {
316 // TODO: Just call merge, probably doesn't belong in protocol at all
321 static CFDictionaryRef
objectCopyBackup(SOSObjectRef object
, uint64_t handle
, CFErrorRef
*error
) {
322 // OMG We failed without an error.
327 SOSDataSourceRef
SOSTestDataSourceCreate(void) {
328 SOSTestDataSourceRef ds
= calloc(1, sizeof(struct SOSTestDataSource
));
330 ds
->ds
.engine
= NULL
;
331 ds
->ds
.dsGetName
= dsGetName
;
332 ds
->ds
.dsSetNotifyPhaseBlock
= dsSetNotifyPhaseBlock
;
333 ds
->ds
.dsCopyManifestWithViewNameSet
= dsCopyManifestWithViewNameSet
;
334 ds
->ds
.dsForEachObject
= foreach_object
;
335 ds
->ds
.dsCopyStateWithKey
= dsCopyStateWithKey
;
336 ds
->ds
.dsCopyItemDataWithKeys
= dsCopyItemDataWithKeys
;
338 ds
->ds
.dsWith
= dsWith
;
339 ds
->ds
.dsRelease
= dispose
;
341 ds
->ds
.dsMergeObject
= mergeObject
;
342 ds
->ds
.dsSetStateWithKey
= dsSetStateWithKey
;
343 ds
->ds
.dsRestoreObject
= dsRestoreObject
;
345 ds
->ds
.objectCopyDigest
= copyDigest
;
346 ds
->ds
.objectCreateWithPropertyList
= createWithPropertyList
;
347 ds
->ds
.objectCopyPropertyList
= copyPropertyList
;
348 ds
->ds
.objectCopyBackup
= objectCopyBackup
;
350 ds
->d2database
= CFDictionaryCreateMutable(0, 0, &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
);
351 ds
->p2database
= CFDictionaryCreateMutable(0, 0, &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
);
352 ds
->statedb
= CFDictionaryCreateMutable(0, 0, &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
);
354 ds
->changes
= CFArrayCreateMutableForCFTypes(kCFAllocatorDefault
);
356 return (SOSDataSourceRef
)ds
;
359 static CFStringRef
SOSTestDataSourceFactoryCopyName(SOSDataSourceFactoryRef factory
)
361 SOSTestDataSourceFactoryRef dsf
= (SOSTestDataSourceFactoryRef
) factory
;
363 __block CFStringRef result
= NULL
;
364 CFDictionaryForEach(dsf
->data_sources
, ^(const void*key
, const void*value
) { if (isString(key
)) result
= key
; });
366 return CFRetainSafe(result
);
369 static SOSDataSourceRef
SOSTestDataSourceFactoryCreateDataSource(SOSDataSourceFactoryRef factory
, CFStringRef dataSourceName
, CFErrorRef
*error
)
371 SOSTestDataSourceFactoryRef dsf
= (SOSTestDataSourceFactoryRef
) factory
;
373 return (SOSDataSourceRef
) CFDictionaryGetValue(dsf
->data_sources
, dataSourceName
);
376 static void SOSTestDataSourceFactoryDispose(SOSDataSourceFactoryRef factory
)
378 SOSTestDataSourceFactoryRef dsf
= (SOSTestDataSourceFactoryRef
) factory
;
380 CFReleaseNull(dsf
->data_sources
);
384 SOSDataSourceFactoryRef
SOSTestDataSourceFactoryCreate() {
385 SOSTestDataSourceFactoryRef dsf
= calloc(1, sizeof(struct SOSTestDataSourceFactory
));
387 dsf
->dsf
.copy_name
= SOSTestDataSourceFactoryCopyName
;
388 dsf
->dsf
.create_datasource
= SOSTestDataSourceFactoryCreateDataSource
;
389 dsf
->dsf
.release
= SOSTestDataSourceFactoryDispose
;
390 dsf
->data_sources
= CFDictionaryCreateMutable(kCFAllocatorDefault
, 0, &kCFTypeDictionaryKeyCallBacks
, NULL
);
395 static bool do_nothing(SOSDataSourceRef ds
, CFErrorRef
*error
) {
399 void SOSTestDataSourceFactorySetDataSource(SOSDataSourceFactoryRef factory
, CFStringRef name
, SOSDataSourceRef ds
)
401 SOSTestDataSourceFactoryRef dsf
= (SOSTestDataSourceFactoryRef
) factory
;
403 // TODO This hack sucks. It leaks now.
404 ds
->dsRelease
= do_nothing
;
406 CFDictionaryRemoveAllValues(dsf
->data_sources
);
407 CFDictionarySetValue(dsf
->data_sources
, name
, ds
);
411 SOSMergeResult
SOSTestDataSourceAddObject(SOSDataSourceRef data_source
, SOSObjectRef object
, CFErrorRef
*error
) {
412 struct SOSTestDataSource
*ds
= (struct SOSTestDataSource
*)data_source
;
414 CFDataRef key
= copyDigest(object
, error
);
415 CFDataRef pk
= copyPrimaryKey(object
, error
);
417 SOSObjectRef myObject
= (SOSObjectRef
)CFDictionaryGetValue(ds
->p2database
, pk
);
418 SOSObjectRef merged
= NULL
;
420 merged
= copyMergedObject(object
, myObject
, error
);
427 if (!CFEqualSafe(merged
, myObject
)) {
429 CFDataRef myKey
= copyDigest(myObject
, error
);
430 CFDictionaryRemoveValue(ds
->d2database
, myKey
);
431 CFReleaseSafe(myKey
);
432 const void *values
[2] = { myObject
, merged
};
433 CFTypeRef entry
= CFArrayCreate(kCFAllocatorDefault
, values
, 2, &kCFTypeArrayCallBacks
);
435 CFArrayAppendValue(ds
->changes
, entry
);
439 CFArrayAppendValue(ds
->changes
, merged
);
441 CFDictionarySetValue(ds
->d2database
, key
, merged
);
442 CFDictionarySetValue(ds
->p2database
, pk
, merged
);
453 bool SOSTestDataSourceDeleteObject(SOSDataSourceRef data_source
, CFDataRef key
, CFErrorRef
*error
) {
454 //struct SOSTestDataSource *ds = (struct SOSTestDataSource *)data_source;
458 CFMutableDictionaryRef
SOSTestDataSourceGetDatabase(SOSDataSourceRef data_source
) {
459 struct SOSTestDataSource
*ds
= (struct SOSTestDataSource
*)data_source
;
460 return ds
->d2database
;
463 // This works for any datasource, not just the test one, but it's only used in testcases, so it's here for now.
464 SOSObjectRef
SOSDataSourceCreateGenericItemWithData(SOSDataSourceRef ds
, CFStringRef account
, CFStringRef service
, bool is_tomb
, CFDataRef data
) {
466 CFNumberRef zero
= CFNumberCreate(kCFAllocatorDefault
, kCFNumberSInt32Type
, &value
);
468 CFNumberRef one
= CFNumberCreate(kCFAllocatorDefault
, kCFNumberSInt32Type
, &value
);
469 CFAbsoluteTime timestamp
= 3700000 + (is_tomb
? 1 : 0);
470 CFDateRef now
= CFDateCreate(kCFAllocatorDefault
, timestamp
);
471 CFDictionaryRef dict
= CFDictionaryCreateForCFTypes(kCFAllocatorDefault
,
472 kSecClass
, kSecClassGenericPassword
,
473 kSecAttrSynchronizable
, one
,
474 kSecAttrTombstone
, is_tomb
? one
: zero
,
475 kSecAttrAccount
, account
,
476 kSecAttrService
, service
,
477 kSecAttrCreationDate
, now
,
478 kSecAttrModificationDate
, now
,
479 kSecAttrAccessGroup
, CFSTR("test"),
480 kSecAttrAccessible
, kSecAttrAccessibleWhenUnlocked
,
481 !is_tomb
&& data
? kSecValueData
: NULL
,data
,
486 CFErrorRef localError
= NULL
;
487 SOSObjectRef object
= ds
->objectCreateWithPropertyList(dict
, &localError
);
489 secerror("createWithPropertyList: %@ failed: %@", dict
, localError
);
490 CFRelease(localError
);
496 SOSObjectRef
SOSDataSourceCreateGenericItem(SOSDataSourceRef ds
, CFStringRef account
, CFStringRef service
) {
497 return SOSDataSourceCreateGenericItemWithData(ds
, account
, service
, false, NULL
);
500 SOSObjectRef
SOSDataSourceCopyObject(SOSDataSourceRef ds
, SOSObjectRef match
, CFErrorRef
*error
)
502 __block SOSObjectRef result
= NULL
;
504 CFDataRef digest
= SOSObjectCopyDigest(ds
, match
, error
);
505 SOSManifestRef manifest
= NULL
;
507 require(digest
, exit
);
508 manifest
= SOSManifestCreateWithData(digest
, error
);
510 SOSDataSourceForEachObject(ds
, manifest
, error
, ^void (CFDataRef key
, SOSObjectRef object
, bool *stop
) {
511 if (object
== NULL
) {
512 if (error
&& !*error
) {
513 SecCFCreateErrorWithFormat(kSOSDataSourceObjectNotFoundError
, sSOSDataSourceErrorDomain
, NULL
, error
, 0, CFSTR("key %@ not in database"), key
);
515 } else if (result
== NULL
) {
516 result
= CFRetainSafe(object
);
521 CFReleaseNull(manifest
);
522 CFReleaseNull(digest
);