X-Git-Url: https://git.saurik.com/apple/security.git/blobdiff_plain/72a12576750f52947eb043106ba5c12c0d07decf..b1ab9ed8d0e0f1c3b66d7daa8fd5564444c56195:/libsecurity_codesigning/lib/SecAssessment.cpp?ds=inline diff --git a/libsecurity_codesigning/lib/SecAssessment.cpp b/libsecurity_codesigning/lib/SecAssessment.cpp new file mode 100644 index 00000000..7acc660a --- /dev/null +++ b/libsecurity_codesigning/lib/SecAssessment.cpp @@ -0,0 +1,387 @@ +/* + * 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 +#include +#include +#include +#include + +using namespace CodeSigning; + + +// +// CF Objects +// +struct _SecAssessment : private CFRuntimeBase { +public: + _SecAssessment(CFURLRef p, CFDictionaryRef r) : path(p), result(r) { } + + CFCopyRef path; + 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 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 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 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 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 result = assessment.result; + if (!(flags & kSecAssessmentFlagEnforce) && overrideAssessment()) { + // turn rejections into approvals, but note that we did that + if (CFDictionaryGetValue(result, kSecAssessmentAssessmentVerdict) == 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(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 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 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("SELECT disabled FROM authority WHERE label = 'Developer ID';", true)) + result = kCFBooleanFalse; + else + result = kCFBooleanTrue; + return true; + } else + MacOSError::throwMe(errSecCSInvalidAttributeValues); + + END_CSAPI_ERRORS1(false) +}