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_100_devicecircle -v -- -i alice
24 // /AppleInternal/Applications/SecurityTests.app/SecurityTests sc_100_devicecircle -v -- -i bob
26 #include <SecureObjectSync/SOSEngine.h>
27 #include <SecureObjectSync/SOSPeer.h>
29 #include "SOSCircle_regressions.h"
31 #include <corecrypto/ccsha2.h>
33 #include <utilities/SecCFWrappers.h>
34 #include <utilities/debugging.h>
38 #include <AssertMacros.h>
43 #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");
77 //static CFStringRef messageKey = CFSTR("Message");
78 static CFStringRef messageFromAliceToBobKey
= CFSTR("AliceToBob");
79 static CFStringRef messageFromBobToAliceKey
= CFSTR("BobToAlice");
82 static CFStringRef messageGoBobGoKey
= CFSTR("GoBobGo");
85 struct SOSKVSTransport
{
86 struct SOSTransport t
;
87 CFStringRef messageKey
;
91 #include <dispatch/dispatch.h>
93 static bool kvsTransportSend(CFStringRef key
, CFDataRef message
) {
94 __block
bool success
= true;
95 __block dispatch_semaphore_t waitSemaphore
= dispatch_semaphore_create(0);
97 CFDictionaryRef objects
= CFDictionaryCreateForCFTypes(kCFAllocatorDefault
, key
, message
, NULL
);
99 CFStringRef desc
= SOSMessageCopyDescription(message
);
100 pass("kvsTransportSend: %@ %@", key
, desc
);
103 SOSCloudKeychainPutObjectsInCloud(objects
, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT
, 0),
104 ^ (CFDictionaryRef returnedValues
, CFErrorRef error
)
106 ok(error
== NULL
, "Error from SOSCloudKeychainPutObjectsInCloud %@:", error
);
109 dispatch_semaphore_signal(waitSemaphore
);
112 dispatch_release(waitSemaphore
);
117 static void putCircleInCloud(SOSCircleRef circle
, dispatch_queue_t work_queue
, dispatch_group_t work_group
)
119 CFErrorRef error
= NULL
;
120 CFDataRef newCloudCircleEncoded
= SOSCircleCopyEncodedData(circle
, kCFAllocatorDefault
, &error
);
121 ok(newCloudCircleEncoded
, "Encoded as: %@ [%@]", newCloudCircleEncoded
, error
);
123 // Send the circle with our application request back to cloud
124 testPutObjectInCloud(circleKey
, newCloudCircleEncoded
, &error
, work_group
, work_queue
);
125 CFReleaseSafe(newCloudCircleEncoded
);
128 static void mutate_inflated_circle(SOSCircleRef newCircle
,
129 dispatch_queue_t work_queue
, dispatch_group_t work_group
,
130 void (^action
)(SOSCircleRef circle
))
132 //JCH action(newCircle);
134 putCircleInCloud(newCircle
, work_queue
, work_group
);
135 pass("Put circle in cloud: (%@)", newCircle
);
138 static void mutate_account_circle(SOSAccountRef account
,
139 CFDataRef encodedCircle
,
140 dispatch_queue_t work_queue
, dispatch_group_t work_group
,
141 void (^action
)(SOSCircleRef circle
))
143 bool mutated
= false;
145 CFErrorRef error
= NULL
;
148 skip("Must be CFData!", 3, encodedCircle
&& (CFDataGetTypeID() == CFGetTypeID(encodedCircle
)));
149 SOSCircleRef newCircle
= SOSCircleCreateFromData(kCFAllocatorDefault
, encodedCircle
, &error
);
150 ok(newCircle
, "Decoded data version of circle: %@", newCircle
);
152 SOSCircleRef accountCircle
= SOSAccountFindCircle(account
, SOSCircleGetName(newCircle
), NULL
);
153 ok(accountCircle
, "Found our circle in account");
155 if (!CFEqual(newCircle
, accountCircle
)) {
156 pass("New circle and accountCircle not equal");
161 bool updated
= SOSAccountUpdateCircle(account
, newCircle
, &error
);
163 pass("Updated account with new circle");
165 mutate_inflated_circle(newCircle
, work_queue
, work_group
, action
);
169 pass("mutate_account_circle exit");
172 static void SOSCloudKeychainRegisterKeysAndGetWithNotification(CFArrayRef keysToRegister
, dispatch_queue_t processQueue
, CloudKeychainReplyBlock replyBlock
)
174 SOSCloudKeychainRegisterKeysAndGet(keysToRegister
, processQueue
, replyBlock
,
175 ^(CFDictionaryRef dict
) { replyBlock(dict
, NULL
);});
178 #define kAlicePeerID CFSTR("alice-peer-id")
179 #define kBobPeerID CFSTR("bob-peer-id")
181 static void runTests(bool Alice
, dispatch_queue_t work_queue
, dispatch_group_t work_group
)
183 SecKeyRef user_key
= NULL
;
184 SecKeyRef public_key
= NULL
;
185 CFErrorRef error
= NULL
;
186 CFStringRef cflabel
= CFSTR("TEST_USERKEY");
187 CFDataRef cfpassword
= CFDataCreate(NULL
, (uint8_t *) "FooFooFoo", 10);
189 GenerateECPair(256, &public_key
, &user_key
);
190 SOSCCRegisterUserCredentials(cflabel
, cfpassword
, &error
);
191 CFReleaseNull(public_key
);
192 CFReleaseNull(cfpassword
);
194 SOSDataSourceFactoryRef our_data_source_factory
= SOSTestDataSourceFactoryCreate();
195 SOSDataSourceRef our_data_source
= SOSTestDataSourceCreate();
196 SOSTestDataSourceFactoryAddDataSource(our_data_source_factory
, circleKey
, our_data_source
);
198 CFDictionaryRef gestalt
= SOSCreatePeerGestaltFromName(Alice
? kAlicePeerID
: kBobPeerID
);
200 SOSAccountRef our_account
= SOSAccountCreate(kCFAllocatorDefault
, gestalt
, our_data_source_factory
, NULL
, NULL
);
201 SOSAccountEnsureCircle(our_account
, circleKey
, NULL
);
203 SOSFullPeerInfoRef our_full_peer_info
= SOSAccountGetMyFullPeerInCircleNamed(our_account
, circleKey
, &error
);
204 __block SOSPeerInfoRef our_peer_info
= SOSFullPeerInfoGetPeerInfo(our_full_peer_info
);
205 CFRetain(our_peer_info
);
206 pass("We are %s (%@)", Alice
?"Alice":"Bob", our_peer_info
);
208 ok(our_peer_info
, "Peer Info: %@ [error: %@]", our_peer_info
, error
);
210 /// Setup ck notifications.
212 CFArrayRef keysToRegister
= NULL
;
214 CFStringRef message_key
;
218 keysToRegister
= CFArrayCreateForCFTypes(kCFAllocatorDefault
, circleKey
, messageFromBobToAliceKey
, NULL
);
219 message_key
= messageFromAliceToBobKey
;
224 keysToRegister
= CFArrayCreateForCFTypes(kCFAllocatorDefault
, circleKey
, messageFromAliceToBobKey
, messageGoBobGoKey
, NULL
);
226 keysToRegister
= CFArrayCreateForCFTypes(kCFAllocatorDefault
, circleKey
, messageFromAliceToBobKey
, NULL
);
228 message_key
= messageFromBobToAliceKey
;
231 SOSPeerSendBlock transport
= ^bool (CFDataRef message
, CFErrorRef
*error
) {
232 kvsTransportSend(message_key
, message
);
237 __block CFIndex current
= 0;
238 typedef CFIndex (^TestStateBlock
) (CFDictionaryRef returnedValues
, CFErrorRef error
);
240 //------------------------------------------------------------------------
242 //------------------------------------------------------------------------
243 CFArrayRef aliceWorkToDo
=
244 CFArrayCreateForCFTypes(kCFAllocatorDefault
,
245 ^ CFIndex (CFDictionaryRef returnedValues
, CFErrorRef error
)
248 We get here as the return from registerkeys.
249 Here we create a fresh circle, add and accept ourselves
250 Then we post to the cloud and wait for a Circle changed notification
253 (void) returnedValues
; (void) error
;
255 testClearAll(work_queue
, work_group
);
257 CFErrorRef localError
= NULL
;
260 testPutObjectInCloud(messageGoBobGoKey
, CFSTR("Go Bob, Go!"), &localError
, work_group
, work_queue
);
263 SOSCircleRef circle
= SOSAccountFindCircle(our_account
, circleKey
, NULL
);
266 ok(SOSCircleRequestAdmission(circle
, SOSAccountGetPrivateCredential(our_account
, &error
), our_full_peer_info
, &localError
), "Requested admission (%@)", our_peer_info
);
267 ok(SOSCircleAcceptRequests(circle
, SOSAccountGetPrivateCredential(our_account
, &error
), our_full_peer_info
, &localError
), "Accepted self");
269 our_peer_info
= SOSFullPeerInfoGetPeerInfo(our_full_peer_info
);
271 putCircleInCloud(circle
, work_queue
, work_group
);
272 pass("Put circle in cloud: (%@)", circle
);
278 ^ CFIndex (CFDictionaryRef returnedValues
, CFErrorRef error
)
280 __block CFIndex incrementAmount
= 0; // Don't increment unless we find stuff
283 When we get here, it should only be because Bob has retrieved
284 our circle and requested admission to our circle.
285 If we don't find a circleKey entry, our test setup is wrong.
289 __block CFErrorRef localError
= NULL
;
290 CFDataRef value
= CFDictionaryGetValue(returnedValues
, circleKey
);
291 VALUECFNULLCHECK(value
);
292 skip("cloudCircle NULL!", 5, value
);
293 ok(value
, "Found circle");
295 mutate_account_circle(our_account
, value
, work_queue
, work_group
,
296 ^ (SOSCircleRef circle
)
298 ok(SOSCircleHasPeer(circle
, our_peer_info
, &localError
), "We're a peer [error: %@]", localError
);
299 CFReleaseNull(localError
);
301 is(SOSCircleCountPeers(circle
), 1, "One peer, woot");
302 is(SOSCircleCountApplicants(circle
), 1, "One applicant, hope it's BOB");
305 ok(SOSCircleAcceptRequests(circle
, SOSAccountGetPrivateCredential(our_account
, &pkerr
), our_full_peer_info
, &localError
), "Accepted peers [error: %@]", localError
);
306 CFReleaseNull(localError
);
308 ok(SOSCircleSyncWithPeer(our_full_peer_info
, circle
, our_data_source_factory
, transport
, kBobPeerID
, &localError
), "Started sync: [error: %@]", localError
);
309 CFReleaseNull(localError
);
314 CFReleaseSafe(localError
);
317 return incrementAmount
;
319 ^ (CFDictionaryRef returnedValues
, CFErrorRef error
)
321 CFErrorRef localError
= NULL
;
322 SOSCircleRef circle
= SOSAccountFindCircle(our_account
, circleKey
, NULL
);
323 CFDataRef message
= CFDictionaryGetValue(returnedValues
, messageFromBobToAliceKey
);
324 VALUECFNULLCHECK(message
);
326 ok(message
, "Saw response to manifest message from Bob");
327 ok(SOSCircleHandlePeerMessage(circle
, our_full_peer_info
, our_data_source_factory
, transport
, kBobPeerID
, message
, &localError
), "handle message from bob: [error: %@]", localError
);
330 // Sync again after adding an empty object
331 CFDictionaryRef object
= CFDictionaryCreate(0, NULL
, NULL
, 0, &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
);
332 ok(SOSTestDataSourceAddObject(our_data_source
, object
, &error
), "add empty object to datasource [error: %@]", error
);
334 ok(ourEngine
= SOSCircleCopyEngine(circle
, our_data_source_factory
, &localError
), "get ourEngine [error: %@]", localError
);
337 SOSPeerRef bobPeer
= SOSCircleCopyPeer(circle
, transport
, kBobPeerID
, &localError
);
338 ok(bobPeer
, "Got bob: [error: %@]", error
);
339 ok(SOSEngineSyncWithPeer(ourEngine
, bobPeer
, false, &localError
), "tell Alice sync with peer Bob");
340 SOSPeerDispose(bobPeer
);
342 CFReleaseNull(localError
);
347 ^ (CFDictionaryRef returnedValues
, CFErrorRef error
)
349 CFErrorRef localError
= NULL
;
350 SOSCircleRef circle
= SOSAccountFindCircle(our_account
, circleKey
);
351 CFDataRef message
= CFDictionaryGetValue(returnedValues
, messageFromBobToAliceKey
);
352 VALUECFNULLCHECK(message
);
353 ok(message
, "Saw response to manifest message from Bob - 2");
354 ok(SOSCircleHandlePeerMessage(circle
, our_data_source_factory
, transport
, kBobPeerID
, message
, &localError
), "handle message from bob: [error: %@]", localError
);
360 //------------------------------------------------------------------------
362 //------------------------------------------------------------------------
364 CFArrayRef bobWorkToDo
=
365 CFArrayCreateForCFTypes(kCFAllocatorDefault
,
367 ^ CFIndex (CFDictionaryRef returnedValues
, CFErrorRef error
)
369 __block CFIndex incrementAmount
= 0; // Don't increment unless we find stuff
370 __block CFErrorRef localError
= NULL
;
373 CFTypeRef goMessage
= CFDictionaryGetValue(returnedValues
, messageGoBobGoKey
);
374 if (!goMessage
) // We can discard any changes we see here; they are stale
377 SOSCloudKeychainRemoveObjectForKey(messageGoBobGoKey
, work_queue
,
378 ^ (CFDictionaryRef returnedValues
, CFErrorRef error
)
380 pass("ACK from messageGoBobGoKey");
384 CFTypeRef value
= CFDictionaryGetValue(returnedValues
, circleKey
);
385 VALUECFNULLCHECK(value
);
387 ok(value
, "Found circle %@", value
);
389 mutate_account_circle(our_account
, value
, work_queue
, work_group
,
390 ^ (SOSCircleRef circle
)
392 int peerCount
= SOSCircleCountPeers(circle
);
393 ok(peerCount
== 1, "One peer, hope it's Alice");
395 printf("NOT One peer, hope it's Alice: saw %d peers\n", peerCount
);
396 ok(SOSCircleCountApplicants(circle
) == 0, "No applicants");
399 ok(SOSCircleRequestAdmission(circle
, SOSAccountGetPrivateCredential(our_account
, &pkerr
), our_full_peer_info
, &localError
), "Requested admission");
400 our_peer_info
= SOSFullPeerInfoGetPeerInfo(our_full_peer_info
);
405 CFReleaseSafe(localError
);
407 return incrementAmount
;
409 ^ CFIndex (CFDictionaryRef returnedValues
, CFErrorRef error
)
411 __block CFIndex incrementAmount
= 0; // Don't increment unless we find stuff
412 __block CFErrorRef localError
= NULL
;
414 CFDataRef value
= CFDictionaryGetValue(returnedValues
, circleKey
);
415 VALUECFNULLCHECK(value
);
416 ok(value
, "Found circle");
418 mutate_account_circle(our_account
, value
, work_queue
, work_group
,
419 ^ (SOSCircleRef circle
)
421 ok(SOSCircleHasPeer(circle
, our_peer_info
, &localError
), "We're a peer");
422 ok(SOSCircleCountPeers(circle
) == 2, "Both peers!");
423 ok(SOSCircleCountApplicants(circle
) == 0, "No applicants!");
426 ok(message
= CFDictionaryGetValue(returnedValues
, messageFromAliceToBobKey
), "got message from alice");
427 // VALUECFNULLCHECK(message);
428 if (message
== NULL
|| CFGetTypeID(message
) == CFNullGetTypeID()) { pass("CFNull message"); return; }
429 ok(SOSCircleHandlePeerMessage(circle
, our_full_peer_info
, our_data_source_factory
, transport
, kAlicePeerID
, message
, &localError
), "handle message from alice: %@", localError
);
434 CFReleaseSafe(localError
);
435 return incrementAmount
;
440 // this next block would normally be looking at the reply to the initial sync message.
441 // Since that one was the same we sent out, we won't get a notification for that.
442 // This block looks at the sync message of the empty object datasource
443 ^ (CFDictionaryRef returnedValues
, CFErrorRef error
)
445 CFErrorRef localError
= NULL
;
446 SOSCircleRef circle
= SOSAccountFindCircle(our_account
, circleKey
);
447 CFDataRef message
= CFDictionaryGetValue(returnedValues
, messageFromBobToAliceKey
);
448 VALUECFNULLCHECK(message
);
449 ok(message
, "Saw response to manifest message from Alice 2");
450 ok(SOSCircleHandlePeerMessage(circle
, our_data_source_factory
, transport
, kAlicePeerID
, message
, &localError
), "handle message from Alice 2: %@", localError
);
457 //------------------------------------------------------------------------
459 //------------------------------------------------------------------------
461 CFArrayRef ourWork
= Alice
? aliceWorkToDo
: bobWorkToDo
;
463 SOSCloudKeychainRegisterKeysAndGetWithNotification(keysToRegister
, work_queue
,
464 ^ (CFDictionaryRef returnedValues
, CFErrorRef error
)
466 pass("Got key changes: %@ [error: %@]", returnedValues
, error
);
468 CFReleaseSafe(error
);
470 TestStateBlock thingToDo
= CFArrayGetValueAtIndex(ourWork
, current
);
474 pass("%s stage %d rv: %@ [error: %@]", Alice
?"Alice":"Bob", (int)current
, returnedValues
, error
);
475 current
+= thingToDo(returnedValues
, error
);
478 if (current
< 0 || current
>= CFArrayGetCount(ourWork
))
479 dispatch_group_leave(work_group
);
482 dispatch_group_wait(work_group
, DISPATCH_TIME_FOREVER
);
484 // We probably never get here since the program exits..
486 CFReleaseNull(aliceWorkToDo
);
487 CFReleaseNull(bobWorkToDo
);
488 CFReleaseNull(gestalt
);
489 CFReleaseNull(our_account
);
490 CFReleaseNull(our_peer_info
);
491 CFReleaseNull(user_key
);
492 CFReleaseNull(public_key
); // Should be NULL but just in case..
498 CFDictionaryRef object
= CFDictionaryCreate(0, NULL
, NULL
, 0, &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
);
499 ok(SOSTestDataSourceAddObject(our_data_source
, object
, &error
), "add empty object to datasource (error: %@)", error
);
500 CFReleaseNull(error
);
501 CFReleaseNull(object
);
503 sendManifestDigest(transportX
, pqrEngine
, our_circle
, our_peer_info
);
504 ok(waitForSemaphore(), "Got ACK for manifest with object");
505 ok(handleCloudMessage(our_circle
, our_data_source
, t
), "Got ACK for manifest with object");
510 // MARK: ----- start of all tests -----
511 static void tests(bool Alice
)
513 dispatch_queue_t work_queue
= dispatch_get_global_queue (DISPATCH_QUEUE_PRIORITY_DEFAULT
, 0);
514 dispatch_group_t work_group
= dispatch_group_create();
516 // Queue the work we want to do.
517 runTests(Alice
, work_queue
, work_group
);
521 // define the options table for the command line
522 static const struct option options
[] =
524 { "verbose", optional_argument
, NULL
, 'v' },
525 { "identity", optional_argument
, NULL
, 'i' },
526 { "clear", optional_argument
, NULL
, 'C' },
530 static int kAliceTestCount
= 22;
531 static int kBobTestCount
= 20;
533 int sc_100_devicecircle(int argc
, char *const *argv
)
535 char *identity
= NULL
;
536 // extern int optind;
541 while (argSlot
= -1, (arg
= getopt_long(argc
, (char * const *)argv
, "i:vC", options
, &argSlot
)) != -1)
545 identity
= (char *)(optarg
);
547 case 'C': // should set up to call testClearAll
550 secerror("arg: %s", optarg
);
556 secerror("We are %s",identity
);
557 if (!strcmp(identity
, "alice"))
561 plan_tests(Alice
?kAliceTestCount
:kBobTestCount
);