]> git.saurik.com Git - apple/security.git/blob - sec/SOSCircle/Regressions/sc-101-accountsync.c
Security-55471.14.8.tar.gz
[apple/security.git] / sec / SOSCircle / Regressions / sc-101-accountsync.c
1 //
2 // sc-100-devicecircle.c
3 // sec
4 //
5 // Created by John Hurley 10/16/12.
6 // Copyright 2012 Apple Inc. All rights reserved.
7 //
8
9 /*
10 This test is a combination of sc-75, sc_93 and sc_94 that can
11 be run on two devices.
12
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
16
17 Whenever you confirm a new peer, must start sync
18
19 Test sc-98 can be run before this to clear out kvs
20 */
21
22 // Run on 2 devices:
23 // /AppleInternal/Applications/SecurityTests.app/SecurityTests sc_101_accountsync -v -- -i alice
24 // /AppleInternal/Applications/SecurityTests.app/SecurityTests sc_101_accountsync -v -- -i bob
25
26 #include <SecureObjectSync/SOSEngine.h>
27 #include <SecureObjectSync/SOSPeer.h>
28
29 #include "SOSCircle_regressions.h"
30
31 #include <corecrypto/ccsha2.h>
32 #include <Security/SecRandom.h>
33
34 #include <utilities/SecCFWrappers.h>
35 #include <utilities/debugging.h>
36 #include <utilities/iOSforOSX.h>
37 #include <stdint.h>
38
39 #include <AssertMacros.h>
40
41 #include <stdio.h>
42 #include <stdlib.h>
43 #include <unistd.h>
44 #include <CoreFoundation/CFDate.h>
45 #include <getopt.h>
46
47 #include <Security/SecKey.h>
48
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>
55
56 #include "SOSCircle_regressions.h"
57 #include "SOSRegressionUtilities.h"
58 #include "SOSTestDataSource.h"
59 #include "SOSTestTransport.h"
60 #include "SOSCloudKeychainClient.h"
61
62 #include <securityd/SOSCloudCircleServer.h>
63
64 #ifndef SEC_CONST_DECL
65 #define SEC_CONST_DECL(k,v) CFTypeRef k = (CFTypeRef)(CFSTR(v));
66 #endif
67
68 //static dispatch_queue_t wait_queue = NULL;
69
70 #define VALUECFNULLCHECK(msg) if (msg == NULL || CFGetTypeID(msg) == CFNullGetTypeID()) { pass("CFNull message"); return 0; }
71
72 // TODO _SecServerKeychainSyncUpdate
73
74 // MARK: ----- Constants -----
75
76 static CFStringRef circleKey = CFSTR("Circle");
77
78 struct SOSKVSTransport {
79 struct SOSTransport t;
80 CFStringRef messageKey;
81 };
82
83 #include <notify.h>
84 #include <dispatch/dispatch.h>
85
86 static void putCircleInCloud(SOSCircleRef circle, dispatch_queue_t work_queue, dispatch_group_t work_group)
87 {
88 CFErrorRef error = NULL;
89 CFDataRef newCloudCircleEncoded = SOSCircleCopyEncodedData(circle, kCFAllocatorDefault, &error);
90 ok(newCloudCircleEncoded, "Encoded as: %@ [%@]", newCloudCircleEncoded, error);
91
92 // Send the circle with our application request back to cloud
93 testPutObjectInCloud(circleKey, newCloudCircleEncoded, &error, work_group, work_queue);
94 CFReleaseSafe(newCloudCircleEncoded);
95 }
96
97
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);
100
101 bool SOSAccountEstablishCircle(SOSAccountRef account, CFStringRef circleName, CFErrorRef *error, dispatch_queue_t work_queue, dispatch_group_t work_group)
102 {
103 CFErrorRef localError = NULL;
104 SOSCircleRef circle = SOSAccountEnsureCircle(account, circleName, NULL);
105 CFRetain(circle);
106 SecKeyRef user_privkey = SOSAccountGetPrivateCredential(account, &localError);
107
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);
111
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");
118
119 putCircleInCloud(circle, work_queue, work_group);
120 pass("Put (new) circle in cloud: (%@)", circle);
121
122 #if 0
123
124
125 SOSCircleRef oldCircle = SOSAccountFindCircle(account, SOSCircleGetName(circle));
126
127 if (!oldCircle)
128 return false; // Can't update one we don't have.
129 // TODO: Ensure we don't let circles get replayed.
130
131 CFDictionarySetValue(account->circles, SOSCircleGetName(circle), circle);
132
133 //----
134 SOSCircleRef circle = SOSAccountFindCircle(our_account, circleKey);
135 CFRetain(circle);
136
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");
139
140 putCircleInCloud(circle, work_queue, work_group);
141
142 //-----
143 #endif
144 return true;
145 }
146
147
148 static void runTests(bool Alice, dispatch_queue_t work_queue, dispatch_group_t work_group)
149 {
150 CFStringRef our_name = Alice ? CFSTR("Alice") : CFSTR("Bob");
151 CFDictionaryRef our_gestalt = SOSCreatePeerGestaltFromName(our_name);
152
153 dispatch_queue_t global_queue = dispatch_get_global_queue (DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
154
155 CFErrorRef error = NULL;
156
157 CFDataRef cfpassword = CFDataCreate(NULL, (uint8_t *) "FooFooFoo", 10);
158
159 CFDataRef parameters = SOSUserKeyCreateGenerateParameters(&error);
160 ok(parameters, "No parameters!");
161 ok(error == NULL, "Error: (%@)", error);
162 CFReleaseNull(error);
163
164 SecKeyRef user_privkey = SOSUserKeygen(cfpassword, parameters, &error);
165 CFReleaseNull(parameters);
166 CFReleaseNull(cfpassword);
167
168 dispatch_semaphore_t start_semaphore = dispatch_semaphore_create(0);
169
170 CFStringRef sBobReady = CFSTR("Bob-Ready");
171 CFStringRef sAliceReady = CFSTR("Alice-Ready");
172 __block CFDataRef foundNonce = NULL;
173 if (Alice) {
174 const CFIndex nonceByteCount = 10;
175 CFMutableDataRef nonce = CFDataCreateMutable(kCFAllocatorDefault, nonceByteCount);
176 CFDataSetLength(nonce, nonceByteCount);
177 SecRandomCopyBytes(kSecRandomDefault, CFDataGetLength(nonce), CFDataGetMutableBytePtr(nonce));
178
179 CloudItemsChangedBlock notification_block = ^ (CFDictionaryRef returnedValues)
180 {
181 CFTypeRef bobReadyValue = CFDictionaryGetValue(returnedValues, sBobReady);
182 if (isData(bobReadyValue) && CFEqual(bobReadyValue, nonce)) {
183 CFDictionaryRef changes = CFDictionaryCreateForCFTypes(kCFAllocatorDefault, sAliceReady, kCFNull, sBobReady, kCFNull, NULL);
184
185 SOSCloudKeychainPutObjectsInCloud(changes, global_queue, NULL);
186
187 pass("signalling");
188 dispatch_semaphore_signal(start_semaphore);
189 CFReleaseSafe(changes);
190 }
191 CFReleaseSafe(error);
192 };
193
194 CloudKeychainReplyBlock reply_block = ^ (CFDictionaryRef returnedValues, CFErrorRef error)
195 {
196 notification_block(returnedValues);
197 };
198
199 pass("Clearing");
200
201 testClearAll(global_queue, work_group);
202
203 CFArrayRef bobKey = CFArrayCreateForCFTypes(kCFAllocatorDefault, sBobReady, NULL);
204 SOSCloudKeychainRegisterKeysAndGet(bobKey, work_queue, reply_block, notification_block);
205
206 CFStringRef description = SOSInterestListCopyDescription(bobKey);
207 pass("%@", description);
208
209 CFReleaseNull(description);
210 CFReleaseNull(bobKey);
211
212 CFDictionaryRef changes = CFDictionaryCreateForCFTypes(kCFAllocatorDefault, sAliceReady, nonce, NULL);
213 SOSCloudKeychainPutObjectsInCloud(changes, global_queue, NULL);
214
215 description = SOSChangesCopyDescription(changes, true);
216 pass("%@", description);
217 CFReleaseNull(description);
218
219 CFReleaseNull(changes);
220 } else {
221 CloudItemsChangedBlock notification_block = ^ (CFDictionaryRef returnedValues)
222 {
223 CFTypeRef aliceReadyValue = CFDictionaryGetValue(returnedValues, sAliceReady);
224 if (isData(aliceReadyValue)) {
225 foundNonce = (CFDataRef) aliceReadyValue;
226 CFRetain(foundNonce);
227
228 pass("signalling found: %@", foundNonce);
229 dispatch_semaphore_signal(start_semaphore);
230 }
231 CFReleaseSafe(error);
232 };
233
234 CloudKeychainReplyBlock reply_block = ^ (CFDictionaryRef returnedValues, CFErrorRef error)
235 {
236 notification_block(returnedValues);
237 };
238
239 CFArrayRef aliceKey = CFArrayCreateForCFTypes(kCFAllocatorDefault, sAliceReady, NULL);
240 SOSCloudKeychainRegisterKeysAndGet(aliceKey, work_queue, reply_block, notification_block);
241
242 CFStringRef description = SOSInterestListCopyDescription(aliceKey);
243 pass("%@", description);
244 CFReleaseNull(description);
245
246 CFReleaseSafe(aliceKey);
247 }
248
249 pass("Waiting");
250 dispatch_semaphore_wait(start_semaphore, DISPATCH_TIME_FOREVER);
251 pass("Moving on");
252
253
254 __block CFArrayRef ourWork = NULL;
255 __block CFIndex current = 0;
256 __block SOSAccountRef our_account = NULL;
257 typedef CFIndex (^TestStateBlock) (SOSAccountRef account, CFErrorRef error);
258
259 SOSDataSourceFactoryRef our_data_source_factory = SOSTestDataSourceFactoryCreate();
260 SOSDataSourceRef our_data_source = SOSTestDataSourceCreate();
261 SOSTestDataSourceFactoryAddDataSource(our_data_source_factory, circleKey, our_data_source);
262
263 CloudItemsChangedBlock notification_block = ^ (CFDictionaryRef returnedValues)
264 {
265 CFStringRef changesString = SOSChangesCopyDescription(returnedValues, false);
266 pass("Got: %@", changesString);
267 CFReleaseNull(changesString);
268
269 CFErrorRef error = NULL;
270
271 SOSAccountHandleUpdates(our_account, returnedValues, &error);
272
273 TestStateBlock thingToDo = CFArrayGetValueAtIndex(ourWork, current);
274
275 if (thingToDo)
276 {
277 pass("%@ stage %d rv: %@ [error: %@]", our_name, (int)current, returnedValues, error);
278 current += thingToDo(our_account, error);
279 }
280
281 if (current < 0 || current >= CFArrayGetCount(ourWork))
282 dispatch_group_leave(work_group);
283
284 CFReleaseSafe(error);
285 };
286
287 CloudKeychainReplyBlock reply_block = ^ (CFDictionaryRef returnedValues, CFErrorRef error)
288 {
289 pass("Reply block");
290 notification_block(returnedValues);
291 };
292
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)));
298
299 CFStringRef description = SOSInterestListCopyDescription(keys);
300
301 pass("%@", description);
302
303 CFReleaseNull(description);
304
305 SOSCloudKeychainRegisterKeysAndGet(keys, work_queue,
306 initialConnection ? reply_block : NULL, notification_block);
307
308 CFReleaseSafe(keys);
309 initialConnection = false;
310 };
311
312 SOSAccountDataUpdateBlock updateKVS = ^ bool (CFDictionaryRef changes, CFErrorRef *error) {
313 CFStringRef changesString = SOSChangesCopyDescription(changes, true);
314 pass("Pushing: %@", changesString);
315 CFReleaseNull(changesString);
316
317 SOSCloudKeychainPutObjectsInCloud(changes, global_queue,
318 ^ (CFDictionaryRef returnedValues, CFErrorRef error)
319 {
320 if (error) {
321 fail("testPutObjectInCloud returned: %@", error);
322 CFRelease(error);
323 }
324 });
325 return true;
326 };
327
328
329 our_account = SOSAccountCreate(kCFAllocatorDefault, our_gestalt, our_data_source_factory, updateKVSKeys, updateKVS);
330
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);
334
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);
340
341 pass("Peers Changed [%s] (Add: %@, Remove: %@)", joined ? "*** I'm in ***" : "Not including me.", peer_additions, peer_removals);
342
343 if (joined) {
344 SOSCircleForEachPeer(circle, ^(SOSPeerInfoRef peer_info)
345 {
346 CFErrorRef error = NULL;
347
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);
351 }
352 });
353 }
354 });
355
356 ok(our_peer_info, "Peer Info: %@ [error: %@]", our_peer_info, error);
357
358 //__block SOSEngineRef ourEngine;
359
360 SOSObjectRef firstObject = SOSDataSourceCreateGenericItem(our_data_source, CFSTR("1234"), CFSTR("service"));
361
362 //------------------------------------------------------------------------
363 // ALICE
364 //------------------------------------------------------------------------
365 CFArrayRef aliceWorkToDo =
366 CFArrayCreateForCFTypes(kCFAllocatorDefault,
367 ^ CFIndex (SOSAccountRef account, CFErrorRef error)
368 {
369 /*
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.
373 */
374 CFErrorRef modifyError = NULL;
375 SOSAccountModifyCircle(account, circleKey, &modifyError, ^(SOSCircleRef circle) {
376 CFErrorRef localError = NULL;
377
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");
381
382 ok(SOSCircleAcceptRequests(circle, user_privkey, our_full_peer_info, &localError), "Accepted peers (%@) [Error: %@]", circle, localError);
383
384 CFReleaseSafe(localError);
385 });
386 CFReleaseSafe(modifyError);
387
388 return +1;
389 },
390 ^ CFIndex (SOSAccountRef account, CFErrorRef error)
391 {
392 // He should be telling us about him and we should be responding.
393
394 CFMutableDictionaryRef ourDatabase = SOSTestDataSourceGetDatabase(our_data_source);
395
396 is(CFDictionaryGetCount(ourDatabase), 0, "Database empty, we're synced");
397
398 pass("1");
399 return +1;
400 },
401 ^ CFIndex (SOSAccountRef account, CFErrorRef error)
402 {
403 CFMutableDictionaryRef ourDatabase = SOSTestDataSourceGetDatabase(our_data_source);
404
405 is(CFDictionaryGetCount(ourDatabase), 1, "One element!");
406
407 return +1;
408 },
409 NULL);
410
411 //------------------------------------------------------------------------
412 // BOB
413 //------------------------------------------------------------------------
414
415 CFArrayRef bobWorkToDo =
416 CFArrayCreateForCFTypes(kCFAllocatorDefault,
417 ^ CFIndex (SOSAccountRef account, CFErrorRef error)
418 {
419 __block CFIndex increment = 0;
420 CFErrorRef modifyError = NULL;
421 SOSAccountModifyCircle(account, circleKey, &modifyError, ^(SOSCircleRef circle) {
422 CFErrorRef localError = NULL;
423
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);
427 increment = +1;
428 }
429
430 CFReleaseSafe(localError);
431 });
432 CFReleaseSafe(modifyError);
433 return increment;
434 },
435 ^ CFIndex (SOSAccountRef account, CFErrorRef error)
436 {
437 CFErrorRef modifyError = NULL;
438 SOSAccountModifyCircle(account, circleKey, &modifyError, ^(SOSCircleRef circle) {
439 CFErrorRef localError = NULL;
440
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");
444
445 CFReleaseSafe(localError);
446 });
447 CFReleaseSafe(modifyError);
448
449 return +1;
450 },
451 ^ CFIndex (SOSAccountRef account, CFErrorRef error)
452 {
453 CFErrorRef localError = NULL;
454 CFMutableDictionaryRef ourDatabase = SOSTestDataSourceGetDatabase(our_data_source);
455 is(CFDictionaryGetCount(ourDatabase), 0, "Database empty, we're synced");
456
457 SOSTestDataSourceAddObject(our_data_source, firstObject, &localError);
458 CFReleaseNull(localError);
459
460 SOSAccountSyncWithAllPeers(account, &localError);
461 CFReleaseNull(localError);
462
463 pass("1");
464 return +1;
465 },
466 ^ CFIndex (SOSAccountRef account, CFErrorRef error)
467 {
468 pass("2");
469
470 CFMutableDictionaryRef ourDatabase = SOSTestDataSourceGetDatabase(our_data_source);
471 is(CFDictionaryGetCount(ourDatabase), 1, "Still one element!");
472
473 return +1;
474 },
475 NULL);
476
477 //------------------------------------------------------------------------
478 // START
479 //------------------------------------------------------------------------
480
481 ourWork = Alice ? aliceWorkToDo : bobWorkToDo;
482
483 if (Alice) {
484 /*
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
487 */
488
489 CFErrorRef modifyError = NULL;
490 SOSAccountModifyCircle(our_account, circleKey, &modifyError, ^(SOSCircleRef circle) {
491 CFErrorRef localError = NULL;
492
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);
495
496 CFReleaseSafe(localError);
497 });
498 CFReleaseSafe(modifyError);
499
500 } else {
501 // Tell alice we're set to go:
502 if (foundNonce) {
503 CFDictionaryRef changes = CFDictionaryCreateForCFTypes(kCFAllocatorDefault, sBobReady, foundNonce, NULL);
504 SOSCloudKeychainPutObjectsInCloud(changes, global_queue, NULL);
505 CFReleaseSafe(changes);
506 } else {
507 fail("No none found to start the handshake");
508 }
509 }
510 dispatch_group_wait(work_group, DISPATCH_TIME_FOREVER);
511
512 // We probably never get here since the program exits..
513
514 CFReleaseNull(aliceWorkToDo);
515 CFReleaseNull(bobWorkToDo);
516 CFReleaseNull(our_peer_info);
517 CFReleaseNull(foundNonce);
518
519 }
520
521 // MARK: ----- start of all tests -----
522 static void tests(bool Alice)
523 {
524 dispatch_queue_t work_queue = dispatch_queue_create("NotificationQueue", DISPATCH_QUEUE_SERIAL); //;
525 dispatch_group_t work_group = dispatch_group_create();
526
527 // Prep the group for exitting the whole shebang.
528 runTests(Alice, work_queue, work_group);
529 }
530
531 // define the options table for the command line
532 static const struct option options[] =
533 {
534 { "verbose", optional_argument, NULL, 'v' },
535 { "identity", optional_argument, NULL, 'i' },
536 { "clear", optional_argument, NULL, 'C' },
537 { }
538 };
539
540 static int kAliceTestCount = 32;
541 static int kBobTestCount = 30;
542
543 int sc_101_accountsync(int argc, char *const *argv)
544 {
545 char *identity = NULL;
546 extern char *optarg;
547 int arg, argSlot;
548 bool Alice = false;
549
550 while (argSlot = -1, (arg = getopt_long(argc, (char * const *)argv, "i:vC", options, &argSlot)) != -1)
551 switch (arg)
552 {
553 case 'i':
554 identity = (char *)(optarg);
555 break;
556 case 'C': // should set up to call testClearAll
557 break;
558 default:
559 secerror("arg: %s", optarg);
560 break;
561 }
562
563 if (identity)
564 {
565 secerror("We are %s",identity);
566 if (!strcmp(identity, "alice"))
567 Alice = true;
568 }
569
570 plan_tests(Alice?kAliceTestCount:kBobTestCount);
571 tests(Alice);
572
573 return 0;
574 }