SOSAccountUpdate.c   [plain text]


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

#include "SOSAccountPriv.h"
#include "SOSAccountLog.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 void SOSAccountAppendPeerMetasForViewBackups(SOSAccountRef account, CFSetRef views, CFMutableArrayRef appendTo)
{
    CFMutableDictionaryRef ringToViewTable = NULL;

    require_quiet(SOSAccountIsInCircle(account, NULL), done);

    require_action_quiet(SOSAccountHasCompletedRequiredBackupSync(account), done,
                         secnotice("backup", "Haven't finished initial backup syncing, not registering backup metas with engine"));

    require_action_quiet(SOSPeerInfoV2DictionaryHasData(SOSAccountGetMyPeerInfo(account), sBackupKeyKey), done,
                         secnotice("backup", "No key to backup to, we don't enable individual view backups"));

    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 = SOSAccountCopyRing(account, ringName, NULL);
            if (ring && 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);
            CFReleaseNull(ring);
        }
    });

    CFDictionaryForEach(ringToViewTable, ^(const void *key, const void *value) {
        SOSRingRef ring = (SOSRingRef) key;
        CFSetRef viewNames = asSet(value, NULL);
        if (isSOSRing(ring) && viewNames) {
            if (SOSAccountIntersectsWithOutstanding(account, viewNames)) {
                CFStringSetPerformWithDescription(viewNames, ^(CFStringRef ringViews) {
                    secnotice("engine-notify", "Not ready, no peer meta: R: %@ Vs: %@", SOSRingGetName(ring), ringViews);
                });
            } 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);

                CFStringSetPerformWithDescription(viewNames, ^(CFStringRef ringViews) {
                    secnotice("engine-notify", "Backup peer meta: R: %@ Vs: %@ VD: %@", SOSRingGetName(ring), ringViews, ring_payload);
                });

                meta_added = true;

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

done:
    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);
            }

            CFStringSetPerformWithDescription(views, ^(CFStringRef viewsDescription) {
                secnotice("engine-notify", "Meta: %@: %@", SOSPeerInfoGetPeerID(peer), viewsDescription);
            });

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

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

        // We don't make a backup peer meta for the magic V0 peer
        // Set up all the rest 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);
        }

        CFStringSetPerformWithDescription(myViews, ^(CFStringRef viewsDescription) {
            secnotice("engine-notify", "My Meta: %@: %@", myPi_id, viewsDescription);
        });
        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);
}

// 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 retirement_dictionary = asDictionary(CFDictionaryGetValue(circle_retirement_messages, circle_name), error);
    require_quiet(retirement_dictionary, finish);
    CFDictionaryForEach(retirement_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)) {
        debugDumpUserParameters(CFSTR("SOSAccountHandleParametersChange got new user key parameters:"), parameters);
        secnotice("keygen", "SOSAccountHandleParametersChange got new public key: %@", newKey);

        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");
                SOSAccountGenerationSignatureUpdate(account, error);
            } else {
                SOSAccountPurgePrivateCredential(account);
                secnotice("keygen", "Got new parameters for public key - could not find or use cached password");
            }

            account->circle_rings_retirements_need_attention = true;
            account->key_interests_need_updating = true;

            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);
    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 it ######");
        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);
    }
    
    
    SOSTransportCircleRef transport = account->circle_transport;

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

    SOSFullPeerInfoRef me_full = account->my_identity;
    SOSPeerInfoRef     me = SOSFullPeerInfoGetPeerInfo(me_full);
    CFStringRef        myPeerID = SOSPeerInfoGetPeerID(me);
    myPeerID = (myPeerID) ? myPeerID: CFSTR("No Peer");

    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");
            secnotice("signing", "##### No trusted peer signature found, accepting hoping for concordance later");
            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.  My PeerID is %@", actionstring[circle_action], concordstring[concstat], userTrustedOldCircle ? "trusted" : "untrusted", myPeerID);
    
    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)) {
                secnotice("leaveCircle", "Leaving circle by newcircle state");
                circleToPush = newCircle;
            } else {
                secnotice("signing", "Can't leave circle, but dumping identities");
                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 the new circle");
            } else {
                CFErrorRef signing_error = NULL;
                
                if (me_full && SOSCircleConcordanceSign(newCircle, me_full, &signing_error)) {
                    circleToPush = newCircle;
                    secnotice("signing", "Concurred with new circle");
                } else {
                    secerror("Failed to concurrence sign, error: %@", signing_error);
                    success = false;
                }
                CFReleaseSafe(signing_error);
            }
            
            if(SOSAccountVerifyAndAcceptHSAApplicants(account, newCircle, error)) {
                circleToPush = newCircle;
                writeUpdate = true;
            }
        } else {
            secnotice("signing", "Not countersigning, not in new circle");
            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 new circle", concStr);
        
        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 new circle");
            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;
        account->key_interests_need_updating = true;
    }
    
    /*
     * 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 new circle, re-publishing old circle", concStr);
            debugDumpCircle(CFSTR("oldCircle"), oldCircle);
            debugDumpCircle(CFSTR("newCircle"), newCircle);
            circleToPush = oldCircle;
        } else {
            secnotice("canary", "%@, Rejecting: new circle Have no old circle - would reset", concStr);
        }
    }
    
    
    if (circleToPush != NULL) {
        secnotice("signing", "Pushing:[%s]", local_remote);
        CFDataRef circle_data = SOSCircleCopyEncodedData(circleToPush, kCFAllocatorDefault, error);
        
        if (circle_data) {
            // Ensure we flush changes
            account->circle_rings_retirements_need_attention = true;

            //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;
}