SOSRingConcordanceTrust.c   [plain text]


//
//  SOSRingConcordanceTrust.c
//  sec
//
//  Created by Richard Murphy on 3/15/15.
//
//

#include <AssertMacros.h>

#include "keychain/SecureObjectSync/SOSInternal.h"
#include "keychain/SecureObjectSync/SOSPeer.h"
#include "keychain/SecureObjectSync/SOSPeerInfoInternal.h"
#include "keychain/SecureObjectSync/SOSPeerInfoCollections.h"
#include "keychain/SecureObjectSync/SOSCircle.h"
#include <Security/SecFramework.h>

#include <Security/SecKey.h>
#include <Security/SecKeyPriv.h>
#include <CoreFoundation/CoreFoundation.h>

#include <utilities/SecCFWrappers.h>

//#include "ckdUtilities.h"

#include <corecrypto/ccder.h>
#include <corecrypto/ccdigest.h>
#include <corecrypto/ccsha2.h>


#include <utilities/der_plist.h>
#include <utilities/der_plist_internal.h>
#include <corecrypto/ccder.h>
#include <utilities/der_date.h>

#include <stdlib.h>

#include "SOSRing.h"
#include "SOSRingUtils.h"

static inline CFDictionaryRef SOSPeerInfoDictionaryCreate(CFSetRef peers) {
    size_t n = CFSetGetCount(peers);
    SOSPeerInfoRef  peerInfos[n];
    CFStringRef     peerIDs[n];
    CFSetGetValues(peers, (const void **) peerInfos);
    for(size_t i = 0; i < n; i++) peerIDs[i] = SOSPeerInfoGetPeerID(peerInfos[i]);
    return CFDictionaryCreate(NULL, (const void **)peerIDs, (const void **)peerInfos, n, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
}

static inline SOSConcordanceStatus CheckPeerStatus(CFStringRef peerID, SOSPeerInfoRef peer, SOSRingRef ring, SecKeyRef userPub, CFErrorRef *error) {
    SOSConcordanceStatus result = kSOSConcordanceNoPeer;
    SecKeyRef pubKey = NULL;

    require_action_quiet(peer, exit, result = kSOSConcordanceNoPeer);
    pubKey = SOSPeerInfoCopyPubKey(peer, error);
    require_quiet(pubKey, exit);
    require_action_quiet(SOSRingHasPeerID(ring, peerID), exit, result = kSOSConcordanceNoPeer);
    require_action_quiet(SOSPeerInfoApplicationVerify(peer, userPub, NULL), exit, result = kSOSConcordanceNoPeer);
    require_action_quiet(SOSRingVerifySignatureExists(ring, pubKey, error), exit, result = kSOSConcordanceNoPeerSig);
    require_action_quiet(SOSRingVerify(ring, pubKey, error), exit, result = kSOSConcordanceBadPeerSig);

    result = kSOSConcordanceTrusted;

exit:
    CFReleaseNull(pubKey);
    return result;
}

static inline SOSConcordanceStatus CombineStatus(SOSConcordanceStatus status1, SOSConcordanceStatus status2)
{
    if (status1 == kSOSConcordanceTrusted || status2 == kSOSConcordanceTrusted)
        return kSOSConcordanceTrusted;

    if (status1 == kSOSConcordanceBadPeerSig || status2 == kSOSConcordanceBadPeerSig)
        return kSOSConcordanceBadPeerSig;

    if (status1 == kSOSConcordanceNoPeerSig || status2 == kSOSConcordanceNoPeerSig)
        return kSOSConcordanceNoPeerSig;

    return status1;
}

SOSConcordanceStatus GetSignersStatus(CFSetRef peers, SOSRingRef signersRing, SOSRingRef statusRing,
                                                    SecKeyRef userPubkey, CFStringRef excludePeerID, CFErrorRef *error) {
    CFDictionaryRef ringPeerInfos = SOSPeerInfoDictionaryCreate(peers);
    __block SOSConcordanceStatus status = kSOSConcordanceNoPeer;
    SOSRingForEachPeerID(signersRing, ^(CFStringRef peerID) {
        SOSPeerInfoRef pi = (SOSPeerInfoRef) CFDictionaryGetValue(ringPeerInfos, peerID);
        SOSConcordanceStatus peerStatus = CheckPeerStatus(peerID, pi, statusRing, userPubkey, error);

        secnotice("ring", "concordance-signer-status: %@ -> %d", peerID, peerStatus);

        if (peerStatus == kSOSConcordanceNoPeerSig &&
            (CFEqualSafe(SOSPeerInfoGetPeerID(pi), excludePeerID) || SOSPeerInfoIsCloudIdentity(pi)))
            peerStatus = kSOSConcordanceNoPeer;

        status = CombineStatus(status, peerStatus);
    });

    return status;
}


SOSConcordanceStatus GetSignersStatus_Transitive(CFSetRef peers, SOSRingRef signersRing, SOSRingRef statusRing,
                                                 SecKeyRef userPubkey, CFStringRef excludePeerID, CFErrorRef *error) {
    __block SOSConcordanceStatus status = kSOSConcordanceNoPeer;
    
    CFSetForEach(peers, ^(const void *value) {
        SOSPeerInfoRef pi = (SOSPeerInfoRef) value;
        CFStringRef peerID = SOSPeerInfoGetPeerID(pi);
        if(SOSRingHasPeerWithID(statusRing, peerID, NULL)) {
            SOSConcordanceStatus peerStatus = CheckPeerStatus(peerID, pi, statusRing, userPubkey, error);
            
            if (peerStatus == kSOSConcordanceNoPeerSig &&
                (CFEqualSafe(SOSPeerInfoGetPeerID(pi), excludePeerID) || SOSPeerInfoIsCloudIdentity(pi)))
                peerStatus = kSOSConcordanceNoPeer;
            
            status = CombineStatus(status, peerStatus);
        }
    });
    
    return status;
}


SOSConcordanceStatus SOSRingUserKeyConcordanceTrust(SOSFullPeerInfoRef me, CFSetRef peers, SOSRingRef knownRing, SOSRingRef proposedRing,
                                                    SecKeyRef knownPubkey, SecKeyRef userPubkey,
                                                    CFStringRef excludePeerID, CFErrorRef *error) {
    if(userPubkey == NULL) {
        SOSCreateError(kSOSErrorPublicKeyAbsent, CFSTR("Concordance with no public key"), NULL, error);
        return kSOSConcordanceNoUserKey;
    }

    if (SOSRingIsEmpty_Internal(proposedRing)) {
        return kSOSConcordanceTrusted;
    }

    if(!SOSRingVerifySignatureExists(proposedRing, userPubkey, error)) {
        SOSCreateError(kSOSErrorBadSignature, CFSTR("No public signature"), (error != NULL) ? *error : NULL, error);
        return kSOSConcordanceNoUserSig;
    }

    if(!SOSRingVerify(proposedRing, userPubkey, error)) {
        SOSCreateError(kSOSErrorBadSignature, CFSTR("Bad public signature"), (error != NULL) ? *error : NULL, error);
        return kSOSConcordanceBadUserSig;
    }

    if (SOSRingIsEmpty_Internal(knownRing) || SOSRingIsOffering_Internal(proposedRing)) {
        return GetSignersStatus(peers, proposedRing, proposedRing, userPubkey, NULL, error);
    }

    if(SOSRingIsOlderGeneration(proposedRing, knownRing)) {
        SOSCreateError(kSOSErrorReplay, CFSTR("Bad generation"), NULL, error);
        return kSOSConcordanceGenOld;
    }

    if(knownPubkey == NULL) knownPubkey = userPubkey;
    if(!SOSRingVerify(knownRing, knownPubkey, error)) knownPubkey = userPubkey;
    return GetSignersStatus(peers, knownRing, proposedRing, knownPubkey, CFSTR("novalue"), error);
}


SOSConcordanceStatus SOSRingPeerKeyConcordanceTrust(SOSFullPeerInfoRef me, CFSetRef peers, SOSRingRef knownRing, SOSRingRef proposedRing,
                                                    __unused SecKeyRef knownPubkey, SecKeyRef userPubkey,
                                                    CFStringRef excludePeerID, CFErrorRef *error) {
    if(userPubkey == NULL) {
        SOSCreateError(kSOSErrorPublicKeyAbsent, CFSTR("Concordance with no public key - need to validate application"), NULL, error);
        return kSOSConcordanceNoUserKey;
    }

    if (SOSRingIsEmpty_Internal(proposedRing)) {
        secnotice("ring", "ring empty -> trusted");
        return kSOSConcordanceTrusted;
    }

    if (SOSRingIsEmpty_Internal(knownRing) || SOSRingIsOffering_Internal(proposedRing)) {
        return GetSignersStatus(peers, proposedRing, proposedRing, userPubkey, NULL, error);
    }

    if(SOSRingIsOlderGeneration(proposedRing, knownRing)) {
        SOSCreateError(kSOSErrorReplay, CFSTR("Bad generation"), NULL, error);
        return kSOSConcordanceGenOld;
    }
    return GetSignersStatus(peers, knownRing, proposedRing, userPubkey, excludePeerID, error);
}