sc-101-accountsync.c [plain text]
#include <SecureObjectSync/SOSEngine.h>
#include <SecureObjectSync/SOSPeer.h>
#include "SOSCircle_regressions.h"
#include <corecrypto/ccsha2.h>
#include <Security/SecRandom.h>
#include <utilities/SecCFWrappers.h>
#include <utilities/debugging.h>
#include <utilities/iOSforOSX.h>
#include <stdint.h>
#include <AssertMacros.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <CoreFoundation/CFDate.h>
#include <getopt.h>
#include <Security/SecKey.h>
#include <SecureObjectSync/SOSFullPeerInfo.h>
#include <SecureObjectSync/SOSPeerInfo.h>
#include <SecureObjectSync/SOSCircle.h>
#include <SecureObjectSync/SOSCloudCircle.h>
#include <SecureObjectSync/SOSInternal.h>
#include <SecureObjectSync/SOSUserKeygen.h>
#include "SOSCircle_regressions.h"
#include "SOSRegressionUtilities.h"
#include "SOSTestDataSource.h"
#include "SOSTestTransport.h"
#include "SOSCloudKeychainClient.h"
#include <securityd/SOSCloudCircleServer.h>
#ifndef SEC_CONST_DECL
#define SEC_CONST_DECL(k,v) CFTypeRef k = (CFTypeRef)(CFSTR(v));
#endif
#define VALUECFNULLCHECK(msg) if (msg == NULL || CFGetTypeID(msg) == CFNullGetTypeID()) { pass("CFNull message"); return 0; }
static CFStringRef circleKey = CFSTR("Circle");
struct SOSKVSTransport {
struct SOSTransport t;
CFStringRef messageKey;
};
#include <notify.h>
#include <dispatch/dispatch.h>
static void putCircleInCloud(SOSCircleRef circle, dispatch_queue_t work_queue, dispatch_group_t work_group)
{
CFErrorRef error = NULL;
CFDataRef newCloudCircleEncoded = SOSCircleCopyEncodedData(circle, kCFAllocatorDefault, &error);
ok(newCloudCircleEncoded, "Encoded as: %@ [%@]", newCloudCircleEncoded, error);
testPutObjectInCloud(circleKey, newCloudCircleEncoded, &error, work_group, work_queue);
CFReleaseSafe(newCloudCircleEncoded);
}
bool SOSAccountEstablishCircle(SOSAccountRef account, CFStringRef circleName, CFErrorRef *error, dispatch_queue_t work_queue, dispatch_group_t work_group);
bool SOSAccountEstablishCircle(SOSAccountRef account, CFStringRef circleName, CFErrorRef *error, dispatch_queue_t work_queue, dispatch_group_t work_group)
{
CFErrorRef localError = NULL;
SOSCircleRef circle = SOSAccountEnsureCircle(account, circleName, NULL);
CFRetain(circle);
SecKeyRef user_privkey = SOSAccountGetPrivateCredential(account, &localError);
SOSFullPeerInfoRef our_full_peer_info = SOSAccountGetMyFullPeerInCircleNamed(account, circleName, &localError);
SOSPeerInfoRef our_peer_info = SOSFullPeerInfoGetPeerInfo(our_full_peer_info);
CFRetain(our_peer_info);
SecKeyRef device_key = SOSFullPeerInfoCopyDeviceKey(our_full_peer_info, &localError);
ok(device_key, "Retrieved device_key from full peer info (Error: %@)", localError);
CFReleaseNull(device_key);
CFReleaseNull(localError);
ok(SOSCircleRequestAdmission(circle, user_privkey, our_full_peer_info, &localError), "Requested admission (%@)", our_peer_info);
ok(SOSCircleAcceptRequests(circle, user_privkey, our_full_peer_info, &localError), "Accepted self");
putCircleInCloud(circle, work_queue, work_group);
pass("Put (new) circle in cloud: (%@)", circle);
#if 0
SOSCircleRef oldCircle = SOSAccountFindCircle(account, SOSCircleGetName(circle));
if (!oldCircle)
return false;
CFDictionarySetValue(account->circles, SOSCircleGetName(circle), circle);
SOSCircleRef circle = SOSAccountFindCircle(our_account, circleKey);
CFRetain(circle);
ok(SOSCircleRequestAdmission(circle, our_full_peer_info, user_key, &localError), "Requested admission (%@)", our_peer_info);
ok(SOSCircleAcceptRequests(circle, our_full_peer_info, user_key, &localError), "Accepted self");
putCircleInCloud(circle, work_queue, work_group);
#endif
return true;
}
static void runTests(bool Alice, dispatch_queue_t work_queue, dispatch_group_t work_group)
{
CFStringRef our_name = Alice ? CFSTR("Alice") : CFSTR("Bob");
CFDictionaryRef our_gestalt = SOSCreatePeerGestaltFromName(our_name);
dispatch_queue_t global_queue = dispatch_get_global_queue (DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
CFErrorRef error = NULL;
CFDataRef cfpassword = CFDataCreate(NULL, (uint8_t *) "FooFooFoo", 10);
CFDataRef parameters = SOSUserKeyCreateGenerateParameters(&error);
ok(parameters, "No parameters!");
ok(error == NULL, "Error: (%@)", error);
CFReleaseNull(error);
SecKeyRef user_privkey = SOSUserKeygen(cfpassword, parameters, &error);
CFReleaseNull(parameters);
CFReleaseNull(cfpassword);
dispatch_semaphore_t start_semaphore = dispatch_semaphore_create(0);
CFStringRef sBobReady = CFSTR("Bob-Ready");
CFStringRef sAliceReady = CFSTR("Alice-Ready");
__block CFDataRef foundNonce = NULL;
if (Alice) {
const CFIndex nonceByteCount = 10;
CFMutableDataRef nonce = CFDataCreateMutable(kCFAllocatorDefault, nonceByteCount);
CFDataSetLength(nonce, nonceByteCount);
SecRandomCopyBytes(kSecRandomDefault, CFDataGetLength(nonce), CFDataGetMutableBytePtr(nonce));
CloudItemsChangedBlock notification_block = ^ (CFDictionaryRef returnedValues)
{
CFTypeRef bobReadyValue = CFDictionaryGetValue(returnedValues, sBobReady);
if (isData(bobReadyValue) && CFEqual(bobReadyValue, nonce)) {
CFDictionaryRef changes = CFDictionaryCreateForCFTypes(kCFAllocatorDefault, sAliceReady, kCFNull, sBobReady, kCFNull, NULL);
SOSCloudKeychainPutObjectsInCloud(changes, global_queue, NULL);
pass("signalling");
dispatch_semaphore_signal(start_semaphore);
CFReleaseSafe(changes);
}
CFReleaseSafe(error);
};
CloudKeychainReplyBlock reply_block = ^ (CFDictionaryRef returnedValues, CFErrorRef error)
{
notification_block(returnedValues);
};
pass("Clearing");
testClearAll(global_queue, work_group);
CFArrayRef bobKey = CFArrayCreateForCFTypes(kCFAllocatorDefault, sBobReady, NULL);
SOSCloudKeychainRegisterKeysAndGet(bobKey, work_queue, reply_block, notification_block);
CFStringRef description = SOSInterestListCopyDescription(bobKey);
pass("%@", description);
CFReleaseNull(description);
CFReleaseNull(bobKey);
CFDictionaryRef changes = CFDictionaryCreateForCFTypes(kCFAllocatorDefault, sAliceReady, nonce, NULL);
SOSCloudKeychainPutObjectsInCloud(changes, global_queue, NULL);
description = SOSChangesCopyDescription(changes, true);
pass("%@", description);
CFReleaseNull(description);
CFReleaseNull(changes);
} else {
CloudItemsChangedBlock notification_block = ^ (CFDictionaryRef returnedValues)
{
CFTypeRef aliceReadyValue = CFDictionaryGetValue(returnedValues, sAliceReady);
if (isData(aliceReadyValue)) {
foundNonce = (CFDataRef) aliceReadyValue;
CFRetain(foundNonce);
pass("signalling found: %@", foundNonce);
dispatch_semaphore_signal(start_semaphore);
}
CFReleaseSafe(error);
};
CloudKeychainReplyBlock reply_block = ^ (CFDictionaryRef returnedValues, CFErrorRef error)
{
notification_block(returnedValues);
};
CFArrayRef aliceKey = CFArrayCreateForCFTypes(kCFAllocatorDefault, sAliceReady, NULL);
SOSCloudKeychainRegisterKeysAndGet(aliceKey, work_queue, reply_block, notification_block);
CFStringRef description = SOSInterestListCopyDescription(aliceKey);
pass("%@", description);
CFReleaseNull(description);
CFReleaseSafe(aliceKey);
}
pass("Waiting");
dispatch_semaphore_wait(start_semaphore, DISPATCH_TIME_FOREVER);
pass("Moving on");
__block CFArrayRef ourWork = NULL;
__block CFIndex current = 0;
__block SOSAccountRef our_account = NULL;
typedef CFIndex (^TestStateBlock) (SOSAccountRef account, CFErrorRef error);
SOSDataSourceFactoryRef our_data_source_factory = SOSTestDataSourceFactoryCreate();
SOSDataSourceRef our_data_source = SOSTestDataSourceCreate();
SOSTestDataSourceFactoryAddDataSource(our_data_source_factory, circleKey, our_data_source);
CloudItemsChangedBlock notification_block = ^ (CFDictionaryRef returnedValues)
{
CFStringRef changesString = SOSChangesCopyDescription(returnedValues, false);
pass("Got: %@", changesString);
CFReleaseNull(changesString);
CFErrorRef error = NULL;
SOSAccountHandleUpdates(our_account, returnedValues, &error);
TestStateBlock thingToDo = CFArrayGetValueAtIndex(ourWork, current);
if (thingToDo)
{
pass("%@ stage %d rv: %@ [error: %@]", our_name, (int)current, returnedValues, error);
current += thingToDo(our_account, error);
}
if (current < 0 || current >= CFArrayGetCount(ourWork))
dispatch_group_leave(work_group);
CFReleaseSafe(error);
};
CloudKeychainReplyBlock reply_block = ^ (CFDictionaryRef returnedValues, CFErrorRef error)
{
pass("Reply block");
notification_block(returnedValues);
};
__block bool initialConnection = !Alice;
SOSAccountKeyInterestBlock updateKVSKeys = ^(bool getNewKeysOnly, CFArrayRef alwaysKeys, CFArrayRef afterFirstUnlockKeys, CFArrayRef unlockedKeys) {
CFMutableArrayRef keys = CFArrayCreateMutableCopy(kCFAllocatorDefault, 0, alwaysKeys);
CFArrayAppendArray(keys, afterFirstUnlockKeys, CFRangeMake(0, CFArrayGetCount(afterFirstUnlockKeys)));
CFArrayAppendArray(keys, unlockedKeys, CFRangeMake(0, CFArrayGetCount(unlockedKeys)));
CFStringRef description = SOSInterestListCopyDescription(keys);
pass("%@", description);
CFReleaseNull(description);
SOSCloudKeychainRegisterKeysAndGet(keys, work_queue,
initialConnection ? reply_block : NULL, notification_block);
CFReleaseSafe(keys);
initialConnection = false;
};
SOSAccountDataUpdateBlock updateKVS = ^ bool (CFDictionaryRef changes, CFErrorRef *error) {
CFStringRef changesString = SOSChangesCopyDescription(changes, true);
pass("Pushing: %@", changesString);
CFReleaseNull(changesString);
SOSCloudKeychainPutObjectsInCloud(changes, global_queue,
^ (CFDictionaryRef returnedValues, CFErrorRef error)
{
if (error) {
fail("testPutObjectInCloud returned: %@", error);
CFRelease(error);
}
});
return true;
};
our_account = SOSAccountCreate(kCFAllocatorDefault, our_gestalt, our_data_source_factory, updateKVSKeys, updateKVS);
SOSFullPeerInfoRef our_full_peer_info = SOSAccountGetMyFullPeerInCircleNamed(our_account, circleKey, &error);
SOSPeerInfoRef our_peer_info = SOSFullPeerInfoGetPeerInfo(our_full_peer_info);
CFRetain(our_peer_info);
SOSAccountAddChangeBlock(our_account, ^(SOSCircleRef circle,
CFArrayRef peer_additions, CFArrayRef peer_removals,
CFArrayRef applicant_additions, CFArrayRef applicant_removals) {
bool joined = CFArrayContainsValue(peer_additions, CFRangeMake(0, CFArrayGetCount(peer_additions)), our_peer_info);
pass("Peers Changed [%s] (Add: %@, Remove: %@)", joined ? "*** I'm in ***" : "Not including me.", peer_additions, peer_removals);
if (joined) {
SOSCircleForEachPeer(circle, ^(SOSPeerInfoRef peer_info)
{
CFErrorRef error = NULL;
if (!CFEqual(peer_info, our_peer_info)) {
ok(SOSAccountSyncWithPeer(our_account, circle, peer_info, NULL, &error),
"Initiated sync with %@: [Error %@]", peer_info, error);
}
});
}
});
ok(our_peer_info, "Peer Info: %@ [error: %@]", our_peer_info, error);
SOSObjectRef firstObject = SOSDataSourceCreateGenericItem(our_data_source, CFSTR("1234"), CFSTR("service"));
CFArrayRef aliceWorkToDo =
CFArrayCreateForCFTypes(kCFAllocatorDefault,
^ CFIndex (SOSAccountRef account, CFErrorRef error)
{
CFErrorRef modifyError = NULL;
SOSAccountModifyCircle(account, circleKey, &modifyError, ^(SOSCircleRef circle) {
CFErrorRef localError = NULL;
ok(SOSCircleHasPeer(circle, our_peer_info, &localError), "We're a peer [Error: %@]", localError);
is(SOSCircleCountPeers(circle), 1, "One peer, woot");
is(SOSCircleCountApplicants(circle), 1, "One applicant, hope it's BOB");
ok(SOSCircleAcceptRequests(circle, user_privkey, our_full_peer_info, &localError), "Accepted peers (%@) [Error: %@]", circle, localError);
CFReleaseSafe(localError);
});
CFReleaseSafe(modifyError);
return +1;
},
^ CFIndex (SOSAccountRef account, CFErrorRef error)
{
CFMutableDictionaryRef ourDatabase = SOSTestDataSourceGetDatabase(our_data_source);
is(CFDictionaryGetCount(ourDatabase), 0, "Database empty, we're synced");
pass("1");
return +1;
},
^ CFIndex (SOSAccountRef account, CFErrorRef error)
{
CFMutableDictionaryRef ourDatabase = SOSTestDataSourceGetDatabase(our_data_source);
is(CFDictionaryGetCount(ourDatabase), 1, "One element!");
return +1;
},
NULL);
CFArrayRef bobWorkToDo =
CFArrayCreateForCFTypes(kCFAllocatorDefault,
^ CFIndex (SOSAccountRef account, CFErrorRef error)
{
__block CFIndex increment = 0;
CFErrorRef modifyError = NULL;
SOSAccountModifyCircle(account, circleKey, &modifyError, ^(SOSCircleRef circle) {
CFErrorRef localError = NULL;
if (SOSCircleCountPeers(circle) == 1) {
is(SOSCircleCountApplicants(circle), 0, "No applicants");
ok(SOSCircleRequestAdmission(circle, user_privkey, our_full_peer_info, &localError), "Requested admission (%@) [Error: %@]", circle, localError);
increment = +1;
}
CFReleaseSafe(localError);
});
CFReleaseSafe(modifyError);
return increment;
},
^ CFIndex (SOSAccountRef account, CFErrorRef error)
{
CFErrorRef modifyError = NULL;
SOSAccountModifyCircle(account, circleKey, &modifyError, ^(SOSCircleRef circle) {
CFErrorRef localError = NULL;
ok(SOSCircleHasPeer(circle, our_peer_info, &localError), "We're a peer (%@) [Error: %@]", circle, localError);
is(SOSCircleCountPeers(circle), 2, "One peer, hope it's Alice");
is(SOSCircleCountApplicants(circle), 0, "No applicants");
CFReleaseSafe(localError);
});
CFReleaseSafe(modifyError);
return +1;
},
^ CFIndex (SOSAccountRef account, CFErrorRef error)
{
CFErrorRef localError = NULL;
CFMutableDictionaryRef ourDatabase = SOSTestDataSourceGetDatabase(our_data_source);
is(CFDictionaryGetCount(ourDatabase), 0, "Database empty, we're synced");
SOSTestDataSourceAddObject(our_data_source, firstObject, &localError);
CFReleaseNull(localError);
SOSAccountSyncWithAllPeers(account, &localError);
CFReleaseNull(localError);
pass("1");
return +1;
},
^ CFIndex (SOSAccountRef account, CFErrorRef error)
{
pass("2");
CFMutableDictionaryRef ourDatabase = SOSTestDataSourceGetDatabase(our_data_source);
is(CFDictionaryGetCount(ourDatabase), 1, "Still one element!");
return +1;
},
NULL);
ourWork = Alice ? aliceWorkToDo : bobWorkToDo;
if (Alice) {
CFErrorRef modifyError = NULL;
SOSAccountModifyCircle(our_account, circleKey, &modifyError, ^(SOSCircleRef circle) {
CFErrorRef localError = NULL;
ok(SOSCircleRequestAdmission(circle, user_privkey, our_full_peer_info, &localError), "Requested admission (%@) [error: %@]", our_peer_info, localError);
ok(SOSCircleAcceptRequests(circle, user_privkey, our_full_peer_info, &localError), "Accepted self [Error: %@]", localError);
CFReleaseSafe(localError);
});
CFReleaseSafe(modifyError);
} else {
if (foundNonce) {
CFDictionaryRef changes = CFDictionaryCreateForCFTypes(kCFAllocatorDefault, sBobReady, foundNonce, NULL);
SOSCloudKeychainPutObjectsInCloud(changes, global_queue, NULL);
CFReleaseSafe(changes);
} else {
fail("No none found to start the handshake");
}
}
dispatch_group_wait(work_group, DISPATCH_TIME_FOREVER);
CFReleaseNull(aliceWorkToDo);
CFReleaseNull(bobWorkToDo);
CFReleaseNull(our_peer_info);
CFReleaseNull(foundNonce);
}
static void tests(bool Alice)
{
dispatch_queue_t work_queue = dispatch_queue_create("NotificationQueue", DISPATCH_QUEUE_SERIAL); dispatch_group_t work_group = dispatch_group_create();
runTests(Alice, work_queue, work_group);
}
static const struct option options[] =
{
{ "verbose", optional_argument, NULL, 'v' },
{ "identity", optional_argument, NULL, 'i' },
{ "clear", optional_argument, NULL, 'C' },
{ }
};
static int kAliceTestCount = 32;
static int kBobTestCount = 30;
int sc_101_accountsync(int argc, char *const *argv)
{
char *identity = NULL;
extern char *optarg;
int arg, argSlot;
bool Alice = false;
while (argSlot = -1, (arg = getopt_long(argc, (char * const *)argv, "i:vC", options, &argSlot)) != -1)
switch (arg)
{
case 'i':
identity = (char *)(optarg);
break;
case 'C': break;
default:
secerror("arg: %s", optarg);
break;
}
if (identity)
{
secerror("We are %s",identity);
if (!strcmp(identity, "alice"))
Alice = true;
}
plan_tests(Alice?kAliceTestCount:kBobTestCount);
tests(Alice);
return 0;
}