2 // SOSAccountTrustClassicCircle.m
6 #import <Foundation/Foundation.h>
7 #include <AssertMacros.h>
9 #import "Security/SecureObjectSync/SOSAccount.h"
10 #import "Security/SecureObjectSync/SOSAccountTrustClassic.h"
11 #import "Security/SecureObjectSync/SOSTransportCircle.h"
12 #import "Security/SecureObjectSync/SOSAccountTrustClassic+Identity.h"
13 #import "Security/SecureObjectSync/SOSAccountTrustClassic+Expansion.h"
14 #import "Security/SecureObjectSync/SOSAccountTrustClassic+Retirement.h"
16 #import "Security/SecureObjectSync/SOSAccountGhost.h"
17 #import "Security/SecureObjectSync/SOSViews.h"
19 @implementation SOSAccountTrustClassic (Circle)
21 -(bool) isInCircle:(CFErrorRef *)error
23 SOSCCStatus result = [self getCircleStatus:error];
25 if (result != kSOSCCInCircle) {
26 SOSErrorCreate(kSOSErrorNoCircle, error, NULL, CFSTR("Not in circle"));
33 -(bool) hasCircle:(CFErrorRef*) error
35 if (!self.trustedCircle)
36 SOSCreateErrorWithFormat(kSOSErrorNoCircle, NULL, error, NULL, CFSTR("No trusted circle"));
38 return self.trustedCircle != NULL;
41 -(SOSCCStatus) thisDeviceStatusInCircle:(SOSCircleRef) circle peer:(SOSPeerInfoRef) this_peer
44 return kSOSCCNotInCircle;
46 if (circle && SOSCircleCountPeers(circle) == 0)
47 return kSOSCCCircleAbsent;
51 if(SOSPeerInfoIsRetirementTicket(this_peer))
52 return kSOSCCNotInCircle;
54 if (SOSCircleHasPeer(circle, this_peer, NULL))
55 return kSOSCCInCircle;
57 if (SOSCircleHasApplicant(circle, this_peer, NULL))
58 return kSOSCCRequestPending;
61 return kSOSCCNotInCircle;
63 -(SOSCCStatus) getCircleStatus:(CFErrorRef*) error
65 return [self thisDeviceStatusInCircle:self.trustedCircle peer:self.peerInfo];
69 -(SOSCircleRef) getCircle:(CFErrorRef *)error
71 CFTypeRef entry = self.trustedCircle;
72 require_action_quiet(!isNull(entry), fail,
73 SOSCreateError(kSOSErrorIncompatibleCircle, CFSTR("Incompatible circle in KVS"), NULL, error));
74 return (SOSCircleRef) entry;
83 -(SOSCircleRef) ensureCircle:(SOSAccount*)a name:(CFStringRef)name err:(CFErrorRef *)error
85 CFErrorRef localError = NULL;
86 if (self.trustedCircle == NULL) {
87 SOSCircleRef newCircle = SOSCircleCreate(NULL, name, NULL);
88 self.trustedCircle = newCircle; // Note that this setter adds a retain
89 CFReleaseNull(newCircle);
90 secnotice("circleop", "Setting key_interests_need_updating to true in ensureCircle");
91 a.key_interests_need_updating = true;
94 require_action_quiet(self.trustedCircle || !isSOSErrorCoded(localError, kSOSErrorIncompatibleCircle), fail,
95 if (error) { *error = localError; localError = NULL; });
98 CFReleaseNull(localError);
99 return self.trustedCircle;
104 switch(self.departureCode) {
105 case kSOSDiscoveredRetirement: /* Fallthrough */
106 case kSOSLostPrivateKey: /* Fallthrough */
107 case kSOSWithdrewMembership: /* Fallthrough */
108 case kSOSMembershipRevoked: /* Fallthrough */
109 case kSOSLeftUntrustedCircle:
111 case kSOSNeverAppliedToCircle: /* Fallthrough */
112 case kSOSNeverLeftCircle: /* Fallthrough */
118 /* This check is new to protect piggybacking by the current peer - in that case we have a remote peer signature that
119 can't have ghost cleanup changing the circle hash.
122 -(bool) ghostBustingOK:(SOSCircleRef) oldCircle updatingTo:(SOSCircleRef) newCircle {
124 // Preliminaries - we must have a peer and it must be in the newCircle in order to attempt busting
125 SOSFullPeerInfoRef me_full = self.fullPeerInfo;
126 if(!me_full) return false;
127 SOSPeerInfoRef me = SOSFullPeerInfoGetPeerInfo(me_full);
128 if(!me || (!SOSCircleHasPeer(newCircle, me, NULL) && !SOSCircleHasApplicant(newCircle, me, NULL))) return false;
130 CFStringRef myPid = SOSPeerInfoGetPeerID(me);
131 CFDictionaryRef newSigs = SOSCircleCopyAllSignatures(newCircle);
132 bool iSignedNew = CFDictionaryGetCountOfKey(newSigs, myPid);
133 long otherPeerSigCount = CFDictionaryGetCount(newSigs) - ((iSignedNew) ? 2: 1);
135 if (SOSCircleHasPeer(oldCircle, me, NULL)) { // If we're already in the old one we're not PBing
137 } else if (!iSignedNew) { // Piggybacking peers always have signed as part of genSigning - so this indicates we're safe to bust.
139 } else if(iSignedNew && otherPeerSigCount > 1) { // if others have seen this we're good to bust.
142 CFReleaseNull(newSigs);
146 // If this circle bears a signature from us and a newer gencount and it isn't our "current" circle, we're
147 // going to trust it. That's the signature of a piggybacked circle where we were the sponsor.
149 -(bool) checkForSponsorshipTrust:(SOSCircleRef) prospective_circle {
150 if(CFEqualSafe(self.trustedCircle, prospective_circle)) return false;
151 SecKeyRef myPubKey = SOSFullPeerInfoCopyPubKey(self.fullPeerInfo, NULL);
152 if(!myPubKey) return false;
153 if(SOSCircleVerify(prospective_circle, myPubKey, NULL) && SOSCircleIsOlderGeneration(self.trustedCircle, prospective_circle)) {
154 [self setTrustedCircle:prospective_circle];
155 CFReleaseNull(myPubKey);
158 CFReleaseNull(myPubKey);
162 static bool SOSCirclePeerOctagonKeysChanged(SOSPeerInfoRef oldPeer, SOSPeerInfoRef newPeer) {
163 bool oldHasOctagonBits = oldPeer && (SOSPeerInfoHasOctagonSigningPubKey(oldPeer) || SOSPeerInfoHasOctagonEncryptionPubKey(oldPeer));
164 bool newHasOctagonBits = newPeer && (SOSPeerInfoHasOctagonSigningPubKey(newPeer) || SOSPeerInfoHasOctagonEncryptionPubKey(newPeer));
167 if(newPeer && newHasOctagonBits) {
170 // New peer to circle has no octagon bits: no change
176 // We removed a peer. This is an octagon bits change if the old peer had octagon bits
177 return oldHasOctagonBits;
180 // This is a peer update: Did the keys change?
181 if(!oldHasOctagonBits && !newHasOctagonBits) {
182 // both peers have no keys: no change
186 SecKeyRef oldSigningKey = SOSPeerInfoCopyOctagonSigningPublicKey(oldPeer, NULL);
187 SecKeyRef newSigningKey = SOSPeerInfoCopyOctagonSigningPublicKey(newPeer, NULL);
189 bool signingKeyChanged = CFEqualSafe(oldSigningKey, newSigningKey);
191 CFReleaseNull(oldSigningKey);
192 CFReleaseNull(newSigningKey);
195 SecKeyRef oldEncryptionKey = SOSPeerInfoCopyOctagonEncryptionPublicKey(oldPeer, NULL);
196 SecKeyRef newEncryptionKey = SOSPeerInfoCopyOctagonEncryptionPublicKey(newPeer, NULL);
198 bool encryptionKeyChanged = CFEqualSafe(oldEncryptionKey, newEncryptionKey);
200 CFReleaseNull(oldEncryptionKey);
201 CFReleaseNull(newEncryptionKey);
203 return signingKeyChanged || encryptionKeyChanged;
207 static bool SOSCircleHasUpdatedPeerInfoWithOctagonKey(SOSCircleRef oldCircle, SOSCircleRef newCircle)
209 __block bool hasUpdated = false;
210 SOSCircleForEachPeer(oldCircle, ^(SOSPeerInfoRef oldPeer) {
211 SOSPeerInfoRef equivalentNewPeer = SOSCircleCopyPeerWithID(newCircle, SOSPeerInfoGetPeerID(oldPeer), NULL);
212 hasUpdated |= SOSCirclePeerOctagonKeysChanged(oldPeer, equivalentNewPeer);
213 CFReleaseNull(equivalentNewPeer);
216 SOSCircleForEachPeer(newCircle, ^(SOSPeerInfoRef newPeer) {
217 SOSPeerInfoRef equivalentOldPeer = SOSCircleCopyPeerWithID(oldCircle, SOSPeerInfoGetPeerID(newPeer), NULL);
218 hasUpdated |= SOSCirclePeerOctagonKeysChanged(equivalentOldPeer, newPeer);
219 CFReleaseNull(equivalentOldPeer);
225 -(bool) handleUpdateCircle:(SOSCircleRef) prospective_circle transport:(SOSKVSCircleStorageTransport*)circleTransport update:(bool) writeUpdate err:(CFErrorRef*)error
228 bool haveOldCircle = true;
229 const char *local_remote = writeUpdate ? "local": "remote";
231 SOSAccount* account = [circleTransport getAccount];
233 secnotice("signing", "start:[%s]", local_remote);
234 if (!account.accountKey || !account.accountKeyIsTrusted) {
235 SOSCreateError(kSOSErrorPublicKeyAbsent, CFSTR("Can't handle updates with no trusted public key here"), NULL, error);
239 if (!prospective_circle) {
240 secerror("##### Can't update to a NULL circle ######");
241 return false; // Can't update one we don't have.
244 // If this is a remote circle, check to see if this is our first opportunity to trust a circle where we
245 // sponsored the only signer.
246 if(!writeUpdate && [ self checkForSponsorshipTrust: prospective_circle ]){
247 SOSCCEnsurePeerRegistration();
248 secnotice("circleop", "Setting key_interests_need_updating to true in handleUpdateCircle");
249 account.key_interests_need_updating = true;
254 CFStringRef newCircleName = SOSCircleGetName(prospective_circle);
256 SOSCircleRef oldCircle = self.trustedCircle;
257 SOSCircleRef emptyCircle = NULL;
259 if(oldCircle == NULL) {
260 SOSCreateErrorWithFormat(kSOSErrorIncompatibleCircle, NULL, error, NULL, CFSTR("Current Entry is NULL; rejecting %@"), prospective_circle);
261 secerror("##### Can't replace circle - we don't care about it ######");
264 if (CFGetTypeID(oldCircle) != SOSCircleGetTypeID()) {
265 secdebug("signing", ">>>>>>>>>>>>>>> Non-Circle Circle found <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<");
266 // We don't know what is in our table, likely it was kCFNull indicating we didn't
267 // understand a circle that came by. We seem to like this one lets make our entry be empty circle
268 emptyCircle = SOSCircleCreate(kCFAllocatorDefault, newCircleName, NULL);
269 oldCircle = emptyCircle;
270 haveOldCircle = false;
271 // And we're paranoid, drop our old peer info if for some reason we didn't before.
272 // SOSAccountDestroyCirclePeerInfo(account, oldCircle, NULL);
276 SOSAccountScanForRetired(account, prospective_circle, error);
277 SOSCircleRef newCircle = SOSAccountCloneCircleWithRetirement(account, prospective_circle, error);
278 if(!newCircle) return false;
279 if([self ghostBustingOK: oldCircle updatingTo:newCircle]) {
280 SOSCircleRef ghostCleaned = SOSAccountCloneCircleWithoutMyGhosts(account, newCircle);
282 CFRetainAssign(newCircle, ghostCleaned);
287 SOSFullPeerInfoRef me_full = self.fullPeerInfo;
288 SOSPeerInfoRef me = SOSFullPeerInfoGetPeerInfo(me_full);
289 CFStringRef myPeerID = SOSPeerInfoGetPeerID(me);
290 myPeerID = (myPeerID) ? myPeerID: CFSTR("No Peer");
292 if (me && SOSCircleUpdatePeerInfo(newCircle, me)) {
293 writeUpdate = true; // If we update our peer in the new circle we should write it if we accept it.
304 static const char *actionstring[] = {
305 "accept", "countersign", "leave", "revert", "ignore",
308 circle_action_t circle_action = ignore;
309 enum DepartureReason leave_reason = kSOSNeverLeftCircle;
311 SecKeyRef old_circle_key = NULL;
312 if(SOSCircleVerify(oldCircle, account.accountKey, NULL)){
313 old_circle_key = account.accountKey;
315 else if(account.previousAccountKey && SOSCircleVerify(oldCircle, account.previousAccountKey, NULL)){
316 old_circle_key = account.previousAccountKey;
319 bool userTrustedOldCircle = (old_circle_key != NULL) && haveOldCircle;
321 SOSConcordanceStatus concstat =
322 SOSCircleConcordanceTrust(oldCircle, newCircle,
323 old_circle_key, account.accountKey,
326 CFStringRef concStr = NULL;
328 case kSOSConcordanceTrusted:
329 circle_action = countersign;
330 concStr = CFSTR("Trusted");
332 case kSOSConcordanceGenOld:
333 circle_action = userTrustedOldCircle ? revert : ignore;
334 concStr = CFSTR("Generation Old");
336 case kSOSConcordanceBadUserSig:
337 case kSOSConcordanceBadPeerSig:
338 circle_action = userTrustedOldCircle ? revert : accept;
339 concStr = CFSTR("Bad Signature");
341 case kSOSConcordanceNoUserSig:
342 circle_action = userTrustedOldCircle ? revert : accept;
343 concStr = CFSTR("No User Signature");
345 case kSOSConcordanceNoPeerSig:
346 circle_action = accept; // We might like this one eventually but don't countersign.
347 concStr = CFSTR("No trusted peer signature");
348 secnotice("signing", "##### No trusted peer signature found, accepting hoping for concordance later");
350 case kSOSConcordanceNoPeer:
351 circle_action = leave;
352 leave_reason = kSOSLeftUntrustedCircle;
353 concStr = CFSTR("No trusted peer left");
355 case kSOSConcordanceNoUserKey:
356 secerror("##### No User Public Key Available, this shouldn't ever happen!!!");
360 secerror("##### Bad Error Return from ConcordanceTrust");
365 secnotice("signing", "Decided on action [%s] based on concordance state [%@] and [%s] circle. My PeerID is %@", actionstring[circle_action], concStr, userTrustedOldCircle ? "trusted" : "untrusted", myPeerID);
367 SOSCircleRef circleToPush = NULL;
369 if (circle_action == leave) {
370 circle_action = ignore; (void) circle_action; // Acknowledge this is a dead store.
372 if (me && SOSCircleHasPeer(oldCircle, me, NULL)) {
373 secnotice("account", "Leaving circle with peer %@", me);
374 debugDumpCircle(CFSTR("oldCircle"), oldCircle);
375 debugDumpCircle(CFSTR("newCircle"), newCircle);
376 debugDumpCircle(CFSTR("prospective_circle"), prospective_circle);
377 secnotice("account", "Key state: accountKey %@, previousAccountKey %@, old_circle_key %@",
378 account.accountKey, account.previousAccountKey, old_circle_key);
380 if (sosAccountLeaveCircle(account, newCircle, error)) {
381 secnotice("circleOps", "Leaving circle by newcircle state");
382 circleToPush = newCircle;
384 secnotice("signing", "Can't leave circle, but dumping identities");
387 self.departureCode = leave_reason;
388 circle_action = accept;
392 // We are not in this circle, but we need to update account with it, since we got it from cloud
393 secnotice("signing", "We are not in this circle, but we need to update account with it");
394 debugDumpCircle(CFSTR("oldCircle"), oldCircle);
395 debugDumpCircle(CFSTR("newCircle"), newCircle);
396 debugDumpCircle(CFSTR("prospective_circle"), prospective_circle);
397 circle_action = accept;
401 if (circle_action == countersign) {
402 if (me && SOSCircleHasPeer(newCircle, me, NULL)) {
403 if (SOSCircleVerifyPeerSigned(newCircle, me, NULL)) {
404 secnotice("signing", "Already concur with the new circle");
406 CFErrorRef signing_error = NULL;
408 if (me_full && SOSCircleConcordanceSign(newCircle, me_full, &signing_error)) {
409 circleToPush = newCircle;
410 secnotice("signing", "Concurred with new circle");
412 secerror("Failed to concurrence sign, error: %@", signing_error);
415 CFReleaseSafe(signing_error);
418 secnotice("signing", "Not countersigning, not in new circle");
419 debugDumpCircle(CFSTR("circle to countersign"), newCircle);
421 circle_action = accept;
424 if (circle_action == accept) {
425 if(SOSCircleHasUpdatedPeerInfoWithOctagonKey(oldCircle, newCircle)){
426 notify_post(kSOSCCCircleOctagonKeysChangedNotification);
428 if (me && SOSCircleHasActivePeer(oldCircle, me, NULL) && !SOSCircleHasPeer(newCircle, me, NULL)) {
429 // Don't destroy evidence of other code determining reason for leaving.
430 if(![self hasLeft]) self.departureCode = kSOSMembershipRevoked;
431 secnotice("circleOps", "Member of old circle but not of new circle (%d)", self.departureCode);
432 debugDumpCircle(CFSTR("oldCircle"), oldCircle);
433 debugDumpCircle(CFSTR("newCircle"), newCircle);
437 && SOSCircleHasActivePeer(oldCircle, me, NULL)
438 && !(SOSCircleCountPeers(oldCircle) == 1 && SOSCircleHasPeer(oldCircle, me, NULL)) // If it was our offering, don't change ID to avoid ghosts
439 && !SOSCircleHasPeer(newCircle, me, NULL) && !SOSCircleHasApplicant(newCircle, me, NULL)) {
440 secnotice("circle", "Purging my peer (ID: %@) for circle '%@'!!!", SOSPeerInfoGetPeerID(me), SOSCircleGetName(oldCircle));
441 if (self.fullPeerInfo)
442 SOSFullPeerInfoPurgePersistentKey(self.fullPeerInfo, NULL);
447 if (me && SOSCircleHasRejectedApplicant(newCircle, me, NULL)) {
448 SOSPeerInfoRef reject = SOSCircleCopyRejectedApplicant(newCircle, me, NULL);
449 if(CFEqualSafe(reject, me) && SOSPeerInfoApplicationVerify(me, account.accountKey, NULL)) {
450 secnotice("circle", "Rejected, Purging my applicant peer (ID: %@) for circle '%@'", SOSPeerInfoGetPeerID(me), SOSCircleGetName(oldCircle));
451 debugDumpCircle(CFSTR("oldCircle"), oldCircle);
452 debugDumpCircle(CFSTR("newCircle"), newCircle);
453 if (self.fullPeerInfo)
454 SOSFullPeerInfoPurgePersistentKey(self.fullPeerInfo, NULL);
458 secnotice("circle", "Rejected, Reapplying (ID: %@) for circle '%@'", SOSPeerInfoGetPeerID(me), SOSCircleGetName(oldCircle));
459 debugDumpCircle(CFSTR("oldCircle"), oldCircle);
460 debugDumpCircle(CFSTR("newCircle"), newCircle);
461 SOSCircleRequestReadmission(newCircle, account.accountKey, me, NULL);
464 CFReleaseNull(reject);
467 CFRetainSafe(oldCircle);
468 account.previousAccountKey = account.accountKey;
470 secnotice("signing", "%@, Accepting new circle", concStr);
471 if (circle_action == accept) {
472 [self setTrustedCircle:newCircle];
475 if (me && account.accountKeyIsTrusted
476 && SOSCircleHasApplicant(oldCircle, me, NULL)
477 && SOSCircleCountPeers(newCircle) > 0
478 && !SOSCircleHasPeer(newCircle, me, NULL) && !SOSCircleHasApplicant(newCircle, me, NULL)) {
479 // We weren't rejected (above would have set me to NULL.
480 // We were applying and we weren't accepted.
481 // Our application is declared lost, let us reapply.
483 secnotice("signing", "requesting readmission to new circle");
484 if (SOSCircleRequestReadmission(newCircle, account.accountKey, me, NULL))
488 if (me && SOSCircleHasActivePeer(oldCircle, me, NULL)) {
489 [account.trust cleanupRetirementTickets:account circle:oldCircle time:RETIREMENT_FINALIZATION_SECONDS err:NULL];
492 SOSAccountNotifyOfChange(account, oldCircle, newCircle);
494 CFReleaseNull(oldCircle);
497 circleToPush = newCircle;
498 secnotice("circleop", "Setting key_interests_need_updating to true in handleUpdateCircle");
499 account.key_interests_need_updating = true;
503 * In the revert section we'll guard the KVS idea of circles by rejecting "bad" new circles
504 * and pushing our current view of the circle (oldCircle). We'll only do this if we actually
505 * are a member of oldCircle - never for an empty circle.
508 if (circle_action == revert) {
509 if(haveOldCircle && me && SOSCircleHasActivePeer(oldCircle, me, NULL)) {
510 secnotice("signing", "%@, Rejecting new circle, re-publishing old circle", concStr);
511 debugDumpCircle(CFSTR("oldCircle"), oldCircle);
512 debugDumpCircle(CFSTR("newCircle"), newCircle);
513 circleToPush = oldCircle;
514 [self setTrustedCircle:oldCircle];
516 secnotice("canary", "%@, Rejecting: new circle Have no old circle - would reset", concStr);
521 if (circleToPush != NULL) {
522 secnotice("signing", "Pushing:[%s]", local_remote);
523 CFDataRef circle_data = SOSCircleCopyEncodedData(circleToPush, kCFAllocatorDefault, error);
526 // Ensure we flush changes
527 account.circle_rings_retirements_need_attention = true;
529 //posting new circle to peers
530 success &= [circleTransport postCircle:SOSCircleGetName(circleToPush) circleData:circle_data err:error];
531 //cleanup old KVS keys
532 (void) SOSAccountCleanupAllKVSKeys(account, NULL);
536 CFReleaseNull(circle_data);
538 CFReleaseSafe(newCircle);
539 CFReleaseNull(emptyCircle);
541 // There are errors collected above that are soft (worked around)
542 if(success && error && *error) {
543 CFReleaseNull(*error);
549 -(bool) updateCircleFromRemote:(SOSKVSCircleStorageTransport*)circleTransport newCircle:(SOSCircleRef)newCircle err:(CFErrorRef*)error
551 return [self handleUpdateCircle:newCircle transport:circleTransport update:false err:error];
554 -(bool) updateCircle:(SOSKVSCircleStorageTransport*)circleTransport newCircle:(SOSCircleRef) newCircle err:(CFErrorRef*)error
556 return [self handleUpdateCircle:newCircle transport:circleTransport update:true err:error];
559 -(bool) modifyCircle:(SOSKVSCircleStorageTransport*)circleTransport err:(CFErrorRef*)error action:(SOSModifyCircleBlock)block
561 bool success = false;
562 SOSCircleRef circleCopy = NULL;
563 require_action_quiet(self.trustedCircle, fail, SOSErrorCreate(kSOSErrorNoCircle, error, NULL, CFSTR("No circle to get peer key from")));
565 circleCopy = SOSCircleCopyCircle(kCFAllocatorDefault, self.trustedCircle, error);
566 require_quiet(circleCopy, fail);
569 require_quiet(block(circleCopy), fail);
571 success = [self updateCircle:circleTransport newCircle:circleCopy err:error];
574 CFReleaseSafe(circleCopy);
579 -(void) generationSignatureUpdateWith:(SOSAccount*)account key:(SecKeyRef) privKey
581 if (self.trustedCircle && self.fullPeerInfo) {
582 [self modifyCircle:account.circle_transport err:NULL action:^(SOSCircleRef circle) {
583 SOSPeerInfoRef myPI = account.peerInfo;
584 bool iAmPeer = SOSCircleHasPeer(circle, myPI, NULL);
585 bool change = SOSCircleUpdatePeerInfo(circle, myPI);
586 if(iAmPeer && !SOSCircleVerify(circle, account.accountKey, NULL)) {
587 change |= [self upgradeiCloudIdentity:circle privKey:privKey];
588 [self removeInvalidApplications:circle userPublic:account.accountKey];
589 change |= SOSCircleGenerationSign(circle, privKey, self.fullPeerInfo, NULL);
590 [self setDepartureCode:kSOSNeverLeftCircle];
592 SOSFullPeerInfoRef icfpi = SOSCircleCopyiCloudFullPeerInfoRef(circle, NULL);
594 SOSAccountRemoveIncompleteiCloudIdentities(account, circle, privKey, NULL);
595 change |= [self addiCloudIdentity:circle key:privKey err:NULL];
597 CFReleaseNull(icfpi);
600 secnotice("updatingGenSignature", "we changed the circle? %@", change ? CFSTR("YES") : CFSTR("NO"));
606 -(void) forEachCirclePeerExceptMe:(SOSIteratePeerBlock)block
608 if (self.trustedCircle && self.peerInfo) {
609 NSString* myPi_id = self.peerID;
610 SOSCircleForEachPeer(self.trustedCircle, ^(SOSPeerInfoRef peer) {
611 CFStringRef peerID = SOSPeerInfoGetPeerID(peer);
612 if (peerID && ![myPi_id isEqualToString:(__bridge NSString*) peerID]) {
618 -(bool) leaveCircle:(SOSAccount*)account err:(CFErrorRef*) error
621 secnotice("circleOps", "Leaving circle by client request");
622 result &= [self modifyCircle:account.circle_transport err:error action:^(SOSCircleRef circle) {
623 return sosAccountLeaveCircle(account, circle, error);
626 self.departureCode = kSOSWithdrewMembership;
631 -(bool) resetToOffering:(SOSAccountTransaction*) aTxn key:(SecKeyRef)userKey err:(CFErrorRef*) error
633 SOSFullPeerInfoPurgePersistentKey(self.fullPeerInfo, NULL);
634 self.fullPeerInfo = nil;
636 secnotice("resetToOffering", "Resetting circle to offering by request from client");
638 return userKey && [self resetCircleToOffering:aTxn userKey:userKey err:error];
642 -(bool) resetCircleToOffering:(SOSAccountTransaction*) aTxn userKey:(SecKeyRef)user_key err:(CFErrorRef *)error
646 SOSAccount* account = aTxn.account;
647 if(![self hasCircle:error])
650 if(![self ensureFullPeerAvailable:(__bridge CFDictionaryRef)(account.gestalt) deviceID:(__bridge CFStringRef)(account.deviceID) backupKey:(__bridge CFDataRef)(account.backup_key) err:error])
653 (void)[self resetAllRings:account err:error];
655 [self modifyCircle:account.circle_transport err:error action:^bool(SOSCircleRef circle) {
657 SOSFullPeerInfoRef cloud_identity = NULL;
658 CFErrorRef localError = NULL;
660 require_quiet(SOSCircleResetToOffering(circle, user_key, self.fullPeerInfo, &localError), err_out);
662 self.departureCode = kSOSNeverLeftCircle;
664 require_quiet([self addEscrowToPeerInfo:self.fullPeerInfo err:error], err_out);
666 require_quiet([self addiCloudIdentity:circle key:user_key err:error], err_out);
668 SOSAccountPublishCloudParameters(account, NULL);
672 secerror("error resetting circle (%@) to offering: %@", circle, localError);
673 if (localError && error && *error == NULL) {
677 CFReleaseNull(localError);
678 CFReleaseNull(cloud_identity);
682 [self setValueInExpansion:kSOSUnsyncedViewsKey value:kCFBooleanTrue err:NULL];
683 SOSAccountUpdateOutOfSyncViews(aTxn, SOSViewsGetAllCurrent());
690 void SOSAccountForEachCirclePeerExceptMe(SOSAccount* account, void (^action)(SOSPeerInfoRef peer)) {
691 SOSPeerInfoRef myPi = account.peerInfo;
692 SOSCircleRef circle = NULL;
694 SOSAccountTrustClassic *trust = account.trust;
695 circle = trust.trustedCircle;
696 if (circle && myPi) {
697 CFStringRef myPi_id = SOSPeerInfoGetPeerID(myPi);
698 SOSCircleForEachPeer(circle, ^(SOSPeerInfoRef peer) {
699 CFStringRef peerID = SOSPeerInfoGetPeerID(peer);
700 if (peerID && !CFEqual(peerID, myPi_id)) {
707 -(bool) joinCircle:(SOSAccountTransaction*) aTxn userKey:(SecKeyRef) user_key useCloudPeer:(bool) use_cloud_peer err:(CFErrorRef*) error
709 __block bool result = false;
710 __block SOSFullPeerInfoRef cloud_full_peer = NULL;
711 __block SOSAccount* account = aTxn.account;
712 require_action_quiet(self.trustedCircle, fail, SOSCreateErrorWithFormat(kSOSErrorPeerNotFound, NULL, error, NULL, CFSTR("Don't have circle when joining???")));
713 require_quiet([self ensureFullPeerAvailable:(__bridge CFDictionaryRef)account.gestalt deviceID:(__bridge CFStringRef)account.deviceID backupKey:(__bridge CFDataRef)account.backup_key err:error], fail);
715 if (SOSCircleCountPeers(self.trustedCircle) == 0 || SOSAccountGhostResultsInReset(account)) {
716 secnotice("resetToOffering", "Resetting circle to offering since there are no peers");
717 // this also clears initial sync data
718 result = [self resetCircleToOffering:aTxn userKey:user_key err:error];
720 [self setValueInExpansion:kSOSUnsyncedViewsKey value:kCFBooleanTrue err:NULL];
722 if (use_cloud_peer) {
723 cloud_full_peer = SOSCircleCopyiCloudFullPeerInfoRef(self.trustedCircle, NULL);
726 [self modifyCircle: account.circle_transport err:error action:^(SOSCircleRef circle) {
727 result = SOSAccountAddEscrowToPeerInfo(account, self.fullPeerInfo, error);
728 result &= SOSCircleRequestAdmission(circle, user_key, self.fullPeerInfo, error);
729 self.departureCode = kSOSNeverLeftCircle;
730 if(result && cloud_full_peer) {
731 CFErrorRef localError = NULL;
732 CFStringRef cloudid = SOSPeerInfoGetPeerID(SOSFullPeerInfoGetPeerInfo(cloud_full_peer));
733 require_quiet(cloudid, finish);
734 require_quiet(SOSCircleHasActivePeerWithID(circle, cloudid, &localError), finish);
735 require_quiet(SOSCircleAcceptRequest(circle, user_key, cloud_full_peer, self.peerInfo, &localError), finish);
739 secerror("Failed to join with cloud identity: %@", localError);
740 CFReleaseNull(localError);
746 if (use_cloud_peer) {
747 SOSAccountUpdateOutOfSyncViews(aTxn, SOSViewsGetAllCurrent());
752 CFReleaseNull(cloud_full_peer);