]> git.saurik.com Git - apple/security.git/blob - OSX/sec/SOSCircle/SecureObjectSync/SOSAccountTrustClassic+Circle.m
770135fdbaaa1829f9eb8d34d1735bd27bf8766c
[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 static const char *concordstring[] = {
20 "kSOSConcordanceTrusted",
21 "kSOSConcordanceGenOld", // kSOSErrorReplay
22 "kSOSConcordanceNoUserSig", // kSOSErrorBadSignature
23 "kSOSConcordanceNoUserKey", // kSOSErrorNoKey
24 "kSOSConcordanceNoPeer", // kSOSErrorPeerNotFound
25 "kSOSConcordanceBadUserSig", // kSOSErrorBadSignature
26 "kSOSConcordanceBadPeerSig", // kSOSErrorBadSignature
27 "kSOSConcordanceNoPeerSig",
28 "kSOSConcordanceWeSigned",
29 };
30
31 @implementation SOSAccountTrustClassic (Circle)
32
33 -(bool) isInCircle:(CFErrorRef *)error
34 {
35 SOSCCStatus result = [self getCircleStatus:error];
36
37 if (result != kSOSCCInCircle) {
38 SOSErrorCreate(kSOSErrorNoCircle, error, NULL, CFSTR("Not in circle"));
39 return false;
40 }
41
42 return true;
43 }
44
45 -(bool) hasCircle:(CFErrorRef*) error
46 {
47 if (!self.trustedCircle)
48 SOSCreateErrorWithFormat(kSOSErrorNoCircle, NULL, error, NULL, CFSTR("No trusted circle"));
49
50 return self.trustedCircle != NULL;
51 }
52
53 -(SOSCCStatus) thisDeviceStatusInCircle:(SOSCircleRef) circle peer:(SOSPeerInfoRef) this_peer
54 {
55 if (!circle)
56 return kSOSCCNotInCircle;
57
58 if (circle && SOSCircleCountPeers(circle) == 0)
59 return kSOSCCCircleAbsent;
60
61 if (this_peer) {
62
63 if(SOSPeerInfoIsRetirementTicket(this_peer))
64 return kSOSCCNotInCircle;
65
66 if (SOSCircleHasPeer(circle, this_peer, NULL))
67 return kSOSCCInCircle;
68
69 if (SOSCircleHasApplicant(circle, this_peer, NULL))
70 return kSOSCCRequestPending;
71 }
72
73 return kSOSCCNotInCircle;
74 }
75 -(SOSCCStatus) getCircleStatus:(CFErrorRef*) error
76 {
77 return [self thisDeviceStatusInCircle:self.trustedCircle peer:self.peerInfo];
78 }
79
80
81 -(SOSCircleRef) getCircle:(CFErrorRef *)error
82 {
83 CFTypeRef entry = self.trustedCircle;
84 require_action_quiet(!isNull(entry), fail,
85 SOSCreateError(kSOSErrorIncompatibleCircle, CFSTR("Incompatible circle in KVS"), NULL, error));
86 return (SOSCircleRef) entry;
87
88 fail:
89 return NULL;
90 }
91
92
93 //Circle
94
95 -(SOSCircleRef) ensureCircle:(SOSAccount*)a name:(CFStringRef)name err:(CFErrorRef *)error
96 {
97 CFErrorRef localError = NULL;
98 if (self.trustedCircle == NULL) {
99 self.trustedCircle = SOSCircleCreate(NULL, name, NULL);
100 a.key_interests_need_updating = true;
101 }
102
103 require_action_quiet(self.trustedCircle || !isSOSErrorCoded(localError, kSOSErrorIncompatibleCircle), fail,
104 if (error) { *error = localError; localError = NULL; });
105
106 fail:
107 CFReleaseNull(localError);
108 return self.trustedCircle;
109 }
110
111 -(bool) hasLeft
112 {
113 switch(self.departureCode) {
114 case kSOSDiscoveredRetirement: /* Fallthrough */
115 case kSOSLostPrivateKey: /* Fallthrough */
116 case kSOSWithdrewMembership: /* Fallthrough */
117 case kSOSMembershipRevoked: /* Fallthrough */
118 case kSOSLeftUntrustedCircle:
119 return true;
120 case kSOSNeverAppliedToCircle: /* Fallthrough */
121 case kSOSNeverLeftCircle: /* Fallthrough */
122 default:
123 return false;
124 }
125 }
126
127 /* This check is new to protect piggybacking by the current peer - in that case we have a remote peer signature that
128 can't have ghost cleanup changing the circle hash.
129 */
130
131 -(bool) ghostBustingOK:(SOSCircleRef) oldCircle updatingTo:(SOSCircleRef) newCircle {
132 bool retval = false;
133 SOSFullPeerInfoRef me_full = self.fullPeerInfo;
134 if(!me_full) return false;
135 SOSPeerInfoRef me = SOSFullPeerInfoGetPeerInfo(me_full);
136 CFStringRef myPid = SOSPeerInfoGetPeerID(me);
137 CFDictionaryRef newSigs = SOSCircleCopyAllSignatures(newCircle);
138 bool iSignedNew = CFDictionaryGetCountOfKey(newSigs, myPid);
139 long otherPeerSigCount = CFDictionaryGetCount(newSigs) - ((iSignedNew) ? 2: 1);
140
141 if (me && SOSCircleHasPeer(oldCircle, me, NULL) && SOSCircleHasPeer(newCircle, me, NULL)) { // If we're already in the old one we're not PBing
142 retval = true;
143 } else if (!iSignedNew) { // Piggybacking peers always have signed as part of genSigning - so this indicates we're safe to bust.
144 retval = true;
145 } else if(iSignedNew && otherPeerSigCount > 1) { // if others have seen this we're good to bust.
146 retval = true;
147 }
148 CFReleaseNull(newSigs);
149 return retval;
150 }
151
152 // If this circle bears a signature from us and a newer gencount and it isn't our "current" circle, we're
153 // going to trust it. That's the signature of a piggybacked circle where we were the sponsor.
154
155 -(bool) checkForSponsorshipTrust:(SOSCircleRef) prospective_circle {
156 if(CFEqualSafe(self.trustedCircle, prospective_circle)) return false;
157 SecKeyRef myPubKey = SOSFullPeerInfoCopyPubKey(self.fullPeerInfo, NULL);
158 if(!myPubKey) return false;
159 if(SOSCircleVerify(prospective_circle, myPubKey, NULL) && SOSCircleIsOlderGeneration(self.trustedCircle, prospective_circle)) {
160 [self setTrustedCircle:prospective_circle];
161 return true;
162 }
163 return false;
164 }
165
166 -(bool) handleUpdateCircle:(SOSCircleRef) prospective_circle transport:(SOSKVSCircleStorageTransport*)circleTransport update:(bool) writeUpdate err:(CFErrorRef*)error
167 {
168 bool success = true;
169 bool haveOldCircle = true;
170 const char *local_remote = writeUpdate ? "local": "remote";
171
172 SOSAccount* account = [circleTransport getAccount];
173
174 secnotice("signing", "start:[%s]", local_remote);
175 if (!account.accountKey || !account.accountKeyIsTrusted) {
176 SOSCreateError(kSOSErrorPublicKeyAbsent, CFSTR("Can't handle updates with no trusted public key here"), NULL, error);
177 return false;
178 }
179
180 if (!prospective_circle) {
181 secerror("##### Can't update to a NULL circle ######");
182 return false; // Can't update one we don't have.
183 }
184
185 // If this is a remote circle, check to see if this is our first opportunity to trust a circle where we
186 // sponsored the only signer.
187 if(!writeUpdate && [ self checkForSponsorshipTrust: prospective_circle ]){
188 SOSCCEnsurePeerRegistration();
189 account.key_interests_need_updating = true;
190 return true;
191
192 }
193
194 CFStringRef newCircleName = SOSCircleGetName(prospective_circle);
195
196 SOSCircleRef oldCircle = self.trustedCircle;
197 SOSCircleRef emptyCircle = NULL;
198
199 if(oldCircle == NULL) {
200 SOSCreateErrorWithFormat(kSOSErrorIncompatibleCircle, NULL, error, NULL, CFSTR("Current Entry is NULL; rejecting %@"), prospective_circle);
201 secerror("##### Can't replace circle - we don't care about it ######");
202 return false;
203 }
204 if (CFGetTypeID(oldCircle) != SOSCircleGetTypeID()) {
205 secdebug("signing", ">>>>>>>>>>>>>>> Non-Circle Circle found <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<");
206 // We don't know what is in our table, likely it was kCFNull indicating we didn't
207 // understand a circle that came by. We seem to like this one lets make our entry be empty circle
208 emptyCircle = SOSCircleCreate(kCFAllocatorDefault, newCircleName, NULL);
209 oldCircle = emptyCircle;
210 haveOldCircle = false;
211 // And we're paranoid, drop our old peer info if for some reason we didn't before.
212 // SOSAccountDestroyCirclePeerInfo(account, oldCircle, NULL);
213 }
214
215
216 SOSAccountScanForRetired(account, prospective_circle, error);
217 SOSCircleRef newCircle = SOSAccountCloneCircleWithRetirement(account, prospective_circle, error);
218 if(!newCircle) return false;
219 if([self ghostBustingOK: oldCircle updatingTo:newCircle]) {
220 SOSCircleRef ghostCleaned = SOSAccountCloneCircleWithoutMyGhosts(account, newCircle);
221 if(ghostCleaned) {
222 CFRetainAssign(newCircle, ghostCleaned);
223 writeUpdate = true;
224 }
225 }
226
227 SOSFullPeerInfoRef me_full = self.fullPeerInfo;
228 SOSPeerInfoRef me = SOSFullPeerInfoGetPeerInfo(me_full);
229 CFStringRef myPeerID = SOSPeerInfoGetPeerID(me);
230 myPeerID = (myPeerID) ? myPeerID: CFSTR("No Peer");
231
232 if (me && SOSCircleUpdatePeerInfo(newCircle, me)) {
233 writeUpdate = true; // If we update our peer in the new circle we should write it if we accept it.
234 }
235
236 typedef enum {
237 accept,
238 countersign,
239 leave,
240 revert,
241 ignore
242 } circle_action_t;
243
244 static const char *actionstring[] = {
245 "accept", "countersign", "leave", "revert", "ignore",
246 };
247
248 circle_action_t circle_action = ignore;
249 enum DepartureReason leave_reason = kSOSNeverLeftCircle;
250
251 SecKeyRef old_circle_key = NULL;
252 if(SOSCircleVerify(oldCircle, account.accountKey, NULL)){
253 old_circle_key = account.accountKey;
254 }
255 else if(account.previousAccountKey && SOSCircleVerify(oldCircle, account.previousAccountKey, NULL)){
256 old_circle_key = account.previousAccountKey;
257 }
258
259 bool userTrustedOldCircle = (old_circle_key != NULL) && haveOldCircle;
260
261 SOSConcordanceStatus concstat =
262 SOSCircleConcordanceTrust(oldCircle, newCircle,
263 old_circle_key, account.accountKey,
264 me, error);
265
266 CFStringRef concStr = NULL;
267 switch(concstat) {
268 case kSOSConcordanceTrusted:
269 circle_action = countersign;
270 concStr = CFSTR("Trusted");
271 break;
272 case kSOSConcordanceGenOld:
273 circle_action = userTrustedOldCircle ? revert : ignore;
274 concStr = CFSTR("Generation Old");
275 break;
276 case kSOSConcordanceBadUserSig:
277 case kSOSConcordanceBadPeerSig:
278 circle_action = userTrustedOldCircle ? revert : accept;
279 concStr = CFSTR("Bad Signature");
280 break;
281 case kSOSConcordanceNoUserSig:
282 circle_action = userTrustedOldCircle ? revert : accept;
283 concStr = CFSTR("No User Signature");
284 break;
285 case kSOSConcordanceNoPeerSig:
286 circle_action = accept; // We might like this one eventually but don't countersign.
287 concStr = CFSTR("No trusted peer signature");
288 secnotice("signing", "##### No trusted peer signature found, accepting hoping for concordance later");
289 break;
290 case kSOSConcordanceNoPeer:
291 circle_action = leave;
292 leave_reason = kSOSLeftUntrustedCircle;
293 concStr = CFSTR("No trusted peer left");
294 break;
295 case kSOSConcordanceNoUserKey:
296 secerror("##### No User Public Key Available, this shouldn't ever happen!!!");
297 abort();
298 break;
299 default:
300 secerror("##### Bad Error Return from ConcordanceTrust");
301 abort();
302 break;
303 }
304
305 secnotice("signing", "Decided on action [%s] based on concordance state [%s] and [%s] circle. My PeerID is %@", actionstring[circle_action], concordstring[concstat], userTrustedOldCircle ? "trusted" : "untrusted", myPeerID);
306
307 SOSCircleRef circleToPush = NULL;
308
309 if (circle_action == leave) {
310 circle_action = ignore; (void) circle_action; // Acknowledge this is a dead store.
311
312 if (me && SOSCircleHasPeer(oldCircle, me, NULL)) {
313 secnotice("account", "Leaving circle with peer %@", me);
314 debugDumpCircle(CFSTR("oldCircle"), oldCircle);
315 debugDumpCircle(CFSTR("newCircle"), newCircle);
316 debugDumpCircle(CFSTR("prospective_circle"), prospective_circle);
317 secnotice("account", "Key state: accountKey %@, previousAccountKey %@, old_circle_key %@",
318 account.accountKey, account.previousAccountKey, old_circle_key);
319
320 if (sosAccountLeaveCircle(account, newCircle, error)) {
321 secnotice("leaveCircle", "Leaving circle by newcircle state");
322 circleToPush = newCircle;
323 } else {
324 secnotice("signing", "Can't leave circle, but dumping identities");
325 success = false;
326 }
327 self.departureCode = leave_reason;
328 circle_action = accept;
329 me = NULL;
330 me_full = NULL;
331 } else {
332 // We are not in this circle, but we need to update account with it, since we got it from cloud
333 secnotice("signing", "We are not in this circle, but we need to update account with it");
334 debugDumpCircle(CFSTR("oldCircle"), oldCircle);
335 debugDumpCircle(CFSTR("newCircle"), newCircle);
336 debugDumpCircle(CFSTR("prospective_circle"), prospective_circle);
337 circle_action = accept;
338 }
339 }
340
341 if (circle_action == countersign) {
342 if (me && SOSCircleHasPeer(newCircle, me, NULL)) {
343 if (SOSCircleVerifyPeerSigned(newCircle, me, NULL)) {
344 secnotice("signing", "Already concur with the new circle");
345 } else {
346 CFErrorRef signing_error = NULL;
347
348 if (me_full && SOSCircleConcordanceSign(newCircle, me_full, &signing_error)) {
349 circleToPush = newCircle;
350 secnotice("signing", "Concurred with new circle");
351 } else {
352 secerror("Failed to concurrence sign, error: %@", signing_error);
353 success = false;
354 }
355 CFReleaseSafe(signing_error);
356 }
357 } else {
358 secnotice("signing", "Not countersigning, not in new circle");
359 debugDumpCircle(CFSTR("circle to countersign"), newCircle);
360 }
361 circle_action = accept;
362 }
363
364 if (circle_action == accept) {
365 if (me && SOSCircleHasActivePeer(oldCircle, me, NULL) && !SOSCircleHasPeer(newCircle, me, NULL)) {
366 // Don't destroy evidence of other code determining reason for leaving.
367 if(![self hasLeft]) self.departureCode = kSOSMembershipRevoked;
368 secnotice("account", "Member of old circle but not of new circle");
369 debugDumpCircle(CFSTR("oldCircle"), oldCircle);
370 debugDumpCircle(CFSTR("newCircle"), newCircle);
371 }
372
373 if (me
374 && SOSCircleHasActivePeer(oldCircle, me, NULL)
375 && !(SOSCircleCountPeers(oldCircle) == 1 && SOSCircleHasPeer(oldCircle, me, NULL)) // If it was our offering, don't change ID to avoid ghosts
376 && !SOSCircleHasPeer(newCircle, me, NULL) && !SOSCircleHasApplicant(newCircle, me, NULL)) {
377 secnotice("circle", "Purging my peer (ID: %@) for circle '%@'!!!", SOSPeerInfoGetPeerID(me), SOSCircleGetName(oldCircle));
378 if (self.fullPeerInfo)
379 SOSFullPeerInfoPurgePersistentKey(self.fullPeerInfo, NULL);
380 me = NULL;
381 me_full = NULL;
382 }
383
384 if (me && SOSCircleHasRejectedApplicant(newCircle, me, NULL)) {
385 SOSPeerInfoRef reject = SOSCircleCopyRejectedApplicant(newCircle, me, NULL);
386 if(CFEqualSafe(reject, me) && SOSPeerInfoApplicationVerify(me, account.accountKey, NULL)) {
387 secnotice("circle", "Rejected, Purging my applicant peer (ID: %@) for circle '%@'", SOSPeerInfoGetPeerID(me), SOSCircleGetName(oldCircle));
388 debugDumpCircle(CFSTR("oldCircle"), oldCircle);
389 debugDumpCircle(CFSTR("newCircle"), newCircle);
390 if (self.fullPeerInfo)
391 SOSFullPeerInfoPurgePersistentKey(self.fullPeerInfo, NULL);
392 me = NULL;
393 me_full = NULL;
394 } else {
395 secnotice("circle", "Rejected, Reapplying (ID: %@) for circle '%@'", SOSPeerInfoGetPeerID(me), SOSCircleGetName(oldCircle));
396 debugDumpCircle(CFSTR("oldCircle"), oldCircle);
397 debugDumpCircle(CFSTR("newCircle"), newCircle);
398 SOSCircleRequestReadmission(newCircle, account.accountKey, me, NULL);
399 writeUpdate = true;
400 }
401 }
402
403 CFRetainSafe(oldCircle);
404 account.previousAccountKey = account.accountKey;
405
406 secnotice("signing", "%@, Accepting new circle", concStr);
407 if (circle_action == accept) {
408 [self setTrustedCircle:newCircle];
409 }
410
411 if (me && account.accountKeyIsTrusted
412 && SOSCircleHasApplicant(oldCircle, me, NULL)
413 && SOSCircleCountPeers(newCircle) > 0
414 && !SOSCircleHasPeer(newCircle, me, NULL) && !SOSCircleHasApplicant(newCircle, me, NULL)) {
415 // We weren't rejected (above would have set me to NULL.
416 // We were applying and we weren't accepted.
417 // Our application is declared lost, let us reapply.
418
419 secnotice("signing", "requesting readmission to new circle");
420 if (SOSCircleRequestReadmission(newCircle, account.accountKey, me, NULL))
421 writeUpdate = true;
422 }
423
424 if (me && SOSCircleHasActivePeer(oldCircle, me, NULL)) {
425 [account.trust cleanupRetirementTickets:account circle:oldCircle time:RETIREMENT_FINALIZATION_SECONDS err:NULL];
426 }
427
428 SOSAccountNotifyOfChange(account, oldCircle, newCircle);
429
430 CFReleaseNull(oldCircle);
431
432 if (writeUpdate)
433 circleToPush = newCircle;
434 account.key_interests_need_updating = true;
435 }
436
437 /*
438 * In the revert section we'll guard the KVS idea of circles by rejecting "bad" new circles
439 * and pushing our current view of the circle (oldCircle). We'll only do this if we actually
440 * are a member of oldCircle - never for an empty circle.
441 */
442
443 if (circle_action == revert) {
444 if(haveOldCircle && me && SOSCircleHasActivePeer(oldCircle, me, NULL)) {
445 secnotice("signing", "%@, Rejecting new circle, re-publishing old circle", concStr);
446 debugDumpCircle(CFSTR("oldCircle"), oldCircle);
447 debugDumpCircle(CFSTR("newCircle"), newCircle);
448 circleToPush = oldCircle;
449 [self setTrustedCircle:oldCircle];
450 } else {
451 secnotice("canary", "%@, Rejecting: new circle Have no old circle - would reset", concStr);
452 }
453 }
454
455
456 if (circleToPush != NULL) {
457 secnotice("signing", "Pushing:[%s]", local_remote);
458 CFDataRef circle_data = SOSCircleCopyEncodedData(circleToPush, kCFAllocatorDefault, error);
459
460 if (circle_data) {
461 // Ensure we flush changes
462 account.circle_rings_retirements_need_attention = true;
463
464 //posting new circle to peers
465 success &= [circleTransport postCircle:SOSCircleGetName(circleToPush) circleData:circle_data err:error];
466 //cleanup old KVS keys
467 SOSAccountCleanupAllKVSKeys(account, error);
468 } else {
469 success = false;
470 }
471 CFReleaseNull(circle_data);
472 }
473 CFReleaseSafe(newCircle);
474 CFReleaseNull(emptyCircle);
475
476 return success;
477 }
478
479 -(bool) updateCircleFromRemote:(SOSKVSCircleStorageTransport*)circleTransport newCircle:(SOSCircleRef)newCircle err:(CFErrorRef*)error
480 {
481 return [self handleUpdateCircle:newCircle transport:circleTransport update:false err:error];
482 }
483
484 -(bool) updateCircle:(SOSKVSCircleStorageTransport*)circleTransport newCircle:(SOSCircleRef) newCircle err:(CFErrorRef*)error
485 {
486 return [self handleUpdateCircle:newCircle transport:circleTransport update:true err:error];
487 }
488
489 -(bool) modifyCircle:(SOSKVSCircleStorageTransport*)circleTransport err:(CFErrorRef*)error action:(SOSModifyCircleBlock)block
490 {
491 bool success = false;
492 SOSCircleRef circleCopy = NULL;
493 require_action_quiet(self.trustedCircle, fail, SOSErrorCreate(kSOSErrorNoCircle, error, NULL, CFSTR("No circle to get peer key from")));
494
495 circleCopy = SOSCircleCopyCircle(kCFAllocatorDefault, self.trustedCircle, error);
496 require_quiet(circleCopy, fail);
497
498 success = true;
499 require_quiet(block(circleCopy), fail);
500
501 success = [self updateCircle:circleTransport newCircle:circleCopy err:error];
502
503 fail:
504 CFReleaseSafe(circleCopy);
505 return success;
506
507 }
508
509 -(void) generationSignatureUpdateWith:(SOSAccount*)account key:(SecKeyRef) privKey
510 {
511 if (self.trustedCircle && self.fullPeerInfo) {
512 [self modifyCircle:account.circle_transport err:NULL action:^(SOSCircleRef circle) {
513 SOSPeerInfoRef myPI = account.peerInfo;
514 bool iAmPeer = SOSCircleHasPeer(circle, myPI, NULL);
515 bool change = SOSCircleUpdatePeerInfo(circle, myPI);
516 if(iAmPeer && !SOSCircleVerify(circle, account.accountKey, NULL)) {
517 change |= [self upgradeiCloudIdentity:circle privKey:privKey];
518 [self removeInvalidApplications:circle userPublic:account.accountKey];
519 change |= SOSCircleGenerationSign(circle, privKey, self.fullPeerInfo, NULL);
520 [self setDepartureCode:kSOSNeverLeftCircle];
521 } else if(iAmPeer) {
522 SOSFullPeerInfoRef icfpi = SOSCircleCopyiCloudFullPeerInfoRef(circle, NULL);
523 if(!icfpi) {
524 SOSAccountRemoveIncompleteiCloudIdentities(account, circle, privKey, NULL);
525 change |= [self addiCloudIdentity:circle key:privKey err:NULL];
526 } else {
527 CFReleaseNull(icfpi);
528 }
529 }
530 secnotice("updatingGenSignature", "we changed the circle? %@", change ? CFSTR("YES") : CFSTR("NO"));
531 return change;
532 }];
533 }
534 }
535
536 -(void) forEachCirclePeerExceptMe:(SOSIteratePeerBlock)block
537 {
538 if (self.trustedCircle && self.peerInfo) {
539 NSString* myPi_id = self.peerID;
540 SOSCircleForEachPeer(self.trustedCircle, ^(SOSPeerInfoRef peer) {
541 CFStringRef peerID = SOSPeerInfoGetPeerID(peer);
542 if (peerID && ![myPi_id isEqualToString:(__bridge NSString*) peerID]) {
543 block(peer);
544 }
545 });
546 }
547 }
548 -(bool) leaveCircle:(SOSAccount*)account err:(CFErrorRef*) error
549 {
550 bool result = true;
551 secnotice("leaveCircle", "Leaving circle by client request");
552 result &= [self modifyCircle:account.circle_transport err:error action:^(SOSCircleRef circle) {
553 return sosAccountLeaveCircle(account, circle, error);
554 }];
555
556 self.departureCode = kSOSWithdrewMembership;
557
558 return result;
559 }
560
561 -(bool) resetToOffering:(SOSAccountTransaction*) aTxn key:(SecKeyRef)userKey err:(CFErrorRef*) error
562 {
563 SOSFullPeerInfoPurgePersistentKey(self.fullPeerInfo, NULL);
564 self.fullPeerInfo = nil;
565
566 secnotice("resetToOffering", "Resetting circle to offering by request from client");
567
568 return userKey && [self resetCircleToOffering:aTxn userKey:userKey err:error];
569 }
570
571
572 -(bool) resetCircleToOffering:(SOSAccountTransaction*) aTxn userKey:(SecKeyRef)user_key err:(CFErrorRef *)error
573 {
574 bool result = false;
575
576 SOSAccount* account = aTxn.account;
577 if(![self hasCircle:error])
578 return result;
579
580 if(![self ensureFullPeerAvailable:(__bridge CFDictionaryRef)(account.gestalt) deviceID:(__bridge CFStringRef)(account.deviceID) backupKey:(__bridge CFDataRef)(account.backup_key) err:error])
581 return result;
582
583 (void)[self resetAllRings:account err:error];
584
585 [self modifyCircle:account.circle_transport err:error action:^bool(SOSCircleRef circle) {
586 bool result = false;
587 SOSFullPeerInfoRef cloud_identity = NULL;
588 CFErrorRef localError = NULL;
589
590 require_quiet(SOSCircleResetToOffering(circle, user_key, self.fullPeerInfo, &localError), err_out);
591
592 self.departureCode = kSOSNeverLeftCircle;
593
594 require_quiet([self addEscrowToPeerInfo:self.fullPeerInfo err:error], err_out);
595
596 require_quiet([self addiCloudIdentity:circle key:user_key err:error], err_out);
597 result = true;
598 SOSAccountPublishCloudParameters(account, NULL);
599
600 err_out:
601 if (result == false)
602 secerror("error resetting circle (%@) to offering: %@", circle, localError);
603 if (localError && error && *error == NULL) {
604 *error = localError;
605 localError = NULL;
606 }
607 CFReleaseNull(localError);
608 CFReleaseNull(cloud_identity);
609 return result;
610 }];
611
612 [self setValueInExpansion:kSOSUnsyncedViewsKey value:kCFBooleanTrue err:NULL];
613 SOSAccountUpdateOutOfSyncViews(aTxn, SOSViewsGetAllCurrent());
614
615 result = true;
616
617 return result;
618 }
619
620 void SOSAccountForEachCirclePeerExceptMe(SOSAccount* account, void (^action)(SOSPeerInfoRef peer)) {
621 SOSPeerInfoRef myPi = account.peerInfo;
622 SOSCircleRef circle = NULL;
623
624 SOSAccountTrustClassic *trust = account.trust;
625 circle = trust.trustedCircle;
626 if (circle && myPi) {
627 CFStringRef myPi_id = SOSPeerInfoGetPeerID(myPi);
628 SOSCircleForEachPeer(circle, ^(SOSPeerInfoRef peer) {
629 CFStringRef peerID = SOSPeerInfoGetPeerID(peer);
630 if (peerID && !CFEqual(peerID, myPi_id)) {
631 action(peer);
632 }
633 });
634 }
635 }
636
637 -(bool) joinCircle:(SOSAccountTransaction*) aTxn userKey:(SecKeyRef) user_key useCloudPeer:(bool) use_cloud_peer err:(CFErrorRef*) error
638 {
639 __block bool result = false;
640 __block SOSFullPeerInfoRef cloud_full_peer = NULL;
641 __block SOSAccount* account = aTxn.account;
642 require_action_quiet(self.trustedCircle, fail, SOSCreateErrorWithFormat(kSOSErrorPeerNotFound, NULL, error, NULL, CFSTR("Don't have circle when joining???")));
643 require_quiet([self ensureFullPeerAvailable:(__bridge CFDictionaryRef)account.gestalt deviceID:(__bridge CFStringRef)account.deviceID backupKey:(__bridge CFDataRef)account.backup_key err:error], fail);
644
645 if (SOSCircleCountPeers(self.trustedCircle) == 0 || SOSAccountGhostResultsInReset(account)) {
646 secnotice("resetToOffering", "Resetting circle to offering since there are no peers");
647 // this also clears initial sync data
648 result = [self resetCircleToOffering:aTxn userKey:user_key err:error];
649 } else {
650 [self setValueInExpansion:kSOSUnsyncedViewsKey value:kCFBooleanTrue err:NULL];
651
652 if (use_cloud_peer) {
653 cloud_full_peer = SOSCircleCopyiCloudFullPeerInfoRef(self.trustedCircle, NULL);
654 }
655
656 [self modifyCircle: account.circle_transport err:error action:^(SOSCircleRef circle) {
657 result = SOSAccountAddEscrowToPeerInfo(account, self.fullPeerInfo, error);
658 result &= SOSCircleRequestAdmission(circle, user_key, self.fullPeerInfo, error);
659 self.departureCode = kSOSNeverLeftCircle;
660 if(result && cloud_full_peer) {
661 CFErrorRef localError = NULL;
662 CFStringRef cloudid = SOSPeerInfoGetPeerID(SOSFullPeerInfoGetPeerInfo(cloud_full_peer));
663 require_quiet(cloudid, finish);
664 require_quiet(SOSCircleHasActivePeerWithID(circle, cloudid, &localError), finish);
665 require_quiet(SOSCircleAcceptRequest(circle, user_key, cloud_full_peer, self.peerInfo, &localError), finish);
666
667 finish:
668 if (localError){
669 secerror("Failed to join with cloud identity: %@", localError);
670 CFReleaseNull(localError);
671 }
672 }
673 return result;
674 }];
675
676 if (use_cloud_peer) {
677 SOSAccountUpdateOutOfSyncViews(aTxn, SOSViewsGetAllCurrent());
678 }
679 }
680
681 fail:
682 CFReleaseNull(cloud_full_peer);
683 return result;
684 }
685
686 @end