]> git.saurik.com Git - apple/security.git/blob - keychain/SecureObjectSync/SOSAccountTrustClassic+Circle.m
Security-59306.120.7.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/SOSIntervalEvent.h"
18 #import "keychain/SecureObjectSync/SOSViews.h"
19 #import "Analytics/Clients/SOSAnalytics.h"
20
21
22 @implementation SOSAccountTrustClassic (Circle)
23
24 #define ICLOUDIDDATE @"iCloudIDDate"
25
26 -(bool) isInCircleOnly:(CFErrorRef *)error
27 {
28 SOSCCStatus result = [self getCircleStatusOnly:error];
29
30 if (result != kSOSCCInCircle) {
31 SOSErrorCreate(kSOSErrorNoCircle, error, NULL, CFSTR("Not in circle"));
32 return false;
33 }
34
35 return true;
36 }
37
38 -(bool) hasCircle:(CFErrorRef*) error
39 {
40 if (!self.trustedCircle)
41 SOSCreateErrorWithFormat(kSOSErrorNoCircle, NULL, error, NULL, CFSTR("No trusted circle"));
42
43 return self.trustedCircle != NULL;
44 }
45
46 -(SOSCCStatus) thisDeviceStatusInCircle:(SOSCircleRef) circle peer:(SOSPeerInfoRef) this_peer
47 {
48 if (!circle)
49 return kSOSCCNotInCircle;
50
51 if (circle && SOSCircleCountPeers(circle) == 0)
52 return kSOSCCCircleAbsent;
53
54 if (this_peer) {
55
56 if(SOSPeerInfoIsRetirementTicket(this_peer))
57 return kSOSCCNotInCircle;
58
59 if (SOSCircleHasPeer(circle, this_peer, NULL))
60 return kSOSCCInCircle;
61
62 if (SOSCircleHasApplicant(circle, this_peer, NULL))
63 return kSOSCCRequestPending;
64 }
65
66 return kSOSCCNotInCircle;
67 }
68 -(SOSCCStatus) getCircleStatusOnly:(CFErrorRef*) error
69 {
70 return [self thisDeviceStatusInCircle:self.trustedCircle peer:self.peerInfo];
71 }
72
73
74 -(SOSCircleRef) getCircle:(CFErrorRef *)error
75 {
76 CFTypeRef entry = self.trustedCircle;
77 require_action_quiet(!isNull(entry), fail,
78 SOSCreateError(kSOSErrorIncompatibleCircle, CFSTR("Incompatible circle in KVS"), NULL, error));
79 return (SOSCircleRef) entry;
80
81 fail:
82 return NULL;
83 }
84
85
86 //Circle
87
88 -(SOSCircleRef) ensureCircle:(SOSAccount*)a name:(CFStringRef)name err:(CFErrorRef *)error
89 {
90 CFErrorRef localError = NULL;
91 if (self.trustedCircle == NULL) {
92 SOSCircleRef newCircle = SOSCircleCreate(NULL, name, NULL);
93 self.trustedCircle = newCircle; // Note that this setter adds a retain
94 CFReleaseNull(newCircle);
95 secnotice("circleop", "Setting key_interests_need_updating to true in ensureCircle");
96 a.key_interests_need_updating = true;
97 }
98
99 require_action_quiet(self.trustedCircle || !isSOSErrorCoded(localError, kSOSErrorIncompatibleCircle), fail,
100 if (error) { *error = localError; localError = NULL; });
101
102 fail:
103 CFReleaseNull(localError);
104 return self.trustedCircle;
105 }
106
107 -(bool) hasLeft
108 {
109 switch(self.departureCode) {
110 case kSOSDiscoveredRetirement: /* Fallthrough */
111 case kSOSLostPrivateKey: /* Fallthrough */
112 case kSOSWithdrewMembership: /* Fallthrough */
113 case kSOSMembershipRevoked: /* Fallthrough */
114 case kSOSLeftUntrustedCircle:
115 return true;
116 case kSOSNeverAppliedToCircle: /* Fallthrough */
117 case kSOSNeverLeftCircle: /* Fallthrough */
118 default:
119 return false;
120 }
121 }
122
123 /* This check is new to protect piggybacking by the current peer - in that case we have a remote peer signature that
124 can't have ghost cleanup changing the circle hash.
125 */
126
127 -(bool) ghostBustingOK:(SOSCircleRef) oldCircle updatingTo:(SOSCircleRef) newCircle {
128 bool retval = false;
129 // Preliminaries - we must have a peer and it must be in the newCircle in order to attempt busting
130 SOSFullPeerInfoRef me_full = self.fullPeerInfo;
131 if(!me_full) return false;
132 SOSPeerInfoRef me = SOSFullPeerInfoGetPeerInfo(me_full);
133 if(!me || (!SOSCircleHasPeer(newCircle, me, NULL) && !SOSCircleHasApplicant(newCircle, me, NULL))) return false;
134
135 CFStringRef myPid = SOSPeerInfoGetPeerID(me);
136 CFDictionaryRef newSigs = SOSCircleCopyAllSignatures(newCircle);
137 bool iSignedNew = CFDictionaryGetCountOfKey(newSigs, myPid);
138 long otherPeerSigCount = CFDictionaryGetCount(newSigs) - ((iSignedNew) ? 2: 1);
139
140 if (SOSCircleHasPeer(oldCircle, me, NULL)) { // If we're already in the old one we're not PBing
141 retval = true;
142 } else if (!iSignedNew) { // Piggybacking peers always have signed as part of genSigning - so this indicates we're safe to bust.
143 retval = true;
144 } else if(iSignedNew && otherPeerSigCount > 1) { // if others have seen this we're good to bust.
145 retval = true;
146 }
147 CFReleaseNull(newSigs);
148 return retval;
149 }
150
151 // If this circle bears a signature from us and a newer gencount and it isn't our "current" circle, we're
152 // going to trust it. That's the signature of a piggybacked circle where we were the sponsor.
153
154 -(bool) checkForSponsorshipTrust:(SOSCircleRef) prospective_circle {
155 if(CFEqualSafe(self.trustedCircle, prospective_circle)) return false;
156 SecKeyRef myPubKey = SOSFullPeerInfoCopyPubKey(self.fullPeerInfo, NULL);
157 if(!myPubKey) return false;
158 if(SOSCircleVerify(prospective_circle, myPubKey, NULL) && SOSCircleIsOlderGeneration(self.trustedCircle, prospective_circle)) {
159 [self setTrustedCircle:prospective_circle];
160 CFReleaseNull(myPubKey);
161 return true;
162 }
163 CFReleaseNull(myPubKey);
164 return false;
165 }
166
167 static bool publicKeysEqual(SecKeyRef pubKey1, SecKeyRef pubKey2)
168 {
169 // If either pub key is NULL, then the keys are equal if both are NULL.
170 if(pubKey1 == NULL || pubKey2 == NULL) {
171 return pubKey1 == NULL && pubKey2 == NULL;
172 }
173
174 NSData *key1SPKI = CFBridgingRelease(SecKeyCopySubjectPublicKeyInfo(pubKey1));
175 NSData *key2SPKI = CFBridgingRelease(SecKeyCopySubjectPublicKeyInfo(pubKey2));
176
177 return !![key1SPKI isEqual:key2SPKI];
178 }
179
180 static bool SOSCirclePeerOctagonKeysChanged(SOSPeerInfoRef oldPeer, SOSPeerInfoRef newPeer) {
181 if(!oldPeer) {
182 // We've run across some situations where a new peer which should have keys isn't returning yes here.
183 // Therefore, always return yes on peer addition.
184 return !!newPeer;
185 }
186
187 CFErrorRef oldSigningKeyError = NULL;
188 SecKeyRef oldSigningKey = oldPeer ? SOSPeerInfoCopyOctagonSigningPublicKey(oldPeer, &oldSigningKeyError) : NULL;
189
190 CFErrorRef oldEncryptionKeyError = NULL;
191 SecKeyRef oldEncryptionKey = oldPeer ? SOSPeerInfoCopyOctagonEncryptionPublicKey(oldPeer, &oldEncryptionKeyError) : NULL;
192
193 CFErrorRef newSigningKeyError = NULL;
194 SecKeyRef newSigningKey = newPeer ? SOSPeerInfoCopyOctagonSigningPublicKey(newPeer, &newSigningKeyError) : NULL;
195
196 CFErrorRef newEncryptionKeyError = NULL;
197 SecKeyRef newEncryptionKey = newPeer ? SOSPeerInfoCopyOctagonEncryptionPublicKey(newPeer, &newEncryptionKeyError) : NULL;
198
199 if(oldPeer && oldSigningKeyError) {
200 secerror("circleOps: Cannot fetch signing key for old %@: %@", oldPeer, oldSigningKeyError);
201 }
202 if(oldPeer && oldEncryptionKeyError) {
203 secerror("circleOps: Cannot fetch encryption key for old %@: %@", oldPeer, oldEncryptionKeyError);
204 }
205 if(newPeer && newSigningKeyError) {
206 secerror("circleOps: Cannot fetch signing key for new %@: %@", newPeer, newSigningKeyError);
207 }
208 if(newPeer && newEncryptionKeyError) {
209 secerror("circleOps: Cannot fetch encryption key for new %@: %@", newPeer, newEncryptionKeyError);
210 }
211
212 bool signingKeyChanged = !publicKeysEqual(oldSigningKey, newSigningKey);
213 bool encryptionKeyChanged = !publicKeysEqual(oldEncryptionKey, newEncryptionKey);
214
215 bool keysChanged = signingKeyChanged || encryptionKeyChanged;
216
217 CFReleaseNull(oldSigningKeyError);
218 CFReleaseNull(oldSigningKey);
219 CFReleaseNull(oldEncryptionKeyError);
220 CFReleaseNull(oldEncryptionKey);
221
222 CFReleaseNull(newSigningKeyError);
223 CFReleaseNull(newSigningKey);
224 CFReleaseNull(newEncryptionKeyError);
225 CFReleaseNull(newEncryptionKey);
226 return keysChanged;
227 }
228
229 static bool SOSCircleHasUpdatedPeerInfoWithOctagonKey(SOSCircleRef oldCircle, SOSCircleRef newCircle)
230 {
231 __block bool hasUpdated = false;
232 SOSCircleForEachPeer(oldCircle, ^(SOSPeerInfoRef oldPeer) {
233 SOSPeerInfoRef equivalentNewPeer = SOSCircleCopyPeerWithID(newCircle, SOSPeerInfoGetPeerID(oldPeer), NULL);
234 hasUpdated |= SOSCirclePeerOctagonKeysChanged(oldPeer, equivalentNewPeer);
235 CFReleaseNull(equivalentNewPeer);
236 });
237
238 SOSCircleForEachPeer(newCircle, ^(SOSPeerInfoRef newPeer) {
239 SOSPeerInfoRef equivalentOldPeer = SOSCircleCopyPeerWithID(oldCircle, SOSPeerInfoGetPeerID(newPeer), NULL);
240 hasUpdated |= SOSCirclePeerOctagonKeysChanged(equivalentOldPeer, newPeer);
241 CFReleaseNull(equivalentOldPeer);
242 });
243
244 return hasUpdated;
245 }
246
247 // Check on the iCloud identity availability every 24-36 hours random interval
248 - (SOSIntervalEvent *) iCloudCheckEventHandle: (SOSAccount *) account {
249 return [[SOSIntervalEvent alloc] initWithDefaults:account.settings dateDescription:@"iCloudIDCheck" earliest:60*60*24 latest:60*60*36];
250 }
251
252 // Cleanup unusable iCloud identities every 5-7 days random interval
253 - (SOSIntervalEvent *) iCloudCleanerHandle: (SOSAccount *) account {
254 return [[SOSIntervalEvent alloc] initWithDefaults:account.settings dateDescription:@"iCloudCleanerCheck" earliest:60*60*24*5 latest:60*60*24*7];
255 }
256
257 -(bool) handleUpdateCircle:(SOSCircleRef) prospective_circle transport:(SOSKVSCircleStorageTransport*)circleTransport update:(bool) writeUpdate err:(CFErrorRef*)error
258 {
259 bool success = true;
260 bool haveOldCircle = true;
261 const char *local_remote = writeUpdate ? "local": "remote";
262
263 SOSAccount* account = [circleTransport getAccount];
264
265 secnotice("signing", "start:[%s]", local_remote);
266 if (!account.accountKey || !account.accountKeyIsTrusted) {
267 SOSCreateError(kSOSErrorPublicKeyAbsent, CFSTR("Can't handle updates with no trusted public key here"), NULL, error);
268 return false;
269 }
270
271 if (!prospective_circle) {
272 secerror("##### Can't update to a NULL circle ######");
273 return false; // Can't update one we don't have.
274 }
275
276 // If this is a remote circle, check to see if this is our first opportunity to trust a circle where we
277 // sponsored the only signer.
278 if(!writeUpdate && [ self checkForSponsorshipTrust: prospective_circle ]){
279 SOSCCEnsurePeerRegistration();
280 secnotice("circleop", "Setting key_interests_need_updating to true in handleUpdateCircle");
281 account.key_interests_need_updating = true;
282 return true;
283
284 }
285
286 CFStringRef newCircleName = SOSCircleGetName(prospective_circle);
287
288 SOSCircleRef oldCircle = self.trustedCircle;
289 SOSCircleRef emptyCircle = NULL;
290
291 if(oldCircle == NULL) {
292 SOSCreateErrorWithFormat(kSOSErrorIncompatibleCircle, NULL, error, NULL, CFSTR("Current Entry is NULL; rejecting %@"), prospective_circle);
293 secerror("##### Can't replace circle - we don't care about it ######");
294 return false;
295 }
296 if (CFGetTypeID(oldCircle) != SOSCircleGetTypeID()) {
297 secdebug("signing", ">>>>>>>>>>>>>>> Non-Circle Circle found <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<");
298 // We don't know what is in our table, likely it was kCFNull indicating we didn't
299 // understand a circle that came by. We seem to like this one lets make our entry be empty circle
300 emptyCircle = SOSCircleCreate(kCFAllocatorDefault, newCircleName, NULL);
301 oldCircle = emptyCircle;
302 haveOldCircle = false;
303 // And we're paranoid, drop our old peer info if for some reason we didn't before.
304 // SOSAccountDestroyCirclePeerInfo(account, oldCircle, NULL);
305 }
306
307
308 SOSAccountScanForRetired(account, prospective_circle, error);
309 SOSCircleRef newCircle = SOSAccountCloneCircleWithRetirement(account, prospective_circle, error);
310 if(!newCircle) return false;
311
312 SOSFullPeerInfoRef me_full = self.fullPeerInfo;
313 SOSPeerInfoRef me = SOSFullPeerInfoGetPeerInfo(me_full);
314 CFStringRef myPeerID = SOSPeerInfoGetPeerID(me);
315 myPeerID = (myPeerID) ? myPeerID: CFSTR("No Peer");
316
317 if (me && SOSCircleUpdatePeerInfo(newCircle, me)) {
318 writeUpdate = true; // If we update our peer in the new circle we should write it if we accept it.
319 }
320
321 typedef enum {
322 accept,
323 countersign,
324 leave,
325 revert,
326 ignore
327 } circle_action_t;
328
329 static const char *actionstring[] = {
330 "accept", "countersign", "leave", "revert", "ignore",
331 };
332
333 circle_action_t circle_action = ignore;
334 enum DepartureReason leave_reason = kSOSNeverLeftCircle;
335
336 SecKeyRef old_circle_key = NULL;
337 if(SOSCircleVerify(oldCircle, account.accountKey, NULL)){
338 old_circle_key = account.accountKey;
339 }
340 else if(account.previousAccountKey && SOSCircleVerify(oldCircle, account.previousAccountKey, NULL)){
341 old_circle_key = account.previousAccountKey;
342 }
343
344 bool userTrustedOldCircle = (old_circle_key != NULL) && haveOldCircle;
345
346 SOSConcordanceStatus concstat =
347 SOSCircleConcordanceTrust(oldCircle, newCircle,
348 old_circle_key, account.accountKey,
349 me, error);
350
351 CFStringRef concStr = NULL;
352 switch(concstat) {
353 case kSOSConcordanceTrusted:
354 circle_action = countersign;
355 concStr = CFSTR("Trusted");
356 break;
357 case kSOSConcordanceGenOld:
358 circle_action = userTrustedOldCircle ? revert : ignore;
359 concStr = CFSTR("Generation Old");
360 break;
361 case kSOSConcordanceBadUserSig:
362 case kSOSConcordanceBadPeerSig:
363 circle_action = userTrustedOldCircle ? revert : accept;
364 concStr = CFSTR("Bad Signature");
365 break;
366 case kSOSConcordanceNoUserSig:
367 circle_action = userTrustedOldCircle ? revert : accept;
368 concStr = CFSTR("No User Signature");
369 break;
370 case kSOSConcordanceNoPeerSig:
371 circle_action = accept; // We might like this one eventually but don't countersign.
372 concStr = CFSTR("No trusted peer signature");
373 secnotice("signing", "##### No trusted peer signature found, accepting hoping for concordance later");
374 break;
375 case kSOSConcordanceNoPeer:
376 circle_action = leave;
377 leave_reason = kSOSLeftUntrustedCircle;
378 concStr = CFSTR("No trusted peer left");
379 break;
380 case kSOSConcordanceNoUserKey:
381 secerror("##### No User Public Key Available, this shouldn't ever happen!!!");
382 abort();
383 break;
384 default:
385 secerror("##### Bad Error Return from ConcordanceTrust");
386 abort();
387 break;
388 }
389
390 secnotice("signing", "Decided on action [%s] based on concordance state [%@] and [%s] circle. My PeerID is %@", actionstring[circle_action], concStr, userTrustedOldCircle ? "trusted" : "untrusted", myPeerID);
391
392 SOSCircleRef circleToPush = NULL;
393
394 if (circle_action == leave) {
395 circle_action = ignore; (void) circle_action; // Acknowledge this is a dead store.
396
397 if (me && SOSCircleHasPeer(oldCircle, me, NULL)) {
398 secnotice("account", "Leaving circle with peer %@", me);
399 debugDumpCircle(CFSTR("oldCircle"), oldCircle);
400 debugDumpCircle(CFSTR("newCircle"), newCircle);
401 debugDumpCircle(CFSTR("prospective_circle"), prospective_circle);
402 secnotice("account", "Key state: accountKey %@, previousAccountKey %@, old_circle_key %@",
403 account.accountKey, account.previousAccountKey, old_circle_key);
404
405 if (sosAccountLeaveCircle(account, newCircle, nil, error)) {
406 secnotice("circleOps", "Leaving circle by newcircle state");
407 circleToPush = newCircle;
408 } else {
409 secnotice("signing", "Can't leave circle, but dumping identities");
410 success = false;
411 }
412 self.departureCode = leave_reason;
413 circle_action = accept;
414 me = NULL;
415 me_full = NULL;
416 } else {
417 // We are not in this circle, but we need to update account with it, since we got it from cloud
418 secnotice("signing", "We are not in this circle, but we need to update account with it");
419 debugDumpCircle(CFSTR("oldCircle"), oldCircle);
420 debugDumpCircle(CFSTR("newCircle"), newCircle);
421 debugDumpCircle(CFSTR("prospective_circle"), prospective_circle);
422 circle_action = accept;
423 }
424 }
425
426 if (circle_action == countersign) {
427 if (me && SOSCircleHasPeer(newCircle, me, NULL)) {
428 if (SOSCircleVerifyPeerSigned(newCircle, me, NULL)) {
429 secnotice("signing", "Already concur with the new circle");
430 } else {
431 CFErrorRef signing_error = NULL;
432
433 if (me_full && SOSCircleConcordanceSign(newCircle, me_full, &signing_error)) {
434 circleToPush = newCircle;
435 secnotice("signing", "Concurred with new circle");
436 } else {
437 secerror("Failed to concurrence sign, error: %@", signing_error);
438 success = false;
439 }
440 CFReleaseSafe(signing_error);
441 }
442 } else {
443 secnotice("signing", "Not countersigning, not in new circle");
444 [account.trust resetRingDictionary];
445 }
446 circle_action = accept;
447 }
448
449 if (circle_action == accept) {
450 if(SOSCircleHasUpdatedPeerInfoWithOctagonKey(oldCircle, newCircle)){
451 secnotice("circleOps", "Sending kSOSCCCircleOctagonKeysChangedNotification");
452 notify_post(kSOSCCCircleOctagonKeysChangedNotification);
453 }
454 if (me && SOSCircleHasActivePeer(oldCircle, me, NULL) && !SOSCircleHasPeer(newCircle, me, NULL)) {
455 // Don't destroy evidence of other code determining reason for leaving.
456 if(![self hasLeft]) self.departureCode = kSOSMembershipRevoked;
457 secnotice("circleOps", "Member of old circle but not of new circle (%d)", self.departureCode);
458 debugDumpCircle(CFSTR("oldCircle"), oldCircle);
459 debugDumpCircle(CFSTR("newCircle"), newCircle);
460 }
461
462 if (me
463 && SOSCircleHasActivePeer(oldCircle, me, NULL)
464 && !(SOSCircleCountPeers(oldCircle) == 1 && SOSCircleHasPeer(oldCircle, me, NULL)) // If it was our offering, don't change ID to avoid ghosts
465 && !SOSCircleHasPeer(newCircle, me, NULL) && !SOSCircleHasApplicant(newCircle, me, NULL)) {
466 secnotice("circle", "Purging my peer (ID: %@) for circle '%@'!!!", SOSPeerInfoGetPeerID(me), SOSCircleGetName(oldCircle));
467 if (self.fullPeerInfo)
468 SOSFullPeerInfoPurgePersistentKey(self.fullPeerInfo, NULL);
469 me = NULL;
470 me_full = NULL;
471 }
472
473 if (me && SOSCircleHasRejectedApplicant(newCircle, me, NULL)) {
474 SOSPeerInfoRef reject = SOSCircleCopyRejectedApplicant(newCircle, me, NULL);
475 if(CFEqualSafe(reject, me) && SOSPeerInfoApplicationVerify(me, account.accountKey, NULL)) {
476 secnotice("circle", "Rejected, Purging my applicant peer (ID: %@) for circle '%@'", SOSPeerInfoGetPeerID(me), SOSCircleGetName(oldCircle));
477 debugDumpCircle(CFSTR("oldCircle"), oldCircle);
478 debugDumpCircle(CFSTR("newCircle"), newCircle);
479 if (self.fullPeerInfo)
480 SOSFullPeerInfoPurgePersistentKey(self.fullPeerInfo, NULL);
481 me = NULL;
482 me_full = NULL;
483 } else {
484 secnotice("circle", "Rejected, Reapplying (ID: %@) for circle '%@'", SOSPeerInfoGetPeerID(me), SOSCircleGetName(oldCircle));
485 debugDumpCircle(CFSTR("oldCircle"), oldCircle);
486 debugDumpCircle(CFSTR("newCircle"), newCircle);
487 SOSCircleRequestReadmission(newCircle, account.accountKey, me, NULL);
488 writeUpdate = true;
489 }
490 CFReleaseNull(reject);
491 }
492
493 if(me && account.accountKeyIsTrusted && SOSCircleHasPeer(newCircle, me, NULL)) {
494 // do this on daily interval +/- 8 hours random to keep all peers doing this at the same time
495 SOSIntervalEvent *iCloudCheckEvent = [self iCloudCheckEventHandle: account];
496 if([iCloudCheckEvent checkDate]) {
497 bool fixedIdentities = [self fixICloudIdentities:account circle:newCircle];
498 if(fixedIdentities) {
499 writeUpdate = true;
500 secnotice("circleOps", "Fixed iCloud Identity in circle");
501 } else {
502 secnotice("circleOps", "Failed to fix broken icloud identity");
503 }
504 [iCloudCheckEvent followup];
505 }
506 }
507
508 CFRetainSafe(oldCircle);
509 account.previousAccountKey = account.accountKey;
510
511 secnotice("signing", "%@, Accepting new circle", concStr);
512 if (circle_action == accept) {
513 [self setTrustedCircle:newCircle];
514 }
515
516 if (me && account.accountKeyIsTrusted
517 && SOSCircleHasApplicant(oldCircle, me, NULL)
518 && SOSCircleCountPeers(newCircle) > 0
519 && !SOSCircleHasPeer(newCircle, me, NULL) && !SOSCircleHasApplicant(newCircle, me, NULL)) {
520 // We weren't rejected (above would have set me to NULL.
521 // We were applying and we weren't accepted.
522 // Our application is declared lost, let us reapply.
523
524 secnotice("signing", "requesting readmission to new circle");
525 if (SOSCircleRequestReadmission(newCircle, account.accountKey, me, NULL))
526 writeUpdate = true;
527 }
528
529 if (me && SOSCircleHasActivePeer(oldCircle, me, NULL)) {
530 [account.trust cleanupRetirementTickets:account circle:oldCircle time:RETIREMENT_FINALIZATION_SECONDS err:NULL];
531 }
532
533 SOSAccountNotifyOfChange(account, oldCircle, newCircle);
534
535 CFReleaseNull(oldCircle);
536
537 if (writeUpdate)
538 circleToPush = newCircle;
539 secnotice("circleop", "Setting key_interests_need_updating to true in handleUpdateCircle");
540 account.key_interests_need_updating = true;
541 }
542
543 /*
544 * In the revert section we'll guard the KVS idea of circles by rejecting "bad" new circles
545 * and pushing our current view of the circle (oldCircle). We'll only do this if we actually
546 * are a member of oldCircle - never for an empty circle.
547 */
548
549 if (circle_action == revert) {
550 if(haveOldCircle && me && SOSCircleHasActivePeer(oldCircle, me, NULL)) {
551 secnotice("signing", "%@, Rejecting new circle, re-publishing old circle", concStr);
552 circleToPush = oldCircle;
553 [self setTrustedCircle:oldCircle];
554 } else {
555 secnotice("canary", "%@, Rejecting: new circle Have no old circle - would reset", concStr);
556 }
557 }
558
559
560 if (circleToPush != NULL) {
561 secnotice("signing", "Pushing:[%s]", local_remote);
562 CFDataRef circle_data = SOSCircleCopyEncodedData(circleToPush, kCFAllocatorDefault, error);
563
564 if (circle_data) {
565 // Ensure we flush changes
566 account.circle_rings_retirements_need_attention = true;
567
568 //posting new circle to peers
569 success &= [circleTransport postCircle:SOSCircleGetName(circleToPush) circleData:circle_data err:error];
570 } else {
571 success = false;
572 }
573 CFReleaseNull(circle_data);
574 }
575 CFReleaseSafe(newCircle);
576 CFReleaseNull(emptyCircle);
577
578 // There are errors collected above that are soft (worked around)
579 if(success && error && *error) {
580 CFReleaseNull(*error);
581 }
582
583 return success;
584 }
585
586 -(bool) updateCircleFromRemote:(SOSKVSCircleStorageTransport*)circleTransport newCircle:(SOSCircleRef)newCircle err:(CFErrorRef*)error
587 {
588 return [self handleUpdateCircle:newCircle transport:circleTransport update:false err:error];
589 }
590
591 -(bool) updateCircle:(SOSKVSCircleStorageTransport*)circleTransport newCircle:(SOSCircleRef) newCircle err:(CFErrorRef*)error
592 {
593 return [self handleUpdateCircle:newCircle transport:circleTransport update:true err:error];
594 }
595
596 -(bool) modifyCircle:(SOSKVSCircleStorageTransport*)circleTransport err:(CFErrorRef*)error action:(SOSModifyCircleBlock)block
597 {
598 bool success = false;
599 SOSCircleRef circleCopy = NULL;
600 require_action_quiet(self.trustedCircle, fail, SOSErrorCreate(kSOSErrorNoCircle, error, NULL, CFSTR("No circle to get peer key from")));
601
602 circleCopy = SOSCircleCopyCircle(kCFAllocatorDefault, self.trustedCircle, error);
603 require_quiet(circleCopy, fail);
604
605 success = true;
606 require_quiet(block(circleCopy), fail);
607
608 success = [self updateCircle:circleTransport newCircle:circleCopy err:error];
609
610 fail:
611 CFReleaseSafe(circleCopy);
612 return success;
613
614 }
615
616 // true means things changed.
617 -(bool) fixICloudIdentities:(SOSAccount *) account circle: (SOSCircleRef) circle {
618 bool retval = false;
619 SOSFullPeerInfoRef icfpi = SOSCircleCopyiCloudFullPeerInfoRef(circle, NULL);
620 if(!icfpi) {
621 SOSAccountRestartPrivateCredentialTimer(account);
622 if((SOSAccountGetPrivateCredential(account, NULL) != NULL) || SOSAccountAssertStashedAccountCredential(account, NULL)) {
623 SecKeyRef privKey = SOSAccountGetPrivateCredential(account, NULL);
624 if(privKey) {
625 SOSIntervalEvent *iCloudCleanupEvent = [self iCloudCleanerHandle: account];
626 if([iCloudCleanupEvent checkDate]) {
627 SOSAccountRemoveIncompleteiCloudIdentities(account, circle, privKey, NULL);
628 [iCloudCleanupEvent followup];
629 }
630 CFErrorRef error = NULL;
631 bool identityAdded = [self addiCloudIdentity:circle key:privKey err:&error];
632 if(identityAdded) {
633 account.notifyBackupOnExit = true;
634 retval = true;
635 [[SOSAnalytics logger] logSuccessForEventNamed:@"iCloudIdentityFix"];
636 } else {
637 [[SOSAnalytics logger] logResultForEvent:@"iCloudIdentityFix" hardFailure:true result:(__bridge NSError * _Nullable)(error)];
638 }
639 CFReleaseNull(error);
640 } else {
641 NSDictionary *attr = @{ @"reason" : @"noPrivateKey" };
642 [[SOSAnalytics logger] logHardFailureForEventNamed:@"iCloudIdentityFix" withAttributes:attr];
643 }
644 } else {
645 NSDictionary *attr = @{ @"reason" : @"noPrivateKey" };
646 [[SOSAnalytics logger] logHardFailureForEventNamed:@"iCloudIdentityFix" withAttributes:attr];
647 }
648 } else {
649 // everything is fine.
650 CFReleaseNull(icfpi);
651 }
652 return retval;
653 }
654
655 -(void) generationSignatureUpdateWith:(SOSAccount*)account key:(SecKeyRef) privKey
656 {
657 // rdar://51233857 - don't gensign if there isn't a change in the userKey
658 // also don't rebake the circle to fix the icloud identity if there isn't
659 // a change as that will mess up piggybacking.
660 if(SOSAccountFullPeerInfoVerify(account, privKey, NULL) && SOSCircleVerify(account.trust.trustedCircle, account.accountKey, NULL)) {
661 secnotice("updatingGenSignature", "no change to userKey - skipping gensign");
662 return;
663 }
664
665 if (self.trustedCircle && self.fullPeerInfo) {
666 [self modifyCircle:account.circle_transport err:NULL action:^(SOSCircleRef circle) {
667 SOSPeerInfoRef myPI = account.peerInfo;
668 bool iAmPeer = SOSCircleHasPeer(circle, myPI, NULL);
669 bool change = SOSCircleUpdatePeerInfo(circle, myPI);
670 if(iAmPeer && !SOSCircleVerify(circle, account.accountKey, NULL)) {
671 change |= [self upgradeiCloudIdentity:circle privKey:privKey];
672 [self removeInvalidApplications:circle userPublic:account.accountKey];
673 change |= SOSCircleGenerationSign(circle, privKey, self.fullPeerInfo, NULL);
674 [self setDepartureCode:kSOSNeverLeftCircle];
675 } else if(iAmPeer) {
676 change |= [self fixICloudIdentities:account circle:circle];
677 }
678 secnotice("updatingGenSignature", "we changed the circle? %@", change ? CFSTR("YES") : CFSTR("NO"));
679 SOSIntervalEvent *iCloudCheckEvent = [self iCloudCheckEventHandle: account];
680 [iCloudCheckEvent followup];
681 return change;
682 }];
683 }
684 }
685
686 -(void) forEachCirclePeerExceptMe:(SOSIteratePeerBlock)block
687 {
688 if (self.trustedCircle && self.peerInfo) {
689 NSString* myPi_id = self.peerID;
690 SOSCircleForEachPeer(self.trustedCircle, ^(SOSPeerInfoRef peer) {
691 CFStringRef peerID = SOSPeerInfoGetPeerID(peer);
692 if (peerID && ![myPi_id isEqualToString:(__bridge NSString*) peerID]) {
693 block(peer);
694 }
695 });
696 }
697 }
698
699 -(bool) leaveCircleWithAccount:(SOSAccount*)account withAnalytics:(NSData*)parentEvent err:(CFErrorRef*) error
700 {
701 bool result = true;
702 secnotice("circleOps", "leaveCircleWithAccount: Leaving circle by client request");
703 result &= [self modifyCircle:account.circle_transport err:error action:^(SOSCircleRef circle) {
704 return sosAccountLeaveCircle(account, circle, parentEvent, error);
705 }];
706
707 self.departureCode = kSOSWithdrewMembership;
708
709 return result;
710 }
711
712 -(bool) leaveCircle:(SOSAccount*)account err:(CFErrorRef*) error
713 {
714 bool result = true;
715 secnotice("circleOps", "Leaving circle by client request");
716 result &= [self modifyCircle:account.circle_transport err:error action:^(SOSCircleRef circle) {
717 return sosAccountLeaveCircle(account, circle, nil, error);
718 }];
719 account.backup_key = nil;
720 self.departureCode = kSOSWithdrewMembership;
721
722 return result;
723 }
724
725 -(bool) resetToOffering:(SOSAccountTransaction*) aTxn key:(SecKeyRef)userKey err:(CFErrorRef*) error
726 {
727 SOSFullPeerInfoPurgePersistentKey(self.fullPeerInfo, NULL);
728 self.fullPeerInfo = nil;
729
730 secnotice("resetToOffering", "Resetting circle to offering by request from client");
731
732 return userKey && [self resetCircleToOffering:aTxn userKey:userKey err:error];
733 }
734
735
736 -(bool) resetCircleToOffering:(SOSAccountTransaction*) aTxn userKey:(SecKeyRef)user_key err:(CFErrorRef *)error
737 {
738 bool result = false;
739
740 SOSAccount* account = aTxn.account;
741 if(![self hasCircle:error])
742 return result;
743
744 if(![self ensureFullPeerAvailable:(__bridge CFDictionaryRef)(account.gestalt) deviceID:(__bridge CFStringRef)(account.deviceID) backupKey:(__bridge CFDataRef)(account.backup_key) err:error])
745 return result;
746
747 (void)[self resetAllRings:account err:error];
748
749 [self modifyCircle:account.circle_transport err:error action:^bool(SOSCircleRef circle) {
750 bool result = false;
751 SOSFullPeerInfoRef cloud_identity = NULL;
752 CFErrorRef localError = NULL;
753
754 require_quiet(SOSCircleResetToOffering(circle, user_key, self.fullPeerInfo, &localError), err_out);
755
756 self.departureCode = kSOSNeverLeftCircle;
757
758 require_quiet([self addEscrowToPeerInfo:self.fullPeerInfo err:error], err_out);
759
760 require_quiet([self addiCloudIdentity:circle key:user_key err:error], err_out);
761 result = true;
762 SOSAccountPublishCloudParameters(account, NULL);
763 account.notifyBackupOnExit = true;
764
765 err_out:
766 if (result == false)
767 secerror("error resetting circle (%@) to offering: %@", circle, localError);
768 if (localError && error && *error == NULL) {
769 *error = localError;
770 localError = NULL;
771 }
772 CFReleaseNull(localError);
773 CFReleaseNull(cloud_identity);
774 return result;
775 }];
776
777 SOSAccountInitializeInitialSync(account);
778 SOSAccountUpdateOutOfSyncViews(aTxn, SOSViewsGetAllCurrent());
779
780 result = true;
781
782 notify_post(kSOSCCCircleOctagonKeysChangedNotification);
783
784 return result;
785 }
786
787 void SOSAccountForEachCirclePeerExceptMe(SOSAccount* account, void (^action)(SOSPeerInfoRef peer)) {
788 SOSPeerInfoRef myPi = account.peerInfo;
789 SOSCircleRef circle = NULL;
790
791 SOSAccountTrustClassic *trust = account.trust;
792 circle = trust.trustedCircle;
793 if (circle && myPi) {
794 CFStringRef myPi_id = SOSPeerInfoGetPeerID(myPi);
795 SOSCircleForEachPeer(circle, ^(SOSPeerInfoRef peer) {
796 CFStringRef peerID = SOSPeerInfoGetPeerID(peer);
797 if (peerID && !CFEqual(peerID, myPi_id)) {
798 action(peer);
799 }
800 });
801 }
802 }
803
804 -(bool) joinCircle:(SOSAccountTransaction*) aTxn userKey:(SecKeyRef) user_key useCloudPeer:(bool) use_cloud_peer err:(CFErrorRef*) error
805 {
806 __block bool result = false;
807 __block SOSFullPeerInfoRef cloud_full_peer = NULL;
808 __block SOSAccount* account = aTxn.account;
809 require_action_quiet(self.trustedCircle, fail, SOSCreateErrorWithFormat(kSOSErrorPeerNotFound, NULL, error, NULL, CFSTR("Don't have circle when joining???")));
810 require_quiet([self ensureFullPeerAvailable:(__bridge CFDictionaryRef)account.gestalt deviceID:(__bridge CFStringRef)account.deviceID backupKey:(__bridge CFDataRef)account.backup_key err:error], fail);
811
812 if (SOSCircleCountPeers(self.trustedCircle) == 0 || SOSAccountGhostResultsInReset(account)) {
813 secnotice("resetToOffering", "Resetting circle to offering since there are no peers");
814 // this also clears initial sync data
815 result = [self resetCircleToOffering:aTxn userKey:user_key err:error];
816 } else {
817 [self setValueInExpansion:kSOSUnsyncedViewsKey value:kCFBooleanTrue err:NULL];
818
819 if (use_cloud_peer) {
820 cloud_full_peer = SOSCircleCopyiCloudFullPeerInfoRef(self.trustedCircle, NULL);
821 }
822
823 [self modifyCircle: account.circle_transport err:error action:^(SOSCircleRef circle) {
824 result = SOSAccountAddEscrowToPeerInfo(account, self.fullPeerInfo, error);
825 result &= SOSCircleRequestAdmission(circle, user_key, self.fullPeerInfo, error);
826 self.departureCode = kSOSNeverLeftCircle;
827 if(result && cloud_full_peer) {
828 CFErrorRef localError = NULL;
829 CFStringRef cloudid = SOSPeerInfoGetPeerID(SOSFullPeerInfoGetPeerInfo(cloud_full_peer));
830 require_quiet(cloudid, finish);
831 require_quiet(SOSCircleHasActivePeerWithID(circle, cloudid, &localError), finish);
832 require_quiet(SOSCircleAcceptRequest(circle, user_key, cloud_full_peer, self.peerInfo, &localError), finish);
833
834 finish:
835 if (localError){
836 secerror("Failed to join with cloud identity: %@", localError);
837 CFReleaseNull(localError);
838 }
839 }
840 return result;
841 }];
842
843 if (use_cloud_peer || SOSAccountHasCompletedInitialSync(account)) {
844 SOSAccountUpdateOutOfSyncViews(aTxn, SOSViewsGetAllCurrent());
845 }
846 }
847
848 fail:
849 CFReleaseNull(cloud_full_peer);
850 return result;
851 }
852
853 @end