#include "cs.h"
#include "SecAssessment.h"
#include "policydb.h"
#include "policyengine.h"
#include "xpcengine.h"
#include "csutilities.h"
#include <CoreFoundation/CFRuntime.h>
#include <security_utilities/globalizer.h>
#include <security_utilities/unix++.h>
#include <security_utilities/cfmunge.h>
#include <notify.h>
#include <esp.h>
using namespace CodeSigning;
static void esp_do_check(const char *op, CFDictionaryRef dict)
{
OSStatus result = __esp_check_ns(op, (void *)(CFDictionaryRef)dict);
if (result != noErr)
MacOSError::throwMe(result);
}
struct _SecAssessment : private CFRuntimeBase {
public:
_SecAssessment(CFURLRef p, AuthorityType typ, CFDictionaryRef r) : path(p), type(typ), result(r) { }
CFCopyRef<CFURLRef> path;
AuthorityType type;
CFRef<CFDictionaryRef> result;
public:
static _SecAssessment &ref(SecAssessmentRef r)
{ return *(_SecAssessment *)r; }
void *operator new (size_t size)
{
return (void *)_CFRuntimeCreateInstance(NULL, SecAssessmentGetTypeID(),
sizeof(_SecAssessment) - sizeof(CFRuntimeBase), NULL);
}
static void finalize(CFTypeRef obj)
{ ((_SecAssessment *)obj)->~_SecAssessment(); }
};
typedef _SecAssessment SecAssessment;
static const CFRuntimeClass assessmentClass = {
0, "SecAssessment", NULL, NULL, SecAssessment::finalize, NULL, NULL, NULL, NULL };
static dispatch_once_t assessmentOnce;
CFTypeID assessmentType = _kCFRuntimeNotATypeID;
CFTypeID SecAssessmentGetTypeID()
{
dispatch_once(&assessmentOnce, ^void() {
if ((assessmentType = _CFRuntimeRegisterClass(&assessmentClass)) == _kCFRuntimeNotATypeID)
abort();
});
return assessmentType;
}
CFStringRef kSecAssessmentContextKeyOperation = CFSTR("operation");
CFStringRef kSecAssessmentOperationTypeExecute = CFSTR("operation:execute");
CFStringRef kSecAssessmentOperationTypeInstall = CFSTR("operation:install");
CFStringRef kSecAssessmentOperationTypeOpenDocument = CFSTR("operation:lsopen");
class ReadPolicy : public PolicyDatabase {
public:
ReadPolicy() : PolicyDatabase(defaultDatabase) { }
};
ModuleNexus<ReadPolicy> gDatabase;
ModuleNexus<PolicyEngine> gEngine;
CFStringRef kSecAssessmentAssessmentVerdict = CFSTR("assessment:verdict");
CFStringRef kSecAssessmentAssessmentOriginator = CFSTR("assessment:originator");
CFStringRef kSecAssessmentAssessmentAuthority = CFSTR("assessment:authority");
CFStringRef kSecAssessmentAssessmentSource = CFSTR("assessment:authority:source");
CFStringRef kSecAssessmentAssessmentAuthorityRow = CFSTR("assessment:authority:row");
CFStringRef kSecAssessmentAssessmentAuthorityOverride = CFSTR("assessment:authority:override");
CFStringRef kSecAssessmentAssessmentAuthorityOriginalVerdict = CFSTR("assessment:authority:verdict");
CFStringRef kSecAssessmentAssessmentFromCache = CFSTR("assessment:authority:cached");
CFStringRef kDisabledOverride = CFSTR("security disabled");
SecAssessmentRef SecAssessmentCreate(CFURLRef path,
SecAssessmentFlags flags,
CFDictionaryRef context,
CFErrorRef *errors)
{
BEGIN_CSAPI
if (flags & kSecAssessmentFlagAsynchronous)
MacOSError::throwMe(errSecCSUnimplemented);
AuthorityType type = typeFor(context, kAuthorityExecute);
CFRef<CFMutableDictionaryRef> result = makeCFMutableDictionary();
SYSPOLICY_ASSESS_API(cfString(path).c_str(), int(type), flags);
try {
if (__esp_enabled() && (flags & kSecAssessmentFlagDirect)) {
CFTemp<CFDictionaryRef> dict("{path=%O, flags=%d, context=%O, override=%d}", path, flags, context, overrideAssessment());
esp_do_check("cs-assessment-evaluate", dict);
}
if (!(flags & (kSecAssessmentFlagRequestOrigin | kSecAssessmentFlagIgnoreCache))) {
if (gDatabase().checkCache(path, type, flags, result))
return new SecAssessment(path, type, result.yield());
}
if (flags & kSecAssessmentFlagDirect) {
SYSPOLICY_ASSESS_LOCAL();
gEngine().evaluate(path, type, flags, context, result);
} else {
SYSPOLICY_ASSESS_REMOTE();
xpcEngineAssess(path, flags, context, result);
}
} catch (CommonError &error) {
switch (error.osStatus()) {
case CSSMERR_TP_CERT_REVOKED:
throw;
default:
if (!overrideAssessment(flags))
throw; break;
}
cfadd(result, "{%O=#F,'assessment:error'=%d}}", kSecAssessmentAssessmentVerdict, error.osStatus());
} catch (...) {
if (!overrideAssessment(flags))
throw; cfadd(result, "{%O=#F}", kSecAssessmentAssessmentVerdict);
}
if (__esp_enabled() && (flags & kSecAssessmentFlagDirect)) {
CFTemp<CFDictionaryRef> dict("{path=%O, flags=%d, context=%O, override=%d, result=%O}", path, flags, context, overrideAssessment(), (CFDictionaryRef)result);
__esp_notify_ns("cs-assessment-evaluate", (void *)(CFDictionaryRef)dict);
}
return new SecAssessment(path, type, result.yield());
END_CSAPI_ERRORS1(NULL)
}
static void traceResult(CFURLRef target, MessageTrace &trace, std::string &sanitized)
{
static const char *interestingBundles[] = {
"UNBUNDLED",
"com.apple.",
"com.install4j.",
"com.MindVision.",
"com.yourcompany.",
"com.adobe.flashplayer.installmanager",
"com.adobe.Installers.Setup",
"com.adobe.PDApp.setup",
"com.bittorrent.uTorrent",
"com.divx.divx6formacinstaller",
"com.getdropbox.dropbox",
"com.google.Chrome",
"com.Google.GoogleEarthPlugin.plugin",
"com.Google.GoogleEarthPlus",
"com.hp.Installer",
"com.macpaw.CleanMyMac",
"com.microsoft.SilverlightInstaller",
"com.paragon-software.filesystems.NTFS.pkg",
"com.RealNetworks.RealPlayer",
"com.skype.skype",
"it.alfanet.squared5.MPEGStreamclip",
"org.mozilla.firefox",
"org.videolan.vlc",
NULL };
string identifier = "UNBUNDLED";
string version = "UNKNOWN";
if (CFRef<CFBundleRef> bundle = CFBundleCreate(NULL, target)) {
if (CFStringRef ident = CFBundleGetIdentifier(bundle))
identifier = cfString(ident);
if (CFStringRef vers = CFStringRef(CFBundleGetValueForInfoDictionaryKey(bundle, CFSTR("CFBundleShortVersionString"))))
version = cfString(vers);
}
CFRef<CFURLRef> url = CFURLCopyAbsoluteURL(target);
sanitized = cfString(url);
string::size_type rslash = sanitized.rfind('/');
if (rslash != string::npos)
sanitized = sanitized.substr(rslash+1);
bool keepFilename = false;
for (const char **pfx = interestingBundles; *pfx; pfx++) {
size_t pfxlen = strlen(*pfx);
if (identifier.compare(0, pfxlen, *pfx, pfxlen) == 0)
if (pfxlen == identifier.size() || (*pfx)[pfxlen-1] == '.') {
keepFilename = true;
break;
}
}
if (!keepFilename) {
string::size_type dot = sanitized.rfind('.');
if (dot != string::npos)
sanitized = sanitized.substr(dot);
else
sanitized = "(none)";
}
trace.add("signature2", "bundle:%s", identifier.c_str());
trace.add("signature3", "%s", sanitized.c_str());
trace.add("signature5", "%s", version.c_str());
}
static void traceAssessment(SecAssessment &assessment, AuthorityType type, CFDictionaryRef result)
{
if (CFDictionaryGetValue(result, CFSTR("assessment:remote")))
return;
string authority = "UNSPECIFIED";
bool overridden = false;
bool old_overridden = false;
if (CFDictionaryRef authdict = CFDictionaryRef(CFDictionaryGetValue(result, kSecAssessmentAssessmentAuthority))) {
if (CFStringRef auth = CFStringRef(CFDictionaryGetValue(authdict, kSecAssessmentAssessmentSource)))
authority = cfString(auth);
else
authority = "no authority";
if (CFTypeRef override = CFDictionaryGetValue(authdict, kSecAssessmentAssessmentAuthorityOverride))
if (CFEqual(override, kDisabledOverride)) {
old_overridden = true;
if (CFDictionaryGetValue(authdict, kSecAssessmentAssessmentAuthorityOriginalVerdict) == kCFBooleanFalse)
overridden = true;
}
}
MessageTrace trace("com.apple.security.assessment.outcome2", NULL);
std::string sanitized;
traceResult(assessment.path, trace, sanitized);
trace.add("signature4", "%d", type);
if (CFDictionaryGetValue(result, kSecAssessmentAssessmentVerdict) == kCFBooleanFalse) {
trace.add("signature", "denied:%s", authority.c_str());
trace.send("assessment denied for %s", sanitized.c_str());
} else if (overridden) { trace.add("signature", "defeated:%s", authority.c_str());
trace.send("assessment denied for %s but overridden", sanitized.c_str());
} else if (old_overridden) { trace.add("signature", "override:%s", authority.c_str());
trace.send("assessment granted for %s and overridden", sanitized.c_str());
} else {
trace.add("signature", "granted:%s", authority.c_str());
trace.send("assessment granted for %s by %s", sanitized.c_str(), authority.c_str());
}
}
static void traceUpdate(CFTypeRef target, CFDictionaryRef context, CFDictionaryRef result)
{
if (target == NULL || CFGetTypeID(target) != CFURLGetTypeID())
return;
CFStringRef edit = CFStringRef(CFDictionaryGetValue(context, kSecAssessmentContextKeyUpdate));
if (!CFEqual(edit, kSecAssessmentUpdateOperationAdd))
return;
MessageTrace trace("com.apple.security.assessment.update", NULL);
std::string sanitized;
traceResult(CFURLRef(target), trace, sanitized);
trace.send("added rule for %s", sanitized.c_str());
}
CFDictionaryRef SecAssessmentCopyResult(SecAssessmentRef assessmentRef,
SecAssessmentFlags flags,
CFErrorRef *errors)
{
BEGIN_CSAPI
SecAssessment &assessment = SecAssessment::ref(assessmentRef);
CFCopyRef<CFDictionaryRef> result = assessment.result;
if (overrideAssessment(flags)) {
CFTypeRef verdict = CFDictionaryGetValue(result, kSecAssessmentAssessmentVerdict);
if (verdict == kCFBooleanFalse) {
CFRef<CFMutableDictionaryRef> adulterated = makeCFMutableDictionary(result.get());
CFDictionarySetValue(adulterated, kSecAssessmentAssessmentVerdict, kCFBooleanTrue);
if (CFDictionaryRef authority = CFDictionaryRef(CFDictionaryGetValue(adulterated, kSecAssessmentAssessmentAuthority))) {
CFRef<CFMutableDictionaryRef> authority2 = makeCFMutableDictionary(authority);
CFDictionarySetValue(authority2, kSecAssessmentAssessmentAuthorityOverride, kDisabledOverride);
CFDictionarySetValue(authority2, kSecAssessmentAssessmentAuthorityOriginalVerdict, verdict);
CFDictionarySetValue(adulterated, kSecAssessmentAssessmentAuthority, authority2);
} else {
cfadd(adulterated, "{%O={%O=%O}}",
kSecAssessmentAssessmentAuthority, kSecAssessmentAssessmentAuthorityOverride, kDisabledOverride);
}
result = adulterated.get();
}
}
traceAssessment(assessment, assessment.type, result);
return result.yield();
END_CSAPI_ERRORS1(NULL)
}
CFStringRef kSecAssessmentContextKeyUpdate = CFSTR("update");
CFStringRef kSecAssessmentUpdateOperationAdd = CFSTR("update:add");
CFStringRef kSecAssessmentUpdateOperationRemove = CFSTR("update:remove");
CFStringRef kSecAssessmentUpdateOperationEnable = CFSTR("update:enable");
CFStringRef kSecAssessmentUpdateOperationDisable = CFSTR("update:disable");
CFStringRef kSecAssessmentUpdateOperationFind = CFSTR("update:find");
CFStringRef kSecAssessmentUpdateKeyAuthorization = CFSTR("update:authorization");
CFStringRef kSecAssessmentUpdateKeyPriority = CFSTR("update:priority");
CFStringRef kSecAssessmentUpdateKeyLabel = CFSTR("update:label");
CFStringRef kSecAssessmentUpdateKeyExpires = CFSTR("update:expires");
CFStringRef kSecAssessmentUpdateKeyAllow = CFSTR("update:allow");
CFStringRef kSecAssessmentUpdateKeyRemarks = CFSTR("update:remarks");
CFStringRef kSecAssessmentUpdateKeyRow = CFSTR("update:row");
CFStringRef kSecAssessmentUpdateKeyCount = CFSTR("update:count");
CFStringRef kSecAssessmentUpdateKeyFound = CFSTR("update:found");
CFStringRef kSecAssessmentRuleKeyID = CFSTR("rule:id");
CFStringRef kSecAssessmentRuleKeyPriority = CFSTR("rule:priority");
CFStringRef kSecAssessmentRuleKeyAllow = CFSTR("rule:allow");
CFStringRef kSecAssessmentRuleKeyLabel = CFSTR("rule:label");
CFStringRef kSecAssessmentRuleKeyRemarks = CFSTR("rule:remarks");
CFStringRef kSecAssessmentRuleKeyRequirement = CFSTR("rule:requirement");
CFStringRef kSecAssessmentRuleKeyType = CFSTR("rule:type");
CFStringRef kSecAssessmentRuleKeyExpires = CFSTR("rule:expires");
CFStringRef kSecAssessmentRuleKeyDisabled = CFSTR("rule:disabled");
CFStringRef kSecAssessmentRuleKeyBookmark = CFSTR("rule:bookmark");
Boolean SecAssessmentUpdate(CFTypeRef target,
SecAssessmentFlags flags,
CFDictionaryRef context,
CFErrorRef *errors)
{
if (CFDictionaryRef outcome = SecAssessmentCopyUpdate(target, flags, context, errors)) {
CFRelease(outcome);
return true;
} else {
return false;
}
}
CFDictionaryRef SecAssessmentCopyUpdate(CFTypeRef target,
SecAssessmentFlags flags,
CFDictionaryRef context,
CFErrorRef *errors)
{
BEGIN_CSAPI
CFDictionary ctx(context, errSecCSInvalidAttributeValues);
CFRef<CFDictionaryRef> result;
if (flags & kSecAssessmentFlagDirect) {
if (__esp_enabled()) {
CFTemp<CFDictionaryRef> dict("{target=%O, flags=%d, context=%O}", target, flags, context);
OSStatus esp_result = __esp_check_ns("cs-assessment-update", (void *)(CFDictionaryRef)dict);
if (esp_result != noErr)
return NULL;
}
result = gEngine().update(target, flags, ctx);
} else {
result = xpcEngineUpdate(target, flags, ctx);
}
if (__esp_enabled() && (flags & kSecAssessmentFlagDirect)) {
CFTemp<CFDictionaryRef> dict("{target=%O, flags=%d, context=%O, outcome=%O}", target, flags, context, (CFDictionaryRef)result);
__esp_notify_ns("cs-assessment-update", (void *)(CFDictionaryRef)dict);
}
traceUpdate(target, context, result);
return result.yield();
END_CSAPI_ERRORS1(false)
}
Boolean SecAssessmentControl(CFStringRef control, void *arguments, CFErrorRef *errors)
{
BEGIN_CSAPI
CFTemp<CFDictionaryRef> dict("{control=%O}", control);
esp_do_check("cs-assessment-control", dict);
if (CFEqual(control, CFSTR("ui-enable"))) {
setAssessment(true);
MessageTrace trace("com.apple.security.assessment.state", "enable");
trace.send("enable assessment outcomes");
return true;
} else if (CFEqual(control, CFSTR("ui-disable"))) {
setAssessment(false);
MessageTrace trace("com.apple.security.assessment.state", "disable");
trace.send("disable assessment outcomes");
return true;
} else if (CFEqual(control, CFSTR("ui-status"))) {
CFBooleanRef &result = *(CFBooleanRef*)(arguments);
if (overrideAssessment())
result = kCFBooleanFalse;
else
result = kCFBooleanTrue;
return true;
} else if (CFEqual(control, CFSTR("ui-enable-devid"))) {
CFTemp<CFDictionaryRef> ctx("{%O=%s}", kSecAssessmentUpdateKeyLabel, "Developer ID");
if (CFDictionaryRef result = gEngine().enable(NULL, kAuthorityInvalid, kSecCSDefaultFlags, ctx))
CFRelease(result);
MessageTrace trace("com.apple.security.assessment.state", "enable-devid");
trace.send("enable Developer ID approval");
return true;
} else if (CFEqual(control, CFSTR("ui-disable-devid"))) {
CFTemp<CFDictionaryRef> ctx("{%O=%s}", kSecAssessmentUpdateKeyLabel, "Developer ID");
if (CFDictionaryRef result = gEngine().disable(NULL, kAuthorityInvalid, kSecCSDefaultFlags, ctx))
CFRelease(result);
MessageTrace trace("com.apple.security.assessment.state", "disable-devid");
trace.send("disable Developer ID approval");
return true;
} else if (CFEqual(control, CFSTR("ui-get-devid"))) {
CFBooleanRef &result = *(CFBooleanRef*)(arguments);
if (gEngine().value<int>("SELECT disabled FROM authority WHERE label = 'Developer ID';", true))
result = kCFBooleanFalse;
else
result = kCFBooleanTrue;
return true;
} else if (CFEqual(control, CFSTR("ui-record-reject"))) {
xpcEngineRecord(CFDictionaryRef(arguments));
return true;
} else if (CFEqual(control, CFSTR("ui-record-reject-local"))) {
gEngine().recordFailure(CFDictionaryRef(arguments));
return true;
} else if (CFEqual(control, CFSTR("ui-recall-reject"))) {
CFDictionaryRef &result = *(CFDictionaryRef*)(arguments);
CFRef<CFDataRef> infoData = cfLoadFile(lastRejectFile);
if (infoData)
result = makeCFDictionaryFrom(infoData);
else
result = NULL;
return true;
} else
MacOSError::throwMe(errSecCSInvalidAttributeValues);
END_CSAPI_ERRORS1(false)
}