]> git.saurik.com Git - apple/security.git/blob - keychain/SecureObjectSync/SOSAccountTrustClassic+Circle.m
Security-59754.41.1.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, 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 [self purgeIdentity];
468 me = NULL;
469 me_full = NULL;
470 }
471
472 if (me && SOSCircleHasRejectedApplicant(newCircle, me, NULL)) {
473 SOSPeerInfoRef reject = SOSCircleCopyRejectedApplicant(newCircle, me, NULL);
474 if(CFEqualSafe(reject, me) && SOSPeerInfoApplicationVerify(me, account.accountKey, NULL)) {
475 secnotice("circle", "Rejected, Purging my applicant peer (ID: %@) for circle '%@'", SOSPeerInfoGetPeerID(me), SOSCircleGetName(oldCircle));
476 debugDumpCircle(CFSTR("oldCircle"), oldCircle);
477 debugDumpCircle(CFSTR("newCircle"), newCircle);
478 [self purgeIdentity];
479 me = NULL;
480 me_full = NULL;
481 } else {
482 secnotice("circle", "Rejected, Reapplying (ID: %@) for circle '%@'", SOSPeerInfoGetPeerID(me), SOSCircleGetName(oldCircle));
483 debugDumpCircle(CFSTR("oldCircle"), oldCircle);
484 debugDumpCircle(CFSTR("newCircle"), newCircle);
485 SOSCircleRequestReadmission(newCircle, account.accountKey, me, NULL);
486 writeUpdate = true;
487 }
488 CFReleaseNull(reject);
489 }
490
491 if(me && account.accountKeyIsTrusted && SOSCircleHasPeer(newCircle, me, NULL)) {
492 // do this on daily interval +/- 8 hours random to keep all peers doing this at the same time
493 SOSIntervalEvent *iCloudCheckEvent = [self iCloudCheckEventHandle: account];
494 if([iCloudCheckEvent checkDate]) {
495 bool fixedIdentities = [self fixICloudIdentities:account circle:newCircle];
496 if(fixedIdentities) {
497 writeUpdate = true;
498 secnotice("circleOps", "Fixed iCloud Identity in circle");
499 } else {
500 secnotice("circleOps", "Failed to fix broken icloud identity");
501 }
502 [iCloudCheckEvent followup];
503 }
504 }
505
506 CFRetainSafe(oldCircle);
507 account.previousAccountKey = account.accountKey;
508
509 secnotice("signing", "%@, Accepting new circle", concStr);
510 if (circle_action == accept) {
511 [self setTrustedCircle:newCircle];
512 }
513
514 if (me && account.accountKeyIsTrusted
515 && SOSCircleHasApplicant(oldCircle, me, NULL)
516 && SOSCircleCountPeers(newCircle) > 0
517 && !SOSCircleHasPeer(newCircle, me, NULL) && !SOSCircleHasApplicant(newCircle, me, NULL)) {
518 // We weren't rejected (above would have set me to NULL.
519 // We were applying and we weren't accepted.
520 // Our application is declared lost, let us reapply.
521
522 secnotice("signing", "requesting readmission to new circle");
523 if (SOSCircleRequestReadmission(newCircle, account.accountKey, me, NULL))
524 writeUpdate = true;
525 }
526
527 if (me && SOSCircleHasActivePeer(oldCircle, me, NULL)) {
528 [account.trust cleanupRetirementTickets:account circle:oldCircle time:RETIREMENT_FINALIZATION_SECONDS err:NULL];
529 }
530
531 SOSAccountNotifyOfChange(account, oldCircle, newCircle);
532
533 CFReleaseNull(oldCircle);
534
535 if (writeUpdate)
536 circleToPush = newCircle;
537 secnotice("circleop", "Setting key_interests_need_updating to true in handleUpdateCircle");
538 account.key_interests_need_updating = true;
539 }
540
541 /*
542 * In the revert section we'll guard the KVS idea of circles by rejecting "bad" new circles
543 * and pushing our current view of the circle (oldCircle). We'll only do this if we actually
544 * are a member of oldCircle - never for an empty circle.
545 */
546
547 if (circle_action == revert) {
548 if(haveOldCircle && me && SOSCircleHasActivePeer(oldCircle, me, NULL)) {
549 secnotice("signing", "%@, Rejecting new circle, re-publishing old circle", concStr);
550 circleToPush = oldCircle;
551 [self setTrustedCircle:oldCircle];
552 } else {
553 secnotice("canary", "%@, Rejecting: new circle Have no old circle - would reset", concStr);
554 }
555 }
556
557
558 if (circleToPush != NULL) {
559 secnotice("signing", "Pushing:[%s]", local_remote);
560 CFDataRef circle_data = SOSCircleCopyEncodedData(circleToPush, kCFAllocatorDefault, error);
561
562 if (circle_data) {
563 // Ensure we flush changes
564 account.circle_rings_retirements_need_attention = true;
565
566 //posting new circle to peers
567 success &= [circleTransport postCircle:SOSCircleGetName(circleToPush) circleData:circle_data err:error];
568 } else {
569 success = false;
570 }
571 CFReleaseNull(circle_data);
572 }
573 CFReleaseSafe(newCircle);
574 CFReleaseNull(emptyCircle);
575
576 // There are errors collected above that are soft (worked around)
577 if(success && error && *error) {
578 CFReleaseNull(*error);
579 }
580
581 return success;
582 }
583
584 -(bool) updateCircleFromRemote:(SOSKVSCircleStorageTransport*)circleTransport newCircle:(SOSCircleRef)newCircle err:(CFErrorRef*)error
585 {
586 return [self handleUpdateCircle:newCircle transport:circleTransport update:false err:error];
587 }
588
589 -(bool) updateCircle:(SOSKVSCircleStorageTransport*)circleTransport newCircle:(SOSCircleRef) newCircle err:(CFErrorRef*)error
590 {
591 return [self handleUpdateCircle:newCircle transport:circleTransport update:true err:error];
592 }
593
594 -(bool) modifyCircle:(SOSKVSCircleStorageTransport*)circleTransport err:(CFErrorRef*)error action:(SOSModifyCircleBlock)block
595 {
596 bool success = false;
597 SOSCircleRef circleCopy = NULL;
598 require_action_quiet(self.trustedCircle, fail, SOSErrorCreate(kSOSErrorNoCircle, error, NULL, CFSTR("No circle to get peer key from")));
599
600 circleCopy = SOSCircleCopyCircle(kCFAllocatorDefault, self.trustedCircle, error);
601 require_quiet(circleCopy, fail);
602
603 success = true;
604 require_quiet(block(circleCopy), fail);
605
606 success = [self updateCircle:circleTransport newCircle:circleCopy err:error];
607
608 fail:
609 CFReleaseSafe(circleCopy);
610 return success;
611
612 }
613
614 // true means things changed.
615 -(bool) fixICloudIdentities:(SOSAccount *) account circle: (SOSCircleRef) circle {
616 bool retval = false;
617 SOSFullPeerInfoRef icfpi = SOSCircleCopyiCloudFullPeerInfoRef(circle, NULL);
618 if(!icfpi) {
619 SOSAccountRestartPrivateCredentialTimer(account);
620 if((SOSAccountGetPrivateCredential(account, NULL) != NULL) || SOSAccountAssertStashedAccountCredential(account, NULL)) {
621 SecKeyRef privKey = SOSAccountGetPrivateCredential(account, NULL);
622 if(privKey) {
623 SOSIntervalEvent *iCloudCleanupEvent = [self iCloudCleanerHandle: account];
624 if([iCloudCleanupEvent checkDate]) {
625 SOSAccountRemoveIncompleteiCloudIdentities(account, circle, privKey, NULL);
626 [iCloudCleanupEvent followup];
627 }
628 CFErrorRef error = NULL;
629 bool identityAdded = [self addiCloudIdentity:circle key:privKey err:&error];
630 if(identityAdded) {
631 account.notifyBackupOnExit = true;
632 retval = true;
633 [[SOSAnalytics logger] logSuccessForEventNamed:@"iCloudIdentityFix"];
634 } else {
635 [[SOSAnalytics logger] logResultForEvent:@"iCloudIdentityFix" hardFailure:true result:(__bridge NSError * _Nullable)(error)];
636 }
637 CFReleaseNull(error);
638 } else {
639 NSDictionary *attr = @{ @"reason" : @"noPrivateKey" };
640 [[SOSAnalytics logger] logHardFailureForEventNamed:@"iCloudIdentityFix" withAttributes:attr];
641 }
642 } else {
643 NSDictionary *attr = @{ @"reason" : @"noPrivateKey" };
644 [[SOSAnalytics logger] logHardFailureForEventNamed:@"iCloudIdentityFix" withAttributes:attr];
645 }
646 } else {
647 // everything is fine.
648 CFReleaseNull(icfpi);
649 }
650 return retval;
651 }
652
653 -(void) generationSignatureUpdateWith:(SOSAccount*)account key:(SecKeyRef) privKey
654 {
655 // rdar://51233857 - don't gensign if there isn't a change in the userKey
656 // also don't rebake the circle to fix the icloud identity if there isn't
657 // a change as that will mess up piggybacking.
658 if(account.trust.trustedCircle && SOSAccountFullPeerInfoVerify(account, privKey, NULL) && SOSCircleVerify(account.trust.trustedCircle, account.accountKey, NULL)) {
659 secnotice("updatingGenSignature", "no change to userKey - skipping gensign");
660 return;
661 }
662
663 if (self.trustedCircle && self.fullPeerInfo) {
664 [self modifyCircle:account.circle_transport err:NULL action:^(SOSCircleRef circle) {
665 SOSPeerInfoRef myPI = account.peerInfo;
666 bool iAmPeer = SOSCircleHasPeer(circle, myPI, NULL);
667 bool change = SOSCircleUpdatePeerInfo(circle, myPI);
668 if(iAmPeer && !SOSCircleVerify(circle, account.accountKey, NULL)) {
669 change |= [self upgradeiCloudIdentity:circle privKey:privKey];
670 [self removeInvalidApplications:circle userPublic:account.accountKey];
671 change |= SOSCircleGenerationSign(circle, privKey, self.fullPeerInfo, NULL);
672 [self setDepartureCode:kSOSNeverLeftCircle];
673 } else if(iAmPeer) {
674 change |= [self fixICloudIdentities:account circle:circle];
675 }
676 secnotice("updatingGenSignature", "we changed the circle? %@", change ? CFSTR("YES") : CFSTR("NO"));
677 SOSIntervalEvent *iCloudCheckEvent = [self iCloudCheckEventHandle: account];
678 [iCloudCheckEvent followup];
679 return change;
680 }];
681 }
682 }
683
684 -(void) forEachCirclePeerExceptMe:(SOSIteratePeerBlock)block
685 {
686 if (self.trustedCircle && self.peerInfo) {
687 NSString* myPi_id = self.peerID;
688 SOSCircleForEachPeer(self.trustedCircle, ^(SOSPeerInfoRef peer) {
689 CFStringRef peerID = SOSPeerInfoGetPeerID(peer);
690 if (peerID && ![myPi_id isEqualToString:(__bridge NSString*) peerID]) {
691 block(peer);
692 }
693 });
694 }
695 }
696
697 -(bool) leaveCircleWithAccount:(SOSAccount*)account err:(CFErrorRef*) error
698 {
699 bool result = true;
700 secnotice("circleOps", "leaveCircleWithAccount: Leaving circle by client request");
701 result &= [self modifyCircle:account.circle_transport err:error action:^(SOSCircleRef circle) {
702 return sosAccountLeaveCircle(account, circle, error);
703 }];
704
705 self.departureCode = kSOSWithdrewMembership;
706
707 return result;
708 }
709
710 -(bool) leaveCircle:(SOSAccount*)account err:(CFErrorRef*) error
711 {
712 bool result = true;
713 secnotice("circleOps", "Leaving circle by client request");
714 result &= [self modifyCircle:account.circle_transport err:error action:^(SOSCircleRef circle) {
715 return sosAccountLeaveCircle(account, circle, error);
716 }];
717 account.backup_key = nil;
718 self.departureCode = kSOSWithdrewMembership;
719
720 return result;
721 }
722
723 -(bool) resetToOffering:(SOSAccountTransaction*) aTxn key:(SecKeyRef)userKey err:(CFErrorRef*) error
724 {
725 [self purgeIdentity];
726
727 secnotice("resetToOffering", "Resetting circle to offering by request from client");
728
729 return userKey && [self resetCircleToOffering:aTxn userKey:userKey err:error];
730 }
731
732
733 -(bool) resetCircleToOffering:(SOSAccountTransaction*) aTxn userKey:(SecKeyRef)user_key err:(CFErrorRef *)error
734 {
735 bool result = false;
736
737 SOSAccount* account = aTxn.account;
738 if(![self hasCircle:error])
739 return result;
740
741 if(![self ensureFullPeerAvailable:account err:error])
742 return result;
743
744 (void)[self resetAllRings:account err:error];
745
746 [self modifyCircle:account.circle_transport err:error action:^bool(SOSCircleRef circle) {
747 bool result = false;
748 SOSFullPeerInfoRef cloud_identity = NULL;
749 CFErrorRef localError = NULL;
750
751 require_quiet(SOSCircleResetToOffering(circle, user_key, self.fullPeerInfo, &localError), err_out);
752
753 self.departureCode = kSOSNeverLeftCircle;
754
755 require_quiet([self addEscrowToPeerInfo:self.fullPeerInfo err:error], err_out);
756
757 require_quiet([self addiCloudIdentity:circle key:user_key err:error], err_out);
758 result = true;
759 SOSAccountPublishCloudParameters(account, NULL);
760 account.notifyBackupOnExit = true;
761
762 err_out:
763 if (result == false)
764 secerror("error resetting circle (%@) to offering: %@", circle, localError);
765 if (localError && error && *error == NULL) {
766 *error = localError;
767 localError = NULL;
768 }
769 CFReleaseNull(localError);
770 CFReleaseNull(cloud_identity);
771 return result;
772 }];
773
774 SOSAccountInitializeInitialSync(account);
775 SOSAccountUpdateOutOfSyncViews(aTxn, SOSViewsGetAllCurrent());
776
777 result = true;
778
779 notify_post(kSOSCCCircleOctagonKeysChangedNotification);
780
781 return result;
782 }
783
784 void SOSAccountForEachCirclePeerExceptMe(SOSAccount* account, void (^action)(SOSPeerInfoRef peer)) {
785 SOSPeerInfoRef myPi = account.peerInfo;
786 SOSCircleRef circle = NULL;
787
788 SOSAccountTrustClassic *trust = account.trust;
789 circle = trust.trustedCircle;
790 if (circle && myPi) {
791 CFStringRef myPi_id = SOSPeerInfoGetPeerID(myPi);
792 SOSCircleForEachPeer(circle, ^(SOSPeerInfoRef peer) {
793 CFStringRef peerID = SOSPeerInfoGetPeerID(peer);
794 if (peerID && !CFEqual(peerID, myPi_id)) {
795 action(peer);
796 }
797 });
798 }
799 }
800
801 -(bool) joinCircle:(SOSAccountTransaction*) aTxn userKey:(SecKeyRef) user_key useCloudPeer:(bool) use_cloud_peer err:(CFErrorRef*) error
802 {
803 __block bool result = false;
804 __block SOSFullPeerInfoRef cloud_full_peer = NULL;
805 __block SOSAccount* account = aTxn.account;
806 require_action_quiet(self.trustedCircle, fail, SOSCreateErrorWithFormat(kSOSErrorPeerNotFound, NULL, error, NULL, CFSTR("Don't have circle when joining???")));
807 require_quiet([self ensureFullPeerAvailable:account err:error], fail);
808
809 if (SOSCircleCountPeers(self.trustedCircle) == 0 || SOSAccountGhostResultsInReset(account)) {
810 secnotice("resetToOffering", "Resetting circle to offering since there are no peers");
811 // this also clears initial sync data
812 result = [self resetCircleToOffering:aTxn userKey:user_key err:error];
813 } else {
814 [self setValueInExpansion:kSOSUnsyncedViewsKey value:kCFBooleanTrue err:NULL];
815
816 if (use_cloud_peer) {
817 cloud_full_peer = SOSCircleCopyiCloudFullPeerInfoRef(self.trustedCircle, NULL);
818 }
819
820 [self modifyCircle: account.circle_transport err:error action:^(SOSCircleRef circle) {
821 result = SOSAccountAddEscrowToPeerInfo(account, self.fullPeerInfo, error);
822 result &= SOSCircleRequestAdmission(circle, user_key, self.fullPeerInfo, error);
823 self.departureCode = kSOSNeverLeftCircle;
824 if(result && cloud_full_peer) {
825 CFErrorRef localError = NULL;
826 CFStringRef cloudid = SOSPeerInfoGetPeerID(SOSFullPeerInfoGetPeerInfo(cloud_full_peer));
827 require_quiet(cloudid, finish);
828 require_quiet(SOSCircleHasActivePeerWithID(circle, cloudid, &localError), finish);
829 require_quiet(SOSCircleAcceptRequest(circle, user_key, cloud_full_peer, self.peerInfo, &localError), finish);
830
831 finish:
832 if (localError){
833 secerror("Failed to join with cloud identity: %@", localError);
834 CFReleaseNull(localError);
835 }
836 }
837 return result;
838 }];
839
840 if (use_cloud_peer || SOSAccountHasCompletedInitialSync(account)) {
841 SOSAccountUpdateOutOfSyncViews(aTxn, SOSViewsGetAllCurrent());
842 }
843 }
844
845 fail:
846 CFReleaseNull(cloud_full_peer);
847 return result;
848 }
849
850 @end