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 SOSFullPeerInfoRef me_full = self.fullPeerInfo;
134 if(!me_full) return false;
135 SOSPeerInfoRef me = SOSFullPeerInfoGetPeerInfo(me_full);
136 CFStringRef myPid = SOSPeerInfoGetPeerID(me);
137 CFDictionaryRef newSigs = SOSCircleCopyAllSignatures(newCircle);
138 bool iSignedNew = CFDictionaryGetCountOfKey(newSigs, myPid);
139 long otherPeerSigCount = CFDictionaryGetCount(newSigs) - ((iSignedNew) ? 2: 1);
141 if (me && SOSCircleHasPeer(oldCircle, me, NULL) && SOSCircleHasPeer(newCircle, me, NULL)) { // If we're already in the old one we're not PBing
143 } else if (!iSignedNew) { // Piggybacking peers always have signed as part of genSigning - so this indicates we're safe to bust.
145 } else if(iSignedNew && otherPeerSigCount > 1) { // if others have seen this we're good to bust.
148 CFReleaseNull(newSigs);
152 // If this circle bears a signature from us and a newer gencount and it isn't our "current" circle, we're
153 // going to trust it. That's the signature of a piggybacked circle where we were the sponsor.
155 -(bool) checkForSponsorshipTrust:(SOSCircleRef) prospective_circle {
156 if(CFEqualSafe(self.trustedCircle, prospective_circle)) return false;
157 SecKeyRef myPubKey = SOSFullPeerInfoCopyPubKey(self.fullPeerInfo, NULL);
158 if(!myPubKey) return false;
159 if(SOSCircleVerify(prospective_circle, myPubKey, NULL) && SOSCircleIsOlderGeneration(self.trustedCircle, prospective_circle)) {
160 [self setTrustedCircle:prospective_circle];
166 -(bool) handleUpdateCircle:(SOSCircleRef) prospective_circle transport:(SOSKVSCircleStorageTransport*)circleTransport update:(bool) writeUpdate err:(CFErrorRef*)error
169 bool haveOldCircle = true;
170 const char *local_remote = writeUpdate ? "local": "remote";
172 SOSAccount* account = [circleTransport getAccount];
174 secnotice("signing", "start:[%s]", local_remote);
175 if (!account.accountKey || !account.accountKeyIsTrusted) {
176 SOSCreateError(kSOSErrorPublicKeyAbsent, CFSTR("Can't handle updates with no trusted public key here"), NULL, error);
180 if (!prospective_circle) {
181 secerror("##### Can't update to a NULL circle ######");
182 return false; // Can't update one we don't have.
185 // If this is a remote circle, check to see if this is our first opportunity to trust a circle where we
186 // sponsored the only signer.
187 if(!writeUpdate && [ self checkForSponsorshipTrust: prospective_circle ]){
188 SOSCCEnsurePeerRegistration();
189 account.key_interests_need_updating = true;
194 CFStringRef newCircleName = SOSCircleGetName(prospective_circle);
196 SOSCircleRef oldCircle = self.trustedCircle;
197 SOSCircleRef emptyCircle = NULL;
199 if(oldCircle == NULL) {
200 SOSCreateErrorWithFormat(kSOSErrorIncompatibleCircle, NULL, error, NULL, CFSTR("Current Entry is NULL; rejecting %@"), prospective_circle);
201 secerror("##### Can't replace circle - we don't care about it ######");
204 if (CFGetTypeID(oldCircle) != SOSCircleGetTypeID()) {
205 secdebug("signing", ">>>>>>>>>>>>>>> Non-Circle Circle found <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<");
206 // We don't know what is in our table, likely it was kCFNull indicating we didn't
207 // understand a circle that came by. We seem to like this one lets make our entry be empty circle
208 emptyCircle = SOSCircleCreate(kCFAllocatorDefault, newCircleName, NULL);
209 oldCircle = emptyCircle;
210 haveOldCircle = false;
211 // And we're paranoid, drop our old peer info if for some reason we didn't before.
212 // SOSAccountDestroyCirclePeerInfo(account, oldCircle, NULL);
216 SOSAccountScanForRetired(account, prospective_circle, error);
217 SOSCircleRef newCircle = SOSAccountCloneCircleWithRetirement(account, prospective_circle, error);
218 if(!newCircle) return false;
219 if([self ghostBustingOK: oldCircle updatingTo:newCircle]) {
220 SOSCircleRef ghostCleaned = SOSAccountCloneCircleWithoutMyGhosts(account, newCircle);
222 CFRetainAssign(newCircle, ghostCleaned);
227 SOSFullPeerInfoRef me_full = self.fullPeerInfo;
228 SOSPeerInfoRef me = SOSFullPeerInfoGetPeerInfo(me_full);
229 CFStringRef myPeerID = SOSPeerInfoGetPeerID(me);
230 myPeerID = (myPeerID) ? myPeerID: CFSTR("No Peer");
232 if (me && SOSCircleUpdatePeerInfo(newCircle, me)) {
233 writeUpdate = true; // If we update our peer in the new circle we should write it if we accept it.
244 static const char *actionstring[] = {
245 "accept", "countersign", "leave", "revert", "ignore",
248 circle_action_t circle_action = ignore;
249 enum DepartureReason leave_reason = kSOSNeverLeftCircle;
251 SecKeyRef old_circle_key = NULL;
252 if(SOSCircleVerify(oldCircle, account.accountKey, NULL)){
253 old_circle_key = account.accountKey;
255 else if(account.previousAccountKey && SOSCircleVerify(oldCircle, account.previousAccountKey, NULL)){
256 old_circle_key = account.previousAccountKey;
259 bool userTrustedOldCircle = (old_circle_key != NULL) && haveOldCircle;
261 SOSConcordanceStatus concstat =
262 SOSCircleConcordanceTrust(oldCircle, newCircle,
263 old_circle_key, account.accountKey,
266 CFStringRef concStr = NULL;
268 case kSOSConcordanceTrusted:
269 circle_action = countersign;
270 concStr = CFSTR("Trusted");
272 case kSOSConcordanceGenOld:
273 circle_action = userTrustedOldCircle ? revert : ignore;
274 concStr = CFSTR("Generation Old");
276 case kSOSConcordanceBadUserSig:
277 case kSOSConcordanceBadPeerSig:
278 circle_action = userTrustedOldCircle ? revert : accept;
279 concStr = CFSTR("Bad Signature");
281 case kSOSConcordanceNoUserSig:
282 circle_action = userTrustedOldCircle ? revert : accept;
283 concStr = CFSTR("No User Signature");
285 case kSOSConcordanceNoPeerSig:
286 circle_action = accept; // We might like this one eventually but don't countersign.
287 concStr = CFSTR("No trusted peer signature");
288 secnotice("signing", "##### No trusted peer signature found, accepting hoping for concordance later");
290 case kSOSConcordanceNoPeer:
291 circle_action = leave;
292 leave_reason = kSOSLeftUntrustedCircle;
293 concStr = CFSTR("No trusted peer left");
295 case kSOSConcordanceNoUserKey:
296 secerror("##### No User Public Key Available, this shouldn't ever happen!!!");
300 secerror("##### Bad Error Return from ConcordanceTrust");
305 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);
307 SOSCircleRef circleToPush = NULL;
309 if (circle_action == leave) {
310 circle_action = ignore; (void) circle_action; // Acknowledge this is a dead store.
312 if (me && SOSCircleHasPeer(oldCircle, me, NULL)) {
313 secnotice("account", "Leaving circle with peer %@", me);
314 debugDumpCircle(CFSTR("oldCircle"), oldCircle);
315 debugDumpCircle(CFSTR("newCircle"), newCircle);
316 debugDumpCircle(CFSTR("prospective_circle"), prospective_circle);
317 secnotice("account", "Key state: accountKey %@, previousAccountKey %@, old_circle_key %@",
318 account.accountKey, account.previousAccountKey, old_circle_key);
320 if (sosAccountLeaveCircle(account, newCircle, error)) {
321 secnotice("leaveCircle", "Leaving circle by newcircle state");
322 circleToPush = newCircle;
324 secnotice("signing", "Can't leave circle, but dumping identities");
327 self.departureCode = leave_reason;
328 circle_action = accept;
332 // We are not in this circle, but we need to update account with it, since we got it from cloud
333 secnotice("signing", "We are not in this circle, but we need to update account with it");
334 debugDumpCircle(CFSTR("oldCircle"), oldCircle);
335 debugDumpCircle(CFSTR("newCircle"), newCircle);
336 debugDumpCircle(CFSTR("prospective_circle"), prospective_circle);
337 circle_action = accept;
341 if (circle_action == countersign) {
342 if (me && SOSCircleHasPeer(newCircle, me, NULL)) {
343 if (SOSCircleVerifyPeerSigned(newCircle, me, NULL)) {
344 secnotice("signing", "Already concur with the new circle");
346 CFErrorRef signing_error = NULL;
348 if (me_full && SOSCircleConcordanceSign(newCircle, me_full, &signing_error)) {
349 circleToPush = newCircle;
350 secnotice("signing", "Concurred with new circle");
352 secerror("Failed to concurrence sign, error: %@", signing_error);
355 CFReleaseSafe(signing_error);
358 secnotice("signing", "Not countersigning, not in new circle");
359 debugDumpCircle(CFSTR("circle to countersign"), newCircle);
361 circle_action = accept;
364 if (circle_action == accept) {
365 if (me && SOSCircleHasActivePeer(oldCircle, me, NULL) && !SOSCircleHasPeer(newCircle, me, NULL)) {
366 // Don't destroy evidence of other code determining reason for leaving.
367 if(![self hasLeft]) self.departureCode = kSOSMembershipRevoked;
368 secnotice("account", "Member of old circle but not of new circle");
369 debugDumpCircle(CFSTR("oldCircle"), oldCircle);
370 debugDumpCircle(CFSTR("newCircle"), newCircle);
374 && SOSCircleHasActivePeer(oldCircle, me, NULL)
375 && !(SOSCircleCountPeers(oldCircle) == 1 && SOSCircleHasPeer(oldCircle, me, NULL)) // If it was our offering, don't change ID to avoid ghosts
376 && !SOSCircleHasPeer(newCircle, me, NULL) && !SOSCircleHasApplicant(newCircle, me, NULL)) {
377 secnotice("circle", "Purging my peer (ID: %@) for circle '%@'!!!", SOSPeerInfoGetPeerID(me), SOSCircleGetName(oldCircle));
378 if (self.fullPeerInfo)
379 SOSFullPeerInfoPurgePersistentKey(self.fullPeerInfo, NULL);
384 if (me && SOSCircleHasRejectedApplicant(newCircle, me, NULL)) {
385 SOSPeerInfoRef reject = SOSCircleCopyRejectedApplicant(newCircle, me, NULL);
386 if(CFEqualSafe(reject, me) && SOSPeerInfoApplicationVerify(me, account.accountKey, NULL)) {
387 secnotice("circle", "Rejected, Purging my applicant peer (ID: %@) for circle '%@'", SOSPeerInfoGetPeerID(me), SOSCircleGetName(oldCircle));
388 debugDumpCircle(CFSTR("oldCircle"), oldCircle);
389 debugDumpCircle(CFSTR("newCircle"), newCircle);
390 if (self.fullPeerInfo)
391 SOSFullPeerInfoPurgePersistentKey(self.fullPeerInfo, NULL);
395 secnotice("circle", "Rejected, Reapplying (ID: %@) for circle '%@'", SOSPeerInfoGetPeerID(me), SOSCircleGetName(oldCircle));
396 debugDumpCircle(CFSTR("oldCircle"), oldCircle);
397 debugDumpCircle(CFSTR("newCircle"), newCircle);
398 SOSCircleRequestReadmission(newCircle, account.accountKey, me, NULL);
403 CFRetainSafe(oldCircle);
404 account.previousAccountKey = account.accountKey;
406 secnotice("signing", "%@, Accepting new circle", concStr);
407 if (circle_action == accept) {
408 [self setTrustedCircle:newCircle];
411 if (me && account.accountKeyIsTrusted
412 && SOSCircleHasApplicant(oldCircle, me, NULL)
413 && SOSCircleCountPeers(newCircle) > 0
414 && !SOSCircleHasPeer(newCircle, me, NULL) && !SOSCircleHasApplicant(newCircle, me, NULL)) {
415 // We weren't rejected (above would have set me to NULL.
416 // We were applying and we weren't accepted.
417 // Our application is declared lost, let us reapply.
419 secnotice("signing", "requesting readmission to new circle");
420 if (SOSCircleRequestReadmission(newCircle, account.accountKey, me, NULL))
424 if (me && SOSCircleHasActivePeer(oldCircle, me, NULL)) {
425 [account.trust cleanupRetirementTickets:account circle:oldCircle time:RETIREMENT_FINALIZATION_SECONDS err:NULL];
428 SOSAccountNotifyOfChange(account, oldCircle, newCircle);
430 CFReleaseNull(oldCircle);
433 circleToPush = newCircle;
434 account.key_interests_need_updating = true;
438 * In the revert section we'll guard the KVS idea of circles by rejecting "bad" new circles
439 * and pushing our current view of the circle (oldCircle). We'll only do this if we actually
440 * are a member of oldCircle - never for an empty circle.
443 if (circle_action == revert) {
444 if(haveOldCircle && me && SOSCircleHasActivePeer(oldCircle, me, NULL)) {
445 secnotice("signing", "%@, Rejecting new circle, re-publishing old circle", concStr);
446 debugDumpCircle(CFSTR("oldCircle"), oldCircle);
447 debugDumpCircle(CFSTR("newCircle"), newCircle);
448 circleToPush = oldCircle;
449 [self setTrustedCircle:oldCircle];
451 secnotice("canary", "%@, Rejecting: new circle Have no old circle - would reset", concStr);
456 if (circleToPush != NULL) {
457 secnotice("signing", "Pushing:[%s]", local_remote);
458 CFDataRef circle_data = SOSCircleCopyEncodedData(circleToPush, kCFAllocatorDefault, error);
461 // Ensure we flush changes
462 account.circle_rings_retirements_need_attention = true;
464 //posting new circle to peers
465 success &= [circleTransport postCircle:SOSCircleGetName(circleToPush) circleData:circle_data err:error];
466 //cleanup old KVS keys
467 SOSAccountCleanupAllKVSKeys(account, error);
471 CFReleaseNull(circle_data);
473 CFReleaseSafe(newCircle);
474 CFReleaseNull(emptyCircle);
479 -(bool) updateCircleFromRemote:(SOSKVSCircleStorageTransport*)circleTransport newCircle:(SOSCircleRef)newCircle err:(CFErrorRef*)error
481 return [self handleUpdateCircle:newCircle transport:circleTransport update:false err:error];
484 -(bool) updateCircle:(SOSKVSCircleStorageTransport*)circleTransport newCircle:(SOSCircleRef) newCircle err:(CFErrorRef*)error
486 return [self handleUpdateCircle:newCircle transport:circleTransport update:true err:error];
489 -(bool) modifyCircle:(SOSKVSCircleStorageTransport*)circleTransport err:(CFErrorRef*)error action:(SOSModifyCircleBlock)block
491 bool success = false;
492 SOSCircleRef circleCopy = NULL;
493 require_action_quiet(self.trustedCircle, fail, SOSErrorCreate(kSOSErrorNoCircle, error, NULL, CFSTR("No circle to get peer key from")));
495 circleCopy = SOSCircleCopyCircle(kCFAllocatorDefault, self.trustedCircle, error);
496 require_quiet(circleCopy, fail);
499 require_quiet(block(circleCopy), fail);
501 success = [self updateCircle:circleTransport newCircle:circleCopy err:error];
504 CFReleaseSafe(circleCopy);
509 -(void) generationSignatureUpdateWith:(SOSAccount*)account key:(SecKeyRef) privKey
511 if (self.trustedCircle && self.fullPeerInfo) {
512 [self modifyCircle:account.circle_transport err:NULL action:^(SOSCircleRef circle) {
513 SOSPeerInfoRef myPI = account.peerInfo;
514 bool iAmPeer = SOSCircleHasPeer(circle, myPI, NULL);
515 bool change = SOSCircleUpdatePeerInfo(circle, myPI);
516 if(iAmPeer && !SOSCircleVerify(circle, account.accountKey, NULL)) {
517 change |= [self upgradeiCloudIdentity:circle privKey:privKey];
518 [self removeInvalidApplications:circle userPublic:account.accountKey];
519 change |= SOSCircleGenerationSign(circle, privKey, self.fullPeerInfo, NULL);
520 [self setDepartureCode:kSOSNeverLeftCircle];
522 SOSFullPeerInfoRef icfpi = SOSCircleCopyiCloudFullPeerInfoRef(circle, NULL);
524 SOSAccountRemoveIncompleteiCloudIdentities(account, circle, privKey, NULL);
525 change |= [self addiCloudIdentity:circle key:privKey err:NULL];
527 CFReleaseNull(icfpi);
530 secnotice("updatingGenSignature", "we changed the circle? %@", change ? CFSTR("YES") : CFSTR("NO"));
536 -(void) forEachCirclePeerExceptMe:(SOSIteratePeerBlock)block
538 if (self.trustedCircle && self.peerInfo) {
539 NSString* myPi_id = self.peerID;
540 SOSCircleForEachPeer(self.trustedCircle, ^(SOSPeerInfoRef peer) {
541 CFStringRef peerID = SOSPeerInfoGetPeerID(peer);
542 if (peerID && ![myPi_id isEqualToString:(__bridge NSString*) peerID]) {
548 -(bool) leaveCircle:(SOSAccount*)account err:(CFErrorRef*) error
551 secnotice("leaveCircle", "Leaving circle by client request");
552 result &= [self modifyCircle:account.circle_transport err:error action:^(SOSCircleRef circle) {
553 return sosAccountLeaveCircle(account, circle, error);
556 self.departureCode = kSOSWithdrewMembership;
561 -(bool) resetToOffering:(SOSAccountTransaction*) aTxn key:(SecKeyRef)userKey err:(CFErrorRef*) error
563 SOSFullPeerInfoPurgePersistentKey(self.fullPeerInfo, NULL);
564 self.fullPeerInfo = nil;
566 secnotice("resetToOffering", "Resetting circle to offering by request from client");
568 return userKey && [self resetCircleToOffering:aTxn userKey:userKey err:error];
572 -(bool) resetCircleToOffering:(SOSAccountTransaction*) aTxn userKey:(SecKeyRef)user_key err:(CFErrorRef *)error
576 SOSAccount* account = aTxn.account;
577 if(![self hasCircle:error])
580 if(![self ensureFullPeerAvailable:(__bridge CFDictionaryRef)(account.gestalt) deviceID:(__bridge CFStringRef)(account.deviceID) backupKey:(__bridge CFDataRef)(account.backup_key) err:error])
583 (void)[self resetAllRings:account err:error];
585 [self modifyCircle:account.circle_transport err:error action:^bool(SOSCircleRef circle) {
587 SOSFullPeerInfoRef cloud_identity = NULL;
588 CFErrorRef localError = NULL;
590 require_quiet(SOSCircleResetToOffering(circle, user_key, self.fullPeerInfo, &localError), err_out);
592 self.departureCode = kSOSNeverLeftCircle;
594 require_quiet([self addEscrowToPeerInfo:self.fullPeerInfo err:error], err_out);
596 require_quiet([self addiCloudIdentity:circle key:user_key err:error], err_out);
598 SOSAccountPublishCloudParameters(account, NULL);
602 secerror("error resetting circle (%@) to offering: %@", circle, localError);
603 if (localError && error && *error == NULL) {
607 CFReleaseNull(localError);
608 CFReleaseNull(cloud_identity);
612 [self setValueInExpansion:kSOSUnsyncedViewsKey value:kCFBooleanTrue err:NULL];
613 SOSAccountUpdateOutOfSyncViews(aTxn, SOSViewsGetAllCurrent());
620 void SOSAccountForEachCirclePeerExceptMe(SOSAccount* account, void (^action)(SOSPeerInfoRef peer)) {
621 SOSPeerInfoRef myPi = account.peerInfo;
622 SOSCircleRef circle = NULL;
624 SOSAccountTrustClassic *trust = account.trust;
625 circle = trust.trustedCircle;
626 if (circle && myPi) {
627 CFStringRef myPi_id = SOSPeerInfoGetPeerID(myPi);
628 SOSCircleForEachPeer(circle, ^(SOSPeerInfoRef peer) {
629 CFStringRef peerID = SOSPeerInfoGetPeerID(peer);
630 if (peerID && !CFEqual(peerID, myPi_id)) {
637 -(bool) joinCircle:(SOSAccountTransaction*) aTxn userKey:(SecKeyRef) user_key useCloudPeer:(bool) use_cloud_peer err:(CFErrorRef*) error
639 __block bool result = false;
640 __block SOSFullPeerInfoRef cloud_full_peer = NULL;
641 __block SOSAccount* account = aTxn.account;
642 require_action_quiet(self.trustedCircle, fail, SOSCreateErrorWithFormat(kSOSErrorPeerNotFound, NULL, error, NULL, CFSTR("Don't have circle when joining???")));
643 require_quiet([self ensureFullPeerAvailable:(__bridge CFDictionaryRef)account.gestalt deviceID:(__bridge CFStringRef)account.deviceID backupKey:(__bridge CFDataRef)account.backup_key err:error], fail);
645 if (SOSCircleCountPeers(self.trustedCircle) == 0 || SOSAccountGhostResultsInReset(account)) {
646 secnotice("resetToOffering", "Resetting circle to offering since there are no peers");
647 // this also clears initial sync data
648 result = [self resetCircleToOffering:aTxn userKey:user_key err:error];
650 [self setValueInExpansion:kSOSUnsyncedViewsKey value:kCFBooleanTrue err:NULL];
652 if (use_cloud_peer) {
653 cloud_full_peer = SOSCircleCopyiCloudFullPeerInfoRef(self.trustedCircle, NULL);
656 [self modifyCircle: account.circle_transport err:error action:^(SOSCircleRef circle) {
657 result = SOSAccountAddEscrowToPeerInfo(account, self.fullPeerInfo, error);
658 result &= SOSCircleRequestAdmission(circle, user_key, self.fullPeerInfo, error);
659 self.departureCode = kSOSNeverLeftCircle;
660 if(result && cloud_full_peer) {
661 CFErrorRef localError = NULL;
662 CFStringRef cloudid = SOSPeerInfoGetPeerID(SOSFullPeerInfoGetPeerInfo(cloud_full_peer));
663 require_quiet(cloudid, finish);
664 require_quiet(SOSCircleHasActivePeerWithID(circle, cloudid, &localError), finish);
665 require_quiet(SOSCircleAcceptRequest(circle, user_key, cloud_full_peer, self.peerInfo, &localError), finish);
669 secerror("Failed to join with cloud identity: %@", localError);
670 CFReleaseNull(localError);
676 if (use_cloud_peer) {
677 SOSAccountUpdateOutOfSyncViews(aTxn, SOSViewsGetAllCurrent());
682 CFReleaseNull(cloud_full_peer);