X-Git-Url: https://git.saurik.com/apple/security.git/blobdiff_plain/c38e3ce98599a410a47dc10253faa4d5830f13b2..427c49bcad63d042b29ada2ac27e3dfc4845c779:/sec/SOSCircle/SecureObjectSync/SOSAccount.c?ds=sidebyside diff --git a/sec/SOSCircle/SecureObjectSync/SOSAccount.c b/sec/SOSCircle/SecureObjectSync/SOSAccount.c new file mode 100644 index 00000000..8ac8431e --- /dev/null +++ b/sec/SOSCircle/SecureObjectSync/SOSAccount.c @@ -0,0 +1,3193 @@ +/* + * Created by Michael Brouwer on 6/22/12. + * Copyright 2012 Apple Inc. All Rights Reserved. + */ + +/* + * SOSAccount.c - Implementation of the secure object syncing account. + * An account contains a SOSCircle for each protection domain synced. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include + +#include +#include // For SecError + +#include +#include + +#include + +static CFStringRef kicloud_identity_name = CFSTR("Cloud Identity"); + +// +// Forward statics. +// + +static bool SOSAccountHandleUpdateCircle(SOSAccountRef account, SOSCircleRef newCircle, bool writeUpdate, bool initialSync, CFErrorRef *error); + +// +// DER Encoding utilities +// + +// +// Encodes data or a zero length data +// +static size_t der_sizeof_data_or_null(CFDataRef data, CFErrorRef* error) +{ + if (data) { + return der_sizeof_data(data, error); + } else { + return der_sizeof_null(kCFNull, error); + } +} + +static uint8_t* der_encode_data_or_null(CFDataRef data, CFErrorRef* error, const uint8_t* der, uint8_t* der_end) +{ + if (data) { + return der_encode_data(data, error, der, der_end); + } else { + return der_encode_null(kCFNull, error, der, der_end); + } +} + + +static const uint8_t* der_decode_data_or_null(CFAllocatorRef allocator, CFDataRef* data, + CFErrorRef* error, + const uint8_t* der, const uint8_t* der_end) +{ + CFTypeRef value = NULL; + der = der_decode_plist(allocator, 0, &value, error, der, der_end); + if (value && CFGetTypeID(value) != CFDataGetTypeID()) { + CFReleaseNull(value); + } + if (data) { + *data = value; + } + return der; +} + + +// +// Mark: public_bytes encode/decode +// + +static size_t der_sizeof_public_bytes(SecKeyRef publicKey, CFErrorRef* error) +{ + CFDataRef publicData = NULL; + + if (publicKey) + SecKeyCopyPublicBytes(publicKey, &publicData); + + size_t size = der_sizeof_data_or_null(publicData, error); + + CFReleaseNull(publicData); + + return size; +} + +static uint8_t* der_encode_public_bytes(SecKeyRef publicKey, CFErrorRef* error, const uint8_t* der, uint8_t* der_end) +{ + CFDataRef publicData = NULL; + + if (publicKey) + SecKeyCopyPublicBytes(publicKey, &publicData); + + uint8_t *result = der_encode_data_or_null(publicData, error, der, der_end); + + CFReleaseNull(publicData); + + return result; +} + +static const uint8_t* der_decode_public_bytes(CFAllocatorRef allocator, CFIndex algorithmID, SecKeyRef* publicKey, CFErrorRef* error, const uint8_t* der, const uint8_t* der_end) +{ + CFDataRef dataFound = NULL; + der = der_decode_data_or_null(allocator, &dataFound, error, der, der_end); + + if (der && dataFound && publicKey) { + *publicKey = SecKeyCreateFromPublicData(allocator, algorithmID, dataFound); + } + CFReleaseNull(dataFound); + + return der; +} + + +// +// Cloud Paramters encode/decode +// + +static size_t der_sizeof_cloud_parameters(SecKeyRef publicKey, CFDataRef paramters, CFErrorRef* error) +{ + size_t public_key_size = der_sizeof_public_bytes(publicKey, error); + size_t parameters_size = der_sizeof_data_or_null(paramters, error); + + return ccder_sizeof(CCDER_CONSTRUCTED_SEQUENCE, public_key_size + parameters_size); +} + +static uint8_t* der_encode_cloud_parameters(SecKeyRef publicKey, CFDataRef paramters, CFErrorRef* error, + const uint8_t* der, uint8_t* der_end) +{ + uint8_t* original_der_end = der_end; + + return ccder_encode_constructed_tl(CCDER_CONSTRUCTED_SEQUENCE, original_der_end, der, + der_encode_public_bytes(publicKey, error, der, + der_encode_data_or_null(paramters, error, der, der_end))); +} + +static const uint8_t* der_decode_cloud_parameters(CFAllocatorRef allocator, + CFIndex algorithmID, SecKeyRef* publicKey, + CFDataRef *parameters, + CFErrorRef* error, + const uint8_t* der, const uint8_t* der_end) +{ + const uint8_t *sequence_end; + der = ccder_decode_sequence_tl(&sequence_end, der, der_end); + der = der_decode_public_bytes(allocator, algorithmID, publicKey, error, der, sequence_end); + der = der_decode_data_or_null(allocator, parameters, error, der, sequence_end); + + return der; +} + + +// +// bool encoding/decoding +// + + +static const uint8_t* ccder_decode_bool(bool* boolean, const uint8_t* der, const uint8_t *der_end) +{ + if (NULL == der) + return NULL; + + size_t payload_size = 0; + const uint8_t *payload = ccder_decode_tl(CCDER_BOOLEAN, &payload_size, der, der_end); + + if (NULL == payload || (der_end - payload) < 1 || payload_size != 1) { + return NULL; + } + + if (boolean) + *boolean = (*payload != 0); + + return payload + payload_size; +} + + +static size_t ccder_sizeof_bool(bool value __unused, CFErrorRef *error) +{ + return ccder_sizeof(CCDER_BOOLEAN, 1); +} + + +static uint8_t* ccder_encode_bool(bool value, const uint8_t *der, uint8_t *der_end) +{ + uint8_t value_byte = value; + + return ccder_encode_tl(CCDER_BOOLEAN, 1, der, + ccder_encode_body(1, &value_byte, der, der_end)); +} + +struct __OpaqueSOSAccount { + CFRuntimeBase _base; + + dispatch_queue_t queue; + + CFDictionaryRef gestalt; + + CFMutableDictionaryRef circle_identities; + CFMutableDictionaryRef circles; + CFMutableDictionaryRef retired_peers; + + bool user_public_trusted; + CFDataRef user_key_parameters; + SecKeyRef user_public; + SecKeyRef previous_public; + enum DepartureReason departure_code; + + // Non-persistent data + + SOSDataSourceFactoryRef factory; + SecKeyRef _user_private; + dispatch_source_t user_private_timer; + int lock_notification_token; + + // Live Notification + CFMutableArrayRef change_blocks; + + SOSAccountKeyInterestBlock update_interest_block; + SOSAccountDataUpdateBlock update_block; + SOSAccountMessageProcessedBlock processed_message_block; + + CFMutableDictionaryRef deferred_updates; + + CFMutableDictionaryRef pending_changes; +}; + +CFGiblisWithCompareFor(SOSAccount); + +static inline bool SOSAccountHasLeft(SOSAccountRef account) { + switch(account->departure_code) { + case kSOSWithdrewMembership: /* Fallthrough */ + case kSOSMembershipRevoked: /* Fallthrough */ + case kSOSLeftUntrustedCircle: + return true; + case kSOSNeverAppliedToCircle: /* Fallthrough */ + case kSOSNeverLeftCircle: /* Fallthrough */ + default: + return false; + } +} + +// Private static functions. + +static bool SOSUpdateKeyInterest(SOSAccountRef account, bool getNewKeysOnly, CFErrorRef *error); + +static bool SOSAccountEnsureFactoryCircles(SOSAccountRef a) +{ + bool result = false; + if (a) + { + require(a->factory, xit); + CFArrayRef circle_names = a->factory->copy_names(a->factory); + require(circle_names, xit); + CFArrayForEach(circle_names, ^(const void*name) { + if (isString(name)) + SOSAccountEnsureCircle(a, (CFStringRef)name, NULL); + }); + + CFReleaseNull(circle_names); + result = true; + } +xit: + return result; +} + +static SOSAccountRef SOSAccountCreateBasic(CFAllocatorRef allocator, + CFDictionaryRef gestalt, + SOSDataSourceFactoryRef factory, + SOSAccountKeyInterestBlock interest_block, + SOSAccountDataUpdateBlock update_block) { + SOSAccountRef a = CFTypeAllocate(SOSAccount, struct __OpaqueSOSAccount, allocator); + + a->queue = dispatch_queue_create("Account Queue", DISPATCH_QUEUE_SERIAL); + + a->gestalt = gestalt; + CFRetain(a->gestalt); + + a->circles = CFDictionaryCreateMutableForCFTypes(allocator); + a->circle_identities = CFDictionaryCreateMutableForCFTypes(allocator); + a->retired_peers = CFDictionaryCreateMutableForCFTypes(allocator); + + a->factory = factory; // We adopt the factory. kthanksbai. + + a->change_blocks = CFArrayCreateMutableForCFTypes(allocator); + + a->update_interest_block = Block_copy(interest_block); + a->update_block = Block_copy(update_block); + + a->pending_changes = CFDictionaryCreateMutableForCFTypes(allocator); + a->departure_code = kSOSNeverAppliedToCircle; + + return a; +} + + +static SOSFullPeerInfoRef SOSAccountGetMyFullPeerInCircleNamedIfPresent(SOSAccountRef account, CFStringRef name, CFErrorRef *error) { + if (CFDictionaryGetValue(account->circles, name) == NULL) { + SOSCreateErrorWithFormat(kSOSErrorNoCircle, NULL, error, NULL, CFSTR("No circle named '%@'"), name); + return NULL; + } + + return (SOSFullPeerInfoRef) CFDictionaryGetValue(account->circle_identities, name); +} + + +static void SOSAccountForEachKnownCircle(SOSAccountRef account, + void (^handle_incompatible)(CFStringRef name), + void (^handle_no_peer)(SOSCircleRef circle), + void (^handle_peer)(SOSCircleRef circle, SOSFullPeerInfoRef full_peer)) { + CFDictionaryForEach(account->circles, ^(const void *key, const void *value) { + if (isNull(value)) { + if (handle_incompatible) + handle_incompatible((CFStringRef)key); + } else { + SOSCircleRef circle = (SOSCircleRef) value; + CFRetainSafe(circle); + SOSFullPeerInfoRef fpi = SOSAccountGetMyFullPeerInCircleNamedIfPresent(account, SOSCircleGetName(circle), NULL); + if (!fpi) { + if (handle_no_peer) + handle_no_peer(circle); + } else { + CFRetainSafe(fpi); + if (handle_peer) + handle_peer(circle, fpi); + CFReleaseSafe(fpi); + } + CFReleaseSafe(circle); + } + }); +} + + +bool SOSAccountUpdateGestalt(SOSAccountRef account, CFDictionaryRef new_gestalt) +{ + if (CFEqual(new_gestalt, account->gestalt)) + return false; + + SOSAccountForEachKnownCircle(account, NULL, NULL, ^(SOSCircleRef circle, SOSFullPeerInfoRef full_peer) { + if (SOSFullPeerInfoUpdateGestalt(full_peer, new_gestalt, NULL)) { + SOSAccountModifyCircle(account, SOSCircleGetName(circle), + NULL, ^(SOSCircleRef circle_to_change) { + (void) SOSCircleUpdatePeerInfo(circle_to_change, SOSFullPeerInfoGetPeerInfo(full_peer)); + }); + }; + }); + + CFReleaseNull(account->gestalt); + account->gestalt = new_gestalt; + CFRetain(account->gestalt); + + return true; +} + +SOSAccountRef SOSAccountCreate(CFAllocatorRef allocator, + CFDictionaryRef gestalt, + SOSDataSourceFactoryRef factory, + SOSAccountKeyInterestBlock interest_block, + SOSAccountDataUpdateBlock update_block) { + SOSAccountRef a = SOSAccountCreateBasic(allocator, gestalt, factory, interest_block, update_block); + + SOSAccountEnsureFactoryCircles(a); + + return a; +} + +static void SOSAccountDestroy(CFTypeRef aObj) { + SOSAccountRef a = (SOSAccountRef) aObj; + + if (a->factory) + a->factory->release(a->factory); + + CFReleaseNull(a->gestalt); + CFReleaseNull(a->circle_identities); + CFReleaseNull(a->circles); + CFReleaseNull(a->retired_peers); + + a->user_public_trusted = false; + CFReleaseNull(a->user_public); + CFReleaseNull(a->user_key_parameters); + + SOSAccountPurgePrivateCredential(a); + CFReleaseNull(a->previous_public); + + CFReleaseNull(a->change_blocks); + Block_release(a->update_interest_block); + Block_release(a->update_block); + CFReleaseNull(a->processed_message_block); + CFReleaseNull(a->pending_changes); + CFReleaseNull(a->deferred_updates); + a->departure_code = kSOSNeverAppliedToCircle; + + dispatch_release(a->queue); +} + +static void SOSAccountSetToNew(SOSAccountRef a) { + CFAllocatorRef allocator = CFGetAllocator(a); + CFReleaseNull(a->circle_identities); + CFReleaseNull(a->circles); + CFReleaseNull(a->retired_peers); + + CFReleaseNull(a->user_key_parameters); + CFReleaseNull(a->user_public); + CFReleaseNull(a->previous_public); + CFReleaseNull(a->_user_private); + + CFReleaseNull(a->pending_changes); + CFReleaseNull(a->deferred_updates); + + a->user_public_trusted = false; + a->departure_code = kSOSNeverAppliedToCircle; + a->user_private_timer = 0; + a->lock_notification_token = 0; + + // keeping gestalt; + // keeping factory; + // Live Notification + // change_blocks; + // update_interest_block; + // update_block; + + a->circles = CFDictionaryCreateMutableForCFTypes(allocator); + a->circle_identities = CFDictionaryCreateMutableForCFTypes(allocator); + a->retired_peers = CFDictionaryCreateMutableForCFTypes(allocator); + a->pending_changes = CFDictionaryCreateMutableForCFTypes(allocator); + + SOSAccountEnsureFactoryCircles(a); +} + + +static CFStringRef SOSAccountCopyDescription(CFTypeRef aObj) { + SOSAccountRef a = (SOSAccountRef) aObj; + + return CFStringCreateWithFormat(NULL, NULL, CFSTR(""), a, a->gestalt, a->circles, a->circle_identities); +} + +static Boolean SOSAccountCompare(CFTypeRef lhs, CFTypeRef rhs) +{ + SOSAccountRef laccount = (SOSAccountRef) lhs; + SOSAccountRef raccount = (SOSAccountRef) rhs; + + return CFEqual(laccount->gestalt, raccount->gestalt) + && CFEqual(laccount->circles, raccount->circles) + && CFEqual(laccount->circle_identities, raccount->circle_identities); + // ??? retired_peers +} + +#if OLD_CODERS_SUPPORTED + +// +// MARK: Persistent Encode decode +// +SOSAccountRef SOSAccountCreateFromDER_V1(CFAllocatorRef allocator, + SOSDataSourceFactoryRef factory, + SOSAccountKeyInterestBlock interest_block, + SOSAccountDataUpdateBlock update_block, + CFErrorRef* error, + const uint8_t** der_p, const uint8_t *der_end) +{ + SOSAccountRef account = NULL; + + const uint8_t *sequence_end; + *der_p = ccder_decode_constructed_tl(CCDER_CONSTRUCTED_SEQUENCE, &sequence_end, *der_p, der_end); + + { + CFDictionaryRef decoded_gestalt = NULL; + *der_p = der_decode_dictionary(kCFAllocatorDefault, kCFPropertyListImmutable, &decoded_gestalt, error, + *der_p, der_end); + + if (*der_p == 0) + return NULL; + + account = SOSAccountCreateBasic(allocator, decoded_gestalt, factory, interest_block, update_block); + CFReleaseNull(decoded_gestalt); + } + + CFArrayRef array = NULL; + *der_p = der_decode_array(kCFAllocatorDefault, 0, &array, error, *der_p, sequence_end); + + *der_p = ccder_decode_bool(&account->user_public_trusted, *der_p, sequence_end); + *der_p = der_decode_public_bytes(kCFAllocatorDefault, kSecECDSAAlgorithmID, &account->user_public, error, *der_p, sequence_end); + *der_p = der_decode_data_or_null(kCFAllocatorDefault, &account->user_key_parameters, error, *der_p, sequence_end); + *der_p = der_decode_dictionary(kCFAllocatorDefault, kCFPropertyListMutableContainers, (CFDictionaryRef *) &account->retired_peers, error, *der_p, sequence_end); + if (*der_p != sequence_end) + *der_p = NULL; + + __block bool success = true; + + require_quiet(array && *der_p, fail); + + CFArrayForEach(array, ^(const void *value) { + if (success) { + if (isString(value)) { + CFDictionaryAddValue(account->circles, value, kCFNull); + } else { + CFDataRef circleData = NULL; + CFDataRef fullPeerInfoData = NULL; + + if (isData(value)) { + circleData = (CFDataRef) value; + } else if (isArray(value)) { + CFArrayRef pair = (CFArrayRef) value; + + CFTypeRef circleObject = CFArrayGetValueAtIndex(pair, 0); + CFTypeRef fullPeerInfoObject = CFArrayGetValueAtIndex(pair, 1); + + if (CFArrayGetCount(pair) == 2 && isData(circleObject) && isData(fullPeerInfoObject)) { + circleData = (CFDataRef) circleObject; + fullPeerInfoData = (CFDataRef) fullPeerInfoObject; + } + } + + if (circleData) { + SOSCircleRef circle = SOSCircleCreateFromData(kCFAllocatorDefault, circleData, error); + require_action_quiet(circle, fail, success = false); + + CFStringRef circleName = SOSCircleGetName(circle); + CFDictionaryAddValue(account->circles, circleName, circle); + + if (fullPeerInfoData) { + SOSFullPeerInfoRef full_peer = SOSFullPeerInfoCreateFromData(kCFAllocatorDefault, fullPeerInfoData, error); + require_action_quiet(full_peer, fail, success = false); + + CFDictionaryAddValue(account->circle_identities, circleName, full_peer); + CFReleaseNull(full_peer); + } + fail: + CFReleaseNull(circle); + } + } + } + }); + CFReleaseNull(array); + + require_quiet(success, fail); + require_action_quiet(SOSAccountEnsureFactoryCircles(account), fail, + SOSCreateError(kSOSErrorBadFormat, CFSTR("Cannot EnsureFactoryCircles"), (error != NULL) ? *error : NULL, error)); + + return account; + +fail: + // Create a default error if we don't have one: + SOSCreateError(kSOSErrorBadFormat, CFSTR("Bad Account DER"), NULL, error); + CFReleaseNull(account); + return NULL; +} + +SOSAccountRef SOSAccountCreateFromDER_V2(CFAllocatorRef allocator, + SOSDataSourceFactoryRef factory, + SOSAccountKeyInterestBlock interest_block, + SOSAccountDataUpdateBlock update_block, + CFErrorRef* error, + const uint8_t** der_p, const uint8_t *der_end) +{ + SOSAccountRef account = NULL; + const uint8_t *dersave = *der_p; + const uint8_t *derend = der_end; + + const uint8_t *sequence_end; + *der_p = ccder_decode_constructed_tl(CCDER_CONSTRUCTED_SEQUENCE, &sequence_end, *der_p, der_end); + + { + CFDictionaryRef decoded_gestalt = NULL; + *der_p = der_decode_dictionary(kCFAllocatorDefault, kCFPropertyListImmutable, &decoded_gestalt, error, + *der_p, der_end); + + if (*der_p == 0) + return NULL; + + account = SOSAccountCreateBasic(allocator, decoded_gestalt, factory, interest_block, update_block); + CFReleaseNull(decoded_gestalt); + } + + CFArrayRef array = NULL; + *der_p = der_decode_array(kCFAllocatorDefault, 0, &array, error, *der_p, sequence_end); + + uint64_t tmp_departure_code = kSOSNeverAppliedToCircle; + *der_p = ccder_decode_uint64(&tmp_departure_code, *der_p, sequence_end); + *der_p = ccder_decode_bool(&account->user_public_trusted, *der_p, sequence_end); + *der_p = der_decode_public_bytes(kCFAllocatorDefault, kSecECDSAAlgorithmID, &account->user_public, error, *der_p, sequence_end); + *der_p = der_decode_data_or_null(kCFAllocatorDefault, &account->user_key_parameters, error, *der_p, sequence_end); + *der_p = der_decode_dictionary(kCFAllocatorDefault, kCFPropertyListMutableContainers, (CFDictionaryRef *) &account->retired_peers, error, *der_p, sequence_end); + if (*der_p != sequence_end) + *der_p = NULL; + account->departure_code = (enum DepartureReason) tmp_departure_code; + + __block bool success = true; + + require_quiet(array && *der_p, fail); + + CFArrayForEach(array, ^(const void *value) { + if (success) { + if (isString(value)) { + CFDictionaryAddValue(account->circles, value, kCFNull); + } else { + CFDataRef circleData = NULL; + CFDataRef fullPeerInfoData = NULL; + + if (isData(value)) { + circleData = (CFDataRef) value; + } else if (isArray(value)) { + CFArrayRef pair = (CFArrayRef) value; + + CFTypeRef circleObject = CFArrayGetValueAtIndex(pair, 0); + CFTypeRef fullPeerInfoObject = CFArrayGetValueAtIndex(pair, 1); + + if (CFArrayGetCount(pair) == 2 && isData(circleObject) && isData(fullPeerInfoObject)) { + circleData = (CFDataRef) circleObject; + fullPeerInfoData = (CFDataRef) fullPeerInfoObject; + } + } + + if (circleData) { + SOSCircleRef circle = SOSCircleCreateFromData(kCFAllocatorDefault, circleData, error); + require_action_quiet(circle, fail, success = false); + + CFStringRef circleName = SOSCircleGetName(circle); + CFDictionaryAddValue(account->circles, circleName, circle); + + if (fullPeerInfoData) { + SOSFullPeerInfoRef full_peer = SOSFullPeerInfoCreateFromData(kCFAllocatorDefault, fullPeerInfoData, error); + require_action_quiet(full_peer, fail, success = false); + + CFDictionaryAddValue(account->circle_identities, circleName, full_peer); + } + fail: + CFReleaseNull(circle); + } + } + } + }); + CFReleaseNull(array); + + require_quiet(success, fail); + require_action_quiet(SOSAccountEnsureFactoryCircles(account), fail, + SOSCreateError(kSOSErrorBadFormat, CFSTR("Cannot EnsureFactoryCircles"), (error != NULL) ? *error : NULL, error)); + + return account; + +fail: + // Create a default error if we don't have one: + account->factory = NULL; // give the factory back. + CFReleaseNull(account); + // Try the der inflater from the previous release. + account = SOSAccountCreateFromDER_V1(allocator, factory, interest_block, update_block, error, &dersave, derend); + if(account) account->departure_code = kSOSNeverAppliedToCircle; + return account; +} + +#endif /* OLD_CODERS_SUPPORTED */ + +#define CURRENT_ACCOUNT_PERSISTENT_VERSION 6 + +SOSAccountRef SOSAccountCreateFromDER(CFAllocatorRef allocator, + SOSDataSourceFactoryRef factory, + SOSAccountKeyInterestBlock interest_block, + SOSAccountDataUpdateBlock update_block, + CFErrorRef* error, + const uint8_t** der_p, const uint8_t *der_end) +{ + SOSAccountRef account = NULL; +#if UPGRADE_FROM_PREVIOUS_VERSION + const uint8_t *dersave = *der_p; + const uint8_t *derend = der_end; +#endif + uint64_t version = 0; + + const uint8_t *sequence_end; + *der_p = ccder_decode_constructed_tl(CCDER_CONSTRUCTED_SEQUENCE, &sequence_end, *der_p, der_end); + *der_p = ccder_decode_uint64(&version, *der_p, sequence_end); + if(!(*der_p) || version < CURRENT_ACCOUNT_PERSISTENT_VERSION) { +#if UPGRADE_FROM_PREVIOUS_VERSION + return SOSAccountCreateFromDER_V3(allocator, factory, interest_block, update_block, error, &dersave, derend); +#else + return NULL; +#endif + } + + { + CFDictionaryRef decoded_gestalt = NULL; + *der_p = der_decode_dictionary(kCFAllocatorDefault, kCFPropertyListImmutable, &decoded_gestalt, error, + *der_p, der_end); + + if (*der_p == 0) + return NULL; + + account = SOSAccountCreateBasic(allocator, decoded_gestalt, factory, interest_block, update_block); + CFReleaseNull(decoded_gestalt); + } + + CFArrayRef array = NULL; + *der_p = der_decode_array(kCFAllocatorDefault, 0, &array, error, *der_p, sequence_end); + + uint64_t tmp_departure_code = kSOSNeverAppliedToCircle; + *der_p = ccder_decode_uint64(&tmp_departure_code, *der_p, sequence_end); + *der_p = ccder_decode_bool(&account->user_public_trusted, *der_p, sequence_end); + *der_p = der_decode_public_bytes(kCFAllocatorDefault, kSecECDSAAlgorithmID, &account->user_public, error, *der_p, sequence_end); + *der_p = der_decode_public_bytes(kCFAllocatorDefault, kSecECDSAAlgorithmID, &account->previous_public, error, *der_p, sequence_end); + *der_p = der_decode_data_or_null(kCFAllocatorDefault, &account->user_key_parameters, error, *der_p, sequence_end); + *der_p = der_decode_dictionary(kCFAllocatorDefault, kCFPropertyListMutableContainers, (CFDictionaryRef *) &account->retired_peers, error, *der_p, sequence_end); + if (*der_p != sequence_end) + *der_p = NULL; + account->departure_code = (enum DepartureReason) tmp_departure_code; + + __block bool success = true; + + require_quiet(array && *der_p, fail); + + CFArrayForEach(array, ^(const void *value) { + if (success) { + if (isString(value)) { + CFDictionaryAddValue(account->circles, value, kCFNull); + } else { + CFDataRef circleData = NULL; + CFDataRef fullPeerInfoData = NULL; + + if (isData(value)) { + circleData = (CFDataRef) value; + } else if (isArray(value)) { + CFArrayRef pair = (CFArrayRef) value; + + CFTypeRef circleObject = CFArrayGetValueAtIndex(pair, 0); + CFTypeRef fullPeerInfoObject = CFArrayGetValueAtIndex(pair, 1); + + if (CFArrayGetCount(pair) == 2 && isData(circleObject) && isData(fullPeerInfoObject)) { + circleData = (CFDataRef) circleObject; + fullPeerInfoData = (CFDataRef) fullPeerInfoObject; + } + } + + if (circleData) { + SOSCircleRef circle = SOSCircleCreateFromData(kCFAllocatorDefault, circleData, error); + require_action_quiet(circle, fail, success = false); + + CFStringRef circleName = SOSCircleGetName(circle); + CFDictionaryAddValue(account->circles, circleName, circle); + + if (fullPeerInfoData) { + SOSFullPeerInfoRef full_peer = SOSFullPeerInfoCreateFromData(kCFAllocatorDefault, fullPeerInfoData, error); + require_action_quiet(full_peer, fail, success = false); + + CFDictionaryAddValue(account->circle_identities, circleName, full_peer); + } + fail: + CFReleaseNull(circle); + } + } + } + }); + CFReleaseNull(array); + + require_quiet(success, fail); + require_action_quiet(SOSAccountEnsureFactoryCircles(account), fail, + SOSCreateError(kSOSErrorBadFormat, CFSTR("Cannot EnsureFactoryCircles"), (error != NULL) ? *error : NULL, error)); + + return account; + +fail: + account->factory = NULL; // give the factory back. + CFReleaseNull(account); + return NULL; +} + + +SOSAccountRef SOSAccountCreateFromDER_V3(CFAllocatorRef allocator, + SOSDataSourceFactoryRef factory, + SOSAccountKeyInterestBlock interest_block, + SOSAccountDataUpdateBlock update_block, + CFErrorRef* error, + const uint8_t** der_p, const uint8_t *der_end) +{ + SOSAccountRef account = NULL; + uint64_t version = 0; + + const uint8_t *sequence_end; + *der_p = ccder_decode_constructed_tl(CCDER_CONSTRUCTED_SEQUENCE, &sequence_end, *der_p, der_end); + *der_p = ccder_decode_uint64(&version, *der_p, sequence_end); + if(!(*der_p) || version != 3) { + // In this case we want to silently fail so that an account gets newly created. + return NULL; + } + + { + CFDictionaryRef decoded_gestalt = NULL; + *der_p = der_decode_dictionary(kCFAllocatorDefault, kCFPropertyListImmutable, &decoded_gestalt, error, + *der_p, der_end); + + if (*der_p == 0) + return NULL; + + account = SOSAccountCreateBasic(allocator, decoded_gestalt, factory, interest_block, update_block); + CFReleaseNull(decoded_gestalt); + } + + CFArrayRef array = NULL; + *der_p = der_decode_array(kCFAllocatorDefault, 0, &array, error, *der_p, sequence_end); + + uint64_t tmp_departure_code = kSOSNeverAppliedToCircle; + *der_p = ccder_decode_uint64(&tmp_departure_code, *der_p, sequence_end); + *der_p = ccder_decode_bool(&account->user_public_trusted, *der_p, sequence_end); + *der_p = der_decode_public_bytes(kCFAllocatorDefault, kSecECDSAAlgorithmID, &account->user_public, error, *der_p, sequence_end); + *der_p = der_decode_data_or_null(kCFAllocatorDefault, &account->user_key_parameters, error, *der_p, sequence_end); + *der_p = der_decode_dictionary(kCFAllocatorDefault, kCFPropertyListMutableContainers, (CFDictionaryRef *) &account->retired_peers, error, *der_p, sequence_end); + if (*der_p != sequence_end) + *der_p = NULL; + account->departure_code = (enum DepartureReason) tmp_departure_code; + + __block bool success = true; + + require_quiet(array && *der_p, fail); + + CFArrayForEach(array, ^(const void *value) { + if (success) { + if (isString(value)) { + CFDictionaryAddValue(account->circles, value, kCFNull); + } else { + CFDataRef circleData = NULL; + CFDataRef fullPeerInfoData = NULL; + + if (isData(value)) { + circleData = (CFDataRef) value; + } else if (isArray(value)) { + CFArrayRef pair = (CFArrayRef) value; + + CFTypeRef circleObject = CFArrayGetValueAtIndex(pair, 0); + CFTypeRef fullPeerInfoObject = CFArrayGetValueAtIndex(pair, 1); + + if (CFArrayGetCount(pair) == 2 && isData(circleObject) && isData(fullPeerInfoObject)) { + circleData = (CFDataRef) circleObject; + fullPeerInfoData = (CFDataRef) fullPeerInfoObject; + } + } + + if (circleData) { + SOSCircleRef circle = SOSCircleCreateFromData(kCFAllocatorDefault, circleData, error); + require_action_quiet(circle, fail, success = false); + + CFStringRef circleName = SOSCircleGetName(circle); + CFDictionaryAddValue(account->circles, circleName, circle); + + if (fullPeerInfoData) { + SOSFullPeerInfoRef full_peer = SOSFullPeerInfoCreateFromData(kCFAllocatorDefault, fullPeerInfoData, error); + require_action_quiet(full_peer, fail, success = false); + + CFDictionaryAddValue(account->circle_identities, circleName, full_peer); + } + fail: + CFReleaseNull(circle); + } + } + } + }); + CFReleaseNull(array); + + require_quiet(success, fail); + require_action_quiet(SOSAccountEnsureFactoryCircles(account), fail, + SOSCreateError(kSOSErrorBadFormat, CFSTR("Cannot EnsureFactoryCircles"), (error != NULL) ? *error : NULL, error)); + + return account; + +fail: + // Create a default error if we don't have one: + account->factory = NULL; // give the factory back. + CFReleaseNull(account); + // Don't try the der inflater from the previous release. + // account = SOSAccountCreateFromDER_V2(allocator, factory, interest_block, update_block, error, &dersave, derend); + if(account) account->departure_code = kSOSNeverAppliedToCircle; + return account; +} + +SOSAccountRef SOSAccountCreateFromData(CFAllocatorRef allocator, CFDataRef circleData, + SOSDataSourceFactoryRef factory, + SOSAccountKeyInterestBlock interest_block, + SOSAccountDataUpdateBlock update_block, + CFErrorRef* error) +{ + size_t size = CFDataGetLength(circleData); + const uint8_t *der = CFDataGetBytePtr(circleData); + SOSAccountRef account = SOSAccountCreateFromDER(allocator, factory, interest_block, update_block, + error, + &der, der + size); + return account; +} + +static CFMutableArrayRef SOSAccountCopyCircleArrayToEncode(SOSAccountRef account) +{ + CFMutableArrayRef arrayToEncode = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault); + + CFDictionaryForEach(account->circles, ^(const void *key, const void *value) { + if (isNull(value)) { + CFArrayAppendValue(arrayToEncode, key); // Encode the name of the circle that's out of date. + } else { + SOSCircleRef circle = (SOSCircleRef) value; + CFDataRef encodedCircle = SOSCircleCopyEncodedData(circle, kCFAllocatorDefault, NULL); + CFTypeRef arrayEntry = encodedCircle; + CFRetainSafe(arrayEntry); + + SOSFullPeerInfoRef full_peer = (SOSFullPeerInfoRef) CFDictionaryGetValue(account->circle_identities, key); + + if (full_peer) { + CFDataRef encodedPeer = SOSFullPeerInfoCopyEncodedData(full_peer, kCFAllocatorDefault, NULL); + CFTypeRef originalArrayEntry = arrayEntry; + arrayEntry = CFArrayCreateForCFTypes(kCFAllocatorDefault, encodedCircle, encodedPeer, NULL); + + CFReleaseSafe(originalArrayEntry); + CFReleaseNull(encodedPeer); + } + + CFArrayAppendValue(arrayToEncode, arrayEntry); + + CFReleaseSafe(arrayEntry); + CFReleaseNull(encodedCircle); + } + + }); + + return arrayToEncode; +} + +size_t SOSAccountGetDEREncodedSize(SOSAccountRef account, CFErrorRef *error) +{ + size_t sequence_size = 0; + CFMutableArrayRef arrayToEncode = SOSAccountCopyCircleArrayToEncode(account); + uint64_t version = CURRENT_ACCOUNT_PERSISTENT_VERSION; + + require_quiet(accumulate_size(&sequence_size, ccder_sizeof_uint64(version)), fail); + require_quiet(accumulate_size(&sequence_size, der_sizeof_dictionary(account->gestalt, error)), fail); + require_quiet(accumulate_size(&sequence_size, der_sizeof_array(arrayToEncode, error)), fail); + require_quiet(accumulate_size(&sequence_size, ccder_sizeof_uint64(account->departure_code)), fail); + require_quiet(accumulate_size(&sequence_size, ccder_sizeof_bool(account->user_public_trusted, error)), fail); + require_quiet(accumulate_size(&sequence_size, der_sizeof_public_bytes(account->user_public, error)), fail); + require_quiet(accumulate_size(&sequence_size, der_sizeof_public_bytes(account->previous_public, error)), fail); + require_quiet(accumulate_size(&sequence_size, der_sizeof_data_or_null(account->user_key_parameters, error)), fail); + require_quiet(accumulate_size(&sequence_size, der_sizeof_dictionary(account->retired_peers, error)), fail); + + CFReleaseNull(arrayToEncode); + return ccder_sizeof(CCDER_CONSTRUCTED_SEQUENCE, sequence_size); + +fail: + CFReleaseNull(arrayToEncode); + SecCFDERCreateError(kSecDERErrorUnknownEncoding, CFSTR("don't know how to encode"), NULL, error); + return 0; +} + +uint8_t* SOSAccountEncodeToDER(SOSAccountRef account, CFErrorRef* error, const uint8_t* der, uint8_t* der_end) +{ + CFMutableArrayRef arrayToEncode = SOSAccountCopyCircleArrayToEncode(account); + uint64_t version = CURRENT_ACCOUNT_PERSISTENT_VERSION; + der_end = ccder_encode_constructed_tl(CCDER_CONSTRUCTED_SEQUENCE, der_end, der, + ccder_encode_uint64(version, der, + der_encode_dictionary(account->gestalt, error, der, + der_encode_array(arrayToEncode, error, der, + ccder_encode_uint64(account->departure_code, der, + ccder_encode_bool(account->user_public_trusted, der, + der_encode_public_bytes(account->user_public, error, der, + der_encode_public_bytes(account->previous_public, error, der, + der_encode_data_or_null(account->user_key_parameters, error, der, + der_encode_dictionary(account->retired_peers, error, der, der_end)))))))))); + + CFReleaseNull(arrayToEncode); + + return der_end; +} + + + +size_t SOSAccountGetDEREncodedSize_V3(SOSAccountRef account, CFErrorRef *error) +{ + size_t sequence_size = 0; + CFMutableArrayRef arrayToEncode = SOSAccountCopyCircleArrayToEncode(account); + uint64_t version = CURRENT_ACCOUNT_PERSISTENT_VERSION; + + require_quiet(accumulate_size(&sequence_size, ccder_sizeof_uint64(version)), fail); + require_quiet(accumulate_size(&sequence_size, der_sizeof_dictionary(account->gestalt, error)), fail); + require_quiet(accumulate_size(&sequence_size, der_sizeof_array(arrayToEncode, error)), fail); + require_quiet(accumulate_size(&sequence_size, ccder_sizeof_uint64(account->departure_code)), fail); + require_quiet(accumulate_size(&sequence_size, ccder_sizeof_bool(account->user_public_trusted, error)), fail); + require_quiet(accumulate_size(&sequence_size, der_sizeof_public_bytes(account->user_public, error)), fail); + require_quiet(accumulate_size(&sequence_size, der_sizeof_data_or_null(account->user_key_parameters, error)), fail); + require_quiet(accumulate_size(&sequence_size, der_sizeof_dictionary(account->retired_peers, error)), fail); + + CFReleaseNull(arrayToEncode); + return ccder_sizeof(CCDER_CONSTRUCTED_SEQUENCE, sequence_size); + +fail: + CFReleaseNull(arrayToEncode); + SecCFDERCreateError(kSecDERErrorUnknownEncoding, CFSTR("don't know how to encode"), NULL, error); + return 0; +} + +uint8_t* SOSAccountEncodeToDER_V3(SOSAccountRef account, CFErrorRef* error, const uint8_t* der, uint8_t* der_end) +{ + CFMutableArrayRef arrayToEncode = SOSAccountCopyCircleArrayToEncode(account); + uint64_t version = 3; + der_end = ccder_encode_constructed_tl(CCDER_CONSTRUCTED_SEQUENCE, der_end, der, + ccder_encode_uint64(version, der, + der_encode_dictionary(account->gestalt, error, der, + der_encode_array(arrayToEncode, error, der, + ccder_encode_uint64(account->departure_code, der, + ccder_encode_bool(account->user_public_trusted, der, + der_encode_public_bytes(account->user_public, error, der, + der_encode_data_or_null(account->user_key_parameters, error, der, + der_encode_dictionary(account->retired_peers, error, der, der_end))))))))); + + CFReleaseNull(arrayToEncode); + + return der_end; +} + +#if OLD_CODERS_SUPPORTED + +/* Original V2 encoders */ + +size_t SOSAccountGetDEREncodedSize_V2(SOSAccountRef account, CFErrorRef *error) +{ + size_t sequence_size = 0; + CFMutableArrayRef arrayToEncode = SOSAccountCopyCircleArrayToEncode(account); + + require_quiet(accumulate_size(&sequence_size, der_sizeof_dictionary(account->gestalt, error)), fail); + require_quiet(accumulate_size(&sequence_size, der_sizeof_array(arrayToEncode, error)), fail); + require_quiet(accumulate_size(&sequence_size, ccder_sizeof_uint64(account->departure_code)), fail); + require_quiet(accumulate_size(&sequence_size, ccder_sizeof_bool(account->user_public_trusted, error)), fail); + require_quiet(accumulate_size(&sequence_size, der_sizeof_public_bytes(account->user_public, error)), fail); + require_quiet(accumulate_size(&sequence_size, der_sizeof_data_or_null(account->user_key_parameters, error)), fail); + require_quiet(accumulate_size(&sequence_size, der_sizeof_dictionary(account->retired_peers, error)), fail); + + CFReleaseNull(arrayToEncode); + return ccder_sizeof(CCDER_CONSTRUCTED_SEQUENCE, sequence_size); + +fail: + CFReleaseNull(arrayToEncode); + SecCFDERCreateError(kSecDERErrorUnknownEncoding, CFSTR("don't know how to encode"), NULL, error); + return 0; +} + +uint8_t* SOSAccountEncodeToDER_V2(SOSAccountRef account, CFErrorRef* error, const uint8_t* der, uint8_t* der_end) +{ + CFMutableArrayRef arrayToEncode = SOSAccountCopyCircleArrayToEncode(account); + + der_end = ccder_encode_constructed_tl(CCDER_CONSTRUCTED_SEQUENCE, der_end, der, + der_encode_dictionary(account->gestalt, error, der, + der_encode_array(arrayToEncode, error, der, + ccder_encode_uint64(account->departure_code, der, + ccder_encode_bool(account->user_public_trusted, der, + der_encode_public_bytes(account->user_public, error, der, + der_encode_data_or_null(account->user_key_parameters, error, der, + der_encode_dictionary(account->retired_peers, error, der, der_end)))))))); + + CFReleaseNull(arrayToEncode); + + return der_end; +} + + +/* Original V1 encoders */ + + +size_t SOSAccountGetDEREncodedSize_V1(SOSAccountRef account, CFErrorRef *error) +{ + size_t sequence_size = 0; + CFMutableArrayRef arrayToEncode = SOSAccountCopyCircleArrayToEncode(account); + + require_quiet(accumulate_size(&sequence_size, der_sizeof_dictionary(account->gestalt, error)), fail); + require_quiet(accumulate_size(&sequence_size, der_sizeof_array(arrayToEncode, error)), fail); + require_quiet(accumulate_size(&sequence_size, ccder_sizeof_bool(account->user_public_trusted, error)), fail); + require_quiet(accumulate_size(&sequence_size, der_sizeof_public_bytes(account->user_public, error)), fail); + require_quiet(accumulate_size(&sequence_size, der_sizeof_data_or_null(account->user_key_parameters, error)), fail); + require_quiet(accumulate_size(&sequence_size, der_sizeof_dictionary(account->retired_peers, error)), fail); + + CFReleaseNull(arrayToEncode); + return ccder_sizeof(CCDER_CONSTRUCTED_SEQUENCE, sequence_size); + +fail: + CFReleaseNull(arrayToEncode); + SecCFDERCreateError(kSecDERErrorUnknownEncoding, CFSTR("don't know how to encode"), NULL, error); + return 0; +} + +uint8_t* SOSAccountEncodeToDER_V1(SOSAccountRef account, CFErrorRef* error, const uint8_t* der, uint8_t* der_end) +{ + CFMutableArrayRef arrayToEncode = SOSAccountCopyCircleArrayToEncode(account); + + der_end = ccder_encode_constructed_tl(CCDER_CONSTRUCTED_SEQUENCE, der_end, der, + der_encode_dictionary(account->gestalt, error, der, + der_encode_array(arrayToEncode, error, der, + ccder_encode_bool(account->user_public_trusted, der, + der_encode_public_bytes(account->user_public, error, der, + der_encode_data_or_null(account->user_key_parameters, error, der, + der_encode_dictionary(account->retired_peers, error, der, der_end))))))); + + CFReleaseNull(arrayToEncode); + + return der_end; +} +#endif /* OLD_CODERS_SUPPORTED */ + +/************************/ + +CFDataRef SOSAccountCopyEncodedData(SOSAccountRef account, CFAllocatorRef allocator, CFErrorRef *error) +{ + size_t size = SOSAccountGetDEREncodedSize(account, error); + if (size == 0) + return NULL; + uint8_t buffer[size]; + uint8_t* start = SOSAccountEncodeToDER(account, error, buffer, buffer + sizeof(buffer)); + CFDataRef result = CFDataCreate(kCFAllocatorDefault, start, size); + return result; +} + +dispatch_queue_t SOSAccountGetQueue(SOSAccountRef account) { + return account->queue; +} + +// +// MARK: User Credential management +// + +void SOSAccountPurgePrivateCredential(SOSAccountRef account) +{ + CFReleaseNull(account->_user_private); + if (account->user_private_timer) { + dispatch_source_cancel(account->user_private_timer); + dispatch_release(account->user_private_timer); + account->user_private_timer = NULL; + xpc_transaction_end(); + } + if (account->lock_notification_token) { + notify_cancel(account->lock_notification_token); + account->lock_notification_token = 0; + } +} + +static void SOSAccountSetPrivateCredential(SOSAccountRef account, SecKeyRef private) { + if (!private) + return SOSAccountPurgePrivateCredential(account); + + CFRetain(private); + CFReleaseSafe(account->_user_private); + account->_user_private = private; + + bool resume_timer = false; + if (!account->user_private_timer) { + xpc_transaction_begin(); + resume_timer = true; + account->user_private_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, account->queue); + dispatch_source_set_event_handler(account->user_private_timer, ^{ + SOSAccountPurgePrivateCredential(account); + }); + + notify_register_dispatch(kUserKeybagStateChangeNotification, &account->lock_notification_token, account->queue, ^(int token) { + bool locked = false; + CFErrorRef lockCheckError = NULL; + + if (!SecAKSGetIsLocked(&locked, &lockCheckError)) { + secerror("Checking for locked after change failed: %@", lockCheckError); + } + + if (locked) { + SOSAccountPurgePrivateCredential(account); + } + }); + } + + // (Re)set the timer's fire time to now + 120 seconds with a 5 second fuzz factor. + dispatch_time_t purgeTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(10 * 60 * NSEC_PER_SEC)); + dispatch_source_set_timer(account->user_private_timer, purgeTime, DISPATCH_TIME_FOREVER, (int64_t)(5 * NSEC_PER_SEC)); + if (resume_timer) + dispatch_resume(account->user_private_timer); +} + +SecKeyRef SOSAccountGetPrivateCredential(SOSAccountRef account, CFErrorRef* error) +{ + if (account->_user_private == NULL) { + SOSCreateError(kSOSErrorPrivateKeyAbsent, CFSTR("Private Key not available - failed to prompt user recently"), NULL, error); + } + return account->_user_private; +} + +static bool SOSAccountHasPublicKey(SOSAccountRef account, CFErrorRef* error) +{ + if (account->user_public == NULL || account->user_public_trusted == false) { + SOSCreateError(kSOSErrorPublicKeyAbsent, CFSTR("Public Key not available - failed to register before call"), NULL, error); + return false; + } + + return true; +} + +static bool SOSAccountIsMyPeerActiveInCircle(SOSAccountRef account, SOSCircleRef circle, CFErrorRef* error); + +static void SOSAccountGenerationSignatureUpdate(SOSAccountRef account, SecKeyRef privKey) { + SOSAccountForEachCircle(account, ^(SOSCircleRef circle) { + SOSFullPeerInfoRef fpi = SOSAccountGetMyFullPeerInCircle(account, circle, NULL); + if(SOSCircleHasPeer(circle, SOSFullPeerInfoGetPeerInfo(fpi), NULL) && + !SOSCircleVerify(circle, account->user_public, NULL)) { + SOSAccountModifyCircle(account, SOSCircleGetName(circle), NULL, ^(SOSCircleRef circle) { + SOSFullPeerInfoRef cloud_fpi = SOSCircleGetiCloudFullPeerInfoRef(circle); + require_quiet(cloud_fpi != NULL, gen_sign); + require_quiet(SOSFullPeerInfoUpgradeSignatures(cloud_fpi, privKey, NULL), gen_sign); + if(!SOSCircleUpdatePeerInfo(circle, SOSFullPeerInfoGetPeerInfo(cloud_fpi))) { + } + gen_sign: // finally generation sign this. + SOSCircleGenerationSign(circle, privKey, fpi, NULL); + account->departure_code = kSOSNeverLeftCircle; + }); + } + }); +} + +/* this one is meant to be local - not published over KVS. */ +static void SOSAccountPeerSignatureUpdate(SOSAccountRef account, SecKeyRef privKey) { + SOSAccountForEachCircle(account, ^(SOSCircleRef circle) { + SOSFullPeerInfoRef fpi = SOSAccountGetMyFullPeerInCircle(account, circle, NULL); + SOSFullPeerInfoUpgradeSignatures(fpi, privKey, NULL); + }); +} + +static void SOSAccountSetPreviousPublic(SOSAccountRef account) { + CFReleaseNull(account->previous_public); + account->previous_public = account->user_public; + CFRetain(account->previous_public); +} + +static void SOSAccountSetTrustedUserPublicKey(SOSAccountRef account, bool public_was_trusted, SecKeyRef privKey) +{ + if (!privKey) return; + SecKeyRef publicKey = SecKeyCreatePublicFromPrivate(privKey); + + if (account->user_public && account->user_public_trusted && CFEqual(publicKey, account->user_public)) return; + + if(public_was_trusted && account->user_public) { + CFReleaseNull(account->previous_public); + account->previous_public = account->user_public; + CFRetain(account->previous_public); + } + + CFReleaseNull(account->user_public); + account->user_public = publicKey; + account->user_public_trusted = true; + + if(!account->previous_public) { + account->previous_public = account->user_public; + CFRetain(account->previous_public); + } + + secnotice("trust", "trusting new public key: %@", account->user_public); +} + +static void SOSAccountProcessDeferredUpdates(SOSAccountRef account) { + CFErrorRef error = NULL; + if (account->deferred_updates && !SOSAccountHandleUpdates(account, account->deferred_updates, &error)) + secerror("Failed to handle updates when setting public key (%@)", error); + + CFReleaseNull(account->deferred_updates); +} + + +bool SOSAccountTryUserCredentials(SOSAccountRef account, CFStringRef user_account __unused, CFDataRef user_password, CFErrorRef *error) +{ + bool success = false; + + if (!SOSAccountHasPublicKey(account, error)) + return false; + + if (account->user_key_parameters) { + SecKeyRef new_key = SOSUserKeygen(user_password, account->user_key_parameters, error); + if (new_key) { + SecKeyRef new_public_key = SecKeyCreatePublicFromPrivate(new_key); + + if (CFEqualSafe(new_public_key, account->user_public)) { + SOSAccountSetPrivateCredential(account, new_key); + success = true; + } else { + SOSCreateError(kSOSErrorWrongPassword, CFSTR("Password passed in incorrect: ▇█████▇▇██"), NULL, error); + } + CFReleaseSafe(new_public_key); + CFReleaseSafe(new_key); + } + } else { + SOSCreateError(kSOSErrorProcessingFailure, CFSTR("Have public key but no parameters??"), NULL, error); + } + + return success; +} + +static bool SOSAccountPublishCloudParameters(SOSAccountRef account, CFErrorRef* error) +{ + bool success = false; + CFMutableDataRef cloudParameters = CFDataCreateMutableWithScratch(kCFAllocatorDefault, + der_sizeof_cloud_parameters(account->user_public, + account->user_key_parameters, + error)); + if (der_encode_cloud_parameters(account->user_public, account->user_key_parameters, error, + CFDataGetMutableBytePtr(cloudParameters), + CFDataGetMutablePastEndPtr(cloudParameters)) != NULL) { + + CFDictionaryRef changes = CFDictionaryCreateForCFTypes(kCFAllocatorDefault, + kSOSKVSKeyParametersKey, cloudParameters, + NULL); + + CFErrorRef changeError = NULL; + if (account->update_block(changes, &changeError)) { + success = true; + } else { + SOSCreateErrorWithFormat(kSOSErrorSendFailure, changeError, error, NULL, + CFSTR("update parameters key failed [%@]"), changes); + } + CFReleaseSafe(changes); + CFReleaseSafe(changeError); + } else { + SOSCreateError(kSOSErrorEncodeFailure, CFSTR("Encoding parameters failed"), NULL, error); + } + + CFReleaseNull(cloudParameters); + + return success; +} + +bool SOSAccountAssertUserCredentials(SOSAccountRef account, CFStringRef user_account __unused, CFDataRef user_password, CFErrorRef *error) +{ + bool public_was_trusted = account->user_public_trusted; + account->user_public_trusted = false; + SecKeyRef user_private = NULL; + + if (account->user_public && account->user_key_parameters) { + // We have an untrusted public key – see if our generation makes the same key: + // if so we trust it and we have the private key. + // if not we still don't trust it. + require_quiet(user_private = SOSUserKeygen(user_password, account->user_key_parameters, error), exit); + SecKeyRef public_candidate = SecKeyCreatePublicFromPrivate(user_private); + if (!CFEqualSafe(account->user_public, public_candidate)) { + secnotice("trust", "Public keys don't match: calculated: %@, expected: %@", + account->user_public, public_candidate); + debugDumpUserParameters(CFSTR("params"), account->user_key_parameters); + CFReleaseNull(user_private); + } else { + SOSAccountPeerSignatureUpdate(account, user_private); + SOSAccountSetTrustedUserPublicKey(account, public_was_trusted, user_private); + } + CFReleaseSafe(public_candidate); + } + + if (!account->user_public_trusted) { + // We may or may not have parameters here. + // In any case we tried using them and they didn't match + // So forget all that and start again, assume we're the first to push anything useful. + + CFReleaseNull(account->user_key_parameters); + account->user_key_parameters = SOSUserKeyCreateGenerateParameters(error); + require_quiet(user_private = SOSUserKeygen(user_password, account->user_key_parameters, error), exit); + + SOSAccountPeerSignatureUpdate(account, user_private); + SOSAccountSetTrustedUserPublicKey(account, public_was_trusted, user_private); + + CFErrorRef publishError = NULL; + if (!SOSAccountPublishCloudParameters(account, &publishError)) + secerror("Failed to publish new cloud parameters: %@", publishError); + CFReleaseSafe(publishError); + } + + SOSAccountProcessDeferredUpdates(account); + SOSAccountGenerationSignatureUpdate(account, user_private); + SOSAccountSetPrivateCredential(account, user_private); +exit: + CFReleaseSafe(user_private); + + return account->user_public_trusted; +} + +// +// MARK: Circle management +// + +int SOSAccountCountCircles(SOSAccountRef a) { + assert(a); + assert(a->circle_identities); + assert(a->circles); + return (int)CFDictionaryGetCount(a->circles); +} + +static SecKeyRef GeneratePermanentFullECKey_internal(int keySize, CFStringRef name, CFTypeRef accessibility, CFBooleanRef sync, CFErrorRef* error) +{ + SecKeyRef full_key = NULL; + + CFNumberRef key_size_num = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &keySize); + + CFDictionaryRef priv_key_attrs = CFDictionaryCreateForCFTypes(kCFAllocatorDefault, + kSecAttrIsPermanent, kCFBooleanTrue, + NULL); + + CFDictionaryRef keygen_parameters = CFDictionaryCreateForCFTypes(kCFAllocatorDefault, + kSecAttrKeyType, kSecAttrKeyTypeEC, + kSecAttrKeySizeInBits, key_size_num, + kSecPrivateKeyAttrs, priv_key_attrs, + kSecAttrAccessible, accessibility, + kSecAttrAccessGroup, kSOSInternalAccessGroup, + kSecAttrLabel, name, + kSecAttrSynchronizable, sync, + kSecUseTombstones, kCFBooleanTrue, + NULL); + + CFReleaseNull(priv_key_attrs); + + CFReleaseNull(key_size_num); + OSStatus status = SecKeyGeneratePair(keygen_parameters, NULL, &full_key); + CFReleaseNull(keygen_parameters); + + if (status) + secerror("status: %ld", (long)status); + if (status != errSecSuccess && error != NULL && *error == NULL) { + *error = CFErrorCreate(kCFAllocatorDefault, kCFErrorDomainOSStatus, status, NULL); + } + + return full_key; +} + +static SecKeyRef GeneratePermanentFullECKey(int keySize, CFStringRef name, CFErrorRef* error) { + return GeneratePermanentFullECKey_internal(keySize, name, kSecAttrAccessibleWhenUnlockedThisDeviceOnly, kCFBooleanFalse, error); +} + +static SecKeyRef GeneratePermanentFullECKeyForCloudIdentity(int keySize, CFStringRef name, CFErrorRef* error) { + return GeneratePermanentFullECKey_internal(keySize, name, kSecAttrAccessibleWhenUnlocked, kCFBooleanTrue, error); +} + + +SOSFullPeerInfoRef SOSAccountGetMyFullPeerInCircleNamed(SOSAccountRef account, CFStringRef name, CFErrorRef *error) { + if (CFDictionaryGetValue(account->circles, name) == NULL) { + SOSCreateErrorWithFormat(kSOSErrorNoCircle, NULL, error, NULL, CFSTR("No circle named '%@'"), name); + return NULL; + } + SOSFullPeerInfoRef circle_full_peer_info = (SOSFullPeerInfoRef) CFDictionaryGetValue(account->circle_identities, name); + + + if (circle_full_peer_info == NULL) { + CFStringRef keyName = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("ID for %@-%@"), SOSPeerGestaltGetName(account->gestalt), name); + SecKeyRef full_key = GeneratePermanentFullECKey(256, keyName, error); + CFReleaseNull(keyName); + + if (full_key) { + circle_full_peer_info = SOSFullPeerInfoCreate(kCFAllocatorDefault, account->gestalt, full_key, error); + + CFReleaseNull(full_key); + + if (!circle_full_peer_info) { + secerror("Can't make FullPeerInfo for %@-%@ (%@) - is AKS ok?", SOSPeerGestaltGetName(account->gestalt), name, error ? (void*)*error : (void*)CFSTR("-")); + return circle_full_peer_info; + } + + CFDictionarySetValue(account->circle_identities, name, circle_full_peer_info); + CFReleaseNull(circle_full_peer_info); + circle_full_peer_info = (SOSFullPeerInfoRef) CFDictionaryGetValue(account->circle_identities, name); + } + else + secerror("No full_key: %@:", error ? *error : NULL); + } + + return circle_full_peer_info; +} + +static bool SOSAccountDestroyCirclePeerInfoNamed(SOSAccountRef account, CFStringRef name, CFErrorRef* error) { + if (CFDictionaryGetValue(account->circles, name) == NULL) { + SOSCreateErrorWithFormat(kSOSErrorNoCircle, NULL, error, NULL, CFSTR("No circle named '%@'"), name); + return false; + } + + SOSFullPeerInfoRef circle_full_peer_info = (SOSFullPeerInfoRef) CFDictionaryGetValue(account->circle_identities, name); + + if (circle_full_peer_info) { + SOSPeerPurgeAllFor(SOSPeerInfoGetPeerID(SOSFullPeerInfoGetPeerInfo(circle_full_peer_info))); + + SOSFullPeerInfoPurgePersistentKey(circle_full_peer_info, NULL); + } + + CFDictionaryRemoveValue(account->circle_identities, name); + + return true; +} + +static bool SOSAccountDestroyCirclePeerInfo(SOSAccountRef account, SOSCircleRef circle, CFErrorRef* error) { + return SOSAccountDestroyCirclePeerInfoNamed(account, SOSCircleGetName(circle), error); +} + +SOSFullPeerInfoRef SOSAccountGetMyFullPeerInCircle(SOSAccountRef account, SOSCircleRef circle, CFErrorRef* error) { + return SOSAccountGetMyFullPeerInCircleNamed(account, SOSCircleGetName(circle), error); +} + +SOSPeerInfoRef SOSAccountGetMyPeerInCircle(SOSAccountRef account, SOSCircleRef circle, CFErrorRef* error) { + SOSFullPeerInfoRef fpi = SOSAccountGetMyFullPeerInCircleNamed(account, SOSCircleGetName(circle), error); + + return fpi ? SOSFullPeerInfoGetPeerInfo(fpi) : NULL; +} + +SOSPeerInfoRef SOSAccountGetMyPeerInCircleNamed(SOSAccountRef account, CFStringRef name, CFErrorRef *error) +{ + SOSFullPeerInfoRef fpi = SOSAccountGetMyFullPeerInCircleNamed(account, name, error); + + return fpi ? SOSFullPeerInfoGetPeerInfo(fpi) : NULL; +} + +CFArrayRef SOSAccountCopyAccountIdentityPeerInfos(SOSAccountRef account, CFAllocatorRef allocator, CFErrorRef* error) +{ + CFMutableArrayRef result = CFArrayCreateMutableForCFTypes(allocator); + + CFDictionaryForEach(account->circle_identities, ^(const void *key, const void *value) { + SOSFullPeerInfoRef fpi = (SOSFullPeerInfoRef) value; + + CFArrayAppendValue(result, SOSFullPeerInfoGetPeerInfo(fpi)); + }); + + return result; +} + +bool SOSAccountIsAccountIdentity(SOSAccountRef account, SOSPeerInfoRef peer_info, CFErrorRef *error) +{ + __block bool matches = false; + CFDictionaryForEach(account->circle_identities, ^(const void *key, const void *value) { + if (!matches) { + matches = CFEqual(peer_info, SOSFullPeerInfoGetPeerInfo((SOSFullPeerInfoRef) value)); + } + }); + + return matches; +} + +bool SOSAccountSyncWithAllPeers(SOSAccountRef account, CFErrorRef *error) +{ + __block bool result = true; + SOSAccountForEachCircle(account, ^(SOSCircleRef circle) { + if (!SOSAccountSyncWithAllPeersInCircle(account, circle, error)) + result = false; + }); + + return result; +} + +bool SOSAccountSyncWithAllPeersInCircle(SOSAccountRef account, SOSCircleRef circle, + CFErrorRef *error) +{ + SOSPeerInfoRef my_peer = SOSAccountGetMyPeerInCircle(account, circle, error); + if (!my_peer) + return false; + + __block bool didSync = false; + __block bool result = true; + + if (SOSCircleHasPeer(circle, my_peer, NULL)) { + SOSCircleForEachPeer(circle, ^(SOSPeerInfoRef peer) { + if (!CFEqual(SOSPeerInfoGetPeerID(my_peer), SOSPeerInfoGetPeerID(peer))) + { + bool local_didSync = false; + if (!SOSAccountSyncWithPeer(account, circle, peer, &local_didSync, error)) + result = false; + if (!didSync && local_didSync) + { + didSync = true; + } + } + }); + + if (didSync) + { + SetCloudKeychainTraceValueForKey(kCloudKeychainNumberOfTimesSyncedWithPeers, 1); + } + } + + return result; +} + +bool SOSAccountSyncWithPeer(SOSAccountRef account, SOSCircleRef circle, + SOSPeerInfoRef thisPeer, bool* didSendData, CFErrorRef* error) +{ + CFStringRef peer_id = SOSPeerInfoGetPeerID(thisPeer); + CFStringRef peer_write_key = SOSMessageKeyCreateWithAccountAndPeer(account, circle, peer_id); + SOSFullPeerInfoRef myRef = SOSAccountGetMyFullPeerInCircle(account, circle, error); + + __block bool sentData = false; + + + SOSPeerSendBlock writeToKVSKey = ^bool (CFDataRef data, CFErrorRef* error) { + secnotice("account", "writing data of size %ld:", data?CFDataGetLength(data):0); + sentData = (NULL != data); + CFDictionaryRef writeToDo = CFDictionaryCreateForCFTypes(kCFAllocatorDefault, peer_write_key, data, NULL); + bool written = account->update_block(writeToDo, error); + if (account->processed_message_block) + account->processed_message_block(circle, NULL, data); + CFRelease(writeToDo); + return written; + }; + + if (NULL != didSendData) + { + *didSendData = sentData; + } + + bool result = SOSCircleSyncWithPeer(myRef, circle, account->factory, writeToKVSKey, peer_id, error); + CFReleaseNull(peer_write_key); + return result; +} + +static bool SOSAccountIsActivePeerInCircleNamed(SOSAccountRef account, CFStringRef circle_name, CFStringRef peerid, CFErrorRef* error) { + SOSCircleRef circle = SOSAccountFindCircle(account, circle_name, error); + if(!circle) return false; + return SOSCircleHasActivePeerWithID(circle, peerid, error); +} + +static bool SOSAccountIsMyPeerActiveInCircle(SOSAccountRef account, SOSCircleRef circle, CFErrorRef* error) { + SOSFullPeerInfoRef fpi = SOSAccountGetMyFullPeerInCircleNamedIfPresent(account, SOSCircleGetName(circle), NULL); + if(!fpi) return false; + return SOSCircleHasActivePeer(circle, SOSFullPeerInfoGetPeerInfo(fpi), error); +} + +bool SOSAccountCleanupAfterPeer(SOSAccountRef account, size_t seconds, SOSCircleRef circle, + SOSPeerInfoRef cleanupPeer, CFErrorRef* error) +{ + if(!SOSAccountIsMyPeerActiveInCircle(account, circle, NULL)) return true; + + CFMutableDictionaryRef keysToWrite = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault); + + SOSCircleForEachPeer(circle, ^(SOSPeerInfoRef circlePeer) { + CFStringRef from_key = SOSMessageKeyCreateWithCircleAndPeerInfos(circle, cleanupPeer, circlePeer); + CFStringRef to_key = SOSMessageKeyCreateWithCircleAndPeerInfos(circle, circlePeer, cleanupPeer); + + CFDictionaryAddValue(keysToWrite, from_key, kCFNull); + CFDictionaryAddValue(keysToWrite, to_key, kCFNull); + + CFReleaseNull(from_key); + CFReleaseNull(to_key); + }); + + if(SOSPeerInfoRetireRetirementTicket(seconds, cleanupPeer)) { + CFStringRef resignationKey = SOSRetirementKeyCreateWithCircleAndPeer(circle, SOSPeerInfoGetPeerID(cleanupPeer)); + CFDictionarySetValue(keysToWrite, resignationKey, kCFNull); + CFDictionaryRemoveValue(account->retired_peers, resignationKey); + CFReleaseNull(resignationKey); + } + + bool success = account->update_block(keysToWrite, error); + + CFReleaseNull(keysToWrite); + + return success; +} + +bool SOSAccountCleanupRetirementTickets(SOSAccountRef account, size_t seconds, CFErrorRef* error) { + CFMutableDictionaryRef keysToWrite = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault); + + CFDictionaryRef copyToIterate = CFDictionaryCreateCopy(kCFAllocatorDefault, account->retired_peers); + + CFDictionaryForEach(copyToIterate, ^(const void* resignationKey, const void* value) { + CFStringRef circle_name = NULL; + CFStringRef retiree_peerid = NULL; + SOSPeerInfoRef pi = NULL; + SOSKVSKeyType keytype = SOSKVSKeyGetKeyTypeAndParse(resignationKey, &circle_name, &retiree_peerid, NULL); + require_quiet(keytype == kRetirementKey && circle_name && retiree_peerid && isData(value), forget); + pi = SOSPeerInfoCreateFromData(NULL, error, (CFDataRef) value); + require_quiet(pi && CFEqualSafe(retiree_peerid, SOSPeerInfoGetPeerID(pi)), forget); + + require_quiet(!SOSAccountIsActivePeerInCircleNamed(account, circle_name, retiree_peerid, NULL), keep); + require_quiet(SOSPeerInfoRetireRetirementTicket(seconds, pi), keep); + + // Happy day, it's time and it's a ticket we should eradicate from KVS. + CFDictionarySetValue(keysToWrite, resignationKey, kCFNull); + + forget: + CFDictionaryRemoveValue(account->retired_peers, resignationKey); + keep: + CFReleaseSafe(pi); + CFReleaseSafe(circle_name); + CFReleaseSafe(retiree_peerid); + }); + CFReleaseNull(copyToIterate); + + bool success = true; + if(CFDictionaryGetCount(keysToWrite)) { + success = account->update_block(keysToWrite, error); + } + CFReleaseNull(keysToWrite); + + return success; +} + +bool SOSAccountScanForRetired(SOSAccountRef account, SOSCircleRef circle, CFErrorRef *error) { + SOSCircleForEachRetiredPeer(circle, ^(SOSPeerInfoRef peer) { + CFStringRef key = SOSRetirementKeyCreateWithCircleAndPeer(circle, SOSPeerInfoGetPeerID(peer)); + if(key && !CFDictionaryGetValueIfPresent(account->retired_peers, key, NULL)) { + CFDataRef value = SOSPeerInfoCopyEncodedData(peer, NULL, NULL); + if(value) { + CFDictionarySetValue(account->retired_peers, key, value); + SOSAccountCleanupAfterPeer(account, RETIREMENT_FINALIZATION_SECONDS, circle, peer, error); + } + CFReleaseSafe(value); + } + CFReleaseSafe(key); + }); + return true; +} + +SOSCircleRef SOSAccountCloneCircleWithRetirement(SOSAccountRef account, SOSCircleRef starting_circle, CFErrorRef *error) { + CFStringRef circle_to_mod = SOSCircleGetName(starting_circle); + SOSCircleRef new_circle = SOSCircleCopyCircle(NULL, starting_circle, error); + if(!new_circle) return NULL; + + CFDictionaryForEach(account->retired_peers, ^(const void* resignationKey, const void* value) { + CFStringRef circle_name = NULL; + CFStringRef retiree_peerid = NULL; + + SOSKVSKeyType keytype = SOSKVSKeyGetKeyTypeAndParse(resignationKey, &circle_name, &retiree_peerid, NULL); + if(keytype == kRetirementKey && CFEqualSafe(circle_name, circle_to_mod) && SOSCircleHasPeerWithID(new_circle, retiree_peerid, NULL)) { + if(isData(value)) { + SOSPeerInfoRef pi = SOSPeerInfoCreateFromData(NULL, error, (CFDataRef) value); + SOSCircleUpdatePeerInfo(new_circle, pi); + CFReleaseSafe(pi); + } + } + CFReleaseSafe(circle_name); + CFReleaseSafe(retiree_peerid); + }); + + if(SOSCircleCountPeers(new_circle) == 0) { + SOSCircleResetToEmpty(new_circle, NULL); + } + + return new_circle; +} + + +// +// Circle Finding +// +SOSCircleRef SOSAccountFindCompatibleCircle(SOSAccountRef a, CFStringRef name) +{ + CFTypeRef entry = CFDictionaryGetValue(a->circles, name); + + if (CFGetTypeID(entry) == SOSCircleGetTypeID()) + return (SOSCircleRef) entry; + + return NULL; +} + +SOSCircleRef SOSAccountFindCircle(SOSAccountRef a, CFStringRef name, CFErrorRef *error) +{ + CFTypeRef entry = CFDictionaryGetValue(a->circles, name); + + require_action_quiet(!isNull(entry), fail, + SOSCreateError(kSOSErrorIncompatibleCircle, CFSTR("Incompatible circle in KVS"), NULL, error)); + + require_action_quiet(entry, fail, + SOSCreateError(kSOSErrorNoCircle, CFSTR("No circle found"), NULL, error)); + + + return (SOSCircleRef) entry; + +fail: + return NULL; +} + +SOSCircleRef SOSAccountEnsureCircle(SOSAccountRef a, CFStringRef name, CFErrorRef *error) +{ + CFErrorRef localError = NULL; + + SOSCircleRef circle = SOSAccountFindCircle(a, name, &localError); + + require_action_quiet(circle || !isSOSErrorCoded(localError, kSOSErrorIncompatibleCircle), fail, + if (error) { *error = localError; localError = NULL; }); + + + if (NULL == circle) { + circle = SOSCircleCreate(NULL, name, NULL); + if (circle){ + CFDictionaryAddValue(a->circles, name, circle); + CFRelease(circle); + circle = SOSAccountFindCircle(a, name, &localError); + } + SOSUpdateKeyInterest(a, false, NULL); + } + +fail: + CFReleaseNull(localError); + return circle; +} + + +void SOSAccountAddChangeBlock(SOSAccountRef a, SOSAccountCircleMembershipChangeBlock changeBlock) +{ + CFArrayAppendValue(a->change_blocks, changeBlock); +} + +void SOSAccountRemoveChangeBlock(SOSAccountRef a, SOSAccountCircleMembershipChangeBlock changeBlock) +{ + CFArrayRemoveAllValue(a->change_blocks, changeBlock); +} + +static void DifferenceAndCall(CFArrayRef old_members, CFArrayRef new_members, void (^updatedCircle)(CFArrayRef additions, CFArrayRef removals)) +{ + CFMutableArrayRef additions = CFArrayCreateMutableCopy(kCFAllocatorDefault, 0, new_members); + CFMutableArrayRef removals = CFArrayCreateMutableCopy(kCFAllocatorDefault, 0, old_members); + + + CFArrayForEach(old_members, ^(const void * value) { + CFArrayRemoveAllValue(additions, value); + }); + + CFArrayForEach(new_members, ^(const void * value) { + CFArrayRemoveAllValue(removals, value); + }); + + updatedCircle(additions, removals); + + CFReleaseSafe(additions); + CFReleaseSafe(removals); +} + +static void SOSAccountNotifyOfChange(SOSAccountRef account, SOSCircleRef oldCircle, SOSCircleRef newCircle) +{ + CFMutableArrayRef old_members = SOSCircleCopyPeers(oldCircle, kCFAllocatorDefault); + CFMutableArrayRef new_members = SOSCircleCopyPeers(newCircle, kCFAllocatorDefault); + + CFMutableArrayRef old_applicants = SOSCircleCopyApplicants(oldCircle, kCFAllocatorDefault); + CFMutableArrayRef new_applicants = SOSCircleCopyApplicants(newCircle, kCFAllocatorDefault); + + DifferenceAndCall(old_members, new_members, ^(CFArrayRef added_members, CFArrayRef removed_members) { + DifferenceAndCall(old_applicants, new_applicants, ^(CFArrayRef added_applicants, CFArrayRef removed_applicants) { + CFArrayForEach(account->change_blocks, ^(const void * notificationBlock) { + ((SOSAccountCircleMembershipChangeBlock) notificationBlock)(newCircle, added_members, removed_members, added_applicants, removed_applicants); + }); + }); + }); + + CFReleaseNull(old_applicants); + CFReleaseNull(new_applicants); + + CFReleaseNull(old_members); + CFReleaseNull(new_members); +} + +void SOSAccountForEachCircle(SOSAccountRef account, void (^process)(SOSCircleRef circle)) +{ + CFDictionaryForEach(account->circles, ^(const void* key, const void* value) { + assert(value); + process((SOSCircleRef)value); + }); +} + +static void AppendCircleKeyName(CFMutableArrayRef array, CFStringRef name) { + CFStringRef circle_key = SOSCircleKeyCreateWithName(name, NULL); + CFArrayAppendValue(array, circle_key); + CFReleaseNull(circle_key); +} + +static inline void AppendCircleInterests(CFMutableArrayRef circle_keys, CFMutableArrayRef retiree_keys, CFMutableArrayRef message_keys, SOSCircleRef circle, SOSFullPeerInfoRef me) { + CFStringRef my_peer_id = NULL; + + if (me) { + SOSPeerInfoRef my_peer = me ? SOSFullPeerInfoGetPeerInfo(me) : NULL; + my_peer_id = SOSPeerInfoGetPeerID(my_peer); + } + + if (circle_keys) { + CFStringRef circleName = SOSCircleGetName(circle); + AppendCircleKeyName(circle_keys, circleName); + } + + SOSCircleForEachPeer(circle, ^(SOSPeerInfoRef peer) { + if (!CFEqualSafe(my_peer_id, SOSPeerInfoGetPeerID(peer))) { + CFStringRef peer_name = SOSPeerInfoGetPeerID(peer); + if (retiree_keys) { + CFStringRef retirementKey = SOSRetirementKeyCreateWithCircleAndPeer(circle, peer_name); + CFArrayAppendValue(retiree_keys, retirementKey); + CFReleaseNull(retirementKey); + } + + if (my_peer_id && message_keys) { + CFStringRef messageKey = SOSMessageKeyCreateWithCircleAndPeerNames(circle, peer_name, my_peer_id); + CFArrayAppendValue(message_keys, messageKey); + CFRelease(messageKey); + } + } + }); +} + +static void SOSAccountCopyKeyInterests(SOSAccountRef account, + CFMutableArrayRef alwaysKeys, + CFMutableArrayRef afterFirstUnlockKeys, + CFMutableArrayRef whenUnlockedKeys) +{ + CFArrayAppendValue(afterFirstUnlockKeys, kSOSKVSKeyParametersKey); + + SOSAccountForEachKnownCircle(account, ^(CFStringRef name) { + AppendCircleKeyName(afterFirstUnlockKeys, name); + }, ^(SOSCircleRef circle) { + AppendCircleInterests(afterFirstUnlockKeys, afterFirstUnlockKeys, whenUnlockedKeys, circle, NULL); + }, ^(SOSCircleRef circle, SOSFullPeerInfoRef full_peer) { + bool inCircle = SOSCircleHasPeer(circle, SOSFullPeerInfoGetPeerInfo(full_peer), NULL); + + AppendCircleInterests(afterFirstUnlockKeys, afterFirstUnlockKeys, inCircle ? whenUnlockedKeys : NULL, circle, full_peer); + }); +} + +static bool SOSUpdateKeyInterest(SOSAccountRef account, bool getNewKeysOnly, CFErrorRef *error) +{ + if (account->update_interest_block) { + + CFMutableArrayRef alwaysKeys = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault); + CFMutableArrayRef afterFirstUnlockKeys = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault); + CFMutableArrayRef whenUnlockedKeys = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault); + + SOSAccountCopyKeyInterests(account, alwaysKeys, afterFirstUnlockKeys, whenUnlockedKeys); + + account->update_interest_block(getNewKeysOnly, alwaysKeys, afterFirstUnlockKeys, whenUnlockedKeys); + + CFReleaseNull(alwaysKeys); + CFReleaseNull(afterFirstUnlockKeys); + CFReleaseNull(whenUnlockedKeys); + } + + return true; +} + +static bool SOSAccountSendPendingChanges(SOSAccountRef account, CFErrorRef *error) { + CFErrorRef changeError = NULL; + + if (CFDictionaryGetCount(account->pending_changes) == 0) + return true; + + bool success = account->update_block(account->pending_changes, &changeError); + if (success) { + CFDictionaryRemoveAllValues(account->pending_changes); + } else { + SOSCreateErrorWithFormat(kSOSErrorSendFailure, changeError, error, NULL, + CFSTR("Send changes block failed [%@]"), account->pending_changes); + } + + return success; +} + +static bool SOSAccountAddCircleToPending(SOSAccountRef account, SOSCircleRef circle, CFErrorRef *error) +{ + bool success = false; + CFDataRef circle_data = SOSCircleCopyEncodedData(circle, kCFAllocatorDefault, error); + + if (circle_data) { + CFStringRef circle_key = SOSCircleKeyCreateWithCircle(circle, NULL); + + CFDictionarySetValue(account->pending_changes, circle_key, circle_data); + success = true; + + CFReleaseNull(circle_data); + CFReleaseNull(circle_key); + } + + return success; +} + + +static void SOSAccountRecordRetiredPeerInCircleNamed(SOSAccountRef account, CFStringRef circleName, SOSPeerInfoRef retiree) +{ + // Replace Peer with RetiredPeer, if were a peer. + SOSAccountModifyCircle(account, circleName, NULL, ^(SOSCircleRef circle) { + if (SOSCircleUpdatePeerInfo(circle, retiree)) { + CFErrorRef cleanupError = NULL; + SOSAccountCleanupAfterPeer(account, RETIREMENT_FINALIZATION_SECONDS, circle, retiree, &cleanupError); + secerror("Error cleanup up after peer (%@): %@", retiree, cleanupError); + CFReleaseSafe(cleanupError); + } + }); +} + +static bool sosAccountLeaveCircle(SOSAccountRef account, SOSCircleRef circle, CFErrorRef* error) { + SOSFullPeerInfoRef fpi = SOSAccountGetMyFullPeerInCircle(account, circle, NULL); + if(!fpi) return false; + CFErrorRef localError = NULL; + SOSPeerInfoRef pi = SOSFullPeerInfoGetPeerInfo(fpi); + CFStringRef retire_key = SOSRetirementKeyCreateWithCircleAndPeer(circle, SOSPeerInfoGetPeerID(pi)); + SOSPeerInfoRef retire_peer = NULL; + CFDataRef retire_value = NULL; + bool retval = false; + bool writeCircle = false; + + // Create a Retirement Ticket and store it in the retired_peers of the account. + retire_peer = SOSFullPeerInfoPromoteToRetiredAndCopy(fpi, &localError); + require_action_quiet(retire_peer, errout, secerror("Create ticket failed for peer %@: %@", fpi, localError)); + retire_value = SOSPeerInfoCopyEncodedData(retire_peer, NULL, &localError); + require_action_quiet(retire_value, errout, secerror("Failed to encode retirement peer %@: %@", retire_peer, localError)); + + // See if we need to repost the circle we could either be an applicant or a peer already in the circle + if(SOSCircleHasApplicant(circle, pi, NULL)) { + // Remove our application if we have one. + SOSCircleWithdrawRequest(circle, pi, NULL); + writeCircle = true; + } else if (SOSCircleHasPeer(circle, pi, NULL)) { + if (SOSCircleUpdatePeerInfo(circle, retire_peer)) { + CFErrorRef cleanupError = NULL; + SOSAccountCleanupAfterPeer(account, RETIREMENT_FINALIZATION_SECONDS, circle, retire_peer, &cleanupError); + secerror("Error cleanup up after peer (%@): %@", retire_peer, cleanupError); + CFReleaseSafe(cleanupError); + } + writeCircle = true; + } + + // Store the retirement record locally. + CFDictionarySetValue(account->retired_peers, retire_key, retire_value); + + // Write pending change to KVS + CFDictionarySetValue(account->pending_changes, retire_key, retire_value); + + // Kill peer key but don't return error if we can't. + if(!SOSAccountDestroyCirclePeerInfo(account, circle, &localError)) + secerror("Couldn't purge key for peer %@ on retirement: %@", fpi, localError); + + if (writeCircle) { + SOSAccountAddCircleToPending(account, circle, NULL); + } + retval = true; + +errout: + CFReleaseNull(localError); + CFReleaseNull(retire_peer); + CFReleaseNull(retire_key); + CFReleaseNull(retire_value); + return retval; +} + +/* + NSUbiquitousKeyValueStoreInitialSyncChange is only posted if there is any + local value that has been overwritten by a distant value. If there is no + conflict between the local and the distant values when doing the initial + sync (e.g. if the cloud has no data stored or the client has not stored + any data yet), you'll never see that notification. + + NSUbiquitousKeyValueStoreInitialSyncChange implies an initial round trip + with server but initial round trip with server does not imply + NSUbiquitousKeyValueStoreInitialSyncChange. + */ + +// +// MARK: Handle Circle Updates +// + + +static bool SOSAccountHandleUpdateCircle(SOSAccountRef account, SOSCircleRef prospective_circle, bool writeUpdate, bool initialSync, CFErrorRef *error) +{ + bool success = true; + + secnotice("signing", "start: %@", prospective_circle); + if (!account->user_public || !account->user_public_trusted) { + SOSCreateError(kSOSErrorPublicKeyAbsent, CFSTR("Can't handle updates with no trusted public key here"), NULL, error); + return false; + } + + if (!prospective_circle) { + secerror("##### Can't update to a NULL circle ######"); + return false; // Can't update one we don't have. + } + + CFStringRef newCircleName = SOSCircleGetName(prospective_circle); + SOSCircleRef oldCircle = SOSAccountFindCompatibleCircle(account, newCircleName); + SOSFullPeerInfoRef me_full = SOSAccountGetMyFullPeerInCircle(account, oldCircle, NULL); + SOSPeerInfoRef me = SOSFullPeerInfoGetPeerInfo(me_full); + + if (initialSync) + secerror("##### Processing initial sync. Old (local) circle: %@, New (cloud) circle: %@", oldCircle, prospective_circle); + + if (!oldCircle) + return false; // Can't update one we don't have. + + SOSAccountScanForRetired(account, prospective_circle, error); + SOSCircleRef newCircle = SOSAccountCloneCircleWithRetirement(account, prospective_circle, error); + if(!newCircle) return false; + + SOSCircleUpdatePeerInfo(newCircle, me); + + typedef enum { + accept, + countersign, + leave, + revert, + ignore + } circle_action_t; + + circle_action_t circle_action = ignore; + enum DepartureReason leave_reason = kSOSNeverLeftCircle; + + SecKeyRef old_circle_key = NULL; + if(SOSCircleVerify(oldCircle, account->user_public, NULL)) old_circle_key = account->user_public; + else if(account->previous_public && SOSCircleVerify(oldCircle, account->previous_public, NULL)) old_circle_key = account->previous_public; + bool userTrustedOldCircle = (old_circle_key != NULL); + + SOSConcordanceStatus concstat = + SOSCircleConcordanceTrust(oldCircle, newCircle, + old_circle_key, account->user_public, + me, error); + + CFStringRef concStr = NULL; + switch(concstat) { + case kSOSConcordanceTrusted: + circle_action = countersign; + concStr = CFSTR("Trusted"); + break; + case kSOSConcordanceGenOld: + circle_action = userTrustedOldCircle ? revert : ignore; + concStr = CFSTR("Generation Old"); + break; + case kSOSConcordanceBadUserSig: + case kSOSConcordanceBadPeerSig: + circle_action = userTrustedOldCircle ? revert : accept; + concStr = CFSTR("Bad Signature"); + break; + case kSOSConcordanceNoUserSig: + circle_action = userTrustedOldCircle ? revert : accept; + concStr = CFSTR("No User Signature"); + break; + case kSOSConcordanceNoPeerSig: + circle_action = accept; // We might like this one eventually but don't countersign. + concStr = CFSTR("No trusted peer signature"); + secerror("##### No trusted peer signature found, accepting hoping for concordance later %@", newCircle); + break; + case kSOSConcordanceNoPeer: + circle_action = leave; + leave_reason = kSOSLeftUntrustedCircle; + concStr = CFSTR("No trusted peer left"); + break; + case kSOSConcordanceNoUserKey: + secerror("##### No User Public Key Available, this shouldn't ever happen!!!"); + abort(); + break; + default: + secerror("##### Bad Error Return from ConcordanceTrust"); + abort(); + break; + } + + secnotice("signing", "Decided on action %d based on concordance state %d and %s circle.", circle_action, concstat, userTrustedOldCircle ? "trusted" : "untrusted"); + + SOSCircleRef circleToPush = NULL; + + if (circle_action == leave) { + circle_action = ignore; + + if (me && SOSCircleHasPeer(oldCircle, me, NULL)) { + if (sosAccountLeaveCircle(account, newCircle, error)) { + account->departure_code = leave_reason; + circleToPush = newCircle; + circle_action = accept; + me = NULL; + me_full = NULL; + } + } + else { + // We are not in this circle, but we need to update account with it, since we got it from cloud + secnotice("updatecircle", "We are not in this circle, but we need to update account with it"); + circle_action = accept; + } + } + + if (circle_action == countersign) { + if (me && SOSCircleHasPeer(newCircle, me, NULL) && !SOSCircleVerifyPeerSigned(newCircle, me, NULL)) { + CFErrorRef signing_error = NULL; + + if (me_full && SOSCircleConcordanceSign(newCircle, me_full, &signing_error)) { + circleToPush = newCircle; + secnotice("signing", "Concurred with: %@", newCircle); + } else { + secerror("Failed to concurrence sign, error: %@ Old: %@ New: %@", signing_error, oldCircle, newCircle); + } + CFReleaseSafe(signing_error); + } + circle_action = accept; + } + + if (circle_action == accept) { + if (me && SOSCircleHasActivePeer(oldCircle, me, NULL) && !SOSCircleHasPeer(newCircle, me, NULL)) { + // Don't destroy evidence of other code determining reason for leaving. + if(!SOSAccountHasLeft(account)) account->departure_code = kSOSMembershipRevoked; + } + + if (me + && SOSCircleHasActivePeer(oldCircle, me, NULL) + && !(SOSCircleCountPeers(oldCircle) == 1 && SOSCircleHasPeer(oldCircle, me, NULL)) // If it was our offering, don't change ID to avoid ghosts + && !SOSCircleHasPeer(newCircle, me, NULL) && !SOSCircleHasApplicant(newCircle, me, NULL)) { + secnotice("circle", "Purging my peer (ID: %@) for circle '%@'!!!", SOSPeerInfoGetPeerID(me), SOSCircleGetName(oldCircle)); + SOSAccountDestroyCirclePeerInfo(account, oldCircle, NULL); + me = NULL; + me_full = NULL; + } + + if (me && SOSCircleHasRejectedApplicant(newCircle, me, NULL)) { + SOSPeerInfoRef reject = SOSCircleCopyRejectedApplicant(newCircle, me, NULL); + if(CFEqualSafe(reject, me) && SOSPeerInfoApplicationVerify(me, account->user_public, NULL)) { + secnotice("circle", "Rejected, Purging my applicant peer (ID: %@) for circle '%@'", SOSPeerInfoGetPeerID(me), SOSCircleGetName(oldCircle)); + SOSAccountDestroyCirclePeerInfo(account, oldCircle, NULL); + me = NULL; + me_full = NULL; + } else { + SOSCircleRequestReadmission(newCircle, account->user_public, me_full, NULL); + writeUpdate = true; + } + } + + CFRetain(oldCircle); // About to replace the oldCircle + CFDictionarySetValue(account->circles, newCircleName, newCircle); + SOSAccountSetPreviousPublic(account); + + secnotice("signing", "%@, Accepting circle: %@", concStr, newCircle); + + if (me_full && account->user_public_trusted + && SOSCircleHasApplicant(oldCircle, me, NULL) + && SOSCircleCountPeers(newCircle) > 0 + && !SOSCircleHasPeer(newCircle, me, NULL) && !SOSCircleHasApplicant(newCircle, me, NULL)) { + // We weren't rejected (above would have set me to NULL. + // We were applying and we weren't accepted. + // Our application is declared lost, let us reapply. + + if (SOSCircleRequestReadmission(newCircle, account->user_public, me_full, NULL)) + writeUpdate = true; + } + + if (me && SOSCircleHasActivePeer(oldCircle, me, NULL)) { + SOSAccountCleanupRetirementTickets(account, RETIREMENT_FINALIZATION_SECONDS, NULL); + } + + SOSAccountNotifyOfChange(account, oldCircle, newCircle); + + CFReleaseNull(oldCircle); + + if (writeUpdate) + circleToPush = newCircle; + + success = SOSUpdateKeyInterest(account, true, error); + } + + if (circle_action == revert) { + secnotice("signing", "%@, Rejecting: %@ re-publishing %@", concStr, newCircle, oldCircle); + + circleToPush = oldCircle; + } + + + if (circleToPush != NULL) { + success = (success + && SOSAccountAddCircleToPending(account, circleToPush, error) + && SOSAccountSendPendingChanges(account, error)); + } + + CFReleaseSafe(newCircle); + + return success; +} + +static bool SOSAccountUpdateCircleFromRemote(SOSAccountRef account, SOSCircleRef newCircle, bool initialSync, CFErrorRef *error) +{ + return SOSAccountHandleUpdateCircle(account, newCircle, false, initialSync, error); +} + +bool SOSAccountUpdateCircle(SOSAccountRef account, SOSCircleRef newCircle, CFErrorRef *error) +{ + return SOSAccountHandleUpdateCircle(account, newCircle, true, false, error); +} + +bool SOSAccountModifyCircle(SOSAccountRef account, + CFStringRef circleName, + CFErrorRef* error, + void (^action)(SOSCircleRef circle)) +{ + bool success = false; + + SOSCircleRef circle = NULL; + SOSCircleRef accountCircle = SOSAccountFindCircle(account, circleName, error); + require_quiet(accountCircle, fail); + + circle = SOSCircleCopyCircle(kCFAllocatorDefault, accountCircle, error); + require_quiet(circle, fail); + + action(circle); + success = SOSAccountUpdateCircle(account, circle, error); + +fail: + CFReleaseSafe(circle); + return success; +} + +static SOSCircleRef SOSAccountCreateCircleFrom(CFStringRef circleName, CFTypeRef value, CFErrorRef *error) { + if (value && !isData(value) && !isNull(value)) { + CFStringRef description = CFCopyTypeIDDescription(CFGetTypeID(value)); + SOSCreateErrorWithFormat(kSOSErrorUnexpectedType, NULL, error, NULL, + CFSTR("Expected data or NULL got %@"), description); + CFReleaseSafe(description); + return NULL; + } + + SOSCircleRef circle = NULL; + if (!value || isNull(value)) { + circle = SOSCircleCreate(kCFAllocatorDefault, circleName, error); + } else { + circle = SOSCircleCreateFromData(NULL, (CFDataRef) value, error); + if (circle) { + CFStringRef name = SOSCircleGetName(circle); + if (!CFEqualSafe(name, circleName)) { + SOSCreateErrorWithFormat(kSOSErrorNameMismatch, NULL, error, NULL, + CFSTR("Expected circle named %@, got %@"), circleName, name); + CFReleaseNull(circle); + } + } + } + return circle; +} + +static SOSCCStatus SOSCCCircleStatus(SOSCircleRef circle) +{ + if (SOSCircleCountPeers(circle) == 0) + return kSOSCCCircleAbsent; + + return kSOSCCNotInCircle; +} + +static SOSCCStatus SOSCCThisDeviceStatusInCircle(SOSCircleRef circle, SOSPeerInfoRef this_peer) +{ + if (SOSCircleCountPeers(circle) == 0) + return kSOSCCCircleAbsent; + + if (SOSCircleHasPeer(circle, this_peer, NULL)) + return kSOSCCInCircle; + + if (SOSCircleHasApplicant(circle, this_peer, NULL)) + return kSOSCCRequestPending; + + return kSOSCCNotInCircle; +} + +static SOSCCStatus UnionStatus(SOSCCStatus accumulated_status, SOSCCStatus additional_circle_status) +{ + switch (additional_circle_status) { + case kSOSCCInCircle: + return accumulated_status; + case kSOSCCRequestPending: + return (accumulated_status == kSOSCCInCircle) ? + kSOSCCRequestPending : + accumulated_status; + case kSOSCCNotInCircle: + return (accumulated_status == kSOSCCInCircle || + accumulated_status == kSOSCCRequestPending) ? + kSOSCCNotInCircle : + accumulated_status; + case kSOSCCCircleAbsent: + return (accumulated_status == kSOSCCInCircle || + accumulated_status == kSOSCCRequestPending || + accumulated_status == kSOSCCNotInCircle) ? + kSOSCCCircleAbsent : + accumulated_status; + default: + return additional_circle_status; + }; + +} + +SOSCCStatus SOSAccountIsInCircles(SOSAccountRef account, CFErrorRef* error) +{ + if (!SOSAccountHasPublicKey(account, error)) { + return kSOSCCError; + } + + __block bool set_once = false; + __block SOSCCStatus status = kSOSCCInCircle; + + SOSAccountForEachKnownCircle(account, ^(CFStringRef name) { + set_once = true; + status = kSOSCCError; + SOSCreateError(kSOSErrorIncompatibleCircle, CFSTR("Incompatible circle"), NULL, error); + }, ^(SOSCircleRef circle) { + set_once = true; + status = UnionStatus(status, SOSCCCircleStatus(circle)); + }, ^(SOSCircleRef circle, SOSFullPeerInfoRef full_peer) { + set_once = true; + SOSCCStatus circle_status = SOSCCThisDeviceStatusInCircle(circle, SOSFullPeerInfoGetPeerInfo(full_peer)); + status = UnionStatus(status, circle_status); + }); + + if (!set_once) + status = kSOSCCCircleAbsent; + + return status; +} + +static SOSPeerInfoRef GenerateNewCloudIdentityPeerInfo(CFErrorRef *error) { + SecKeyRef cloud_key = GeneratePermanentFullECKeyForCloudIdentity(256, kicloud_identity_name, error); + SOSPeerInfoRef cloud_peer = NULL; + CFDictionaryRef query = NULL; + CFDictionaryRef change = NULL; + CFStringRef new_name = NULL; + + CFDictionaryRef gestalt = CFDictionaryCreateForCFTypes(kCFAllocatorDefault, + kPIUserDefinedDeviceName, CFSTR("iCloud"), + NULL); + require_action_quiet(gestalt, fail, SecError(errSecAllocate, error, CFSTR("Can't allocate gestalt"))); + + cloud_peer = SOSPeerInfoCreateCloudIdentity(kCFAllocatorDefault, gestalt, cloud_key, error); + + require(cloud_peer, fail); + + query = CFDictionaryCreateForCFTypes(kCFAllocatorDefault, + kSecClass, kSecClassKey, + kSecAttrSynchronizable,kCFBooleanTrue, + kSecUseTombstones, kCFBooleanTrue, + kSecValueRef, cloud_key, + NULL); + + new_name = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, + CFSTR("Cloud Identity - '%@'"), SOSPeerInfoGetPeerID(cloud_peer)); + + change = CFDictionaryCreateForCFTypes(kCFAllocatorDefault, + kSecAttrLabel, new_name, + NULL); + + SecError(SecItemUpdate(query, change), error, CFSTR("Couldn't update name")); + +fail: + CFReleaseNull(new_name); + CFReleaseNull(query); + CFReleaseNull(change); + CFReleaseNull(gestalt); + CFReleaseNull(cloud_key); + + return cloud_peer; +} + +static SOSFullPeerInfoRef CopyCloudKeychainIdentity(SOSPeerInfoRef cloudPeer, CFErrorRef *error) { + return SOSFullPeerInfoCreateCloudIdentity(NULL, cloudPeer, error); +} + +static bool SOSAccountResetThisCircleToOffering(SOSAccountRef account, SOSCircleRef circle, SecKeyRef user_key, CFErrorRef *error) { + SOSFullPeerInfoRef myCirclePeer = SOSAccountGetMyFullPeerInCircle(account, circle, error); + if (!myCirclePeer) + return false; + + SOSAccountModifyCircle(account, SOSCircleGetName(circle), error, ^(SOSCircleRef circle) { + bool result = false; + SOSFullPeerInfoRef cloud_identity = NULL; + CFErrorRef localError = NULL; + + require_quiet(SOSCircleResetToOffering(circle, user_key, myCirclePeer, &localError), err_out); + + { + SOSPeerInfoRef cloud_peer = GenerateNewCloudIdentityPeerInfo(error); + require_quiet(cloud_peer, err_out); + cloud_identity = CopyCloudKeychainIdentity(cloud_peer, error); + require_quiet(cloud_identity, err_out); + } + + account->departure_code = kSOSNeverLeftCircle; + require_quiet(SOSCircleRequestAdmission(circle, user_key, cloud_identity, &localError), err_out); + require_quiet(SOSCircleAcceptRequest(circle, user_key, myCirclePeer, SOSFullPeerInfoGetPeerInfo(cloud_identity), &localError), err_out); + result = true; + SOSAccountPublishCloudParameters(account, NULL); + + err_out: + if (result == false) + secerror("error resetting circle (%@) to offering: %@", circle, localError); + if (localError && error && *error == NULL) { + *error = localError; + localError = NULL; + } + CFReleaseNull(localError); + CFReleaseNull(cloud_identity); + }); + + return true; +} + +static bool SOSAccountJoinThisCircle(SOSAccountRef account, SecKeyRef user_key, + SOSCircleRef circle, bool use_cloud_peer, CFErrorRef* error) { + __block bool result = false; + __block SOSFullPeerInfoRef cloud_full_peer = NULL; + + SOSFullPeerInfoRef myCirclePeer = SOSAccountGetMyFullPeerInCircle(account, circle, error); + require_action_quiet(myCirclePeer, fail, + SOSCreateErrorWithFormat(kSOSErrorPeerNotFound, NULL, error, NULL, CFSTR("Can't find/create peer for circle: %@"), circle)); + if (use_cloud_peer) { + cloud_full_peer = SOSCircleGetiCloudFullPeerInfoRef(circle); + } + + if (SOSCircleCountPeers(circle) == 0) { + result = SOSAccountResetThisCircleToOffering(account, circle, user_key, error); + } else { + SOSAccountModifyCircle(account, SOSCircleGetName(circle), error, ^(SOSCircleRef circle) { + result = SOSCircleRequestAdmission(circle, user_key, myCirclePeer, error); + account->departure_code = kSOSNeverLeftCircle; + if(result && cloud_full_peer) { + CFErrorRef localError = NULL; + CFStringRef cloudid = SOSPeerInfoGetPeerID(SOSFullPeerInfoGetPeerInfo(cloud_full_peer)); + require_quiet(cloudid, finish); + require_quiet(SOSCircleHasActivePeerWithID(circle, cloudid, &localError), finish); + require_quiet(SOSCircleAcceptRequest(circle, user_key, cloud_full_peer, SOSFullPeerInfoGetPeerInfo(myCirclePeer), &localError), finish); + finish: + if (localError){ + secerror("Failed to join with cloud identity: %@", localError); + CFReleaseNull(localError); + } + } + }); + } + +fail: + CFReleaseNull(cloud_full_peer); + return result; +} + +static bool SOSAccountJoinCircles_internal(SOSAccountRef account, bool use_cloud_identity, CFErrorRef* error) { + SecKeyRef user_key = SOSAccountGetPrivateCredential(account, error); + if (!user_key) + return false; + + __block bool success = true; + + SOSAccountForEachKnownCircle(account, ^(CFStringRef name) { // Incompatible + success = false; + SOSCreateError(kSOSErrorIncompatibleCircle, CFSTR("Incompatible circle"), NULL, error); + }, ^(SOSCircleRef circle) { // No Peer + success = SOSAccountJoinThisCircle(account, user_key, circle, use_cloud_identity, error) && success; + }, ^(SOSCircleRef circle, SOSFullPeerInfoRef full_peer) { // Have Peer + SOSPeerInfoRef myPeer = SOSFullPeerInfoGetPeerInfo(full_peer); + if(SOSCircleHasPeer(circle, myPeer, NULL)) goto already_present; + if(SOSCircleHasApplicant(circle, myPeer, NULL)) goto already_applied; + if(SOSCircleHasRejectedApplicant(circle, myPeer, NULL)) { + SOSCircleRemoveRejectedPeer(circle, myPeer, NULL); + } + + secerror("Resetting my peer (ID: %@) for circle '%@' during application", SOSPeerInfoGetPeerID(myPeer), SOSCircleGetName(circle)); + CFErrorRef localError = NULL; + if (!SOSAccountDestroyCirclePeerInfo(account, circle, &localError)) { + secerror("Failed to destroy peer (%@) during application, error=%@", myPeer, localError); + CFReleaseNull(localError); + } + already_applied: + success = SOSAccountJoinThisCircle(account, user_key, circle, use_cloud_identity, error) && success; + return; + already_present: + success = true; + return; + }); + + if(success) account->departure_code = kSOSNeverLeftCircle; + + return success; +} + +bool SOSAccountJoinCircles(SOSAccountRef account, CFErrorRef* error) { + return SOSAccountJoinCircles_internal(account, false, error); +} + + +bool SOSAccountJoinCirclesAfterRestore(SOSAccountRef account, CFErrorRef* error) { + return SOSAccountJoinCircles_internal(account, true, error); +} + + +bool SOSAccountLeaveCircles(SOSAccountRef account, CFErrorRef* error) +{ + __block bool result = true; + + SOSAccountForEachKnownCircle(account, NULL, NULL, ^(SOSCircleRef circle, SOSFullPeerInfoRef myCirclePeer) { + SOSAccountModifyCircle(account, SOSCircleGetName(circle), error, ^(SOSCircleRef circle) { + result = sosAccountLeaveCircle(account, circle, error); // TODO: What about multiple errors! + }); + }); + + account->departure_code = kSOSWithdrewMembership; + + return SOSAccountSendPendingChanges(account, error) && result; +} + +bool SOSAccountBail(SOSAccountRef account, uint64_t limit_in_seconds, CFErrorRef* error) +{ + dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); + dispatch_group_t group = dispatch_group_create(); + __block bool result = false; + + secnotice("circle", "Attempting to leave circle - best effort - in %llu seconds\n", limit_in_seconds); + // Add a task to the group + dispatch_group_async(group, queue, ^{ + SOSAccountForEachKnownCircle(account, NULL, NULL, ^(SOSCircleRef circle, SOSFullPeerInfoRef myCirclePeer) { + SOSAccountModifyCircle(account, SOSCircleGetName(circle), error, ^(SOSCircleRef circle) { + result = sosAccountLeaveCircle(account, circle, error); // TODO: What about multiple errors! + }); + }); + + account->departure_code = kSOSWithdrewMembership; + if(result) result = SOSAccountSendPendingChanges(account, error); + }); + dispatch_time_t milestone = dispatch_time(DISPATCH_TIME_NOW, limit_in_seconds * NSEC_PER_SEC); + + dispatch_group_wait(group, milestone); + dispatch_release(group); + return result; +} + + +static void for_each_applicant_in_each_circle(SOSAccountRef account, CFArrayRef peer_infos, + void (^action)(SOSCircleRef circle, SOSFullPeerInfoRef myCirclePeer, SOSPeerInfoRef peer)) { + SOSAccountForEachKnownCircle(account, NULL, NULL, ^(SOSCircleRef circle, SOSFullPeerInfoRef full_peer) { + SOSPeerInfoRef me = SOSFullPeerInfoGetPeerInfo(full_peer); + CFErrorRef peer_error = NULL; + if (SOSCircleHasPeer(circle, me, &peer_error)) { + CFArrayForEach(peer_infos, ^(const void *value) { + SOSPeerInfoRef peer = (SOSPeerInfoRef) value; + if (SOSCircleHasApplicant(circle, peer, NULL)) { + SOSAccountModifyCircle(account, SOSCircleGetName(circle), NULL, ^(SOSCircleRef circle) { + action(circle, full_peer, peer); + }); + } + }); + } + if (peer_error) + secerror("Got error in SOSCircleHasPeer: %@", peer_error); + CFReleaseSafe(peer_error); // TODO: We should be accumulating errors here. + }); +} + +bool SOSAccountAcceptApplicants(SOSAccountRef account, CFArrayRef applicants, CFErrorRef* error) { + SecKeyRef user_key = SOSAccountGetPrivateCredential(account, error); + if (!user_key) + return false; + + __block bool success = true; + __block int64_t num_peers = 0; + + for_each_applicant_in_each_circle(account, applicants, ^(SOSCircleRef circle, SOSFullPeerInfoRef myCirclePeer, SOSPeerInfoRef peer) { + if (!SOSCircleAcceptRequest(circle, user_key, myCirclePeer, peer, error)) + success = false; + else + num_peers = MAX(num_peers, SOSCircleCountPeers(circle)); + }); + + return success; +} + +bool SOSAccountRejectApplicants(SOSAccountRef account, CFArrayRef applicants, CFErrorRef* error) { + __block bool success = true; + __block int64_t num_peers = 0; + + for_each_applicant_in_each_circle(account, applicants, ^(SOSCircleRef circle, SOSFullPeerInfoRef myCirclePeer, SOSPeerInfoRef peer) { + if (!SOSCircleRejectRequest(circle, myCirclePeer, peer, error)) + success = false; + else + num_peers = MAX(num_peers, SOSCircleCountPeers(circle)); + }); + + return success; +} + +bool SOSAccountResetToOffering(SOSAccountRef account, CFErrorRef* error) { + SecKeyRef user_key = SOSAccountGetPrivateCredential(account, error); + if (!user_key) + return false; + + __block bool result = true; + + SOSAccountForEachKnownCircle(account, ^(CFStringRef name) { + SOSCircleRef circle = SOSCircleCreate(NULL, name, NULL); + if (circle) + CFDictionaryAddValue(account->circles, name, circle); + + SOSAccountResetThisCircleToOffering(account, circle, user_key, error); + }, ^(SOSCircleRef circle) { + SOSAccountResetThisCircleToOffering(account, circle, user_key, error); + }, ^(SOSCircleRef circle, SOSFullPeerInfoRef full_peer) { + SOSAccountResetThisCircleToOffering(account, circle, user_key, error); + }); + + return result; +} + +bool SOSAccountResetToEmpty(SOSAccountRef account, CFErrorRef* error) { + if (!SOSAccountHasPublicKey(account, error)) + return false; + + __block bool result = true; + SOSAccountForEachCircle(account, ^(SOSCircleRef circle) { + SOSAccountModifyCircle(account, SOSCircleGetName(circle), error, ^(SOSCircleRef circle) { + if (!SOSCircleResetToEmpty(circle, error)) + { + secerror("error: %@", *error); + result = false; + } + account->departure_code = kSOSWithdrewMembership; + }); + }); + + return result; +} + +CFArrayRef SOSAccountCopyApplicants(SOSAccountRef account, CFErrorRef *error) { + if (!SOSAccountHasPublicKey(account, error)) + return NULL; + CFMutableArrayRef applicants = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault); + + SOSAccountForEachCircle(account, ^(SOSCircleRef circle) { + SOSCircleForEachApplicant(circle, ^(SOSPeerInfoRef peer) { + CFArrayAppendValue(applicants, peer); + }); + }); + + return applicants; +} + +CFArrayRef SOSAccountCopyPeers(SOSAccountRef account, CFErrorRef *error) { + if (!SOSAccountHasPublicKey(account, error)) + return NULL; + + CFMutableArrayRef peers = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault); + + SOSAccountForEachCircle(account, ^(SOSCircleRef circle) { + SOSCircleForEachPeer(circle, ^(SOSPeerInfoRef peer) { + CFArrayAppendValue(peers, peer); + }); + }); + + return peers; +} + +CFArrayRef SOSAccountCopyActivePeers(SOSAccountRef account, CFErrorRef *error) { + if (!SOSAccountHasPublicKey(account, error)) + return NULL; + + CFMutableArrayRef peers = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault); + + SOSAccountForEachCircle(account, ^(SOSCircleRef circle) { + SOSCircleForEachActivePeer(circle, ^(SOSPeerInfoRef peer) { + CFArrayAppendValue(peers, peer); + }); + }); + + return peers; +} + +CFArrayRef SOSAccountCopyActiveValidPeers(SOSAccountRef account, CFErrorRef *error) { + if (!SOSAccountHasPublicKey(account, error)) + return NULL; + + CFMutableArrayRef peers = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault); + + SOSAccountForEachCircle(account, ^(SOSCircleRef circle) { + SOSCircleForEachActiveValidPeer(circle, account->user_public, ^(SOSPeerInfoRef peer) { + CFArrayAppendValue(peers, peer); + }); + }); + + return peers; +} + + +CFArrayRef SOSAccountCopyConcurringPeers(SOSAccountRef account, CFErrorRef *error) +{ + if (!SOSAccountHasPublicKey(account, error)) + return NULL; + + CFMutableArrayRef concurringPeers = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault); + + SOSAccountForEachCircle(account, ^(SOSCircleRef circle) { + CFMutableArrayRef circleConcurring = SOSCircleCopyConcurringPeers(circle, NULL); + CFArrayAppendArray(concurringPeers, circleConcurring, CFRangeMake(0, CFArrayGetCount(circleConcurring))); + CFReleaseSafe(circleConcurring); + }); + + return concurringPeers; +} + +CFStringRef SOSAccountCopyIncompatibilityInfo(SOSAccountRef account, CFErrorRef* error) +{ + return CFSTR("We're compatible, go away"); +} + +enum DepartureReason SOSAccountGetLastDepartureReason(SOSAccountRef account, CFErrorRef* error) +{ + return account->departure_code; +} + +// +// TODO: Handle '|' and "¬" in other strings. +// +const CFStringRef kSOSKVSKeyParametersKey = CFSTR(">KeyParameters"); +const CFStringRef kSOSKVSInitialSyncKey = CFSTR("^InitialSync"); +const CFStringRef kSOSKVSAccountChangedKey = CFSTR("^AccountChanged"); + +const CFStringRef sWarningPrefix = CFSTR("!"); +const CFStringRef sAncientCirclePrefix = CFSTR("@"); +const CFStringRef sCirclePrefix = CFSTR("o"); +const CFStringRef sRetirementPrefix = CFSTR("-"); +const CFStringRef sCircleSeparator = CFSTR("|"); +const CFStringRef sFromToSeparator = CFSTR(":"); + +static CFStringRef stringEndingIn(CFMutableStringRef in, CFStringRef token) { + if(token == NULL) return CFStringCreateCopy(NULL, in); + CFRange tokenAt = CFStringFind(in, token, 0); + if(tokenAt.location == kCFNotFound) return NULL; + CFStringRef retval = CFStringCreateWithSubstring(NULL, in, CFRangeMake(0, tokenAt.location)); + CFStringDelete(in, CFRangeMake(0, tokenAt.location+1)); + return retval; +} + +SOSKVSKeyType SOSKVSKeyGetKeyTypeAndParse(CFStringRef key, CFStringRef *circle, CFStringRef *from, CFStringRef *to) +{ + SOSKVSKeyType retval = kUnknownKey; + + if(CFStringHasPrefix(key, sCirclePrefix)) retval = kCircleKey; + else if(CFStringHasPrefix(key, sRetirementPrefix)) retval = kRetirementKey; + else if(CFStringHasPrefix(key, kSOSKVSKeyParametersKey)) retval = kParametersKey; + else if(CFStringHasPrefix(key, kSOSKVSInitialSyncKey)) retval = kInitialSyncKey; + else if(CFStringHasPrefix(key, kSOSKVSAccountChangedKey)) retval = kAccountChangedKey; + else retval = kMessageKey; + + switch(retval) { + case kCircleKey: + if (circle) { + CFRange fromRange = CFRangeMake(1, CFStringGetLength(key)-1); + *circle = CFStringCreateWithSubstring(NULL, key, fromRange); + } + break; + case kMessageKey: { + CFStringRef mCircle = NULL; + CFStringRef mFrom = NULL; + CFStringRef mTo = NULL; + CFMutableStringRef keycopy = CFStringCreateMutableCopy(NULL, 128, key); + + if( ((mCircle = stringEndingIn(keycopy, sCircleSeparator)) != NULL) && + ((mFrom = stringEndingIn(keycopy, sFromToSeparator)) != NULL) && + (CFStringGetLength(mFrom) > 0) ) { + mTo = stringEndingIn(keycopy, NULL); + if (circle) *circle = CFStringCreateCopy(NULL, mCircle); + if (from) *from = CFStringCreateCopy(NULL, mFrom); + if (to && mTo) *to = CFStringCreateCopy(NULL, mTo); + } else { + retval = kUnknownKey; + } + CFReleaseNull(mCircle); + CFReleaseNull(mFrom); + CFReleaseNull(mTo); + CFReleaseNull(keycopy); + } + break; + case kRetirementKey: { + CFStringRef mCircle = NULL; + CFStringRef mPeer = NULL; + CFMutableStringRef keycopy = CFStringCreateMutableCopy(NULL, 128, key); + CFStringDelete(keycopy, CFRangeMake(0, 1)); + if( ((mCircle = stringEndingIn(keycopy, sCircleSeparator)) != NULL) && + ((mPeer = stringEndingIn(keycopy, NULL)) != NULL)) { + if (circle) *circle = CFStringCreateCopy(NULL, mCircle); + if (from) *from = CFStringCreateCopy(NULL, mPeer); + } else { + retval = kUnknownKey; + } + // TODO - Update our circle + CFReleaseNull(mCircle); + CFReleaseNull(mPeer); + CFReleaseNull(keycopy); + } + break; + case kAccountChangedKey: + case kParametersKey: + case kInitialSyncKey: + case kUnknownKey: + break; + } + + return retval; +} + + +SOSKVSKeyType SOSKVSKeyGetKeyType(CFStringRef key) +{ + return SOSKVSKeyGetKeyTypeAndParse(key, NULL, NULL, NULL); +} + +CFStringRef SOSCircleKeyCreateWithCircle(SOSCircleRef circle, CFErrorRef *error) +{ + return SOSCircleKeyCreateWithName(SOSCircleGetName(circle), error); +} + + +CFStringRef SOSCircleKeyCreateWithName(CFStringRef circleName, CFErrorRef *error) +{ + return CFStringCreateWithFormat(NULL, NULL, CFSTR("%@%@"), sCirclePrefix, circleName); +} + +CFStringRef SOSCircleKeyCopyCircleName(CFStringRef key, CFErrorRef *error) +{ + CFStringRef circleName = NULL; + + if (kCircleKey != SOSKVSKeyGetKeyTypeAndParse(key, &circleName, NULL, NULL)) { + SOSCreateErrorWithFormat(kSOSErrorNoCircleName, NULL, error, NULL, CFSTR("Couldn't find circle name in key '%@'"), key); + + CFReleaseNull(circleName); + } + + return circleName; +} + +CFStringRef SOSMessageKeyCopyCircleName(CFStringRef key, CFErrorRef *error) +{ + CFStringRef circleName = NULL; + + if (SOSKVSKeyGetKeyTypeAndParse(key, &circleName, NULL, NULL) != kMessageKey) { + SOSCreateErrorWithFormat(kSOSErrorNoCircleName, NULL, error, NULL, CFSTR("Couldn't find circle name in key '%@'"), key); + + CFReleaseNull(circleName); + } + return circleName; +} + +CFStringRef SOSMessageKeyCopyFromPeerName(CFStringRef messageKey, CFErrorRef *error) +{ + CFStringRef fromPeer = NULL; + + if (SOSKVSKeyGetKeyTypeAndParse(messageKey, NULL, &fromPeer, NULL) != kMessageKey) { + SOSCreateErrorWithFormat(kSOSErrorNoCircleName, NULL, error, NULL, CFSTR("Couldn't find from peer in key '%@'"), messageKey); + + CFReleaseNull(fromPeer); + } + return fromPeer; +} + +CFStringRef SOSMessageKeyCreateWithCircleAndPeerNames(SOSCircleRef circle, CFStringRef from_peer_name, CFStringRef to_peer_name) +{ + return CFStringCreateWithFormat(NULL, NULL, CFSTR("%@%@%@%@%@"), + SOSCircleGetName(circle), sCircleSeparator, from_peer_name, sFromToSeparator, to_peer_name); +} + +CFStringRef SOSMessageKeyCreateWithCircleAndPeerInfos(SOSCircleRef circle, SOSPeerInfoRef from_peer, SOSPeerInfoRef to_peer) +{ + return SOSMessageKeyCreateWithCircleAndPeerNames(circle, SOSPeerInfoGetPeerID(from_peer), SOSPeerInfoGetPeerID(to_peer)); +} + +CFStringRef SOSMessageKeyCreateWithAccountAndPeer(SOSAccountRef account, SOSCircleRef circle, CFStringRef peer_name) { + // TODO: Handle errors! + CFErrorRef error = NULL; + + SOSFullPeerInfoRef me = SOSAccountGetMyFullPeerInCircle(account, circle, &error); + SOSPeerInfoRef my_pi = SOSFullPeerInfoGetPeerInfo(me); + CFStringRef result = SOSMessageKeyCreateWithCircleAndPeerNames(circle, SOSPeerInfoGetPeerID(my_pi), peer_name); + CFReleaseSafe(error); + return result; +} + +CFStringRef SOSRetirementKeyCreateWithCircleAndPeer(SOSCircleRef circle, CFStringRef retirement_peer_name) +{ + return CFStringCreateWithFormat(NULL, NULL, CFSTR("%@%@%@%@"), + sRetirementPrefix, SOSCircleGetName(circle), sCircleSeparator, retirement_peer_name); +} + + +static SOSPeerCoderStatus SOSAccountHandlePeerMessage(SOSAccountRef account, + CFStringRef circle_id, + CFStringRef peer_name, + CFDataRef message, + SOSAccountSendBlock send_block, + CFErrorRef *error) +{ + bool success = false; + CFStringRef peer_key = NULL; + + SOSCircleRef circle = SOSAccountFindCircle(account, circle_id, error); + require_quiet(circle, fail); + SOSFullPeerInfoRef myFullPeer = SOSAccountGetMyFullPeerInCircle(account, circle, error); + SOSPeerInfoRef myPeer = SOSFullPeerInfoGetPeerInfo(myFullPeer); + require_action_quiet(SOSCircleHasPeer(circle, myPeer, NULL), fail, SOSCreateErrorWithFormat(kSOSErrorNotReady, NULL, error, NULL, CFSTR("Not in circle, can't handle message"))); + + peer_key = SOSMessageKeyCreateWithAccountAndPeer(account, circle, peer_name); + + SOSPeerSendBlock peer_send_block = ^bool (CFDataRef message, CFErrorRef *error) { + return send_block(circle, peer_key, message, error); + }; + + success = SOSCircleHandlePeerMessage(circle, myFullPeer, account->factory, peer_send_block, peer_name, message, error); + +fail: + CFReleaseNull(peer_key); + return success; +} + +bool SOSAccountHandleUpdates(SOSAccountRef account, + CFDictionaryRef updates, + CFErrorRef *error) { + + if(CFDictionaryGetValue(updates, kSOSKVSAccountChangedKey) != NULL) { + SOSAccountSetToNew(account); + } + + CFTypeRef parameters = CFDictionaryGetValue(updates, kSOSKVSKeyParametersKey); + if (isData(parameters)) { + SecKeyRef newKey = NULL; + CFDataRef newParameters = NULL; + const uint8_t *parse_end = der_decode_cloud_parameters(kCFAllocatorDefault, kSecECDSAAlgorithmID, + &newKey, &newParameters, error, + CFDataGetBytePtr(parameters), CFDataGetPastEndPtr(parameters)); + + if (parse_end == CFDataGetPastEndPtr(parameters)) { + if (CFEqualSafe(account->user_public, newKey)) { + secnotice("updates", "Got same public key sent our way. Ignoring."); + } else if (CFEqualSafe(account->previous_public, newKey)) { + secnotice("updates", "Got previous public key repeated. Ignoring."); + } else { + CFReleaseNull(account->user_public); + SOSAccountPurgePrivateCredential(account); + CFReleaseNull(account->user_key_parameters); + + account->user_public_trusted = false; + + account->user_public = newKey; + newKey = NULL; + + account->user_key_parameters = newParameters; + newParameters = NULL; + + secnotice("updates", "Got new parameters for public key: %@", account->user_public); + debugDumpUserParameters(CFSTR("params"), account->user_key_parameters); + } + } + + CFReleaseNull(newKey); + CFReleaseNull(newParameters); + } + + if (!account->user_public_trusted) { + if (!account->deferred_updates) { + account->deferred_updates = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault); + } + + CFDictionaryForEach(updates, ^(const void *key, const void *value) { + if (!CFEqualSafe(key, kSOSKVSKeyParametersKey) && !CFEqualSafe(key, kSOSKVSAccountChangedKey)) + CFDictionarySetValue(account->deferred_updates, key, value); + }); + secnotice("updates", "No public peer key, deferring updates: %@", updates); + return true; + } + + // Iterate though keys in updates. Perform circle change update. + // Then instantiate circles and engines and peers for all peers that + // are receiving a message in updates. + __block bool is_initial_sync = CFDictionaryContainsKey(updates, kSOSKVSInitialSyncKey); + + CFDictionaryForEach(updates, ^(const void *key, const void *value) { + CFStringRef circle_name = NULL; + CFErrorRef localError = NULL; + SOSCircleRef circle = NULL; + + if (SOSKVSKeyGetKeyTypeAndParse(key, &circle_name, NULL, NULL) == kCircleKey) { + circle = SOSAccountCreateCircleFrom(circle_name, value, &localError); + if (!circle) { + if (isSOSErrorCoded(localError, kSOSErrorIncompatibleCircle)) { + SOSAccountDestroyCirclePeerInfoNamed(account, circle_name, NULL); + CFDictionarySetValue(account->circles, circle_name, kCFNull); + } else { + SOSCreateErrorWithFormat(kSOSErrorNameMismatch, localError, error, NULL, + CFSTR("Bad key for message, no circle '%@'"), key); + goto circle_done; + } + } + + if (!SOSAccountUpdateCircleFromRemote(account, circle, is_initial_sync, &localError)) { + SOSCreateErrorWithFormat(kSOSErrorProcessingFailure, localError, error, NULL, + CFSTR("Error handling circle change '%@'"), key); + secnotice("update", "Error updating circle '%@': %@", key, circle); + goto circle_done; + } + } + circle_done: + CFReleaseSafe(circle_name); + CFReleaseNull(circle); + CFReleaseNull(localError); + }); + + CFDictionaryForEach(updates, ^(const void *key, const void *value) { + CFErrorRef localError = NULL; + CFStringRef circle_name = NULL; + CFStringRef from_name = NULL; + CFStringRef to_name = NULL; + switch (SOSKVSKeyGetKeyTypeAndParse(key, &circle_name, &from_name, &to_name)) { + case kParametersKey: + case kInitialSyncKey: + case kCircleKey: + break; + case kMessageKey: + { + SOSFullPeerInfoRef my_peer = NULL; + + require_action_quiet(isData(value), message_error, SOSCreateErrorWithFormat(kSOSErrorUnexpectedType, localError, error, NULL, CFSTR("Non-Data for message(%@) from '%@'"), value, key)); + require_quiet(my_peer = SOSAccountGetMyFullPeerInCircleNamedIfPresent(account, circle_name, &localError), message_error); + + CFStringRef my_id = SOSPeerInfoGetPeerID(SOSFullPeerInfoGetPeerInfo(my_peer)); + require_quiet(SOSAccountIsActivePeerInCircleNamed(account, circle_name, my_id, &localError), skip); + require_quiet(CFEqual(my_id, to_name), skip); + require_quiet(!CFEqual(my_id, from_name), skip); + + SOSAccountSendBlock cacheInDictionary = ^ bool (SOSCircleRef circle, CFStringRef key, CFDataRef new_message, CFErrorRef* error) { + CFDictionarySetValue(account->pending_changes, key, new_message); + + if (account->processed_message_block) { + account->processed_message_block(circle, value, new_message); + } + + return true; + }; + + if (SOSAccountHandlePeerMessage(account, circle_name, from_name, value, cacheInDictionary, &localError) == kSOSPeerCoderFailure) { + SOSCreateErrorWithFormat(kSOSErrorNameMismatch, localError, error, NULL, + CFSTR("Error handling peer message from '%@'"), key); + localError = NULL; // Released by SOSCreateErrorWithFormat + goto message_error; + } + + message_error: + skip: + break; + } + case kRetirementKey: + if(isData(value)) { + SOSPeerInfoRef pi = SOSPeerInfoCreateFromData(NULL, error, (CFDataRef) value); + if(pi && CFEqual(from_name, SOSPeerInfoGetPeerID(pi)) && SOSPeerInfoInspectRetirementTicket(pi, error)) { + CFDictionarySetValue(account->retired_peers, key, value); + SOSAccountRecordRetiredPeerInCircleNamed(account, circle_name, pi); + } + CFReleaseSafe(pi); + } + break; + + case kAccountChangedKey: // Handled at entry to function to make sure these are processed first. + break; + + case kUnknownKey: + secnotice("updates", "Unknown key '%@', ignoring", key); + break; + + } + + CFReleaseNull(circle_name); + CFReleaseNull(from_name); + CFReleaseNull(to_name); + + if (error && *error) + secerror("Peer message processing error for: %@ -> %@ (%@)", key, value, *error); + if (localError) + secerror("Peer message local processing error for: %@ -> %@ (%@)", key, value, localError); + + CFReleaseNull(localError); + }); + + return SOSAccountSendPendingChanges(account, error); +} + +void SOSAccountSetMessageProcessedBlock(SOSAccountRef account, SOSAccountMessageProcessedBlock processedBlock) +{ + CFRetainSafe(processedBlock); + CFReleaseNull(account->processed_message_block); + account->processed_message_block = processedBlock; +} + +CFStringRef SOSInterestListCopyDescription(CFArrayRef interests) +{ + CFMutableStringRef description = CFStringCreateMutable(kCFAllocatorDefault, 0); + CFStringAppendFormat(description, NULL, CFSTR("")); + + return description; +}