2 //  SOSAccountRingUpdate.c 
   9 #include "SOSAccountPriv.h" 
  10 #include <Security/SecureObjectSync/SOSTransportCircle.h> 
  11 #include <Security/SecureObjectSync/SOSTransport.h> 
  12 #include <Security/SecureObjectSync/SOSViews.h> 
  13 #include <Security/SecureObjectSync/SOSRing.h> 
  14 #include <Security/SecureObjectSync/SOSRingUtils.h> 
  15 #include <Security/SecureObjectSync/SOSPeerInfoCollections.h> 
  18 static inline bool SOSAccountHasLeft(SOSAccountRef account
) { 
  19     switch(account
->departure_code
) { 
  20         case kSOSWithdrewMembership
: /* Fallthrough */ 
  21         case kSOSMembershipRevoked
: /* Fallthrough */ 
  22         case kSOSLeftUntrustedCircle
: 
  24         case kSOSNeverAppliedToCircle
: /* Fallthrough */ 
  25         case kSOSNeverLeftCircle
: /* Fallthrough */ 
  32 static const char * __unused concordstring
[] = { 
  33     "kSOSConcordanceTrusted", 
  34     "kSOSConcordanceGenOld",     // kSOSErrorReplay 
  35     "kSOSConcordanceNoUserSig",  // kSOSErrorBadSignature 
  36     "kSOSConcordanceNoUserKey",  // kSOSErrorNoKey 
  37     "kSOSConcordanceNoPeer",     // kSOSErrorPeerNotFound 
  38     "kSOSConcordanceBadUserSig", // kSOSErrorBadSignature 
  39     "kSOSConcordanceBadPeerSig", // kSOSErrorBadSignature 
  40     "kSOSConcordanceNoPeerSig", 
  41     "kSOSConcordanceWeSigned", 
  42     "kSOSConcordanceInvalidMembership", 
  43     "kSOSConcordanceMissingMe", 
  44     "kSOSConcordanceImNotWorthy", 
  48 static bool SOSAccountIsPeerRetired(SOSAccountRef account
, CFSetRef peers
){ 
  49     CFMutableArrayRef peerInfos 
= CFArrayCreateMutableForCFTypes(kCFAllocatorDefault
); 
  52     CFSetForEach(peers
, ^(const void *value
) { 
  53         SOSPeerInfoRef peer 
= (SOSPeerInfoRef
)value
; 
  54         if(SOSPeerInfoIsRetirementTicket(peer
)) 
  55             CFArrayAppendValue(peerInfos
, peer
); 
  57     if(CFArrayGetCount(peerInfos
) > 0){ 
  58         if(!SOSAccountRemoveBackupPeers(account
, peerInfos
, NULL
)) 
  59             secerror("Could not remove peers: %@, from the backup", peerInfos
); 
  66     CFReleaseNull(peerInfos
); 
  71 static bool SOSAccountBackupSliceKeyBagNeedsFix(SOSAccountRef account
, SOSBackupSliceKeyBagRef bskb
) { 
  73     if (SOSBSKBIsDirect(bskb
) || account
->backup_key 
== NULL
) 
  76     CFSetRef peers 
= SOSBSKBGetPeers(bskb
); 
  78     /* first scan for retired peers, and kick'em out!*/ 
  79     SOSAccountIsPeerRetired(account
, peers
); 
  81     SOSPeerInfoRef myPeer 
= SOSAccountGetMyPeerInfo(account
); 
  85         SOSPeerInfoRef meInBag 
= (SOSPeerInfoRef
) CFSetGetValue(peers
, myPeer
); 
  86         CFDataRef myBK 
= SOSPeerInfoCopyBackupKey(myPeer
); 
  87         CFDataRef meInBagBK 
= SOSPeerInfoCopyBackupKey(meInBag
); 
  88         needsFix 
= !(meInBag 
&& CFEqualSafe(myBK
, 
  91         CFReleaseNull(meInBagBK
); 
  94     CFDataRef rkbg 
= SOSAccountCopyRecoveryPublic(kCFAllocatorDefault
, account
, NULL
); 
  95     if(rkbg
) needsFix 
|= !SOSBKSBPrefixedKeyIsInKeyBag(bskb
, bskbRkbgPrefix
, rkbg
); 
  96     else needsFix 
|= SOSBSKBHasRecoveryKey(bskb
); // if we don't have a recovery key - the bskb shouldn't 
 112 static const char * __unused actionstring
[] = { 
 113     "accept", "countersign", "leave", "revert", "modify", "ignore", 
 116 bool SOSAccountHandleUpdateRing(SOSAccountRef account
, SOSRingRef prospectiveRing
, bool writeUpdate
, CFErrorRef 
*error
) { 
 118     bool haveOldRing 
= true; 
 119     const char * __unused localRemote 
= writeUpdate 
? "local": "remote"; 
 120     SOSFullPeerInfoRef fpi 
= account
->my_identity
; 
 121     SOSPeerInfoRef     pi 
= SOSFullPeerInfoGetPeerInfo(fpi
); 
 122     CFStringRef        peerID 
= SOSPeerInfoGetPeerID(pi
); 
 123     bool               peerActive 
= (fpi 
&& pi 
&& peerID 
&& SOSAccountIsInCircle(account
, NULL
)); 
 126     secdebug("ringSigning", "start:[%s] %@", localRemote
, prospectiveRing
); 
 128     require_quiet(SOSAccountHasPublicKey(account
, error
), errOut
); 
 130     require_action_quiet(prospectiveRing
, errOut
, 
 131                          SOSCreateError(kSOSErrorIncompatibleCircle
, CFSTR("No Ring to work with"), NULL
, error
)); 
 133     require_action_quiet(SOSRingIsStable(prospectiveRing
), errOut
, SOSCreateError(kSOSErrorIncompatibleCircle
, CFSTR("You give rings a bad name"), NULL
, error
)); 
 135     // We should at least have a sane ring system in the account object 
 136     require_quiet(SOSAccountCheckForRings(account
, error
), errOut
); 
 138     CFStringRef ringName 
= SOSRingGetName(prospectiveRing
); 
 139     SOSRingRef oldRing 
= SOSAccountCopyRing(account
, ringName
, NULL
); 
 141     SOSTransportCircleRef transport 
= account
->circle_transport
; 
 143     SOSRingRef newRing 
= CFRetainSafe(prospectiveRing
); // TODO:  SOSAccountCloneRingWithRetirement(account, prospectiveRing, error); 
 145     ringAction_t ringAction 
= ignore
; 
 147     bool userTrustedoldRing 
= true; 
 149     SOSCircleRef circle 
= SOSAccountGetCircle(account
, NULL
); 
 150     CFSetRef peers 
= SOSCircleCopyPeers(circle
, kCFAllocatorDefault
); 
 152     SecKeyRef oldKey 
= account
->user_public
; 
 155     // for now user keys aren't explored. 
 156     // we should ask the ring if it cares about it and then do the magic to find the right user keys. 
 157     SecKeyRef oldKey 
= account
->user_public
; 
 159     if(SOSRingPKTrusted(oldRing
, account
->user_public
, NULL
)) oldKey 
= account
->user_public
; 
 160     else if(account
->previous_public 
&& SOSRingPKTrusted(oldRing
, account
->previous_public
, NULL
)) oldKey 
= account
->previous_public
; 
 161     bool userTrustedoldRing 
= (oldKey 
!= NULL
) && haveOldRing
; 
 166         oldRing 
= CFRetainSafe(newRing
); 
 169     SOSConcordanceStatus concstat 
= SOSRingConcordanceTrust(fpi
, peers
, oldRing
, newRing
, oldKey
, account
->user_public
, peerID
, error
); 
 170     CFReleaseNull(peers
); 
 172     CFStringRef concStr 
= NULL
; 
 174         case kSOSConcordanceTrusted
: 
 175             ringAction 
= countersign
; 
 176             concStr 
= CFSTR("Trusted"); 
 178         case kSOSConcordanceGenOld
: 
 179             ringAction 
= userTrustedoldRing 
? revert 
: ignore
; 
 180             concStr 
= CFSTR("Generation Old"); 
 182         case kSOSConcordanceBadUserSig
: 
 183         case kSOSConcordanceBadPeerSig
: 
 184             ringAction 
= userTrustedoldRing 
? revert 
: accept
; 
 185             concStr 
= CFSTR("Bad Signature"); 
 187         case kSOSConcordanceNoUserSig
: 
 188             ringAction 
= userTrustedoldRing 
? revert 
: accept
; 
 189             concStr 
= CFSTR("No User Signature"); 
 191         case kSOSConcordanceNoPeerSig
: 
 192             ringAction 
= accept
; // We might like this one eventually but don't countersign. 
 193             concStr 
= CFSTR("No trusted peer signature"); 
 194             secnotice("signing", "##### No trusted peer signature found, accepting hoping for concordance later %@", newRing
); 
 196         case kSOSConcordanceNoPeer
: 
 198             concStr 
= CFSTR("No trusted peer left"); 
 200         case kSOSConcordanceNoUserKey
: 
 201             secerror("##### No User Public Key Available, this shouldn't ever happen!!!"); 
 205         case kSOSConcordanceMissingMe
: 
 206         case kSOSConcordanceImNotWorthy
: 
 208             concStr 
= CFSTR("Incorrect membership for me"); 
 210         case kSOSConcordanceInvalidMembership
: 
 211             ringAction 
= userTrustedoldRing 
? revert 
: ignore
; 
 212             concStr 
= CFSTR("Invalid Ring Membership"); 
 215             secerror("##### Bad Error Return from ConcordanceTrust"); 
 222     secdebug("ringSigning", "Decided on action [%s] based on concordance state [%s] and [%s] circle.", actionstring
[ringAction
], concordstring
[concstat
], userTrustedoldRing 
? "trusted" : "untrusted"); 
 224     SOSRingRef ringToPush 
= NULL
; 
 225     bool iWasInOldRing 
= peerID 
&& SOSRingHasPeerID(oldRing
, peerID
); 
 226     bool iAmInNewRing 
= peerID 
&& SOSRingHasPeerID(newRing
, peerID
); 
 227     bool ringIsBackup 
= SOSRingGetType(newRing
) == kSOSRingBackup
; 
 228     bool ringIsRecovery 
= SOSRingGetType(newRing
) == kSOSRingRecovery
; 
 230     if (ringIsBackup 
&& peerActive
) { 
 231         if (ringAction 
== accept 
|| ringAction 
== countersign
) { 
 232             CFErrorRef localError 
= NULL
; 
 233             SOSBackupSliceKeyBagRef bskb 
= SOSRingCopyBackupSliceKeyBag(newRing
, &localError
); 
 236                 secnotice("ringSigning", "Backup ring with no backup slice keybag (%@)", localError
); 
 237             } else if (SOSAccountBackupSliceKeyBagNeedsFix(account
, bskb
)) { 
 240             CFReleaseSafe(localError
); 
 244         if (ringAction 
== modify
) { 
 245             CFErrorRef updateError 
= NULL
; 
 246             SOSAccountSetRing(account
, newRing
, ringName
, error
); 
 248             if(SOSAccountUpdateOurPeerInBackup(account
, newRing
, &updateError
)) { 
 249                 secdebug("signing", "Modified backup ring to include us"); 
 251                 secerror("Could not add ourselves to the backup: (%@)", updateError
); 
 253             CFReleaseSafe(updateError
); 
 255             // Fall through to normal modify handling. 
 259     if (ringIsRecovery 
&& peerActive 
&& (ringAction 
== modify
)) { 
 260         SOSAccountSetRing(account
, newRing
, ringName
, error
); 
 264     if (ringAction 
== modify
) { 
 268     if (ringAction 
== leave
) { 
 270             if (sosAccountLeaveRing(account
, newRing
, error
)) { 
 271                 ringToPush 
= newRing
; 
 273                 secdebug("ringSigning", "Can't leave ring %@", oldRing
); 
 278             // We are not in this ring, but we need to update account with it, since we got it from cloud 
 283     if (ringAction 
== countersign
) { 
 285             if (SOSRingPeerTrusted(newRing
, fpi
, NULL
)) { 
 286                 secdebug("ringSigning", "Already concur with: %@", newRing
); 
 288                 CFErrorRef signingError 
= NULL
; 
 290                 if (fpi 
&& SOSRingConcordanceSign(newRing
, fpi
, &signingError
)) { 
 291                     ringToPush 
= newRing
; 
 293                     secerror("Failed to concordance sign, error: %@  Old: %@ New: %@", signingError
, oldRing
, newRing
); 
 296                 CFReleaseSafe(signingError
); 
 299             secdebug("ringSigning", "Not countersigning, not in ring: %@", newRing
); 
 304     if (ringAction 
== accept
) { 
 305         if (iWasInOldRing 
&& !iAmInNewRing
) { 
 307             //  Don't destroy evidence of other code determining reason for leaving. 
 308             //if(!SOSAccountHasLeft(account)) account->departure_code = kSOSMembershipRevoked; 
 309             // TODO: LeaveReason for rings 
 312         if (pi 
&& SOSRingHasRejection(newRing
, peerID
)) { 
 313             // TODO: ReasonForLeaving for rings 
 314             SOSRingRemoveRejection(newRing
, peerID
); 
 317         SOSAccountSetRing(account
, newRing
, ringName
, error
); 
 319         if (pi 
&& account
->user_public_trusted
 
 320             && SOSRingHasApplicant(oldRing
, peerID
) 
 321             && SOSRingCountPeers(newRing
) > 0 
 322             && !iAmInNewRing 
&& !SOSRingHasApplicant(newRing
, peerID
)) { 
 323             // We weren't rejected (above would have set me to NULL. 
 324             // We were applying and we weren't accepted. 
 325             // Our application is declared lost, let us reapply. 
 327             if (SOSRingApply(newRing
, account
->user_public
, fpi
, NULL
)) 
 328                 if(peerActive
) writeUpdate 
= true; 
 331         if (pi 
&& SOSRingHasPeerID(oldRing
, peerID
)) { 
 332             SOSAccountCleanupRetirementTickets(account
, RETIREMENT_FINALIZATION_SECONDS
, NULL
); 
 336         account
->circle_rings_retirements_need_attention 
= true; 
 339             ringToPush 
= newRing
; 
 340         account
->key_interests_need_updating 
= true; 
 344      * In the revert section we'll guard the KVS idea of circles by rejecting "bad" new rings 
 345      * and pushing our current view of the ring (oldRing).  We'll only do this if we actually 
 346      * are a member of oldRing - never for an empty ring. 
 349     if (ringAction 
== revert
) { 
 350         if(haveOldRing 
&& peerActive 
&& SOSRingHasPeerID(oldRing
, peerID
)) { 
 351             secdebug("ringSigning", "%@, Rejecting: %@ re-publishing %@", concStr
, newRing
, oldRing
); 
 352             ringToPush 
= oldRing
; 
 354             secdebug("ringSigning", "%@, Rejecting: %@ Have no old circle - would reset", concStr
, newRing
); 
 359     if (ringToPush 
!= NULL
) { 
 360         secdebug("ringSigning", "Pushing:[%s] %@", localRemote
, ringToPush
); 
 361         CFDataRef ringData 
= SOSRingCopyEncodedData(ringToPush
, error
); 
 363             success 
&= SOSTransportCircleRingPostRing(transport
, SOSRingGetName(ringToPush
), ringData
, error
); 
 367         CFReleaseNull(ringData
); 
 369     CFReleaseNull(oldRing
); 
 370     CFReleaseSafe(newRing
);