+//
+// 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);
+ });
+
+#if ENABLE_V2_BACKUP
+ // 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);
+#endif
+
+ // 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);
+
+ 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);
+}
+
+static void SOSAccountRecordRetiredPeerInCircle(SOSAccountRef account, SOSPeerInfoRef retiree)
+{
+ // Replace Peer with RetiredPeer, if were a peer.
+ SOSAccountModifyCircle(account, NULL, ^(SOSCircleRef circle) {
+ bool updated = SOSCircleUpdatePeerInfo(circle, retiree);
+ if (updated) {
+ 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;
+ });
+}
+
+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);
+
+ SOSAccountRecordRetiredPeerInCircle(account, pi);
+
+ 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);
+ CFReleaseNull(account->user_key_parameters);
+ account->user_key_parameters = newParameters;
+ newKey = NULL;
+ newParameters = 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);
+ }
+
+ SOSUpdateKeyInterest();
+
+ 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();
+ }
+
+ /*
+ * 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;
+}