#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)");
const CFStringRef kSOSViewKeychainV0_tomb = CFSTR("KeychainV0-tomb"); const CFStringRef kSOSViewBackupBagV0_tomb = CFSTR("BackupBagV0-tomb"); 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");
const CFStringRef kSOSViewKeychainV0 = CFSTR("KeychainV0");
const CFStringRef kSOSViewWiFi = CFSTR("WiFi");
const CFStringRef kSOSViewAutofillPasswords = CFSTR("Passwords");
const CFStringRef kSOSViewSafariCreditCards = CFSTR("CreditCards");
const CFStringRef kSOSViewiCloudIdentity = CFSTR("iCloudIdentity");
const CFStringRef kSOSViewBackupBagV0 = CFSTR("BackupBagV0"); const CFStringRef kSOSViewOtherSyncable = CFSTR("OtherSyncable");
const CFStringRef kSOSViewPCSMasterKey = CFSTR("PCS-MasterKey");
const CFStringRef kSOSViewPCSiCloudDrive = CFSTR("PCS-iCloudDrive"); const CFStringRef kSOSViewPCSPhotos = CFSTR("PCS-Photos"); const CFStringRef kSOSViewPCSCloudKit = CFSTR("PCS-CloudKit"); const CFStringRef kSOSViewPCSEscrow = CFSTR("PCS-Escrow");
const CFStringRef kSOSViewPCSFDE = CFSTR("PCS-FDE");
const CFStringRef kSOSViewPCSMailDrop = CFSTR("PCS-Maildrop"); const CFStringRef kSOSViewPCSiCloudBackup = CFSTR("PCS-Backup");
const CFStringRef kSOSViewPCSNotes = CFSTR("PCS-Notes");
const CFStringRef kSOSViewPCSiMessage = CFSTR("PCS-iMessage");
const CFStringRef kSOSViewPCSFeldspar = CFSTR("PCS-Feldspar");
const CFStringRef kSOSViewAppleTV = CFSTR("AppleTV");
const CFStringRef kSOSViewHomeKit = CFSTR("HomeKit");
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");
CFGiblisGetSingleton(CFSetRef, SOSViewsGetV0ViewSet, defaultViewSet, ^{
const void *values[] = { kSOSViewKeychainV0 };
*defaultViewSet = CFSetCreate(kCFAllocatorDefault, values, array_size(values), &kCFTypeSetCallBacks);
});
CFGiblisGetSingleton(CFSetRef, SOSViewsGetV0SubviewSet, subViewSet, (^{
const void *values[] = { kSOSViewWiFi, kSOSViewAutofillPasswords, kSOSViewSafariCreditCards,
kSOSViewiCloudIdentity, kSOSViewBackupBagV0, kSOSViewOtherSyncable };
*subViewSet = CFSetCreate(kCFAllocatorDefault, values, array_size(values), &kCFTypeSetCallBacks);
}));
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);
});
bool SOSViewsIsV0Subview(CFStringRef viewName) {
return CFSetContainsValue(SOSViewsGetV0BackupViewSet(), 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 = CFSetCreateMutableForCFTypes(kCFAllocatorDefault);
CFSetAddValue(allViews, kSOSViewKeychainV0);
CFSetAddValue(allViews, kSOSViewPCSMasterKey);
CFSetAddValue(allViews, kSOSViewPCSiCloudDrive);
CFSetAddValue(allViews, kSOSViewPCSPhotos);
CFSetAddValue(allViews, kSOSViewPCSCloudKit);
CFSetAddValue(allViews, kSOSViewPCSEscrow);
CFSetAddValue(allViews, kSOSViewPCSFDE);
CFSetAddValue(allViews, kSOSViewPCSMailDrop);
CFSetAddValue(allViews, kSOSViewPCSiCloudBackup);
CFSetAddValue(allViews, kSOSViewPCSNotes);
CFSetAddValue(allViews, kSOSViewPCSiMessage);
CFSetAddValue(allViews, kSOSViewPCSFeldspar);
CFSetAddValue(allViews, kSOSViewAppleTV);
CFSetAddValue(allViews, kSOSViewHomeKit);
CFSetAddValue(allViews, kSOSViewWiFi);
CFSetAddValue(allViews, kSOSViewAutofillPasswords);
CFSetAddValue(allViews, kSOSViewSafariCreditCards);
CFSetAddValue(allViews, kSOSViewiCloudIdentity);
CFSetAddValue(allViews, kSOSViewBackupBagV0);
CFSetAddValue(allViews, kSOSViewOtherSyncable);
});
return sTestViewSet ? sTestViewSet : allViews;
}
static CFMutableSetRef CFSetCreateMutableCopyForSOSViews(CFAllocatorRef allocator, CFSetRef original) {
if(!original) return NULL;
return CFSetCreateMutableCopy(allocator, 0, original);
}
CFMutableSetRef SOSViewsCreateDefault(bool includeLegacy, CFErrorRef *error) {
CFMutableSetRef result = CFSetCreateMutableCopyForSOSViews(NULL, SOSViewsGetAllCurrent());
CFSetRemoveValue(result, kSOSViewKeychainV0);
if (!includeLegacy) {
CFSetRemoveValue(result, kSOSViewWiFi);
CFSetRemoveValue(result, kSOSViewAutofillPasswords);
CFSetRemoveValue(result, kSOSViewSafariCreditCards);
CFSetRemoveValue(result, kSOSViewOtherSyncable);
}
return result;
}
void SOSViewsForEachDefaultEnabledViewName(void (^operation)(CFStringRef viewName)) {
CFMutableSetRef defaultViews = SOSViewsCreateDefault(false, NULL);
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;
}
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) {
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;
}
static bool viewErrorReport(CFIndex errorCode, CFErrorRef *error, CFStringRef format, CFStringRef viewname, int retval) {
return SOSCreateErrorWithFormat(errorCode, NULL, error, NULL, format, viewname, retval);
}
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(SOSViewsIsKnownView(viewname), fail,
viewErrorReport(kSOSErrorNameMismatch, error, viewUnknownError, viewname, 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);
return retval;
}
bool SOSViewSetEnable(SOSPeerInfoRef pi, CFSetRef viewSet) {
__block bool retval = true;
__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) && !CFSetContainsValue(newviews, viewName)) {
addedView = true;
CFSetAddValue(newviews, viewName);
} else {
retval = false;
secnotice("views", "couldn't add view %@", viewName);
}
});
require_quiet(retval, errOut);
if (addedView) {
SOSPeerInfoSetViews(pi, newviews);
}
errOut:
CFReleaseNull(newviews);
return retval;
}
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(SOSViewsIsKnownView(viewname), fail,
viewErrorReport(kSOSErrorNameMismatch, error, viewUnknownError, viewname, retval = kSOSCCNoSuchView));
CFSetRemoveValue(newviews, viewname);
SOSPeerInfoSetViews(pi, newviews);
CFReleaseSafe(newviews);
return kSOSCCViewNotMember;
fail:
CFReleaseNull(newviews);
secnotice("views","Failed to disable view(%@): %@", viewname, *error);
return retval;
}
bool SOSViewSetDisable(SOSPeerInfoRef pi, CFSetRef viewSet) {
__block bool retval = true;
__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 {
retval = false;
secnotice("views", "couldn't delete view %@", viewName);
}
});
require_quiet(retval, errOut);
if(removed) {
SOSPeerInfoSetViews(pi, newviews);
}
errOut:
CFReleaseNull(newviews);
return retval;
}
SOSViewResultCode SOSViewsQuery(SOSPeerInfoRef pi, CFStringRef viewname, CFErrorRef *error) {
SOSViewResultCode retval = kSOSCCNoSuchView;
CFSetRef views = NULL;
secnotice("views", "Querying %@", viewname);
require_action_quiet(SOSViewsIsKnownView(viewname), fail,
SOSCreateError(kSOSErrorNameMismatch, viewUnknownError, NULL, error));
views = SOSPeerInfoCopyEnabledViews(pi);
if(!views){
retval = kSOSCCViewNotMember;
CFReleaseNull(views);
return retval;
}
retval = (CFSetContainsValue(views, viewname)) ? kSOSCCViewMember: kSOSCCViewNotMember;
CFReleaseNull(views);
return retval;
fail:
secnotice("views","Failed to query view(%@): %@", viewname, *error);
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;
}
CFDictionaryRef SOSViewsCreateActiveViewMatrixDictionary(SOSAccountRef account, SOSCircleRef circle, CFErrorRef *error) {
CFMutableDictionaryRef retval = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
SOSPeerInfoRef myPI = SOSAccountGetMyPeerInfo(account);
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; CFReleaseNull(peers);
CFReleaseNull(myViews);
return retval;
errOut:
CFReleaseNull(retval);
CFReleaseNull(peers);
return NULL;
}
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;
}