2 // SOSAccountTrustClassicCircle.m
6 #import <Foundation/Foundation.h>
7 #include <AssertMacros.h>
9 #import "keychain/SecureObjectSync/SOSAccount.h"
10 #import "keychain/SecureObjectSync/SOSAccountTrustClassic.h"
11 #import "keychain/SecureObjectSync/SOSTransportCircle.h"
12 #import "keychain/SecureObjectSync/SOSAccountTrustClassic+Identity.h"
13 #import "keychain/SecureObjectSync/SOSAccountTrustClassic+Expansion.h"
14 #import "keychain/SecureObjectSync/SOSAccountTrustClassic+Retirement.h"
16 #import "keychain/SecureObjectSync/SOSAccountGhost.h"
17 #import "keychain/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 publicKeysEqual(SecKeyRef pubKey1, SecKeyRef pubKey2)
164 // If either pub key is NULL, then the keys are equal if both are NULL.
165 if(pubKey1 == NULL || pubKey2 == NULL) {
166 return pubKey1 == NULL && pubKey2 == NULL;
169 NSData *key1SPKI = CFBridgingRelease(SecKeyCopySubjectPublicKeyInfo(pubKey1));
170 NSData *key2SPKI = CFBridgingRelease(SecKeyCopySubjectPublicKeyInfo(pubKey2));
172 return !![key1SPKI isEqual:key2SPKI];
175 static bool SOSCirclePeerOctagonKeysChanged(SOSPeerInfoRef oldPeer, SOSPeerInfoRef newPeer) {
177 // We've run across some situations where a new peer which should have keys isn't returning yes here.
178 // Therefore, always return yes on peer addition.
182 CFErrorRef oldSigningKeyError = NULL;
183 SecKeyRef oldSigningKey = oldPeer ? SOSPeerInfoCopyOctagonSigningPublicKey(oldPeer, &oldSigningKeyError) : NULL;
185 CFErrorRef oldEncryptionKeyError = NULL;
186 SecKeyRef oldEncryptionKey = oldPeer ? SOSPeerInfoCopyOctagonEncryptionPublicKey(oldPeer, &oldEncryptionKeyError) : NULL;
188 CFErrorRef newSigningKeyError = NULL;
189 SecKeyRef newSigningKey = newPeer ? SOSPeerInfoCopyOctagonSigningPublicKey(newPeer, &newSigningKeyError) : NULL;
191 CFErrorRef newEncryptionKeyError = NULL;
192 SecKeyRef newEncryptionKey = newPeer ? SOSPeerInfoCopyOctagonEncryptionPublicKey(newPeer, &newEncryptionKeyError) : NULL;
194 if(oldPeer && oldSigningKeyError) {
195 secerror("circleOps: Cannot fetch signing key for old %@: %@", oldPeer, oldSigningKeyError);
197 if(oldPeer && oldEncryptionKeyError) {
198 secerror("circleOps: Cannot fetch encryption key for old %@: %@", oldPeer, oldEncryptionKeyError);
200 if(newPeer && newSigningKeyError) {
201 secerror("circleOps: Cannot fetch signing key for new %@: %@", newPeer, newSigningKeyError);
203 if(newPeer && newEncryptionKeyError) {
204 secerror("circleOps: Cannot fetch encryption key for new %@: %@", newPeer, newEncryptionKeyError);
207 bool signingKeyChanged = !publicKeysEqual(oldSigningKey, newSigningKey);
208 bool encryptionKeyChanged = !publicKeysEqual(oldEncryptionKey, newEncryptionKey);
210 bool keysChanged = signingKeyChanged || encryptionKeyChanged;
212 CFReleaseNull(oldSigningKeyError);
213 CFReleaseNull(oldSigningKey);
214 CFReleaseNull(oldEncryptionKeyError);
215 CFReleaseNull(oldEncryptionKey);
217 CFReleaseNull(newSigningKeyError);
218 CFReleaseNull(newSigningKey);
219 CFReleaseNull(newEncryptionKeyError);
220 CFReleaseNull(newEncryptionKey);
224 static bool SOSCircleHasUpdatedPeerInfoWithOctagonKey(SOSCircleRef oldCircle, SOSCircleRef newCircle)
226 __block bool hasUpdated = false;
227 SOSCircleForEachPeer(oldCircle, ^(SOSPeerInfoRef oldPeer) {
228 SOSPeerInfoRef equivalentNewPeer = SOSCircleCopyPeerWithID(newCircle, SOSPeerInfoGetPeerID(oldPeer), NULL);
229 hasUpdated |= SOSCirclePeerOctagonKeysChanged(oldPeer, equivalentNewPeer);
230 CFReleaseNull(equivalentNewPeer);
233 SOSCircleForEachPeer(newCircle, ^(SOSPeerInfoRef newPeer) {
234 SOSPeerInfoRef equivalentOldPeer = SOSCircleCopyPeerWithID(oldCircle, SOSPeerInfoGetPeerID(newPeer), NULL);
235 hasUpdated |= SOSCirclePeerOctagonKeysChanged(equivalentOldPeer, newPeer);
236 CFReleaseNull(equivalentOldPeer);
242 -(bool) handleUpdateCircle:(SOSCircleRef) prospective_circle transport:(SOSKVSCircleStorageTransport*)circleTransport update:(bool) writeUpdate err:(CFErrorRef*)error
245 bool haveOldCircle = true;
246 const char *local_remote = writeUpdate ? "local": "remote";
248 SOSAccount* account = [circleTransport getAccount];
250 secnotice("signing", "start:[%s]", local_remote);
251 if (!account.accountKey || !account.accountKeyIsTrusted) {
252 SOSCreateError(kSOSErrorPublicKeyAbsent, CFSTR("Can't handle updates with no trusted public key here"), NULL, error);
256 if (!prospective_circle) {
257 secerror("##### Can't update to a NULL circle ######");
258 return false; // Can't update one we don't have.
261 // If this is a remote circle, check to see if this is our first opportunity to trust a circle where we
262 // sponsored the only signer.
263 if(!writeUpdate && [ self checkForSponsorshipTrust: prospective_circle ]){
264 SOSCCEnsurePeerRegistration();
265 secnotice("circleop", "Setting key_interests_need_updating to true in handleUpdateCircle");
266 account.key_interests_need_updating = true;
271 CFStringRef newCircleName = SOSCircleGetName(prospective_circle);
273 SOSCircleRef oldCircle = self.trustedCircle;
274 SOSCircleRef emptyCircle = NULL;
276 if(oldCircle == NULL) {
277 SOSCreateErrorWithFormat(kSOSErrorIncompatibleCircle, NULL, error, NULL, CFSTR("Current Entry is NULL; rejecting %@"), prospective_circle);
278 secerror("##### Can't replace circle - we don't care about it ######");
281 if (CFGetTypeID(oldCircle) != SOSCircleGetTypeID()) {
282 secdebug("signing", ">>>>>>>>>>>>>>> Non-Circle Circle found <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<");
283 // We don't know what is in our table, likely it was kCFNull indicating we didn't
284 // understand a circle that came by. We seem to like this one lets make our entry be empty circle
285 emptyCircle = SOSCircleCreate(kCFAllocatorDefault, newCircleName, NULL);
286 oldCircle = emptyCircle;
287 haveOldCircle = false;
288 // And we're paranoid, drop our old peer info if for some reason we didn't before.
289 // SOSAccountDestroyCirclePeerInfo(account, oldCircle, NULL);
293 SOSAccountScanForRetired(account, prospective_circle, error);
294 SOSCircleRef newCircle = SOSAccountCloneCircleWithRetirement(account, prospective_circle, error);
295 if(!newCircle) return false;
297 SOSFullPeerInfoRef me_full = self.fullPeerInfo;
298 SOSPeerInfoRef me = SOSFullPeerInfoGetPeerInfo(me_full);
299 CFStringRef myPeerID = SOSPeerInfoGetPeerID(me);
300 myPeerID = (myPeerID) ? myPeerID: CFSTR("No Peer");
302 if (me && SOSCircleUpdatePeerInfo(newCircle, me)) {
303 writeUpdate = true; // If we update our peer in the new circle we should write it if we accept it.
314 static const char *actionstring[] = {
315 "accept", "countersign", "leave", "revert", "ignore",
318 circle_action_t circle_action = ignore;
319 enum DepartureReason leave_reason = kSOSNeverLeftCircle;
321 SecKeyRef old_circle_key = NULL;
322 if(SOSCircleVerify(oldCircle, account.accountKey, NULL)){
323 old_circle_key = account.accountKey;
325 else if(account.previousAccountKey && SOSCircleVerify(oldCircle, account.previousAccountKey, NULL)){
326 old_circle_key = account.previousAccountKey;
329 bool userTrustedOldCircle = (old_circle_key != NULL) && haveOldCircle;
331 SOSConcordanceStatus concstat =
332 SOSCircleConcordanceTrust(oldCircle, newCircle,
333 old_circle_key, account.accountKey,
336 CFStringRef concStr = NULL;
338 case kSOSConcordanceTrusted:
339 circle_action = countersign;
340 concStr = CFSTR("Trusted");
342 case kSOSConcordanceGenOld:
343 circle_action = userTrustedOldCircle ? revert : ignore;
344 concStr = CFSTR("Generation Old");
346 case kSOSConcordanceBadUserSig:
347 case kSOSConcordanceBadPeerSig:
348 circle_action = userTrustedOldCircle ? revert : accept;
349 concStr = CFSTR("Bad Signature");
351 case kSOSConcordanceNoUserSig:
352 circle_action = userTrustedOldCircle ? revert : accept;
353 concStr = CFSTR("No User Signature");
355 case kSOSConcordanceNoPeerSig:
356 circle_action = accept; // We might like this one eventually but don't countersign.
357 concStr = CFSTR("No trusted peer signature");
358 secnotice("signing", "##### No trusted peer signature found, accepting hoping for concordance later");
360 case kSOSConcordanceNoPeer:
361 circle_action = leave;
362 leave_reason = kSOSLeftUntrustedCircle;
363 concStr = CFSTR("No trusted peer left");
365 case kSOSConcordanceNoUserKey:
366 secerror("##### No User Public Key Available, this shouldn't ever happen!!!");
370 secerror("##### Bad Error Return from ConcordanceTrust");
375 secnotice("signing", "Decided on action [%s] based on concordance state [%@] and [%s] circle. My PeerID is %@", actionstring[circle_action], concStr, userTrustedOldCircle ? "trusted" : "untrusted", myPeerID);
377 SOSCircleRef circleToPush = NULL;
379 if (circle_action == leave) {
380 circle_action = ignore; (void) circle_action; // Acknowledge this is a dead store.
382 if (me && SOSCircleHasPeer(oldCircle, me, NULL)) {
383 secnotice("account", "Leaving circle with peer %@", me);
384 debugDumpCircle(CFSTR("oldCircle"), oldCircle);
385 debugDumpCircle(CFSTR("newCircle"), newCircle);
386 debugDumpCircle(CFSTR("prospective_circle"), prospective_circle);
387 secnotice("account", "Key state: accountKey %@, previousAccountKey %@, old_circle_key %@",
388 account.accountKey, account.previousAccountKey, old_circle_key);
390 if (sosAccountLeaveCircle(account, newCircle, error)) {
391 secnotice("circleOps", "Leaving circle by newcircle state");
392 circleToPush = newCircle;
394 secnotice("signing", "Can't leave circle, but dumping identities");
397 self.departureCode = leave_reason;
398 circle_action = accept;
402 // We are not in this circle, but we need to update account with it, since we got it from cloud
403 secnotice("signing", "We are not in this circle, but we need to update account with it");
404 debugDumpCircle(CFSTR("oldCircle"), oldCircle);
405 debugDumpCircle(CFSTR("newCircle"), newCircle);
406 debugDumpCircle(CFSTR("prospective_circle"), prospective_circle);
407 circle_action = accept;
411 if (circle_action == countersign) {
412 if (me && SOSCircleHasPeer(newCircle, me, NULL)) {
413 if (SOSCircleVerifyPeerSigned(newCircle, me, NULL)) {
414 secnotice("signing", "Already concur with the new circle");
416 CFErrorRef signing_error = NULL;
418 if (me_full && SOSCircleConcordanceSign(newCircle, me_full, &signing_error)) {
419 circleToPush = newCircle;
420 secnotice("signing", "Concurred with new circle");
422 secerror("Failed to concurrence sign, error: %@", signing_error);
425 CFReleaseSafe(signing_error);
428 secnotice("signing", "Not countersigning, not in new circle");
429 [account.trust resetRingDictionary];
431 circle_action = accept;
434 if (circle_action == accept) {
435 if(SOSCircleHasUpdatedPeerInfoWithOctagonKey(oldCircle, newCircle)){
436 secnotice("circleOps", "Sending kSOSCCCircleOctagonKeysChangedNotification");
437 notify_post(kSOSCCCircleOctagonKeysChangedNotification);
439 if (me && SOSCircleHasActivePeer(oldCircle, me, NULL) && !SOSCircleHasPeer(newCircle, me, NULL)) {
440 // Don't destroy evidence of other code determining reason for leaving.
441 if(![self hasLeft]) self.departureCode = kSOSMembershipRevoked;
442 secnotice("circleOps", "Member of old circle but not of new circle (%d)", self.departureCode);
443 debugDumpCircle(CFSTR("oldCircle"), oldCircle);
444 debugDumpCircle(CFSTR("newCircle"), newCircle);
448 && SOSCircleHasActivePeer(oldCircle, me, NULL)
449 && !(SOSCircleCountPeers(oldCircle) == 1 && SOSCircleHasPeer(oldCircle, me, NULL)) // If it was our offering, don't change ID to avoid ghosts
450 && !SOSCircleHasPeer(newCircle, me, NULL) && !SOSCircleHasApplicant(newCircle, me, NULL)) {
451 secnotice("circle", "Purging my peer (ID: %@) for circle '%@'!!!", SOSPeerInfoGetPeerID(me), SOSCircleGetName(oldCircle));
452 if (self.fullPeerInfo)
453 SOSFullPeerInfoPurgePersistentKey(self.fullPeerInfo, NULL);
458 if (me && SOSCircleHasRejectedApplicant(newCircle, me, NULL)) {
459 SOSPeerInfoRef reject = SOSCircleCopyRejectedApplicant(newCircle, me, NULL);
460 if(CFEqualSafe(reject, me) && SOSPeerInfoApplicationVerify(me, account.accountKey, NULL)) {
461 secnotice("circle", "Rejected, Purging my applicant peer (ID: %@) for circle '%@'", SOSPeerInfoGetPeerID(me), SOSCircleGetName(oldCircle));
462 debugDumpCircle(CFSTR("oldCircle"), oldCircle);
463 debugDumpCircle(CFSTR("newCircle"), newCircle);
464 if (self.fullPeerInfo)
465 SOSFullPeerInfoPurgePersistentKey(self.fullPeerInfo, NULL);
469 secnotice("circle", "Rejected, Reapplying (ID: %@) for circle '%@'", SOSPeerInfoGetPeerID(me), SOSCircleGetName(oldCircle));
470 debugDumpCircle(CFSTR("oldCircle"), oldCircle);
471 debugDumpCircle(CFSTR("newCircle"), newCircle);
472 SOSCircleRequestReadmission(newCircle, account.accountKey, me, NULL);
475 CFReleaseNull(reject);
478 CFRetainSafe(oldCircle);
479 account.previousAccountKey = account.accountKey;
481 secnotice("signing", "%@, Accepting new circle", concStr);
482 if (circle_action == accept) {
483 [self setTrustedCircle:newCircle];
486 if (me && account.accountKeyIsTrusted
487 && SOSCircleHasApplicant(oldCircle, me, NULL)
488 && SOSCircleCountPeers(newCircle) > 0
489 && !SOSCircleHasPeer(newCircle, me, NULL) && !SOSCircleHasApplicant(newCircle, me, NULL)) {
490 // We weren't rejected (above would have set me to NULL.
491 // We were applying and we weren't accepted.
492 // Our application is declared lost, let us reapply.
494 secnotice("signing", "requesting readmission to new circle");
495 if (SOSCircleRequestReadmission(newCircle, account.accountKey, me, NULL))
499 if (me && SOSCircleHasActivePeer(oldCircle, me, NULL)) {
500 [account.trust cleanupRetirementTickets:account circle:oldCircle time:RETIREMENT_FINALIZATION_SECONDS err:NULL];
503 SOSAccountNotifyOfChange(account, oldCircle, newCircle);
505 CFReleaseNull(oldCircle);
508 circleToPush = newCircle;
509 secnotice("circleop", "Setting key_interests_need_updating to true in handleUpdateCircle");
510 account.key_interests_need_updating = true;
514 * In the revert section we'll guard the KVS idea of circles by rejecting "bad" new circles
515 * and pushing our current view of the circle (oldCircle). We'll only do this if we actually
516 * are a member of oldCircle - never for an empty circle.
519 if (circle_action == revert) {
520 if(haveOldCircle && me && SOSCircleHasActivePeer(oldCircle, me, NULL)) {
521 secnotice("signing", "%@, Rejecting new circle, re-publishing old circle", concStr);
522 circleToPush = oldCircle;
523 [self setTrustedCircle:oldCircle];
525 secnotice("canary", "%@, Rejecting: new circle Have no old circle - would reset", concStr);
530 if (circleToPush != NULL) {
531 secnotice("signing", "Pushing:[%s]", local_remote);
532 CFDataRef circle_data = SOSCircleCopyEncodedData(circleToPush, kCFAllocatorDefault, error);
535 // Ensure we flush changes
536 account.circle_rings_retirements_need_attention = true;
538 //posting new circle to peers
539 success &= [circleTransport postCircle:SOSCircleGetName(circleToPush) circleData:circle_data err:error];
543 CFReleaseNull(circle_data);
545 CFReleaseSafe(newCircle);
546 CFReleaseNull(emptyCircle);
548 // There are errors collected above that are soft (worked around)
549 if(success && error && *error) {
550 CFReleaseNull(*error);
556 -(bool) updateCircleFromRemote:(SOSKVSCircleStorageTransport*)circleTransport newCircle:(SOSCircleRef)newCircle err:(CFErrorRef*)error
558 return [self handleUpdateCircle:newCircle transport:circleTransport update:false err:error];
561 -(bool) updateCircle:(SOSKVSCircleStorageTransport*)circleTransport newCircle:(SOSCircleRef) newCircle err:(CFErrorRef*)error
563 return [self handleUpdateCircle:newCircle transport:circleTransport update:true err:error];
566 -(bool) modifyCircle:(SOSKVSCircleStorageTransport*)circleTransport err:(CFErrorRef*)error action:(SOSModifyCircleBlock)block
568 bool success = false;
569 SOSCircleRef circleCopy = NULL;
570 require_action_quiet(self.trustedCircle, fail, SOSErrorCreate(kSOSErrorNoCircle, error, NULL, CFSTR("No circle to get peer key from")));
572 circleCopy = SOSCircleCopyCircle(kCFAllocatorDefault, self.trustedCircle, error);
573 require_quiet(circleCopy, fail);
576 require_quiet(block(circleCopy), fail);
578 success = [self updateCircle:circleTransport newCircle:circleCopy err:error];
581 CFReleaseSafe(circleCopy);
586 -(void) generationSignatureUpdateWith:(SOSAccount*)account key:(SecKeyRef) privKey
588 // rdar://51233857 - don't gensign if there isn't a change in the userKey
589 // also don't rebake the circle to fix the icloud identity if there isn't
590 // a change as that will mess up piggybacking.
591 if(SOSAccountFullPeerInfoVerify(account, privKey, NULL) && SOSCircleVerify(account.trust.trustedCircle, account.accountKey, NULL)) {
592 secnotice("updatingGenSignature", "no change to userKey - skipping gensign");
596 if (self.trustedCircle && self.fullPeerInfo) {
597 [self modifyCircle:account.circle_transport err:NULL action:^(SOSCircleRef circle) {
598 SOSPeerInfoRef myPI = account.peerInfo;
599 bool iAmPeer = SOSCircleHasPeer(circle, myPI, NULL);
600 bool change = SOSCircleUpdatePeerInfo(circle, myPI);
601 if(iAmPeer && !SOSCircleVerify(circle, account.accountKey, NULL)) {
602 change |= [self upgradeiCloudIdentity:circle privKey:privKey];
603 [self removeInvalidApplications:circle userPublic:account.accountKey];
604 change |= SOSCircleGenerationSign(circle, privKey, self.fullPeerInfo, NULL);
605 [self setDepartureCode:kSOSNeverLeftCircle];
607 SOSFullPeerInfoRef icfpi = SOSCircleCopyiCloudFullPeerInfoRef(circle, NULL);
609 SOSAccountRemoveIncompleteiCloudIdentities(account, circle, privKey, NULL);
610 bool identityAdded = [self addiCloudIdentity:circle key:privKey err:NULL];
612 account.notifyBackupOnExit = true;
614 change |= identityAdded;
616 CFReleaseNull(icfpi);
619 secnotice("updatingGenSignature", "we changed the circle? %@", change ? CFSTR("YES") : CFSTR("NO"));
625 -(void) forEachCirclePeerExceptMe:(SOSIteratePeerBlock)block
627 if (self.trustedCircle && self.peerInfo) {
628 NSString* myPi_id = self.peerID;
629 SOSCircleForEachPeer(self.trustedCircle, ^(SOSPeerInfoRef peer) {
630 CFStringRef peerID = SOSPeerInfoGetPeerID(peer);
631 if (peerID && ![myPi_id isEqualToString:(__bridge NSString*) peerID]) {
638 -(bool) leaveCircleWithAccount:(SOSAccount*)account withAnalytics:(NSData*)parentEvent err:(CFErrorRef*) error
641 secnotice("circleOps", "leaveCircleWithAccount: Leaving circle by client request");
642 result &= [self modifyCircle:account.circle_transport err:error action:^(SOSCircleRef circle) {
643 return sosAccountLeaveCircleWithAnalytics(account, circle, parentEvent, error);
646 self.departureCode = kSOSWithdrewMembership;
651 -(bool) leaveCircle:(SOSAccount*)account err:(CFErrorRef*) error
654 secnotice("circleOps", "Leaving circle by client request");
655 result &= [self modifyCircle:account.circle_transport err:error action:^(SOSCircleRef circle) {
656 return sosAccountLeaveCircle(account, circle, error);
658 account.backup_key = nil;
659 self.departureCode = kSOSWithdrewMembership;
664 -(bool) resetToOffering:(SOSAccountTransaction*) aTxn key:(SecKeyRef)userKey err:(CFErrorRef*) error
666 SOSFullPeerInfoPurgePersistentKey(self.fullPeerInfo, NULL);
667 self.fullPeerInfo = nil;
669 secnotice("resetToOffering", "Resetting circle to offering by request from client");
671 return userKey && [self resetCircleToOffering:aTxn userKey:userKey err:error];
675 -(bool) resetCircleToOffering:(SOSAccountTransaction*) aTxn userKey:(SecKeyRef)user_key err:(CFErrorRef *)error
679 SOSAccount* account = aTxn.account;
680 if(![self hasCircle:error])
683 if(![self ensureFullPeerAvailable:(__bridge CFDictionaryRef)(account.gestalt) deviceID:(__bridge CFStringRef)(account.deviceID) backupKey:(__bridge CFDataRef)(account.backup_key) err:error])
686 (void)[self resetAllRings:account err:error];
688 [self modifyCircle:account.circle_transport err:error action:^bool(SOSCircleRef circle) {
690 SOSFullPeerInfoRef cloud_identity = NULL;
691 CFErrorRef localError = NULL;
693 require_quiet(SOSCircleResetToOffering(circle, user_key, self.fullPeerInfo, &localError), err_out);
695 self.departureCode = kSOSNeverLeftCircle;
697 require_quiet([self addEscrowToPeerInfo:self.fullPeerInfo err:error], err_out);
699 require_quiet([self addiCloudIdentity:circle key:user_key err:error], err_out);
701 SOSAccountPublishCloudParameters(account, NULL);
702 account.notifyBackupOnExit = true;
706 secerror("error resetting circle (%@) to offering: %@", circle, localError);
707 if (localError && error && *error == NULL) {
711 CFReleaseNull(localError);
712 CFReleaseNull(cloud_identity);
716 SOSAccountInitializeInitialSync(account);
717 SOSAccountUpdateOutOfSyncViews(aTxn, SOSViewsGetAllCurrent());
721 notify_post(kSOSCCCircleOctagonKeysChangedNotification);
726 void SOSAccountForEachCirclePeerExceptMe(SOSAccount* account, void (^action)(SOSPeerInfoRef peer)) {
727 SOSPeerInfoRef myPi = account.peerInfo;
728 SOSCircleRef circle = NULL;
730 SOSAccountTrustClassic *trust = account.trust;
731 circle = trust.trustedCircle;
732 if (circle && myPi) {
733 CFStringRef myPi_id = SOSPeerInfoGetPeerID(myPi);
734 SOSCircleForEachPeer(circle, ^(SOSPeerInfoRef peer) {
735 CFStringRef peerID = SOSPeerInfoGetPeerID(peer);
736 if (peerID && !CFEqual(peerID, myPi_id)) {
743 -(bool) joinCircle:(SOSAccountTransaction*) aTxn userKey:(SecKeyRef) user_key useCloudPeer:(bool) use_cloud_peer err:(CFErrorRef*) error
745 __block bool result = false;
746 __block SOSFullPeerInfoRef cloud_full_peer = NULL;
747 __block SOSAccount* account = aTxn.account;
748 require_action_quiet(self.trustedCircle, fail, SOSCreateErrorWithFormat(kSOSErrorPeerNotFound, NULL, error, NULL, CFSTR("Don't have circle when joining???")));
749 require_quiet([self ensureFullPeerAvailable:(__bridge CFDictionaryRef)account.gestalt deviceID:(__bridge CFStringRef)account.deviceID backupKey:(__bridge CFDataRef)account.backup_key err:error], fail);
751 if (SOSCircleCountPeers(self.trustedCircle) == 0 || SOSAccountGhostResultsInReset(account)) {
752 secnotice("resetToOffering", "Resetting circle to offering since there are no peers");
753 // this also clears initial sync data
754 result = [self resetCircleToOffering:aTxn userKey:user_key err:error];
756 [self setValueInExpansion:kSOSUnsyncedViewsKey value:kCFBooleanTrue err:NULL];
758 if (use_cloud_peer) {
759 cloud_full_peer = SOSCircleCopyiCloudFullPeerInfoRef(self.trustedCircle, NULL);
762 [self modifyCircle: account.circle_transport err:error action:^(SOSCircleRef circle) {
763 result = SOSAccountAddEscrowToPeerInfo(account, self.fullPeerInfo, error);
764 result &= SOSCircleRequestAdmission(circle, user_key, self.fullPeerInfo, error);
765 self.departureCode = kSOSNeverLeftCircle;
766 if(result && cloud_full_peer) {
767 CFErrorRef localError = NULL;
768 CFStringRef cloudid = SOSPeerInfoGetPeerID(SOSFullPeerInfoGetPeerInfo(cloud_full_peer));
769 require_quiet(cloudid, finish);
770 require_quiet(SOSCircleHasActivePeerWithID(circle, cloudid, &localError), finish);
771 require_quiet(SOSCircleAcceptRequest(circle, user_key, cloud_full_peer, self.peerInfo, &localError), finish);
775 secerror("Failed to join with cloud identity: %@", localError);
776 CFReleaseNull(localError);
782 if (use_cloud_peer || SOSAccountHasCompletedInitialSync(account)) {
783 SOSAccountUpdateOutOfSyncViews(aTxn, SOSViewsGetAllCurrent());
788 CFReleaseNull(cloud_full_peer);