SOSViews.c   [plain text]


/*
 * Copyright (c) 2015 Apple Inc. All Rights Reserved.
 *
 * @APPLE_LICENSE_HEADER_START@
 *
 * This file contains Original Code and/or Modifications of Original Code
 * as defined in and that are subject to the Apple Public Source License
 * Version 2.0 (the 'License'). You may not use this file except in
 * compliance with the License. Please obtain a copy of the License at
 * http://www.opensource.apple.com/apsl/ and read it before using this
 * file.
 *
 * The Original Code and all software distributed under the License are
 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
 * Please see the License for the specific language governing rights and
 * limitations under the License.
 *
 * @APPLE_LICENSE_HEADER_END@
 */

/*
 * SOSViews.c -  Implementation of views
 */

#include <AssertMacros.h>
#include <TargetConditionals.h>

#include "SOSViews.h"
#include <utilities/SecCFWrappers.h>
#include <utilities/SecCFRelease.h>
#include <utilities/SecXPCError.h>

#include <utilities/SecCFError.h>
#include <utilities/der_set.h>
#include <Security/SecureObjectSync/SOSInternal.h>

#include <Security/SecureObjectSync/SOSPeerInfo.h>
#include <Security/SecureObjectSync/SOSPeerInfoV2.h>
#include <Security/SecureObjectSync/SOSPeerInfoPriv.h>
#include <Security/SecureObjectSync/SOSCloudCircle.h>
#include <Security/SecureObjectSync/SOSAccount.h>
#include <Security/SecureObjectSync/SOSAccountPriv.h>

CFStringRef viewMemError                = CFSTR("Failed to get memory for views in PeerInfo");
CFStringRef viewUnknownError            = CFSTR("Unknown view(%@) (ViewResultCode=%d)");
CFStringRef viewInvalidError            = CFSTR("Peer is invalid for this view(%@) (ViewResultCode=%d)");

// Internal Views:
const CFStringRef kSOSViewKeychainV0_tomb           = CFSTR("KeychainV0-tomb"); // iCloud Keychain backup for v0 peers (no tombstones)
const CFStringRef kSOSViewBackupBagV0_tomb          = CFSTR("BackupBagV0-tomb");     // iCloud Keychain backup bag for v0 peers (no tombstones)
const CFStringRef kSOSViewWiFi_tomb                 = CFSTR("WiFi-tomb");
const CFStringRef kSOSViewAutofillPasswords_tomb    = CFSTR("Passwords-tomb");
const CFStringRef kSOSViewSafariCreditCards_tomb    = CFSTR("CreditCards-tomb");
const CFStringRef kSOSViewiCloudIdentity_tomb       = CFSTR("iCloudIdentity-tomb");
const CFStringRef kSOSViewOtherSyncable_tomb        = CFSTR("OtherSyncable-tomb");

// Views
const CFStringRef kSOSViewKeychainV0            = CFSTR("KeychainV0");      // iCloud Keychain syncing for v0 peers

#undef DOVIEWMACRO
#define DOVIEWMACRO(VIEWNAME, DEFSTRING, CMDSTRING, DEFAULTSETTING, INITIALSYNCSETTING, ALWAYSONSETTING, BACKUPSETTING, V0SETTING) \
const CFStringRef kSOSView##VIEWNAME          = CFSTR(DEFSTRING);
#include "Security/SecureObjectSync/ViewList.list"

// View Hints
// Note that by definition, there cannot be a V0 view hint
// These will be deprecated for new constants found in SecItemPriv.h
const CFStringRef kSOSViewHintPCSMasterKey      = CFSTR("PCS-MasterKey");
const CFStringRef kSOSViewHintPCSiCloudDrive    = CFSTR("PCS-iCloudDrive");
const CFStringRef kSOSViewHintPCSPhotos         = CFSTR("PCS-Photos");
const CFStringRef kSOSViewHintPCSCloudKit       = CFSTR("PCS-CloudKit");
const CFStringRef kSOSViewHintPCSEscrow         = CFSTR("PCS-Escrow");
const CFStringRef kSOSViewHintPCSFDE            = CFSTR("PCS-FDE");
const CFStringRef kSOSViewHintPCSMailDrop       = CFSTR("PCS-Maildrop");
const CFStringRef kSOSViewHintPCSiCloudBackup   = CFSTR("PCS-Backup");
const CFStringRef kSOSViewHintPCSNotes          = CFSTR("PCS-Notes");
const CFStringRef kSOSViewHintPCSiMessage       = CFSTR("PCS-iMessage");
const CFStringRef kSOSViewHintPCSFeldspar       = CFSTR("PCS-Feldspar");

const CFStringRef kSOSViewHintAppleTV           = CFSTR("AppleTV");
const CFStringRef kSOSViewHintHomeKit           = CFSTR("HomeKit");

CFMutableSetRef SOSViewCopyViewSet(ViewSetKind setKind) {
    CFMutableSetRef result = CFSetCreateMutableForCFTypes(kCFAllocatorDefault);

#undef DOVIEWMACRO
#define __TYPE_MEMBER_ false
#define __TYPE_MEMBER_D true
#define __TYPE_MEMBER_I true
#define __TYPE_MEMBER_A true
#define __TYPE_MEMBER_V true
#define __TYPE_MEMBER_B true
#define DOVIEWMACRO(VIEWNAME, DEFSTRING, CMDSTRING, DEFAULT, INITIAL, ALWAYSON, BACKUP, V0) \
    if ((setKind == kViewSetAll) || \
       ((setKind == kViewSetDefault)   && __TYPE_MEMBER_##DEFAULT)  || \
       ((setKind == kViewSetInitial)   && __TYPE_MEMBER_##INITIAL)  || \
       ((setKind == kViewSetAlwaysOn)  && __TYPE_MEMBER_##ALWAYSON) || \
       ((setKind == kViewSetRequiredForBackup)  && __TYPE_MEMBER_##BACKUP) || \
       ((setKind == kViewSetV0)  && __TYPE_MEMBER_##V0)       ) { \
           CFSetAddValue(result, kSOSView##VIEWNAME); \
    }

#include "Security/SecureObjectSync/ViewList.list"

    return result;
}

CFGiblisGetSingleton(CFSetRef, SOSViewsGetV0ViewSet, defaultViewSet, ^{
    // Since peer->views must never be NULL, fill in with a default
    const void *values[] = { kSOSViewKeychainV0 };
    *defaultViewSet = CFSetCreate(kCFAllocatorDefault, values, array_size(values), &kCFTypeSetCallBacks);
});

CFGiblisGetSingleton(CFSetRef, SOSViewsGetV0SubviewSet, subViewSet, (^{
    // Since peer->views must never be NULL, fill in with a default
    *subViewSet = SOSViewCopyViewSet(kViewSetV0);
}));

CFGiblisGetSingleton(CFSetRef, SOSViewsGetV0BackupViewSet, defaultViewSet, ^{
    const void *values[] = { kSOSViewKeychainV0_tomb };
    *defaultViewSet = CFSetCreate(kCFAllocatorDefault, values, array_size(values), &kCFTypeSetCallBacks);
});

CFGiblisGetSingleton(CFSetRef, SOSViewsGetV0BackupBagViewSet, defaultViewSet, ^{
    const void *values[] = { kSOSViewBackupBagV0_tomb };
    *defaultViewSet = CFSetCreate(kCFAllocatorDefault, values, array_size(values), &kCFTypeSetCallBacks);
});


CFGiblisGetSingleton(CFSetRef, SOSViewsGetInitialSyncSubviewSet, subViewSet, (^{
    *subViewSet = SOSViewCopyViewSet(kViewSetInitial);
}));


bool SOSViewsIsV0Subview(CFStringRef viewName) {
    return CFSetContainsValue(SOSViewsGetV0SubviewSet(), viewName);
}

CFSetRef sTestViewSet = NULL;
void SOSViewsSetTestViewsSet(CFSetRef testViewNames) {
    CFRetainAssign(sTestViewSet, testViewNames);
}

CFSetRef SOSViewsGetAllCurrent(void) {
    static dispatch_once_t dot;
    static CFMutableSetRef allViews = NULL;
    dispatch_once(&dot, ^{
        allViews = SOSViewCopyViewSet(kViewSetAll);

        CFSetAddValue(allViews, kSOSViewKeychainV0);
        if(sTestViewSet) CFSetUnion(allViews, sTestViewSet);
    });
    return allViews;
}

const char *SOSViewsXlateAction(SOSViewActionCode action) {
    switch(action) {
        case kSOSCCViewEnable: return "kSOSCCViewEnable";
        case kSOSCCViewDisable: return "kSOSCCViewDisable";
        case kSOSCCViewQuery: return "kSOSCCViewQuery";
        default: return "unknownViewAction";
    }
}


// Eventually this will want to know the gestalt or security properties...
void SOSViewsForEachDefaultEnabledViewName(void (^operation)(CFStringRef viewName)) {
    CFMutableSetRef defaultViews = SOSViewCopyViewSet(kViewSetDefault);

    CFSetForEach(defaultViews, ^(const void *value) {
        CFStringRef name = asString(value, NULL);

        if (name) {
            operation(name);
        }
    });

    CFReleaseNull(defaultViews);
}

static bool SOSViewsIsKnownView(CFStringRef viewname) {
    CFSetRef allViews = SOSViewsGetAllCurrent();
    if(CFSetContainsValue(allViews, viewname)) return true;
    secnotice("views","Not a known view");
    return false;
}

static bool viewErrorReport(CFIndex errorCode, CFErrorRef *error, CFStringRef format, CFStringRef viewname, int retval) {
    return SOSCreateErrorWithFormat(errorCode, NULL, error, NULL, format, viewname, retval);
}

static bool SOSViewsRequireIsKnownView(CFStringRef viewname, CFErrorRef* error) {
    return SOSViewsIsKnownView(viewname) || viewErrorReport(kSOSErrorNameMismatch, error, viewUnknownError, viewname, kSOSCCNoSuchView);
}

bool SOSPeerInfoIsEnabledView(SOSPeerInfoRef pi, CFStringRef viewName) {
    if (pi->version < kSOSPeerV2BaseVersion) {
        return CFSetContainsValue(SOSViewsGetV0ViewSet(), viewName);
    } else {
        return SOSPeerInfoV2DictionaryHasSetContaining(pi, sViewsKey, viewName);
    }
}

void SOSPeerInfoWithEnabledViewSet(SOSPeerInfoRef pi, void (^operation)(CFSetRef enabled)) {
    if (pi->version < kSOSPeerV2BaseVersion) {
        operation(SOSViewsGetV0ViewSet());
    } else {
        SOSPeerInfoV2DictionaryWithSet(pi, sViewsKey, operation);
    }
}

CFMutableSetRef SOSPeerInfoCopyEnabledViews(SOSPeerInfoRef pi) {
    if (pi->version < kSOSPeerV2BaseVersion) {
        return CFSetCreateMutableCopy(kCFAllocatorDefault, CFSetGetCount(SOSViewsGetV0ViewSet()), SOSViewsGetV0ViewSet());
    } else {
        CFMutableSetRef views = SOSPeerInfoV2DictionaryCopySet(pi, sViewsKey);
        if (!views) {
            // This is unexpected: log and return an empty set to prevent <rdar://problem/21938868>
            secerror("%@ v2 peer has no views", SOSPeerInfoGetPeerID(pi));
            views = CFSetCreateMutableForCFTypes(kCFAllocatorDefault);
        }
        return views;
    }
}

CFSetRef SOSPeerInfoGetPermittedViews(SOSPeerInfoRef pi) {
    return SOSViewsGetAllCurrent();
}

static void SOSPeerInfoSetViews(SOSPeerInfoRef pi, CFSetRef newviews) {
    if(!newviews) {
        secnotice("views","Asked to swap to NULL views");
        return;
    }
    SOSPeerInfoV2DictionarySetValue(pi, sViewsKey, newviews);
}

static bool SOSPeerInfoViewIsValid(SOSPeerInfoRef pi, CFStringRef viewname) {
    return true;
}

SOSViewResultCode SOSViewsEnable(SOSPeerInfoRef pi, CFStringRef viewname, CFErrorRef *error) {
    SOSViewResultCode retval = kSOSCCGeneralViewError;

    CFMutableSetRef newviews = SOSPeerInfoCopyEnabledViews(pi);
    require_action_quiet(newviews, fail,
                         SOSCreateError(kSOSErrorAllocationFailure, viewMemError, NULL, error));
    require_action_quiet(SOSViewsRequireIsKnownView(viewname, error), fail,
                         retval = kSOSCCNoSuchView);
    require_action_quiet(SOSPeerInfoViewIsValid(pi, viewname), fail,
                         viewErrorReport(kSOSErrorNameMismatch, error, viewInvalidError, viewname, retval = kSOSCCViewNotQualified));
    CFSetAddValue(newviews, viewname);
    SOSPeerInfoSetViews(pi, newviews);
    CFReleaseSafe(newviews);
    return kSOSCCViewMember;

fail:
    CFReleaseNull(newviews);
    secnotice("views","Failed to enable view(%@): %@", viewname, error ? *error : NULL);
    return retval;
}

bool SOSViewSetEnable(SOSPeerInfoRef pi, CFSetRef viewSet) {
    __block bool addedView = false;
    CFMutableSetRef newviews = SOSPeerInfoCopyEnabledViews(pi);
    require_action_quiet(newviews, errOut, secnotice("views", "failed to copy enabled views"));

    CFSetForEach(viewSet, ^(const void *value) {
        CFStringRef viewName = (CFStringRef) value;
        if(SOSViewsIsKnownView(viewName) && SOSPeerInfoViewIsValid(pi, viewName)) {
            if (!CFSetContainsValue(newviews, viewName)) {
                addedView = true;
                CFSetAddValue(newviews, viewName);
            }
        } else {
            secnotice("views", "couldn't add view %@", viewName);
        }
    });
    require_quiet(addedView, errOut);

    SOSPeerInfoSetViews(pi, newviews);

errOut:
    CFReleaseNull(newviews);
    return addedView;
}


SOSViewResultCode SOSViewsDisable(SOSPeerInfoRef pi, CFStringRef viewname, CFErrorRef *error) {
    SOSViewResultCode retval = kSOSCCGeneralViewError;
    CFMutableSetRef newviews = SOSPeerInfoCopyEnabledViews(pi);
    require_action_quiet(newviews, fail,
                         SOSCreateError(kSOSErrorAllocationFailure, viewMemError, NULL, error));
    require_action_quiet(SOSViewsRequireIsKnownView(viewname, error), fail, retval = kSOSCCNoSuchView);

    CFSetRemoveValue(newviews, viewname);
    SOSPeerInfoSetViews(pi, newviews);
    CFReleaseSafe(newviews);
    return kSOSCCViewNotMember;

fail:
    CFReleaseNull(newviews);
    secnotice("views","Failed to disable view(%@): %@", viewname, error ? *error : NULL);
    return retval;
}


bool SOSViewSetDisable(SOSPeerInfoRef pi, CFSetRef viewSet) {
    __block bool removed = false;
    CFMutableSetRef newviews = SOSPeerInfoCopyEnabledViews(pi);
    require_action_quiet(newviews, errOut, secnotice("views", "failed to copy enabled views"));

    CFSetForEach(viewSet, ^(const void *value) {
        CFStringRef viewName = (CFStringRef) value;
        if(SOSViewsIsKnownView(viewName) && CFSetContainsValue(newviews, viewName)) {
            removed = true;
            CFSetRemoveValue(newviews, viewName);
        } else {
            secnotice("views", "couldn't delete view %@", viewName);
        }
    });

    require_quiet(removed, errOut);

    SOSPeerInfoSetViews(pi, newviews);

errOut:
    CFReleaseNull(newviews);
    return removed;
}


SOSViewResultCode SOSViewsQuery(SOSPeerInfoRef pi, CFStringRef viewname, CFErrorRef *error) {
    SOSViewResultCode retval = kSOSCCNoSuchView;
    CFSetRef views = NULL;
    require_quiet(SOSViewsRequireIsKnownView(viewname, error), fail);

    views = SOSPeerInfoCopyEnabledViews(pi);
    if(!views){
        retval = kSOSCCViewNotMember;
        CFReleaseNull(views);
        return retval;
    }

    // kSOSViewKeychainV0 is set if there is a V0 PeerInfo in the circle.  It represents all of the subviews in
    // SOSViewsGetV0SubviewSet() so we return kSOSCCViewMember for that case.  kSOSViewKeychainV0 and the subviews
    // are mutually exclusive.
    else if(CFSetContainsValue(views, kSOSViewKeychainV0) && CFSetContainsValue(SOSViewsGetV0SubviewSet(), viewname)) {
        retval = kSOSCCViewMember;
    } else {
        retval = (CFSetContainsValue(views, viewname)) ? kSOSCCViewMember: kSOSCCViewNotMember;
    }

    CFReleaseNull(views);
    return retval;

fail:
    secnotice("views","Failed to query view(%@): %@", viewname, error ? *error : NULL);
    CFReleaseNull(views);
    return retval;
}

static CFArrayRef SOSCreateActiveViewIntersectionArrayForPeerInfos(SOSPeerInfoRef pi1, SOSPeerInfoRef pi2) {
    CFMutableArrayRef  retval = NULL;
    CFSetRef views1 = SOSPeerInfoCopyEnabledViews(pi1);
    CFSetRef views2 = SOSPeerInfoCopyEnabledViews(pi2);
    size_t count = CFSetGetCount(views1);
    if(count == 0){
        CFReleaseNull(views1);
        CFReleaseNull(views2);
        return NULL;
    }
    CFStringRef pi1views[count];

    retval = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
    CFSetGetValues(views1, (const void **) &pi1views);
    for(size_t i = 0; i < count; i++) {
        if(CFSetContainsValue(views2, pi1views[i])) {
            CFArrayAppendValue(retval, pi1views[i]);
        }
    }
    CFReleaseNull(views1);
    CFReleaseNull(views2);

    return retval;
}

CFArrayRef SOSCreateActiveViewIntersectionArrayForPeerID(SOSAccountRef account, CFStringRef peerID) {
    CFArrayRef  retval = NULL;
    SOSPeerInfoRef myPI = SOSAccountGetMyPeerInfo(account);
    SOSPeerInfoRef theirPI = SOSAccountCopyPeerWithID(account, peerID, NULL);
    require_action_quiet(myPI, errOut, retval = NULL);
    require_action_quiet(theirPI, errOut, retval = NULL);

    retval = SOSCreateActiveViewIntersectionArrayForPeerInfos(myPI, theirPI);

errOut:
    CFReleaseNull(theirPI);
    return retval;
}

// This needs to create a dictionary of sets of intersected views for an account
CFDictionaryRef SOSViewsCreateActiveViewMatrixDictionary(SOSAccountRef account, SOSCircleRef circle, CFErrorRef *error) {
    CFMutableDictionaryRef retval = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
    SOSPeerInfoRef myPI = SOSAccountGetMyPeerInfo(account);

    // For now, all views require that a valid member peer is in the circle and active/valid
    CFMutableSetRef peers = SOSCircleCopyPeers(circle, kCFAllocatorDefault);

    require_action_quiet(retval, errOut, SOSCreateError(kSOSErrorAllocationFailure, CFSTR("Could not allocate ViewMatrix"), NULL, error));
    require_action_quiet(myPI, errOut, SOSCreateError(kSOSErrorPeerNotFound, CFSTR("Could not find our PeerInfo"), NULL, error));
    require(peers, errOut);

    CFSetRef myViews = SOSPeerInfoCopyEnabledViews(myPI);

    if (myViews)
        CFSetForEach(myViews, ^(const void *value) {
            CFStringRef viewname = (CFStringRef) value;
            CFMutableSetRef viewset = CFSetCreateMutable(NULL, 0, &kCFTypeSetCallBacks);
            CFSetForEach(peers, ^(const void *peervalue) {
                SOSPeerInfoRef pi = (SOSPeerInfoRef) peervalue;
                CFSetRef piViews = SOSPeerInfoCopyEnabledViews(pi);

                if(piViews && CFSetContainsValue(piViews, viewname)) {
                    CFStringRef peerID = SOSPeerInfoGetPeerID(pi);
                    CFSetAddValue(viewset, peerID);
                }
                CFReleaseNull(piViews);

            });
            CFDictionaryAddValue(retval, viewname, viewset);
        });

    if(CFDictionaryGetCount(retval) == 0) goto errOut;  // Not really an error - just no intersection of views with anyone
    CFReleaseNull(peers);
    CFReleaseNull(myViews);
    return retval;

errOut:
    CFReleaseNull(retval);
    CFReleaseNull(peers);
    return NULL;
}





/* Need XPC way to carry CFSets of views */


CFSetRef CreateCFSetRefFromXPCObject(xpc_object_t xpcSetDER, CFErrorRef* error) {
    CFSetRef retval = NULL;
    require_action_quiet(xpcSetDER, errOut, SecCFCreateErrorWithFormat(kSecXPCErrorUnexpectedNull, sSecXPCErrorDomain, NULL, error, NULL, CFSTR("Unexpected Null Set to decode")));

    require_action_quiet(xpc_get_type(xpcSetDER) == XPC_TYPE_DATA, errOut, SecCFCreateErrorWithFormat(kSecXPCErrorUnexpectedType, sSecXPCErrorDomain, NULL, error, NULL, CFSTR("xpcSetDER not data, got %@"), xpcSetDER));

    const uint8_t* der = xpc_data_get_bytes_ptr(xpcSetDER);
    const uint8_t* der_end = der + xpc_data_get_length(xpcSetDER);
    der = der_decode_set(kCFAllocatorDefault, kCFPropertyListMutableContainersAndLeaves, &retval, error, der, der_end);
    if (der != der_end) {
        SecError(errSecDecode, error, CFSTR("trailing garbage at end of SecAccessControl data"));
        goto errOut;
    }
    return retval;
errOut:
    CFReleaseNull(retval);
    return NULL;
}

xpc_object_t CreateXPCObjectWithCFSetRef(CFSetRef setref, CFErrorRef *error) {
    xpc_object_t result = NULL;
    size_t data_size = 0;
    uint8_t *data = NULL;
    require_action_quiet(setref, errOut, SecCFCreateErrorWithFormat(kSecXPCErrorUnexpectedNull, sSecXPCErrorDomain, NULL, error, NULL, CFSTR("Unexpected Null Set to encode")));
    require_quiet((data_size = der_sizeof_set(setref, error)) != 0, errOut);
    require_quiet((data = (uint8_t *)malloc(data_size)) != NULL, errOut);
    
    der_encode_set(setref, error, data, data + data_size);
    result = xpc_data_create(data, data_size);
    free(data);
errOut:
    return result;
}