]> git.saurik.com Git - apple/security.git/blob - OSX/sec/SOSCircle/SecureObjectSync/SOSAccountTrustClassic+Circle.m
Security-58286.200.222.tar.gz
[apple/security.git] / OSX / sec / SOSCircle / SecureObjectSync / SOSAccountTrustClassic+Circle.m
1 //
2 // SOSAccountTrustClassicCircle.m
3 // Security
4 //
5
6 #import <Foundation/Foundation.h>
7 #include <AssertMacros.h>
8
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"
15
16 #import "Security/SecureObjectSync/SOSAccountGhost.h"
17 #import "Security/SecureObjectSync/SOSViews.h"
18
19 @implementation SOSAccountTrustClassic (Circle)
20
21 -(bool) isInCircleOnly:(CFErrorRef *)error
22 {
23 SOSCCStatus result = [self getCircleStatusOnly:error];
24
25 if (result != kSOSCCInCircle) {
26 SOSErrorCreate(kSOSErrorNoCircle, error, NULL, CFSTR("Not in circle"));
27 return false;
28 }
29
30 return true;
31 }
32
33 -(bool) hasCircle:(CFErrorRef*) error
34 {
35 if (!self.trustedCircle)
36 SOSCreateErrorWithFormat(kSOSErrorNoCircle, NULL, error, NULL, CFSTR("No trusted circle"));
37
38 return self.trustedCircle != NULL;
39 }
40
41 -(SOSCCStatus) thisDeviceStatusInCircle:(SOSCircleRef) circle peer:(SOSPeerInfoRef) this_peer
42 {
43 if (!circle)
44 return kSOSCCNotInCircle;
45
46 if (circle && SOSCircleCountPeers(circle) == 0)
47 return kSOSCCCircleAbsent;
48
49 if (this_peer) {
50
51 if(SOSPeerInfoIsRetirementTicket(this_peer))
52 return kSOSCCNotInCircle;
53
54 if (SOSCircleHasPeer(circle, this_peer, NULL))
55 return kSOSCCInCircle;
56
57 if (SOSCircleHasApplicant(circle, this_peer, NULL))
58 return kSOSCCRequestPending;
59 }
60
61 return kSOSCCNotInCircle;
62 }
63 -(SOSCCStatus) getCircleStatusOnly:(CFErrorRef*) error
64 {
65 return [self thisDeviceStatusInCircle:self.trustedCircle peer:self.peerInfo];
66 }
67
68
69 -(SOSCircleRef) getCircle:(CFErrorRef *)error
70 {
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;
75
76 fail:
77 return NULL;
78 }
79
80
81 //Circle
82
83 -(SOSCircleRef) ensureCircle:(SOSAccount*)a name:(CFStringRef)name err:(CFErrorRef *)error
84 {
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;
92 }
93
94 require_action_quiet(self.trustedCircle || !isSOSErrorCoded(localError, kSOSErrorIncompatibleCircle), fail,
95 if (error) { *error = localError; localError = NULL; });
96
97 fail:
98 CFReleaseNull(localError);
99 return self.trustedCircle;
100 }
101
102 -(bool) hasLeft
103 {
104 switch(self.departureCode) {
105 case kSOSDiscoveredRetirement: /* Fallthrough */
106 case kSOSLostPrivateKey: /* Fallthrough */
107 case kSOSWithdrewMembership: /* Fallthrough */
108 case kSOSMembershipRevoked: /* Fallthrough */
109 case kSOSLeftUntrustedCircle:
110 return true;
111 case kSOSNeverAppliedToCircle: /* Fallthrough */
112 case kSOSNeverLeftCircle: /* Fallthrough */
113 default:
114 return false;
115 }
116 }
117
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.
120 */
121
122 -(bool) ghostBustingOK:(SOSCircleRef) oldCircle updatingTo:(SOSCircleRef) newCircle {
123 bool retval = false;
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;
129
130 CFStringRef myPid = SOSPeerInfoGetPeerID(me);
131 CFDictionaryRef newSigs = SOSCircleCopyAllSignatures(newCircle);
132 bool iSignedNew = CFDictionaryGetCountOfKey(newSigs, myPid);
133 long otherPeerSigCount = CFDictionaryGetCount(newSigs) - ((iSignedNew) ? 2: 1);
134
135 if (SOSCircleHasPeer(oldCircle, me, NULL)) { // If we're already in the old one we're not PBing
136 retval = true;
137 } else if (!iSignedNew) { // Piggybacking peers always have signed as part of genSigning - so this indicates we're safe to bust.
138 retval = true;
139 } else if(iSignedNew && otherPeerSigCount > 1) { // if others have seen this we're good to bust.
140 retval = true;
141 }
142 CFReleaseNull(newSigs);
143 return retval;
144 }
145
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.
148
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);
156 return true;
157 }
158 CFReleaseNull(myPubKey);
159 return false;
160 }
161
162 static bool SOSCirclePeerOctagonKeysChanged(SOSPeerInfoRef oldPeer, SOSPeerInfoRef newPeer) {
163 bool oldHasOctagonBits = oldPeer && (SOSPeerInfoHasOctagonSigningPubKey(oldPeer) || SOSPeerInfoHasOctagonEncryptionPubKey(oldPeer));
164 bool newHasOctagonBits = newPeer && (SOSPeerInfoHasOctagonSigningPubKey(newPeer) || SOSPeerInfoHasOctagonEncryptionPubKey(newPeer));
165
166 if(!oldPeer) {
167 if(newPeer && newHasOctagonBits) {
168 return true;
169 } else {
170 // New peer to circle has no octagon bits: no change
171 return false;
172 }
173 } else {
174 // Have an old peer
175 if(!newPeer) {
176 // We removed a peer. This is an octagon bits change if the old peer had octagon bits
177 return oldHasOctagonBits;
178 }
179
180 // This is a peer update: Did the keys change?
181 if(!oldHasOctagonBits && !newHasOctagonBits) {
182 // both peers have no keys: no change
183 return false;
184
185 }
186 SecKeyRef oldSigningKey = SOSPeerInfoCopyOctagonSigningPublicKey(oldPeer, NULL);
187 SecKeyRef newSigningKey = SOSPeerInfoCopyOctagonSigningPublicKey(newPeer, NULL);
188
189 bool signingKeyChanged = CFEqualSafe(oldSigningKey, newSigningKey);
190
191 CFReleaseNull(oldSigningKey);
192 CFReleaseNull(newSigningKey);
193
194
195 SecKeyRef oldEncryptionKey = SOSPeerInfoCopyOctagonEncryptionPublicKey(oldPeer, NULL);
196 SecKeyRef newEncryptionKey = SOSPeerInfoCopyOctagonEncryptionPublicKey(newPeer, NULL);
197
198 bool encryptionKeyChanged = CFEqualSafe(oldEncryptionKey, newEncryptionKey);
199
200 CFReleaseNull(oldEncryptionKey);
201 CFReleaseNull(newEncryptionKey);
202
203 return signingKeyChanged || encryptionKeyChanged;
204 }
205 }
206
207 static bool SOSCircleHasUpdatedPeerInfoWithOctagonKey(SOSCircleRef oldCircle, SOSCircleRef newCircle)
208 {
209 __block bool hasUpdated = false;
210 SOSCircleForEachPeer(oldCircle, ^(SOSPeerInfoRef oldPeer) {
211 SOSPeerInfoRef equivalentNewPeer = SOSCircleCopyPeerWithID(newCircle, SOSPeerInfoGetPeerID(oldPeer), NULL);
212 hasUpdated |= SOSCirclePeerOctagonKeysChanged(oldPeer, equivalentNewPeer);
213 CFReleaseNull(equivalentNewPeer);
214 });
215
216 SOSCircleForEachPeer(newCircle, ^(SOSPeerInfoRef newPeer) {
217 SOSPeerInfoRef equivalentOldPeer = SOSCircleCopyPeerWithID(oldCircle, SOSPeerInfoGetPeerID(newPeer), NULL);
218 hasUpdated |= SOSCirclePeerOctagonKeysChanged(equivalentOldPeer, newPeer);
219 CFReleaseNull(equivalentOldPeer);
220 });
221
222 return hasUpdated;
223 }
224
225 -(bool) handleUpdateCircleWithAnalytics:(SOSCircleRef) prospective_circle transport:(SOSCircleStorageTransport*)circleTransport update:(bool) writeUpdate parentEvent:(NSData*)parentEvent err:(CFErrorRef*)error
226 {
227 bool success = true;
228 bool haveOldCircle = true;
229 const char *local_remote = writeUpdate ? "local": "remote";
230
231 NSError* localError = nil;
232 SFSignInAnalytics* parent = [NSKeyedUnarchiver unarchivedObjectOfClass:[SFSignInAnalytics class] fromData:parentEvent error:&localError];
233
234 SOSAccount* account = [circleTransport getAccount];
235
236 secnotice("signing", "start:[%s]", local_remote);
237 if (!account.accountKey || !account.accountKeyIsTrusted) {
238 SOSCreateError(kSOSErrorPublicKeyAbsent, CFSTR("Can't handle updates with no trusted public key here"), NULL, error);
239 return false;
240 }
241
242 if (!prospective_circle) {
243 secerror("##### Can't update to a NULL circle ######");
244 return false; // Can't update one we don't have.
245 }
246
247 // If this is a remote circle, check to see if this is our first opportunity to trust a circle where we
248 // sponsored the only signer.
249 if(!writeUpdate && [ self checkForSponsorshipTrust: prospective_circle ]){
250 SOSCCEnsurePeerRegistration();
251 secnotice("circleop", "Setting key_interests_need_updating to true in handleUpdateCircle");
252 account.key_interests_need_updating = true;
253 return true;
254
255 }
256
257 CFStringRef newCircleName = SOSCircleGetName(prospective_circle);
258
259 SOSCircleRef oldCircle = self.trustedCircle;
260 SOSCircleRef emptyCircle = NULL;
261
262 if(oldCircle == NULL) {
263 SOSCreateErrorWithFormat(kSOSErrorIncompatibleCircle, NULL, error, NULL, CFSTR("Current Entry is NULL; rejecting %@"), prospective_circle);
264 secerror("##### Can't replace circle - we don't care about it ######");
265 return false;
266 }
267 if (CFGetTypeID(oldCircle) != SOSCircleGetTypeID()) {
268 secdebug("signing", ">>>>>>>>>>>>>>> Non-Circle Circle found <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<");
269 // We don't know what is in our table, likely it was kCFNull indicating we didn't
270 // understand a circle that came by. We seem to like this one lets make our entry be empty circle
271 emptyCircle = SOSCircleCreate(kCFAllocatorDefault, newCircleName, NULL);
272 oldCircle = emptyCircle;
273 haveOldCircle = false;
274 // And we're paranoid, drop our old peer info if for some reason we didn't before.
275 // SOSAccountDestroyCirclePeerInfo(account, oldCircle, NULL);
276 }
277
278 CFErrorRef retiredError = NULL;
279 SFSignInAnalytics *scanForRetiredEvent = [parent newSubTaskForEvent:@"scanForRetiredEvent"];
280 SOSAccountScanForRetired(account, prospective_circle, &retiredError);
281 if(retiredError){
282 [scanForRetiredEvent logRecoverableError:(__bridge NSError*)retiredError];
283 secerror("scan for retired error: %@", retiredError);
284 if(error){
285 *error = retiredError;
286 }else{
287 CFReleaseNull(retiredError);
288 }
289 }
290 [scanForRetiredEvent stopWithAttributes:nil];
291
292 SOSCircleRef newCircle = SOSAccountCloneCircleWithRetirement(account, prospective_circle, error);
293 if(!newCircle) return false;
294
295 SFSignInAnalytics *ghostBustingEvent = [parent newSubTaskForEvent:@"ghostBustingEvent"];
296 if([self ghostBustingOK: oldCircle updatingTo:newCircle]) {
297 SOSCircleRef ghostCleaned = SOSAccountCloneCircleWithoutMyGhosts(account, newCircle);
298 if(ghostCleaned) {
299 CFRetainAssign(newCircle, ghostCleaned);
300 writeUpdate = true;
301 }
302 }
303 [ghostBustingEvent stopWithAttributes:nil];
304
305 SOSFullPeerInfoRef me_full = self.fullPeerInfo;
306 SOSPeerInfoRef me = SOSFullPeerInfoGetPeerInfo(me_full);
307 CFStringRef myPeerID = SOSPeerInfoGetPeerID(me);
308 myPeerID = (myPeerID) ? myPeerID: CFSTR("No Peer");
309
310 if (me && SOSCircleUpdatePeerInfo(newCircle, me)) {
311 writeUpdate = true; // If we update our peer in the new circle we should write it if we accept it.
312 }
313
314 typedef enum {
315 accept,
316 countersign,
317 leave,
318 revert,
319 ignore
320 } circle_action_t;
321
322 static const char *actionstring[] = {
323 "accept", "countersign", "leave", "revert", "ignore",
324 };
325
326 circle_action_t circle_action = ignore;
327 enum DepartureReason leave_reason = kSOSNeverLeftCircle;
328
329 SecKeyRef old_circle_key = NULL;
330
331 CFErrorRef verifyCircleError = NULL;
332 SFSignInAnalytics *verifyCircleEvent = [parent newSubTaskForEvent:@"verifyCircleEvent"];
333 if(SOSCircleVerify(oldCircle, account.accountKey, &verifyCircleError)){
334 old_circle_key = account.accountKey;
335 }
336 else if(account.previousAccountKey && SOSCircleVerify(oldCircle, account.previousAccountKey, &verifyCircleError)){
337 old_circle_key = account.previousAccountKey;
338 }
339 if(verifyCircleError){
340 [verifyCircleEvent logRecoverableError:(__bridge NSError*)verifyCircleError];
341 secerror("verifyCircle error: %@", verifyCircleError);
342 if(error){
343 *error = verifyCircleError;
344 }else{
345 CFReleaseNull(verifyCircleError);
346 }
347 }
348 [verifyCircleEvent stopWithAttributes:nil];
349
350 bool userTrustedOldCircle = (old_circle_key != NULL) && haveOldCircle;
351
352 SFSignInAnalytics *concordanceTrustEvent = [parent newSubTaskForEvent:@"concordanceTrustEvent"];
353 CFErrorRef concordanceError = NULL;
354 SOSConcordanceStatus concstat =
355 SOSCircleConcordanceTrust(oldCircle, newCircle,
356 old_circle_key, account.accountKey,
357 me, &concordanceError);
358 if(concordanceError){
359 [concordanceTrustEvent logRecoverableError:(__bridge NSError*)concordanceError];
360 secerror("concordance trust error: %@", concordanceError);
361 if(error){
362 *error = concordanceError;
363 }else{
364 CFReleaseNull(concordanceError);
365 }
366 }
367 [concordanceTrustEvent stopWithAttributes:nil];
368
369 CFStringRef concStr = NULL;
370 switch(concstat) {
371 case kSOSConcordanceTrusted:
372 circle_action = countersign;
373 concStr = CFSTR("Trusted");
374 break;
375 case kSOSConcordanceGenOld:
376 circle_action = userTrustedOldCircle ? revert : ignore;
377 concStr = CFSTR("Generation Old");
378 break;
379 case kSOSConcordanceBadUserSig:
380 case kSOSConcordanceBadPeerSig:
381 circle_action = userTrustedOldCircle ? revert : accept;
382 concStr = CFSTR("Bad Signature");
383 break;
384 case kSOSConcordanceNoUserSig:
385 circle_action = userTrustedOldCircle ? revert : accept;
386 concStr = CFSTR("No User Signature");
387 break;
388 case kSOSConcordanceNoPeerSig:
389 circle_action = accept; // We might like this one eventually but don't countersign.
390 concStr = CFSTR("No trusted peer signature");
391 secnotice("signing", "##### No trusted peer signature found, accepting hoping for concordance later");
392 break;
393 case kSOSConcordanceNoPeer:
394 circle_action = leave;
395 leave_reason = kSOSLeftUntrustedCircle;
396 concStr = CFSTR("No trusted peer left");
397 break;
398 case kSOSConcordanceNoUserKey:
399 secerror("##### No User Public Key Available, this shouldn't ever happen!!!");
400 abort();
401 break;
402 default:
403 secerror("##### Bad Error Return from ConcordanceTrust");
404 abort();
405 break;
406 }
407
408 secnotice("signing", "Decided on action [%s] based on concordance state [%@] and [%s] circle. My PeerID is %@", actionstring[circle_action], concStr, userTrustedOldCircle ? "trusted" : "untrusted", myPeerID);
409
410 SOSCircleRef circleToPush = NULL;
411
412 if (circle_action == leave) {
413 circle_action = ignore; (void) circle_action; // Acknowledge this is a dead store.
414
415 if (me && SOSCircleHasPeer(oldCircle, me, NULL)) {
416 secnotice("account", "Leaving circle with peer %@", me);
417 debugDumpCircle(CFSTR("oldCircle"), oldCircle);
418 debugDumpCircle(CFSTR("newCircle"), newCircle);
419 debugDumpCircle(CFSTR("prospective_circle"), prospective_circle);
420 secnotice("account", "Key state: accountKey %@, previousAccountKey %@, old_circle_key %@",
421 account.accountKey, account.previousAccountKey, old_circle_key);
422
423 if (sosAccountLeaveCircleWithAnalytics(account, newCircle, parentEvent, error)) {
424 secnotice("circleOps", "Leaving circle by newcircle state");
425 circleToPush = newCircle;
426 } else {
427 secnotice("signing", "Can't leave circle, but dumping identities");
428 success = false;
429 }
430 self.departureCode = leave_reason;
431 circle_action = accept;
432 me = NULL;
433 me_full = NULL;
434 } else {
435 // We are not in this circle, but we need to update account with it, since we got it from cloud
436 secnotice("signing", "We are not in this circle, but we need to update account with it");
437 debugDumpCircle(CFSTR("oldCircle"), oldCircle);
438 debugDumpCircle(CFSTR("newCircle"), newCircle);
439 debugDumpCircle(CFSTR("prospective_circle"), prospective_circle);
440 circle_action = accept;
441 }
442 }
443
444 if (circle_action == countersign) {
445 if (me && SOSCircleHasPeer(newCircle, me, NULL)) {
446 SFSignInAnalytics *verifyPeerSignedEvent = [parent newSubTaskForEvent:@"verifyPeerSignedEvent"];
447 CFErrorRef verifyPeerSignedError = NULL;
448 if (SOSCircleVerifyPeerSigned(newCircle, me, &verifyPeerSignedError)) {
449 secnotice("signing", "Already concur with the new circle");
450 } else {
451 CFErrorRef signing_error = NULL;
452 SFSignInAnalytics *circleConcordanceSignEvent = [parent newSubTaskForEvent:@"circleConcordanceSignEvent"];
453 if (me_full && SOSCircleConcordanceSign(newCircle, me_full, &signing_error)) {
454 circleToPush = newCircle;
455 secnotice("signing", "Concurred with new circle");
456 } else {
457 secerror("Failed to concurrence sign, error: %@", signing_error);
458 success = false;
459 }
460 if(signing_error){
461 [circleConcordanceSignEvent logRecoverableError:(__bridge NSError*)signing_error];
462 secerror("circle concordance sign error: %@", signing_error);
463 if(error){
464 *error = signing_error;
465 }else{
466 CFReleaseNull(signing_error);
467 }
468 }
469 [circleConcordanceSignEvent stopWithAttributes:nil];
470 CFReleaseSafe(signing_error);
471 }
472 if(verifyPeerSignedError){
473 [verifyPeerSignedEvent logRecoverableError:(__bridge NSError*)verifyPeerSignedError];
474 secerror("verify peer signed error: %@", verifyPeerSignedError);
475 if(error){
476 *error = verifyPeerSignedError;
477 }else{
478 CFReleaseNull(verifyPeerSignedError);
479 }
480 }
481 [verifyPeerSignedEvent stopWithAttributes:nil];
482 } else {
483 secnotice("signing", "Not countersigning, not in new circle");
484 }
485 circle_action = accept;
486 }
487
488 if (circle_action == accept) {
489 if(SOSCircleHasUpdatedPeerInfoWithOctagonKey(oldCircle, newCircle)){
490 notify_post(kSOSCCCircleOctagonKeysChangedNotification);
491 }
492 if (me && SOSCircleHasActivePeer(oldCircle, me, NULL) && !SOSCircleHasPeer(newCircle, me, NULL)) {
493 // Don't destroy evidence of other code determining reason for leaving.
494 if(![self hasLeft]) self.departureCode = kSOSMembershipRevoked;
495 secnotice("circleOps", "Member of old circle but not of new circle (%d)", self.departureCode);
496 debugDumpCircle(CFSTR("oldCircle"), oldCircle);
497 debugDumpCircle(CFSTR("newCircle"), newCircle);
498 }
499
500 if (me
501 && SOSCircleHasActivePeer(oldCircle, me, NULL)
502 && !(SOSCircleCountPeers(oldCircle) == 1 && SOSCircleHasPeer(oldCircle, me, NULL)) // If it was our offering, don't change ID to avoid ghosts
503 && !SOSCircleHasPeer(newCircle, me, NULL) && !SOSCircleHasApplicant(newCircle, me, NULL)) {
504 secnotice("circle", "Purging my peer (ID: %@) for circle '%@'!!!", SOSPeerInfoGetPeerID(me), SOSCircleGetName(oldCircle));
505 if (self.fullPeerInfo)
506 SOSFullPeerInfoPurgePersistentKey(self.fullPeerInfo, NULL);
507 me = NULL;
508 me_full = NULL;
509 }
510
511 if (me && SOSCircleHasRejectedApplicant(newCircle, me, NULL)) {
512 SOSPeerInfoRef reject = SOSCircleCopyRejectedApplicant(newCircle, me, NULL);
513 if(CFEqualSafe(reject, me) && SOSPeerInfoApplicationVerify(me, account.accountKey, NULL)) {
514 secnotice("circle", "Rejected, Purging my applicant peer (ID: %@) for circle '%@'", SOSPeerInfoGetPeerID(me), SOSCircleGetName(oldCircle));
515 debugDumpCircle(CFSTR("oldCircle"), oldCircle);
516 debugDumpCircle(CFSTR("newCircle"), newCircle);
517 if (self.fullPeerInfo)
518 SOSFullPeerInfoPurgePersistentKey(self.fullPeerInfo, NULL);
519 me = NULL;
520 me_full = NULL;
521 } else {
522 secnotice("circle", "Rejected, Reapplying (ID: %@) for circle '%@'", SOSPeerInfoGetPeerID(me), SOSCircleGetName(oldCircle));
523 debugDumpCircle(CFSTR("oldCircle"), oldCircle);
524 debugDumpCircle(CFSTR("newCircle"), newCircle);
525 SOSCircleRequestReadmission(newCircle, account.accountKey, me, NULL);
526 writeUpdate = true;
527 }
528 CFReleaseNull(reject);
529 }
530
531 CFRetainSafe(oldCircle);
532 account.previousAccountKey = account.accountKey;
533
534 secnotice("signing", "%@, Accepting new circle", concStr);
535 if (circle_action == accept) {
536 [self setTrustedCircle:newCircle];
537 }
538
539 if (me && account.accountKeyIsTrusted
540 && SOSCircleHasApplicant(oldCircle, me, NULL)
541 && SOSCircleCountPeers(newCircle) > 0
542 && !SOSCircleHasPeer(newCircle, me, NULL) && !SOSCircleHasApplicant(newCircle, me, NULL)) {
543 // We weren't rejected (above would have set me to NULL.
544 // We were applying and we weren't accepted.
545 // Our application is declared lost, let us reapply.
546
547 secnotice("signing", "requesting readmission to new circle");
548 if (SOSCircleRequestReadmission(newCircle, account.accountKey, me, NULL))
549 writeUpdate = true;
550 }
551
552 if (me && SOSCircleHasActivePeer(oldCircle, me, NULL)) {
553 SFSignInAnalytics *cleanupRetirementTicketsEvent = [parent newSubTaskForEvent:@"cleanupRetirementTicketsEvent"];
554 CFErrorRef cleanupError = NULL;
555 [account.trust cleanupRetirementTickets:account circle:oldCircle time:RETIREMENT_FINALIZATION_SECONDS err:&cleanupError];
556 if(cleanupError){
557 [cleanupRetirementTicketsEvent logRecoverableError:(__bridge NSError*)cleanupError];
558 secerror("cleanup retirement tickets error: %@", cleanupError);
559 if(error){
560 *error = cleanupError;
561 }else{
562 CFReleaseNull(cleanupError);
563 }
564 }
565 [cleanupRetirementTicketsEvent stopWithAttributes:nil];
566 }
567
568 SFSignInAnalytics *notifyOfChangeEvent = [parent newSubTaskForEvent:@"notifyOfChangeEvent"];
569 SOSAccountNotifyOfChange(account, oldCircle, newCircle);
570 [notifyOfChangeEvent stopWithAttributes:nil];
571
572 CFReleaseNull(oldCircle);
573
574 if (writeUpdate)
575 circleToPush = newCircle;
576 secnotice("circleop", "Setting key_interests_need_updating to true in handleUpdateCircle");
577 account.key_interests_need_updating = true;
578 }
579
580 /*
581 * In the revert section we'll guard the KVS idea of circles by rejecting "bad" new circles
582 * and pushing our current view of the circle (oldCircle). We'll only do this if we actually
583 * are a member of oldCircle - never for an empty circle.
584 */
585
586 if (circle_action == revert) {
587 if(haveOldCircle && me && SOSCircleHasActivePeer(oldCircle, me, NULL)) {
588 secnotice("signing", "%@, Rejecting new circle, re-publishing old circle", concStr);
589 circleToPush = oldCircle;
590 [self setTrustedCircle:oldCircle];
591 } else {
592 secnotice("canary", "%@, Rejecting: new circle Have no old circle - would reset", concStr);
593 }
594 }
595
596 if (circleToPush != NULL) {
597 secnotice("signing", "Pushing:[%s]", local_remote);
598 CFDataRef circle_data = SOSCircleCopyEncodedData(circleToPush, kCFAllocatorDefault, error);
599
600 if (circle_data) {
601 // Ensure we flush changes
602 account.circle_rings_retirements_need_attention = true;
603
604 //posting new circle to peers
605 SFSignInAnalytics *postCircleEvent = [parent newSubTaskForEvent:@"postCircleEvent"];
606 CFErrorRef postCircleError = NULL;
607 success &= [circleTransport postCircle:SOSCircleGetName(circleToPush) circleData:circle_data err:&postCircleError];
608 if(postCircleError){
609 [postCircleEvent logRecoverableError:(__bridge NSError*)postCircleError];
610 secerror("posting circle failed: %@", postCircleError);
611 if(error){
612 *error = postCircleError;
613 }else{
614 CFReleaseNull(postCircleError);
615 }
616 }
617 [postCircleEvent stopWithAttributes:nil];
618 } else {
619 success = false;
620 }
621 CFReleaseNull(circle_data);
622 }
623 CFReleaseSafe(newCircle);
624 CFReleaseNull(emptyCircle);
625
626 // There are errors collected above that are soft (worked around)
627 if(success && error && *error) {
628 CFReleaseNull(*error);
629 }
630
631 return success;
632 }
633
634 -(bool) handleUpdateCircle:(SOSCircleRef) prospective_circle transport:(SOSKVSCircleStorageTransport*)circleTransport update:(bool) writeUpdate err:(CFErrorRef*)error
635 {
636 bool success = true;
637 bool haveOldCircle = true;
638 const char *local_remote = writeUpdate ? "local": "remote";
639
640 SOSAccount* account = [circleTransport getAccount];
641
642 secnotice("signing", "start:[%s]", local_remote);
643 if (!account.accountKey || !account.accountKeyIsTrusted) {
644 SOSCreateError(kSOSErrorPublicKeyAbsent, CFSTR("Can't handle updates with no trusted public key here"), NULL, error);
645 return false;
646 }
647
648 if (!prospective_circle) {
649 secerror("##### Can't update to a NULL circle ######");
650 return false; // Can't update one we don't have.
651 }
652
653 // If this is a remote circle, check to see if this is our first opportunity to trust a circle where we
654 // sponsored the only signer.
655 if(!writeUpdate && [ self checkForSponsorshipTrust: prospective_circle ]){
656 SOSCCEnsurePeerRegistration();
657 secnotice("circleop", "Setting key_interests_need_updating to true in handleUpdateCircle");
658 account.key_interests_need_updating = true;
659 return true;
660
661 }
662
663 CFStringRef newCircleName = SOSCircleGetName(prospective_circle);
664
665 SOSCircleRef oldCircle = self.trustedCircle;
666 SOSCircleRef emptyCircle = NULL;
667
668 if(oldCircle == NULL) {
669 SOSCreateErrorWithFormat(kSOSErrorIncompatibleCircle, NULL, error, NULL, CFSTR("Current Entry is NULL; rejecting %@"), prospective_circle);
670 secerror("##### Can't replace circle - we don't care about it ######");
671 return false;
672 }
673 if (CFGetTypeID(oldCircle) != SOSCircleGetTypeID()) {
674 secdebug("signing", ">>>>>>>>>>>>>>> Non-Circle Circle found <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<");
675 // We don't know what is in our table, likely it was kCFNull indicating we didn't
676 // understand a circle that came by. We seem to like this one lets make our entry be empty circle
677 emptyCircle = SOSCircleCreate(kCFAllocatorDefault, newCircleName, NULL);
678 oldCircle = emptyCircle;
679 haveOldCircle = false;
680 // And we're paranoid, drop our old peer info if for some reason we didn't before.
681 // SOSAccountDestroyCirclePeerInfo(account, oldCircle, NULL);
682 }
683
684
685 SOSAccountScanForRetired(account, prospective_circle, error);
686 SOSCircleRef newCircle = SOSAccountCloneCircleWithRetirement(account, prospective_circle, error);
687 if(!newCircle) return false;
688 if([self ghostBustingOK: oldCircle updatingTo:newCircle]) {
689 SOSCircleRef ghostCleaned = SOSAccountCloneCircleWithoutMyGhosts(account, newCircle);
690 if(ghostCleaned) {
691 CFRetainAssign(newCircle, ghostCleaned);
692 writeUpdate = true;
693 }
694 }
695
696 SOSFullPeerInfoRef me_full = self.fullPeerInfo;
697 SOSPeerInfoRef me = SOSFullPeerInfoGetPeerInfo(me_full);
698 CFStringRef myPeerID = SOSPeerInfoGetPeerID(me);
699 myPeerID = (myPeerID) ? myPeerID: CFSTR("No Peer");
700
701 if (me && SOSCircleUpdatePeerInfo(newCircle, me)) {
702 writeUpdate = true; // If we update our peer in the new circle we should write it if we accept it.
703 }
704
705 typedef enum {
706 accept,
707 countersign,
708 leave,
709 revert,
710 ignore
711 } circle_action_t;
712
713 static const char *actionstring[] = {
714 "accept", "countersign", "leave", "revert", "ignore",
715 };
716
717 circle_action_t circle_action = ignore;
718 enum DepartureReason leave_reason = kSOSNeverLeftCircle;
719
720 SecKeyRef old_circle_key = NULL;
721 if(SOSCircleVerify(oldCircle, account.accountKey, NULL)){
722 old_circle_key = account.accountKey;
723 }
724 else if(account.previousAccountKey && SOSCircleVerify(oldCircle, account.previousAccountKey, NULL)){
725 old_circle_key = account.previousAccountKey;
726 }
727
728 bool userTrustedOldCircle = (old_circle_key != NULL) && haveOldCircle;
729
730 SOSConcordanceStatus concstat =
731 SOSCircleConcordanceTrust(oldCircle, newCircle,
732 old_circle_key, account.accountKey,
733 me, error);
734
735 CFStringRef concStr = NULL;
736 switch(concstat) {
737 case kSOSConcordanceTrusted:
738 circle_action = countersign;
739 concStr = CFSTR("Trusted");
740 break;
741 case kSOSConcordanceGenOld:
742 circle_action = userTrustedOldCircle ? revert : ignore;
743 concStr = CFSTR("Generation Old");
744 break;
745 case kSOSConcordanceBadUserSig:
746 case kSOSConcordanceBadPeerSig:
747 circle_action = userTrustedOldCircle ? revert : accept;
748 concStr = CFSTR("Bad Signature");
749 break;
750 case kSOSConcordanceNoUserSig:
751 circle_action = userTrustedOldCircle ? revert : accept;
752 concStr = CFSTR("No User Signature");
753 break;
754 case kSOSConcordanceNoPeerSig:
755 circle_action = accept; // We might like this one eventually but don't countersign.
756 concStr = CFSTR("No trusted peer signature");
757 secnotice("signing", "##### No trusted peer signature found, accepting hoping for concordance later");
758 break;
759 case kSOSConcordanceNoPeer:
760 circle_action = leave;
761 leave_reason = kSOSLeftUntrustedCircle;
762 concStr = CFSTR("No trusted peer left");
763 break;
764 case kSOSConcordanceNoUserKey:
765 secerror("##### No User Public Key Available, this shouldn't ever happen!!!");
766 abort();
767 break;
768 default:
769 secerror("##### Bad Error Return from ConcordanceTrust");
770 abort();
771 break;
772 }
773
774 secnotice("signing", "Decided on action [%s] based on concordance state [%@] and [%s] circle. My PeerID is %@", actionstring[circle_action], concStr, userTrustedOldCircle ? "trusted" : "untrusted", myPeerID);
775
776 SOSCircleRef circleToPush = NULL;
777
778 if (circle_action == leave) {
779 circle_action = ignore; (void) circle_action; // Acknowledge this is a dead store.
780
781 if (me && SOSCircleHasPeer(oldCircle, me, NULL)) {
782 secnotice("account", "Leaving circle with peer %@", me);
783 debugDumpCircle(CFSTR("oldCircle"), oldCircle);
784 debugDumpCircle(CFSTR("newCircle"), newCircle);
785 debugDumpCircle(CFSTR("prospective_circle"), prospective_circle);
786 secnotice("account", "Key state: accountKey %@, previousAccountKey %@, old_circle_key %@",
787 account.accountKey, account.previousAccountKey, old_circle_key);
788
789 if (sosAccountLeaveCircle(account, newCircle, error)) {
790 secnotice("circleOps", "Leaving circle by newcircle state");
791 circleToPush = newCircle;
792 } else {
793 secnotice("signing", "Can't leave circle, but dumping identities");
794 success = false;
795 }
796 self.departureCode = leave_reason;
797 circle_action = accept;
798 me = NULL;
799 me_full = NULL;
800 } else {
801 // We are not in this circle, but we need to update account with it, since we got it from cloud
802 secnotice("signing", "We are not in this circle, but we need to update account with it");
803 debugDumpCircle(CFSTR("oldCircle"), oldCircle);
804 debugDumpCircle(CFSTR("newCircle"), newCircle);
805 debugDumpCircle(CFSTR("prospective_circle"), prospective_circle);
806 circle_action = accept;
807 }
808 }
809
810 if (circle_action == countersign) {
811 if (me && SOSCircleHasPeer(newCircle, me, NULL)) {
812 if (SOSCircleVerifyPeerSigned(newCircle, me, NULL)) {
813 secnotice("signing", "Already concur with the new circle");
814 } else {
815 CFErrorRef signing_error = NULL;
816
817 if (me_full && SOSCircleConcordanceSign(newCircle, me_full, &signing_error)) {
818 circleToPush = newCircle;
819 secnotice("signing", "Concurred with new circle");
820 } else {
821 secerror("Failed to concurrence sign, error: %@", signing_error);
822 success = false;
823 }
824 CFReleaseSafe(signing_error);
825 }
826 } else {
827 secnotice("signing", "Not countersigning, not in new circle");
828 }
829 circle_action = accept;
830 }
831
832 if (circle_action == accept) {
833 if(SOSCircleHasUpdatedPeerInfoWithOctagonKey(oldCircle, newCircle)){
834 notify_post(kSOSCCCircleOctagonKeysChangedNotification);
835 }
836 if (me && SOSCircleHasActivePeer(oldCircle, me, NULL) && !SOSCircleHasPeer(newCircle, me, NULL)) {
837 // Don't destroy evidence of other code determining reason for leaving.
838 if(![self hasLeft]) self.departureCode = kSOSMembershipRevoked;
839 secnotice("circleOps", "Member of old circle but not of new circle (%d)", self.departureCode);
840 debugDumpCircle(CFSTR("oldCircle"), oldCircle);
841 debugDumpCircle(CFSTR("newCircle"), newCircle);
842 }
843
844 if (me
845 && SOSCircleHasActivePeer(oldCircle, me, NULL)
846 && !(SOSCircleCountPeers(oldCircle) == 1 && SOSCircleHasPeer(oldCircle, me, NULL)) // If it was our offering, don't change ID to avoid ghosts
847 && !SOSCircleHasPeer(newCircle, me, NULL) && !SOSCircleHasApplicant(newCircle, me, NULL)) {
848 secnotice("circle", "Purging my peer (ID: %@) for circle '%@'!!!", SOSPeerInfoGetPeerID(me), SOSCircleGetName(oldCircle));
849 if (self.fullPeerInfo)
850 SOSFullPeerInfoPurgePersistentKey(self.fullPeerInfo, NULL);
851 me = NULL;
852 me_full = NULL;
853 }
854
855 if (me && SOSCircleHasRejectedApplicant(newCircle, me, NULL)) {
856 SOSPeerInfoRef reject = SOSCircleCopyRejectedApplicant(newCircle, me, NULL);
857 if(CFEqualSafe(reject, me) && SOSPeerInfoApplicationVerify(me, account.accountKey, NULL)) {
858 secnotice("circle", "Rejected, Purging my applicant peer (ID: %@) for circle '%@'", SOSPeerInfoGetPeerID(me), SOSCircleGetName(oldCircle));
859 debugDumpCircle(CFSTR("oldCircle"), oldCircle);
860 debugDumpCircle(CFSTR("newCircle"), newCircle);
861 if (self.fullPeerInfo)
862 SOSFullPeerInfoPurgePersistentKey(self.fullPeerInfo, NULL);
863 me = NULL;
864 me_full = NULL;
865 } else {
866 secnotice("circle", "Rejected, Reapplying (ID: %@) for circle '%@'", SOSPeerInfoGetPeerID(me), SOSCircleGetName(oldCircle));
867 debugDumpCircle(CFSTR("oldCircle"), oldCircle);
868 debugDumpCircle(CFSTR("newCircle"), newCircle);
869 SOSCircleRequestReadmission(newCircle, account.accountKey, me, NULL);
870 writeUpdate = true;
871 }
872 CFReleaseNull(reject);
873 }
874
875 CFRetainSafe(oldCircle);
876 account.previousAccountKey = account.accountKey;
877
878 secnotice("signing", "%@, Accepting new circle", concStr);
879 if (circle_action == accept) {
880 [self setTrustedCircle:newCircle];
881 }
882
883 if (me && account.accountKeyIsTrusted
884 && SOSCircleHasApplicant(oldCircle, me, NULL)
885 && SOSCircleCountPeers(newCircle) > 0
886 && !SOSCircleHasPeer(newCircle, me, NULL) && !SOSCircleHasApplicant(newCircle, me, NULL)) {
887 // We weren't rejected (above would have set me to NULL.
888 // We were applying and we weren't accepted.
889 // Our application is declared lost, let us reapply.
890
891 secnotice("signing", "requesting readmission to new circle");
892 if (SOSCircleRequestReadmission(newCircle, account.accountKey, me, NULL))
893 writeUpdate = true;
894 }
895
896 if (me && SOSCircleHasActivePeer(oldCircle, me, NULL)) {
897 [account.trust cleanupRetirementTickets:account circle:oldCircle time:RETIREMENT_FINALIZATION_SECONDS err:NULL];
898 }
899
900 SOSAccountNotifyOfChange(account, oldCircle, newCircle);
901
902 CFReleaseNull(oldCircle);
903
904 if (writeUpdate)
905 circleToPush = newCircle;
906 secnotice("circleop", "Setting key_interests_need_updating to true in handleUpdateCircle");
907 account.key_interests_need_updating = true;
908 }
909
910 /*
911 * In the revert section we'll guard the KVS idea of circles by rejecting "bad" new circles
912 * and pushing our current view of the circle (oldCircle). We'll only do this if we actually
913 * are a member of oldCircle - never for an empty circle.
914 */
915
916 if (circle_action == revert) {
917 if(haveOldCircle && me && SOSCircleHasActivePeer(oldCircle, me, NULL)) {
918 secnotice("signing", "%@, Rejecting new circle, re-publishing old circle", concStr);
919 circleToPush = oldCircle;
920 [self setTrustedCircle:oldCircle];
921 } else {
922 secnotice("canary", "%@, Rejecting: new circle Have no old circle - would reset", concStr);
923 }
924 }
925
926
927 if (circleToPush != NULL) {
928 secnotice("signing", "Pushing:[%s]", local_remote);
929 CFDataRef circle_data = SOSCircleCopyEncodedData(circleToPush, kCFAllocatorDefault, error);
930
931 if (circle_data) {
932 // Ensure we flush changes
933 account.circle_rings_retirements_need_attention = true;
934
935 //posting new circle to peers
936 success &= [circleTransport postCircle:SOSCircleGetName(circleToPush) circleData:circle_data err:error];
937 } else {
938 success = false;
939 }
940 CFReleaseNull(circle_data);
941 }
942 CFReleaseSafe(newCircle);
943 CFReleaseNull(emptyCircle);
944
945 // There are errors collected above that are soft (worked around)
946 if(success && error && *error) {
947 CFReleaseNull(*error);
948 }
949
950 return success;
951 }
952
953 -(bool) updateCircleFromRemote:(SOSKVSCircleStorageTransport*)circleTransport newCircle:(SOSCircleRef)newCircle err:(CFErrorRef*)error
954 {
955 return [self handleUpdateCircle:newCircle transport:circleTransport update:false err:error];
956 }
957
958 -(bool) updateCircle:(SOSKVSCircleStorageTransport*)circleTransport newCircle:(SOSCircleRef) newCircle err:(CFErrorRef*)error
959 {
960 return [self handleUpdateCircle:newCircle transport:circleTransport update:true err:error];
961 }
962
963 -(bool) updateCircleWithAnalytics:(SOSKVSCircleStorageTransport*)circleTransport newCircle:(SOSCircleRef) newCircle parentEvent:(NSData*)parentEvent err:(CFErrorRef*)error
964 {
965 return [self handleUpdateCircle:newCircle transport:circleTransport update:true err:error];
966 }
967
968 -(bool) modifyCircleWithAnalytics:(SOSKVSCircleStorageTransport*)circleTransport parentEvent:(NSData*)parentEvent err:(CFErrorRef*)error action:(SOSModifyCircleBlock)block
969 {
970 bool success = false;
971 SOSCircleRef circleCopy = NULL;
972 require_action_quiet(self.trustedCircle, fail, SOSErrorCreate(kSOSErrorNoCircle, error, NULL, CFSTR("No circle to get peer key from")));
973
974 circleCopy = SOSCircleCopyCircle(kCFAllocatorDefault, self.trustedCircle, error);
975 require_quiet(circleCopy, fail);
976
977 success = true;
978 require_quiet(block(circleCopy), fail);
979
980 success = [self updateCircleWithAnalytics:circleTransport newCircle:circleCopy parentEvent:parentEvent err:error];
981
982 fail:
983 CFReleaseSafe(circleCopy);
984 return success;
985 }
986
987 -(bool) modifyCircle:(SOSKVSCircleStorageTransport*)circleTransport err:(CFErrorRef*)error action:(SOSModifyCircleBlock)block
988 {
989 bool success = false;
990 SOSCircleRef circleCopy = NULL;
991 require_action_quiet(self.trustedCircle, fail, SOSErrorCreate(kSOSErrorNoCircle, error, NULL, CFSTR("No circle to get peer key from")));
992
993 circleCopy = SOSCircleCopyCircle(kCFAllocatorDefault, self.trustedCircle, error);
994 require_quiet(circleCopy, fail);
995
996 success = true;
997 require_quiet(block(circleCopy), fail);
998
999 success = [self updateCircle:circleTransport newCircle:circleCopy err:error];
1000
1001 fail:
1002 CFReleaseSafe(circleCopy);
1003 return success;
1004
1005 }
1006
1007 -(void) generationSignatureUpdateWith:(SOSAccount*)account key:(SecKeyRef) privKey
1008 {
1009 if (self.trustedCircle && self.fullPeerInfo) {
1010 [self modifyCircle:account.circle_transport err:NULL action:^(SOSCircleRef circle) {
1011 SOSPeerInfoRef myPI = account.peerInfo;
1012 bool iAmPeer = SOSCircleHasPeer(circle, myPI, NULL);
1013 bool change = SOSCircleUpdatePeerInfo(circle, myPI);
1014 if(iAmPeer && !SOSCircleVerify(circle, account.accountKey, NULL)) {
1015 change |= [self upgradeiCloudIdentity:circle privKey:privKey];
1016 [self removeInvalidApplications:circle userPublic:account.accountKey];
1017 change |= SOSCircleGenerationSign(circle, privKey, self.fullPeerInfo, NULL);
1018 [self setDepartureCode:kSOSNeverLeftCircle];
1019 } else if(iAmPeer) {
1020 SOSFullPeerInfoRef icfpi = SOSCircleCopyiCloudFullPeerInfoRef(circle, NULL);
1021 if(!icfpi) {
1022 SOSAccountRemoveIncompleteiCloudIdentities(account, circle, privKey, NULL);
1023 bool identityAdded = [self addiCloudIdentity:circle key:privKey err:NULL];
1024 if(identityAdded) {
1025 account.notifyBackupOnExit = true;
1026 }
1027 change |= identityAdded;
1028 } else {
1029 CFReleaseNull(icfpi);
1030 }
1031 }
1032 secnotice("updatingGenSignature", "we changed the circle? %@", change ? CFSTR("YES") : CFSTR("NO"));
1033 return change;
1034 }];
1035 }
1036 }
1037
1038 -(void) forEachCirclePeerExceptMe:(SOSIteratePeerBlock)block
1039 {
1040 if (self.trustedCircle && self.peerInfo) {
1041 NSString* myPi_id = self.peerID;
1042 SOSCircleForEachPeer(self.trustedCircle, ^(SOSPeerInfoRef peer) {
1043 CFStringRef peerID = SOSPeerInfoGetPeerID(peer);
1044 if (peerID && ![myPi_id isEqualToString:(__bridge NSString*) peerID]) {
1045 block(peer);
1046 }
1047 });
1048 }
1049 }
1050
1051 -(bool) leaveCircleWithAccount:(SOSAccount*)account withAnalytics:(NSData*)parentEvent err:(CFErrorRef*) error
1052 {
1053 bool result = true;
1054 secnotice("circleOps", "leaveCircleWithAccount: Leaving circle by client request");
1055 result &= [self modifyCircle:account.circle_transport err:error action:^(SOSCircleRef circle) {
1056 return sosAccountLeaveCircleWithAnalytics(account, circle, parentEvent, error);
1057 }];
1058
1059 self.departureCode = kSOSWithdrewMembership;
1060
1061 return result;
1062 }
1063
1064 -(bool) leaveCircle:(SOSAccount*)account err:(CFErrorRef*) error
1065 {
1066 bool result = true;
1067 secnotice("circleOps", "Leaving circle by client request");
1068 result &= [self modifyCircle:account.circle_transport err:error action:^(SOSCircleRef circle) {
1069 return sosAccountLeaveCircle(account, circle, error);
1070 }];
1071
1072 self.departureCode = kSOSWithdrewMembership;
1073
1074 return result;
1075 }
1076
1077 -(bool) resetToOffering:(SOSAccountTransaction*) aTxn key:(SecKeyRef)userKey err:(CFErrorRef*) error
1078 {
1079 SOSFullPeerInfoPurgePersistentKey(self.fullPeerInfo, NULL);
1080 self.fullPeerInfo = nil;
1081
1082 secnotice("resetToOffering", "Resetting circle to offering by request from client");
1083
1084 return userKey && [self resetCircleToOffering:aTxn userKey:userKey err:error];
1085 }
1086
1087
1088 -(bool) resetCircleToOffering:(SOSAccountTransaction*) aTxn userKey:(SecKeyRef)user_key err:(CFErrorRef *)error
1089 {
1090 bool result = false;
1091
1092 SOSAccount* account = aTxn.account;
1093 if(![self hasCircle:error])
1094 return result;
1095
1096 if(![self ensureFullPeerAvailable:(__bridge CFDictionaryRef)(account.gestalt) deviceID:(__bridge CFStringRef)(account.deviceID) backupKey:(__bridge CFDataRef)(account.backup_key) err:error])
1097 return result;
1098
1099 (void)[self resetAllRings:account err:error];
1100
1101 [self modifyCircle:account.circle_transport err:error action:^bool(SOSCircleRef circle) {
1102 bool result = false;
1103 SOSFullPeerInfoRef cloud_identity = NULL;
1104 CFErrorRef localError = NULL;
1105
1106 require_quiet(SOSCircleResetToOffering(circle, user_key, self.fullPeerInfo, &localError), err_out);
1107
1108 self.departureCode = kSOSNeverLeftCircle;
1109
1110 require_quiet([self addEscrowToPeerInfo:self.fullPeerInfo err:error], err_out);
1111
1112 require_quiet([self addiCloudIdentity:circle key:user_key err:error], err_out);
1113 result = true;
1114 SOSAccountPublishCloudParameters(account, NULL);
1115 account.notifyBackupOnExit = true;
1116
1117 err_out:
1118 if (result == false)
1119 secerror("error resetting circle (%@) to offering: %@", circle, localError);
1120 if (localError && error && *error == NULL) {
1121 *error = localError;
1122 localError = NULL;
1123 }
1124 CFReleaseNull(localError);
1125 CFReleaseNull(cloud_identity);
1126 return result;
1127 }];
1128
1129 [self setValueInExpansion:kSOSUnsyncedViewsKey value:kCFBooleanTrue err:NULL];
1130 SOSAccountUpdateOutOfSyncViews(aTxn, SOSViewsGetAllCurrent());
1131
1132 result = true;
1133
1134 return result;
1135 }
1136
1137 void SOSAccountForEachCirclePeerExceptMe(SOSAccount* account, void (^action)(SOSPeerInfoRef peer)) {
1138 SOSPeerInfoRef myPi = account.peerInfo;
1139 SOSCircleRef circle = NULL;
1140
1141 SOSAccountTrustClassic *trust = account.trust;
1142 circle = trust.trustedCircle;
1143 if (circle && myPi) {
1144 CFStringRef myPi_id = SOSPeerInfoGetPeerID(myPi);
1145 SOSCircleForEachPeer(circle, ^(SOSPeerInfoRef peer) {
1146 CFStringRef peerID = SOSPeerInfoGetPeerID(peer);
1147 if (peerID && !CFEqual(peerID, myPi_id)) {
1148 action(peer);
1149 }
1150 });
1151 }
1152 }
1153
1154 -(bool) joinCircle:(SOSAccountTransaction*) aTxn userKey:(SecKeyRef) user_key useCloudPeer:(bool) use_cloud_peer err:(CFErrorRef*) error
1155 {
1156 __block bool result = false;
1157 __block SOSFullPeerInfoRef cloud_full_peer = NULL;
1158 __block SOSAccount* account = aTxn.account;
1159 require_action_quiet(self.trustedCircle, fail, SOSCreateErrorWithFormat(kSOSErrorPeerNotFound, NULL, error, NULL, CFSTR("Don't have circle when joining???")));
1160 require_quiet([self ensureFullPeerAvailable:(__bridge CFDictionaryRef)account.gestalt deviceID:(__bridge CFStringRef)account.deviceID backupKey:(__bridge CFDataRef)account.backup_key err:error], fail);
1161
1162 if (SOSCircleCountPeers(self.trustedCircle) == 0 || SOSAccountGhostResultsInReset(account)) {
1163 secnotice("resetToOffering", "Resetting circle to offering since there are no peers");
1164 // this also clears initial sync data
1165 result = [self resetCircleToOffering:aTxn userKey:user_key err:error];
1166 } else {
1167 [self setValueInExpansion:kSOSUnsyncedViewsKey value:kCFBooleanTrue err:NULL];
1168
1169 if (use_cloud_peer) {
1170 cloud_full_peer = SOSCircleCopyiCloudFullPeerInfoRef(self.trustedCircle, NULL);
1171 }
1172
1173 [self modifyCircle: account.circle_transport err:error action:^(SOSCircleRef circle) {
1174 result = SOSAccountAddEscrowToPeerInfo(account, self.fullPeerInfo, error);
1175 result &= SOSCircleRequestAdmission(circle, user_key, self.fullPeerInfo, error);
1176 self.departureCode = kSOSNeverLeftCircle;
1177 if(result && cloud_full_peer) {
1178 CFErrorRef localError = NULL;
1179 CFStringRef cloudid = SOSPeerInfoGetPeerID(SOSFullPeerInfoGetPeerInfo(cloud_full_peer));
1180 require_quiet(cloudid, finish);
1181 require_quiet(SOSCircleHasActivePeerWithID(circle, cloudid, &localError), finish);
1182 require_quiet(SOSCircleAcceptRequest(circle, user_key, cloud_full_peer, self.peerInfo, &localError), finish);
1183
1184 finish:
1185 if (localError){
1186 secerror("Failed to join with cloud identity: %@", localError);
1187 CFReleaseNull(localError);
1188 }
1189 }
1190 return result;
1191 }];
1192
1193 if (use_cloud_peer) {
1194 SOSAccountUpdateOutOfSyncViews(aTxn, SOSViewsGetAllCurrent());
1195 }
1196 }
1197
1198 fail:
1199 CFReleaseNull(cloud_full_peer);
1200 return result;
1201 }
1202
1203 @end