X-Git-Url: https://git.saurik.com/apple/security.git/blobdiff_plain/80e2389990082500d76eb566d4946be3e786c3ef..d8f41ccd20de16f8ebe2ccc84d47bf1cb2b26bbb:/Security/libsecurity_codesigning/lib/SecAssessment.cpp?ds=sidebyside diff --git a/Security/libsecurity_codesigning/lib/SecAssessment.cpp b/Security/libsecurity_codesigning/lib/SecAssessment.cpp new file mode 100644 index 00000000..60da620f --- /dev/null +++ b/Security/libsecurity_codesigning/lib/SecAssessment.cpp @@ -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 +#include +#include +#include +#include +#include + +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 path; + AuthorityType type; + CFRef 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 gDatabase; + + +// +// An on-demand instance of the policy engine +// +ModuleNexus 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 result = makeCFMutableDictionary(); + + SYSPOLICY_ASSESS_API(cfString(path).c_str(), int(type), flags); + + try { + if (__esp_enabled() && (flags & kSecAssessmentFlagDirect)) { + CFTemp 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 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 bundle = CFBundleCreate(NULL, target)) { + if (CFStringRef ident = CFBundleGetIdentifier(bundle)) + identifier = cfString(ident); + if (CFStringRef vers = CFStringRef(CFBundleGetValueForInfoDictionaryKey(bundle, CFSTR("CFBundleShortVersionString")))) + version = cfString(vers); + } + + CFRef 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 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 adulterated = makeCFMutableDictionary(result.get()); + CFDictionarySetValue(adulterated, kSecAssessmentAssessmentVerdict, kCFBooleanTrue); + if (CFDictionaryRef authority = CFDictionaryRef(CFDictionaryGetValue(adulterated, kSecAssessmentAssessmentAuthority))) { + CFRef 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 result; + + if (flags & kSecAssessmentFlagDirect) { + if (__esp_enabled()) { + CFTemp 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 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 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 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 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("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 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) +}