]> git.saurik.com Git - apple/security.git/blobdiff - Security/libsecurity_codesigning/lib/SecAssessment.cpp
Security-57031.1.35.tar.gz
[apple/security.git] / Security / libsecurity_codesigning / lib / SecAssessment.cpp
diff --git a/Security/libsecurity_codesigning/lib/SecAssessment.cpp b/Security/libsecurity_codesigning/lib/SecAssessment.cpp
new file mode 100644 (file)
index 0000000..60da620
--- /dev/null
@@ -0,0 +1,525 @@
+/*
+ * 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)
+}