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/SOSIntervalEvent.h"
18 #import "keychain/SecureObjectSync/SOSViews.h"
19 #import "Analytics/Clients/SOSAnalytics.h"
22 @implementation SOSAccountTrustClassic (Circle)
24 #define ICLOUDIDDATE @"iCloudIDDate"
26 -(bool) isInCircleOnly:(CFErrorRef *)error
28 SOSCCStatus result = [self getCircleStatusOnly:error];
30 if (result != kSOSCCInCircle) {
31 SOSErrorCreate(kSOSErrorNoCircle, error, NULL, CFSTR("Not in circle"));
38 -(bool) hasCircle:(CFErrorRef*) error
40 if (!self.trustedCircle)
41 SOSCreateErrorWithFormat(kSOSErrorNoCircle, NULL, error, NULL, CFSTR("No trusted circle"));
43 return self.trustedCircle != NULL;
46 -(SOSCCStatus) thisDeviceStatusInCircle:(SOSCircleRef) circle peer:(SOSPeerInfoRef) this_peer
49 return kSOSCCNotInCircle;
51 if (circle && SOSCircleCountPeers(circle) == 0)
52 return kSOSCCCircleAbsent;
56 if(SOSPeerInfoIsRetirementTicket(this_peer))
57 return kSOSCCNotInCircle;
59 if (SOSCircleHasPeer(circle, this_peer, NULL))
60 return kSOSCCInCircle;
62 if (SOSCircleHasApplicant(circle, this_peer, NULL))
63 return kSOSCCRequestPending;
66 return kSOSCCNotInCircle;
68 -(SOSCCStatus) getCircleStatusOnly:(CFErrorRef*) error
70 return [self thisDeviceStatusInCircle:self.trustedCircle peer:self.peerInfo];
74 -(SOSCircleRef) getCircle:(CFErrorRef *)error
76 CFTypeRef entry = self.trustedCircle;
77 require_action_quiet(!isNull(entry), fail,
78 SOSCreateError(kSOSErrorIncompatibleCircle, CFSTR("Incompatible circle in KVS"), NULL, error));
79 return (SOSCircleRef) entry;
88 -(SOSCircleRef) ensureCircle:(SOSAccount*)a name:(CFStringRef)name err:(CFErrorRef *)error
90 CFErrorRef localError = NULL;
91 if (self.trustedCircle == NULL) {
92 SOSCircleRef newCircle = SOSCircleCreate(NULL, name, NULL);
93 self.trustedCircle = newCircle; // Note that this setter adds a retain
94 CFReleaseNull(newCircle);
95 secnotice("circleop", "Setting key_interests_need_updating to true in ensureCircle");
96 a.key_interests_need_updating = true;
99 require_action_quiet(self.trustedCircle || !isSOSErrorCoded(localError, kSOSErrorIncompatibleCircle), fail,
100 if (error) { *error = localError; localError = NULL; });
103 CFReleaseNull(localError);
104 return self.trustedCircle;
109 switch(self.departureCode) {
110 case kSOSDiscoveredRetirement: /* Fallthrough */
111 case kSOSLostPrivateKey: /* Fallthrough */
112 case kSOSWithdrewMembership: /* Fallthrough */
113 case kSOSMembershipRevoked: /* Fallthrough */
114 case kSOSLeftUntrustedCircle:
116 case kSOSNeverAppliedToCircle: /* Fallthrough */
117 case kSOSNeverLeftCircle: /* Fallthrough */
123 /* This check is new to protect piggybacking by the current peer - in that case we have a remote peer signature that
124 can't have ghost cleanup changing the circle hash.
127 -(bool) ghostBustingOK:(SOSCircleRef) oldCircle updatingTo:(SOSCircleRef) newCircle {
129 // Preliminaries - we must have a peer and it must be in the newCircle in order to attempt busting
130 SOSFullPeerInfoRef me_full = self.fullPeerInfo;
131 if(!me_full) return false;
132 SOSPeerInfoRef me = SOSFullPeerInfoGetPeerInfo(me_full);
133 if(!me || (!SOSCircleHasPeer(newCircle, me, NULL) && !SOSCircleHasApplicant(newCircle, me, NULL))) return false;
135 CFStringRef myPid = SOSPeerInfoGetPeerID(me);
136 CFDictionaryRef newSigs = SOSCircleCopyAllSignatures(newCircle);
137 bool iSignedNew = CFDictionaryGetCountOfKey(newSigs, myPid);
138 long otherPeerSigCount = CFDictionaryGetCount(newSigs) - ((iSignedNew) ? 2: 1);
140 if (SOSCircleHasPeer(oldCircle, me, NULL)) { // If we're already in the old one we're not PBing
142 } else if (!iSignedNew) { // Piggybacking peers always have signed as part of genSigning - so this indicates we're safe to bust.
144 } else if(iSignedNew && otherPeerSigCount > 1) { // if others have seen this we're good to bust.
147 CFReleaseNull(newSigs);
151 // If this circle bears a signature from us and a newer gencount and it isn't our "current" circle, we're
152 // going to trust it. That's the signature of a piggybacked circle where we were the sponsor.
154 -(bool) checkForSponsorshipTrust:(SOSCircleRef) prospective_circle {
155 if(CFEqualSafe(self.trustedCircle, prospective_circle)) return false;
156 SecKeyRef myPubKey = SOSFullPeerInfoCopyPubKey(self.fullPeerInfo, NULL);
157 if(!myPubKey) return false;
158 if(SOSCircleVerify(prospective_circle, myPubKey, NULL) && SOSCircleIsOlderGeneration(self.trustedCircle, prospective_circle)) {
159 [self setTrustedCircle:prospective_circle];
160 CFReleaseNull(myPubKey);
163 CFReleaseNull(myPubKey);
167 static bool publicKeysEqual(SecKeyRef pubKey1, SecKeyRef pubKey2)
169 // If either pub key is NULL, then the keys are equal if both are NULL.
170 if(pubKey1 == NULL || pubKey2 == NULL) {
171 return pubKey1 == NULL && pubKey2 == NULL;
174 NSData *key1SPKI = CFBridgingRelease(SecKeyCopySubjectPublicKeyInfo(pubKey1));
175 NSData *key2SPKI = CFBridgingRelease(SecKeyCopySubjectPublicKeyInfo(pubKey2));
177 return !![key1SPKI isEqual:key2SPKI];
180 static bool SOSCirclePeerOctagonKeysChanged(SOSPeerInfoRef oldPeer, SOSPeerInfoRef newPeer) {
182 // We've run across some situations where a new peer which should have keys isn't returning yes here.
183 // Therefore, always return yes on peer addition.
187 CFErrorRef oldSigningKeyError = NULL;
188 SecKeyRef oldSigningKey = oldPeer ? SOSPeerInfoCopyOctagonSigningPublicKey(oldPeer, &oldSigningKeyError) : NULL;
190 CFErrorRef oldEncryptionKeyError = NULL;
191 SecKeyRef oldEncryptionKey = oldPeer ? SOSPeerInfoCopyOctagonEncryptionPublicKey(oldPeer, &oldEncryptionKeyError) : NULL;
193 CFErrorRef newSigningKeyError = NULL;
194 SecKeyRef newSigningKey = newPeer ? SOSPeerInfoCopyOctagonSigningPublicKey(newPeer, &newSigningKeyError) : NULL;
196 CFErrorRef newEncryptionKeyError = NULL;
197 SecKeyRef newEncryptionKey = newPeer ? SOSPeerInfoCopyOctagonEncryptionPublicKey(newPeer, &newEncryptionKeyError) : NULL;
199 if(oldPeer && oldSigningKeyError) {
200 secerror("circleOps: Cannot fetch signing key for old %@: %@", oldPeer, oldSigningKeyError);
202 if(oldPeer && oldEncryptionKeyError) {
203 secerror("circleOps: Cannot fetch encryption key for old %@: %@", oldPeer, oldEncryptionKeyError);
205 if(newPeer && newSigningKeyError) {
206 secerror("circleOps: Cannot fetch signing key for new %@: %@", newPeer, newSigningKeyError);
208 if(newPeer && newEncryptionKeyError) {
209 secerror("circleOps: Cannot fetch encryption key for new %@: %@", newPeer, newEncryptionKeyError);
212 bool signingKeyChanged = !publicKeysEqual(oldSigningKey, newSigningKey);
213 bool encryptionKeyChanged = !publicKeysEqual(oldEncryptionKey, newEncryptionKey);
215 bool keysChanged = signingKeyChanged || encryptionKeyChanged;
217 CFReleaseNull(oldSigningKeyError);
218 CFReleaseNull(oldSigningKey);
219 CFReleaseNull(oldEncryptionKeyError);
220 CFReleaseNull(oldEncryptionKey);
222 CFReleaseNull(newSigningKeyError);
223 CFReleaseNull(newSigningKey);
224 CFReleaseNull(newEncryptionKeyError);
225 CFReleaseNull(newEncryptionKey);
229 static bool SOSCircleHasUpdatedPeerInfoWithOctagonKey(SOSCircleRef oldCircle, SOSCircleRef newCircle)
231 __block bool hasUpdated = false;
232 SOSCircleForEachPeer(oldCircle, ^(SOSPeerInfoRef oldPeer) {
233 SOSPeerInfoRef equivalentNewPeer = SOSCircleCopyPeerWithID(newCircle, SOSPeerInfoGetPeerID(oldPeer), NULL);
234 hasUpdated |= SOSCirclePeerOctagonKeysChanged(oldPeer, equivalentNewPeer);
235 CFReleaseNull(equivalentNewPeer);
238 SOSCircleForEachPeer(newCircle, ^(SOSPeerInfoRef newPeer) {
239 SOSPeerInfoRef equivalentOldPeer = SOSCircleCopyPeerWithID(oldCircle, SOSPeerInfoGetPeerID(newPeer), NULL);
240 hasUpdated |= SOSCirclePeerOctagonKeysChanged(equivalentOldPeer, newPeer);
241 CFReleaseNull(equivalentOldPeer);
247 // Check on the iCloud identity availability every 24-36 hours random interval
248 - (SOSIntervalEvent *) iCloudCheckEventHandle: (SOSAccount *) account {
249 return [[SOSIntervalEvent alloc] initWithDefaults:account.settings dateDescription:@"iCloudIDCheck" earliest:60*60*24 latest:60*60*36];
252 // Cleanup unusable iCloud identities every 5-7 days random interval
253 - (SOSIntervalEvent *) iCloudCleanerHandle: (SOSAccount *) account {
254 return [[SOSIntervalEvent alloc] initWithDefaults:account.settings dateDescription:@"iCloudCleanerCheck" earliest:60*60*24*5 latest:60*60*24*7];
257 -(bool) handleUpdateCircle:(SOSCircleRef) prospective_circle transport:(SOSKVSCircleStorageTransport*)circleTransport update:(bool) writeUpdate err:(CFErrorRef*)error
260 bool haveOldCircle = true;
261 const char *local_remote = writeUpdate ? "local": "remote";
263 SOSAccount* account = [circleTransport getAccount];
265 secnotice("signing", "start:[%s]", local_remote);
266 if (!account.accountKey || !account.accountKeyIsTrusted) {
267 SOSCreateError(kSOSErrorPublicKeyAbsent, CFSTR("Can't handle updates with no trusted public key here"), NULL, error);
271 if (!prospective_circle) {
272 secerror("##### Can't update to a NULL circle ######");
273 return false; // Can't update one we don't have.
276 // If this is a remote circle, check to see if this is our first opportunity to trust a circle where we
277 // sponsored the only signer.
278 if(!writeUpdate && [ self checkForSponsorshipTrust: prospective_circle ]){
279 SOSCCEnsurePeerRegistration();
280 secnotice("circleop", "Setting key_interests_need_updating to true in handleUpdateCircle");
281 account.key_interests_need_updating = true;
286 CFStringRef newCircleName = SOSCircleGetName(prospective_circle);
288 SOSCircleRef oldCircle = self.trustedCircle;
289 SOSCircleRef emptyCircle = NULL;
291 if(oldCircle == NULL) {
292 SOSCreateErrorWithFormat(kSOSErrorIncompatibleCircle, NULL, error, NULL, CFSTR("Current Entry is NULL; rejecting %@"), prospective_circle);
293 secerror("##### Can't replace circle - we don't care about it ######");
296 if (CFGetTypeID(oldCircle) != SOSCircleGetTypeID()) {
297 secdebug("signing", ">>>>>>>>>>>>>>> Non-Circle Circle found <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<");
298 // We don't know what is in our table, likely it was kCFNull indicating we didn't
299 // understand a circle that came by. We seem to like this one lets make our entry be empty circle
300 emptyCircle = SOSCircleCreate(kCFAllocatorDefault, newCircleName, NULL);
301 oldCircle = emptyCircle;
302 haveOldCircle = false;
303 // And we're paranoid, drop our old peer info if for some reason we didn't before.
304 // SOSAccountDestroyCirclePeerInfo(account, oldCircle, NULL);
308 SOSAccountScanForRetired(account, prospective_circle, error);
309 SOSCircleRef newCircle = SOSAccountCloneCircleWithRetirement(account, prospective_circle, error);
310 if(!newCircle) return false;
312 SOSFullPeerInfoRef me_full = self.fullPeerInfo;
313 SOSPeerInfoRef me = SOSFullPeerInfoGetPeerInfo(me_full);
314 CFStringRef myPeerID = SOSPeerInfoGetPeerID(me);
315 myPeerID = (myPeerID) ? myPeerID: CFSTR("No Peer");
317 if (me && SOSCircleUpdatePeerInfo(newCircle, me)) {
318 writeUpdate = true; // If we update our peer in the new circle we should write it if we accept it.
329 static const char *actionstring[] = {
330 "accept", "countersign", "leave", "revert", "ignore",
333 circle_action_t circle_action = ignore;
334 enum DepartureReason leave_reason = kSOSNeverLeftCircle;
336 SecKeyRef old_circle_key = NULL;
337 if(SOSCircleVerify(oldCircle, account.accountKey, NULL)){
338 old_circle_key = account.accountKey;
340 else if(account.previousAccountKey && SOSCircleVerify(oldCircle, account.previousAccountKey, NULL)){
341 old_circle_key = account.previousAccountKey;
344 bool userTrustedOldCircle = (old_circle_key != NULL) && haveOldCircle;
346 SOSConcordanceStatus concstat =
347 SOSCircleConcordanceTrust(oldCircle, newCircle,
348 old_circle_key, account.accountKey,
351 CFStringRef concStr = NULL;
353 case kSOSConcordanceTrusted:
354 circle_action = countersign;
355 concStr = CFSTR("Trusted");
357 case kSOSConcordanceGenOld:
358 circle_action = userTrustedOldCircle ? revert : ignore;
359 concStr = CFSTR("Generation Old");
361 case kSOSConcordanceBadUserSig:
362 case kSOSConcordanceBadPeerSig:
363 circle_action = userTrustedOldCircle ? revert : accept;
364 concStr = CFSTR("Bad Signature");
366 case kSOSConcordanceNoUserSig:
367 circle_action = userTrustedOldCircle ? revert : accept;
368 concStr = CFSTR("No User Signature");
370 case kSOSConcordanceNoPeerSig:
371 circle_action = accept; // We might like this one eventually but don't countersign.
372 concStr = CFSTR("No trusted peer signature");
373 secnotice("signing", "##### No trusted peer signature found, accepting hoping for concordance later");
375 case kSOSConcordanceNoPeer:
376 circle_action = leave;
377 leave_reason = kSOSLeftUntrustedCircle;
378 concStr = CFSTR("No trusted peer left");
380 case kSOSConcordanceNoUserKey:
381 secerror("##### No User Public Key Available, this shouldn't ever happen!!!");
385 secerror("##### Bad Error Return from ConcordanceTrust");
390 secnotice("signing", "Decided on action [%s] based on concordance state [%@] and [%s] circle. My PeerID is %@", actionstring[circle_action], concStr, userTrustedOldCircle ? "trusted" : "untrusted", myPeerID);
392 SOSCircleRef circleToPush = NULL;
394 if (circle_action == leave) {
395 circle_action = ignore; (void) circle_action; // Acknowledge this is a dead store.
397 if (me && SOSCircleHasPeer(oldCircle, me, NULL)) {
398 secnotice("account", "Leaving circle with peer %@", me);
399 debugDumpCircle(CFSTR("oldCircle"), oldCircle);
400 debugDumpCircle(CFSTR("newCircle"), newCircle);
401 debugDumpCircle(CFSTR("prospective_circle"), prospective_circle);
402 secnotice("account", "Key state: accountKey %@, previousAccountKey %@, old_circle_key %@",
403 account.accountKey, account.previousAccountKey, old_circle_key);
405 if (sosAccountLeaveCircle(account, newCircle, error)) {
406 secnotice("circleOps", "Leaving circle by newcircle state");
407 circleToPush = newCircle;
409 secnotice("signing", "Can't leave circle, but dumping identities");
412 self.departureCode = leave_reason;
413 circle_action = accept;
417 // We are not in this circle, but we need to update account with it, since we got it from cloud
418 secnotice("signing", "We are not in this circle, but we need to update account with it");
419 debugDumpCircle(CFSTR("oldCircle"), oldCircle);
420 debugDumpCircle(CFSTR("newCircle"), newCircle);
421 debugDumpCircle(CFSTR("prospective_circle"), prospective_circle);
422 circle_action = accept;
426 if (circle_action == countersign) {
427 if (me && SOSCircleHasPeer(newCircle, me, NULL)) {
428 if (SOSCircleVerifyPeerSigned(newCircle, me, NULL)) {
429 secnotice("signing", "Already concur with the new circle");
431 CFErrorRef signing_error = NULL;
433 if (me_full && SOSCircleConcordanceSign(newCircle, me_full, &signing_error)) {
434 circleToPush = newCircle;
435 secnotice("signing", "Concurred with new circle");
437 secerror("Failed to concurrence sign, error: %@", signing_error);
440 CFReleaseSafe(signing_error);
443 secnotice("signing", "Not countersigning, not in new circle");
444 [account.trust resetRingDictionary];
446 circle_action = accept;
449 if (circle_action == accept) {
450 if(SOSCircleHasUpdatedPeerInfoWithOctagonKey(oldCircle, newCircle)){
451 secnotice("circleOps", "Sending kSOSCCCircleOctagonKeysChangedNotification");
452 notify_post(kSOSCCCircleOctagonKeysChangedNotification);
454 if (me && SOSCircleHasActivePeer(oldCircle, me, NULL) && !SOSCircleHasPeer(newCircle, me, NULL)) {
455 // Don't destroy evidence of other code determining reason for leaving.
456 if(![self hasLeft]) self.departureCode = kSOSMembershipRevoked;
457 secnotice("circleOps", "Member of old circle but not of new circle (%d)", self.departureCode);
458 debugDumpCircle(CFSTR("oldCircle"), oldCircle);
459 debugDumpCircle(CFSTR("newCircle"), newCircle);
463 && SOSCircleHasActivePeer(oldCircle, me, NULL)
464 && !(SOSCircleCountPeers(oldCircle) == 1 && SOSCircleHasPeer(oldCircle, me, NULL)) // If it was our offering, don't change ID to avoid ghosts
465 && !SOSCircleHasPeer(newCircle, me, NULL) && !SOSCircleHasApplicant(newCircle, me, NULL)) {
466 secnotice("circle", "Purging my peer (ID: %@) for circle '%@'!!!", SOSPeerInfoGetPeerID(me), SOSCircleGetName(oldCircle));
467 [self purgeIdentity];
472 if (me && SOSCircleHasRejectedApplicant(newCircle, me, NULL)) {
473 SOSPeerInfoRef reject = SOSCircleCopyRejectedApplicant(newCircle, me, NULL);
474 if(CFEqualSafe(reject, me) && SOSPeerInfoApplicationVerify(me, account.accountKey, NULL)) {
475 secnotice("circle", "Rejected, Purging my applicant peer (ID: %@) for circle '%@'", SOSPeerInfoGetPeerID(me), SOSCircleGetName(oldCircle));
476 debugDumpCircle(CFSTR("oldCircle"), oldCircle);
477 debugDumpCircle(CFSTR("newCircle"), newCircle);
478 [self purgeIdentity];
482 secnotice("circle", "Rejected, Reapplying (ID: %@) for circle '%@'", SOSPeerInfoGetPeerID(me), SOSCircleGetName(oldCircle));
483 debugDumpCircle(CFSTR("oldCircle"), oldCircle);
484 debugDumpCircle(CFSTR("newCircle"), newCircle);
485 SOSCircleRequestReadmission(newCircle, account.accountKey, me, NULL);
488 CFReleaseNull(reject);
491 if(me && account.accountKeyIsTrusted && SOSCircleHasPeer(newCircle, me, NULL)) {
492 // do this on daily interval +/- 8 hours random to keep all peers doing this at the same time
493 SOSIntervalEvent *iCloudCheckEvent = [self iCloudCheckEventHandle: account];
494 if([iCloudCheckEvent checkDate]) {
495 bool fixedIdentities = [self fixICloudIdentities:account circle:newCircle];
496 if(fixedIdentities) {
498 secnotice("circleOps", "Fixed iCloud Identity in circle");
500 secnotice("circleOps", "Failed to fix broken icloud identity");
502 [iCloudCheckEvent followup];
506 CFRetainSafe(oldCircle);
507 account.previousAccountKey = account.accountKey;
509 secnotice("signing", "%@, Accepting new circle", concStr);
510 if (circle_action == accept) {
511 [self setTrustedCircle:newCircle];
514 if (me && account.accountKeyIsTrusted
515 && SOSCircleHasApplicant(oldCircle, me, NULL)
516 && SOSCircleCountPeers(newCircle) > 0
517 && !SOSCircleHasPeer(newCircle, me, NULL) && !SOSCircleHasApplicant(newCircle, me, NULL)) {
518 // We weren't rejected (above would have set me to NULL.
519 // We were applying and we weren't accepted.
520 // Our application is declared lost, let us reapply.
522 secnotice("signing", "requesting readmission to new circle");
523 if (SOSCircleRequestReadmission(newCircle, account.accountKey, me, NULL))
527 if (me && SOSCircleHasActivePeer(oldCircle, me, NULL)) {
528 [account.trust cleanupRetirementTickets:account circle:oldCircle time:RETIREMENT_FINALIZATION_SECONDS err:NULL];
531 SOSAccountNotifyOfChange(account, oldCircle, newCircle);
533 CFReleaseNull(oldCircle);
536 circleToPush = newCircle;
537 secnotice("circleop", "Setting key_interests_need_updating to true in handleUpdateCircle");
538 account.key_interests_need_updating = true;
542 * In the revert section we'll guard the KVS idea of circles by rejecting "bad" new circles
543 * and pushing our current view of the circle (oldCircle). We'll only do this if we actually
544 * are a member of oldCircle - never for an empty circle.
547 if (circle_action == revert) {
548 if(haveOldCircle && me && SOSCircleHasActivePeer(oldCircle, me, NULL)) {
549 secnotice("signing", "%@, Rejecting new circle, re-publishing old circle", concStr);
550 circleToPush = oldCircle;
551 [self setTrustedCircle:oldCircle];
553 secnotice("canary", "%@, Rejecting: new circle Have no old circle - would reset", concStr);
558 if (circleToPush != NULL) {
559 secnotice("signing", "Pushing:[%s]", local_remote);
560 CFDataRef circle_data = SOSCircleCopyEncodedData(circleToPush, kCFAllocatorDefault, error);
563 // Ensure we flush changes
564 account.circle_rings_retirements_need_attention = true;
566 //posting new circle to peers
567 success &= [circleTransport postCircle:SOSCircleGetName(circleToPush) circleData:circle_data err:error];
571 CFReleaseNull(circle_data);
573 CFReleaseSafe(newCircle);
574 CFReleaseNull(emptyCircle);
576 // There are errors collected above that are soft (worked around)
577 if(success && error && *error) {
578 CFReleaseNull(*error);
584 -(bool) updateCircleFromRemote:(SOSKVSCircleStorageTransport*)circleTransport newCircle:(SOSCircleRef)newCircle err:(CFErrorRef*)error
586 return [self handleUpdateCircle:newCircle transport:circleTransport update:false err:error];
589 -(bool) updateCircle:(SOSKVSCircleStorageTransport*)circleTransport newCircle:(SOSCircleRef) newCircle err:(CFErrorRef*)error
591 return [self handleUpdateCircle:newCircle transport:circleTransport update:true err:error];
594 -(bool) modifyCircle:(SOSKVSCircleStorageTransport*)circleTransport err:(CFErrorRef*)error action:(SOSModifyCircleBlock)block
596 bool success = false;
597 SOSCircleRef circleCopy = NULL;
598 require_action_quiet(self.trustedCircle, fail, SOSErrorCreate(kSOSErrorNoCircle, error, NULL, CFSTR("No circle to get peer key from")));
600 circleCopy = SOSCircleCopyCircle(kCFAllocatorDefault, self.trustedCircle, error);
601 require_quiet(circleCopy, fail);
604 require_quiet(block(circleCopy), fail);
606 success = [self updateCircle:circleTransport newCircle:circleCopy err:error];
609 CFReleaseSafe(circleCopy);
614 // true means things changed.
615 -(bool) fixICloudIdentities:(SOSAccount *) account circle: (SOSCircleRef) circle {
617 SOSFullPeerInfoRef icfpi = SOSCircleCopyiCloudFullPeerInfoRef(circle, NULL);
619 SOSAccountRestartPrivateCredentialTimer(account);
620 if((SOSAccountGetPrivateCredential(account, NULL) != NULL) || SOSAccountAssertStashedAccountCredential(account, NULL)) {
621 SecKeyRef privKey = SOSAccountGetPrivateCredential(account, NULL);
623 SOSIntervalEvent *iCloudCleanupEvent = [self iCloudCleanerHandle: account];
624 if([iCloudCleanupEvent checkDate]) {
625 SOSAccountRemoveIncompleteiCloudIdentities(account, circle, privKey, NULL);
626 [iCloudCleanupEvent followup];
628 CFErrorRef error = NULL;
629 bool identityAdded = [self addiCloudIdentity:circle key:privKey err:&error];
631 account.notifyBackupOnExit = true;
633 [[SOSAnalytics logger] logSuccessForEventNamed:@"iCloudIdentityFix"];
635 [[SOSAnalytics logger] logResultForEvent:@"iCloudIdentityFix" hardFailure:true result:(__bridge NSError * _Nullable)(error)];
637 CFReleaseNull(error);
639 NSDictionary *attr = @{ @"reason" : @"noPrivateKey" };
640 [[SOSAnalytics logger] logHardFailureForEventNamed:@"iCloudIdentityFix" withAttributes:attr];
643 NSDictionary *attr = @{ @"reason" : @"noPrivateKey" };
644 [[SOSAnalytics logger] logHardFailureForEventNamed:@"iCloudIdentityFix" withAttributes:attr];
647 // everything is fine.
648 CFReleaseNull(icfpi);
653 -(void) generationSignatureUpdateWith:(SOSAccount*)account key:(SecKeyRef) privKey
655 // rdar://51233857 - don't gensign if there isn't a change in the userKey
656 // also don't rebake the circle to fix the icloud identity if there isn't
657 // a change as that will mess up piggybacking.
658 if(account.trust.trustedCircle && SOSAccountFullPeerInfoVerify(account, privKey, NULL) && SOSCircleVerify(account.trust.trustedCircle, account.accountKey, NULL)) {
659 secnotice("updatingGenSignature", "no change to userKey - skipping gensign");
663 if (self.trustedCircle && self.fullPeerInfo) {
664 [self modifyCircle:account.circle_transport err:NULL action:^(SOSCircleRef circle) {
665 SOSPeerInfoRef myPI = account.peerInfo;
666 bool iAmPeer = SOSCircleHasPeer(circle, myPI, NULL);
667 bool change = SOSCircleUpdatePeerInfo(circle, myPI);
668 if(iAmPeer && !SOSCircleVerify(circle, account.accountKey, NULL)) {
669 change |= [self upgradeiCloudIdentity:circle privKey:privKey];
670 [self removeInvalidApplications:circle userPublic:account.accountKey];
671 change |= SOSCircleGenerationSign(circle, privKey, self.fullPeerInfo, NULL);
672 [self setDepartureCode:kSOSNeverLeftCircle];
674 change |= [self fixICloudIdentities:account circle:circle];
676 secnotice("updatingGenSignature", "we changed the circle? %@", change ? CFSTR("YES") : CFSTR("NO"));
677 SOSIntervalEvent *iCloudCheckEvent = [self iCloudCheckEventHandle: account];
678 [iCloudCheckEvent followup];
684 -(void) forEachCirclePeerExceptMe:(SOSIteratePeerBlock)block
686 if (self.trustedCircle && self.peerInfo) {
687 NSString* myPi_id = self.peerID;
688 SOSCircleForEachPeer(self.trustedCircle, ^(SOSPeerInfoRef peer) {
689 CFStringRef peerID = SOSPeerInfoGetPeerID(peer);
690 if (peerID && ![myPi_id isEqualToString:(__bridge NSString*) peerID]) {
697 -(bool) leaveCircleWithAccount:(SOSAccount*)account err:(CFErrorRef*) error
700 secnotice("circleOps", "leaveCircleWithAccount: Leaving circle by client request");
701 result &= [self modifyCircle:account.circle_transport err:error action:^(SOSCircleRef circle) {
702 return sosAccountLeaveCircle(account, circle, error);
705 self.departureCode = kSOSWithdrewMembership;
710 -(bool) leaveCircle:(SOSAccount*)account err:(CFErrorRef*) error
713 secnotice("circleOps", "Leaving circle by client request");
714 result &= [self modifyCircle:account.circle_transport err:error action:^(SOSCircleRef circle) {
715 return sosAccountLeaveCircle(account, circle, error);
717 account.backup_key = nil;
718 self.departureCode = kSOSWithdrewMembership;
723 -(bool) resetToOffering:(SOSAccountTransaction*) aTxn key:(SecKeyRef)userKey err:(CFErrorRef*) error
725 [self purgeIdentity];
727 secnotice("resetToOffering", "Resetting circle to offering by request from client");
729 return userKey && [self resetCircleToOffering:aTxn userKey:userKey err:error];
733 -(bool) resetCircleToOffering:(SOSAccountTransaction*) aTxn userKey:(SecKeyRef)user_key err:(CFErrorRef *)error
737 SOSAccount* account = aTxn.account;
738 if(![self hasCircle:error])
741 if(![self ensureFullPeerAvailable:account err:error])
744 (void)[self resetAllRings:account err:error];
746 [self modifyCircle:account.circle_transport err:error action:^bool(SOSCircleRef circle) {
748 SOSFullPeerInfoRef cloud_identity = NULL;
749 CFErrorRef localError = NULL;
751 require_quiet(SOSCircleResetToOffering(circle, user_key, self.fullPeerInfo, &localError), err_out);
753 self.departureCode = kSOSNeverLeftCircle;
755 require_quiet([self addEscrowToPeerInfo:self.fullPeerInfo err:error], err_out);
757 require_quiet([self addiCloudIdentity:circle key:user_key err:error], err_out);
759 SOSAccountPublishCloudParameters(account, NULL);
760 account.notifyBackupOnExit = true;
764 secerror("error resetting circle (%@) to offering: %@", circle, localError);
765 if (localError && error && *error == NULL) {
769 CFReleaseNull(localError);
770 CFReleaseNull(cloud_identity);
774 SOSAccountInitializeInitialSync(account);
775 SOSAccountUpdateOutOfSyncViews(aTxn, SOSViewsGetAllCurrent());
779 notify_post(kSOSCCCircleOctagonKeysChangedNotification);
784 void SOSAccountForEachCirclePeerExceptMe(SOSAccount* account, void (^action)(SOSPeerInfoRef peer)) {
785 SOSPeerInfoRef myPi = account.peerInfo;
786 SOSCircleRef circle = NULL;
788 SOSAccountTrustClassic *trust = account.trust;
789 circle = trust.trustedCircle;
790 if (circle && myPi) {
791 CFStringRef myPi_id = SOSPeerInfoGetPeerID(myPi);
792 SOSCircleForEachPeer(circle, ^(SOSPeerInfoRef peer) {
793 CFStringRef peerID = SOSPeerInfoGetPeerID(peer);
794 if (peerID && !CFEqual(peerID, myPi_id)) {
801 -(bool) joinCircle:(SOSAccountTransaction*) aTxn userKey:(SecKeyRef) user_key useCloudPeer:(bool) use_cloud_peer err:(CFErrorRef*) error
803 __block bool result = false;
804 __block SOSFullPeerInfoRef cloud_full_peer = NULL;
805 __block SOSAccount* account = aTxn.account;
806 require_action_quiet(self.trustedCircle, fail, SOSCreateErrorWithFormat(kSOSErrorPeerNotFound, NULL, error, NULL, CFSTR("Don't have circle when joining???")));
807 require_quiet([self ensureFullPeerAvailable:account err:error], fail);
809 if (SOSCircleCountPeers(self.trustedCircle) == 0 || SOSAccountGhostResultsInReset(account)) {
810 secnotice("resetToOffering", "Resetting circle to offering since there are no peers");
811 // this also clears initial sync data
812 result = [self resetCircleToOffering:aTxn userKey:user_key err:error];
814 [self setValueInExpansion:kSOSUnsyncedViewsKey value:kCFBooleanTrue err:NULL];
816 if (use_cloud_peer) {
817 cloud_full_peer = SOSCircleCopyiCloudFullPeerInfoRef(self.trustedCircle, NULL);
820 [self modifyCircle: account.circle_transport err:error action:^(SOSCircleRef circle) {
821 result = SOSAccountAddEscrowToPeerInfo(account, self.fullPeerInfo, error);
822 result &= SOSCircleRequestAdmission(circle, user_key, self.fullPeerInfo, error);
823 self.departureCode = kSOSNeverLeftCircle;
824 if(result && cloud_full_peer) {
825 CFErrorRef localError = NULL;
826 CFStringRef cloudid = SOSPeerInfoGetPeerID(SOSFullPeerInfoGetPeerInfo(cloud_full_peer));
827 require_quiet(cloudid, finish);
828 require_quiet(SOSCircleHasActivePeerWithID(circle, cloudid, &localError), finish);
829 require_quiet(SOSCircleAcceptRequest(circle, user_key, cloud_full_peer, self.peerInfo, &localError), finish);
833 secerror("Failed to join with cloud identity: %@", localError);
834 CFReleaseNull(localError);
840 if (use_cloud_peer || SOSAccountHasCompletedInitialSync(account)) {
841 SOSAccountUpdateOutOfSyncViews(aTxn, SOSViewsGetAllCurrent());
846 CFReleaseNull(cloud_full_peer);