]> git.saurik.com Git - apple/security.git/blobdiff - OSX/sec/SOSCircle/SecureObjectSync/SOSAccountRingUpdate.c
Security-57336.1.9.tar.gz
[apple/security.git] / OSX / sec / SOSCircle / SecureObjectSync / SOSAccountRingUpdate.c
diff --git a/OSX/sec/SOSCircle/SecureObjectSync/SOSAccountRingUpdate.c b/OSX/sec/SOSCircle/SecureObjectSync/SOSAccountRingUpdate.c
new file mode 100644 (file)
index 0000000..6579872
--- /dev/null
@@ -0,0 +1,370 @@
+//
+//  SOSAccountRingUpdate.c
+//  sec
+//
+//
+
+#include <stdio.h>
+
+#include "SOSAccountPriv.h"
+#include <Security/SecureObjectSync/SOSTransportCircle.h>
+#include <Security/SecureObjectSync/SOSTransport.h>
+#include <Security/SecureObjectSync/SOSViews.h>
+#include <Security/SecureObjectSync/SOSRing.h>
+#include <Security/SecureObjectSync/SOSRingUtils.h>
+#include <Security/SecureObjectSync/SOSPeerInfoCollections.h>
+
+#if 0
+static inline bool SOSAccountHasLeft(SOSAccountRef account) {
+    switch(account->departure_code) {
+        case kSOSWithdrewMembership: /* Fallthrough */
+        case kSOSMembershipRevoked: /* Fallthrough */
+        case kSOSLeftUntrustedCircle:
+            return true;
+        case kSOSNeverAppliedToCircle: /* Fallthrough */
+        case kSOSNeverLeftCircle: /* Fallthrough */
+        default:
+            return false;
+    }
+}
+#endif
+
+static const char *concordstring[] = {
+    "kSOSConcordanceTrusted",
+    "kSOSConcordanceGenOld",     // kSOSErrorReplay
+    "kSOSConcordanceNoUserSig",  // kSOSErrorBadSignature
+    "kSOSConcordanceNoUserKey",  // kSOSErrorNoKey
+    "kSOSConcordanceNoPeer",     // kSOSErrorPeerNotFound
+    "kSOSConcordanceBadUserSig", // kSOSErrorBadSignature
+    "kSOSConcordanceBadPeerSig", // kSOSErrorBadSignature
+    "kSOSConcordanceNoPeerSig",
+    "kSOSConcordanceWeSigned",
+    "kSOSConcordanceInvalidMembership",
+    "kSOSConcordanceMissingMe",
+    "kSOSConcordanceImNotWorthy",
+};
+
+
+static bool SOSAccountIsPeerRetired(SOSAccountRef account, CFSetRef peers){
+    CFMutableArrayRef peerIDs = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault);
+    bool result = false;
+    
+    CFSetForEach(peers, ^(const void *value) {
+        SOSPeerInfoRef peer = (SOSPeerInfoRef)value;
+        if(SOSPeerInfoIsRetirementTicket(peer))
+            CFArrayAppendValue(peerIDs, peer);
+    });
+    if(CFArrayGetCount(peerIDs) > 0){
+        if(!SOSAccountRemoveBackupPeers(account, peerIDs, NULL))
+            secerror("Could not remove peers: %@, from the backup", peerIDs);
+        else
+            return true;
+    }
+    else
+        result = true;
+    
+    return result;
+}
+
+static bool SOSAccountBackupSliceKeyBagNeedsFix(SOSAccountRef account, SOSBackupSliceKeyBagRef bskb) {
+
+    if (SOSBSKBIsDirect(bskb) || account->backup_key == NULL)
+        return false;
+
+    CFSetRef peers = SOSBSKBGetPeers(bskb);
+    
+    /* first scan for retired peers, and kick'em out!*/
+    SOSAccountIsPeerRetired(account, peers);
+    
+    SOSPeerInfoRef myPeer = SOSAccountGetMyPeerInfo(account);
+    bool needsFix = true;
+
+    if (myPeer) {
+        SOSPeerInfoRef meInBag = (SOSPeerInfoRef) CFSetGetValue(peers, myPeer);
+        CFDataRef myBK = SOSPeerInfoCopyBackupKey(myPeer);
+        CFDataRef meInBagBK = SOSPeerInfoCopyBackupKey(meInBag);
+        needsFix = !(meInBag && CFEqualSafe(myBK,
+                                            meInBagBK));
+        CFReleaseNull(myBK);
+        CFReleaseNull(meInBagBK);
+    }
+
+    return needsFix;
+}
+
+
+bool SOSAccountHandleUpdateRing(SOSAccountRef account, SOSRingRef prospectiveRing, bool writeUpdate, CFErrorRef *error) {
+    bool success = true;
+    bool haveOldRing = true;
+    const char *localRemote = writeUpdate ? "local": "remote";
+    SOSFullPeerInfoRef fpi = account->my_identity;
+    SOSPeerInfoRef     pi = SOSFullPeerInfoGetPeerInfo(fpi);
+    CFStringRef        peerID = SOSPeerInfoGetPeerID(pi);
+    bool               neverWrite = !(fpi && pi && peerID && SOSAccountIsInCircle(account, NULL));
+
+    secinfo("signing", "start:[%s] %@", localRemote, prospectiveRing);
+
+    require_action_quiet(!(writeUpdate && neverWrite), errOut, SOSCreateError(kSOSErrorNotReady, CFSTR("Can't update from local if FullPeerInfo not present"), NULL, error));
+
+    require_quiet(SOSAccountHasPublicKey(account, error), errOut);
+
+    require_action_quiet(prospectiveRing, errOut,
+                         SOSCreateError(kSOSErrorIncompatibleCircle, CFSTR("No Ring to work with"), NULL, error));
+
+    // We should at least have a sane ring system in the account object
+    require_quiet(SOSAccountCheckForRings(account, error), errOut);
+
+    CFStringRef ringName = SOSRingGetName(prospectiveRing);
+    SOSRingRef oldRing = SOSAccountGetRing(account, ringName, NULL);
+
+    SOSTransportCircleRef transport = account->circle_transport;
+
+    // SOSAccountScanForRetired(account, prospectiveRing, error);
+    
+    SOSRingRef newRing = CFRetainSafe(prospectiveRing); // TODO:  SOSAccountCloneRingWithRetirement(account, prospectiveRing, error);
+
+    typedef enum {
+        accept,
+        countersign,
+        leave,
+        revert,
+        modify,
+        ignore
+    } ringAction_t;
+
+    static const char *actionstring[] = {
+        "accept", "countersign", "leave", "revert", "modify", "ignore",
+    };
+
+    ringAction_t ringAction = ignore;
+    enum DepartureReason leaveReason = kSOSNeverLeftCircle;
+
+    bool userTrustedoldRing = true;
+
+    SOSCircleRef circle = SOSAccountGetCircle(account, NULL);
+    CFSetRef peers = SOSCircleCopyPeers(circle, kCFAllocatorDefault);
+
+    SecKeyRef oldKey = account->user_public;
+
+#if 0
+    // for now user keys aren't explored.
+    // we should ask the ring if it cares about it and then do the magic to find the right user keys.
+    SecKeyRef oldKey = account->user_public;
+
+    if(SOSRingPKTrusted(oldRing, account->user_public, NULL)) oldKey = account->user_public;
+    else if(account->previous_public && SOSRingPKTrusted(oldRing, account->previous_public, NULL)) oldKey = account->previous_public;
+    bool userTrustedoldRing = (oldKey != NULL) && haveOldRing;
+
+#endif
+
+    if (!oldRing) {
+        oldRing = newRing;
+    }
+
+    SOSConcordanceStatus concstat = SOSRingConcordanceTrust(fpi, peers, oldRing, newRing, oldKey, account->user_public, peerID, error);
+    CFReleaseNull(peers);
+
+    CFStringRef concStr = NULL;
+    switch(concstat) {
+        case kSOSConcordanceTrusted:
+            ringAction = countersign;
+            concStr = CFSTR("Trusted");
+            break;
+        case kSOSConcordanceGenOld:
+            ringAction = userTrustedoldRing ? revert : ignore;
+            concStr = CFSTR("Generation Old");
+            break;
+        case kSOSConcordanceBadUserSig:
+        case kSOSConcordanceBadPeerSig:
+            ringAction = userTrustedoldRing ? revert : accept;
+            concStr = CFSTR("Bad Signature");
+            break;
+        case kSOSConcordanceNoUserSig:
+            ringAction = userTrustedoldRing ? revert : accept;
+            concStr = CFSTR("No User Signature");
+            break;
+        case kSOSConcordanceNoPeerSig:
+            ringAction = accept; // We might like this one eventually but don't countersign.
+            concStr = CFSTR("No trusted peer signature");
+            secerror("##### No trusted peer signature found, accepting hoping for concordance later %@", newRing);
+            break;
+        case kSOSConcordanceNoPeer:
+            ringAction = leave;
+            leaveReason = kSOSLeftUntrustedCircle;
+            concStr = CFSTR("No trusted peer left");
+            break;
+        case kSOSConcordanceNoUserKey:
+            secerror("##### No User Public Key Available, this shouldn't ever happen!!!");
+            ringAction = ignore;
+            break;
+            
+        case kSOSConcordanceMissingMe:
+        case kSOSConcordanceImNotWorthy:
+            ringAction = modify;
+            concStr = CFSTR("Incorrect membership for me");
+            break;
+        case kSOSConcordanceInvalidMembership:
+            ringAction = userTrustedoldRing ? revert : ignore;
+            concStr = CFSTR("Invalid Ring Membership");
+            break;
+        default:
+            secerror("##### Bad Error Return from ConcordanceTrust");
+            ringAction = ignore;
+            break;
+    }
+
+    secnotice("signing", "Decided on action [%s] based on concordance state [%s] and [%s] circle.", actionstring[ringAction], concordstring[concstat], userTrustedoldRing ? "trusted" : "untrusted");
+
+    SOSRingRef ringToPush = NULL;
+    bool iWasInOldRing = peerID && SOSRingHasPeerID(oldRing, peerID);
+    bool iAmInNewRing = peerID && SOSRingHasPeerID(newRing, peerID);
+    bool ringIsBackup = SOSRingGetType(newRing) == kSOSRingBackup;
+
+    if (ringIsBackup && !neverWrite) {
+        if (ringAction == accept || ringAction == countersign) {
+            CFErrorRef localError = NULL;
+            SOSBackupSliceKeyBagRef bskb = SOSRingCopyBackupSliceKeyBag(newRing, &localError);
+
+            if(!bskb) {
+                secnotice("signing", "Backup ring with no backup slice keybag (%@)", localError);
+            } else if (SOSAccountBackupSliceKeyBagNeedsFix(account, bskb)) {
+                ringAction = modify;
+            }
+            CFReleaseSafe(localError);
+            CFReleaseSafe(bskb);
+        }
+
+        if (ringAction == modify) {
+            CFErrorRef updateError = NULL;
+            CFDictionarySetValue(account->trusted_rings, ringName, newRing);
+
+            if(SOSAccountUpdateOurPeerInBackup(account, newRing, &updateError)) {
+                secdebug("signing", "Modified backup ring to include us");
+            } else {
+                secerror("Could not add ourselves to the backup: (%@)", updateError);
+            }
+            CFReleaseSafe(updateError);
+
+            // Fall through to normal modify handling.
+        }
+    }
+
+    if (ringAction == modify) {
+        ringAction = ignore;
+    }
+
+    if (ringAction == leave) {
+        if (iWasInOldRing) {
+            if (sosAccountLeaveRing(account, newRing, error)) {
+                ringToPush = newRing;
+            } else {
+                secnotice("signing", "Can't leave ring %@", oldRing);
+                success = false;
+            }
+            account->departure_code = leaveReason;
+            ringAction = accept;
+        } else {
+            // We are not in this ring, but we need to update account with it, since we got it from cloud
+            secnotice("signing", "We are not in this ring, but we need to update account with it");
+            ringAction = accept;
+        }
+    }
+
+    if (ringAction == countersign) {
+        if (iAmInNewRing) {
+            if (SOSRingPeerTrusted(newRing, fpi, NULL)) {
+                secinfo("signing", "Already concur with: %@", newRing);
+            } else {
+                CFErrorRef signingError = NULL;
+
+                if (fpi && SOSRingConcordanceSign(newRing, fpi, &signingError)) {
+                    ringToPush = newRing;
+                    secinfo("signing", "Concurred with: %@", newRing);
+                } else {
+                    secerror("Failed to concurrence sign, error: %@  Old: %@ New: %@", signingError, oldRing, newRing);
+                    success = false;
+                }
+                CFReleaseSafe(signingError);
+            }
+        } else {
+            secnotice("signing", "Not countersigning, not in ring: %@", newRing);
+        }
+        ringAction = accept;
+    }
+
+    if (ringAction == accept) {
+        if (iWasInOldRing && !iAmInNewRing) {
+
+            //  Don't destroy evidence of other code determining reason for leaving.
+            //if(!SOSAccountHasLeft(account)) account->departure_code = kSOSMembershipRevoked;
+            // TODO: LeaveReason for rings
+        }
+
+        if (pi && SOSRingHasRejection(newRing, peerID)) {
+            // TODO: ReasonForLeaving for rings
+            SOSRingRemoveRejection(newRing, peerID);
+        }
+
+        CFRetainSafe(oldRing);
+        CFDictionarySetValue(account->trusted_rings, ringName, newRing);
+        // TODO: Why was this?  SOSAccountSetPreviousPublic(account);
+
+        secnotice("signing", "%@, Accepting ring: %@", concStr, newRing);
+
+        if (pi && account->user_public_trusted
+            && SOSRingHasApplicant(oldRing, peerID)
+            && SOSRingCountPeers(newRing) > 0
+            && !iAmInNewRing && !SOSRingHasApplicant(newRing, peerID)) {
+            // We weren't rejected (above would have set me to NULL.
+            // We were applying and we weren't accepted.
+            // Our application is declared lost, let us reapply.
+
+            if (SOSRingApply(newRing, account->user_public, fpi, NULL))
+                writeUpdate = true;
+        }
+
+        if (pi && SOSRingHasPeerID(oldRing, peerID)) {
+            SOSAccountCleanupRetirementTickets(account, RETIREMENT_FINALIZATION_SECONDS, NULL);
+        }
+
+        CFReleaseNull(oldRing);
+
+        account->circle_rings_retirements_need_attention = true;
+
+        if (writeUpdate && !neverWrite)
+            ringToPush = newRing;
+        SOSUpdateKeyInterest();
+    }
+
+    /*
+     * In the revert section we'll guard the KVS idea of circles by rejecting "bad" new rings
+     * and pushing our current view of the ring (oldRing).  We'll only do this if we actually
+     * are a member of oldRing - never for an empty ring.
+     */
+
+    if (ringAction == revert) {
+        if(haveOldRing && !neverWrite && SOSRingHasPeerID(oldRing, peerID)) {
+            secnotice("signing", "%@, Rejecting: %@ re-publishing %@", concStr, newRing, oldRing);
+            ringToPush = oldRing;
+        } else {
+            secnotice("canary", "%@, Rejecting: %@ Have no old circle - would reset", concStr, newRing);
+        }
+    }
+
+
+    if (ringToPush != NULL) {
+        secnotice("signing", "Pushing:[%s] %@", localRemote, ringToPush);
+        CFDataRef ringData = SOSRingCopyEncodedData(ringToPush, error);
+        if (ringData) {
+            success &= SOSTransportCircleRingPostRing(transport, SOSRingGetName(ringToPush), ringData, error);
+        } else {
+            success = false;
+        }
+        CFReleaseNull(ringData);
+    }
+
+    CFReleaseSafe(newRing);
+    return success;
+errOut:
+    return false;
+}