SOSAccountUpdate.c   [plain text]


//
//  SOSAccountUpdate.c
//  sec
//

#include "SOSAccountPriv.h"
#include <Security/SecureObjectSync/SOSAccountHSAJoin.h>
#include <Security/SecureObjectSync/SOSTransportCircle.h>
#include <Security/SecureObjectSync/SOSTransport.h>
#include <Security/SecureObjectSync/SOSViews.h>
#include <Security/SecureObjectSync/SOSPeerInfoCollections.h>
#include <Security/SecureObjectSync/SOSPeerInfoPriv.h>
#include <Security/SecureObjectSync/SOSPeerInfoV2.h>
#include <Security/SecureObjectSync/SOSPeerInfoDER.h>
#include <Security/SecureObjectSync/SOSBackupSliceKeyBag.h>

static void DifferenceAndCall(CFSetRef old_members, CFSetRef new_members, void (^updatedCircle)(CFSetRef additions, CFSetRef removals))
{
    CFMutableSetRef additions = CFSetCreateMutableCopy(kCFAllocatorDefault, 0, new_members);
    CFMutableSetRef removals = CFSetCreateMutableCopy(kCFAllocatorDefault, 0, old_members);
    
    
    CFSetForEach(old_members, ^(const void * value) {
        CFSetRemoveValue(additions, value);
    });
    
    CFSetForEach(new_members, ^(const void * value) {
        CFSetRemoveValue(removals, value);
    });
    
    updatedCircle(additions, removals);
    
    CFReleaseSafe(additions);
    CFReleaseSafe(removals);
}

static CFMutableSetRef SOSAccountCopyIntersectedViews(CFSetRef peerViews, CFSetRef myViews) {
    __block CFMutableSetRef views = CFSetCreateMutable(NULL, 0, &kCFTypeSetCallBacks);
    if (peerViews && myViews) CFSetForEach(peerViews, ^(const void *view) {
        if (CFSetContainsValue(myViews, view)) {
            CFSetAddValue(views, view);
        }
    });
    return views;
}

static inline bool isSyncing(SOSPeerInfoRef peer, SecKeyRef upub) {
    if(!SOSPeerInfoApplicationVerify(peer, upub, NULL)) return false;
    if(SOSPeerInfoIsRetirementTicket(peer)) return false;
    return true;
}

static bool isBackupSOSRing(SOSRingRef ring)
{
    return isSOSRing(ring) && (kSOSRingBackup == SOSRingGetType(ring));
}

static bool CFSetIntersectionNotEmpty(CFSetRef set1, CFSetRef set2) {
    __block bool intersectionEmpty = true;
    CFSetForEach(set1, ^(const void *value) {
        if (CFSetContainsValue(set2, value)) {
            intersectionEmpty = false;
        };
    });
    return !intersectionEmpty;
}

__unused
static void SOSAccountAppendPeerMetasForViewBackups(SOSAccountRef account, CFSetRef views, CFMutableArrayRef appendTo)
{
    if (account->trusted_rings == NULL || CFDictionaryGetCount(account->trusted_rings) == 0) return;

    CFMutableDictionaryRef ringToViewTable = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault);

    CFSetForEach(views, ^(const void *value) {
        CFStringRef viewName = value;
        if (isString(viewName) && !CFEqualSafe(viewName, kSOSViewKeychainV0)) {
            CFStringRef ringName = SOSBackupCopyRingNameForView(viewName);
            viewName = ringName;
            SOSRingRef ring = (SOSRingRef) CFDictionaryGetValue(account->trusted_rings, ringName);

            if (isBackupSOSRing(ring)) {
                CFTypeRef currentValue = (CFTypeRef) CFDictionaryGetValue(ringToViewTable, ring);

                if (isSet(currentValue)) {
                    CFSetAddValue((CFMutableSetRef)currentValue, viewName);
                } else {
                    CFMutableSetRef viewNameSet = CFSetCreateMutableForCFTypes(kCFAllocatorDefault);
                    CFSetAddValue(viewNameSet, viewName);

                    CFDictionarySetValue(ringToViewTable, ring, viewNameSet);
                    CFReleaseNull(viewNameSet);
                }
            } else {
                secwarning("View '%@' not being backed up – ring %@:%@ not backup ring.", viewName, ringName, ring);
            }
            CFReleaseNull(ringName);
        }
    });

    CFSetRef unsynced = asSet(SOSAccountGetValue(account, kSOSUnsyncedViewsKey, NULL), NULL);

    CFDictionaryForEach(ringToViewTable, ^(const void *key, const void *value) {
        SOSRingRef ring = (SOSRingRef) key;
        CFSetRef viewNames = (CFSetRef) value;
        if (isSOSRing(ring) && isSet(viewNames)) {
            if (unsynced && CFSetIntersectionNotEmpty(unsynced, viewNames)) {
                secnotice("engine-notify", "Haven't initially synced views, not making backup peer meta: U: %@ R: %@ Vs: %@", unsynced, SOSRingGetName(ring), viewNames);
            } else {
                bool meta_added = false;
                CFErrorRef create_error = NULL;
                SOSBackupSliceKeyBagRef key_bag = NULL;
                SOSPeerMetaRef newMeta = NULL;

                CFDataRef ring_payload = SOSRingGetPayload(ring, NULL);
                require_quiet(isData(ring_payload), skip);

                key_bag = SOSBackupSliceKeyBagCreateFromData(kCFAllocatorDefault, ring_payload, &create_error);
                require_quiet(key_bag, skip);

                newMeta = SOSPeerMetaCreateWithComponents(SOSRingGetName(ring), viewNames, ring_payload);
                require_quiet(SecAllocationError(newMeta, &create_error, CFSTR("Didn't make peer meta for: %@"), ring), skip);
                CFArrayAppendValue(appendTo, newMeta);

                secnotice("engine-notify", "Backup peer meta: R: %@ Vs: %@ VD: %@", SOSRingGetName(ring), viewNames, ring_payload);

                meta_added = true;

            skip:
                if (!meta_added) {
                    secerror("Failed to register backup meta from %@ for views %@. Error (%@)", ring, viewNames, create_error);
                }
                CFReleaseNull(newMeta);
                CFReleaseNull(key_bag);
                CFReleaseNull(create_error);
            }
        }
    });

    CFReleaseNull(ringToViewTable);
}

bool SOSAccountSyncingV0(SOSAccountRef account) {
    __block bool syncingV0 = false;
    SOSAccountForEachCirclePeerExceptMe(account, ^(SOSPeerInfoRef peer) {
        if (SOSPeerInfoIsEnabledView(peer, kSOSViewKeychainV0)) {
            syncingV0 = true;
        }
    });

    return syncingV0;
}

void SOSAccountNotifyEngines(SOSAccountRef account)
{
    SOSPeerInfoRef myPi = SOSFullPeerInfoGetPeerInfo(account->my_identity);
    CFStringRef myPi_id = SOSPeerInfoGetPeerID(myPi);
    CFMutableArrayRef syncing_peer_metas = NULL;
    CFMutableArrayRef zombie_peer_metas = NULL;
    CFErrorRef localError = NULL;
    SOSPeerMetaRef myMeta = NULL;

    if (myPi_id && isSyncing(myPi, account->user_public) && SOSCircleHasPeer(account->trusted_circle, myPi, NULL)) {
        CFMutableSetRef myViews = SOSPeerInfoCopyEnabledViews(myPi);

        // We add V0 views to everyone if we see a V0 peer, or a peer with the view explicity enabled
        // V2 peers shouldn't be explicity enabling the uber V0 view, though the seeds did.
        __block bool addV0Views = SOSAccountSyncingV0(account);

        syncing_peer_metas = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault);
        zombie_peer_metas = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault);
        SOSAccountForEachCirclePeerExceptMe(account, ^(SOSPeerInfoRef peer) {
            CFMutableArrayRef arrayToAddTo = isSyncing(peer, account->user_public) ? syncing_peer_metas : zombie_peer_metas;

            // Compute views each peer is in that we are also in ourselves
            CFMutableSetRef peerEnabledViews = SOSPeerInfoCopyEnabledViews(peer);
            CFMutableSetRef views = SOSAccountCopyIntersectedViews(peerEnabledViews, myViews);
            CFReleaseNull(peerEnabledViews);

            if(addV0Views) {
                CFSetAddValue(views, kSOSViewKeychainV0);
            }

            SOSPeerMetaRef peerMeta = SOSPeerMetaCreateWithComponents(SOSPeerInfoGetPeerID(peer), views, NULL);
            CFReleaseNull(views);

            CFArrayAppendValue(arrayToAddTo, peerMeta);
            CFReleaseNull(peerMeta);
        });

        // We don't make a backup peer for the magic V0 peer, so do it before we munge the set.
        SOSAccountAppendPeerMetasForViewBackups(account, myViews, syncing_peer_metas);

        // If we saw someone else needing V0, we sync V0, too!
        if (addV0Views) {
            CFSetAddValue(myViews, kSOSViewKeychainV0);
        }

        myMeta = SOSPeerMetaCreateWithComponents(myPi_id, myViews, NULL);
        CFReleaseSafe(myViews);
    }

    SOSEngineRef engine = SOSDataSourceFactoryGetEngineForDataSourceName(account->factory, SOSCircleGetName(account->trusted_circle), NULL);
    if (engine) {
        SOSEngineCircleChanged(engine, myMeta, syncing_peer_metas, zombie_peer_metas);
    }

    CFReleaseNull(myMeta);
    CFReleaseSafe(localError);
    CFReleaseNull(syncing_peer_metas);
    CFReleaseNull(zombie_peer_metas);
}

// murf Upcoming call to View Changes Here
static void SOSAccountNotifyOfChange(SOSAccountRef account, SOSCircleRef oldCircle, SOSCircleRef newCircle)
{
    account->circle_rings_retirements_need_attention = true;

    CFMutableSetRef old_members = SOSCircleCopyPeers(oldCircle, kCFAllocatorDefault);
    CFMutableSetRef new_members = SOSCircleCopyPeers(newCircle, kCFAllocatorDefault);
    
    CFMutableSetRef old_applicants = SOSCircleCopyApplicants(oldCircle, kCFAllocatorDefault);
    CFMutableSetRef new_applicants = SOSCircleCopyApplicants(newCircle, kCFAllocatorDefault);

    SOSPeerInfoRef me = SOSAccountGetMyPeerInfo(account);
    if(me && CFSetContainsValue(new_members, me))
        SOSAccountSetValue(account, kSOSEscrowRecord, kCFNull, NULL); //removing the escrow records from the account object

    DifferenceAndCall(old_members, new_members, ^(CFSetRef added_members, CFSetRef removed_members) {
        DifferenceAndCall(old_applicants, new_applicants, ^(CFSetRef added_applicants, CFSetRef removed_applicants) {
            CFArrayForEach(account->change_blocks, ^(const void * notificationBlock) {
                secnotice("updates", "calling change block");
                ((SOSAccountCircleMembershipChangeBlock) notificationBlock)(newCircle, added_members, removed_members, added_applicants, removed_applicants);
            });
        });
    });

    CFReleaseNull(old_applicants);
    CFReleaseNull(new_applicants);
    
    CFReleaseNull(old_members);
    CFReleaseNull(new_members);
}

CF_RETURNS_RETAINED
CFDictionaryRef SOSAccountHandleRetirementMessages(SOSAccountRef account, CFDictionaryRef circle_retirement_messages, CFErrorRef *error) {
    CFStringRef circle_name = SOSCircleGetName(account->trusted_circle);
    CFMutableArrayRef handledRetirementIDs = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault);
    // We only handle one circle, look it up:

    require_quiet(account->trusted_circle, finish); // We don't fail, we intentionally handle nothing.
    CFDictionaryRef retirment_dictionary = CFDictionaryGetValue(circle_retirement_messages, circle_name);

    CFDictionaryForEach(retirment_dictionary, ^(const void *key, const void *value) {
        if(isData(value)) {
            SOSPeerInfoRef pi = SOSPeerInfoCreateFromData(NULL, error, (CFDataRef) value);
            if(pi && CFEqual(key, SOSPeerInfoGetPeerID(pi)) && SOSPeerInfoInspectRetirementTicket(pi, error)) {
                CFSetAddValue(account->retirees, pi);

                account->circle_rings_retirements_need_attention = true; // Have to handle retirements.

                CFArrayAppendValue(handledRetirementIDs, key);
            }
            CFReleaseNull(pi);
        }
    });

    // If we are in the retiree list, we somehow got resurrected
    // clearly we took care of proper departure before so leave
    // and delcare that we withdrew this time.
    SOSPeerInfoRef me = SOSAccountGetMyPeerInfo(account);
    if (me && CFSetContainsValue(account->retirees, me)) {
        SOSAccountPurgeIdentity(account);
        account->departure_code = kSOSDiscoveredRetirement;
    }

finish:
    {
    CFDictionaryRef result = (CFArrayGetCount(handledRetirementIDs) == 0) ? CFDictionaryCreateForCFTypes(kCFAllocatorDefault, NULL)
                                                                          : CFDictionaryCreateForCFTypes(kCFAllocatorDefault, circle_name, handledRetirementIDs, NULL);

    CFReleaseNull(handledRetirementIDs);
    return result;
    }
}

static SOSCircleRef SOSAccountCreateCircleFrom(CFStringRef circleName, CFTypeRef value, CFErrorRef *error) {
    if (value && !isData(value) && !isNull(value)) {
        secnotice("circleCreat", "Value provided not appropriate for a circle");
        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)) {
        secnotice("circleCreat", "No circle found in data: %@", value);
        circle = NULL;
    } else {
        circle = SOSCircleCreateFromData(NULL, (CFDataRef) value, error);
        if (circle) {
            CFStringRef name = SOSCircleGetName(circle);
            if (!CFEqualSafe(name, circleName)) {
                secnotice("circleCreat", "Expected circle named %@, got %@", circleName, name);
                SOSCreateErrorWithFormat(kSOSErrorNameMismatch, NULL, error, NULL,
                                         CFSTR("Expected circle named %@, got %@"), circleName, name);
                CFReleaseNull(circle);
            }
        } else {
            secnotice("circleCreat", "SOSCircleCreateFromData returned NULL.");
        }
    }
    return circle;
}

bool SOSAccountHandleCircleMessage(SOSAccountRef account,
                                   CFStringRef circleName, CFDataRef encodedCircleMessage, CFErrorRef *error) {
    bool success = false;
    CFErrorRef localError = NULL;
    SOSCircleRef circle = SOSAccountCreateCircleFrom(circleName, encodedCircleMessage, &localError);
    if (circle) {
        success = SOSAccountUpdateCircleFromRemote(account, circle, &localError);
        CFReleaseSafe(circle);
    } else {
        secerror("NULL circle found, ignoring ...");
        success = true;  // don't pend this NULL thing.
    }

    if (!success) {
        if (isSOSErrorCoded(localError, kSOSErrorIncompatibleCircle)) {
            secerror("Incompatible circle found, abandoning membership: %@", circleName);
            CFReleaseNull(account->my_identity);
            CFReleaseNull(account->trusted_circle);
        }

        if (error) {
            *error = localError;
            localError = NULL;
        }

    }

    CFReleaseNull(localError);

    return success;
}

bool SOSAccountHandleParametersChange(SOSAccountRef account, CFDataRef parameters, CFErrorRef *error){
    
    SecKeyRef newKey = NULL;
    CFDataRef newParameters = NULL;
    bool success = false;
    
    if(SOSAccountRetrieveCloudParameters(account, &newKey, parameters, &newParameters, error)) {
        if (CFEqualSafe(account->user_public, newKey)) {
            secnotice("updates", "Got same public key sent our way. Ignoring.");
            success = true;
        } else if (CFEqualSafe(account->previous_public, newKey)) {
            secnotice("updates", "Got previous public key repeated. Ignoring.");
            success = true;
        } else {
            SOSAccountSetUnTrustedUserPublicKey(account, newKey);
            SOSAccountSetParameters(account, newParameters);
            newKey = NULL;

            if(SOSAccountRetryUserCredentials(account)) {
                secnotice("keygen", "Successfully used cached password with new parameters: %@", account->user_public);
                SOSAccountGenerationSignatureUpdate(account, error);
            } else {
                SOSAccountPurgePrivateCredential(account);
                secnotice("keygen", "Got new parameters for public key - failed with cached password: %@", account->user_public);
                debugDumpUserParameters(CFSTR("params"), account->user_key_parameters);
            }

            account->circle_rings_retirements_need_attention = true;
            SOSUpdateKeyInterest(account);

            success = true;
        }
    }
    
    CFReleaseNull(newKey);
    CFReleaseNull(newParameters);
    
    return success;
}

static inline bool SOSAccountHasLeft(SOSAccountRef account) {
    switch(account->departure_code) {
        case kSOSDiscoveredRetirement: /* Fallthrough */
        case kSOSLostPrivateKey: /* Fallthrough */
        case kSOSWithdrewMembership: /* Fallthrough */
        case kSOSMembershipRevoked: /* Fallthrough */
        case kSOSLeftUntrustedCircle:
            return true;
        case kSOSNeverAppliedToCircle: /* Fallthrough */
        case kSOSNeverLeftCircle: /* Fallthrough */
        default:
            return false;
    }
}

static const char *concordstring[] = {
    "kSOSConcordanceTrusted",
    "kSOSConcordanceGenOld",     // kSOSErrorReplay
    "kSOSConcordanceNoUserSig",  // kSOSErrorBadSignature
    "kSOSConcordanceNoUserKey",  // kSOSErrorNoKey
    "kSOSConcordanceNoPeer",     // kSOSErrorPeerNotFound
    "kSOSConcordanceBadUserSig", // kSOSErrorBadSignature
    "kSOSConcordanceBadPeerSig", // kSOSErrorBadSignature
    "kSOSConcordanceNoPeerSig",
    "kSOSConcordanceWeSigned",
};

bool SOSAccountHandleUpdateCircle(SOSAccountRef account, SOSCircleRef prospective_circle, bool writeUpdate, CFErrorRef *error)
{
    bool success = true;
    bool haveOldCircle = true;
    const char *local_remote = writeUpdate ? "local": "remote";

    secnotice("signing", "start:[%s] %@", local_remote, prospective_circle);
    if (!account->user_public || !account->user_public_trusted) {
        SOSCreateError(kSOSErrorPublicKeyAbsent, CFSTR("Can't handle updates with no trusted public key here"), NULL, error);
        return false;
    }
    
    if (!prospective_circle) {
        secerror("##### Can't update to a NULL circle ######");
        return false; // Can't update one we don't have.
    }
    
    CFStringRef newCircleName = SOSCircleGetName(prospective_circle);
    SOSCircleRef oldCircle = account->trusted_circle;
    SOSCircleRef emptyCircle = NULL;
    
    if(oldCircle == NULL) {
        SOSCreateErrorWithFormat(kSOSErrorIncompatibleCircle, NULL, error, NULL, CFSTR("Current Entry is NULL; rejecting %@"), prospective_circle);
        secerror("##### Can't replace circle - we don't care about %@ ######", prospective_circle);
        return false;
    }
    if (CFGetTypeID(oldCircle) != SOSCircleGetTypeID()) {
        secdebug("signing", ">>>>>>>>>>>>>>>  Non-Circle Circle found <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<");
        // We don't know what is in our table, likely it was kCFNull indicating we didn't
        // understand a circle that came by. We seem to like this one lets make our entry be empty circle
        emptyCircle = SOSCircleCreate(kCFAllocatorDefault, newCircleName, NULL);
        oldCircle = emptyCircle;
        haveOldCircle = false;
        // And we're paranoid, drop our old peer info if for some reason we didn't before.
        // SOSAccountDestroyCirclePeerInfo(account, oldCircle, NULL);
    }
    
    SOSFullPeerInfoRef me_full = account->my_identity;
    SOSPeerInfoRef     me = SOSFullPeerInfoGetPeerInfo(me_full);
    
    SOSTransportCircleRef transport = account->circle_transport;

    SOSAccountScanForRetired(account, prospective_circle, error);
    SOSCircleRef newCircle = SOSAccountCloneCircleWithRetirement(account, prospective_circle, error);
    if(!newCircle) return false;

    if (me && SOSCircleUpdatePeerInfo(newCircle, me)) {
        writeUpdate = true; // If we update our peer in the new circle we should write it if we accept it.
    }

    typedef enum {
        accept,
        countersign,
        leave,
        revert,
        ignore
    } circle_action_t;
    
    static const char *actionstring[] = {
        "accept", "countersign", "leave", "revert", "ignore",
    };
    
    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) && haveOldCircle;
    
    SOSConcordanceStatus concstat =
    SOSCircleConcordanceTrust(oldCircle, newCircle,
                              old_circle_key, account->user_public,
                              me, error);
    
    CFStringRef concStr = NULL;
    switch(concstat) {
        case kSOSConcordanceTrusted:
            circle_action = countersign;
            concStr = CFSTR("Trusted");
            break;
        case kSOSConcordanceGenOld:
            circle_action = userTrustedOldCircle ? revert : ignore;
            concStr = CFSTR("Generation Old");
            break;
        case kSOSConcordanceBadUserSig:
        case kSOSConcordanceBadPeerSig:
            circle_action = userTrustedOldCircle ? revert : accept;
            concStr = CFSTR("Bad Signature");
            break;
        case kSOSConcordanceNoUserSig:
            circle_action = userTrustedOldCircle ? revert : accept;
            concStr = CFSTR("No User Signature");
            break;
        case kSOSConcordanceNoPeerSig:
            circle_action = accept; // We might like this one eventually but don't countersign.
            concStr = CFSTR("No trusted peer signature");
            secerror("##### No trusted peer signature found, accepting hoping for concordance later %@", newCircle);
            break;
        case kSOSConcordanceNoPeer:
            circle_action = leave;
            leave_reason = kSOSLeftUntrustedCircle;
            concStr = CFSTR("No trusted peer left");
            break;
        case kSOSConcordanceNoUserKey:
            secerror("##### No User Public Key Available, this shouldn't ever happen!!!");
            abort();
            break;
        default:
            secerror("##### Bad Error Return from ConcordanceTrust");
            abort();
            break;
    }
    
    secnotice("signing", "Decided on action [%s] based on concordance state [%s] and [%s] circle.", actionstring[circle_action], concordstring[concstat], userTrustedOldCircle ? "trusted" : "untrusted");
    
    SOSCircleRef circleToPush = NULL;

    if (circle_action == leave) {
        circle_action = ignore; (void) circle_action; // Acknowledge this is a dead store.
        
        if (me && SOSCircleHasPeer(oldCircle, me, NULL)) {
            secnotice("account", "Leaving circle with peer %@", me);
            debugDumpCircle(CFSTR("oldCircle"), oldCircle);
            debugDumpCircle(CFSTR("newCircle"), newCircle);
            debugDumpCircle(CFSTR("prospective_circle"), prospective_circle);
            secnotice("account", "Key state: user_public %@, previous_public %@, old_circle_key %@",
                      account->user_public, account->previous_public, old_circle_key);

            if (sosAccountLeaveCircle(account, newCircle, error)) {
                circleToPush = newCircle;
            } else {
                secnotice("signing", "Can't leave circle %@, but dumping identities", oldCircle);
                success = false;
            }
            account->departure_code = leave_reason;
            circle_action = accept;
            me = NULL;
            me_full = NULL;
        } else {
            // We are not in this circle, but we need to update account with it, since we got it from cloud
            secnotice("signing", "We are not in this circle, but we need to update account with it");
            debugDumpCircle(CFSTR("oldCircle"), oldCircle);
            debugDumpCircle(CFSTR("newCircle"), newCircle);
            debugDumpCircle(CFSTR("prospective_circle"), prospective_circle);
            circle_action = accept;
        }
    }
    
    if (circle_action == countersign) {
        if (me && SOSCircleHasPeer(newCircle, me, NULL)) {
            if (SOSCircleVerifyPeerSigned(newCircle, me, NULL)) {
                secnotice("signing", "Already concur with: %@", newCircle);
            } else {
                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);
                    success = false;
                }
                CFReleaseSafe(signing_error);
            }
            
            if(SOSAccountVerifyAndAcceptHSAApplicants(account, newCircle, error)) {
                circleToPush = newCircle;
                writeUpdate = true;
            }
        } else {
            secnotice("signing", "Not countersigning, not in circle: %@", newCircle);
            debugDumpCircle(CFSTR("circle to countersign"), newCircle);
        }
        circle_action = accept;
    }
    
    if (circle_action == accept) {
        if (me && SOSCircleHasActivePeer(oldCircle, me, NULL) && !SOSCircleHasPeer(newCircle, me, NULL)) {
            //  Don't destroy evidence of other code determining reason for leaving.
            if(!SOSAccountHasLeft(account)) account->departure_code = kSOSMembershipRevoked;
            secnotice("account", "Member of old circle but not of new circle");
            debugDumpCircle(CFSTR("oldCircle"), oldCircle);
            debugDumpCircle(CFSTR("newCircle"), newCircle);
        }
        
        if (me
            && SOSCircleHasActivePeer(oldCircle, me, NULL)
            && !(SOSCircleCountPeers(oldCircle) == 1 && SOSCircleHasPeer(oldCircle, me, NULL)) // If it was our offering, don't change ID to avoid ghosts
            && !SOSCircleHasPeer(newCircle, me, NULL) && !SOSCircleHasApplicant(newCircle, me, NULL)) {
            secnotice("circle", "Purging my peer (ID: %@) for circle '%@'!!!", SOSPeerInfoGetPeerID(me), SOSCircleGetName(oldCircle));
            if (account->my_identity)
                SOSFullPeerInfoPurgePersistentKey(account->my_identity, NULL);
            CFReleaseNull(account->my_identity);
            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));
                debugDumpCircle(CFSTR("oldCircle"), oldCircle);
                debugDumpCircle(CFSTR("newCircle"), newCircle);
                if (account->my_identity)
                    SOSFullPeerInfoPurgePersistentKey(account->my_identity, NULL);
                CFReleaseNull(account->my_identity);
                me = NULL;
                me_full = NULL;
            } else {
                secnotice("circle", "Rejected, Reapplying (ID: %@) for circle '%@'", SOSPeerInfoGetPeerID(me), SOSCircleGetName(oldCircle));
                debugDumpCircle(CFSTR("oldCircle"), oldCircle);
                debugDumpCircle(CFSTR("newCircle"), newCircle);
                SOSCircleRequestReadmission(newCircle, account->user_public, me, NULL);
                writeUpdate = true;
            }
        }
        
        CFRetainSafe(oldCircle);
        CFRetainAssign(account->trusted_circle, newCircle);
        SOSAccountSetPreviousPublic(account);
        
        secnotice("signing", "%@, Accepting circle: %@", concStr, newCircle);
        
        if (me && account->user_public_trusted
            && SOSCircleHasApplicant(oldCircle, me, NULL)
            && SOSCircleCountPeers(newCircle) > 0
            && !SOSCircleHasPeer(newCircle, me, NULL) && !SOSCircleHasApplicant(newCircle, me, NULL)) {
            // We weren't rejected (above would have set me to NULL.
            // We were applying and we weren't accepted.
            // Our application is declared lost, let us reapply.
            
            secnotice("signing", "requesting readmission to circle %@", newCircle);
            if (SOSCircleRequestReadmission(newCircle, account->user_public, me, 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;
        SOSUpdateKeyInterest(account);
    }
    
    /*
     * In the revert section we'll guard the KVS idea of circles by rejecting "bad" new circles
     * and pushing our current view of the circle (oldCircle).  We'll only do this if we actually
     * are a member of oldCircle - never for an empty circle.
     */
    
    if (circle_action == revert) {
        if(haveOldCircle && me && SOSCircleHasActivePeer(oldCircle, me, NULL)) {
            secnotice("signing", "%@, Rejecting: %@ re-publishing %@", concStr, newCircle, oldCircle);
            debugDumpCircle(CFSTR("oldCircle"), oldCircle);
            debugDumpCircle(CFSTR("newCircle"), newCircle);
            circleToPush = oldCircle;
        } else {
            secnotice("canary", "%@, Rejecting: %@ Have no old circle - would reset", concStr, newCircle);
        }
    }
    
    
    if (circleToPush != NULL) {
        secnotice("signing", "Pushing:[%s] %@", local_remote, circleToPush);
        CFDataRef circle_data = SOSCircleCopyEncodedData(circleToPush, kCFAllocatorDefault, error);
        
        if (circle_data) {
            //recording circle we are pushing in KVS
            success &= SOSTransportCircleRecordLastCirclePushedInKVS(transport, SOSCircleGetName(circleToPush), circle_data);
            //posting new circle to peers
            success &= SOSTransportCirclePostCircle(transport, SOSCircleGetName(circleToPush), circle_data, error);
        } else {
            success = false;
        }
        CFReleaseNull(circle_data);
    }
    
    CFReleaseSafe(newCircle);
    CFReleaseNull(emptyCircle);
    return success;
}