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