#include "SOSAccountPriv.h"
#include <Security/SecureObjectSync/SOSPeerInfoCollections.h>
#include <Security/SecureObjectSync/SOSTransportCircle.h>
#include <Security/SecureObjectSync/SOSTransportMessage.h>
#include <Security/SecureObjectSync/SOSTransportMessageIDS.h>
#include <Security/SecureObjectSync/SOSKVSKeys.h>
#include <Security/SecureObjectSync/SOSTransport.h>
#include <Security/SecureObjectSync/SOSTransportKeyParameter.h>
#include <Security/SecureObjectSync/SOSTransportKeyParameterKVS.h>
#include <Security/SecureObjectSync/SOSEngine.h>
#include <Security/SecureObjectSync/SOSPeerCoder.h>
#include <Security/SecureObjectSync/SOSInternal.h>
#include <Security/SecureObjectSync/SOSRing.h>
#include <Security/SecureObjectSync/SOSRingUtils.h>
#include <Security/SecureObjectSync/SOSPeerInfoSecurityProperties.h>
#include <Security/SecureObjectSync/SOSPeerInfoV2.h>
#include <Security/SecureObjectSync/SOSAccountTransaction.h>
#include <Security/SecItemInternal.h>
#include <SOSCircle/CKBridge/SOSCloudKeychainClient.h>
#include <SOSCircle/Regressions/SOSRegressionUtilities.h>
#include <utilities/SecCFWrappers.h>
CFGiblisWithCompareFor(SOSAccount);
const CFStringRef SOSTransportMessageTypeIDS = CFSTR("IDS");
const CFStringRef SOSTransportMessageTypeIDSV2 = CFSTR("IDS2.0");
const CFStringRef SOSTransportMessageTypeKVS = CFSTR("KVS");
const CFStringRef kSOSDSIDKey = CFSTR("AccountDSID");
const CFStringRef kSOSEscrowRecord = CFSTR("EscrowRecord");
const CFStringRef kSOSUnsyncedViewsKey = CFSTR("unsynced");
const CFStringRef kSOSPendingEnableViewsToBeSetKey = CFSTR("pendingEnableViews");
const CFStringRef kSOSPendingDisableViewsToBeSetKey = CFSTR("pendingDisableViews");
#define DATE_LENGTH 25
const CFStringRef kSOSAccountDebugScope = CFSTR("Scope");
bool SOSAccountEnsureFactoryCircles(SOSAccountRef a)
{
bool result = false;
CFStringRef circle_name = NULL;
require_quiet(a, xit);
require_quiet(a->factory, xit);
circle_name = SOSDataSourceFactoryCopyName(a->factory);
require(circle_name, xit);
SOSAccountEnsureCircle(a, circle_name, NULL);
result = true;
xit:
CFReleaseNull(circle_name);
return result;
}
SOSAccountRef SOSAccountCreateBasic(CFAllocatorRef allocator,
CFDictionaryRef gestalt,
SOSDataSourceFactoryRef factory) {
SOSAccountRef a = CFTypeAllocate(SOSAccount, struct __OpaqueSOSAccount, allocator);
a->queue = dispatch_queue_create("Account Queue", DISPATCH_QUEUE_SERIAL);
a->gestalt = CFRetainSafe(gestalt);
a->trusted_circle = NULL;
a->backups = CFDictionaryCreateMutableForCFTypes(allocator);
a->my_identity = NULL;
a->retirees = CFSetCreateMutableForSOSPeerInfosByID(allocator);
a->factory = factory;
a->isListeningForSync = false;
a->_user_private = NULL;
a->_password_tmp = NULL;
a->user_private_timer = NULL;
a->lock_notification_token = NOTIFY_TOKEN_INVALID;
a->change_blocks = CFArrayCreateMutableForCFTypes(allocator);
a->waitForInitialSync_blocks = NULL;
a->departure_code = kSOSNeverAppliedToCircle;
a->key_transport = (SOSTransportKeyParameterRef)SOSTransportKeyParameterKVSCreate(a, NULL);
a->circle_transport = NULL;
a->kvs_message_transport = NULL;
a->ids_message_transport = NULL;
a->expansion = CFDictionaryCreateMutableForCFTypes(allocator);
SOSAccountAddRingDictionary(a);
a->saveBlock = NULL;
a->circle_rings_retirements_need_attention = false;
a->engine_peer_state_needs_repair = false;
a->key_interests_need_updating = false;
a->deviceID = NULL;
return a;
}
void SOSAccountWithTransaction_Locked(SOSAccountRef account, void (^action)(SOSAccountRef account, SOSAccountTransactionRef txn)) {
SOSAccountTransactionRef at = SOSAccountTransactionCreate(account);
action(account, at);
SOSAccountTransactionFinish(at);
CFReleaseNull(at);
}
void SOSAccountWithTransaction(SOSAccountRef account, bool sync, void (^action)(SOSAccountRef account, SOSAccountTransactionRef txn)) {
dispatch_block_t with_transaction = ^{
SOSAccountWithTransaction_Locked(account, action);
};
if (sync) {
dispatch_sync(SOSAccountGetQueue(account), with_transaction);
} else {
dispatch_async(SOSAccountGetQueue(account), with_transaction);
}
}
void SOSAccountWithTransactionSync(SOSAccountRef account, void (^action)(SOSAccountRef account, SOSAccountTransactionRef txn)) {
SOSAccountWithTransaction(account, true, action);
}
void SOSAccountWithTransactionAsync(SOSAccountRef account, bool sync, void (^action)(SOSAccountRef account, SOSAccountTransactionRef txn)) {
SOSAccountWithTransaction(account, false, action);
}
void SOSAccountSetSaveBlock(SOSAccountRef account, SOSAccountSaveBlock saveBlock) {
CFAssignRetained(account->saveBlock, Block_copy(saveBlock));
}
void SOSAccountFlattenToSaveBlock(SOSAccountRef account) {
if (account->saveBlock) {
CFErrorRef localError = NULL;
CFDataRef saveData = SOSAccountCopyEncodedData(account, kCFAllocatorDefault, &localError);
(account->saveBlock)(saveData, localError);
CFReleaseNull(saveData);
CFReleaseNull(localError);
}
}
SOSSecurityPropertyResultCode SOSAccountUpdateSecurityProperty(SOSAccountRef account, CFStringRef property, SOSSecurityPropertyActionCode actionCode, CFErrorRef *error) {
SOSSecurityPropertyResultCode retval = kSOSCCGeneralSecurityPropertyError;
bool updateCircle = false;
require_action_quiet(account->trusted_circle, errOut, SOSCreateError(kSOSErrorNoCircle, CFSTR("No Trusted Circle"), NULL, error));
require_action_quiet(account->my_identity, errOut, SOSCreateError(kSOSErrorPeerNotFound, CFSTR("No Peer for Account"), NULL, error));
retval = SOSFullPeerInfoUpdateSecurityProperty(account->my_identity, actionCode, property, error);
if(actionCode == kSOSCCSecurityPropertyEnable && retval == kSOSCCSecurityPropertyValid) {
updateCircle = true;
} else if(actionCode == kSOSCCSecurityPropertyDisable && retval == kSOSCCSecurityPropertyNotValid) {
updateCircle = true;
} else if(actionCode == kSOSCCSecurityPropertyPending) {
updateCircle = true;
}
if (updateCircle) {
SOSAccountModifyCircle(account, NULL, ^(SOSCircleRef circle_to_change) {
secnotice("circleChange", "Calling SOSCircleUpdatePeerInfo for security property change");
return SOSCircleUpdatePeerInfo(circle_to_change, SOSFullPeerInfoGetPeerInfo(account->my_identity));
});
}
errOut:
return retval;
}
SOSSecurityPropertyResultCode SOSAccountSecurityPropertyStatus(SOSAccountRef account, CFStringRef property, CFErrorRef *error) {
SOSSecurityPropertyResultCode retval = kSOSCCGeneralViewError;
require_action_quiet(account->trusted_circle, errOut, SOSCreateError(kSOSErrorNoCircle, CFSTR("No Trusted Circle"), NULL, error));
require_action_quiet(account->my_identity, errOut, SOSCreateError(kSOSErrorPeerNotFound, CFSTR("No Peer for Account"), NULL, error));
retval = SOSFullPeerInfoSecurityPropertyStatus(account->my_identity, property, error);
errOut:
return retval;
}
bool SOSAccountUpdateGestalt(SOSAccountRef account, CFDictionaryRef new_gestalt)
{
if (CFEqualSafe(new_gestalt, account->gestalt))
return false;
if (account->trusted_circle && account->my_identity
&& SOSFullPeerInfoUpdateGestalt(account->my_identity, new_gestalt, NULL)) {
SOSAccountModifyCircle(account, NULL, ^(SOSCircleRef circle_to_change) {
secnotice("circleChange", "Calling SOSCircleUpdatePeerInfo for gestalt change");
return SOSCircleUpdatePeerInfo(circle_to_change, SOSAccountGetMyPeerInfo(account));
});
}
CFRetainAssign(account->gestalt, new_gestalt);
return true;
}
static bool SOSAccountUpdateDSID(SOSAccountRef account, CFStringRef dsid){
SOSAccountSetValue(account, kSOSDSIDKey, dsid, NULL);
SOSTransportCircleSendOfficialDSID(account->circle_transport, dsid, NULL);
return true;
}
void SOSAccountAssertDSID(SOSAccountRef account, CFStringRef dsid) {
CFStringRef accountDSID = SOSAccountGetValue(account, kSOSDSIDKey, NULL);
if(accountDSID == NULL) {
secdebug("updates", "Setting dsid, current dsid is empty for this account: %@", dsid);
SOSAccountUpdateDSID(account, dsid);
} else if(CFStringCompare(dsid, accountDSID, 0) != kCFCompareEqualTo) {
secnotice("updates", "Changing DSID from: %@ to %@", accountDSID, dsid);
SOSAccountSetToNew(account);
SOSAccountUpdateDSID(account, dsid);
} else {
secnotice("updates", "Not Changing DSID: %@ to %@", accountDSID, dsid);
}
}
bool SOSAccountUpdateFullPeerInfo(SOSAccountRef account, CFSetRef minimumViews, CFSetRef excludedViews) {
if (account->trusted_circle && account->my_identity) {
if(SOSFullPeerInfoUpdateToCurrent(account->my_identity, minimumViews, excludedViews)) {
SOSAccountModifyCircle(account, NULL, ^(SOSCircleRef circle_to_change) {
secnotice("circleChange", "Calling SOSCircleUpdatePeerInfo for gestalt change");
return SOSCircleUpdatePeerInfo(circle_to_change, SOSFullPeerInfoGetPeerInfo(account->my_identity));
});
}
}
return true;
}
static bool SOSAccountValueSetContainsValue(SOSAccountRef account, CFStringRef key, CFTypeRef value) {
CFSetRef foundSet = asSet(SOSAccountGetValue(account, key, NULL), NULL);
return foundSet && CFSetContainsValue(foundSet, value);
}
static void SOSAccountValueUnionWith(SOSAccountRef account, CFStringRef key, CFSetRef valuesToUnion) {
CFMutableSetRef unionedSet = CFSetCreateMutableCopy(kCFAllocatorDefault, 0, valuesToUnion);
CFSetRef foundSet = asSet(SOSAccountGetValue(account, key, NULL), NULL);
if (foundSet) {
CFSetUnion(unionedSet, foundSet);
}
SOSAccountSetValue(account, key, unionedSet, NULL);
CFReleaseNull(unionedSet);
}
static void SOSAccountValueSubtractFrom(SOSAccountRef account, CFStringRef key, CFSetRef valuesToSubtract) {
CFSetRef foundSet = asSet(SOSAccountGetValue(account, key, NULL), NULL);
if (foundSet) {
CFMutableSetRef subtractedSet = CFSetCreateMutableCopy(kCFAllocatorDefault, 0, foundSet);
CFSetSubtract(subtractedSet, valuesToSubtract);
SOSAccountSetValue(account, key, subtractedSet, NULL);
CFReleaseNull(subtractedSet);
}
}
void SOSAccountPendEnableViewSet(SOSAccountRef account, CFSetRef enabledViews)
{
if(CFSetGetValue(enabledViews, kSOSViewKeychainV0) != NULL) secnotice("viewChange", "Warning, attempting to Add KeychainV0");
SOSAccountValueUnionWith(account, kSOSPendingEnableViewsToBeSetKey, enabledViews);
SOSAccountValueSubtractFrom(account, kSOSPendingDisableViewsToBeSetKey, enabledViews);
}
void SOSAccountPendDisableViewSet(SOSAccountRef account, CFSetRef disabledViews)
{
SOSAccountValueUnionWith(account, kSOSPendingDisableViewsToBeSetKey, disabledViews);
SOSAccountValueSubtractFrom(account, kSOSPendingEnableViewsToBeSetKey, disabledViews);
}
static SOSViewResultCode SOSAccountVirtualV0Behavior(SOSAccountRef account, SOSViewActionCode actionCode) {
SOSViewResultCode retval = kSOSCCGeneralViewError;
if (SOSAccountSyncingV0(account)) {
require_action_quiet(actionCode == kSOSCCViewDisable, errOut, CFSTR("Can't disable V0 view and it's on right now"));
retval = kSOSCCViewMember;
} else {
require_action_quiet(actionCode == kSOSCCViewEnable, errOut, CFSTR("Can't enable V0 and it's off right now"));
retval = kSOSCCViewNotMember;
}
errOut:
return retval;
}
SOSViewResultCode SOSAccountUpdateView(SOSAccountRef account, CFStringRef viewname, SOSViewActionCode actionCode, CFErrorRef *error) {
SOSViewResultCode retval = kSOSCCGeneralViewError;
SOSViewResultCode currentStatus = kSOSCCGeneralViewError;
bool alreadyInSync = SOSAccountHasCompletedInitialSync(account);
bool updateCircle = false;
require_action_quiet(account->trusted_circle, errOut, SOSCreateError(kSOSErrorNoCircle, CFSTR("No Trusted Circle"), NULL, error));
require_action_quiet(account->my_identity, errOut, SOSCreateError(kSOSErrorPeerNotFound, CFSTR("No Peer for Account"), NULL, error));
require_action_quiet((actionCode == kSOSCCViewEnable) || (actionCode == kSOSCCViewDisable), errOut, CFSTR("Invalid View Action"));
currentStatus = SOSAccountViewStatus(account, viewname, error);
require_action_quiet((currentStatus == kSOSCCViewNotMember) || (currentStatus == kSOSCCViewMember), errOut, CFSTR("View Membership Not Actionable"));
if (CFEqualSafe(viewname, kSOSViewKeychainV0)) {
retval = SOSAccountVirtualV0Behavior(account, actionCode);
} else if (SOSAccountSyncingV0(account) && SOSViewsIsV0Subview(viewname)) {
require_action_quiet(actionCode = kSOSCCViewDisable, errOut, CFSTR("Have V0 peer can't disable"));
retval = kSOSCCViewMember;
} else {
CFMutableSetRef pendingSet = CFSetCreateMutableForCFTypes(kCFAllocatorDefault);
CFSetAddValue(pendingSet, viewname);
if(actionCode == kSOSCCViewEnable && currentStatus == kSOSCCViewNotMember) {
if(alreadyInSync) {
retval = SOSFullPeerInfoUpdateViews(account->my_identity, actionCode, viewname, error);
if(retval == kSOSCCViewMember) updateCircle = true;
} else {
SOSAccountPendEnableViewSet(account, pendingSet);
retval = kSOSCCViewMember;
updateCircle = false;
}
} else if(actionCode == kSOSCCViewDisable && currentStatus == kSOSCCViewMember) {
if(alreadyInSync) {
retval = SOSFullPeerInfoUpdateViews(account->my_identity, actionCode, viewname, error);
if(retval == kSOSCCViewNotMember) updateCircle = true;
} else {
SOSAccountPendDisableViewSet(account, pendingSet);
retval = kSOSCCViewNotMember;
updateCircle = false;
}
} else {
retval = currentStatus;
}
CFReleaseNull(pendingSet);
if (updateCircle) {
SOSAccountModifyCircle(account, NULL, ^(SOSCircleRef circle_to_change) {
secnotice("circleChange", "Calling SOSCircleUpdatePeerInfo for views change");
return SOSCircleUpdatePeerInfo(circle_to_change, SOSFullPeerInfoGetPeerInfo(account->my_identity));
});
}
}
errOut:
return retval;
}
SOSViewResultCode SOSAccountViewStatus(SOSAccountRef account, CFStringRef viewname, CFErrorRef *error) {
SOSViewResultCode retval = kSOSCCGeneralViewError;
require_action_quiet(account->trusted_circle, errOut, SOSCreateError(kSOSErrorNoCircle, CFSTR("No Trusted Circle"), NULL, error));
require_action_quiet(account->my_identity, errOut, SOSCreateError(kSOSErrorPeerNotFound, CFSTR("No Peer for Account"), NULL, error));
if (SOSAccountValueSetContainsValue(account, kSOSPendingEnableViewsToBeSetKey, viewname)) {
retval = kSOSCCViewMember;
} else if (SOSAccountValueSetContainsValue(account, kSOSPendingDisableViewsToBeSetKey, viewname)) {
retval = kSOSCCViewNotMember;
} else {
retval = SOSFullPeerInfoViewStatus(account->my_identity, viewname, error);
}
if (retval != kSOSCCViewMember) {
if ((CFEqualSafe(viewname, kSOSViewKeychainV0) || SOSViewsIsV0Subview(viewname))
&& SOSAccountSyncingV0(account)) {
retval = kSOSCCViewMember;
}
}
if (retval == kSOSCCViewMember) {
bool isApplicant = SOSCircleHasApplicant(account->trusted_circle, SOSAccountGetMyPeerInfo(account), error);
if (isApplicant) {
retval = kSOSCCViewPending;
}
}
errOut:
return retval;
}
static void dumpViewSet(CFStringRef label, CFSetRef views) {
if(views) {
CFStringSetPerformWithDescription(views, ^(CFStringRef description) {
secnotice("circleChange", "%@ list: %@", label, description);
});
} else {
secnotice("circleChange", "No %@ list provided.", label);
}
}
static bool SOSAccountScreenViewListForValidV0(SOSAccountRef account, CFMutableSetRef viewSet, SOSViewActionCode actionCode) {
bool retval = true;
if(viewSet && CFSetContainsValue(viewSet, kSOSViewKeychainV0)) {
retval = SOSAccountVirtualV0Behavior(account, actionCode) != kSOSCCGeneralViewError;
CFSetRemoveValue(viewSet, kSOSViewKeychainV0);
}
return retval;
}
bool SOSAccountUpdateViewSets(SOSAccountRef account, CFSetRef origEnabledViews, CFSetRef origDisabledViews) {
bool retval = false;
bool updateCircle = false;
SOSPeerInfoRef pi = NULL;
CFMutableSetRef enabledViews = NULL;
CFMutableSetRef disabledViews = NULL;
if(origEnabledViews) enabledViews = CFSetCreateMutableCopy(kCFAllocatorDefault, 0, origEnabledViews);
if(origDisabledViews) disabledViews = CFSetCreateMutableCopy(kCFAllocatorDefault, 0, origDisabledViews);
dumpViewSet(CFSTR("Enabled"), enabledViews);
dumpViewSet(CFSTR("Disabled"), disabledViews);
require_action_quiet(account->trusted_circle, errOut, secnotice("views", "Attempt to set viewsets with no trusted circle"));
SOSFullPeerInfoRef fpi = SOSAccountGetMyFullPeerInfo(account);
require_action_quiet(fpi, errOut, secnotice("views", "Attempt to set viewsets with no fullPeerInfo"));
require_action_quiet(enabledViews || disabledViews, errOut, secnotice("views", "No work to do"));
pi = SOSPeerInfoCreateCopy(kCFAllocatorDefault, SOSFullPeerInfoGetPeerInfo(fpi), NULL);
require_action_quiet(pi, errOut, secnotice("views", "Couldn't copy PeerInfoRef"));
if(!SOSPeerInfoVersionIsCurrent(pi)) {
CFErrorRef updateFailure = NULL;
require_action_quiet(SOSPeerInfoUpdateToV2(pi, &updateFailure), errOut,
(secnotice("views", "Unable to update peer to V2- can't update views: %@", updateFailure), (void) CFReleaseNull(updateFailure)));
secnotice("V2update", "Updating PeerInfo to V2 within SOSAccountUpdateViewSets");
updateCircle = true;
}
CFStringSetPerformWithDescription(enabledViews, ^(CFStringRef description) {
secnotice("viewChange", "Enabling %@", description);
});
CFStringSetPerformWithDescription(disabledViews, ^(CFStringRef description) {
secnotice("viewChange", "Disabling %@", description);
});
require_action_quiet(SOSAccountScreenViewListForValidV0(account, enabledViews, kSOSCCViewEnable), errOut, secnotice("viewChange", "Bad view change (enable) with kSOSViewKeychainV0"));
require_action_quiet(SOSAccountScreenViewListForValidV0(account, disabledViews, kSOSCCViewDisable), errOut, secnotice("viewChange", "Bad view change (disable) with kSOSViewKeychainV0"));
if(SOSAccountHasCompletedInitialSync(account)) {
if(enabledViews) updateCircle |= SOSViewSetEnable(pi, enabledViews);
if(disabledViews) updateCircle |= SOSViewSetDisable(pi, disabledViews);
retval = true;
} else {
if(enabledViews) SOSAccountPendEnableViewSet(account, enabledViews);
if(disabledViews) SOSAccountPendDisableViewSet(account, disabledViews);
retval = true;
}
if(updateCircle) {
require_quiet(SOSFullPeerInfoUpdateToThisPeer(fpi, pi, NULL), errOut);
require_quiet(SOSAccountModifyCircle(account, NULL, ^(SOSCircleRef circle_to_change) {
secnotice("circleChange", "Calling SOSCircleUpdatePeerInfo for views or peerInfo change");
return SOSCircleUpdatePeerInfo(circle_to_change, SOSFullPeerInfoGetPeerInfo(account->my_identity));
}), errOut);
account->circle_rings_retirements_need_attention = true;
}
errOut:
CFReleaseNull(enabledViews);
CFReleaseNull(disabledViews);
CFReleaseNull(pi);
return retval;
}
SOSAccountRef SOSAccountCreate(CFAllocatorRef allocator,
CFDictionaryRef gestalt,
SOSDataSourceFactoryRef factory) {
SOSAccountRef a = SOSAccountCreateBasic(allocator, gestalt, factory);
SOSAccountEnsureFactoryCircles(a);
a->key_interests_need_updating = true;
return a;
}
static void SOSAccountDestroy(CFTypeRef aObj) {
SOSAccountRef a = (SOSAccountRef) aObj;
SOSAccountCancelSyncChecking(a);
SOSEngineRef engine = SOSDataSourceFactoryGetEngineForDataSourceName(a->factory, SOSCircleGetName(a->trusted_circle), NULL);
if (engine)
SOSEngineSetSyncCompleteListenerQueue(engine, NULL);
dispatch_sync(a->queue, ^{
CFReleaseNull(a->gestalt);
CFReleaseNull(a->my_identity);
CFReleaseNull(a->trusted_circle);
CFReleaseNull(a->backups);
CFReleaseNull(a->retirees);
a->user_public_trusted = false;
CFReleaseNull(a->user_public);
CFReleaseNull(a->user_key_parameters);
SOSAccountPurgePrivateCredential(a);
CFReleaseNull(a->previous_public);
CFReleaseNull(a->_user_private);
CFReleaseNull(a->_password_tmp);
a->departure_code = kSOSNeverAppliedToCircle;
CFReleaseNull(a->kvs_message_transport);
CFReleaseNull(a->ids_message_transport);
CFReleaseNull(a->key_transport);
CFReleaseNull(a->circle_transport);
dispatch_release(a->queue);
dispatch_release(a->user_private_timer);
CFReleaseNull(a->change_blocks);
CFReleaseNull(a->waitForInitialSync_blocks);
CFReleaseNull(a->expansion);
CFReleaseNull(a->saveBlock);
CFReleaseNull(a->deviceID);
});
}
static OSStatus do_delete(CFDictionaryRef query) {
OSStatus result;
result = SecItemDelete(query);
if (result) {
secerror("SecItemDelete: %d", (int)result);
}
return result;
}
static int
do_keychain_delete_aks_bags()
{
OSStatus result;
CFDictionaryRef item = CFDictionaryCreateForCFTypes(kCFAllocatorDefault,
kSecClass, kSecClassGenericPassword,
kSecAttrAccessGroup, CFSTR("com.apple.sbd"),
kSecAttrAccount, CFSTR("SecureBackupPublicKeybag"),
kSecAttrService, CFSTR("SecureBackupService"),
kSecAttrSynchronizable, kCFBooleanTrue,
kSecUseTombstones, kCFBooleanFalse,
NULL);
result = do_delete(item);
CFReleaseSafe(item);
return result;
}
static int
do_keychain_delete_identities()
{
OSStatus result;
CFDictionaryRef item = CFDictionaryCreateForCFTypes(kCFAllocatorDefault,
kSecClass, kSecClassKey,
kSecAttrSynchronizable, kCFBooleanTrue,
kSecUseTombstones, kCFBooleanFalse,
kSecAttrAccessGroup, CFSTR("com.apple.security.sos"),
NULL);
result = do_delete(item);
CFReleaseSafe(item);
return result;
}
static int
do_keychain_delete_lakitu()
{
OSStatus result;
CFDictionaryRef item = CFDictionaryCreateForCFTypes(kCFAllocatorDefault,
kSecClass, kSecClassGenericPassword,
kSecAttrSynchronizable, kCFBooleanTrue,
kSecUseTombstones, kCFBooleanFalse,
kSecAttrAccessGroup, CFSTR("com.apple.lakitu"),
kSecAttrAccount, CFSTR("EscrowServiceBypassToken"),
kSecAttrService, CFSTR("EscrowService"),
NULL);
result = do_delete(item);
CFReleaseSafe(item);
return result;
}
static int
do_keychain_delete_sbd()
{
OSStatus result;
CFDictionaryRef item = CFDictionaryCreateForCFTypes(kCFAllocatorDefault,
kSecClass, kSecClassGenericPassword,
kSecAttrSynchronizable, kCFBooleanTrue,
kSecUseTombstones, kCFBooleanFalse,
kSecAttrAccessGroup, CFSTR("com.apple.sbd"),
NULL);
result = do_delete(item);
CFReleaseSafe(item);
return result;
}
void static SOSAccountResetKeyInterests(SOSAccountRef a) {
CFDictionaryRef emptyDictionary = CFDictionaryCreateForCFTypes(kCFAllocatorDefault, NULL);
SOSCloudKeychainUpdateKeys(emptyDictionary, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(CFDictionaryRef returnedValues, CFErrorRef error) {
if (error) {
secerror("Error updating keys: %@", error);
}
});
CFReleaseNull(emptyDictionary);
}
void SOSAccountSetToNew(SOSAccountRef a) {
secnotice("accountChange", "Setting Account to New");
int result = 0;
CFReleaseNull(a->my_identity);
CFReleaseNull(a->trusted_circle);
CFReleaseNull(a->backups);
CFReleaseNull(a->retirees);
CFReleaseNull(a->user_key_parameters);
CFReleaseNull(a->user_public);
CFReleaseNull(a->previous_public);
CFReleaseNull(a->_user_private);
CFReleaseNull(a->_password_tmp);
CFReleaseNull(a->key_transport);
CFReleaseNull(a->circle_transport);
CFReleaseNull(a->kvs_message_transport);
CFReleaseNull(a->ids_message_transport);
CFReleaseNull(a->expansion);
CFReleaseNull(a->deviceID);
result = do_keychain_delete_aks_bags(); (void) result;
secdebug("set to new", "result for deleting aks bags: %d", result);
result = do_keychain_delete_identities(); (void) result;
secdebug("set to new", "result for deleting identities: %d", result);
result = do_keychain_delete_lakitu(); (void) result;
secdebug("set to new", "result for deleting lakitu: %d", result);
result = do_keychain_delete_sbd(); (void) result;
secdebug("set to new", "result for deleting sbd: %d", result);
a->user_public_trusted = false;
a->departure_code = kSOSNeverAppliedToCircle;
if (a->user_private_timer) {
dispatch_source_cancel(a->user_private_timer);
dispatch_release(a->user_private_timer);
a->user_private_timer = NULL;
xpc_transaction_end();
}
if (a->lock_notification_token != NOTIFY_TOKEN_INVALID) {
notify_cancel(a->lock_notification_token);
a->lock_notification_token = NOTIFY_TOKEN_INVALID;
}
a->key_transport = (SOSTransportKeyParameterRef)SOSTransportKeyParameterKVSCreate(a, NULL);
a->circle_transport = NULL;
a->kvs_message_transport = NULL;
a->ids_message_transport = NULL;
a->backups = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault);
a->retirees = CFSetCreateMutableForSOSPeerInfosByID(kCFAllocatorDefault);
a->expansion = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault);
SOSAccountAddRingDictionary(a);
SOSAccountEnsureFactoryCircles(a);
SOSAccountResetKeyInterests(a);
a->key_interests_need_updating = true;
}
bool SOSAccountIsNew(SOSAccountRef account, CFErrorRef *error){
bool result = false;
require_quiet(account->user_public_trusted == false, exit);
require_quiet(account->departure_code == kSOSNeverAppliedToCircle, exit);
require_quiet(account->user_private_timer == NULL, exit);
require_quiet(account->lock_notification_token == NOTIFY_TOKEN_INVALID, exit);
require_quiet (CFDictionaryGetCount(account->backups) == 0, exit);
require_quiet(CFSetGetCount(account->retirees) == 0, exit);
result = true;
exit:
return result;
}
static CFStringRef SOSAccountCopyFormatDescription(CFTypeRef aObj, CFDictionaryRef formatOptions) {
SOSAccountRef a = (SOSAccountRef) aObj;
CFStringRef gestaltDescription = CFDictionaryCopyCompactDescription(a->gestalt);
CFStringRef result = CFStringCreateWithFormat(NULL, NULL, CFSTR("<SOSAccount@%p: %c%c%c%c%c G: %@ Me: %@ C: %@ >"), a,
a->user_public ? 'P' : 'p',
a->user_public_trusted ? 'T' : 't',
a->isListeningForSync ? 'L' : 'l',
SOSAccountHasCompletedInitialSync(a) ? 'C' : 'c',
SOSAccountHasCompletedRequiredBackupSync(a) ? 'B' : 'b',
gestaltDescription, a->my_identity, a->trusted_circle);
CFReleaseNull(gestaltDescription);
return result;
}
CFStringRef SOSAccountCreateCompactDescription(SOSAccountRef a) {
CFStringRef gestaltDescription = CFDictionaryCopySuperCompactDescription(a->gestalt);
CFStringRef result = CFStringCreateWithFormat(NULL, NULL, CFSTR("%@"), gestaltDescription);
CFReleaseNull(gestaltDescription);
return result;
}
static Boolean SOSAccountCompare(CFTypeRef lhs, CFTypeRef rhs)
{
SOSAccountRef laccount = (SOSAccountRef) lhs;
SOSAccountRef raccount = (SOSAccountRef) rhs;
return CFEqualSafe(laccount->gestalt, raccount->gestalt)
&& CFEqualSafe(laccount->trusted_circle, raccount->trusted_circle)
&& CFEqualSafe(laccount->expansion, raccount->expansion)
&& CFEqualSafe(laccount->my_identity, raccount->my_identity);
}
dispatch_queue_t SOSAccountGetQueue(SOSAccountRef account) {
return account->queue;
}
void SOSAccountSetUserPublicTrustedForTesting(SOSAccountRef account){
account->user_public_trusted = true;
}
SOSFullPeerInfoRef SOSAccountCopyAccountIdentityPeerInfo(SOSAccountRef account, CFAllocatorRef allocator, CFErrorRef* error)
{
return CFRetainSafe(account->my_identity);
}
static bool SOSAccountThisDeviceCanSyncWithCircle(SOSAccountRef account) {
bool ok = false;
__block CFErrorRef error = NULL;
if (!SOSAccountHasPublicKey(account, &error)) {
CFReleaseSafe(error);
return false;
}
bool hasID = true;
require_action_quiet(account->my_identity, xit,
SOSCreateError(kSOSErrorBadFormat, CFSTR("Account identity not set"), NULL, &error));
SOSTransportMessageIDSGetIDSDeviceID(account);
require_action_quiet(account->trusted_circle, xit,
SOSCreateError(kSOSErrorBadFormat, CFSTR("Account trusted circle not set"), NULL, &error));
require_action_quiet(hasID, xit,
SOSCreateError(kSOSErrorBadFormat, CFSTR("Missing IDS device ID"), NULL, &error));
ok = SOSCircleHasPeerWithID(account->trusted_circle,
SOSPeerInfoGetPeerID(SOSFullPeerInfoGetPeerInfo(account->my_identity)), &error);
xit:
if (!ok) {
secerror("sync with device failure: %@", error);
}
CFReleaseSafe(error);
return ok;
}
static bool SOSAccountIsThisPeerIDMe(SOSAccountRef account, CFStringRef peerID) {
SOSPeerInfoRef mypi = SOSFullPeerInfoGetPeerInfo(account->my_identity);
CFStringRef myPeerID = SOSPeerInfoGetPeerID(mypi);
return myPeerID && CFEqualSafe(myPeerID, peerID);
}
bool SOSAccountSendIKSPSyncList(SOSAccountRef account, CFErrorRef *error){
bool result = true;
__block CFErrorRef localError = NULL;
__block CFMutableArrayRef ids = NULL;
SOSCircleRef circle = NULL;
require_action_quiet(SOSAccountIsInCircle(account, NULL), xit,
SOSCreateError(kSOSErrorNoCircle, CFSTR("This device is not in circle"),
NULL, &localError));
circle = SOSAccountGetCircle(account, error);
ids = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault);
require_action_quiet(SOSAccountThisDeviceCanSyncWithCircle(account), xit,
SOSCreateError(kSOSErrorNoCircle, CFSTR("This device cannot sync with circle"),
NULL, &localError));
SOSCircleForEachValidPeer(circle, account->user_public, ^(SOSPeerInfoRef peer) {
if (!SOSAccountIsThisPeerIDMe(account, SOSPeerInfoGetPeerID(peer))) {
if(SOSPeerInfoShouldUseIDSTransport(SOSFullPeerInfoGetPeerInfo(account->my_identity), peer) &&
SOSPeerInfoShouldUseIDSMessageFragmentation(SOSFullPeerInfoGetPeerInfo(account->my_identity), peer)){
SOSTransportMessageIDSSetFragmentationPreference(account->ids_message_transport, kCFBooleanTrue);
CFStringRef deviceID = SOSPeerInfoCopyDeviceID(peer);
if(deviceID != NULL){
CFArrayAppendValue(ids, deviceID);
}
CFReleaseNull(deviceID);
}
}
});
require_quiet(CFArrayGetCount(ids) != 0, xit);
secnotice("IDS Transport", "List of IDS Peers to ping: %@", ids);
SOSCloudKeychainGetIDSDeviceAvailability(ids, SOSAccountGetMyPeerID(account), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(CFDictionaryRef returnedValues, CFErrorRef sync_error) {
bool success = (sync_error == NULL);
if(!success)
secerror("Failed to send list of IDS peers to IDSKSP: %@", sync_error);
});
xit:
if(error && *error != NULL)
secerror("SOSAccountSendIKSPSyncList had an error: %@", *error);
if(localError)
secerror("SOSAccountSendIKSPSyncList had an error: %@", localError);
CFReleaseNull(ids);
CFReleaseNull(localError);
return result;
}
bool SOSAccountSyncWithAllKVSPeers(SOSAccountRef account, CFErrorRef *error)
{
__block bool result = true;
if(SOSAccountIsInCircle(account, NULL)) {
SOSCircleForEachValidPeer(account->trusted_circle, account->user_public, ^(SOSPeerInfoRef peer) {
if (!SOSAccountIsThisPeerIDMe(account, SOSPeerInfoGetPeerID(peer))) {
CFStringRef deviceID = SOSPeerInfoCopyDeviceID(peer);
if(deviceID == NULL || !SOSPeerInfoShouldUseIDSTransport(SOSFullPeerInfoGetPeerInfo(account->my_identity), peer)){
result = SOSAccountSyncWithKVSPeer(account, SOSPeerInfoGetPeerID(peer), error);
if(result){
secnotice("KVS Transport", "synced with peer: %@", SOSPeerInfoGetPeerID(peer));
}
else{
secnotice("KVS Transport", "failed to sync with peer: %@", SOSPeerInfoGetPeerID(peer));
}
}
CFReleaseNull(deviceID);
}
});
}
secnotice("sync", "SOSAccountSyncWithAllKVSPeers returns: %d", result);
return true;
}
static CFMutableArrayRef SOSAccountCopyPeerIDsForDSID(SOSAccountRef account, CFStringRef deviceID, CFErrorRef* error) {
CFMutableArrayRef peerIDs = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault);
SOSCircleForEachValidPeer(account->trusted_circle, account->user_public, ^(SOSPeerInfoRef peer) {
CFStringRef peerDeviceID = SOSPeerInfoCopyDeviceID(peer);
if(peerDeviceID != NULL && CFStringCompare(peerDeviceID, deviceID, 0) == 0){
CFArrayAppendValue(peerIDs, SOSPeerInfoGetPeerID(peer));
}
CFReleaseNull(peerDeviceID);
});
if (peerIDs == NULL || CFArrayGetCount(peerIDs) == 0) {
CFReleaseNull(peerIDs);
SOSErrorCreate(kSOSErrorPeerNotFound, error, NULL, CFSTR("No peer with DSID: %@"), deviceID);
}
return peerIDs;
}
static bool SOSAccountSyncWithKVSPeers(SOSAccountRef account, CFArrayRef peerIDs, CFErrorRef *error) {
CFDictionaryRef circleToPeerIDs = NULL;
CFErrorRef localError = NULL;
bool result = false;
require_action_quiet(SOSAccountThisDeviceCanSyncWithCircle(account), xit,
SOSCreateError(kSOSErrorNoCircle, CFSTR("This device cannot sync with circle"),
NULL, &localError));
circleToPeerIDs = CFDictionaryCreateForCFTypes(kCFAllocatorDefault,
SOSCircleGetName(account->trusted_circle), peerIDs,
NULL);
result = SOSTransportMessageSyncWithPeers(account->kvs_message_transport, circleToPeerIDs, &localError);
SOSEngineRef engine = SOSTransportMessageGetEngine(account->kvs_message_transport);
result &= SOSEngineSyncWithPeers(engine, &localError);
if (result)
SetCloudKeychainTraceValueForKey(kCloudKeychainNumberOfTimesSyncedWithPeers, 1);
xit:
CFReleaseNull(circleToPeerIDs);
if (!result) {
if (isSOSErrorCoded(localError, kSOSErrorPeerNotFound)) {
secnotice("Account", "Arming account to update SOSEngine with current trusted peers");
account->engine_peer_state_needs_repair = true;
}
CFErrorPropagate(localError, error);
localError = NULL;
}
return result;
}
bool SOSAccountSyncWithKVSUsingIDSID(SOSAccountRef account, CFStringRef deviceID, CFErrorRef *error) {
bool result = false;
CFErrorRef localError = NULL;
secnotice("KVS Transport","Syncing with KVS capable peer via DSID: %@", deviceID);
CFArrayRef peerIDs = SOSAccountCopyPeerIDsForDSID(account, deviceID, &localError);
require_quiet(peerIDs, xit);
CFStringArrayPerfromWithDescription(peerIDs, ^(CFStringRef peerIDList) {
secnotice("KVS Transport", "Syncing with KVS capable peers: %@", peerIDList);
});
result = SOSAccountSyncWithKVSPeers(account, peerIDs, &localError);
secerror("KVS sync %s. (%@)", result ? "succeeded" : "failed", localError);
xit:
CFReleaseNull(peerIDs);
CFErrorPropagate(localError, error);
return result;
}
bool SOSAccountSyncWithKVSPeer(SOSAccountRef account, CFStringRef peerID, CFErrorRef *error)
{
bool result = false;
CFErrorRef localError = NULL;
secnotice("KVS Transport","Syncing with KVS capable peer: %@", peerID);
CFArrayRef peerIDs = CFArrayCreateForCFTypes(kCFAllocatorDefault, peerID, NULL);
result = SOSAccountSyncWithKVSPeers(account, peerIDs, &localError);
secerror("KVS sync %s. (%@)", result ? "succeeded" : "failed", localError);
CFReleaseNull(peerIDs);
CFErrorPropagate(localError, error);
return result;
}
#define LOG_ENGINE_STATE_INTERVAL 20
bool SOSAccountSyncWithIDSPeer(SOSAccountRef account, CFStringRef deviceID, CFErrorRef *error)
{
CFErrorRef localError = NULL;
CFMutableDictionaryRef circleToPeerIDs = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault);
CFMutableArrayRef ids = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault);
static int engineLogCountDown = 0;
bool result = true;
require_action_quiet(SOSAccountThisDeviceCanSyncWithCircle(account), xit,
SOSCreateError(kSOSErrorNoCircle, CFSTR("This device cannot sync with circle"),
NULL, &localError));
SOSCircleForEachValidPeer(account->trusted_circle, account->user_public, ^(SOSPeerInfoRef peer) {
CFStringRef peerDeviceID = SOSPeerInfoCopyDeviceID(peer);
if(peerDeviceID != NULL && CFStringCompare(peerDeviceID, deviceID, 0) == 0){
CFArrayAppendValue(ids, SOSPeerInfoGetPeerID(peer));
}
CFReleaseNull(peerDeviceID);
});
require_action_quiet(CFArrayGetCount(ids), xit, SOSCreateError(kSOSErrorNoCircle, CFSTR("Cannot find peer in circle"),
NULL, &localError));
secnotice("IDS Transport","Syncing with IDS capable peer: %@", ids);
CFDictionarySetValue(circleToPeerIDs, SOSCircleGetName(account->trusted_circle), ids);
result = SOSTransportMessageSyncWithPeers(account->ids_message_transport, circleToPeerIDs, &localError);
secnotice("IDS Transport", "IDS Sync result: %d", result);
SOSEngineRef engine = SOSTransportMessageGetEngine(account->ids_message_transport);
result &= SOSEngineSyncWithPeers(engine, &localError);
if (result)
SetCloudKeychainTraceValueForKey(kCloudKeychainNumberOfTimesSyncedWithPeers, 1);
if(engineLogCountDown <= 0) {
SOSEngineLogState(engine);
engineLogCountDown = LOG_ENGINE_STATE_INTERVAL;
} else {
engineLogCountDown--;
}
xit:
CFReleaseNull(circleToPeerIDs);
if (!result) {
secdebug("Account", "Could not sync with peer %@, error: %@", deviceID, localError);
if (isSOSErrorCoded(localError, kSOSErrorPeerNotFound)) {
secnotice("Account", "Arming account to update SOSEngine with current trusted peers");
account->engine_peer_state_needs_repair = true;
}
CFErrorPropagate(localError, error);
localError = NULL;
}
CFReleaseNull(ids);
CFReleaseSafe(localError);
return result;
}
bool SOSAccountCleanupAfterPeer(SOSAccountRef account, size_t seconds, SOSCircleRef circle,
SOSPeerInfoRef cleanupPeer, CFErrorRef* error)
{
bool success = true;
SOSPeerInfoRef myPeerInfo = SOSFullPeerInfoGetPeerInfo(account->my_identity);
require_action_quiet(account->my_identity && myPeerInfo, xit, SOSErrorCreate(kSOSErrorPeerNotFound, error, NULL, CFSTR("I have no peer")));
require_quiet(SOSCircleHasActivePeer(circle, SOSFullPeerInfoGetPeerInfo(account->my_identity), error), xit);
CFStringRef cleanupPeerID = SOSPeerInfoGetPeerID(cleanupPeer);
CFStringRef circle_name = SOSCircleGetName(circle);
CFMutableDictionaryRef circleToPeerIDs = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault);
CFArrayAppendValue(CFDictionaryEnsureCFArrayAndGetCurrentValue(circleToPeerIDs, circle_name), cleanupPeerID);
CFErrorRef localError = NULL;
if (!(success &= SOSTransportMessageCleanupAfterPeerMessages(account->kvs_message_transport, circleToPeerIDs, &localError))) {
secnotice("account", "Failed to cleanup after peer %@ messages: %@", cleanupPeerID, localError);
}
if (account->ids_message_transport && !SOSTransportMessageCleanupAfterPeerMessages(account->ids_message_transport, circleToPeerIDs, &localError)) {
secnotice("account", "Failed to cleanup after peer %@ messages: %@", cleanupPeerID, localError);
}
CFReleaseNull(localError);
if((success &= SOSPeerInfoRetireRetirementTicket(seconds, cleanupPeer))) {
if (!(success &= SOSTransportCircleExpireRetirementRecords(account->circle_transport, circleToPeerIDs, &localError))) {
secnotice("account", "Failed to cleanup after peer %@ retirement: %@", cleanupPeerID, localError);
}
}
CFReleaseNull(localError);
CFReleaseNull(circleToPeerIDs);
xit:
return success;
}
bool SOSAccountCleanupRetirementTickets(SOSAccountRef account, size_t seconds, CFErrorRef* error) {
CFMutableSetRef retirees_to_remove = CFSetCreateMutableForSOSPeerInfosByID(kCFAllocatorDefault);
__block bool success = true;
CFSetForEach(account->retirees, ^(const void *value) {
SOSPeerInfoRef retiree = (SOSPeerInfoRef) value;
if (retiree) {
if (!SOSPeerInfoIsRetirementTicket(retiree) ||
(SOSPeerInfoRetireRetirementTicket(seconds, retiree) && !SOSCircleHasActivePeer(account->trusted_circle, retiree, NULL))) {
CFSetAddValue(retirees_to_remove, retiree);
};
}
});
CFMutableArrayRef retirees_to_cleanup = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault);
CFSetForEach(retirees_to_remove, ^(const void *value) {
CFArrayAppendValue(retirees_to_cleanup, value);
CFSetRemoveValue(account->retirees, value);
});
CFReleaseNull(retirees_to_remove);
CFDictionaryRef retirements_to_remove = CFDictionaryCreateForCFTypes(kCFAllocatorDefault,
SOSCircleGetName(account->trusted_circle), retirees_to_cleanup,
NULL);
CFReleaseNull(retirees_to_cleanup);
success = SOSTransportCircleExpireRetirementRecords(account->circle_transport, retirements_to_remove, error);
CFReleaseNull(retirements_to_remove);
return success;
}
bool SOSAccountScanForRetired(SOSAccountRef account, SOSCircleRef circle, CFErrorRef *error) {
SOSCircleForEachRetiredPeer(circle, ^(SOSPeerInfoRef peer) {
CFSetSetValue(account->retirees, peer);
CFErrorRef cleanupError = NULL;
if (!SOSAccountCleanupAfterPeer(account, RETIREMENT_FINALIZATION_SECONDS, circle, peer, &cleanupError)) {
secnotice("retirement", "Error cleaning up after peer, probably orphaned some stuff in KVS: (%@) – moving on", cleanupError);
}
CFReleaseSafe(cleanupError);
});
return true;
}
SOSCircleRef SOSAccountCloneCircleWithRetirement(SOSAccountRef account, SOSCircleRef starting_circle, CFErrorRef *error) {
SOSCircleRef new_circle = SOSCircleCopyCircle(NULL, starting_circle, error);
SOSFullPeerInfoRef meFull = SOSAccountGetMyFullPeerInfo(account);
SOSPeerInfoRef me = SOSFullPeerInfoGetPeerInfo(meFull);
bool iAmApplicant = me && SOSCircleHasApplicant(new_circle, me, NULL);
if(!new_circle) return NULL;
__block bool workDone = false;
if (account->retirees) {
CFSetForEach(account->retirees, ^(const void* value) {
SOSPeerInfoRef pi = (SOSPeerInfoRef) value;
if (isSOSPeerInfo(pi)) {
SOSCircleUpdatePeerInfo(new_circle, pi);
workDone = true;
}
});
}
if(workDone && SOSCircleCountPeers(new_circle) == 0) {
SecKeyRef userPrivKey = SOSAccountGetPrivateCredential(account, error);
if(iAmApplicant) {
if(userPrivKey) {
secnotice("resetToOffering", "Reset to offering with last retirement and me as applicant");
if(!SOSCircleResetToOffering(new_circle, userPrivKey, meFull, error) ||
!SOSAccountAddiCloudIdentity(account, new_circle, userPrivKey, error)) {
CFReleaseNull(new_circle);
return NULL;
}
} else {
CFReleaseNull(new_circle);
return NULL;
}
} else {
secnotice("resetToEmpty", "Reset to empty with last retirement");
SOSCircleResetToEmpty(new_circle, NULL);
}
}
return new_circle;
}
void SOSAccountAddChangeBlock(SOSAccountRef a, SOSAccountCircleMembershipChangeBlock changeBlock) {
SOSAccountCircleMembershipChangeBlock copy = Block_copy(changeBlock);
CFArrayAppendValue(a->change_blocks, copy);
CFReleaseNull(copy);
}
void SOSAccountRemoveChangeBlock(SOSAccountRef a, SOSAccountCircleMembershipChangeBlock changeBlock) {
CFArrayRemoveAllValue(a->change_blocks, changeBlock);
}
void SOSAccountAddSyncablePeerBlock(SOSAccountRef a, CFStringRef ds_name, SOSAccountSyncablePeersBlock changeBlock) {
if (!changeBlock) return;
CFRetainSafe(ds_name);
SOSAccountCircleMembershipChangeBlock block_to_register = ^void (SOSCircleRef new_circle,
CFSetRef added_peers, CFSetRef removed_peers,
CFSetRef added_applicants, CFSetRef removed_applicants) {
if (!CFEqualSafe(SOSCircleGetName(new_circle), ds_name))
return;
SOSPeerInfoRef myPi = SOSFullPeerInfoGetPeerInfo(a->my_identity);
CFStringRef myPi_id = myPi ? SOSPeerInfoGetPeerID(myPi) : NULL;
CFMutableArrayRef peer_ids = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault);
CFMutableArrayRef added_ids = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault);
CFMutableArrayRef removed_ids = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault);
if (SOSCircleHasPeer(new_circle, myPi, NULL)) {
SOSCircleForEachPeer(new_circle, ^(SOSPeerInfoRef peer) {
CFArrayAppendValueIfNot(peer_ids, SOSPeerInfoGetPeerID(peer), myPi_id);
});
CFSetForEach(added_peers, ^(const void *value) {
CFArrayAppendValueIfNot(added_ids, SOSPeerInfoGetPeerID((SOSPeerInfoRef) value), myPi_id);
});
CFSetForEach(removed_peers, ^(const void *value) {
CFArrayAppendValueIfNot(removed_ids, SOSPeerInfoGetPeerID((SOSPeerInfoRef) value), myPi_id);
});
}
if (CFArrayGetCount(peer_ids) || CFSetContainsValue(removed_peers, myPi))
changeBlock(peer_ids, added_ids, removed_ids);
CFReleaseSafe(peer_ids);
CFReleaseSafe(added_ids);
CFReleaseSafe(removed_ids);
};
CFRetainSafe(changeBlock);
SOSAccountAddChangeBlock(a, block_to_register);
CFSetRef empty = CFSetCreateMutableForSOSPeerInfosByID(kCFAllocatorDefault);
if (a->trusted_circle && CFEqualSafe(ds_name, SOSCircleGetName(a->trusted_circle))) {
block_to_register(a->trusted_circle, empty, empty, empty, empty);
}
CFReleaseSafe(empty);
}
void SOSAccountPurgeIdentity(SOSAccountRef account) {
if (account->my_identity) {
CFErrorRef purgeError = NULL;
if (!SOSFullPeerInfoPurgePersistentKey(account->my_identity, &purgeError)) {
secwarning("Couldn't purge persistent key for %@ [%@]", account->my_identity, purgeError);
}
CFReleaseNull(purgeError);
CFReleaseNull(account->my_identity);
}
}
bool sosAccountLeaveCircle(SOSAccountRef account, SOSCircleRef circle, CFErrorRef* error) {
SOSFullPeerInfoRef fpi = account->my_identity;
if(!fpi) return false;
CFErrorRef localError = NULL;
bool retval = false;
SOSPeerInfoRef retire_peer = SOSFullPeerInfoPromoteToRetiredAndCopy(fpi, &localError);
if (!retire_peer) {
secerror("Create ticket failed for peer %@: %@", fpi, localError);
} else {
if(SOSCircleHasApplicant(circle, retire_peer, NULL)) {
SOSCircleWithdrawRequest(circle, retire_peer, NULL);
} else if (SOSCircleHasPeer(circle, retire_peer, NULL)) {
if (SOSCircleUpdatePeerInfo(circle, retire_peer)) {
CFErrorRef cleanupError = NULL;
if (!SOSAccountCleanupAfterPeer(account, RETIREMENT_FINALIZATION_SECONDS, circle, retire_peer, &cleanupError)) {
secerror("Error cleanup up after peer (%@): %@", retire_peer, cleanupError);
}
CFReleaseSafe(cleanupError);
}
}
CFSetAddValue(account->retirees, retire_peer);
CFErrorRef postError = NULL;
if (!SOSTransportCirclePostRetirement(account->circle_transport, SOSCircleGetName(circle), retire_peer, &postError)){
secwarning("Couldn't post retirement (%@)", postError);
}
if(!SOSTransportCircleFlushChanges(account->circle_transport, &postError)){
secwarning("Couldn't flush retirement data (%@)", postError);
}
CFReleaseNull(postError);
}
SOSAccountPurgeIdentity(account);
retval = true;
CFReleaseNull(localError);
CFReleaseNull(retire_peer);
return retval;
}
bool sosAccountLeaveRing(SOSAccountRef account, SOSRingRef ring, CFErrorRef* error) {
SOSFullPeerInfoRef fpi = account->my_identity;
if(!fpi) return false;
SOSPeerInfoRef pi = SOSFullPeerInfoGetPeerInfo(fpi);
CFStringRef peerID = SOSPeerInfoGetPeerID(pi);
CFErrorRef localError = NULL;
bool retval = false;
bool writeRing = false;
bool writePeerInfo = false;
if(SOSRingHasPeerID(ring, peerID)) {
writePeerInfo = true;
}
#if 0
if(SOSRingHasApplicant(ring, peerID)) {
writeRing = true;
}
#endif
if(writePeerInfo || writeRing) {
SOSRingWithdraw(ring, NULL, fpi, error);
}
CFDataRef peerInfoData = SOSFullPeerInfoCopyEncodedData(fpi, kCFAllocatorDefault, error);
SOSTransportCircleSendPeerInfo(account->circle_transport, peerID, peerInfoData, NULL);
if (writeRing) {
CFDataRef ring_data = SOSRingCopyEncodedData(ring, error);
if (ring_data) {
SOSTransportCircleRingPostRing(account->circle_transport, SOSRingGetName(ring), ring_data, NULL); }
CFReleaseNull(ring_data);
}
retval = true;
CFReleaseNull(localError);
return retval;
}
bool SOSAccountPostDebugScope(SOSAccountRef account, CFTypeRef scope, CFErrorRef *error) {
bool result = false;
SOSTransportCircleRef transport = account->circle_transport;
if (transport) {
result = SOSTransportCircleSendDebugInfo(transport, kSOSAccountDebugScope, scope, error);
}
return result;
}
static SOSCCStatus SOSCCThisDeviceStatusInCircle(SOSCircleRef circle, SOSPeerInfoRef this_peer) {
if (!circle)
return kSOSCCNotInCircle;
if (circle && SOSCircleCountPeers(circle) == 0)
return kSOSCCCircleAbsent;
if (this_peer) {
if(SOSPeerInfoIsRetirementTicket(this_peer))
return kSOSCCNotInCircle;
if (SOSCircleHasPeer(circle, this_peer, NULL))
return kSOSCCInCircle;
if (SOSCircleHasApplicant(circle, this_peer, NULL))
return kSOSCCRequestPending;
}
return kSOSCCNotInCircle;
}
CFStringRef SOSAccountGetSOSCCStatusString(SOSCCStatus status) {
switch(status) {
case kSOSCCInCircle: return CFSTR("kSOSCCInCircle");
case kSOSCCNotInCircle: return CFSTR("kSOSCCNotInCircle");
case kSOSCCRequestPending: return CFSTR("kSOSCCRequestPending");
case kSOSCCCircleAbsent: return CFSTR("kSOSCCCircleAbsent");
case kSOSCCError: return CFSTR("kSOSCCError");
}
return CFSTR("kSOSCCError");
}
bool SOSAccountIsInCircle(SOSAccountRef account, CFErrorRef *error) {
SOSCCStatus result = SOSAccountGetCircleStatus(account, error);
if (result != kSOSCCInCircle) {
SOSErrorCreate(kSOSErrorNoCircle, error, NULL, CFSTR("Not in circle"));
return false;
}
return true;
}
SOSCCStatus SOSAccountGetCircleStatus(SOSAccountRef account, CFErrorRef* error) {
if (!SOSAccountHasPublicKey(account, error)) {
return kSOSCCError;
}
return SOSCCThisDeviceStatusInCircle(account->trusted_circle, SOSAccountGetMyPeerInfo(account));
}
bool SOSAccountAddiCloudIdentity(SOSAccountRef account, SOSCircleRef circle, SecKeyRef user_key, CFErrorRef *error) {
bool result = false;
SOSFullPeerInfoRef cloud_identity = NULL;
SOSPeerInfoRef cloud_peer = GenerateNewCloudIdentityPeerInfo(error);
require_quiet(cloud_peer, err_out);
cloud_identity = CopyCloudKeychainIdentity(cloud_peer, error);
CFReleaseNull(cloud_peer);
require_quiet(cloud_identity, err_out);
require_quiet(SOSCircleRequestAdmission(circle, user_key, cloud_identity, error), err_out);
require_quiet(SOSCircleAcceptRequest(circle, user_key, account->my_identity, SOSFullPeerInfoGetPeerInfo(cloud_identity), error), err_out);
result = true;
err_out:
return result;
}
bool SOSAccountRemoveIncompleteiCloudIdentities(SOSAccountRef account, SOSCircleRef circle, SecKeyRef privKey, CFErrorRef *error) {
bool retval = false;
CFMutableSetRef iCloud2Remove = CFSetCreateMutableForCFTypes(kCFAllocatorDefault);
SOSCircleForEachActivePeer(circle, ^(SOSPeerInfoRef peer) {
if(SOSPeerInfoIsCloudIdentity(peer)) {
SOSFullPeerInfoRef icfpi = SOSFullPeerInfoCreateCloudIdentity(kCFAllocatorDefault, peer, NULL);
if(!icfpi) {
CFSetAddValue(iCloud2Remove, peer);
}
CFReleaseNull(icfpi);
}
});
if(CFSetGetCount(iCloud2Remove) > 0) {
retval = true;
SOSCircleRemovePeers(circle, privKey, account->my_identity, iCloud2Remove, error);
}
CFReleaseNull(iCloud2Remove);
return retval;
}
static bool SOSAccountResetCircleToOffering(SOSAccountTransactionRef aTxn, SecKeyRef user_key, CFErrorRef *error) {
SOSAccountRef account = aTxn->account;
bool result = false;
require(SOSAccountHasCircle(account, error), fail);
require(SOSAccountEnsureFullPeerAvailable(account, error), fail);
(void) SOSAccountResetAllRings(account, error);
SOSAccountModifyCircle(account, error, ^(SOSCircleRef circle) {
bool result = false;
SOSFullPeerInfoRef cloud_identity = NULL;
CFErrorRef localError = NULL;
require_quiet(SOSCircleResetToOffering(circle, user_key, account->my_identity, &localError), err_out);
account->departure_code = kSOSNeverLeftCircle;
require_quiet(SOSAccountAddEscrowToPeerInfo(account, SOSAccountGetMyFullPeerInfo(account), error), err_out);
require_quiet(SOSAccountAddiCloudIdentity(account, circle, user_key, error), 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 result;
});
SOSAccountSetValue(account, kSOSUnsyncedViewsKey, kCFBooleanTrue, NULL);
SOSAccountUpdateOutOfSyncViews(aTxn, SOSViewsGetAllCurrent());
result = true;
fail:
return result;
}
bool SOSAccountResetToOffering(SOSAccountTransactionRef aTxn, CFErrorRef* error) {
SOSAccountRef account = aTxn->account;
SecKeyRef user_key = SOSAccountGetPrivateCredential(account, error);
if (!user_key)
return false;
CFReleaseNull(account->my_identity);
secnotice("resetToOffering", "Resetting circle to offering by request from client");
return user_key && SOSAccountResetCircleToOffering(aTxn, user_key, error);
}
bool SOSAccountResetToEmpty(SOSAccountRef account, CFErrorRef* error) {
if (!SOSAccountHasPublicKey(account, error))
return false;
__block bool result = true;
result &= SOSAccountResetAllRings(account, error);
CFReleaseNull(account->my_identity);
account->departure_code = kSOSWithdrewMembership;
secnotice("resetToEmpty", "Reset Circle to empty by client request");
result &= SOSAccountModifyCircle(account, error, ^(SOSCircleRef circle) {
result = SOSCircleResetToEmpty(circle, error);
return result;
});
if (!result) {
secerror("error: %@", error ? *error : NULL);
}
return result;
}
bool SOSAccountEnsureInBackupRings(SOSAccountRef account) {
__block bool result = false;
__block CFErrorRef error = NULL;
secnotice("backup", "Ensuring in rings");
CFDataRef backupKey = NULL;
require_action_quiet(account->backup_key, exit, result = true);
backupKey = SOSPeerInfoV2DictionaryCopyData(SOSAccountGetMyPeerInfo(account), sBackupKeyKey);
require_action_quiet(!CFEqualSafe(backupKey, account->backup_key), exit, result = true); require_quiet(SOSAccountUpdatePeerInfo(account, CFSTR("Backup public key"), &error, ^bool(SOSFullPeerInfoRef fpi, CFErrorRef *error) {
return SOSFullPeerInfoUpdateBackupKey(fpi, account->backup_key, error);
}), exit);
require_quiet(account->backup_key, exit);
require_quiet(SOSBSKBIsGoodBackupPublic(account->backup_key, &error), exit);
CFErrorRef localError = NULL;
if (!SOSDeleteV0Keybag(&localError)) {
secerror("Failed to delete v0 keybag: %@", localError);
}
CFReleaseNull(localError);
result = true;
SOSAccountForEachBackupView(account, ^(const void *value) {
CFStringRef viewName = (CFStringRef)value;
result &= SOSAccountNewBKSBForView(account, viewName, &error);
});
exit:
if (!result) {
secnotice("backupkey", "Failed to setup backup public key: %@", error ? (CFTypeRef) error : (CFTypeRef) CFSTR("No error space provided"));
}
CFReleaseNull(backupKey);
return result;
}
static bool SOSAccountJoinCircle(SOSAccountTransactionRef aTxn, SecKeyRef user_key,
bool use_cloud_peer, CFErrorRef* error) {
SOSAccountRef account = aTxn->account;
__block bool result = false;
__block SOSFullPeerInfoRef cloud_full_peer = NULL;
require_action_quiet(account->trusted_circle, fail, SOSCreateErrorWithFormat(kSOSErrorPeerNotFound, NULL, error, NULL, CFSTR("Don't have circle when joining???")));
require_quiet(SOSAccountEnsureFullPeerAvailable(account, error), fail);
SOSFullPeerInfoRef myCirclePeer = account->my_identity;
if (SOSCircleCountPeers(account->trusted_circle) == 0) {
secnotice("resetToOffering", "Resetting circle to offering since there are no peers");
result = SOSAccountResetCircleToOffering(aTxn, user_key, error);
} else {
SOSAccountSetValue(account, kSOSUnsyncedViewsKey, kCFBooleanTrue, NULL);
if (use_cloud_peer) {
cloud_full_peer = SOSCircleCopyiCloudFullPeerInfoRef(account->trusted_circle, NULL);
}
SOSAccountModifyCircle(account, error, ^(SOSCircleRef circle) {
result = SOSAccountAddEscrowToPeerInfo(account, myCirclePeer, error);
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);
}
}
return result;
});
if (use_cloud_peer) {
SOSAccountUpdateOutOfSyncViews(aTxn, SOSViewsGetAllCurrent());
}
}
fail:
CFReleaseNull(cloud_full_peer);
return result;
}
static bool SOSAccountJoinCircles_internal(SOSAccountTransactionRef aTxn, bool use_cloud_identity, CFErrorRef* error) {
SOSAccountRef account = aTxn->account;
bool success = false;
SecKeyRef user_key = SOSAccountGetPrivateCredential(account, error);
require_quiet(user_key, done);
require_action_quiet(account->trusted_circle, done, SOSErrorCreate(kSOSErrorNoCircle, error, NULL, CFSTR("No circle to join")));
if (account->my_identity != NULL) {
SOSPeerInfoRef myPeer = SOSFullPeerInfoGetPeerInfo(account->my_identity);
success = SOSCircleHasPeer(account->trusted_circle, myPeer, NULL);
require_quiet(!success, done);
SOSCircleRemoveRejectedPeer(account->trusted_circle, myPeer, NULL);
if (!SOSCircleHasApplicant(account->trusted_circle, myPeer, NULL)) {
secerror("Resetting my peer (ID: %@) for circle '%@' during application", SOSPeerInfoGetPeerID(myPeer), SOSCircleGetName(account->trusted_circle));
CFReleaseNull(account->my_identity);
myPeer = NULL;
}
}
success = SOSAccountJoinCircle(aTxn, user_key, use_cloud_identity, error);
require_quiet(success, done);
account->departure_code = kSOSNeverLeftCircle;
done:
return success;
}
bool SOSAccountJoinCircles(SOSAccountTransactionRef aTxn, CFErrorRef* error) {
return SOSAccountJoinCircles_internal(aTxn, false, error);
}
CFStringRef SOSAccountCopyDeviceID(SOSAccountRef account, CFErrorRef *error){
CFStringRef result = NULL;
require_action_quiet(account->my_identity, fail, SOSErrorCreate(kSOSErrorPeerNotFound, error, NULL, CFSTR("No peer for me")));
result = SOSPeerInfoCopyDeviceID(SOSFullPeerInfoGetPeerInfo(account->my_identity));
fail:
return result;
}
bool SOSAccountSetMyDSID(SOSAccountRef account, CFStringRef IDS, CFErrorRef* error){
bool result = true;
secdebug("IDS Transport", "We are setting our device ID: %@", IDS);
if(IDS != NULL && (CFStringGetLength(IDS) > 0)){
require_action_quiet(account->my_identity, fail, SOSErrorCreate(kSOSErrorPeerNotFound, error, NULL, CFSTR("No peer for me")));
result = SOSAccountModifyCircle(account, error, ^bool(SOSCircleRef circle) {
SOSFullPeerInfoUpdateDeviceID(account->my_identity, IDS, error);
SOSFullPeerInfoUpdateTransportType(account->my_identity, SOSTransportMessageTypeIDSV2, error);
SOSFullPeerInfoUpdateTransportPreference(account->my_identity, kCFBooleanFalse, error);
SOSFullPeerInfoUpdateTransportFragmentationPreference(account->my_identity, kCFBooleanTrue, error);
return SOSCircleHasPeer(circle, SOSFullPeerInfoGetPeerInfo(account->my_identity), NULL);
});
}
else
result = false;
SOSCCSyncWithAllPeers();
fail:
CFReleaseNull(account->deviceID);
account->deviceID = CFRetainSafe(IDS);
return result;
}
bool SOSAccountSendIDSTestMessage(SOSAccountRef account, CFStringRef message, CFErrorRef *error){
bool result = true;
CFMutableDictionaryRef circleToPeerMessages = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault);
CFMutableDictionaryRef peerToMessage = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault);
char *messageCharStar;
asprintf(&messageCharStar, "%d", kIDSSendOneMessage);
CFStringRef messageString = CFStringCreateWithCString(kCFAllocatorDefault, messageCharStar, kCFStringEncodingUTF8);
CFMutableDictionaryRef mutableDictionary = CFDictionaryCreateMutableForCFTypesWith(kCFAllocatorDefault, kIDSOperationType, messageString, kIDSMessageToSendKey, CFSTR("send IDS test message"), NULL);
SOSCircleForEachPeer(account->trusted_circle, ^(SOSPeerInfoRef peer) {
if(!CFEqualSafe(peer, SOSAccountGetMyPeerInfo(account)))
CFDictionaryAddValue(peerToMessage, SOSPeerInfoGetPeerID(peer), mutableDictionary);
});
CFDictionaryAddValue(circleToPeerMessages, SOSCircleGetName(account->trusted_circle), peerToMessage);
result = SOSTransportMessageSendMessages(account->ids_message_transport, circleToPeerMessages, error);
CFReleaseNull(mutableDictionary);
CFReleaseNull(peerToMessage);
CFReleaseNull(circleToPeerMessages);
CFReleaseNull(messageString);
free(messageCharStar);
return result;
}
bool SOSAccountStartPingTest(SOSAccountRef account, CFStringRef message, CFErrorRef *error){
bool result = false;
if(account->ids_message_transport == NULL)
account->ids_message_transport = (SOSTransportMessageRef)SOSTransportMessageIDSCreate(account, SOSCircleGetName(account->trusted_circle), error);
require_quiet(account->ids_message_transport, fail);
CFMutableDictionaryRef circleToPeerMessages = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault);
CFMutableDictionaryRef peerToMessage = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault);
char *messageCharStar;
asprintf(&messageCharStar, "%d", kIDSStartPingTestMessage);
CFStringRef operationToString = CFStringCreateWithCString(kCFAllocatorDefault, messageCharStar, kCFStringEncodingUTF8);
CFStringRef messageToSend = CFSTR("send IDS test message");
CFMutableDictionaryRef mutableDictionary = CFDictionaryCreateMutableForCFTypesWith(kCFAllocatorDefault, kIDSOperationType, operationToString, kIDSMessageToSendKey, messageToSend, NULL);
SOSCircleForEachPeer(account->trusted_circle, ^(SOSPeerInfoRef peer) {
if(CFStringCompare(SOSAccountGetMyPeerID(account), SOSPeerInfoGetPeerID(peer), 0) != 0)
CFDictionaryAddValue(peerToMessage, SOSPeerInfoGetPeerID(peer), mutableDictionary);
});
CFDictionaryAddValue(circleToPeerMessages, SOSCircleGetName(account->trusted_circle), peerToMessage);
result = SOSTransportMessageSendMessages(account->ids_message_transport, circleToPeerMessages, error);
CFReleaseNull(mutableDictionary);
CFReleaseNull(peerToMessage);
CFReleaseNull(circleToPeerMessages);
CFReleaseNull(operationToString);
free(messageCharStar);
fail:
return result;
}
bool SOSAccountRetrieveDeviceIDFromIDSKeychainSyncingProxy(SOSAccountRef account, CFErrorRef *error){
bool result = true;
__block bool success = true;
__block CFErrorRef localError = NULL;
dispatch_semaphore_t wait_for = dispatch_semaphore_create(0);
dispatch_retain(wait_for);
SOSCloudKeychainGetIDSDeviceID(^(CFDictionaryRef returnedValues, CFErrorRef sync_error){
success = (sync_error == NULL);
if (!success) {
CFRetainAssign(localError, sync_error);
}
dispatch_semaphore_signal(wait_for);
dispatch_release(wait_for);
});
dispatch_semaphore_wait(wait_for, DISPATCH_TIME_FOREVER);
dispatch_release(wait_for);
if(!success && localError != NULL && error != NULL){
secerror("Could not ask IDSKeychainSyncingProxy for Device ID: %@", localError);
*error = localError;
result = false;
}
else{
secdebug("IDS Transport", "Attempting to retrieve the IDS Device ID");
}
return result;
}
bool SOSAccountJoinCirclesAfterRestore(SOSAccountTransactionRef aTxn, CFErrorRef* error) {
return SOSAccountJoinCircles_internal(aTxn, true, error);
}
bool SOSAccountLeaveCircle(SOSAccountRef account, CFErrorRef* error)
{
bool result = true;
secnotice("leaveCircle", "Leaving circle by client request");
result &= SOSAccountModifyCircle(account, error, ^(SOSCircleRef circle) {
return sosAccountLeaveCircle(account, circle, error);
});
account->departure_code = kSOSWithdrewMembership;
return result;
}
bool SOSAccountRemovePeersFromCircle(SOSAccountRef account, CFArrayRef peers, CFErrorRef* error)
{
SecKeyRef user_key = SOSAccountGetPrivateCredential(account, error);
if (!user_key)
return false;
bool result = true;
CFMutableSetRef peersToRemove = CFSetCreateMutableForSOSPeerInfosByIDWithArray(kCFAllocatorDefault, peers);
bool leaveCircle = CFSetContainsValue(peersToRemove, SOSAccountGetMyPeerInfo(account));
CFSetRemoveValue(peersToRemove, SOSAccountGetMyPeerInfo(account));
result &= SOSAccountModifyCircle(account, error, ^(SOSCircleRef circle) {
bool success = false;
if(CFSetGetCount(peersToRemove) != 0) {
require_quiet(SOSCircleRemovePeers(circle, user_key, SOSAccountGetMyFullPeerInfo(account), peersToRemove, error), done);
success = SOSAccountGenerationSignatureUpdate(account, error);
} else success = true;
if (success && leaveCircle) {
secnotice("leaveCircle", "Leaving circle by client request");
success = sosAccountLeaveCircle(account, circle, error);
}
done:
return success;
});
return 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, ^{
SOSAccountModifyCircle(account, error, ^(SOSCircleRef circle) {
secnotice("leaveCircle", "Leaving circle by client request");
return sosAccountLeaveCircle(account, circle, error);
});
});
dispatch_time_t milestone = dispatch_time(DISPATCH_TIME_NOW, limit_in_seconds * NSEC_PER_SEC);
dispatch_group_wait(group, milestone);
account->departure_code = kSOSWithdrewMembership;
dispatch_release(group);
return result;
}
static void for_each_applicant_in_each_circle(SOSAccountRef account, CFArrayRef peer_infos,
bool (^action)(SOSCircleRef circle, SOSFullPeerInfoRef myCirclePeer, SOSPeerInfoRef peer)) {
SOSPeerInfoRef me = SOSFullPeerInfoGetPeerInfo(account->my_identity);
CFErrorRef peer_error = NULL;
if (account->trusted_circle && me &&
SOSCircleHasPeer(account->trusted_circle, me, &peer_error)) {
SOSAccountModifyCircle(account, NULL, ^(SOSCircleRef circle) {
__block bool modified = false;
CFArrayForEach(peer_infos, ^(const void *value) {
SOSPeerInfoRef peer = (SOSPeerInfoRef) value;
if (isSOSPeerInfo(peer) && SOSCircleHasApplicant(circle, peer, NULL)) {
if (action(circle, account->my_identity, peer)) {
modified = true;
}
}
});
return modified;
});
}
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) {
bool accepted = SOSCircleAcceptRequest(circle, user_key, myCirclePeer, peer, error);
if (!accepted)
success = false;
else
num_peers = MAX(num_peers, SOSCircleCountPeers(circle));
return accepted;
});
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) {
bool rejected = SOSCircleRejectRequest(circle, myCirclePeer, peer, error);
if (!rejected)
success = false;
else
num_peers = MAX(num_peers, SOSCircleCountPeers(circle));
return rejected;
});
return success;
}
CFStringRef SOSAccountCopyIncompatibilityInfo(SOSAccountRef account, CFErrorRef* error) {
return CFSTR("We're compatible, go away");
}
enum DepartureReason SOSAccountGetLastDepartureReason(SOSAccountRef account, CFErrorRef* error) {
return account->departure_code;
}
void SOSAccountSetLastDepartureReason(SOSAccountRef account, enum DepartureReason reason) {
account->departure_code = reason;
}
CFArrayRef SOSAccountCopyGeneration(SOSAccountRef account, CFErrorRef *error) {
CFArrayRef result = NULL;
CFNumberRef generation = NULL;
require_quiet(SOSAccountHasPublicKey(account, error), fail);
require_action_quiet(account->trusted_circle, fail, SOSErrorCreate(kSOSErrorNoCircle, error, NULL, CFSTR("No circle")));
generation = (CFNumberRef)SOSCircleGetGeneration(account->trusted_circle);
result = CFArrayCreateForCFTypes(kCFAllocatorDefault, generation, NULL);
fail:
return result;
}
bool SOSValidateUserPublic(SOSAccountRef account, CFErrorRef *error) {
if (!SOSAccountHasPublicKey(account, error))
return NULL;
return account->user_public_trusted;
}
bool SOSAccountEnsurePeerRegistration(SOSAccountRef account, CFErrorRef *error) {
bool result = true;
secnotice("updates", "Ensuring peer registration.");
require_quiet(account->trusted_circle, done);
require_quiet(account->my_identity, done);
require_quiet(account->user_public_trusted, done);
require_quiet(SOSAccountIsMyPeerActive(account, NULL), done);
CFStringRef my_id = SOSPeerInfoGetPeerID(SOSFullPeerInfoGetPeerInfo(account->my_identity));
SOSCircleForEachValidSyncingPeer(account->trusted_circle, account->user_public, ^(SOSPeerInfoRef peer) {
if (!SOSPeerInfoPeerIDEqual(peer, my_id)) {
CFErrorRef localError = NULL;
SOSTransportMessageRef messageTransport = NULL;
messageTransport = SOSPeerInfoHasDeviceID(peer) ? account->ids_message_transport : account->kvs_message_transport;
SOSEngineInitializePeerCoder(messageTransport->engine, account->my_identity, peer, &localError);
if (localError)
secnotice("updates", "can't initialize transport for peer %@ with %@ (%@)", peer, account->my_identity, localError);
CFReleaseSafe(localError);
}
});
SOSTransportMessageIDSGetIDSDeviceID(account);
done:
return result;
}
static inline bool SOSAccountEnsureExpansion(SOSAccountRef account, CFErrorRef *error) {
if (!account->expansion) {
account->expansion = CFDictionaryCreateMutableForCFTypes(NULL);
}
return SecAllocationError(account->expansion, error, CFSTR("Can't Alloc Account Expansion dictionary"));
}
bool SOSAccountClearValue(SOSAccountRef account, CFStringRef key, CFErrorRef *error) {
bool success = SOSAccountEnsureExpansion(account, error);
require_quiet(success, errOut);
CFDictionaryRemoveValue(account->expansion, key);
errOut:
return success;
}
bool SOSAccountSetValue(SOSAccountRef account, CFStringRef key, CFTypeRef value, CFErrorRef *error) {
if (value == NULL) return SOSAccountClearValue(account, key, error);
bool success = SOSAccountEnsureExpansion(account, error);
require_quiet(success, errOut);
CFDictionarySetValue(account->expansion, key, value);
errOut:
return success;
}
CFTypeRef SOSAccountGetValue(SOSAccountRef account, CFStringRef key, CFErrorRef *error) {
if (!account->expansion) {
return NULL;
}
return CFDictionaryGetValue(account->expansion, key);
}
bool SOSAccountAddEscrowRecords(SOSAccountRef account, CFStringRef dsid, CFDictionaryRef record, CFErrorRef *error){
CFMutableDictionaryRef escrowRecords = (CFMutableDictionaryRef)SOSAccountGetValue(account, kSOSEscrowRecord, error);
CFMutableDictionaryRef escrowCopied = NULL;
bool success = false;
if(isDictionary(escrowRecords) && escrowRecords != NULL)
escrowCopied = CFDictionaryCreateMutableCopy(kCFAllocatorDefault, CFDictionaryGetCount(escrowRecords), escrowRecords);
else
escrowCopied = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault);
CFDictionaryAddValue(escrowCopied, dsid, record);
SOSAccountSetValue(account, kSOSEscrowRecord, escrowCopied, error);
if(*error == NULL)
success = true;
CFReleaseNull(escrowCopied);
return success;
}
bool SOSAccountAddEscrowToPeerInfo(SOSAccountRef account, SOSFullPeerInfoRef myPeer, CFErrorRef *error){
bool success = false;
CFDictionaryRef escrowRecords = SOSAccountGetValue(account, kSOSEscrowRecord, error);
success = SOSFullPeerInfoReplaceEscrowRecords(myPeer, escrowRecords, error);
return success;
}
bool SOSAccountCheckPeerAvailability(SOSAccountRef account, CFErrorRef *error)
{
CFMutableDictionaryRef circleToPeerMessages = NULL;
CFStringRef operationTypeAsString = NULL;
CFMutableDictionaryRef mutableDictionary = NULL;
CFMutableSetRef peers = NULL;
CFMutableDictionaryRef peerList = NULL;
char* message = NULL;
bool result = false;
if(account->ids_message_transport == NULL)
account->ids_message_transport = (SOSTransportMessageRef)SOSTransportMessageIDSCreate(account, SOSCircleGetName(account->trusted_circle), error);
require_quiet(account->ids_message_transport, fail);
circleToPeerMessages = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault);
asprintf(&message, "%d", kIDSPeerAvailability);
operationTypeAsString = CFStringCreateWithCString(kCFAllocatorDefault, message, kCFStringEncodingUTF8);
mutableDictionary = CFDictionaryCreateMutableForCFTypesWith(kCFAllocatorDefault, kIDSOperationType, operationTypeAsString, kIDSMessageToSendKey, CFSTR("checking peers"), NULL);
peers = SOSCircleCopyPeers(account->trusted_circle, kCFAllocatorDefault);
require_quiet(CFSetGetCount(peers) > 0, fail);
CFReleaseNull(peers);
peerList = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault);
SOSCircleRef circle = account->trusted_circle;
CFSetRef mySubSet = SOSViewsGetV0SubviewSet();
SOSCircleForEachValidPeer(circle, account->user_public, ^(SOSPeerInfoRef peer) {
if(!CFEqualSafe(peer, SOSAccountGetMyPeerInfo(account))){
CFMutableSetRef peerViews = SOSPeerInfoCopyEnabledViews(peer);
CFSetRef intersectSets = CFSetCreateIntersection(kCFAllocatorDefault, mySubSet, peerViews);
if(CFEqualSafe(intersectSets, mySubSet)){
CFStringRef deviceID = SOSPeerInfoCopyDeviceID(peer);
if(deviceID != NULL)
CFDictionaryAddValue(peerList, SOSPeerInfoGetPeerID(peer), mutableDictionary);
CFReleaseNull(deviceID);
}
CFReleaseNull(peerViews);
CFReleaseNull(intersectSets);
}
});
require_quiet(CFDictionaryGetCount(peerList) > 0 , fail);
CFDictionaryAddValue(circleToPeerMessages, SOSCircleGetName(account->trusted_circle), peerList);
result = SOSTransportMessageSendMessages(account->ids_message_transport, circleToPeerMessages, error);
fail:
CFReleaseNull(mutableDictionary);
CFReleaseNull(operationTypeAsString);
CFReleaseNull(peerList);
CFReleaseNull(circleToPeerMessages);
CFReleaseNull(peers);
free(message);
return result;
}
void SOSAccountRecordRetiredPeersInCircle(SOSAccountRef account) {
if (!SOSAccountIsInCircle(account, NULL))
return;
SOSAccountModifyCircle(account, NULL, ^bool (SOSCircleRef circle) {
__block bool updated = false;
CFSetForEach(account->retirees, ^(CFTypeRef element){
SOSPeerInfoRef retiree = asSOSPeerInfo(element);
if (retiree && SOSCircleUpdatePeerInfo(circle, retiree)) {
updated = true;
secnotice("retirement", "Updated retired peer %@ in %@", retiree, circle);
CFErrorRef cleanupError = NULL;
if (!SOSAccountCleanupAfterPeer(account, RETIREMENT_FINALIZATION_SECONDS, circle, retiree, &cleanupError))
secerror("Error cleanup up after peer (%@): %@", retiree, cleanupError);
CFReleaseSafe(cleanupError);
}
});
return updated;
});
}
static size_t SOSPiggyBackBlobGetDEREncodedSize(SOSGenCountRef gencount, SecKeyRef pubKey, CFDataRef signature, CFErrorRef *error) {
size_t total_payload = 0;
CFDataRef publicBytes = NULL;
OSStatus result = SecKeyCopyPublicBytes(pubKey, &publicBytes);
if (result != errSecSuccess) {
SOSCreateError(kSOSErrorBadKey, CFSTR("Failed to export public bytes"), NULL, error);
return 0;
}
require_quiet(accumulate_size(&total_payload, der_sizeof_number(gencount, error)), errOut);
require_quiet(accumulate_size(&total_payload, der_sizeof_data_or_null(publicBytes, error)), errOut);
require_quiet(accumulate_size(&total_payload, der_sizeof_data_or_null(signature, error)), errOut);
return ccder_sizeof(CCDER_CONSTRUCTED_SEQUENCE, total_payload);
errOut:
SecCFDERCreateError(kSecDERErrorUnknownEncoding, CFSTR("don't know how to encode"), NULL, error);
return 0;
}
static uint8_t* SOSPiggyBackBlobEncodeToDER(SOSGenCountRef gencount, SecKeyRef pubKey, CFDataRef signature, CFErrorRef* error, const uint8_t* der, uint8_t* der_end) {
CFDataRef publicBytes = NULL;
OSStatus result = SecKeyCopyPublicBytes(pubKey, &publicBytes);
if (result != errSecSuccess) {
SOSCreateError(kSOSErrorBadKey, CFSTR("Failed to export public bytes"), NULL, error);
return NULL;
}
der_end = ccder_encode_constructed_tl(CCDER_CONSTRUCTED_SEQUENCE, der_end, der,
der_encode_number(gencount, error, der,
der_encode_data_or_null(publicBytes, error, der,
der_encode_data_or_null(signature, error, der, der_end))));
return der_end;
}
static CFDataRef SOSPiggyBackBlobCopyEncodedData(SOSGenCountRef gencount, SecKeyRef pubKey, CFDataRef signature, CFAllocatorRef allocator, CFErrorRef *error)
{
return CFDataCreateWithDER(kCFAllocatorDefault, SOSPiggyBackBlobGetDEREncodedSize(gencount, pubKey, signature, error), ^uint8_t*(size_t size, uint8_t *buffer) {
return SOSPiggyBackBlobEncodeToDER(gencount, pubKey, signature, error, buffer, (uint8_t *) buffer + size);
});
}
struct piggyBackBlob {
SOSGenCountRef gencount;
SecKeyRef pubKey;
CFDataRef signature;
};
static struct piggyBackBlob *SOSPiggyBackBlobCreateFromDER(CFAllocatorRef allocator, CFErrorRef *error,
const uint8_t** der_p, const uint8_t *der_end) {
const uint8_t *sequence_end;
struct piggyBackBlob *retval = NULL;
SOSGenCountRef gencount = NULL;
CFDataRef signature = NULL;
CFDataRef publicBytes = NULL;
*der_p = ccder_decode_constructed_tl(CCDER_CONSTRUCTED_SEQUENCE, &sequence_end, *der_p, der_end);
require_action_quiet(sequence_end != NULL, errOut,
SOSCreateError(kSOSErrorBadFormat, CFSTR("Bad Blob DER"), (error != NULL) ? *error : NULL, error));
*der_p = der_decode_number(allocator, 0, &gencount, error, *der_p, sequence_end);
*der_p = der_decode_data_or_null(kCFAllocatorDefault, &publicBytes, error, *der_p, der_end);
*der_p = der_decode_data_or_null(kCFAllocatorDefault, &signature, error, *der_p, der_end);
require_action_quiet(*der_p && *der_p == der_end, errOut,
SOSCreateError(kSOSErrorBadFormat, CFSTR("Didn't consume all bytes for pbblob"), (error != NULL) ? *error : NULL, error));
retval = malloc(sizeof(struct piggyBackBlob));
retval->gencount = gencount;
retval->signature = signature;
retval->pubKey = SecKeyCreateFromPublicData(kCFAllocatorDefault, kSecECDSAAlgorithmID, publicBytes);
errOut:
if(!retval) {
CFReleaseNull(gencount);
CFReleaseNull(publicBytes);
CFReleaseNull(signature);
}
return retval;
}
static struct piggyBackBlob *SOSPiggyBackBlobCreateFromData(CFAllocatorRef allocator, CFDataRef blobData, CFErrorRef *error)
{
size_t size = CFDataGetLength(blobData);
const uint8_t *der = CFDataGetBytePtr(blobData);
struct piggyBackBlob *inflated = SOSPiggyBackBlobCreateFromDER(allocator, error, &der, der + size);
return inflated;
}
SOSPeerInfoRef SOSAccountCopyApplication(SOSAccountRef account, CFErrorRef* error) {
SOSPeerInfoRef applicant = NULL;
SecKeyRef userKey = SOSAccountGetPrivateCredential(account, error);
if(!userKey) return false;
require_quiet(SOSAccountEnsureFullPeerAvailable(account, error), errOut);
require(SOSFullPeerInfoPromoteToApplication(account->my_identity, userKey, error), errOut);
applicant = SOSPeerInfoCreateCopy(kCFAllocatorDefault, (SOSFullPeerInfoGetPeerInfo(account->my_identity)), error);
errOut:
return applicant;
}
CFDataRef SOSAccountCopyCircleJoiningBlob(SOSAccountRef account, SOSPeerInfoRef applicant, CFErrorRef *error) {
SOSGenCountRef gencount = NULL;
CFDataRef signature = NULL;
SecKeyRef ourKey = NULL;
CFDataRef pbblob = NULL;
SecKeyRef userKey = SOSAccountGetTrustedPublicCredential(account, error);
require_quiet(userKey, errOut);
require_action_quiet(applicant, errOut, SOSCreateError(kSOSErrorProcessingFailure, CFSTR("No applicant provided"), (error != NULL) ? *error : NULL, error));
require_quiet(SOSPeerInfoApplicationVerify(applicant, userKey, error), errOut);
{
SOSFullPeerInfoRef fpi = SOSAccountGetMyFullPeerInfo(account);
ourKey = SOSFullPeerInfoCopyDeviceKey(fpi, error);
require_quiet(ourKey, errOut);
}
SOSCircleRef currentCircle = SOSAccountGetCircle(account, error);
require_quiet(currentCircle, errOut);
SOSCircleRef prunedCircle = SOSCircleCopyCircle(NULL, currentCircle, error);
require_quiet(prunedCircle, errOut);
require_quiet(SOSCirclePreGenerationSign(prunedCircle, userKey, error), errOut);
gencount = SOSGenerationIncrementAndCreate(SOSCircleGetGeneration(prunedCircle));
signature = SOSCircleCopyNextGenSignatureWithPeerAdded(prunedCircle, applicant, ourKey, error);
require_quiet(signature, errOut);
pbblob = SOSPiggyBackBlobCopyEncodedData(gencount, ourKey, signature, kCFAllocatorDefault, error);
errOut:
CFReleaseNull(gencount);
CFReleaseNull(signature);
CFReleaseNull(ourKey);
return pbblob;
}
bool SOSAccountJoinWithCircleJoiningBlob(SOSAccountRef account, CFDataRef joiningBlob, CFErrorRef *error) {
bool retval = false;
SecKeyRef userKey = NULL;
struct piggyBackBlob *pbb = NULL;
userKey = SOSAccountGetPrivateCredential(account, error);
require_quiet(userKey, errOut);
pbb = SOSPiggyBackBlobCreateFromData(kCFAllocatorDefault, joiningBlob, error);
require_quiet(pbb, errOut);
SOSAccountSetValue(account, kSOSUnsyncedViewsKey, kCFBooleanTrue, NULL);
retval = SOSAccountModifyCircle(account, error, ^bool(SOSCircleRef copyOfCurrent) {
return SOSCircleAcceptPeerFromHSA2(copyOfCurrent, userKey,
pbb->gencount,
pbb->pubKey,
pbb->signature,
account->my_identity, error);;
});
errOut:
if(pbb) {
CFReleaseNull(pbb->gencount);
CFReleaseNull(pbb->pubKey);
CFReleaseNull(pbb->signature);
free(pbb);
}
return retval;
}
static char boolToChars(bool val, char truechar, char falsechar) {
return val? truechar: falsechar;
}
#define ACCOUNTLOGSTATE "accountLogState"
void SOSAccountLogState(SOSAccountRef account) {
bool hasPubKey = account->user_public != NULL;
bool pubTrusted = account->user_public_trusted;
bool hasPriv = account->_user_private != NULL;
SOSCCStatus stat = SOSAccountGetCircleStatus(account, NULL);
CFStringRef userPubKeyID = (account->user_public) ? SOSCopyIDOfKeyWithLength(account->user_public, 8, NULL):
CFStringCreateCopy(kCFAllocatorDefault, CFSTR("*No Key*"));
secnotice(ACCOUNTLOGSTATE, "Start");
secnotice(ACCOUNTLOGSTATE, "ACCOUNT: [keyStatus: %c%c%c hpub %@] [SOSCCStatus: %@]",
boolToChars(hasPubKey, 'U', 'u'), boolToChars(pubTrusted, 'T', 't'), boolToChars(hasPriv, 'I', 'i'),
userPubKeyID,
SOSAccountGetSOSCCStatusString(stat)
);
CFReleaseNull(userPubKeyID);
if(account->trusted_circle) SOSCircleLogState(ACCOUNTLOGSTATE, account->trusted_circle, account->user_public, SOSAccountGetMyPeerID(account));
else secnotice(ACCOUNTLOGSTATE, "ACCOUNT: No Circle");
}
void SOSAccountLogViewState(SOSAccountRef account) {
bool isInCircle = SOSAccountIsInCircle(account, NULL);
require_quiet(isInCircle, imOut);
SOSPeerInfoRef mpi = SOSAccountGetMyPeerInfo(account);
bool isInitialComplete = SOSAccountHasCompletedInitialSync(account);
bool isBackupComplete = SOSAccountHasCompletedRequiredBackupSync(account);
CFSetRef views = mpi ? SOSPeerInfoCopyEnabledViews(mpi) : NULL;
CFStringSetPerformWithDescription(views, ^(CFStringRef description) {
secnotice(ACCOUNTLOGSTATE, "Sync: %c%c PeerViews: %@",
boolToChars(isInitialComplete, 'I', 'i'),
boolToChars(isBackupComplete, 'B', 'b'),
description);
});
CFReleaseNull(views);
CFSetRef unsyncedViews = SOSAccountCopyOutstandingViews(account);
CFStringSetPerformWithDescription(views, ^(CFStringRef description) {
secnotice(ACCOUNTLOGSTATE, "outstanding views: %@", description);
});
CFReleaseNull(unsyncedViews);
imOut:
secnotice(ACCOUNTLOGSTATE, "Finish");
return;
}