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 76static CFStringRef circleKey = CFSTR("Circle"); 77 78struct SOSKVSTransport { 79 struct SOSTransport t; 80 CFStringRef messageKey; 81}; 82 83#include <notify.h> 84#include <dispatch/dispatch.h> 85 86static 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) 99bool SOSAccountEstablishCircle(SOSAccountRef account, CFStringRef circleName, CFErrorRef *error, dispatch_queue_t work_queue, dispatch_group_t work_group); 100 101bool 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 148static 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 ----- 522static 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 532static 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 540static int kAliceTestCount = 32; 541static int kBobTestCount = 30; 542 543int 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} 575