--- /dev/null
+/*
+ * Copyright (c) 2011-2014 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@
+ */
+#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);
+}
+
+//
+// CF Objects
+//
+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; }
+
+ // CF Boiler-plate
+ 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, // version
+ "SecAssessment", // name
+ NULL, // init
+ NULL, // copy
+ SecAssessment::finalize, // finalize
+ NULL, // equal
+ NULL, // hash
+ NULL, // formatting
+ NULL // debug string
+};
+
+
+static dispatch_once_t assessmentOnce;
+CFTypeID assessmentType = _kCFRuntimeNotATypeID;
+
+CFTypeID SecAssessmentGetTypeID()
+{
+ dispatch_once(&assessmentOnce, ^void() {
+ if ((assessmentType = _CFRuntimeRegisterClass(&assessmentClass)) == _kCFRuntimeNotATypeID)
+ abort();
+ });
+ return assessmentType;
+}
+
+
+//
+// Common dictionary constants
+//
+CFStringRef kSecAssessmentContextKeyOperation = CFSTR("operation");
+CFStringRef kSecAssessmentOperationTypeExecute = CFSTR("operation:execute");
+CFStringRef kSecAssessmentOperationTypeInstall = CFSTR("operation:install");
+CFStringRef kSecAssessmentOperationTypeOpenDocument = CFSTR("operation:lsopen");
+
+
+//
+// Read-only in-process access to the policy database
+//
+class ReadPolicy : public PolicyDatabase {
+public:
+ ReadPolicy() : PolicyDatabase(defaultDatabase) { }
+};
+ModuleNexus<ReadPolicy> gDatabase;
+
+
+//
+// An on-demand instance of the policy engine
+//
+ModuleNexus<PolicyEngine> gEngine;
+
+
+//
+// Policy evaluation ("assessment") operations
+//
+CFStringRef kSecAssessmentContextKeyFeedback = CFSTR("context:feedback");
+CFStringRef kSecAssessmentFeedbackProgress = CFSTR("feedback:progress");
+CFStringRef kSecAssessmentFeedbackInfoCurrent = CFSTR("current");
+CFStringRef kSecAssessmentFeedbackInfoTotal = CFSTR("total");
+
+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 kSecAssessmentAssessmentWeakSignature = CFSTR("assessment:authority:weak");
+CFStringRef kSecAssessmentAssessmentCodeSigningError = CFSTR("assessment:cserror");
+
+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 & kSecAssessmentFlagDirect) {
+ // ask the engine right here to do its thing
+ SYSPOLICY_ASSESS_LOCAL();
+ gEngine().evaluate(path, type, flags, context, result);
+ } else {
+ // relay the question to our daemon for consideration
+ 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; // let it go as an error
+ break;
+ }
+ // record the error we would have returned
+ cfadd(result, "{%O=#F,'assessment:error'=%d}}", kSecAssessmentAssessmentVerdict, error.osStatus());
+ } catch (...) {
+ // catch stray errors not conforming to the CommonError scheme
+ if (!overrideAssessment(flags))
+ throw; // let it go as an error
+ 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 // sentinel
+ };
+
+ 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; // just traced in syspolicyd
+
+ 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) { // would have failed except for override
+ trace.add("signature", "defeated:%s", authority.c_str());
+ trace.send("assessment denied for %s but overridden", sanitized.c_str());
+ } else if (old_overridden) { // would have succeeded even without override
+ 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)
+{
+ // only trace add operations on URL targets
+ 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());
+}
+
+
+//
+// At present, CopyResult simply retrieves the result already formed by Create.
+// In the future, this will be more lazy.
+//
+CFDictionaryRef SecAssessmentCopyResult(SecAssessmentRef assessmentRef,
+ SecAssessmentFlags flags,
+ CFErrorRef *errors)
+{
+ BEGIN_CSAPI
+
+ SecAssessment &assessment = SecAssessment::ref(assessmentRef);
+ CFCopyRef<CFDictionaryRef> result = assessment.result;
+ if (overrideAssessment(flags)) {
+ // turn rejections into approvals, but note that we did that
+ 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)
+}
+
+
+//
+// Policy editing operations.
+// These all make permanent changes to the system-wide authority records.
+//
+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;
+ }
+
+ // ask the engine right here to do its thing
+ result = gEngine().update(target, flags, ctx);
+ } else {
+ // relay the question to our daemon for consideration
+ 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)
+}
+
+
+//
+// The fcntl of System Policies.
+// For those very special requests.
+//
+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"))) {
+ // send this through syspolicyd for update validation
+ xpcEngineRecord(CFDictionaryRef(arguments));
+ return true;
+ } else if (CFEqual(control, CFSTR("ui-record-reject-local"))) {
+ // perform the local operation (requires root)
+ gEngine().recordFailure(CFDictionaryRef(arguments));
+ return true;
+ } else if (CFEqual(control, CFSTR("ui-recall-reject"))) {
+ // no special privileges required for this, so read directly
+ CFDictionaryRef &result = *(CFDictionaryRef*)(arguments);
+ CFRef<CFDataRef> infoData = cfLoadFile(lastRejectFile);
+ if (infoData)
+ result = makeCFDictionaryFrom(infoData);
+ else
+ result = NULL;
+ return true;
+ } else if (CFEqual(control, CFSTR("rearm-status"))) {
+ CFTimeInterval &result = *(CFTimeInterval*)(arguments);
+ if (!queryRearmTimer(result))
+ result = 0;
+ return true;
+ } else
+ MacOSError::throwMe(errSecCSInvalidAttributeValues);
+
+ END_CSAPI_ERRORS1(false)
+}