2 // sc-100-devicecircle.c
5 // Created by John Hurley 10/16/12.
6 // Copyright 2012 Apple Inc. All rights reserved.
10 This test is a combination of sc-75, sc_93 and sc_94 that can
11 be run on two devices.
13 The test will look for a circle in kvs
14 - if none exists, it will create one
15 - if one exists, it will try to join
17 Whenever you confirm a new peer, must start sync
19 Test sc-98 can be run before this to clear out kvs
23 // /AppleInternal/Applications/SecurityTests.app/SecurityTests sc_101_accountsync -v -- -i alice
24 // /AppleInternal/Applications/SecurityTests.app/SecurityTests sc_101_accountsync -v -- -i bob
26 #include <SecureObjectSync/SOSEngine.h>
27 #include <SecureObjectSync/SOSPeer.h>
29 #include "SOSCircle_regressions.h"
31 #include <corecrypto/ccsha2.h>
32 #include <Security/SecRandom.h>
34 #include <utilities/SecCFWrappers.h>
35 #include <utilities/debugging.h>
36 #include <utilities/iOSforOSX.h>
39 #include <AssertMacros.h>
44 #include <CoreFoundation/CFDate.h>
47 #include <Security/SecKey.h>
49 #include <SecureObjectSync/SOSFullPeerInfo.h>
50 #include <SecureObjectSync/SOSPeerInfo.h>
51 #include <SecureObjectSync/SOSCircle.h>
52 #include <SecureObjectSync/SOSCloudCircle.h>
53 #include <SecureObjectSync/SOSInternal.h>
54 #include <SecureObjectSync/SOSUserKeygen.h>
56 #include "SOSCircle_regressions.h"
57 #include "SOSRegressionUtilities.h"
58 #include "SOSTestDataSource.h"
59 #include "SOSTestTransport.h"
60 #include "SOSCloudKeychainClient.h"
62 #include <securityd/SOSCloudCircleServer.h>
64 #ifndef SEC_CONST_DECL
65 #define SEC_CONST_DECL(k,v) CFTypeRef k = (CFTypeRef)(CFSTR(v));
68 //static dispatch_queue_t wait_queue = NULL;
70 #define VALUECFNULLCHECK(msg) if (msg == NULL || CFGetTypeID(msg) == CFNullGetTypeID()) { pass("CFNull message"); return 0; }
72 // TODO _SecServerKeychainSyncUpdate
74 // MARK: ----- Constants -----
76 static CFStringRef circleKey
= CFSTR("Circle");
78 struct SOSKVSTransport
{
79 struct SOSTransport t
;
80 CFStringRef messageKey
;
84 #include <dispatch/dispatch.h>
86 static void putCircleInCloud(SOSCircleRef circle
, dispatch_queue_t work_queue
, dispatch_group_t work_group
)
88 CFErrorRef error
= NULL
;
89 CFDataRef newCloudCircleEncoded
= SOSCircleCopyEncodedData(circle
, kCFAllocatorDefault
, &error
);
90 ok(newCloudCircleEncoded
, "Encoded as: %@ [%@]", newCloudCircleEncoded
, error
);
92 // Send the circle with our application request back to cloud
93 testPutObjectInCloud(circleKey
, newCloudCircleEncoded
, &error
, work_group
, work_queue
);
94 CFReleaseSafe(newCloudCircleEncoded
);
98 // artifacts of test harness : , dispatch_queue_t work_queue, dispatch_group_t work_group)
99 bool SOSAccountEstablishCircle(SOSAccountRef account
, CFStringRef circleName
, CFErrorRef
*error
, dispatch_queue_t work_queue
, dispatch_group_t work_group
);
101 bool SOSAccountEstablishCircle(SOSAccountRef account
, CFStringRef circleName
, CFErrorRef
*error
, dispatch_queue_t work_queue
, dispatch_group_t work_group
)
103 CFErrorRef localError
= NULL
;
104 SOSCircleRef circle
= SOSAccountEnsureCircle(account
, circleName
, NULL
);
106 SecKeyRef user_privkey
= SOSAccountGetPrivateCredential(account
, &localError
);
108 SOSFullPeerInfoRef our_full_peer_info
= SOSAccountGetMyFullPeerInCircleNamed(account
, circleName
, &localError
);
109 SOSPeerInfoRef our_peer_info
= SOSFullPeerInfoGetPeerInfo(our_full_peer_info
);
110 CFRetain(our_peer_info
);
112 SecKeyRef device_key
= SOSFullPeerInfoCopyDeviceKey(our_full_peer_info
, &localError
);
113 ok(device_key
, "Retrieved device_key from full peer info (Error: %@)", localError
);
114 CFReleaseNull(device_key
);
115 CFReleaseNull(localError
);
116 ok(SOSCircleRequestAdmission(circle
, user_privkey
, our_full_peer_info
, &localError
), "Requested admission (%@)", our_peer_info
);
117 ok(SOSCircleAcceptRequests(circle
, user_privkey
, our_full_peer_info
, &localError
), "Accepted self");
119 putCircleInCloud(circle
, work_queue
, work_group
);
120 pass("Put (new) circle in cloud: (%@)", circle
);
125 SOSCircleRef oldCircle
= SOSAccountFindCircle(account
, SOSCircleGetName(circle
));
128 return false; // Can't update one we don't have.
129 // TODO: Ensure we don't let circles get replayed.
131 CFDictionarySetValue(account
->circles
, SOSCircleGetName(circle
), circle
);
134 SOSCircleRef circle
= SOSAccountFindCircle(our_account
, circleKey
);
137 ok(SOSCircleRequestAdmission(circle
, our_full_peer_info
, user_key
, &localError
), "Requested admission (%@)", our_peer_info
);
138 ok(SOSCircleAcceptRequests(circle
, our_full_peer_info
, user_key
, &localError
), "Accepted self");
140 putCircleInCloud(circle
, work_queue
, work_group
);
148 static void runTests(bool Alice
, dispatch_queue_t work_queue
, dispatch_group_t work_group
)
150 CFStringRef our_name
= Alice
? CFSTR("Alice") : CFSTR("Bob");
151 CFDictionaryRef our_gestalt
= SOSCreatePeerGestaltFromName(our_name
);
153 dispatch_queue_t global_queue
= dispatch_get_global_queue (DISPATCH_QUEUE_PRIORITY_DEFAULT
, 0);
155 CFErrorRef error
= NULL
;
157 CFDataRef cfpassword
= CFDataCreate(NULL
, (uint8_t *) "FooFooFoo", 10);
159 CFDataRef parameters
= SOSUserKeyCreateGenerateParameters(&error
);
160 ok(parameters
, "No parameters!");
161 ok(error
== NULL
, "Error: (%@)", error
);
162 CFReleaseNull(error
);
164 SecKeyRef user_privkey
= SOSUserKeygen(cfpassword
, parameters
, &error
);
165 CFReleaseNull(parameters
);
166 CFReleaseNull(cfpassword
);
168 dispatch_semaphore_t start_semaphore
= dispatch_semaphore_create(0);
170 CFStringRef sBobReady
= CFSTR("Bob-Ready");
171 CFStringRef sAliceReady
= CFSTR("Alice-Ready");
172 __block CFDataRef foundNonce
= NULL
;
174 const CFIndex nonceByteCount
= 10;
175 CFMutableDataRef nonce
= CFDataCreateMutable(kCFAllocatorDefault
, nonceByteCount
);
176 CFDataSetLength(nonce
, nonceByteCount
);
177 SecRandomCopyBytes(kSecRandomDefault
, CFDataGetLength(nonce
), CFDataGetMutableBytePtr(nonce
));
179 CloudItemsChangedBlock notification_block
= ^ (CFDictionaryRef returnedValues
)
181 CFTypeRef bobReadyValue
= CFDictionaryGetValue(returnedValues
, sBobReady
);
182 if (isData(bobReadyValue
) && CFEqual(bobReadyValue
, nonce
)) {
183 CFDictionaryRef changes
= CFDictionaryCreateForCFTypes(kCFAllocatorDefault
, sAliceReady
, kCFNull
, sBobReady
, kCFNull
, NULL
);
185 SOSCloudKeychainPutObjectsInCloud(changes
, global_queue
, NULL
);
188 dispatch_semaphore_signal(start_semaphore
);
189 CFReleaseSafe(changes
);
191 CFReleaseSafe(error
);
194 CloudKeychainReplyBlock reply_block
= ^ (CFDictionaryRef returnedValues
, CFErrorRef error
)
196 notification_block(returnedValues
);
201 testClearAll(global_queue
, work_group
);
203 CFArrayRef bobKey
= CFArrayCreateForCFTypes(kCFAllocatorDefault
, sBobReady
, NULL
);
204 SOSCloudKeychainRegisterKeysAndGet(bobKey
, work_queue
, reply_block
, notification_block
);
206 CFStringRef description
= SOSInterestListCopyDescription(bobKey
);
207 pass("%@", description
);
209 CFReleaseNull(description
);
210 CFReleaseNull(bobKey
);
212 CFDictionaryRef changes
= CFDictionaryCreateForCFTypes(kCFAllocatorDefault
, sAliceReady
, nonce
, NULL
);
213 SOSCloudKeychainPutObjectsInCloud(changes
, global_queue
, NULL
);
215 description
= SOSChangesCopyDescription(changes
, true);
216 pass("%@", description
);
217 CFReleaseNull(description
);
219 CFReleaseNull(changes
);
221 CloudItemsChangedBlock notification_block
= ^ (CFDictionaryRef returnedValues
)
223 CFTypeRef aliceReadyValue
= CFDictionaryGetValue(returnedValues
, sAliceReady
);
224 if (isData(aliceReadyValue
)) {
225 foundNonce
= (CFDataRef
) aliceReadyValue
;
226 CFRetain(foundNonce
);
228 pass("signalling found: %@", foundNonce
);
229 dispatch_semaphore_signal(start_semaphore
);
231 CFReleaseSafe(error
);
234 CloudKeychainReplyBlock reply_block
= ^ (CFDictionaryRef returnedValues
, CFErrorRef error
)
236 notification_block(returnedValues
);
239 CFArrayRef aliceKey
= CFArrayCreateForCFTypes(kCFAllocatorDefault
, sAliceReady
, NULL
);
240 SOSCloudKeychainRegisterKeysAndGet(aliceKey
, work_queue
, reply_block
, notification_block
);
242 CFStringRef description
= SOSInterestListCopyDescription(aliceKey
);
243 pass("%@", description
);
244 CFReleaseNull(description
);
246 CFReleaseSafe(aliceKey
);
250 dispatch_semaphore_wait(start_semaphore
, DISPATCH_TIME_FOREVER
);
254 __block CFArrayRef ourWork
= NULL
;
255 __block CFIndex current
= 0;
256 __block SOSAccountRef our_account
= NULL
;
257 typedef CFIndex (^TestStateBlock
) (SOSAccountRef account
, CFErrorRef error
);
259 SOSDataSourceFactoryRef our_data_source_factory
= SOSTestDataSourceFactoryCreate();
260 SOSDataSourceRef our_data_source
= SOSTestDataSourceCreate();
261 SOSTestDataSourceFactoryAddDataSource(our_data_source_factory
, circleKey
, our_data_source
);
263 CloudItemsChangedBlock notification_block
= ^ (CFDictionaryRef returnedValues
)
265 CFStringRef changesString
= SOSChangesCopyDescription(returnedValues
, false);
266 pass("Got: %@", changesString
);
267 CFReleaseNull(changesString
);
269 CFErrorRef error
= NULL
;
271 SOSAccountHandleUpdates(our_account
, returnedValues
, &error
);
273 TestStateBlock thingToDo
= CFArrayGetValueAtIndex(ourWork
, current
);
277 pass("%@ stage %d rv: %@ [error: %@]", our_name
, (int)current
, returnedValues
, error
);
278 current
+= thingToDo(our_account
, error
);
281 if (current
< 0 || current
>= CFArrayGetCount(ourWork
))
282 dispatch_group_leave(work_group
);
284 CFReleaseSafe(error
);
287 CloudKeychainReplyBlock reply_block
= ^ (CFDictionaryRef returnedValues
, CFErrorRef error
)
290 notification_block(returnedValues
);
293 __block
bool initialConnection
= !Alice
;
294 SOSAccountKeyInterestBlock updateKVSKeys
= ^(bool getNewKeysOnly
, CFArrayRef alwaysKeys
, CFArrayRef afterFirstUnlockKeys
, CFArrayRef unlockedKeys
) {
295 CFMutableArrayRef keys
= CFArrayCreateMutableCopy(kCFAllocatorDefault
, 0, alwaysKeys
);
296 CFArrayAppendArray(keys
, afterFirstUnlockKeys
, CFRangeMake(0, CFArrayGetCount(afterFirstUnlockKeys
)));
297 CFArrayAppendArray(keys
, unlockedKeys
, CFRangeMake(0, CFArrayGetCount(unlockedKeys
)));
299 CFStringRef description
= SOSInterestListCopyDescription(keys
);
301 pass("%@", description
);
303 CFReleaseNull(description
);
305 SOSCloudKeychainRegisterKeysAndGet(keys
, work_queue
,
306 initialConnection
? reply_block
: NULL
, notification_block
);
309 initialConnection
= false;
312 SOSAccountDataUpdateBlock updateKVS
= ^ bool (CFDictionaryRef changes
, CFErrorRef
*error
) {
313 CFStringRef changesString
= SOSChangesCopyDescription(changes
, true);
314 pass("Pushing: %@", changesString
);
315 CFReleaseNull(changesString
);
317 SOSCloudKeychainPutObjectsInCloud(changes
, global_queue
,
318 ^ (CFDictionaryRef returnedValues
, CFErrorRef error
)
321 fail("testPutObjectInCloud returned: %@", error
);
329 our_account
= SOSAccountCreate(kCFAllocatorDefault
, our_gestalt
, our_data_source_factory
, updateKVSKeys
, updateKVS
);
331 SOSFullPeerInfoRef our_full_peer_info
= SOSAccountGetMyFullPeerInCircleNamed(our_account
, circleKey
, &error
);
332 SOSPeerInfoRef our_peer_info
= SOSFullPeerInfoGetPeerInfo(our_full_peer_info
);
333 CFRetain(our_peer_info
);
335 SOSAccountAddChangeBlock(our_account
, ^(SOSCircleRef circle
,
336 CFArrayRef peer_additions
, CFArrayRef peer_removals
,
337 CFArrayRef applicant_additions
, CFArrayRef applicant_removals
) {
338 // Should initiate syncing here!
339 bool joined
= CFArrayContainsValue(peer_additions
, CFRangeMake(0, CFArrayGetCount(peer_additions
)), our_peer_info
);
341 pass("Peers Changed [%s] (Add: %@, Remove: %@)", joined
? "*** I'm in ***" : "Not including me.", peer_additions
, peer_removals
);
344 SOSCircleForEachPeer(circle
, ^(SOSPeerInfoRef peer_info
)
346 CFErrorRef error
= NULL
;
348 if (!CFEqual(peer_info
, our_peer_info
)) {
349 ok(SOSAccountSyncWithPeer(our_account
, circle
, peer_info
, NULL
, &error
),
350 "Initiated sync with %@: [Error %@]", peer_info
, error
);
356 ok(our_peer_info
, "Peer Info: %@ [error: %@]", our_peer_info
, error
);
358 //__block SOSEngineRef ourEngine;
360 SOSObjectRef firstObject
= SOSDataSourceCreateGenericItem(our_data_source
, CFSTR("1234"), CFSTR("service"));
362 //------------------------------------------------------------------------
364 //------------------------------------------------------------------------
365 CFArrayRef aliceWorkToDo
=
366 CFArrayCreateForCFTypes(kCFAllocatorDefault
,
367 ^ CFIndex (SOSAccountRef account
, CFErrorRef error
)
370 When we get here, it should only be because Bob has retrieved
371 our circle and requested admission to our circle.
372 If we don't find a circleKey entry, our test setup is wrong.
374 CFErrorRef modifyError
= NULL
;
375 SOSAccountModifyCircle(account
, circleKey
, &modifyError
, ^(SOSCircleRef circle
) {
376 CFErrorRef localError
= NULL
;
378 ok(SOSCircleHasPeer(circle
, our_peer_info
, &localError
), "We're a peer [Error: %@]", localError
);
379 is(SOSCircleCountPeers(circle
), 1, "One peer, woot");
380 is(SOSCircleCountApplicants(circle
), 1, "One applicant, hope it's BOB");
382 ok(SOSCircleAcceptRequests(circle
, user_privkey
, our_full_peer_info
, &localError
), "Accepted peers (%@) [Error: %@]", circle
, localError
);
384 CFReleaseSafe(localError
);
386 CFReleaseSafe(modifyError
);
390 ^ CFIndex (SOSAccountRef account
, CFErrorRef error
)
392 // He should be telling us about him and we should be responding.
394 CFMutableDictionaryRef ourDatabase
= SOSTestDataSourceGetDatabase(our_data_source
);
396 is(CFDictionaryGetCount(ourDatabase
), 0, "Database empty, we're synced");
401 ^ CFIndex (SOSAccountRef account
, CFErrorRef error
)
403 CFMutableDictionaryRef ourDatabase
= SOSTestDataSourceGetDatabase(our_data_source
);
405 is(CFDictionaryGetCount(ourDatabase
), 1, "One element!");
411 //------------------------------------------------------------------------
413 //------------------------------------------------------------------------
415 CFArrayRef bobWorkToDo
=
416 CFArrayCreateForCFTypes(kCFAllocatorDefault
,
417 ^ CFIndex (SOSAccountRef account
, CFErrorRef error
)
419 __block CFIndex increment
= 0;
420 CFErrorRef modifyError
= NULL
;
421 SOSAccountModifyCircle(account
, circleKey
, &modifyError
, ^(SOSCircleRef circle
) {
422 CFErrorRef localError
= NULL
;
424 if (SOSCircleCountPeers(circle
) == 1) {
425 is(SOSCircleCountApplicants(circle
), 0, "No applicants");
426 ok(SOSCircleRequestAdmission(circle
, user_privkey
, our_full_peer_info
, &localError
), "Requested admission (%@) [Error: %@]", circle
, localError
);
430 CFReleaseSafe(localError
);
432 CFReleaseSafe(modifyError
);
435 ^ CFIndex (SOSAccountRef account
, CFErrorRef error
)
437 CFErrorRef modifyError
= NULL
;
438 SOSAccountModifyCircle(account
, circleKey
, &modifyError
, ^(SOSCircleRef circle
) {
439 CFErrorRef localError
= NULL
;
441 ok(SOSCircleHasPeer(circle
, our_peer_info
, &localError
), "We're a peer (%@) [Error: %@]", circle
, localError
);
442 is(SOSCircleCountPeers(circle
), 2, "One peer, hope it's Alice");
443 is(SOSCircleCountApplicants(circle
), 0, "No applicants");
445 CFReleaseSafe(localError
);
447 CFReleaseSafe(modifyError
);
451 ^ CFIndex (SOSAccountRef account
, CFErrorRef error
)
453 CFErrorRef localError
= NULL
;
454 CFMutableDictionaryRef ourDatabase
= SOSTestDataSourceGetDatabase(our_data_source
);
455 is(CFDictionaryGetCount(ourDatabase
), 0, "Database empty, we're synced");
457 SOSTestDataSourceAddObject(our_data_source
, firstObject
, &localError
);
458 CFReleaseNull(localError
);
460 SOSAccountSyncWithAllPeers(account
, &localError
);
461 CFReleaseNull(localError
);
466 ^ CFIndex (SOSAccountRef account
, CFErrorRef error
)
470 CFMutableDictionaryRef ourDatabase
= SOSTestDataSourceGetDatabase(our_data_source
);
471 is(CFDictionaryGetCount(ourDatabase
), 1, "Still one element!");
477 //------------------------------------------------------------------------
479 //------------------------------------------------------------------------
481 ourWork
= Alice
? aliceWorkToDo
: bobWorkToDo
;
485 Here we create a fresh circle, add and accept ourselves
486 Then we post to the cloud and wait for a Circle changed notification
489 CFErrorRef modifyError
= NULL
;
490 SOSAccountModifyCircle(our_account
, circleKey
, &modifyError
, ^(SOSCircleRef circle
) {
491 CFErrorRef localError
= NULL
;
493 ok(SOSCircleRequestAdmission(circle
, user_privkey
, our_full_peer_info
, &localError
), "Requested admission (%@) [error: %@]", our_peer_info
, localError
);
494 ok(SOSCircleAcceptRequests(circle
, user_privkey
, our_full_peer_info
, &localError
), "Accepted self [Error: %@]", localError
);
496 CFReleaseSafe(localError
);
498 CFReleaseSafe(modifyError
);
501 // Tell alice we're set to go:
503 CFDictionaryRef changes
= CFDictionaryCreateForCFTypes(kCFAllocatorDefault
, sBobReady
, foundNonce
, NULL
);
504 SOSCloudKeychainPutObjectsInCloud(changes
, global_queue
, NULL
);
505 CFReleaseSafe(changes
);
507 fail("No none found to start the handshake");
510 dispatch_group_wait(work_group
, DISPATCH_TIME_FOREVER
);
512 // We probably never get here since the program exits..
514 CFReleaseNull(aliceWorkToDo
);
515 CFReleaseNull(bobWorkToDo
);
516 CFReleaseNull(our_peer_info
);
517 CFReleaseNull(foundNonce
);
521 // MARK: ----- start of all tests -----
522 static void tests(bool Alice
)
524 dispatch_queue_t work_queue
= dispatch_queue_create("NotificationQueue", DISPATCH_QUEUE_SERIAL
); //;
525 dispatch_group_t work_group
= dispatch_group_create();
527 // Prep the group for exitting the whole shebang.
528 runTests(Alice
, work_queue
, work_group
);
531 // define the options table for the command line
532 static const struct option options
[] =
534 { "verbose", optional_argument
, NULL
, 'v' },
535 { "identity", optional_argument
, NULL
, 'i' },
536 { "clear", optional_argument
, NULL
, 'C' },
540 static int kAliceTestCount
= 32;
541 static int kBobTestCount
= 30;
543 int sc_101_accountsync(int argc
, char *const *argv
)
545 char *identity
= NULL
;
550 while (argSlot
= -1, (arg
= getopt_long(argc
, (char * const *)argv
, "i:vC", options
, &argSlot
)) != -1)
554 identity
= (char *)(optarg
);
556 case 'C': // should set up to call testClearAll
559 secerror("arg: %s", optarg
);
565 secerror("We are %s",identity
);
566 if (!strcmp(identity
, "alice"))
570 plan_tests(Alice
?kAliceTestCount
:kBobTestCount
);