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