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, nil, 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 if (self.fullPeerInfo)
468 SOSFullPeerInfoPurgePersistentKey(self.fullPeerInfo, NULL);
473 if (me && SOSCircleHasRejectedApplicant(newCircle, me, NULL)) {
474 SOSPeerInfoRef reject = SOSCircleCopyRejectedApplicant(newCircle, me, NULL);
475 if(CFEqualSafe(reject, me) && SOSPeerInfoApplicationVerify(me, account.accountKey, NULL)) {
476 secnotice("circle", "Rejected, Purging my applicant peer (ID: %@) for circle '%@'", SOSPeerInfoGetPeerID(me), SOSCircleGetName(oldCircle));
477 debugDumpCircle(CFSTR("oldCircle"), oldCircle);
478 debugDumpCircle(CFSTR("newCircle"), newCircle);
479 if (self.fullPeerInfo)
480 SOSFullPeerInfoPurgePersistentKey(self.fullPeerInfo, NULL);
484 secnotice("circle", "Rejected, Reapplying (ID: %@) for circle '%@'", SOSPeerInfoGetPeerID(me), SOSCircleGetName(oldCircle));
485 debugDumpCircle(CFSTR("oldCircle"), oldCircle);
486 debugDumpCircle(CFSTR("newCircle"), newCircle);
487 SOSCircleRequestReadmission(newCircle, account.accountKey, me, NULL);
490 CFReleaseNull(reject);
493 if(me && account.accountKeyIsTrusted && SOSCircleHasPeer(newCircle, me, NULL)) {
494 // do this on daily interval +/- 8 hours random to keep all peers doing this at the same time
495 SOSIntervalEvent *iCloudCheckEvent = [self iCloudCheckEventHandle: account];
496 if([iCloudCheckEvent checkDate]) {
497 bool fixedIdentities = [self fixICloudIdentities:account circle:newCircle];
498 if(fixedIdentities) {
500 secnotice("circleOps", "Fixed iCloud Identity in circle");
502 secnotice("circleOps", "Failed to fix broken icloud identity");
504 [iCloudCheckEvent followup];
508 CFRetainSafe(oldCircle);
509 account.previousAccountKey = account.accountKey;
511 secnotice("signing", "%@, Accepting new circle", concStr);
512 if (circle_action == accept) {
513 [self setTrustedCircle:newCircle];
516 if (me && account.accountKeyIsTrusted
517 && SOSCircleHasApplicant(oldCircle, me, NULL)
518 && SOSCircleCountPeers(newCircle) > 0
519 && !SOSCircleHasPeer(newCircle, me, NULL) && !SOSCircleHasApplicant(newCircle, me, NULL)) {
520 // We weren't rejected (above would have set me to NULL.
521 // We were applying and we weren't accepted.
522 // Our application is declared lost, let us reapply.
524 secnotice("signing", "requesting readmission to new circle");
525 if (SOSCircleRequestReadmission(newCircle, account.accountKey, me, NULL))
529 if (me && SOSCircleHasActivePeer(oldCircle, me, NULL)) {
530 [account.trust cleanupRetirementTickets:account circle:oldCircle time:RETIREMENT_FINALIZATION_SECONDS err:NULL];
533 SOSAccountNotifyOfChange(account, oldCircle, newCircle);
535 CFReleaseNull(oldCircle);
538 circleToPush = newCircle;
539 secnotice("circleop", "Setting key_interests_need_updating to true in handleUpdateCircle");
540 account.key_interests_need_updating = true;
544 * In the revert section we'll guard the KVS idea of circles by rejecting "bad" new circles
545 * and pushing our current view of the circle (oldCircle). We'll only do this if we actually
546 * are a member of oldCircle - never for an empty circle.
549 if (circle_action == revert) {
550 if(haveOldCircle && me && SOSCircleHasActivePeer(oldCircle, me, NULL)) {
551 secnotice("signing", "%@, Rejecting new circle, re-publishing old circle", concStr);
552 circleToPush = oldCircle;
553 [self setTrustedCircle:oldCircle];
555 secnotice("canary", "%@, Rejecting: new circle Have no old circle - would reset", concStr);
560 if (circleToPush != NULL) {
561 secnotice("signing", "Pushing:[%s]", local_remote);
562 CFDataRef circle_data = SOSCircleCopyEncodedData(circleToPush, kCFAllocatorDefault, error);
565 // Ensure we flush changes
566 account.circle_rings_retirements_need_attention = true;
568 //posting new circle to peers
569 success &= [circleTransport postCircle:SOSCircleGetName(circleToPush) circleData:circle_data err:error];
573 CFReleaseNull(circle_data);
575 CFReleaseSafe(newCircle);
576 CFReleaseNull(emptyCircle);
578 // There are errors collected above that are soft (worked around)
579 if(success && error && *error) {
580 CFReleaseNull(*error);
586 -(bool) updateCircleFromRemote:(SOSKVSCircleStorageTransport*)circleTransport newCircle:(SOSCircleRef)newCircle err:(CFErrorRef*)error
588 return [self handleUpdateCircle:newCircle transport:circleTransport update:false err:error];
591 -(bool) updateCircle:(SOSKVSCircleStorageTransport*)circleTransport newCircle:(SOSCircleRef) newCircle err:(CFErrorRef*)error
593 return [self handleUpdateCircle:newCircle transport:circleTransport update:true err:error];
596 -(bool) modifyCircle:(SOSKVSCircleStorageTransport*)circleTransport err:(CFErrorRef*)error action:(SOSModifyCircleBlock)block
598 bool success = false;
599 SOSCircleRef circleCopy = NULL;
600 require_action_quiet(self.trustedCircle, fail, SOSErrorCreate(kSOSErrorNoCircle, error, NULL, CFSTR("No circle to get peer key from")));
602 circleCopy = SOSCircleCopyCircle(kCFAllocatorDefault, self.trustedCircle, error);
603 require_quiet(circleCopy, fail);
606 require_quiet(block(circleCopy), fail);
608 success = [self updateCircle:circleTransport newCircle:circleCopy err:error];
611 CFReleaseSafe(circleCopy);
616 // true means things changed.
617 -(bool) fixICloudIdentities:(SOSAccount *) account circle: (SOSCircleRef) circle {
619 SOSFullPeerInfoRef icfpi = SOSCircleCopyiCloudFullPeerInfoRef(circle, NULL);
621 SOSAccountRestartPrivateCredentialTimer(account);
622 if((SOSAccountGetPrivateCredential(account, NULL) != NULL) || SOSAccountAssertStashedAccountCredential(account, NULL)) {
623 SecKeyRef privKey = SOSAccountGetPrivateCredential(account, NULL);
625 SOSIntervalEvent *iCloudCleanupEvent = [self iCloudCleanerHandle: account];
626 if([iCloudCleanupEvent checkDate]) {
627 SOSAccountRemoveIncompleteiCloudIdentities(account, circle, privKey, NULL);
628 [iCloudCleanupEvent followup];
630 CFErrorRef error = NULL;
631 bool identityAdded = [self addiCloudIdentity:circle key:privKey err:&error];
633 account.notifyBackupOnExit = true;
635 [[SOSAnalytics logger] logSuccessForEventNamed:@"iCloudIdentityFix"];
637 [[SOSAnalytics logger] logResultForEvent:@"iCloudIdentityFix" hardFailure:true result:(__bridge NSError * _Nullable)(error)];
639 CFReleaseNull(error);
641 NSDictionary *attr = @{ @"reason" : @"noPrivateKey" };
642 [[SOSAnalytics logger] logHardFailureForEventNamed:@"iCloudIdentityFix" withAttributes:attr];
645 NSDictionary *attr = @{ @"reason" : @"noPrivateKey" };
646 [[SOSAnalytics logger] logHardFailureForEventNamed:@"iCloudIdentityFix" withAttributes:attr];
649 // everything is fine.
650 CFReleaseNull(icfpi);
655 -(void) generationSignatureUpdateWith:(SOSAccount*)account key:(SecKeyRef) privKey
657 // rdar://51233857 - don't gensign if there isn't a change in the userKey
658 // also don't rebake the circle to fix the icloud identity if there isn't
659 // a change as that will mess up piggybacking.
660 if(SOSAccountFullPeerInfoVerify(account, privKey, NULL) && SOSCircleVerify(account.trust.trustedCircle, account.accountKey, NULL)) {
661 secnotice("updatingGenSignature", "no change to userKey - skipping gensign");
665 if (self.trustedCircle && self.fullPeerInfo) {
666 [self modifyCircle:account.circle_transport err:NULL action:^(SOSCircleRef circle) {
667 SOSPeerInfoRef myPI = account.peerInfo;
668 bool iAmPeer = SOSCircleHasPeer(circle, myPI, NULL);
669 bool change = SOSCircleUpdatePeerInfo(circle, myPI);
670 if(iAmPeer && !SOSCircleVerify(circle, account.accountKey, NULL)) {
671 change |= [self upgradeiCloudIdentity:circle privKey:privKey];
672 [self removeInvalidApplications:circle userPublic:account.accountKey];
673 change |= SOSCircleGenerationSign(circle, privKey, self.fullPeerInfo, NULL);
674 [self setDepartureCode:kSOSNeverLeftCircle];
676 change |= [self fixICloudIdentities:account circle:circle];
678 secnotice("updatingGenSignature", "we changed the circle? %@", change ? CFSTR("YES") : CFSTR("NO"));
679 SOSIntervalEvent *iCloudCheckEvent = [self iCloudCheckEventHandle: account];
680 [iCloudCheckEvent followup];
686 -(void) forEachCirclePeerExceptMe:(SOSIteratePeerBlock)block
688 if (self.trustedCircle && self.peerInfo) {
689 NSString* myPi_id = self.peerID;
690 SOSCircleForEachPeer(self.trustedCircle, ^(SOSPeerInfoRef peer) {
691 CFStringRef peerID = SOSPeerInfoGetPeerID(peer);
692 if (peerID && ![myPi_id isEqualToString:(__bridge NSString*) peerID]) {
699 -(bool) leaveCircleWithAccount:(SOSAccount*)account withAnalytics:(NSData*)parentEvent err:(CFErrorRef*) error
702 secnotice("circleOps", "leaveCircleWithAccount: Leaving circle by client request");
703 result &= [self modifyCircle:account.circle_transport err:error action:^(SOSCircleRef circle) {
704 return sosAccountLeaveCircle(account, circle, parentEvent, error);
707 self.departureCode = kSOSWithdrewMembership;
712 -(bool) leaveCircle:(SOSAccount*)account err:(CFErrorRef*) error
715 secnotice("circleOps", "Leaving circle by client request");
716 result &= [self modifyCircle:account.circle_transport err:error action:^(SOSCircleRef circle) {
717 return sosAccountLeaveCircle(account, circle, nil, error);
719 account.backup_key = nil;
720 self.departureCode = kSOSWithdrewMembership;
725 -(bool) resetToOffering:(SOSAccountTransaction*) aTxn key:(SecKeyRef)userKey err:(CFErrorRef*) error
727 SOSFullPeerInfoPurgePersistentKey(self.fullPeerInfo, NULL);
728 self.fullPeerInfo = nil;
730 secnotice("resetToOffering", "Resetting circle to offering by request from client");
732 return userKey && [self resetCircleToOffering:aTxn userKey:userKey err:error];
736 -(bool) resetCircleToOffering:(SOSAccountTransaction*) aTxn userKey:(SecKeyRef)user_key err:(CFErrorRef *)error
740 SOSAccount* account = aTxn.account;
741 if(![self hasCircle:error])
744 if(![self ensureFullPeerAvailable:(__bridge CFDictionaryRef)(account.gestalt) deviceID:(__bridge CFStringRef)(account.deviceID) backupKey:(__bridge CFDataRef)(account.backup_key) err:error])
747 (void)[self resetAllRings:account err:error];
749 [self modifyCircle:account.circle_transport err:error action:^bool(SOSCircleRef circle) {
751 SOSFullPeerInfoRef cloud_identity = NULL;
752 CFErrorRef localError = NULL;
754 require_quiet(SOSCircleResetToOffering(circle, user_key, self.fullPeerInfo, &localError), err_out);
756 self.departureCode = kSOSNeverLeftCircle;
758 require_quiet([self addEscrowToPeerInfo:self.fullPeerInfo err:error], err_out);
760 require_quiet([self addiCloudIdentity:circle key:user_key err:error], err_out);
762 SOSAccountPublishCloudParameters(account, NULL);
763 account.notifyBackupOnExit = true;
767 secerror("error resetting circle (%@) to offering: %@", circle, localError);
768 if (localError && error && *error == NULL) {
772 CFReleaseNull(localError);
773 CFReleaseNull(cloud_identity);
777 SOSAccountInitializeInitialSync(account);
778 SOSAccountUpdateOutOfSyncViews(aTxn, SOSViewsGetAllCurrent());
782 notify_post(kSOSCCCircleOctagonKeysChangedNotification);
787 void SOSAccountForEachCirclePeerExceptMe(SOSAccount* account, void (^action)(SOSPeerInfoRef peer)) {
788 SOSPeerInfoRef myPi = account.peerInfo;
789 SOSCircleRef circle = NULL;
791 SOSAccountTrustClassic *trust = account.trust;
792 circle = trust.trustedCircle;
793 if (circle && myPi) {
794 CFStringRef myPi_id = SOSPeerInfoGetPeerID(myPi);
795 SOSCircleForEachPeer(circle, ^(SOSPeerInfoRef peer) {
796 CFStringRef peerID = SOSPeerInfoGetPeerID(peer);
797 if (peerID && !CFEqual(peerID, myPi_id)) {
804 -(bool) joinCircle:(SOSAccountTransaction*) aTxn userKey:(SecKeyRef) user_key useCloudPeer:(bool) use_cloud_peer err:(CFErrorRef*) error
806 __block bool result = false;
807 __block SOSFullPeerInfoRef cloud_full_peer = NULL;
808 __block SOSAccount* account = aTxn.account;
809 require_action_quiet(self.trustedCircle, fail, SOSCreateErrorWithFormat(kSOSErrorPeerNotFound, NULL, error, NULL, CFSTR("Don't have circle when joining???")));
810 require_quiet([self ensureFullPeerAvailable:(__bridge CFDictionaryRef)account.gestalt deviceID:(__bridge CFStringRef)account.deviceID backupKey:(__bridge CFDataRef)account.backup_key err:error], fail);
812 if (SOSCircleCountPeers(self.trustedCircle) == 0 || SOSAccountGhostResultsInReset(account)) {
813 secnotice("resetToOffering", "Resetting circle to offering since there are no peers");
814 // this also clears initial sync data
815 result = [self resetCircleToOffering:aTxn userKey:user_key err:error];
817 [self setValueInExpansion:kSOSUnsyncedViewsKey value:kCFBooleanTrue err:NULL];
819 if (use_cloud_peer) {
820 cloud_full_peer = SOSCircleCopyiCloudFullPeerInfoRef(self.trustedCircle, NULL);
823 [self modifyCircle: account.circle_transport err:error action:^(SOSCircleRef circle) {
824 result = SOSAccountAddEscrowToPeerInfo(account, self.fullPeerInfo, error);
825 result &= SOSCircleRequestAdmission(circle, user_key, self.fullPeerInfo, error);
826 self.departureCode = kSOSNeverLeftCircle;
827 if(result && cloud_full_peer) {
828 CFErrorRef localError = NULL;
829 CFStringRef cloudid = SOSPeerInfoGetPeerID(SOSFullPeerInfoGetPeerInfo(cloud_full_peer));
830 require_quiet(cloudid, finish);
831 require_quiet(SOSCircleHasActivePeerWithID(circle, cloudid, &localError), finish);
832 require_quiet(SOSCircleAcceptRequest(circle, user_key, cloud_full_peer, self.peerInfo, &localError), finish);
836 secerror("Failed to join with cloud identity: %@", localError);
837 CFReleaseNull(localError);
843 if (use_cloud_peer || SOSAccountHasCompletedInitialSync(account)) {
844 SOSAccountUpdateOutOfSyncViews(aTxn, SOSViewsGetAllCurrent());
849 CFReleaseNull(cloud_full_peer);