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 static const char *concordstring[] = {
20 "kSOSConcordanceTrusted",
21 "kSOSConcordanceGenOld", // kSOSErrorReplay
22 "kSOSConcordanceNoUserSig", // kSOSErrorBadSignature
23 "kSOSConcordanceNoUserKey", // kSOSErrorNoKey
24 "kSOSConcordanceNoPeer", // kSOSErrorPeerNotFound
25 "kSOSConcordanceBadUserSig", // kSOSErrorBadSignature
26 "kSOSConcordanceBadPeerSig", // kSOSErrorBadSignature
27 "kSOSConcordanceNoPeerSig",
28 "kSOSConcordanceWeSigned",
31 @implementation SOSAccountTrustClassic (Circle)
33 -(bool) isInCircle:(CFErrorRef *)error
35 SOSCCStatus result = [self getCircleStatus:error];
37 if (result != kSOSCCInCircle) {
38 SOSErrorCreate(kSOSErrorNoCircle, error, NULL, CFSTR("Not in circle"));
45 -(bool) hasCircle:(CFErrorRef*) error
47 if (!self.trustedCircle)
48 SOSCreateErrorWithFormat(kSOSErrorNoCircle, NULL, error, NULL, CFSTR("No trusted circle"));
50 return self.trustedCircle != NULL;
53 -(SOSCCStatus) thisDeviceStatusInCircle:(SOSCircleRef) circle peer:(SOSPeerInfoRef) this_peer
56 return kSOSCCNotInCircle;
58 if (circle && SOSCircleCountPeers(circle) == 0)
59 return kSOSCCCircleAbsent;
63 if(SOSPeerInfoIsRetirementTicket(this_peer))
64 return kSOSCCNotInCircle;
66 if (SOSCircleHasPeer(circle, this_peer, NULL))
67 return kSOSCCInCircle;
69 if (SOSCircleHasApplicant(circle, this_peer, NULL))
70 return kSOSCCRequestPending;
73 return kSOSCCNotInCircle;
75 -(SOSCCStatus) getCircleStatus:(CFErrorRef*) error
77 return [self thisDeviceStatusInCircle:self.trustedCircle peer:self.peerInfo];
81 -(SOSCircleRef) getCircle:(CFErrorRef *)error
83 CFTypeRef entry = self.trustedCircle;
84 require_action_quiet(!isNull(entry), fail,
85 SOSCreateError(kSOSErrorIncompatibleCircle, CFSTR("Incompatible circle in KVS"), NULL, error));
86 return (SOSCircleRef) entry;
95 -(SOSCircleRef) ensureCircle:(SOSAccount*)a name:(CFStringRef)name err:(CFErrorRef *)error
97 CFErrorRef localError = NULL;
98 if (self.trustedCircle == NULL) {
99 self.trustedCircle = SOSCircleCreate(NULL, name, NULL);
100 a.key_interests_need_updating = true;
103 require_action_quiet(self.trustedCircle || !isSOSErrorCoded(localError, kSOSErrorIncompatibleCircle), fail,
104 if (error) { *error = localError; localError = NULL; });
107 CFReleaseNull(localError);
108 return self.trustedCircle;
113 switch(self.departureCode) {
114 case kSOSDiscoveredRetirement: /* Fallthrough */
115 case kSOSLostPrivateKey: /* Fallthrough */
116 case kSOSWithdrewMembership: /* Fallthrough */
117 case kSOSMembershipRevoked: /* Fallthrough */
118 case kSOSLeftUntrustedCircle:
120 case kSOSNeverAppliedToCircle: /* Fallthrough */
121 case kSOSNeverLeftCircle: /* Fallthrough */
127 /* This check is new to protect piggybacking by the current peer - in that case we have a remote peer signature that
128 can't have ghost cleanup changing the circle hash.
131 -(bool) ghostBustingOK:(SOSCircleRef) oldCircle updatingTo:(SOSCircleRef) newCircle {
133 // Preliminaries - we must have a peer and it must be in the newCircle in order to attempt busting
134 SOSFullPeerInfoRef me_full = self.fullPeerInfo;
135 if(!me_full) return false;
136 SOSPeerInfoRef me = SOSFullPeerInfoGetPeerInfo(me_full);
137 if(!me || (!SOSCircleHasPeer(newCircle, me, NULL) && !SOSCircleHasApplicant(newCircle, me, NULL))) return false;
139 CFStringRef myPid = SOSPeerInfoGetPeerID(me);
140 CFDictionaryRef newSigs = SOSCircleCopyAllSignatures(newCircle);
141 bool iSignedNew = CFDictionaryGetCountOfKey(newSigs, myPid);
142 long otherPeerSigCount = CFDictionaryGetCount(newSigs) - ((iSignedNew) ? 2: 1);
144 if (SOSCircleHasPeer(oldCircle, me, NULL)) { // If we're already in the old one we're not PBing
146 } else if (!iSignedNew) { // Piggybacking peers always have signed as part of genSigning - so this indicates we're safe to bust.
148 } else if(iSignedNew && otherPeerSigCount > 1) { // if others have seen this we're good to bust.
151 CFReleaseNull(newSigs);
155 // If this circle bears a signature from us and a newer gencount and it isn't our "current" circle, we're
156 // going to trust it. That's the signature of a piggybacked circle where we were the sponsor.
158 -(bool) checkForSponsorshipTrust:(SOSCircleRef) prospective_circle {
159 if(CFEqualSafe(self.trustedCircle, prospective_circle)) return false;
160 SecKeyRef myPubKey = SOSFullPeerInfoCopyPubKey(self.fullPeerInfo, NULL);
161 if(!myPubKey) return false;
162 if(SOSCircleVerify(prospective_circle, myPubKey, NULL) && SOSCircleIsOlderGeneration(self.trustedCircle, prospective_circle)) {
163 [self setTrustedCircle:prospective_circle];
169 static bool SOSCirclePeerOctagonKeysChanged(SOSPeerInfoRef oldPeer, SOSPeerInfoRef newPeer) {
170 bool oldHasOctagonBits = oldPeer && (SOSPeerInfoHasOctagonSigningPubKey(oldPeer) || SOSPeerInfoHasOctagonEncryptionPubKey(oldPeer));
171 bool newHasOctagonBits = newPeer && (SOSPeerInfoHasOctagonSigningPubKey(newPeer) || SOSPeerInfoHasOctagonEncryptionPubKey(newPeer));
174 if(newPeer && newHasOctagonBits) {
177 // New peer to circle has no octagon bits: no change
183 // We removed a peer. This is an octagon bits change if the old peer had octagon bits
184 return oldHasOctagonBits;
187 // This is a peer update: Did the keys change?
188 if(!oldHasOctagonBits && !newHasOctagonBits) {
189 // both peers have no keys: no change
193 bool signingKeyChanged = CFEqualSafe(SOSPeerInfoCopyOctagonSigningPublicKey(oldPeer, NULL), SOSPeerInfoCopyOctagonSigningPublicKey(newPeer, NULL));
194 bool encryptionKeyChanged = CFEqualSafe(SOSPeerInfoCopyOctagonEncryptionPublicKey(oldPeer, NULL), SOSPeerInfoCopyOctagonEncryptionPublicKey(newPeer, NULL));
196 return signingKeyChanged || encryptionKeyChanged;
200 static bool SOSCircleHasUpdatedPeerInfoWithOctagonKey(SOSCircleRef oldCircle, SOSCircleRef newCircle)
202 __block bool hasUpdated = false;
203 SOSCircleForEachPeer(oldCircle, ^(SOSPeerInfoRef oldPeer) {
204 SOSPeerInfoRef equivalentNewPeer = SOSCircleCopyPeerWithID(newCircle, SOSPeerInfoGetPeerID(oldPeer), NULL);
205 hasUpdated |= SOSCirclePeerOctagonKeysChanged(oldPeer, equivalentNewPeer);
208 SOSCircleForEachPeer(newCircle, ^(SOSPeerInfoRef newPeer) {
209 SOSPeerInfoRef equivalentOldPeer = SOSCircleCopyPeerWithID(oldCircle, SOSPeerInfoGetPeerID(newPeer), NULL);
210 hasUpdated |= SOSCirclePeerOctagonKeysChanged(equivalentOldPeer, newPeer);
216 -(bool) handleUpdateCircle:(SOSCircleRef) prospective_circle transport:(SOSKVSCircleStorageTransport*)circleTransport update:(bool) writeUpdate err:(CFErrorRef*)error
219 bool haveOldCircle = true;
220 const char *local_remote = writeUpdate ? "local": "remote";
222 SOSAccount* account = [circleTransport getAccount];
224 secnotice("signing", "start:[%s]", local_remote);
225 if (!account.accountKey || !account.accountKeyIsTrusted) {
226 SOSCreateError(kSOSErrorPublicKeyAbsent, CFSTR("Can't handle updates with no trusted public key here"), NULL, error);
230 if (!prospective_circle) {
231 secerror("##### Can't update to a NULL circle ######");
232 return false; // Can't update one we don't have.
235 // If this is a remote circle, check to see if this is our first opportunity to trust a circle where we
236 // sponsored the only signer.
237 if(!writeUpdate && [ self checkForSponsorshipTrust: prospective_circle ]){
238 SOSCCEnsurePeerRegistration();
239 account.key_interests_need_updating = true;
244 CFStringRef newCircleName = SOSCircleGetName(prospective_circle);
246 SOSCircleRef oldCircle = self.trustedCircle;
247 SOSCircleRef emptyCircle = NULL;
249 if(oldCircle == NULL) {
250 SOSCreateErrorWithFormat(kSOSErrorIncompatibleCircle, NULL, error, NULL, CFSTR("Current Entry is NULL; rejecting %@"), prospective_circle);
251 secerror("##### Can't replace circle - we don't care about it ######");
254 if (CFGetTypeID(oldCircle) != SOSCircleGetTypeID()) {
255 secdebug("signing", ">>>>>>>>>>>>>>> Non-Circle Circle found <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<");
256 // We don't know what is in our table, likely it was kCFNull indicating we didn't
257 // understand a circle that came by. We seem to like this one lets make our entry be empty circle
258 emptyCircle = SOSCircleCreate(kCFAllocatorDefault, newCircleName, NULL);
259 oldCircle = emptyCircle;
260 haveOldCircle = false;
261 // And we're paranoid, drop our old peer info if for some reason we didn't before.
262 // SOSAccountDestroyCirclePeerInfo(account, oldCircle, NULL);
266 SOSAccountScanForRetired(account, prospective_circle, error);
267 SOSCircleRef newCircle = SOSAccountCloneCircleWithRetirement(account, prospective_circle, error);
268 if(!newCircle) return false;
269 if([self ghostBustingOK: oldCircle updatingTo:newCircle]) {
270 SOSCircleRef ghostCleaned = SOSAccountCloneCircleWithoutMyGhosts(account, newCircle);
272 CFRetainAssign(newCircle, ghostCleaned);
277 SOSFullPeerInfoRef me_full = self.fullPeerInfo;
278 SOSPeerInfoRef me = SOSFullPeerInfoGetPeerInfo(me_full);
279 CFStringRef myPeerID = SOSPeerInfoGetPeerID(me);
280 myPeerID = (myPeerID) ? myPeerID: CFSTR("No Peer");
282 if (me && SOSCircleUpdatePeerInfo(newCircle, me)) {
283 writeUpdate = true; // If we update our peer in the new circle we should write it if we accept it.
294 static const char *actionstring[] = {
295 "accept", "countersign", "leave", "revert", "ignore",
298 circle_action_t circle_action = ignore;
299 enum DepartureReason leave_reason = kSOSNeverLeftCircle;
301 SecKeyRef old_circle_key = NULL;
302 if(SOSCircleVerify(oldCircle, account.accountKey, NULL)){
303 old_circle_key = account.accountKey;
305 else if(account.previousAccountKey && SOSCircleVerify(oldCircle, account.previousAccountKey, NULL)){
306 old_circle_key = account.previousAccountKey;
309 bool userTrustedOldCircle = (old_circle_key != NULL) && haveOldCircle;
311 SOSConcordanceStatus concstat =
312 SOSCircleConcordanceTrust(oldCircle, newCircle,
313 old_circle_key, account.accountKey,
316 CFStringRef concStr = NULL;
318 case kSOSConcordanceTrusted:
319 circle_action = countersign;
320 concStr = CFSTR("Trusted");
322 case kSOSConcordanceGenOld:
323 circle_action = userTrustedOldCircle ? revert : ignore;
324 concStr = CFSTR("Generation Old");
326 case kSOSConcordanceBadUserSig:
327 case kSOSConcordanceBadPeerSig:
328 circle_action = userTrustedOldCircle ? revert : accept;
329 concStr = CFSTR("Bad Signature");
331 case kSOSConcordanceNoUserSig:
332 circle_action = userTrustedOldCircle ? revert : accept;
333 concStr = CFSTR("No User Signature");
335 case kSOSConcordanceNoPeerSig:
336 circle_action = accept; // We might like this one eventually but don't countersign.
337 concStr = CFSTR("No trusted peer signature");
338 secnotice("signing", "##### No trusted peer signature found, accepting hoping for concordance later");
340 case kSOSConcordanceNoPeer:
341 circle_action = leave;
342 leave_reason = kSOSLeftUntrustedCircle;
343 concStr = CFSTR("No trusted peer left");
345 case kSOSConcordanceNoUserKey:
346 secerror("##### No User Public Key Available, this shouldn't ever happen!!!");
350 secerror("##### Bad Error Return from ConcordanceTrust");
355 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);
357 SOSCircleRef circleToPush = NULL;
359 if (circle_action == leave) {
360 circle_action = ignore; (void) circle_action; // Acknowledge this is a dead store.
362 if (me && SOSCircleHasPeer(oldCircle, me, NULL)) {
363 secnotice("account", "Leaving circle with peer %@", me);
364 debugDumpCircle(CFSTR("oldCircle"), oldCircle);
365 debugDumpCircle(CFSTR("newCircle"), newCircle);
366 debugDumpCircle(CFSTR("prospective_circle"), prospective_circle);
367 secnotice("account", "Key state: accountKey %@, previousAccountKey %@, old_circle_key %@",
368 account.accountKey, account.previousAccountKey, old_circle_key);
370 if (sosAccountLeaveCircle(account, newCircle, error)) {
371 secnotice("leaveCircle", "Leaving circle by newcircle state");
372 circleToPush = newCircle;
374 secnotice("signing", "Can't leave circle, but dumping identities");
377 self.departureCode = leave_reason;
378 circle_action = accept;
382 // We are not in this circle, but we need to update account with it, since we got it from cloud
383 secnotice("signing", "We are not in this circle, but we need to update account with it");
384 debugDumpCircle(CFSTR("oldCircle"), oldCircle);
385 debugDumpCircle(CFSTR("newCircle"), newCircle);
386 debugDumpCircle(CFSTR("prospective_circle"), prospective_circle);
387 circle_action = accept;
391 if (circle_action == countersign) {
392 if (me && SOSCircleHasPeer(newCircle, me, NULL)) {
393 if (SOSCircleVerifyPeerSigned(newCircle, me, NULL)) {
394 secnotice("signing", "Already concur with the new circle");
396 CFErrorRef signing_error = NULL;
398 if (me_full && SOSCircleConcordanceSign(newCircle, me_full, &signing_error)) {
399 circleToPush = newCircle;
400 secnotice("signing", "Concurred with new circle");
402 secerror("Failed to concurrence sign, error: %@", signing_error);
405 CFReleaseSafe(signing_error);
408 secnotice("signing", "Not countersigning, not in new circle");
409 debugDumpCircle(CFSTR("circle to countersign"), newCircle);
411 circle_action = accept;
414 if (circle_action == accept) {
415 if(SOSCircleHasUpdatedPeerInfoWithOctagonKey(oldCircle, newCircle)){
416 notify_post(kSOSCCCircleOctagonKeysChangedNotification);
418 if (me && SOSCircleHasActivePeer(oldCircle, me, NULL) && !SOSCircleHasPeer(newCircle, me, NULL)) {
419 // Don't destroy evidence of other code determining reason for leaving.
420 if(![self hasLeft]) self.departureCode = kSOSMembershipRevoked;
421 secnotice("account", "Member of old circle but not of new circle");
422 debugDumpCircle(CFSTR("oldCircle"), oldCircle);
423 debugDumpCircle(CFSTR("newCircle"), newCircle);
427 && SOSCircleHasActivePeer(oldCircle, me, NULL)
428 && !(SOSCircleCountPeers(oldCircle) == 1 && SOSCircleHasPeer(oldCircle, me, NULL)) // If it was our offering, don't change ID to avoid ghosts
429 && !SOSCircleHasPeer(newCircle, me, NULL) && !SOSCircleHasApplicant(newCircle, me, NULL)) {
430 secnotice("circle", "Purging my peer (ID: %@) for circle '%@'!!!", SOSPeerInfoGetPeerID(me), SOSCircleGetName(oldCircle));
431 if (self.fullPeerInfo)
432 SOSFullPeerInfoPurgePersistentKey(self.fullPeerInfo, NULL);
437 if (me && SOSCircleHasRejectedApplicant(newCircle, me, NULL)) {
438 SOSPeerInfoRef reject = SOSCircleCopyRejectedApplicant(newCircle, me, NULL);
439 if(CFEqualSafe(reject, me) && SOSPeerInfoApplicationVerify(me, account.accountKey, NULL)) {
440 secnotice("circle", "Rejected, Purging my applicant peer (ID: %@) for circle '%@'", SOSPeerInfoGetPeerID(me), SOSCircleGetName(oldCircle));
441 debugDumpCircle(CFSTR("oldCircle"), oldCircle);
442 debugDumpCircle(CFSTR("newCircle"), newCircle);
443 if (self.fullPeerInfo)
444 SOSFullPeerInfoPurgePersistentKey(self.fullPeerInfo, NULL);
448 secnotice("circle", "Rejected, Reapplying (ID: %@) for circle '%@'", SOSPeerInfoGetPeerID(me), SOSCircleGetName(oldCircle));
449 debugDumpCircle(CFSTR("oldCircle"), oldCircle);
450 debugDumpCircle(CFSTR("newCircle"), newCircle);
451 SOSCircleRequestReadmission(newCircle, account.accountKey, me, NULL);
456 CFRetainSafe(oldCircle);
457 account.previousAccountKey = account.accountKey;
459 secnotice("signing", "%@, Accepting new circle", concStr);
460 if (circle_action == accept) {
461 [self setTrustedCircle:newCircle];
464 if (me && account.accountKeyIsTrusted
465 && SOSCircleHasApplicant(oldCircle, me, NULL)
466 && SOSCircleCountPeers(newCircle) > 0
467 && !SOSCircleHasPeer(newCircle, me, NULL) && !SOSCircleHasApplicant(newCircle, me, NULL)) {
468 // We weren't rejected (above would have set me to NULL.
469 // We were applying and we weren't accepted.
470 // Our application is declared lost, let us reapply.
472 secnotice("signing", "requesting readmission to new circle");
473 if (SOSCircleRequestReadmission(newCircle, account.accountKey, me, NULL))
477 if (me && SOSCircleHasActivePeer(oldCircle, me, NULL)) {
478 [account.trust cleanupRetirementTickets:account circle:oldCircle time:RETIREMENT_FINALIZATION_SECONDS err:NULL];
481 SOSAccountNotifyOfChange(account, oldCircle, newCircle);
483 CFReleaseNull(oldCircle);
486 circleToPush = newCircle;
487 account.key_interests_need_updating = true;
491 * In the revert section we'll guard the KVS idea of circles by rejecting "bad" new circles
492 * and pushing our current view of the circle (oldCircle). We'll only do this if we actually
493 * are a member of oldCircle - never for an empty circle.
496 if (circle_action == revert) {
497 if(haveOldCircle && me && SOSCircleHasActivePeer(oldCircle, me, NULL)) {
498 secnotice("signing", "%@, Rejecting new circle, re-publishing old circle", concStr);
499 debugDumpCircle(CFSTR("oldCircle"), oldCircle);
500 debugDumpCircle(CFSTR("newCircle"), newCircle);
501 circleToPush = oldCircle;
502 [self setTrustedCircle:oldCircle];
504 secnotice("canary", "%@, Rejecting: new circle Have no old circle - would reset", concStr);
509 if (circleToPush != NULL) {
510 secnotice("signing", "Pushing:[%s]", local_remote);
511 CFDataRef circle_data = SOSCircleCopyEncodedData(circleToPush, kCFAllocatorDefault, error);
514 // Ensure we flush changes
515 account.circle_rings_retirements_need_attention = true;
517 //posting new circle to peers
518 success &= [circleTransport postCircle:SOSCircleGetName(circleToPush) circleData:circle_data err:error];
519 //cleanup old KVS keys
520 SOSAccountCleanupAllKVSKeys(account, error);
524 CFReleaseNull(circle_data);
526 CFReleaseSafe(newCircle);
527 CFReleaseNull(emptyCircle);
532 -(bool) updateCircleFromRemote:(SOSKVSCircleStorageTransport*)circleTransport newCircle:(SOSCircleRef)newCircle err:(CFErrorRef*)error
534 return [self handleUpdateCircle:newCircle transport:circleTransport update:false err:error];
537 -(bool) updateCircle:(SOSKVSCircleStorageTransport*)circleTransport newCircle:(SOSCircleRef) newCircle err:(CFErrorRef*)error
539 return [self handleUpdateCircle:newCircle transport:circleTransport update:true err:error];
542 -(bool) modifyCircle:(SOSKVSCircleStorageTransport*)circleTransport err:(CFErrorRef*)error action:(SOSModifyCircleBlock)block
544 bool success = false;
545 SOSCircleRef circleCopy = NULL;
546 require_action_quiet(self.trustedCircle, fail, SOSErrorCreate(kSOSErrorNoCircle, error, NULL, CFSTR("No circle to get peer key from")));
548 circleCopy = SOSCircleCopyCircle(kCFAllocatorDefault, self.trustedCircle, error);
549 require_quiet(circleCopy, fail);
552 require_quiet(block(circleCopy), fail);
554 success = [self updateCircle:circleTransport newCircle:circleCopy err:error];
557 CFReleaseSafe(circleCopy);
562 -(void) generationSignatureUpdateWith:(SOSAccount*)account key:(SecKeyRef) privKey
564 if (self.trustedCircle && self.fullPeerInfo) {
565 [self modifyCircle:account.circle_transport err:NULL action:^(SOSCircleRef circle) {
566 SOSPeerInfoRef myPI = account.peerInfo;
567 bool iAmPeer = SOSCircleHasPeer(circle, myPI, NULL);
568 bool change = SOSCircleUpdatePeerInfo(circle, myPI);
569 if(iAmPeer && !SOSCircleVerify(circle, account.accountKey, NULL)) {
570 change |= [self upgradeiCloudIdentity:circle privKey:privKey];
571 [self removeInvalidApplications:circle userPublic:account.accountKey];
572 change |= SOSCircleGenerationSign(circle, privKey, self.fullPeerInfo, NULL);
573 [self setDepartureCode:kSOSNeverLeftCircle];
575 SOSFullPeerInfoRef icfpi = SOSCircleCopyiCloudFullPeerInfoRef(circle, NULL);
577 SOSAccountRemoveIncompleteiCloudIdentities(account, circle, privKey, NULL);
578 change |= [self addiCloudIdentity:circle key:privKey err:NULL];
580 CFReleaseNull(icfpi);
583 secnotice("updatingGenSignature", "we changed the circle? %@", change ? CFSTR("YES") : CFSTR("NO"));
589 -(void) forEachCirclePeerExceptMe:(SOSIteratePeerBlock)block
591 if (self.trustedCircle && self.peerInfo) {
592 NSString* myPi_id = self.peerID;
593 SOSCircleForEachPeer(self.trustedCircle, ^(SOSPeerInfoRef peer) {
594 CFStringRef peerID = SOSPeerInfoGetPeerID(peer);
595 if (peerID && ![myPi_id isEqualToString:(__bridge NSString*) peerID]) {
601 -(bool) leaveCircle:(SOSAccount*)account err:(CFErrorRef*) error
604 secnotice("leaveCircle", "Leaving circle by client request");
605 result &= [self modifyCircle:account.circle_transport err:error action:^(SOSCircleRef circle) {
606 return sosAccountLeaveCircle(account, circle, error);
609 self.departureCode = kSOSWithdrewMembership;
614 -(bool) resetToOffering:(SOSAccountTransaction*) aTxn key:(SecKeyRef)userKey err:(CFErrorRef*) error
616 SOSFullPeerInfoPurgePersistentKey(self.fullPeerInfo, NULL);
617 self.fullPeerInfo = nil;
619 secnotice("resetToOffering", "Resetting circle to offering by request from client");
621 return userKey && [self resetCircleToOffering:aTxn userKey:userKey err:error];
625 -(bool) resetCircleToOffering:(SOSAccountTransaction*) aTxn userKey:(SecKeyRef)user_key err:(CFErrorRef *)error
629 SOSAccount* account = aTxn.account;
630 if(![self hasCircle:error])
633 if(![self ensureFullPeerAvailable:(__bridge CFDictionaryRef)(account.gestalt) deviceID:(__bridge CFStringRef)(account.deviceID) backupKey:(__bridge CFDataRef)(account.backup_key) err:error])
636 (void)[self resetAllRings:account err:error];
638 [self modifyCircle:account.circle_transport err:error action:^bool(SOSCircleRef circle) {
640 SOSFullPeerInfoRef cloud_identity = NULL;
641 CFErrorRef localError = NULL;
643 require_quiet(SOSCircleResetToOffering(circle, user_key, self.fullPeerInfo, &localError), err_out);
645 self.departureCode = kSOSNeverLeftCircle;
647 require_quiet([self addEscrowToPeerInfo:self.fullPeerInfo err:error], err_out);
649 require_quiet([self addiCloudIdentity:circle key:user_key err:error], err_out);
651 SOSAccountPublishCloudParameters(account, NULL);
655 secerror("error resetting circle (%@) to offering: %@", circle, localError);
656 if (localError && error && *error == NULL) {
660 CFReleaseNull(localError);
661 CFReleaseNull(cloud_identity);
665 [self setValueInExpansion:kSOSUnsyncedViewsKey value:kCFBooleanTrue err:NULL];
666 SOSAccountUpdateOutOfSyncViews(aTxn, SOSViewsGetAllCurrent());
673 void SOSAccountForEachCirclePeerExceptMe(SOSAccount* account, void (^action)(SOSPeerInfoRef peer)) {
674 SOSPeerInfoRef myPi = account.peerInfo;
675 SOSCircleRef circle = NULL;
677 SOSAccountTrustClassic *trust = account.trust;
678 circle = trust.trustedCircle;
679 if (circle && myPi) {
680 CFStringRef myPi_id = SOSPeerInfoGetPeerID(myPi);
681 SOSCircleForEachPeer(circle, ^(SOSPeerInfoRef peer) {
682 CFStringRef peerID = SOSPeerInfoGetPeerID(peer);
683 if (peerID && !CFEqual(peerID, myPi_id)) {
690 -(bool) joinCircle:(SOSAccountTransaction*) aTxn userKey:(SecKeyRef) user_key useCloudPeer:(bool) use_cloud_peer err:(CFErrorRef*) error
692 __block bool result = false;
693 __block SOSFullPeerInfoRef cloud_full_peer = NULL;
694 __block SOSAccount* account = aTxn.account;
695 require_action_quiet(self.trustedCircle, fail, SOSCreateErrorWithFormat(kSOSErrorPeerNotFound, NULL, error, NULL, CFSTR("Don't have circle when joining???")));
696 require_quiet([self ensureFullPeerAvailable:(__bridge CFDictionaryRef)account.gestalt deviceID:(__bridge CFStringRef)account.deviceID backupKey:(__bridge CFDataRef)account.backup_key err:error], fail);
698 if (SOSCircleCountPeers(self.trustedCircle) == 0 || SOSAccountGhostResultsInReset(account)) {
699 secnotice("resetToOffering", "Resetting circle to offering since there are no peers");
700 // this also clears initial sync data
701 result = [self resetCircleToOffering:aTxn userKey:user_key err:error];
703 [self setValueInExpansion:kSOSUnsyncedViewsKey value:kCFBooleanTrue err:NULL];
705 if (use_cloud_peer) {
706 cloud_full_peer = SOSCircleCopyiCloudFullPeerInfoRef(self.trustedCircle, NULL);
709 [self modifyCircle: account.circle_transport err:error action:^(SOSCircleRef circle) {
710 result = SOSAccountAddEscrowToPeerInfo(account, self.fullPeerInfo, error);
711 result &= SOSCircleRequestAdmission(circle, user_key, self.fullPeerInfo, error);
712 self.departureCode = kSOSNeverLeftCircle;
713 if(result && cloud_full_peer) {
714 CFErrorRef localError = NULL;
715 CFStringRef cloudid = SOSPeerInfoGetPeerID(SOSFullPeerInfoGetPeerInfo(cloud_full_peer));
716 require_quiet(cloudid, finish);
717 require_quiet(SOSCircleHasActivePeerWithID(circle, cloudid, &localError), finish);
718 require_quiet(SOSCircleAcceptRequest(circle, user_key, cloud_full_peer, self.peerInfo, &localError), finish);
722 secerror("Failed to join with cloud identity: %@", localError);
723 CFReleaseNull(localError);
729 if (use_cloud_peer) {
730 SOSAccountUpdateOutOfSyncViews(aTxn, SOSViewsGetAllCurrent());
735 CFReleaseNull(cloud_full_peer);