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