2 // SOSAccountTrustClassicExpansion.m
7 #import <Foundation/Foundation.h>
8 #import "keychain/SecureObjectSync/SOSAccount.h"
9 #import "keychain/SecureObjectSync/SOSAccountTrustClassic.h"
10 #import "keychain/SecureObjectSync/SOSAccountTrustClassic+Expansion.h"
11 #import "keychain/SecureObjectSync/SOSAccountTrustClassic+Retirement.h"
12 #import "keychain/SecureObjectSync/SOSAccountTrustClassic+Circle.h"
13 #import "keychain/SecureObjectSync/SOSViews.h"
14 #import "keychain/SecureObjectSync/SOSPeerInfoV2.h"
15 #import "keychain/SecureObjectSync/SOSPeerInfoCollections.h"
16 #import "keychain/SecureObjectSync/SOSTransportCircleKVS.h"
17 #import "keychain/SecureObjectSync/SOSRingRecovery.h"
19 @implementation SOSAccountTrustClassic (Expansion)
29 static const char *actionstring[] = {
30 "accept", "countersign", "leave", "revert", "modify", "ignore",
32 static NSString* kSOSRingKey = @"trusted_rings";
35 // Generic Calls to Expansion Dictionary
37 -(CFTypeRef) getValueFromExpansion:(CFStringRef)key err:(CFErrorRef*)error
39 if (!self.expansion) {
42 return (__bridge CFTypeRef)([self.expansion objectForKey:(__bridge NSString*)key]);
45 -(bool) ensureExpansion:(CFErrorRef *)error
47 if (!self.expansion) {
48 self.expansion = [NSMutableDictionary dictionary];
51 return SecAllocationError((__bridge CFTypeRef)(self.expansion), error, CFSTR("Can't Alloc Account Expansion dictionary"));
54 -(bool) clearValueFromExpansion:(CFStringRef) key err:(CFErrorRef *)error
56 bool success = [self ensureExpansion:error];
58 require_quiet(success, errOut);
60 [self.expansion removeObjectForKey: (__bridge NSString*)(key)];
65 -(bool) setValueInExpansion:(CFStringRef) key value:(CFTypeRef) value err:(CFErrorRef *)error {
66 if (value == NULL) return [self clearValueFromExpansion:key err:error];
68 bool success = [self ensureExpansion:error];
69 require_quiet(success, errOut);
71 [self.expansion setObject:(__bridge id _Nonnull)(value) forKey:(__bridge NSString*)key];
77 -(bool) valueSetContainsValue:(CFStringRef) key value:(CFTypeRef) value
79 CFSetRef foundSet = asSet([self getValueFromExpansion:key err:NULL], NULL);
80 return foundSet && CFSetContainsValue(foundSet, value);
83 -(void) valueUnionWith:(CFStringRef) key valuesToUnion:(CFSetRef) valuesToUnion
85 CFMutableSetRef unionedSet = CFSetCreateMutableCopy(kCFAllocatorDefault, 0, valuesToUnion);
86 CFSetRef foundSet = asSet([self getValueFromExpansion:key err:NULL], NULL);
88 CFSetUnion(unionedSet, foundSet);
90 [self setValueInExpansion:key value:unionedSet err:NULL];
91 CFReleaseNull(unionedSet);
94 -(void) valueSubtractFrom:(CFStringRef) key valuesToSubtract:(CFSetRef) valuesToSubtract
96 CFSetRef foundSet = asSet([self getValueFromExpansion:key err:NULL], NULL);
98 CFMutableSetRef subtractedSet = CFSetCreateMutableCopy(kCFAllocatorDefault, 0, foundSet);
99 CFSetSubtract(subtractedSet, valuesToSubtract);
100 [self setValueInExpansion:key value:subtractedSet err:NULL];
101 CFReleaseNull(subtractedSet);
106 -(void) pendEnableViewSet:(CFSetRef) enabledViews
108 if(CFSetGetValue(enabledViews, kSOSViewKeychainV0) != NULL) secnotice("viewChange", "Warning, attempting to Add KeychainV0");
110 [self valueUnionWith:kSOSPendingEnableViewsToBeSetKey valuesToUnion:enabledViews];
111 [self valueSubtractFrom:kSOSPendingDisableViewsToBeSetKey valuesToSubtract:enabledViews];
115 -(bool) updateV2Dictionary:(SOSAccount*)account v2:(CFDictionaryRef) newV2Dict
117 if(!newV2Dict) return true;
119 [self setValueInExpansion:kSOSTestV2Settings value:newV2Dict err:NULL];
121 if (self.trustedCircle && self.fullPeerInfo
122 && SOSFullPeerInfoUpdateV2Dictionary(self.fullPeerInfo, newV2Dict, NULL)) {
123 [self modifyCircle:account.circle_transport err:NULL action:^(SOSCircleRef circle_to_change) {
124 secnotice("circleChange", "Calling SOSCircleUpdatePeerInfo for gestalt change");
125 return SOSCircleUpdatePeerInfo(circle_to_change, account.peerInfo);
135 -(bool) forEachRing:(RingNameBlock)block
138 __block bool changed = false;
139 __block CFStringRef ringname = NULL;
140 __block CFDataRef ringder = NULL;
141 __block SOSRingRef ring = NULL;
142 __block SOSRingRef newring = NULL;
143 __block CFDataRef newringder = NULL;
145 CFMutableDictionaryRef rings = [self getRings:NULL];
146 CFMutableDictionaryRef ringscopy = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault);
148 CFReleaseNull(ringscopy);
152 CFReleaseNull(ringscopy);
155 CFDictionaryForEach(rings, ^(const void *key, const void *value) {
156 ringname = (CFStringRef) key;
157 ringder = CFDataCreateCopy(kCFAllocatorDefault, (CFDataRef) value);
158 CFDictionaryAddValue(ringscopy, key, ringder);
159 ring = SOSRingCreateFromData(NULL, ringder);
160 newring = block(ringname, ring);
162 newringder = SOSRingCopyEncodedData(newring, NULL);
163 CFDictionaryReplaceValue(ringscopy, key, newringder);
164 CFReleaseNull(newringder);
168 CFReleaseNull(ringder);
169 CFReleaseNull(newring);
172 [self setRings:ringscopy];
176 CFReleaseNull(ringscopy);
180 -(bool) resetAllRings:(SOSAccount*)account err:(CFErrorRef *)error
182 __block bool retval = true;
183 CFMutableSetRef ringList = CFSetCreateMutableForCFTypes(kCFAllocatorDefault);
185 CFReleaseNull(ringList);
189 [self forEachRing: ^SOSRingRef(CFStringRef name, SOSRingRef ring) {
190 CFSetAddValue(ringList, name);
191 return NULL; // just using this to grab names.
194 CFSetForEach(ringList, ^(const void *value) {
195 CFStringRef ringName = (CFStringRef) value;
196 retval = retval && [self resetRing:account ringName:ringName err:error];
199 CFReleaseNull(ringList);
203 -(bool) resetAccountToEmpty:(SOSAccount*)account transport: (SOSCircleStorageTransport*)circleTransport err:(CFErrorRef*) error
205 __block bool result = true;
206 CFErrorRef resetError = NULL;
208 result &= [self resetAllRings:account err:&resetError];
210 secerror("reset all rings error: %@", resetError);
214 CFReleaseNull(resetError);
218 self.fullPeerInfo = nil;
220 self.departureCode = kSOSWithdrewMembership;
221 secnotice("circleOps", "Reset Rings to empty by client request");
223 result &= [self modifyCircle:circleTransport err:error action:^bool(SOSCircleRef circle) {
224 result = SOSCircleResetToEmpty(circle, error);
229 secerror("error: %@", error ? *error : NULL);
231 notify_post(kSOSCCCircleOctagonKeysChangedNotification);
236 -(void) setRings:(CFMutableDictionaryRef) newrings
238 [self.expansion setObject:(__bridge NSMutableDictionary*)newrings forKey:(kSOSRingKey)];
241 -(bool) checkForRings:(CFErrorRef*)error
243 __block bool retval = true;
244 CFMutableDictionaryRef rings = [self getRings:NULL];
245 if(rings && isDictionary(rings)) {
246 [self forEachRing:^SOSRingRef(CFStringRef ringname, SOSRingRef ring) {
248 if(!SOSRingIsStable(ring)) {
250 secnotice("ring", "Ring %@ not stable", ringname);
256 SOSCreateError(kSOSErrorNotReady, CFSTR("Rings not present"), NULL, error);
262 -(bool) setRing:(SOSRingRef) addRing ringName:(CFStringRef) ringName err:(CFErrorRef*)error
264 require_quiet(addRing, errOut);
265 CFMutableDictionaryRef rings = [self getRings:NULL];
266 require_action_quiet(rings, errOut, SOSCreateError(kSOSErrorNoRing, CFSTR("No Rings found"), NULL, error));
267 CFDataRef ringder = SOSRingCopyEncodedData(addRing, error);
268 require_quiet(ringder, errOut);
269 CFDictionarySetValue(rings, ringName, ringder);
270 CFReleaseNull(ringder);
276 -(bool) handleUpdateRing:(SOSAccount*)account prospectiveRing:(SOSRingRef)prospectiveRing transport:(SOSKVSCircleStorageTransport*)circleTransport userPublicKey:(SecKeyRef)userPublic writeUpdate:(bool)localUpdate err:(CFErrorRef *)error
278 bool success = false;
279 bool haveOldRing = true;
280 static uint recRingProcessed = 0;
281 static uint bckRingProcessed = 0;
283 const char * __unused localRemote = localUpdate ? "local": "remote";
284 SOSFullPeerInfoRef fpi = self.fullPeerInfo;
285 SOSPeerInfoRef pi = SOSFullPeerInfoGetPeerInfo(fpi);
286 CFStringRef peerID = SOSPeerInfoGetPeerID(pi);
287 SecKeyRef peerPrivKey = SOSFullPeerInfoCopyDeviceKey(fpi, NULL);
288 SecKeyRef peerPubKey = SOSFullPeerInfoCopyPubKey(fpi, NULL);
289 __block bool peerActive = (fpi && pi && peerID && [self isInCircleOnly:NULL]);
290 bool ringIsBackup = SOSRingGetType(prospectiveRing) == kSOSRingBackup;
291 bool ringIsRecovery = SOSRingGetType(prospectiveRing) == kSOSRingRecovery;
292 CFStringRef ringName = SOSRingGetName(prospectiveRing);
293 CFMutableSetRef peers = SOSCircleCopyPeers(self.trustedCircle, kCFAllocatorDefault); // retirement tickets and iCloud key filtered out
294 CFMutableSetRef filteredPeerIDs = CFSetCreateMutableForCFTypes(kCFAllocatorDefault);
295 CFMutableSetRef filteredPeerInfos = CFSetCreateMutableForSOSPeerInfosByID(kCFAllocatorDefault);
296 CFStringRef ringBackupViewName = NULL;
298 SOSRingRef ringToPush = NULL;
299 SOSRingRef newRing = NULL;
300 SOSRingRef oldRing = NULL;
302 CFStringRef modifierPeerID = CFStringCreateTruncatedCopy(SOSRingGetLastModifier(prospectiveRing), 8);
303 secnotice("ring", "start:[%s] modifier: %@", localRemote, modifierPeerID);
304 CFReleaseNull(modifierPeerID);
306 // don't act on our own echos from KVS (remote ring, our peerID as modifier)
307 if(!localUpdate && CFEqualSafe(peerID, SOSRingGetLastModifier(prospectiveRing))) {
308 secnotice("ring", "Ceasing ring handling for an echo of our own posted ring");
313 require_quiet(SOSAccountHasPublicKey(account, error), errOut);
314 require_action_quiet(peerPubKey, errOut, SOSCreateError(kSOSErrorPublicKeyAbsent, CFSTR("No device public key to work with"), NULL, error));
315 require_action_quiet(peerPrivKey, errOut, SOSCreateError(kSOSErrorPrivateKeyAbsent, CFSTR("No device private key to work with"), NULL, error));
316 require_action_quiet(prospectiveRing, errOut, SOSCreateError(kSOSErrorIncompatibleCircle, CFSTR("No Ring to work with"), NULL, error));
317 require_action_quiet(SOSRingIsStable(prospectiveRing), errOut, SOSCreateError(kSOSErrorIncompatibleCircle, CFSTR("You give rings a bad name"), NULL, error));
319 // We should at least have a sane ring system in the account object
320 require_quiet([self checkForRings:error], errOut);
323 ringBackupViewName = SOSRingGetBackupView(prospectiveRing, NULL);
324 peerActive &= ringBackupViewName && SOSPeerInfoIsViewPermitted(pi, ringBackupViewName) && SOSPeerInfoHasBackupKey(pi);
326 require_action_quiet(peerActive, errOut, success = true);
328 oldRing = [self copyRing:ringName err:NULL];
329 newRing = SOSRingCopyRing(prospectiveRing, NULL);
330 ringAction_t ringAction = ignore;
332 bool userTrustedoldRing = (oldRing) ? SOSRingVerify(oldRing, peerPubKey, NULL): false;
333 SecKeyRef oldKey = userPublic;
336 oldRing = CFRetainSafe(newRing);
339 SOSConcordanceStatus concstat = SOSRingConcordanceTrust(fpi, peers, oldRing, newRing, oldKey, userPublic, peerID, error);
341 CFStringRef concStr = CFSTR("NA");
343 case kSOSConcordanceTrusted:
344 ringAction = countersign;
345 concStr = CFSTR("Trusted");
347 case kSOSConcordanceGenOld:
348 ringAction = userTrustedoldRing ? revert : ignore;
349 concStr = CFSTR("Generation Old");
351 case kSOSConcordanceBadUserSig:
352 case kSOSConcordanceBadPeerSig:
353 ringAction = userTrustedoldRing ? revert : accept;
354 concStr = CFSTR("Bad Signature");
356 case kSOSConcordanceNoUserSig:
357 ringAction = userTrustedoldRing ? revert : accept;
358 concStr = CFSTR("No User Signature");
360 case kSOSConcordanceNoPeerSig:
361 ringAction = accept; // We might like this one eventually but don't countersign.
362 concStr = CFSTR("No trusted peer signature");
363 secnotice("signing", "##### No trusted peer signature found, accepting hoping for concordance later");
365 case kSOSConcordanceNoPeer:
367 concStr = CFSTR("No trusted peer left");
369 case kSOSConcordanceNoUserKey:
370 secerror("##### No User Public Key Available, this shouldn't ever happen!!!");
371 concStr = CFSTR("No User Public Key Available");
375 case kSOSConcordanceMissingMe:
377 concStr = CFSTR("Incorrect membership for me");
379 case kSOSConcordanceImNotWorthy:
381 concStr = CFSTR("This peer shouldn't be in this ring since it isn't in view");
383 case kSOSConcordanceInvalidMembership:
384 ringAction = userTrustedoldRing ? revert : ignore;
385 concStr = CFSTR("Invalid Ring Membership");
388 secerror("##### Bad Error Return from ConcordanceTrust");
389 concStr = CFSTR("Bad Error Return from ConcordanceTrust");
394 secnotice("ring", "Decided on action [%s] based on concordance state [%@] and [%s] ring.",
395 actionstring[ringAction], concStr, userTrustedoldRing ? "trusted" : "untrusted");
397 // if we're ignoring this ring we're done
398 require_action_quiet(ringAction != ignore, errOut, success = true);
399 // can't really remove ourselves since we can't sign when we do - need to rely on other peers to remove us
400 require_action_quiet(ringAction != leave, leaveAndAccept, ringAction = accept);
402 // This will take care of modify, but we're always going to do this scan if we get this far
403 CFSetRef ringPeerIDSet = SOSRingCopyPeerIDs(newRing);
404 if(CFSetGetCount(ringPeerIDSet) == 0) { // this is a reset ring
405 secnotice("ring", "changing state to accept - we have a reset ring");
408 // Get the peerIDs appropriate for the ring
410 SOSCircleForEachBackupCapablePeerForView(self.trustedCircle, userPublic, ringBackupViewName, ^(SOSPeerInfoRef peer) {
411 CFSetAddValue(filteredPeerIDs, SOSPeerInfoGetPeerID(peer));
412 CFSetAddValue(filteredPeerInfos, peer);
415 SOSCircleForEachValidSyncingPeer(self.trustedCircle, userPublic, ^(SOSPeerInfoRef peer) {
416 CFSetAddValue(filteredPeerIDs, SOSPeerInfoGetPeerID(peer));
417 CFSetAddValue(filteredPeerInfos, peer);
421 if(!CFEqual(filteredPeerIDs, ringPeerIDSet)) {
422 secnotice("ring", "mismatch between filteredPeerIDs and ringPeerIDSet, fixing ring and gensigning");
423 secnotice("ring", "filteredPeerIDs %@", filteredPeerIDs);
424 secnotice("ring", " ringPeerIDSet %@", ringPeerIDSet);
425 SOSRingSetPeerIDs(newRing, filteredPeerIDs);
426 SOSRingRemoveSignatures(newRing, NULL);
427 ringAction = countersign;
430 CFReleaseNull(ringPeerIDSet);
432 if (ringAction == countersign) {
433 bool stopCountersign = false;
434 CFIndex peerCount = CFSetGetCount(filteredPeerIDs);
437 // Fix payloads if necessary
438 if (ringIsBackup && SOSPeerInfoHasBackupKey(pi)) {
439 __block bool fixBSKB = false;
440 CFDataRef recoveryKeyData = SOSAccountCopyRecoveryPublic(kCFAllocatorDefault, account, NULL);
441 SOSBackupSliceKeyBagRef currentBSKB = SOSRingCopyBackupSliceKeyBag(newRing, NULL);
443 if(currentBSKB == NULL) {
444 secnotice("ring", "Backup ring contains no BSKB");
448 if(SOSBSKBAllPeersBackupKeysAreInKeyBag(currentBSKB, filteredPeerInfos) == false) {
449 secnotice("ring", "BSKB is missing some backup keys");
453 if(SOSBSKBHasThisRecoveryKey(currentBSKB, recoveryKeyData) == false) {
454 secnotice("ring", "BSKB is missing recovery key");
459 CFErrorRef localError = NULL;
460 CFSetRef viewSet = SOSRingGetBackupViewset(newRing, NULL);
461 secnotice("ring", "Need to fix BSKB - this will prompt a gensign");
463 SOSBackupSliceKeyBagRef bskb = NULL;
464 if(recoveryKeyData) {
465 CFMutableDictionaryRef additionalKeys = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault);
466 CFDictionaryAddValue(additionalKeys, bskbRkbgPrefix, recoveryKeyData);
467 bskb = SOSBackupSliceKeyBagCreateWithAdditionalKeys(kCFAllocatorDefault, filteredPeerInfos, additionalKeys, error);
468 CFReleaseNull(additionalKeys);
470 bskb = SOSBackupSliceKeyBagCreate(kCFAllocatorDefault, filteredPeerInfos, error);
473 if(SOSRingSetBackupKeyBag(newRing, fpi, viewSet, bskb, &localError) == false) {
474 stopCountersign = true;
475 secnotice("ring", "Couldn't fix BSKB (%@)", localError);
477 SOSRingRemoveSignatures(newRing, NULL);
478 SOSRingGenerationSign(newRing, NULL, fpi, error);
479 ringToPush = newRing;
480 CFReleaseNull(localError);
483 CFReleaseNull(recoveryKeyData);
484 CFReleaseNull(currentBSKB);
488 if(stopCountersign) {
490 } else if (SOSRingPeerTrusted(newRing, fpi, NULL)) {
491 secnotice("ring", "Already concur with newRing");
494 CFErrorRef signingError = NULL;
495 if (fpi && SOSRingConcordanceSign(newRing, fpi, &signingError)) {
496 secnotice("ring", "concordance signed");
497 ringToPush = newRing;
500 secnotice("ring", "Failed to concordance sign, error: %@", signingError);
504 CFReleaseSafe(signingError);
510 if (ringAction == accept) {
512 if(!localUpdate) { // processing a remote ring - we accept the new recovery key here
513 if(SOSRingIsEmpty_Internal(newRing)) { // Reset ring will reset the recovery key
514 secnotice("ring", "Reset ring for recovery from remote peer");
515 SOSRecoveryKeyBagRef ringRKBG = SOSRecoveryKeyBagCreateForAccount(kCFAllocatorDefault, (__bridge CFTypeRef)account, SOSRKNullKey(), error);
516 SOSAccountSetRecoveryKeyBagEntry(kCFAllocatorDefault, account, ringRKBG, error);
517 CFReleaseNull(ringRKBG);
518 } else { // normal ring recovery key harvest
519 secnotice("ring", "normal ring recovery key harvest");
520 SOSRecoveryKeyBagRef ringRKBG = SOSRingCopyRecoveryKeyBag(newRing, NULL);
521 SOSAccountSetRecoveryKeyBagEntry(kCFAllocatorDefault, account, ringRKBG, error);
522 CFReleaseNull(ringRKBG);
526 if (pi && SOSRingHasRejection(newRing, peerID)) {
527 SOSRingRemoveRejection(newRing, peerID);
529 [self setRing:newRing ringName:ringName err:error];
530 account.circle_rings_retirements_need_attention = true;
532 ringToPush = newRing;
533 } else if (ringToPush == NULL) {
539 * In the revert section we'll guard the KVS idea of circles by rejecting "bad" new rings
540 * and pushing our current view of the ring (oldRing). We'll only do this if we actually
541 * are a member of oldRing - never for an empty ring.
544 if (ringAction == revert) {
545 if(haveOldRing && SOSRingHasPeerID(oldRing, peerID)) {
546 secnotice("ring", "Rejecting: %@", newRing);
547 secnotice("ring", " RePush: %@", oldRing);
548 ringToPush = oldRing;
550 secnotice("ring", "Rejecting: %@", newRing);
551 secnotice("ring", "Have no old ring - would reset");
555 if (ringToPush != NULL) {
558 } else if(ringIsRecovery) {
561 secnotice("ring", "Pushing:[%s] %@", localRemote, ringToPush);
562 CFDataRef ringData = SOSRingCopyEncodedData(ringToPush, error);
564 success = [circleTransport kvsRingPostRing:SOSRingGetName(ringToPush) ring:ringData err:error];
568 secnotice("ring", "Setting account.key_interests_need_updating to true in handleUpdateRing");
569 account.key_interests_need_updating = true;
570 CFReleaseNull(ringData);
573 CFReleaseNull(filteredPeerIDs);
574 CFReleaseNull(filteredPeerInfos);
575 CFReleaseNull(oldRing);
576 CFReleaseNull(newRing);
577 CFReleaseNull(peers);
578 CFReleaseNull(peerPubKey);
579 CFReleaseNull(peerPrivKey);
583 -(SOSRingRef) copyRing:(CFStringRef)ringName err:(CFErrorRef *)error
585 CFMutableDictionaryRef rings = [self getRings:error];
586 require_action_quiet(rings, errOut, SOSCreateError(kSOSErrorNoRing, CFSTR("No Rings found"), NULL, error));
587 CFTypeRef ringder = CFDictionaryGetValue(rings, ringName);
588 require_action_quiet(ringder, errOut, SOSCreateErrorWithFormat(kSOSErrorNoRing, NULL, error, NULL, CFSTR("No Ring found %@"), ringName));
589 SOSRingRef ring = SOSRingCreateFromData(NULL, ringder);
590 return (SOSRingRef) ring;
596 -(CFMutableDictionaryRef) getRings:(CFErrorRef *)error
598 CFMutableDictionaryRef rings = (__bridge CFMutableDictionaryRef) [self.expansion objectForKey:kSOSRingKey];
600 [self addRingDictionary];
601 rings = [self getRings:error];
607 -(bool) resetRing:(SOSAccount*)account ringName:(CFStringRef) ringName err:(CFErrorRef *)error
611 SOSRingRef ring = [self copyRing:ringName err:error];
612 SOSRingRef newring = SOSRingCreate(ringName, NULL, SOSRingGetType(ring), error);
613 SOSRingGenerationCreateWithBaseline(newring, ring);
614 SOSBackupRingSetViews(newring, self.fullPeerInfo, SOSBackupRingGetViews(ring, NULL), error);
615 require_quiet(newring, errOut);
617 retval = SOSAccountUpdateRing(account, newring, error);
620 CFReleaseNull(newring);