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