2 * Copyright (c) 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 // Test syncing between SecItemDataSource and SOSTestDataSource
27 #include "keychain/SecureObjectSync/Regressions/SOSTestDevice.h"
28 #include "keychain/SecureObjectSync/Regressions/SOSTestDataSource.h"
29 #include "secd_regressions.h"
30 #include "SecdTestKeychainUtilities.h"
32 #include "keychain/SecureObjectSync/SOSEngine.h"
33 #include "keychain/SecureObjectSync/SOSPeer.h"
34 #include <Security/SecBase64.h>
35 #include <Security/SecItem.h>
36 #include <Security/SecItemPriv.h>
37 #include <corecrypto/ccsha2.h>
38 #include "keychain/securityd/SecItemServer.h"
39 #include "keychain/securityd/SecItemDataSource.h"
40 #include <utilities/SecCFWrappers.h>
41 #include <utilities/SecIOFormat.h>
42 #include <utilities/SecFileLocations.h>
44 #include <AssertMacros.h>
46 #include "SOSAccountTesting.h"
50 static void testsync3(const char *name, const char *test_directive, const char *test_reason) {
51 __block int iteration=0;
52 SOSTestDeviceListTestSync(name, test_directive, test_reason, 0, false, ^bool(SOSTestDeviceRef source, SOSTestDeviceRef dest) {
54 if (iteration == 12 || iteration == 13) {
55 pass("pre-rcv %@", dest);
57 if (iteration == 19) {
58 pass("pre-send %@", source);
61 }, ^bool(SOSTestDeviceRef source, SOSTestDeviceRef dest, SOSMessageRef message) {
62 if (iteration == 10) {
63 pass("pre-add %@", source);
64 //SOSTestDeviceAddGenericItem(source, CFSTR("test_account"), CFSTR("test service"));
65 SOSTestDeviceAddRemoteGenericItem(source, CFSTR("test_account"), CFSTR("test service"));
66 pass("post-add %@", source);
67 return true; // db changed
68 } else if (iteration == 12 || iteration == 15) {
69 pass("post-rcv %@", dest);
72 }, CFSTR("AAA"), CFSTR("BBB"), CFSTR("CCC"), NULL);
75 static void testsync2(const char *name, const char *test_directive, const char *test_reason, void (^aliceInit)(SOSDataSourceRef ds), void (^bobInit)(SOSDataSourceRef ds), CFStringRef msg, ...) {
76 __block int iteration=0;
77 SOSTestDeviceListTestSync(name, test_directive, test_reason, kSOSPeerVersion, false, ^bool(SOSTestDeviceRef source, SOSTestDeviceRef dest) {
78 if (iteration == 96) {
79 pass("%@ before message", source);
82 }, ^bool(SOSTestDeviceRef source, SOSTestDeviceRef dest, SOSMessageRef message) {
84 if (iteration == 60) {
85 pass("%@ before addition", source);
86 //SOSTestDeviceAddGenericItem(source, CFSTR("test_account"), CFSTR("test service"));
87 SOSTestDeviceAddRemoteGenericItem(source, CFSTR("test_account"), CFSTR("test service"));
88 pass("%@ after addition", source);
92 }, CFSTR("alice"), CFSTR("bob"), CFSTR("claire"), CFSTR("dave"),CFSTR("edward"), CFSTR("frank"), CFSTR("gary"), NULL);
95 static void testsync(const char *name, const char *test_directive, const char *test_reason, void (^aliceInit)(SOSDataSourceRef ds), void (^bobInit)(SOSDataSourceRef ds), ...) {
96 __block int msg_index = 0;
97 __block int last_msg_index = 0;
99 va_start(args, bobInit);
100 CFArrayRef messages = CFArrayCreateForVC(kCFAllocatorDefault, &kCFTypeArrayCallBacks, args);
101 SOSTestDeviceListTestSync(name, test_directive, test_reason, 0, false,
102 ^bool(SOSTestDeviceRef source, SOSTestDeviceRef dest) {
103 if (msg_index == 0) {
104 aliceInit(source->ds);
109 }, ^bool(SOSTestDeviceRef source, SOSTestDeviceRef dest, SOSMessageRef message) {
110 CFStringRef hexMsg = msg_index < CFArrayGetCount(messages) ? (CFStringRef)CFArrayGetValueAtIndex(messages, msg_index) : 0;
111 /* We are expecting a message and msg is it's digest. */
114 CFStringRef messageDigestStr = SOSMessageCopyDigestHex(message);
116 if (CFEqual(messageDigestStr, hexMsg)) {
117 pass("%s %@ handled message [%d] %@", name, SOSEngineGetMyID(dest->ds->engine), msg_index, message);
120 todo("manifest caching changed");
121 fail("%s %@ received message [%d] digest %@ != %@ %@", name, SOSEngineGetMyID(dest->ds->engine), msg_index, messageDigestStr, hexMsg, message);
124 last_msg_index = msg_index;
127 todo("manifest caching changed");
128 fail("%s %@ sent extra message [%d] with digest %@: %@", name, SOSEngineGetMyID(source->ds->engine), msg_index, messageDigestStr, message);
131 CFReleaseSafe(messageDigestStr);
134 }, CFSTR("alice"), CFSTR("bob"), NULL);
136 if (msg_index < CFArrayGetCount(messages)) {
138 todo("manifest caching changed");
139 fail("%s nothing sent expecting message [%d] digest %@", name, msg_index, CFArrayGetValueAtIndex(messages, msg_index));
141 } else if (last_msg_index < msg_index) {
143 todo("manifest caching changed");
144 fail("%s exchanged %d messages not in expected list", name, msg_index - last_msg_index);
152 // Test syncing an empty circle with 1 to 10 devices and both version 0 and version 2 protocols
153 static void testsyncempty(void) {
154 CFMutableArrayRef deviceIDs = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault);
155 for (int deviceIX=0; deviceIX < 10; ++deviceIX) {
156 CFStringRef deviceID = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%c"), 'A' + deviceIX);
157 CFArrayAppendValue(deviceIDs, deviceID);
158 CFReleaseSafe(deviceID);
160 for (CFIndex version = 0; version < 3; version += 2) {
161 CFMutableDictionaryRef testDevices = SOSTestDeviceListCreate(false, version, deviceIDs, NULL);
162 SOSTestDeviceListSync("syncempty", test_directive, test_reason, testDevices, NULL, NULL);
163 SOSTestDeviceListInSync("syncempty", test_directive, test_reason, testDevices);
164 SOSTestDeviceDestroyEngine(testDevices);
165 CFReleaseSafe(testDevices);
169 CFReleaseSafe(deviceIDs);
173 static CFIndex syncmany_add(int iteration) {
174 if (iteration % 7 < 3 && iteration < 10)
175 return iteration % 17 + 200;
179 static void testsyncmany(const char *name, const char *test_directive, const char *test_reason, int devFirst, int devCount, int version, CFIndex (*should_add)(int iteration)) {
180 CFMutableArrayRef deviceIDs = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault);
181 for (int deviceIX=0; deviceIX < devCount; ++deviceIX) {
182 CFStringRef deviceID = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%c"), 'A' + deviceIX);
183 CFArrayAppendValue(deviceIDs, deviceID);
184 CFReleaseSafe(deviceID);
185 if (deviceIX >= devFirst) {
186 CFMutableDictionaryRef testDevices = SOSTestDeviceListCreate(false, version, deviceIDs, NULL);
187 __block int iteration = 0;
188 SOSTestDeviceListSync(name, test_directive, test_reason, testDevices, ^bool(SOSTestDeviceRef source, SOSTestDeviceRef dest) {
191 // Add 10 items in first 10 sync messages
192 CFIndex toAdd = should_add(iteration);
194 CFStringRef account = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("item%d"), iteration);
195 didAdd = SOSTestDeviceAddGenericItems(source, toAdd, account, CFSTR("testsyncmany"));
196 CFReleaseSafe(account);
198 if (iteration == 279 || iteration == 459)
199 pass("pre-send[%d] %@", iteration, source);
202 }, ^bool(SOSTestDeviceRef source, SOSTestDeviceRef dest, SOSMessageRef message) {
203 if (iteration == 262)
204 pass("post-rcv[%d] %@", iteration, dest);
206 if (iteration == 272 || iteration == 279)
207 pass("post-send[%d] %@", iteration, source);
211 SOSTestDeviceListInSync(name, test_directive, test_reason, testDevices);
212 SOSTestDeviceDestroyEngine(testDevices);
213 CFReleaseSafe(testDevices);
216 CFReleaseSafe(deviceIDs);
219 static void testsync2p(void) {
220 __block int iteration = 0;
221 SOSTestDeviceListTestSync("testsync2p", test_directive, test_reason, 0, false, ^bool(SOSTestDeviceRef source, SOSTestDeviceRef dest) {
223 // Add 10 items in first 10 sync messages
224 if (iteration <= 10) {
225 CFStringRef account = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("item%d"), iteration);
226 SOSTestDeviceAddGenericItem(source, account, CFSTR("testsync2p"));
227 CFReleaseSafe(account);
231 }, ^bool(SOSTestDeviceRef source, SOSTestDeviceRef dest, SOSMessageRef message) {
233 }, CFSTR("Atestsync2p"), CFSTR("Btestsync2p"), NULL);
236 static void synctests(void) {
238 // TODO: Adding items gives us non predictable creation and mod dates so
239 // the message hashes can't be precomputed.
240 CFDictionaryRef item = CFDictionaryCreateForCFTypes
242 kSecClass, kSecClassGenericPassword,
243 kSecAttrAccessible, kSecAttrAccessibleWhenUnlocked,
244 kSecAttrSynchronizable, kCFBooleanTrue,
245 kSecAttrService, CFSTR("service"),
246 kSecAttrAccount, CFSTR("account"),
248 SecItemAdd(item, NULL);
256 // Careful with this in !NO_SERVER, it'll destroy debug keychains.
257 WithPathInKeychainDirectory(CFSTR("keychain-2-debug.db"), ^(const char *keychain_path) {
258 unlink(keychain_path);
261 // Don't ever do this in !NO_SERVER, it'll destroy real keychains.
262 WithPathInKeychainDirectory(CFSTR("keychain-2.db"), ^(const char *keychain_path) {
263 unlink(keychain_path);
266 SecKeychainDbReset(NULL);
268 skip("Keychain not reset", 0, false);
271 testsync3("secd_70_engine3", test_directive, test_reason);
273 // Sync between 2 empty dataSources
274 testsync("secd_70_engine", test_directive, test_reason,
275 ^ (SOSDataSourceRef dataSource) {},
276 ^ (SOSDataSourceRef dataSource) {},
277 CFSTR("2AF312E092D67308A0083DFFBF2B6B754B967864"),
278 CFSTR("2AF312E092D67308A0083DFFBF2B6B754B967864"),
279 CFSTR("2AF312E092D67308A0083DFFBF2B6B754B967864"),
280 CFSTR("2AF312E092D67308A0083DFFBF2B6B754B967864"),
283 // Sync a dataSource with one object to an empty dataSource
284 testsync("secd_70_engine-alice1", test_directive, test_reason,
285 ^ (SOSDataSourceRef dataSource) {
286 __block CFErrorRef error = NULL;
287 SOSObjectRef object = SOSDataSourceCreateGenericItem(dataSource, CFSTR("test_account"), CFSTR("test service"));
288 // TODO: Needs to be a SecDBItemRef for the SecItemDataSource...
289 ok(SOSDataSourceWith(dataSource, &error, ^(SOSTransactionRef txn, bool *commit) {
290 ok(SOSDataSourceMergeObject(dataSource, txn, object, NULL, &error), "dataSource %@ added object %@", SOSEngineGetMyID(dataSource->engine), error);
291 }), "ds transaction failed %@", error);
292 CFReleaseSafe(object);
293 CFReleaseNull(error);
295 ^ (SOSDataSourceRef dataSource) {},
296 CFSTR("DDDB2DCEB7B36F0757F400251ECD11E377A0DCE8"),
297 CFSTR("B2777CC898AE381B3F375B27E4FD9757F6CE9948"),
298 CFSTR("CB67BF9ECF00DC7664834DE7A2D7CC1523D25341"),
299 CFSTR("ADAA3ACE75ED516CB91893413EE9CC9ED04CA47B"),
301 //CFSTR("ADAA3ACE75ED516CB91893413EE9CC9ED04CA47B"),
302 //CFSTR("147B6C509908CC4A9FC4263973A842104A64CE01"),
303 //CFSTR("019B494F3C06B48BB02C280AF1E19AD861A7003C"),
304 //CFSTR("ADAA3ACE75ED516CB91893413EE9CC9ED04CA47B"),
307 // Sync a dataSource with one object to another dataSource with the same object
308 testsync("secd_70_engine-alice1bob1", test_directive, test_reason,
309 ^ (SOSDataSourceRef dataSource) {
311 CFErrorRef error = NULL;
312 // TODO: Needs to be a SecDBItemRef for the SecItemDataSource...
313 CFDictionaryRef object = SOSDataSourceCreateGenericItem(dataSource, CFSTR("test_account"), CFSTR("test service"));
314 ok(SOSDataSourceMergeObject(dataSource, object, NULL, &error), "dataSource %@ added object %@", SOSEngineGetMyID(dataSource->engine), error);
315 CFReleaseSafe(object);
316 CFReleaseNull(error);
319 ^ (SOSDataSourceRef dataSource) {
320 __block CFErrorRef error = NULL;
321 SOSObjectRef object = SOSDataSourceCreateGenericItem(dataSource, CFSTR("test_account"), CFSTR("test service"));
322 ok(SOSDataSourceWith(dataSource, &error, ^(SOSTransactionRef txn, bool *commit) {
323 ok(SOSDataSourceMergeObject(dataSource, txn, object, NULL, &error), "dataSource %@ added object %@", SOSEngineGetMyID(dataSource->engine), error);
324 }), "ds transaction failed %@", error);
325 CFReleaseSafe(object);
326 CFReleaseNull(error);
328 CFSTR("2AF312E092D67308A0083DFFBF2B6B754B967864"),
329 CFSTR("CB67BF9ECF00DC7664834DE7A2D7CC1523D25341"),
330 CFSTR("ADAA3ACE75ED516CB91893413EE9CC9ED04CA47B"),
332 //CFSTR("ADAA3ACE75ED516CB91893413EE9CC9ED04CA47B"),
333 //CFSTR("ADAA3ACE75ED516CB91893413EE9CC9ED04CA47B"),
334 //CFSTR("ADAA3ACE75ED516CB91893413EE9CC9ED04CA47B"),
337 // Sync a dataSource with one object to another dataSource with the same object
338 testsync("secd_70_engine-alice1bob2", test_directive, test_reason,
339 ^ (SOSDataSourceRef dataSource) {
341 CFErrorRef error = NULL;
342 // TODO: Needs to be a SecDBItemRef for the SecItemDataSource...
343 SOSObjectRef object = SOSDataSourceCreateGenericItem(dataSource, CFSTR("test_account"), CFSTR("test service"));
344 ok(SOSDataSourceMergeObject(dataSource, object, NULL, &error), "dataSource %@ added object %@", SOSEngineGetMyID(dataSource->engine), error);
345 CFReleaseSafe(object);
346 CFReleaseNull(error);
349 ^ (SOSDataSourceRef dataSource) {
350 __block CFErrorRef error = NULL;
351 __block SOSObjectRef object = SOSDataSourceCreateGenericItem(dataSource, CFSTR("test_account"), CFSTR("test service"));
352 ok(SOSDataSourceWith(dataSource, &error, ^(SOSTransactionRef txn, bool *commit) {
353 ok(SOSDataSourceMergeObject(dataSource, txn, object, NULL, &error), "dataSource %@ added object %@", SOSEngineGetMyID(dataSource->engine), error);
354 CFReleaseSafe(object);
355 CFReleaseNull(error);
356 object = SOSDataSourceCreateGenericItem(dataSource, CFSTR("account1"), CFSTR("service1"));
357 ok(SOSDataSourceMergeObject(dataSource, txn, object, NULL, &error), "dataSource %@ added object %@", SOSEngineGetMyID(dataSource->engine), error);
358 CFReleaseSafe(object);
359 }), "ds transaction failed %@", error);
360 CFReleaseNull(error);
362 CFSTR("2AF312E092D67308A0083DFFBF2B6B754B967864"),
363 CFSTR("270EB3953B2E1E295F668CFC27CBB7137991A4BE"),
364 CFSTR("D1B3944E3084425F41B2C2EA0BE82170E10AA37D"),
366 //CFSTR("ADAA3ACE75ED516CB91893413EE9CC9ED04CA47B"),
367 //CFSTR("D4049A1063CFBF7CAF8424E13DE3CE926FF5856C"),
368 //CFSTR("9624EA855BBED6B668868BB723443E804D04F6A1"),
369 //CFSTR("063E097CCD4FEB7F3610ED12B3DA828467314846"),
370 //CFSTR("D1B3944E3084425F41B2C2EA0BE82170E10AA37D"),
373 // Sync a dataSource with a tombstone object to another dataSource with the same object
375 todo("<rdar://problem/14049022> Test case in sd-70-engine fails due to need for RowID");
376 testsync("secd_70_engine-update", test_directive, test_reason,
377 ^ (SOSDataSourceRef dataSource) {
378 __block CFErrorRef error = NULL;
379 const char *password = "password1";
380 CFDataRef data = CFDataCreate(kCFAllocatorDefault, (const UInt8 *)password, strlen(password));
381 // TODO: Needs to be a SecDBItemRef for the SecItemDataSource...
382 SOSObjectRef object_to_find = SOSDataSourceCreateGenericItemWithData(dataSource, CFSTR("test_account"), CFSTR("test service"), true, NULL);
383 SOSObjectRef object = SOSDataSourceCopyObject(dataSource, object_to_find, &error);
384 SOSObjectRef old_object = NULL;
386 skip("no object", 1, ok(object, "Finding object %@, error: %@", object_to_find, error));
388 // TODO: Needs to be a SecDBItemRef for the SecItemDataSource...
389 old_object = SOSDataSourceCreateGenericItem(dataSource, CFSTR("test_account"), CFSTR("test service"));
390 ok(SOSDataSourceWith(dataSource, &error, ^(SOSTransactionRef txn, bool *commit) {
391 ok(SOSDataSourceMergeObject(dataSource, txn, object, NULL, &error), "dataSource %@ update object %@", SOSEngineGetMyID(dataSource->engine), error);
392 }), "ds transaction failed %@", error);
395 CFReleaseSafe(old_object);
396 CFReleaseSafe(object);
397 CFReleaseNull(error);
399 ^ (SOSDataSourceRef dataSource) {
400 __block CFErrorRef error = NULL;
401 __block SOSObjectRef object = SOSDataSourceCreateGenericItem(dataSource, CFSTR("test_account"), CFSTR("test service"));
402 ok(SOSDataSourceWith(dataSource, &error, ^(SOSTransactionRef txn, bool *commit) {
403 ok(SOSDataSourceMergeObject(dataSource, txn, object, NULL, &error), "dataSource %@ added object %@", SOSEngineGetMyID(dataSource->engine), error);
404 CFReleaseSafe(object);
405 CFReleaseNull(error);
406 object = SOSDataSourceCreateGenericItem(dataSource, CFSTR("account1"), CFSTR("service1"));
407 ok(SOSDataSourceMergeObject(dataSource, txn, object, NULL, &error), "dataSource %@ added object %@", SOSEngineGetMyID(dataSource->engine), error);
408 CFReleaseSafe(object);
409 }), "ds transaction failed %@", error);
410 CFReleaseNull(error);
412 CFSTR("2AF312E092D67308A0083DFFBF2B6B754B967864"),
413 CFSTR("270EB3953B2E1E295F668CFC27CBB7137991A4BE"),
414 CFSTR("D1B3944E3084425F41B2C2EA0BE82170E10AA37D"),
416 //CFSTR("5D07A221A152D6D6C5F1919189F259A7278A08C5"),
417 //CFSTR("D4049A1063CFBF7CAF8424E13DE3CE926FF5856C"),
418 //CFSTR("137FD34E9BF11B4BA0620E8EBFAB8576BCCCF294"),
419 //CFSTR("5D07A221A152D6D6C5F1919189F259A7278A08C5"),
423 // Sync a dataSource with one object to another dataSource with the same object
424 testsync("secd_70_engine-foreign-add", test_directive, test_reason,
425 ^ (SOSDataSourceRef dataSource) {
427 ^ (SOSDataSourceRef dataSource) {
428 __block CFErrorRef error = NULL;
429 const char *password = "password1";
430 CFDataRef data = CFDataCreate(kCFAllocatorDefault, (const UInt8 *)password, strlen(password));
431 __block SOSObjectRef object = SOSDataSourceCreateGenericItemWithData(dataSource, CFSTR("test_account"), CFSTR("test service"), false, data);
433 ok(SOSDataSourceWith(dataSource, &error, ^(SOSTransactionRef txn, bool *commit) {
434 ok(SOSDataSourceMergeObject(dataSource, txn, object, NULL, &error), "dataSource %@ added object %@", SOSEngineGetMyID(dataSource->engine), error);
435 CFReleaseSafe(object);
436 CFReleaseNull(error);
437 object = SOSDataSourceCreateGenericItem(dataSource, CFSTR("account1"), CFSTR("service1"));
438 ok(SOSDataSourceMergeObject(dataSource, txn, object, NULL, &error), "dataSource %@ added object %@", SOSEngineGetMyID(dataSource->engine), error);
439 CFReleaseSafe(object);
440 }), "ds transaction failed %@", error);
441 CFReleaseNull(error);
443 CFSTR("2AF312E092D67308A0083DFFBF2B6B754B967864"),
444 CFSTR("769F63675CEE9CB968BFD9CA48DB9079BFCAFB6C"),
445 CFSTR("818C24B9BC495940836B9C8F76517C838CEFFA98"),
447 //CFSTR("D1B3944E3084425F41B2C2EA0BE82170E10AA37D"),
448 //CFSTR("607EEF976943FD781CFD2B3850E6DC7979AA61EF"),
449 //CFSTR("28434CD1B90CC205460557CAC03D7F12067F2329"),
450 //CFSTR("D1B3944E3084425F41B2C2EA0BE82170E10AA37D"),
454 // Sync between 2 empty dataSources
455 testsync2("secd_70_engine2", test_directive, test_reason,
456 ^ (SOSDataSourceRef dataSource) {},
457 ^ (SOSDataSourceRef dataSource) {},
458 CFSTR("2AF312E092D67308A0083DFFBF2B6B754B967864"),
459 CFSTR("2AF312E092D67308A0083DFFBF2B6B754B967864"),
460 CFSTR("2AF312E092D67308A0083DFFBF2B6B754B967864"),
461 CFSTR("2AF312E092D67308A0083DFFBF2B6B754B967864"),
462 CFSTR("2AF312E092D67308A0083DFFBF2B6B754B967864"),
466 testsyncmany("syncmany", test_directive, test_reason, 9, 10, 0, syncmany_add);
467 testsyncmany("v2syncmany", test_directive, test_reason, 9, 10, 2, syncmany_add);
472 int secd_70_engine(int argc, char *const *argv)
476 /* custom keychain dir */
477 secd_test_setup_temp_keychain(__FUNCTION__, NULL);