#include <SecureObjectSync/SOSInternal.h>
#include <SecureObjectSync/SOSAccount.h>
#include <SecureObjectSync/SOSCircle.h>
#include <SecureObjectSync/SOSCloudCircle.h>
#include <SecureObjectSync/SOSEngine.h>
#include <SecureObjectSync/SOSPeer.h>
#include <SecureObjectSync/SOSFullPeerInfo.h>
#include <SecureObjectSync/SOSPeerInfo.h>
#include <SecureObjectSync/SOSPeerInfoInternal.h>
#include <SecureObjectSync/SOSUserKeygen.h>
#include <Security/SecKeyPriv.h>
#include <Security/SecItemPriv.h>
#include <CoreFoundation/CFArray.h>
#include <dispatch/dispatch.h>
#include <stdlib.h>
#include <assert.h>
#include <AssertMacros.h>
#include <utilities/SecCFWrappers.h>
#include <utilities/der_plist.h>
#include <utilities/der_plist_internal.h>
#include <utilities/iOSforOSX.h>
#include <utilities/SecAKSWrappers.h>
#include <corecrypto/ccder.h>
#include <securityd/SOSCloudCircleServer.h>
#include <securityd/SecDbItem.h> // For SecError
#include <utilities/debugging.h>
#include <utilities/iCloudKeychainTrace.h>
#include <notify.h>
static CFStringRef kicloud_identity_name = CFSTR("Cloud Identity");
static bool SOSAccountHandleUpdateCircle(SOSAccountRef account, SOSCircleRef newCircle, bool writeUpdate, bool initialSync, CFErrorRef *error);
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;
}
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;
}
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;
}
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;
SOSDataSourceFactoryRef factory;
SecKeyRef _user_private;
dispatch_source_t user_private_timer;
int lock_notification_token;
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:
case kSOSMembershipRevoked:
case kSOSLeftUntrustedCircle:
return true;
case kSOSNeverAppliedToCircle:
case kSOSNeverLeftCircle:
default:
return false;
}
}
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;
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;
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("<SOSAccount@%p: Gestalt: %@\n Circles: %@ CircleIDs: %@>"), 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);
}
#if OLD_CODERS_SUPPORTED
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:
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:
account->factory = NULL; CFReleaseNull(account);
account = SOSAccountCreateFromDER_V1(allocator, factory, interest_block, update_block, error, &dersave, derend);
if(account) account->departure_code = kSOSNeverAppliedToCircle;
return account;
}
#endif
#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; 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) {
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:
account->factory = NULL; CFReleaseNull(account);
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); } 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
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;
}
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
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;
}
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);
}
});
}
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: SOSCircleGenerationSign(circle, privKey, fpi, NULL);
account->departure_code = kSOSNeverLeftCircle;
});
}
});
}
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) {
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) {
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;
}
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);
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;
}
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)
{
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;
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));
if(SOSCircleHasApplicant(circle, pi, NULL)) {
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;
}
CFDictionarySetValue(account->retired_peers, retire_key, retire_value);
CFDictionarySetValue(account->pending_changes, retire_key, retire_value);
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;
}
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; }
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;
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; 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 {
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)) {
if(!SOSAccountHasLeft(account)) account->departure_code = kSOSMembershipRevoked;
}
if (me
&& SOSCircleHasActivePeer(oldCircle, me, NULL)
&& !(SOSCircleCountPeers(oldCircle) == 1 && SOSCircleHasPeer(oldCircle, me, NULL)) && !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); 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)) {
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) { success = false;
SOSCreateError(kSOSErrorIncompatibleCircle, CFSTR("Incompatible circle"), NULL, error);
}, ^(SOSCircleRef circle) { success = SOSAccountJoinThisCircle(account, user_key, circle, use_cloud_identity, error) && success;
}, ^(SOSCircleRef circle, SOSFullPeerInfoRef full_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); });
});
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);
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); });
});
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); });
}
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;
}
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;
}
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) {
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;
}
__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; 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: 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("<Interest: "));
CFArrayForEach(interests, ^(const void* string) {
if (isString(string))
CFStringAppendFormat(description, NULL, CFSTR(" '%@'"), string);
});
CFStringAppend(description, CFSTR(">"));
return description;
}