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) isInCircleOnly:(CFErrorRef *)error
23 SOSCCStatus result = [self getCircleStatusOnly: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) getCircleStatusOnly:(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) handleUpdateCircleWithAnalytics:(SOSCircleRef) prospective_circle transport:(SOSCircleStorageTransport*)circleTransport update:(bool) writeUpdate parentEvent:(NSData*)parentEvent err:(CFErrorRef*)error
228 bool haveOldCircle = true;
229 const char *local_remote = writeUpdate ? "local": "remote";
231 NSError* localError = nil;
232 SFSignInAnalytics* parent = [NSKeyedUnarchiver unarchivedObjectOfClass:[SFSignInAnalytics class] fromData:parentEvent error:&localError];
234 SOSAccount* account = [circleTransport getAccount];
236 secnotice("signing", "start:[%s]", local_remote);
237 if (!account.accountKey || !account.accountKeyIsTrusted) {
238 SOSCreateError(kSOSErrorPublicKeyAbsent, CFSTR("Can't handle updates with no trusted public key here"), NULL, error);
242 if (!prospective_circle) {
243 secerror("##### Can't update to a NULL circle ######");
244 return false; // Can't update one we don't have.
247 // If this is a remote circle, check to see if this is our first opportunity to trust a circle where we
248 // sponsored the only signer.
249 if(!writeUpdate && [ self checkForSponsorshipTrust: prospective_circle ]){
250 SOSCCEnsurePeerRegistration();
251 secnotice("circleop", "Setting key_interests_need_updating to true in handleUpdateCircle");
252 account.key_interests_need_updating = true;
257 CFStringRef newCircleName = SOSCircleGetName(prospective_circle);
259 SOSCircleRef oldCircle = self.trustedCircle;
260 SOSCircleRef emptyCircle = NULL;
262 if(oldCircle == NULL) {
263 SOSCreateErrorWithFormat(kSOSErrorIncompatibleCircle, NULL, error, NULL, CFSTR("Current Entry is NULL; rejecting %@"), prospective_circle);
264 secerror("##### Can't replace circle - we don't care about it ######");
267 if (CFGetTypeID(oldCircle) != SOSCircleGetTypeID()) {
268 secdebug("signing", ">>>>>>>>>>>>>>> Non-Circle Circle found <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<");
269 // We don't know what is in our table, likely it was kCFNull indicating we didn't
270 // understand a circle that came by. We seem to like this one lets make our entry be empty circle
271 emptyCircle = SOSCircleCreate(kCFAllocatorDefault, newCircleName, NULL);
272 oldCircle = emptyCircle;
273 haveOldCircle = false;
274 // And we're paranoid, drop our old peer info if for some reason we didn't before.
275 // SOSAccountDestroyCirclePeerInfo(account, oldCircle, NULL);
278 CFErrorRef retiredError = NULL;
279 SFSignInAnalytics *scanForRetiredEvent = [parent newSubTaskForEvent:@"scanForRetiredEvent"];
280 SOSAccountScanForRetired(account, prospective_circle, &retiredError);
282 [scanForRetiredEvent logRecoverableError:(__bridge NSError*)retiredError];
283 secerror("scan for retired error: %@", retiredError);
285 *error = retiredError;
287 CFReleaseNull(retiredError);
290 [scanForRetiredEvent stopWithAttributes:nil];
292 SOSCircleRef newCircle = SOSAccountCloneCircleWithRetirement(account, prospective_circle, error);
293 if(!newCircle) return false;
295 SFSignInAnalytics *ghostBustingEvent = [parent newSubTaskForEvent:@"ghostBustingEvent"];
296 if([self ghostBustingOK: oldCircle updatingTo:newCircle]) {
297 SOSCircleRef ghostCleaned = SOSAccountCloneCircleWithoutMyGhosts(account, newCircle);
299 CFRetainAssign(newCircle, ghostCleaned);
303 [ghostBustingEvent stopWithAttributes:nil];
305 SOSFullPeerInfoRef me_full = self.fullPeerInfo;
306 SOSPeerInfoRef me = SOSFullPeerInfoGetPeerInfo(me_full);
307 CFStringRef myPeerID = SOSPeerInfoGetPeerID(me);
308 myPeerID = (myPeerID) ? myPeerID: CFSTR("No Peer");
310 if (me && SOSCircleUpdatePeerInfo(newCircle, me)) {
311 writeUpdate = true; // If we update our peer in the new circle we should write it if we accept it.
322 static const char *actionstring[] = {
323 "accept", "countersign", "leave", "revert", "ignore",
326 circle_action_t circle_action = ignore;
327 enum DepartureReason leave_reason = kSOSNeverLeftCircle;
329 SecKeyRef old_circle_key = NULL;
331 CFErrorRef verifyCircleError = NULL;
332 SFSignInAnalytics *verifyCircleEvent = [parent newSubTaskForEvent:@"verifyCircleEvent"];
333 if(SOSCircleVerify(oldCircle, account.accountKey, &verifyCircleError)){
334 old_circle_key = account.accountKey;
336 else if(account.previousAccountKey && SOSCircleVerify(oldCircle, account.previousAccountKey, &verifyCircleError)){
337 old_circle_key = account.previousAccountKey;
339 if(verifyCircleError){
340 [verifyCircleEvent logRecoverableError:(__bridge NSError*)verifyCircleError];
341 secerror("verifyCircle error: %@", verifyCircleError);
343 *error = verifyCircleError;
345 CFReleaseNull(verifyCircleError);
348 [verifyCircleEvent stopWithAttributes:nil];
350 bool userTrustedOldCircle = (old_circle_key != NULL) && haveOldCircle;
352 SFSignInAnalytics *concordanceTrustEvent = [parent newSubTaskForEvent:@"concordanceTrustEvent"];
353 CFErrorRef concordanceError = NULL;
354 SOSConcordanceStatus concstat =
355 SOSCircleConcordanceTrust(oldCircle, newCircle,
356 old_circle_key, account.accountKey,
357 me, &concordanceError);
358 if(concordanceError){
359 [concordanceTrustEvent logRecoverableError:(__bridge NSError*)concordanceError];
360 secerror("concordance trust error: %@", concordanceError);
362 *error = concordanceError;
364 CFReleaseNull(concordanceError);
367 [concordanceTrustEvent stopWithAttributes:nil];
369 CFStringRef concStr = NULL;
371 case kSOSConcordanceTrusted:
372 circle_action = countersign;
373 concStr = CFSTR("Trusted");
375 case kSOSConcordanceGenOld:
376 circle_action = userTrustedOldCircle ? revert : ignore;
377 concStr = CFSTR("Generation Old");
379 case kSOSConcordanceBadUserSig:
380 case kSOSConcordanceBadPeerSig:
381 circle_action = userTrustedOldCircle ? revert : accept;
382 concStr = CFSTR("Bad Signature");
384 case kSOSConcordanceNoUserSig:
385 circle_action = userTrustedOldCircle ? revert : accept;
386 concStr = CFSTR("No User Signature");
388 case kSOSConcordanceNoPeerSig:
389 circle_action = accept; // We might like this one eventually but don't countersign.
390 concStr = CFSTR("No trusted peer signature");
391 secnotice("signing", "##### No trusted peer signature found, accepting hoping for concordance later");
393 case kSOSConcordanceNoPeer:
394 circle_action = leave;
395 leave_reason = kSOSLeftUntrustedCircle;
396 concStr = CFSTR("No trusted peer left");
398 case kSOSConcordanceNoUserKey:
399 secerror("##### No User Public Key Available, this shouldn't ever happen!!!");
403 secerror("##### Bad Error Return from ConcordanceTrust");
408 secnotice("signing", "Decided on action [%s] based on concordance state [%@] and [%s] circle. My PeerID is %@", actionstring[circle_action], concStr, userTrustedOldCircle ? "trusted" : "untrusted", myPeerID);
410 SOSCircleRef circleToPush = NULL;
412 if (circle_action == leave) {
413 circle_action = ignore; (void) circle_action; // Acknowledge this is a dead store.
415 if (me && SOSCircleHasPeer(oldCircle, me, NULL)) {
416 secnotice("account", "Leaving circle with peer %@", me);
417 debugDumpCircle(CFSTR("oldCircle"), oldCircle);
418 debugDumpCircle(CFSTR("newCircle"), newCircle);
419 debugDumpCircle(CFSTR("prospective_circle"), prospective_circle);
420 secnotice("account", "Key state: accountKey %@, previousAccountKey %@, old_circle_key %@",
421 account.accountKey, account.previousAccountKey, old_circle_key);
423 if (sosAccountLeaveCircleWithAnalytics(account, newCircle, parentEvent, error)) {
424 secnotice("circleOps", "Leaving circle by newcircle state");
425 circleToPush = newCircle;
427 secnotice("signing", "Can't leave circle, but dumping identities");
430 self.departureCode = leave_reason;
431 circle_action = accept;
435 // We are not in this circle, but we need to update account with it, since we got it from cloud
436 secnotice("signing", "We are not in this circle, but we need to update account with it");
437 debugDumpCircle(CFSTR("oldCircle"), oldCircle);
438 debugDumpCircle(CFSTR("newCircle"), newCircle);
439 debugDumpCircle(CFSTR("prospective_circle"), prospective_circle);
440 circle_action = accept;
444 if (circle_action == countersign) {
445 if (me && SOSCircleHasPeer(newCircle, me, NULL)) {
446 SFSignInAnalytics *verifyPeerSignedEvent = [parent newSubTaskForEvent:@"verifyPeerSignedEvent"];
447 CFErrorRef verifyPeerSignedError = NULL;
448 if (SOSCircleVerifyPeerSigned(newCircle, me, &verifyPeerSignedError)) {
449 secnotice("signing", "Already concur with the new circle");
451 CFErrorRef signing_error = NULL;
452 SFSignInAnalytics *circleConcordanceSignEvent = [parent newSubTaskForEvent:@"circleConcordanceSignEvent"];
453 if (me_full && SOSCircleConcordanceSign(newCircle, me_full, &signing_error)) {
454 circleToPush = newCircle;
455 secnotice("signing", "Concurred with new circle");
457 secerror("Failed to concurrence sign, error: %@", signing_error);
461 [circleConcordanceSignEvent logRecoverableError:(__bridge NSError*)signing_error];
462 secerror("circle concordance sign error: %@", signing_error);
464 *error = signing_error;
466 CFReleaseNull(signing_error);
469 [circleConcordanceSignEvent stopWithAttributes:nil];
470 CFReleaseSafe(signing_error);
472 if(verifyPeerSignedError){
473 [verifyPeerSignedEvent logRecoverableError:(__bridge NSError*)verifyPeerSignedError];
474 secerror("verify peer signed error: %@", verifyPeerSignedError);
476 *error = verifyPeerSignedError;
478 CFReleaseNull(verifyPeerSignedError);
481 [verifyPeerSignedEvent stopWithAttributes:nil];
483 secnotice("signing", "Not countersigning, not in new circle");
485 circle_action = accept;
488 if (circle_action == accept) {
489 if(SOSCircleHasUpdatedPeerInfoWithOctagonKey(oldCircle, newCircle)){
490 notify_post(kSOSCCCircleOctagonKeysChangedNotification);
492 if (me && SOSCircleHasActivePeer(oldCircle, me, NULL) && !SOSCircleHasPeer(newCircle, me, NULL)) {
493 // Don't destroy evidence of other code determining reason for leaving.
494 if(![self hasLeft]) self.departureCode = kSOSMembershipRevoked;
495 secnotice("circleOps", "Member of old circle but not of new circle (%d)", self.departureCode);
496 debugDumpCircle(CFSTR("oldCircle"), oldCircle);
497 debugDumpCircle(CFSTR("newCircle"), newCircle);
501 && SOSCircleHasActivePeer(oldCircle, me, NULL)
502 && !(SOSCircleCountPeers(oldCircle) == 1 && SOSCircleHasPeer(oldCircle, me, NULL)) // If it was our offering, don't change ID to avoid ghosts
503 && !SOSCircleHasPeer(newCircle, me, NULL) && !SOSCircleHasApplicant(newCircle, me, NULL)) {
504 secnotice("circle", "Purging my peer (ID: %@) for circle '%@'!!!", SOSPeerInfoGetPeerID(me), SOSCircleGetName(oldCircle));
505 if (self.fullPeerInfo)
506 SOSFullPeerInfoPurgePersistentKey(self.fullPeerInfo, NULL);
511 if (me && SOSCircleHasRejectedApplicant(newCircle, me, NULL)) {
512 SOSPeerInfoRef reject = SOSCircleCopyRejectedApplicant(newCircle, me, NULL);
513 if(CFEqualSafe(reject, me) && SOSPeerInfoApplicationVerify(me, account.accountKey, NULL)) {
514 secnotice("circle", "Rejected, Purging my applicant peer (ID: %@) for circle '%@'", SOSPeerInfoGetPeerID(me), SOSCircleGetName(oldCircle));
515 debugDumpCircle(CFSTR("oldCircle"), oldCircle);
516 debugDumpCircle(CFSTR("newCircle"), newCircle);
517 if (self.fullPeerInfo)
518 SOSFullPeerInfoPurgePersistentKey(self.fullPeerInfo, NULL);
522 secnotice("circle", "Rejected, Reapplying (ID: %@) for circle '%@'", SOSPeerInfoGetPeerID(me), SOSCircleGetName(oldCircle));
523 debugDumpCircle(CFSTR("oldCircle"), oldCircle);
524 debugDumpCircle(CFSTR("newCircle"), newCircle);
525 SOSCircleRequestReadmission(newCircle, account.accountKey, me, NULL);
528 CFReleaseNull(reject);
531 CFRetainSafe(oldCircle);
532 account.previousAccountKey = account.accountKey;
534 secnotice("signing", "%@, Accepting new circle", concStr);
535 if (circle_action == accept) {
536 [self setTrustedCircle:newCircle];
539 if (me && account.accountKeyIsTrusted
540 && SOSCircleHasApplicant(oldCircle, me, NULL)
541 && SOSCircleCountPeers(newCircle) > 0
542 && !SOSCircleHasPeer(newCircle, me, NULL) && !SOSCircleHasApplicant(newCircle, me, NULL)) {
543 // We weren't rejected (above would have set me to NULL.
544 // We were applying and we weren't accepted.
545 // Our application is declared lost, let us reapply.
547 secnotice("signing", "requesting readmission to new circle");
548 if (SOSCircleRequestReadmission(newCircle, account.accountKey, me, NULL))
552 if (me && SOSCircleHasActivePeer(oldCircle, me, NULL)) {
553 SFSignInAnalytics *cleanupRetirementTicketsEvent = [parent newSubTaskForEvent:@"cleanupRetirementTicketsEvent"];
554 CFErrorRef cleanupError = NULL;
555 [account.trust cleanupRetirementTickets:account circle:oldCircle time:RETIREMENT_FINALIZATION_SECONDS err:&cleanupError];
557 [cleanupRetirementTicketsEvent logRecoverableError:(__bridge NSError*)cleanupError];
558 secerror("cleanup retirement tickets error: %@", cleanupError);
560 *error = cleanupError;
562 CFReleaseNull(cleanupError);
565 [cleanupRetirementTicketsEvent stopWithAttributes:nil];
568 SFSignInAnalytics *notifyOfChangeEvent = [parent newSubTaskForEvent:@"notifyOfChangeEvent"];
569 SOSAccountNotifyOfChange(account, oldCircle, newCircle);
570 [notifyOfChangeEvent stopWithAttributes:nil];
572 CFReleaseNull(oldCircle);
575 circleToPush = newCircle;
576 secnotice("circleop", "Setting key_interests_need_updating to true in handleUpdateCircle");
577 account.key_interests_need_updating = true;
581 * In the revert section we'll guard the KVS idea of circles by rejecting "bad" new circles
582 * and pushing our current view of the circle (oldCircle). We'll only do this if we actually
583 * are a member of oldCircle - never for an empty circle.
586 if (circle_action == revert) {
587 if(haveOldCircle && me && SOSCircleHasActivePeer(oldCircle, me, NULL)) {
588 secnotice("signing", "%@, Rejecting new circle, re-publishing old circle", concStr);
589 circleToPush = oldCircle;
590 [self setTrustedCircle:oldCircle];
592 secnotice("canary", "%@, Rejecting: new circle Have no old circle - would reset", concStr);
596 if (circleToPush != NULL) {
597 secnotice("signing", "Pushing:[%s]", local_remote);
598 CFDataRef circle_data = SOSCircleCopyEncodedData(circleToPush, kCFAllocatorDefault, error);
601 // Ensure we flush changes
602 account.circle_rings_retirements_need_attention = true;
604 //posting new circle to peers
605 SFSignInAnalytics *postCircleEvent = [parent newSubTaskForEvent:@"postCircleEvent"];
606 CFErrorRef postCircleError = NULL;
607 success &= [circleTransport postCircle:SOSCircleGetName(circleToPush) circleData:circle_data err:&postCircleError];
609 [postCircleEvent logRecoverableError:(__bridge NSError*)postCircleError];
610 secerror("posting circle failed: %@", postCircleError);
612 *error = postCircleError;
614 CFReleaseNull(postCircleError);
617 [postCircleEvent stopWithAttributes:nil];
621 CFReleaseNull(circle_data);
623 CFReleaseSafe(newCircle);
624 CFReleaseNull(emptyCircle);
626 // There are errors collected above that are soft (worked around)
627 if(success && error && *error) {
628 CFReleaseNull(*error);
634 -(bool) handleUpdateCircle:(SOSCircleRef) prospective_circle transport:(SOSKVSCircleStorageTransport*)circleTransport update:(bool) writeUpdate err:(CFErrorRef*)error
637 bool haveOldCircle = true;
638 const char *local_remote = writeUpdate ? "local": "remote";
640 SOSAccount* account = [circleTransport getAccount];
642 secnotice("signing", "start:[%s]", local_remote);
643 if (!account.accountKey || !account.accountKeyIsTrusted) {
644 SOSCreateError(kSOSErrorPublicKeyAbsent, CFSTR("Can't handle updates with no trusted public key here"), NULL, error);
648 if (!prospective_circle) {
649 secerror("##### Can't update to a NULL circle ######");
650 return false; // Can't update one we don't have.
653 // If this is a remote circle, check to see if this is our first opportunity to trust a circle where we
654 // sponsored the only signer.
655 if(!writeUpdate && [ self checkForSponsorshipTrust: prospective_circle ]){
656 SOSCCEnsurePeerRegistration();
657 secnotice("circleop", "Setting key_interests_need_updating to true in handleUpdateCircle");
658 account.key_interests_need_updating = true;
663 CFStringRef newCircleName = SOSCircleGetName(prospective_circle);
665 SOSCircleRef oldCircle = self.trustedCircle;
666 SOSCircleRef emptyCircle = NULL;
668 if(oldCircle == NULL) {
669 SOSCreateErrorWithFormat(kSOSErrorIncompatibleCircle, NULL, error, NULL, CFSTR("Current Entry is NULL; rejecting %@"), prospective_circle);
670 secerror("##### Can't replace circle - we don't care about it ######");
673 if (CFGetTypeID(oldCircle) != SOSCircleGetTypeID()) {
674 secdebug("signing", ">>>>>>>>>>>>>>> Non-Circle Circle found <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<");
675 // We don't know what is in our table, likely it was kCFNull indicating we didn't
676 // understand a circle that came by. We seem to like this one lets make our entry be empty circle
677 emptyCircle = SOSCircleCreate(kCFAllocatorDefault, newCircleName, NULL);
678 oldCircle = emptyCircle;
679 haveOldCircle = false;
680 // And we're paranoid, drop our old peer info if for some reason we didn't before.
681 // SOSAccountDestroyCirclePeerInfo(account, oldCircle, NULL);
685 SOSAccountScanForRetired(account, prospective_circle, error);
686 SOSCircleRef newCircle = SOSAccountCloneCircleWithRetirement(account, prospective_circle, error);
687 if(!newCircle) return false;
688 if([self ghostBustingOK: oldCircle updatingTo:newCircle]) {
689 SOSCircleRef ghostCleaned = SOSAccountCloneCircleWithoutMyGhosts(account, newCircle);
691 CFRetainAssign(newCircle, ghostCleaned);
696 SOSFullPeerInfoRef me_full = self.fullPeerInfo;
697 SOSPeerInfoRef me = SOSFullPeerInfoGetPeerInfo(me_full);
698 CFStringRef myPeerID = SOSPeerInfoGetPeerID(me);
699 myPeerID = (myPeerID) ? myPeerID: CFSTR("No Peer");
701 if (me && SOSCircleUpdatePeerInfo(newCircle, me)) {
702 writeUpdate = true; // If we update our peer in the new circle we should write it if we accept it.
713 static const char *actionstring[] = {
714 "accept", "countersign", "leave", "revert", "ignore",
717 circle_action_t circle_action = ignore;
718 enum DepartureReason leave_reason = kSOSNeverLeftCircle;
720 SecKeyRef old_circle_key = NULL;
721 if(SOSCircleVerify(oldCircle, account.accountKey, NULL)){
722 old_circle_key = account.accountKey;
724 else if(account.previousAccountKey && SOSCircleVerify(oldCircle, account.previousAccountKey, NULL)){
725 old_circle_key = account.previousAccountKey;
728 bool userTrustedOldCircle = (old_circle_key != NULL) && haveOldCircle;
730 SOSConcordanceStatus concstat =
731 SOSCircleConcordanceTrust(oldCircle, newCircle,
732 old_circle_key, account.accountKey,
735 CFStringRef concStr = NULL;
737 case kSOSConcordanceTrusted:
738 circle_action = countersign;
739 concStr = CFSTR("Trusted");
741 case kSOSConcordanceGenOld:
742 circle_action = userTrustedOldCircle ? revert : ignore;
743 concStr = CFSTR("Generation Old");
745 case kSOSConcordanceBadUserSig:
746 case kSOSConcordanceBadPeerSig:
747 circle_action = userTrustedOldCircle ? revert : accept;
748 concStr = CFSTR("Bad Signature");
750 case kSOSConcordanceNoUserSig:
751 circle_action = userTrustedOldCircle ? revert : accept;
752 concStr = CFSTR("No User Signature");
754 case kSOSConcordanceNoPeerSig:
755 circle_action = accept; // We might like this one eventually but don't countersign.
756 concStr = CFSTR("No trusted peer signature");
757 secnotice("signing", "##### No trusted peer signature found, accepting hoping for concordance later");
759 case kSOSConcordanceNoPeer:
760 circle_action = leave;
761 leave_reason = kSOSLeftUntrustedCircle;
762 concStr = CFSTR("No trusted peer left");
764 case kSOSConcordanceNoUserKey:
765 secerror("##### No User Public Key Available, this shouldn't ever happen!!!");
769 secerror("##### Bad Error Return from ConcordanceTrust");
774 secnotice("signing", "Decided on action [%s] based on concordance state [%@] and [%s] circle. My PeerID is %@", actionstring[circle_action], concStr, userTrustedOldCircle ? "trusted" : "untrusted", myPeerID);
776 SOSCircleRef circleToPush = NULL;
778 if (circle_action == leave) {
779 circle_action = ignore; (void) circle_action; // Acknowledge this is a dead store.
781 if (me && SOSCircleHasPeer(oldCircle, me, NULL)) {
782 secnotice("account", "Leaving circle with peer %@", me);
783 debugDumpCircle(CFSTR("oldCircle"), oldCircle);
784 debugDumpCircle(CFSTR("newCircle"), newCircle);
785 debugDumpCircle(CFSTR("prospective_circle"), prospective_circle);
786 secnotice("account", "Key state: accountKey %@, previousAccountKey %@, old_circle_key %@",
787 account.accountKey, account.previousAccountKey, old_circle_key);
789 if (sosAccountLeaveCircle(account, newCircle, error)) {
790 secnotice("circleOps", "Leaving circle by newcircle state");
791 circleToPush = newCircle;
793 secnotice("signing", "Can't leave circle, but dumping identities");
796 self.departureCode = leave_reason;
797 circle_action = accept;
801 // We are not in this circle, but we need to update account with it, since we got it from cloud
802 secnotice("signing", "We are not in this circle, but we need to update account with it");
803 debugDumpCircle(CFSTR("oldCircle"), oldCircle);
804 debugDumpCircle(CFSTR("newCircle"), newCircle);
805 debugDumpCircle(CFSTR("prospective_circle"), prospective_circle);
806 circle_action = accept;
810 if (circle_action == countersign) {
811 if (me && SOSCircleHasPeer(newCircle, me, NULL)) {
812 if (SOSCircleVerifyPeerSigned(newCircle, me, NULL)) {
813 secnotice("signing", "Already concur with the new circle");
815 CFErrorRef signing_error = NULL;
817 if (me_full && SOSCircleConcordanceSign(newCircle, me_full, &signing_error)) {
818 circleToPush = newCircle;
819 secnotice("signing", "Concurred with new circle");
821 secerror("Failed to concurrence sign, error: %@", signing_error);
824 CFReleaseSafe(signing_error);
827 secnotice("signing", "Not countersigning, not in new circle");
829 circle_action = accept;
832 if (circle_action == accept) {
833 if(SOSCircleHasUpdatedPeerInfoWithOctagonKey(oldCircle, newCircle)){
834 notify_post(kSOSCCCircleOctagonKeysChangedNotification);
836 if (me && SOSCircleHasActivePeer(oldCircle, me, NULL) && !SOSCircleHasPeer(newCircle, me, NULL)) {
837 // Don't destroy evidence of other code determining reason for leaving.
838 if(![self hasLeft]) self.departureCode = kSOSMembershipRevoked;
839 secnotice("circleOps", "Member of old circle but not of new circle (%d)", self.departureCode);
840 debugDumpCircle(CFSTR("oldCircle"), oldCircle);
841 debugDumpCircle(CFSTR("newCircle"), newCircle);
845 && SOSCircleHasActivePeer(oldCircle, me, NULL)
846 && !(SOSCircleCountPeers(oldCircle) == 1 && SOSCircleHasPeer(oldCircle, me, NULL)) // If it was our offering, don't change ID to avoid ghosts
847 && !SOSCircleHasPeer(newCircle, me, NULL) && !SOSCircleHasApplicant(newCircle, me, NULL)) {
848 secnotice("circle", "Purging my peer (ID: %@) for circle '%@'!!!", SOSPeerInfoGetPeerID(me), SOSCircleGetName(oldCircle));
849 if (self.fullPeerInfo)
850 SOSFullPeerInfoPurgePersistentKey(self.fullPeerInfo, NULL);
855 if (me && SOSCircleHasRejectedApplicant(newCircle, me, NULL)) {
856 SOSPeerInfoRef reject = SOSCircleCopyRejectedApplicant(newCircle, me, NULL);
857 if(CFEqualSafe(reject, me) && SOSPeerInfoApplicationVerify(me, account.accountKey, NULL)) {
858 secnotice("circle", "Rejected, Purging my applicant peer (ID: %@) for circle '%@'", SOSPeerInfoGetPeerID(me), SOSCircleGetName(oldCircle));
859 debugDumpCircle(CFSTR("oldCircle"), oldCircle);
860 debugDumpCircle(CFSTR("newCircle"), newCircle);
861 if (self.fullPeerInfo)
862 SOSFullPeerInfoPurgePersistentKey(self.fullPeerInfo, NULL);
866 secnotice("circle", "Rejected, Reapplying (ID: %@) for circle '%@'", SOSPeerInfoGetPeerID(me), SOSCircleGetName(oldCircle));
867 debugDumpCircle(CFSTR("oldCircle"), oldCircle);
868 debugDumpCircle(CFSTR("newCircle"), newCircle);
869 SOSCircleRequestReadmission(newCircle, account.accountKey, me, NULL);
872 CFReleaseNull(reject);
875 CFRetainSafe(oldCircle);
876 account.previousAccountKey = account.accountKey;
878 secnotice("signing", "%@, Accepting new circle", concStr);
879 if (circle_action == accept) {
880 [self setTrustedCircle:newCircle];
883 if (me && account.accountKeyIsTrusted
884 && SOSCircleHasApplicant(oldCircle, me, NULL)
885 && SOSCircleCountPeers(newCircle) > 0
886 && !SOSCircleHasPeer(newCircle, me, NULL) && !SOSCircleHasApplicant(newCircle, me, NULL)) {
887 // We weren't rejected (above would have set me to NULL.
888 // We were applying and we weren't accepted.
889 // Our application is declared lost, let us reapply.
891 secnotice("signing", "requesting readmission to new circle");
892 if (SOSCircleRequestReadmission(newCircle, account.accountKey, me, NULL))
896 if (me && SOSCircleHasActivePeer(oldCircle, me, NULL)) {
897 [account.trust cleanupRetirementTickets:account circle:oldCircle time:RETIREMENT_FINALIZATION_SECONDS err:NULL];
900 SOSAccountNotifyOfChange(account, oldCircle, newCircle);
902 CFReleaseNull(oldCircle);
905 circleToPush = newCircle;
906 secnotice("circleop", "Setting key_interests_need_updating to true in handleUpdateCircle");
907 account.key_interests_need_updating = true;
911 * In the revert section we'll guard the KVS idea of circles by rejecting "bad" new circles
912 * and pushing our current view of the circle (oldCircle). We'll only do this if we actually
913 * are a member of oldCircle - never for an empty circle.
916 if (circle_action == revert) {
917 if(haveOldCircle && me && SOSCircleHasActivePeer(oldCircle, me, NULL)) {
918 secnotice("signing", "%@, Rejecting new circle, re-publishing old circle", concStr);
919 circleToPush = oldCircle;
920 [self setTrustedCircle:oldCircle];
922 secnotice("canary", "%@, Rejecting: new circle Have no old circle - would reset", concStr);
927 if (circleToPush != NULL) {
928 secnotice("signing", "Pushing:[%s]", local_remote);
929 CFDataRef circle_data = SOSCircleCopyEncodedData(circleToPush, kCFAllocatorDefault, error);
932 // Ensure we flush changes
933 account.circle_rings_retirements_need_attention = true;
935 //posting new circle to peers
936 success &= [circleTransport postCircle:SOSCircleGetName(circleToPush) circleData:circle_data err:error];
940 CFReleaseNull(circle_data);
942 CFReleaseSafe(newCircle);
943 CFReleaseNull(emptyCircle);
945 // There are errors collected above that are soft (worked around)
946 if(success && error && *error) {
947 CFReleaseNull(*error);
953 -(bool) updateCircleFromRemote:(SOSKVSCircleStorageTransport*)circleTransport newCircle:(SOSCircleRef)newCircle err:(CFErrorRef*)error
955 return [self handleUpdateCircle:newCircle transport:circleTransport update:false err:error];
958 -(bool) updateCircle:(SOSKVSCircleStorageTransport*)circleTransport newCircle:(SOSCircleRef) newCircle err:(CFErrorRef*)error
960 return [self handleUpdateCircle:newCircle transport:circleTransport update:true err:error];
963 -(bool) updateCircleWithAnalytics:(SOSKVSCircleStorageTransport*)circleTransport newCircle:(SOSCircleRef) newCircle parentEvent:(NSData*)parentEvent err:(CFErrorRef*)error
965 return [self handleUpdateCircle:newCircle transport:circleTransport update:true err:error];
968 -(bool) modifyCircleWithAnalytics:(SOSKVSCircleStorageTransport*)circleTransport parentEvent:(NSData*)parentEvent err:(CFErrorRef*)error action:(SOSModifyCircleBlock)block
970 bool success = false;
971 SOSCircleRef circleCopy = NULL;
972 require_action_quiet(self.trustedCircle, fail, SOSErrorCreate(kSOSErrorNoCircle, error, NULL, CFSTR("No circle to get peer key from")));
974 circleCopy = SOSCircleCopyCircle(kCFAllocatorDefault, self.trustedCircle, error);
975 require_quiet(circleCopy, fail);
978 require_quiet(block(circleCopy), fail);
980 success = [self updateCircleWithAnalytics:circleTransport newCircle:circleCopy parentEvent:parentEvent err:error];
983 CFReleaseSafe(circleCopy);
987 -(bool) modifyCircle:(SOSKVSCircleStorageTransport*)circleTransport err:(CFErrorRef*)error action:(SOSModifyCircleBlock)block
989 bool success = false;
990 SOSCircleRef circleCopy = NULL;
991 require_action_quiet(self.trustedCircle, fail, SOSErrorCreate(kSOSErrorNoCircle, error, NULL, CFSTR("No circle to get peer key from")));
993 circleCopy = SOSCircleCopyCircle(kCFAllocatorDefault, self.trustedCircle, error);
994 require_quiet(circleCopy, fail);
997 require_quiet(block(circleCopy), fail);
999 success = [self updateCircle:circleTransport newCircle:circleCopy err:error];
1002 CFReleaseSafe(circleCopy);
1007 -(void) generationSignatureUpdateWith:(SOSAccount*)account key:(SecKeyRef) privKey
1009 if (self.trustedCircle && self.fullPeerInfo) {
1010 [self modifyCircle:account.circle_transport err:NULL action:^(SOSCircleRef circle) {
1011 SOSPeerInfoRef myPI = account.peerInfo;
1012 bool iAmPeer = SOSCircleHasPeer(circle, myPI, NULL);
1013 bool change = SOSCircleUpdatePeerInfo(circle, myPI);
1014 if(iAmPeer && !SOSCircleVerify(circle, account.accountKey, NULL)) {
1015 change |= [self upgradeiCloudIdentity:circle privKey:privKey];
1016 [self removeInvalidApplications:circle userPublic:account.accountKey];
1017 change |= SOSCircleGenerationSign(circle, privKey, self.fullPeerInfo, NULL);
1018 [self setDepartureCode:kSOSNeverLeftCircle];
1019 } else if(iAmPeer) {
1020 SOSFullPeerInfoRef icfpi = SOSCircleCopyiCloudFullPeerInfoRef(circle, NULL);
1022 SOSAccountRemoveIncompleteiCloudIdentities(account, circle, privKey, NULL);
1023 bool identityAdded = [self addiCloudIdentity:circle key:privKey err:NULL];
1025 account.notifyBackupOnExit = true;
1027 change |= identityAdded;
1029 CFReleaseNull(icfpi);
1032 secnotice("updatingGenSignature", "we changed the circle? %@", change ? CFSTR("YES") : CFSTR("NO"));
1038 -(void) forEachCirclePeerExceptMe:(SOSIteratePeerBlock)block
1040 if (self.trustedCircle && self.peerInfo) {
1041 NSString* myPi_id = self.peerID;
1042 SOSCircleForEachPeer(self.trustedCircle, ^(SOSPeerInfoRef peer) {
1043 CFStringRef peerID = SOSPeerInfoGetPeerID(peer);
1044 if (peerID && ![myPi_id isEqualToString:(__bridge NSString*) peerID]) {
1051 -(bool) leaveCircleWithAccount:(SOSAccount*)account withAnalytics:(NSData*)parentEvent err:(CFErrorRef*) error
1054 secnotice("circleOps", "leaveCircleWithAccount: Leaving circle by client request");
1055 result &= [self modifyCircle:account.circle_transport err:error action:^(SOSCircleRef circle) {
1056 return sosAccountLeaveCircleWithAnalytics(account, circle, parentEvent, error);
1059 self.departureCode = kSOSWithdrewMembership;
1064 -(bool) leaveCircle:(SOSAccount*)account err:(CFErrorRef*) error
1067 secnotice("circleOps", "Leaving circle by client request");
1068 result &= [self modifyCircle:account.circle_transport err:error action:^(SOSCircleRef circle) {
1069 return sosAccountLeaveCircle(account, circle, error);
1072 self.departureCode = kSOSWithdrewMembership;
1077 -(bool) resetToOffering:(SOSAccountTransaction*) aTxn key:(SecKeyRef)userKey err:(CFErrorRef*) error
1079 SOSFullPeerInfoPurgePersistentKey(self.fullPeerInfo, NULL);
1080 self.fullPeerInfo = nil;
1082 secnotice("resetToOffering", "Resetting circle to offering by request from client");
1084 return userKey && [self resetCircleToOffering:aTxn userKey:userKey err:error];
1088 -(bool) resetCircleToOffering:(SOSAccountTransaction*) aTxn userKey:(SecKeyRef)user_key err:(CFErrorRef *)error
1090 bool result = false;
1092 SOSAccount* account = aTxn.account;
1093 if(![self hasCircle:error])
1096 if(![self ensureFullPeerAvailable:(__bridge CFDictionaryRef)(account.gestalt) deviceID:(__bridge CFStringRef)(account.deviceID) backupKey:(__bridge CFDataRef)(account.backup_key) err:error])
1099 (void)[self resetAllRings:account err:error];
1101 [self modifyCircle:account.circle_transport err:error action:^bool(SOSCircleRef circle) {
1102 bool result = false;
1103 SOSFullPeerInfoRef cloud_identity = NULL;
1104 CFErrorRef localError = NULL;
1106 require_quiet(SOSCircleResetToOffering(circle, user_key, self.fullPeerInfo, &localError), err_out);
1108 self.departureCode = kSOSNeverLeftCircle;
1110 require_quiet([self addEscrowToPeerInfo:self.fullPeerInfo err:error], err_out);
1112 require_quiet([self addiCloudIdentity:circle key:user_key err:error], err_out);
1114 SOSAccountPublishCloudParameters(account, NULL);
1115 account.notifyBackupOnExit = true;
1118 if (result == false)
1119 secerror("error resetting circle (%@) to offering: %@", circle, localError);
1120 if (localError && error && *error == NULL) {
1121 *error = localError;
1124 CFReleaseNull(localError);
1125 CFReleaseNull(cloud_identity);
1129 [self setValueInExpansion:kSOSUnsyncedViewsKey value:kCFBooleanTrue err:NULL];
1130 SOSAccountUpdateOutOfSyncViews(aTxn, SOSViewsGetAllCurrent());
1137 void SOSAccountForEachCirclePeerExceptMe(SOSAccount* account, void (^action)(SOSPeerInfoRef peer)) {
1138 SOSPeerInfoRef myPi = account.peerInfo;
1139 SOSCircleRef circle = NULL;
1141 SOSAccountTrustClassic *trust = account.trust;
1142 circle = trust.trustedCircle;
1143 if (circle && myPi) {
1144 CFStringRef myPi_id = SOSPeerInfoGetPeerID(myPi);
1145 SOSCircleForEachPeer(circle, ^(SOSPeerInfoRef peer) {
1146 CFStringRef peerID = SOSPeerInfoGetPeerID(peer);
1147 if (peerID && !CFEqual(peerID, myPi_id)) {
1154 -(bool) joinCircle:(SOSAccountTransaction*) aTxn userKey:(SecKeyRef) user_key useCloudPeer:(bool) use_cloud_peer err:(CFErrorRef*) error
1156 __block bool result = false;
1157 __block SOSFullPeerInfoRef cloud_full_peer = NULL;
1158 __block SOSAccount* account = aTxn.account;
1159 require_action_quiet(self.trustedCircle, fail, SOSCreateErrorWithFormat(kSOSErrorPeerNotFound, NULL, error, NULL, CFSTR("Don't have circle when joining???")));
1160 require_quiet([self ensureFullPeerAvailable:(__bridge CFDictionaryRef)account.gestalt deviceID:(__bridge CFStringRef)account.deviceID backupKey:(__bridge CFDataRef)account.backup_key err:error], fail);
1162 if (SOSCircleCountPeers(self.trustedCircle) == 0 || SOSAccountGhostResultsInReset(account)) {
1163 secnotice("resetToOffering", "Resetting circle to offering since there are no peers");
1164 // this also clears initial sync data
1165 result = [self resetCircleToOffering:aTxn userKey:user_key err:error];
1167 [self setValueInExpansion:kSOSUnsyncedViewsKey value:kCFBooleanTrue err:NULL];
1169 if (use_cloud_peer) {
1170 cloud_full_peer = SOSCircleCopyiCloudFullPeerInfoRef(self.trustedCircle, NULL);
1173 [self modifyCircle: account.circle_transport err:error action:^(SOSCircleRef circle) {
1174 result = SOSAccountAddEscrowToPeerInfo(account, self.fullPeerInfo, error);
1175 result &= SOSCircleRequestAdmission(circle, user_key, self.fullPeerInfo, error);
1176 self.departureCode = kSOSNeverLeftCircle;
1177 if(result && cloud_full_peer) {
1178 CFErrorRef localError = NULL;
1179 CFStringRef cloudid = SOSPeerInfoGetPeerID(SOSFullPeerInfoGetPeerInfo(cloud_full_peer));
1180 require_quiet(cloudid, finish);
1181 require_quiet(SOSCircleHasActivePeerWithID(circle, cloudid, &localError), finish);
1182 require_quiet(SOSCircleAcceptRequest(circle, user_key, cloud_full_peer, self.peerInfo, &localError), finish);
1186 secerror("Failed to join with cloud identity: %@", localError);
1187 CFReleaseNull(localError);
1193 if (use_cloud_peer) {
1194 SOSAccountUpdateOutOfSyncViews(aTxn, SOSViewsGetAllCurrent());
1199 CFReleaseNull(cloud_full_peer);