--- /dev/null
+/*
+ * Copyright (c) 2011 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>
+
+using namespace CodeSigning;
+
+
+//
+// CF Objects
+//
+struct _SecAssessment : private CFRuntimeBase {
+public:
+ _SecAssessment(CFURLRef p, CFDictionaryRef r) : path(p), result(r) { }
+
+ CFCopyRef<CFURLRef> path;
+ 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 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 kSecAssessmentAssessmentFromCache = CFSTR("assessment:authority:cached");
+
+CFStringRef kDisabledOverride = CFSTR("security disabled");
+
+CFStringRef kSecAssessmentContextKeyCertificates = CFSTR("context:certificates"); // obsolete
+
+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 {
+ // check the object cache first unless caller denied that or we need extended processing
+ if (!(flags & (kSecAssessmentFlagRequestOrigin | kSecAssessmentFlagIgnoreCache))) {
+ if (gDatabase().checkCache(path, type, result))
+ return new SecAssessment(path, result.yield());
+ }
+
+ 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())
+ 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())
+ throw; // let it go as an error
+ cfadd(result, "{%O=#F}", kSecAssessmentAssessmentVerdict);
+ }
+ return new SecAssessment(path, result.yield());
+
+ END_CSAPI_ERRORS1(NULL)
+}
+
+
+static void traceResult(SecAssessment &assessment, CFDictionaryRef result)
+{
+ if (CFDictionaryGetValue(result, CFSTR("assessment:remote")))
+ return; // just traced in syspolicyd
+
+ CFRef<CFURLRef> url = CFURLCopyAbsoluteURL(assessment.path);
+ string sanitized = cfString(url);
+ string::size_type rslash = sanitized.rfind('/');
+ if (rslash != string::npos)
+ sanitized = sanitized.substr(rslash+1);
+ string::size_type dot = sanitized.rfind('.');
+ if (dot != string::npos)
+ sanitized = sanitized.substr(dot+1);
+ else
+ sanitized = "(none)";
+
+ string identifier = "UNBUNDLED";
+ if (CFRef<CFBundleRef> bundle = CFBundleCreate(NULL, assessment.path))
+ if (CFStringRef ident = CFBundleGetIdentifier(bundle))
+ identifier = cfString(ident);
+
+ string authority = "UNSPECIFIED";
+ bool 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))
+ overridden = true;
+ }
+
+ MessageTrace trace("com.apple.security.assessment.outcome", NULL);
+ trace.add("signature2", "bundle:%s", identifier.c_str());
+ if (CFDictionaryGetValue(result, kSecAssessmentAssessmentVerdict) == kCFBooleanFalse) {
+ trace.add("signature", "denied:%s", authority.c_str());
+ trace.add("signature3", sanitized.c_str());
+ trace.send("assessment denied for %s", sanitized.c_str());
+ } else if (overridden) {
+ trace.add("signature", "override:%s", authority.c_str());
+ trace.add("signature3", sanitized.c_str());
+ trace.send("assessment denied for %s but overridden", sanitized.c_str());
+ } else {
+ trace.add("signature", "granted:%s", authority.c_str());
+ trace.add("signature3", sanitized.c_str());
+ trace.send("assessment granted for %s by %s", sanitized.c_str(), authority.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 (!(flags & kSecAssessmentFlagEnforce) && overrideAssessment()) {
+ // turn rejections into approvals, but note that we did that
+ if (CFDictionaryGetValue(result, kSecAssessmentAssessmentVerdict) == 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(adulterated, kSecAssessmentAssessmentAuthority, authority2);
+ } else {
+ cfadd(adulterated, "{%O={%O=%O}}",
+ kSecAssessmentAssessmentAuthority, kSecAssessmentAssessmentAuthorityOverride, kDisabledOverride);
+ }
+ result = adulterated.get();
+ }
+ }
+ traceResult(assessment, 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);
+
+ if (flags & kSecAssessmentFlagDirect) {
+ // ask the engine right here to do its thing
+ return gEngine().update(target, flags, ctx);
+ } else {
+ // relay the question to our daemon for consideration
+ return xpcEngineUpdate(target, flags, ctx);
+ }
+
+ END_CSAPI_ERRORS1(false)
+}
+
+
+//
+// The fcntl of System Policies.
+// For those very special requests.
+//
+Boolean SecAssessmentControl(CFStringRef control, void *arguments, CFErrorRef *errors)
+{
+ BEGIN_CSAPI
+
+ 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);
+ 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);
+ 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
+ MacOSError::throwMe(errSecCSInvalidAttributeValues);
+
+ END_CSAPI_ERRORS1(false)
+}