From 935e692843d9c528f9a4c5eee98e00961ca5f4a4 Mon Sep 17 00:00:00 2001 From: Apple Date: Mon, 26 Mar 2012 18:47:55 +0000 Subject: [PATCH] libsecurity_codesigning-55037.6.tar.gz --- lib/CSCommon.h | 2 + lib/SecAssessment.cpp | 95 ++-- lib/SecAssessment.h | 81 ++- lib/SecCode.cpp | 3 +- lib/SecStaticCode.cpp | 3 +- lib/StaticCode.cpp | 241 ++++++--- lib/StaticCode.h | 17 +- lib/bundlediskrep.cpp | 3 +- lib/codedirectory.cpp | 12 + lib/codedirectory.h | 2 + lib/policydb.cpp | 93 +++- lib/policydb.h | 22 +- lib/policyengine.cpp | 469 ++++++++++++++---- lib/policyengine.h | 13 +- lib/quarantine++.cpp | 107 ++++ lib/quarantine++.h | 77 +++ lib/reqdumper.cpp | 2 +- lib/security_codesigning.exp | 10 +- lib/syspolicy.sql | 153 ++++-- lib/xpcengine.cpp | 50 +- lib/xpcengine.h | 1 + .../project.pbxproj | 33 +- requirements.grammar | 2 +- 23 files changed, 1182 insertions(+), 309 deletions(-) create mode 100644 lib/quarantine++.cpp create mode 100644 lib/quarantine++.h diff --git a/lib/CSCommon.h b/lib/CSCommon.h index 4aae170..3c87b62 100644 --- a/lib/CSCommon.h +++ b/lib/CSCommon.h @@ -89,6 +89,7 @@ enum { errSecCSInfoPlistFailed = -67030, /* invalid Info.plist (plist or signature have been modified) */ errSecCSNoMainExecutable = -67029, /* the code has no main executable file */ errSecCSBadBundleFormat = -67028, /* bundle format unrecognized, invalid, or unsuitable */ + errSecCSNoMatches = -67027, /* no matches for search or update operation */ }; @@ -176,6 +177,7 @@ enum { kSecCSDefaultFlags = 0, /* no particular flags (default behavior) */ kSecCSConsiderExpiration = 1 << 31, /* consider expired certificates invalid */ + kSecCSEnforceRevocationChecks = 1 << 30, /* force revocation checks regardless of preference settings */ }; diff --git a/lib/SecAssessment.cpp b/lib/SecAssessment.cpp index d5a5a89..fbecc41 100644 --- a/lib/SecAssessment.cpp +++ b/lib/SecAssessment.cpp @@ -30,6 +30,7 @@ #include #include #include +#include using namespace CodeSigning; @@ -114,42 +115,9 @@ ModuleNexus gDatabase; ModuleNexus gEngine; -// -// Help mapping API-ish CFString keys to more convenient internal enumerations -// -typedef struct { - CFStringRef cstring; - uint enumeration; -} StringMap; - -static uint mapEnum(CFDictionaryRef context, CFStringRef attr, const StringMap *map, uint value = 0) -{ - if (context) - if (CFTypeRef value = CFDictionaryGetValue(context, attr)) - for (const StringMap *mp = map; mp->cstring; ++mp) - if (CFEqual(mp->cstring, value)) - return mp->enumeration; - if (value) - return value; - MacOSError::throwMe(errSecCSInvalidAttributeValues); -} - -static const StringMap mapType[] = { - { kSecAssessmentOperationTypeExecute, kAuthorityExecute }, - { kSecAssessmentOperationTypeInstall, kAuthorityInstall }, - { kSecAssessmentOperationTypeOpenDocument, kAuthorityOpenDoc }, - { NULL } -}; - -static AuthorityType typeFor(CFDictionaryRef context, AuthorityType type = kAuthorityInvalid) -{ return mapEnum(context, kSecAssessmentContextKeyOperation, mapType, type); } - - // // Policy evaluation ("assessment") operations // -const CFStringRef kSecAssessmentContextKeyCertificates = CFSTR("context:certificates"); - const CFStringRef kSecAssessmentAssessmentVerdict = CFSTR("assessment:verdict"); const CFStringRef kSecAssessmentAssessmentOriginator = CFSTR("assessment:originator"); const CFStringRef kSecAssessmentAssessmentAuthority = CFSTR("assessment:authority"); @@ -158,6 +126,8 @@ const CFStringRef kSecAssessmentAssessmentAuthorityRow = CFSTR("assessment:autho const CFStringRef kSecAssessmentAssessmentAuthorityOverride = CFSTR("assessment:authority:override"); const CFStringRef kSecAssessmentAssessmentFromCache = CFSTR("assessment:authority:cached"); +const CFStringRef kSecAssessmentContextKeyCertificates = CFSTR("context:certificates"); // obsolete + SecAssessmentRef SecAssessmentCreate(CFURLRef path, SecAssessmentFlags flags, CFDictionaryRef context, @@ -189,10 +159,18 @@ SecAssessmentRef SecAssessmentCreate(CFURLRef path, xpcEngineAssess(path, flags, context, result); } } catch (CommonError &error) { - if (!overrideAssessment()) - throw; // let it go as an 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); @@ -289,16 +267,23 @@ CFDictionaryRef SecAssessmentCopyResult(SecAssessmentRef assessmentRef, // -// Policy editing operations +// Policy editing operations. +// These all make permanent changes to the system-wide authority records. // const CFStringRef kSecAssessmentContextKeyUpdate = CFSTR("update"); -const CFStringRef kSecAssessmentUpdateOperationAddFile = CFSTR("update:addfile"); -const CFStringRef kSecAssessmentUpdateOperationRemoveFile = CFSTR("update:removefile"); +const CFStringRef kSecAssessmentUpdateOperationAdd = CFSTR("update:add"); +const CFStringRef kSecAssessmentUpdateOperationRemove = CFSTR("update:remove"); +const CFStringRef kSecAssessmentUpdateOperationEnable = CFSTR("update:enable"); +const CFStringRef kSecAssessmentUpdateOperationDisable = CFSTR("update:disable"); +const CFStringRef kSecAssessmentUpdateKeyAuthorization = CFSTR("update:authorization"); const CFStringRef kSecAssessmentUpdateKeyPriority = CFSTR("update:priority"); const CFStringRef kSecAssessmentUpdateKeyLabel = CFSTR("update:label"); +const CFStringRef kSecAssessmentUpdateKeyExpires = CFSTR("update:expires"); +const CFStringRef kSecAssessmentUpdateKeyAllow = CFSTR("update:allow"); +const CFStringRef kSecAssessmentUpdateKeyRemarks = CFSTR("update:remarks"); -Boolean SecAssessmentUpdate(CFURLRef path, +Boolean SecAssessmentUpdate(CFTypeRef target, SecAssessmentFlags flags, CFDictionaryRef context, CFErrorRef *errors) @@ -306,15 +291,14 @@ Boolean SecAssessmentUpdate(CFURLRef path, BEGIN_CSAPI CFDictionary ctx(context, errSecCSInvalidAttributeValues); - CFStringRef edit = ctx.get(kSecAssessmentContextKeyUpdate); - AuthorityType type = typeFor(context); - if (edit == kSecAssessmentUpdateOperationAddFile) - return gEngine().add(path, type, flags, context); - else if (edit == kSecAssessmentUpdateOperationRemoveFile) - MacOSError::throwMe(errSecCSUnimplemented); - else - MacOSError::throwMe(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) } @@ -329,12 +313,14 @@ Boolean SecAssessmentControl(CFStringRef control, void *arguments, CFErrorRef *e BEGIN_CSAPI if (CFEqual(control, CFSTR("ui-enable"))) { - UnixPlusPlus::AutoFileDesc flagFile(visibleSecurityFlagFile, O_CREAT | O_WRONLY, 0644); + { UnixPlusPlus::AutoFileDesc flagFile(visibleSecurityFlagFile, O_CREAT | O_WRONLY, 0644); } + notify_post(kNotifySecAssessmentMasterSwitch); MessageTrace trace("com.apple.security.assessment.state", "enable"); trace.send("enable assessment outcomes"); return true; } else if (CFEqual(control, CFSTR("ui-disable"))) { if (::remove(visibleSecurityFlagFile) == 0 || errno == ENOENT) { + notify_post(kNotifySecAssessmentMasterSwitch); MessageTrace trace("com.apple.security.assessment.state", "disable"); trace.send("disable assessment outcomes"); return true; @@ -347,6 +333,19 @@ Boolean SecAssessmentControl(CFStringRef control, void *arguments, CFErrorRef *e else result = kCFBooleanTrue; return true; + } else if (CFEqual(control, CFSTR("ui-enable-devid"))) { + CFTemp ctx("{%O=%s}", kSecAssessmentUpdateKeyLabel, "Developer ID"); + return gEngine().enable(NULL, kAuthorityInvalid, kSecCSDefaultFlags, ctx); + } else if (CFEqual(control, CFSTR("ui-disable-devid"))) { + CFTemp ctx("{%O=%s}", kSecAssessmentUpdateKeyLabel, "Developer ID"); + return gEngine().disable(NULL, kAuthorityInvalid, kSecCSDefaultFlags, ctx); + } 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); diff --git a/lib/SecAssessment.h b/lib/SecAssessment.h index 546b139..9cae775 100644 --- a/lib/SecAssessment.h +++ b/lib/SecAssessment.h @@ -42,17 +42,27 @@ typedef struct _SecAssessment *SecAssessmentRef; CFTypeID SecAssessmentGetTypeID(); +/* + * Notifications sent when the policy authority database changes. + * (Should move to /usr/include/notify_keys.h eventually.) + */ +#define kNotifySecAssessmentMasterSwitch "com.apple.security.assessment.masterswitch" +#define kNotifySecAssessmentUpdate "com.apple.security.assessment.update" + + /*! - * Primary operation codes. These are operations the system policy can express + * Primary operation types. These are operations the system policy can express * opinions on. They are not operations *on* the system configuration itself. * (For those, see SecAssessmentUpdate below.) * * @constant kSecAssessmentContextKeyOperation Context key describing the type of operation - * being contemplated. - * @constant kSecAssessmentOperationTypeInstall Value denoting the operation of installing - * software into the system. + * being contemplated. The default varies depending on the API call used. * @constant kSecAssessmentOperationTypeExecute Value denoting the operation of running or executing * code on the system. + * @constant kSecAssessmentOperationTypeInstall Value denoting the operation of installing + * software into the system. + * @constant kSecAssessmentOperationTypeOpenDocument Value denoting the operation of opening + * (in the LaunchServices sense) of documents. */ extern const CFStringRef kSecAssessmentContextKeyOperation; // proposed operation extern const CFStringRef kSecAssessmentOperationTypeExecute; // .. execute code @@ -71,6 +81,8 @@ extern const CFStringRef kSecAssessmentOperationTypeOpenDocument; // .. LaunchSe evaluation of system policy. This may be substantially slower. @constant kSecAssessmentFlagNoCache Do not save any evaluation outcome in the system caches. Any content already there is left undisturbed. Independent of kSecAssessmentFlagIgnoreCache. + @constant kSecAssessmentFlagEnforce Perform normal operations even if assessments have been + globally bypassed (which would usually approve anything). Flags common to multiple calls are assigned from high-bit down. Flags for particular calls are assigned low-bit up, and are documented with that call. @@ -98,7 +110,7 @@ enum { @param context Optional CFDictionaryRef containing additional information bearing on the requested assessment. @param errors Standard CFError argument for reporting errors. Note that declining to permit - the proposed operation is not an error. Inability to form a judgment is. + the proposed operation is not an error. Inability to arrive at a judgment is. @result On success, a SecAssessment object that can be queried for its outcome. On error, NULL (with *errors set). @@ -111,10 +123,6 @@ enum { @constant kSecAssessmentContextKeyOperation Type of operation (see overview above). This defaults to the kSecAssessmentOperationTypeExecute. - @constant kSecAssessmentContextKeyEdit A CFArray of SecCertificateRefs describing the - certificate chain of a CMS-type signature as pulled from 'path' by the caller. - The first certificate is the signing certificate. The certificates provided must be - sufficient to construct a valid certificate chain. */ extern const CFStringRef kSecAssessmentContextKeyCertificates; // certificate chain as provided by caller @@ -141,8 +149,6 @@ SecAssessmentRef SecAssessmentCreate(CFURLRef path, Extract results from a completed assessment and return them as a CFDictionary. - Assessment result keys (dictionary keys returned on success): - @param assessment A SecAssessmentRef created with SecAssessmentCreate. @param flags Operation flags and options. Pass kSecAssessmentDefaultFlags for default behavior. @@ -152,6 +158,8 @@ SecAssessmentRef SecAssessmentCreate(CFURLRef path, data as requested by flags. The caller owns this dictionary and should release it when done with it. On error, NULL (with *errors set). + Assessment result keys (dictionary keys returned on success): + @constant kSecAssessmentAssessmentVerdict A CFBoolean value indicating whether the system policy allows (kCFBooleanTrue) or denies (kCFBooleanFalse) the proposed operation. @constant kSecAssessmentAssessmentAuthority A CFDictionary describing what sources of authority @@ -169,9 +177,10 @@ CFDictionaryRef SecAssessmentCopyResult(SecAssessmentRef assessment, @function SecAssessmentUpdate Make changes to the system policy configuration. - @param path CFURL describing the file central to an operation - the program - to be executed, archive to be installed, plugin to be loaded, etc. - Pass NULL if the requested operation has no file subject. + @param path CFTypeRef describing the subject of the operation. Depending on the operation, + this may be a CFURL denoting a (single) file or bundle; a SecRequirement describing + a group of files; a CFNumber denoting an existing rule by rule number, or NULL to perform + global changes. @param flags Operation flags and options. Pass kSecAssessmentDefaultFlags for default behavior. @param context Required CFDictionaryRef containing information bearing @@ -184,19 +193,42 @@ CFDictionaryRef SecAssessmentCopyResult(SecAssessmentRef assessment, @constant kSecAssessmentContextKeyEdit Required context key describing the kind of change requested to the system policy configuration. Currently understood values: - @constant kSecAssessmentUpdateOperationAddFile Request to add rules governing operations on the 'path' - argument. - @constant kSecAssessmentUpdateOperationRemoveFile Request to remove rules governing operations on the - 'path' argument. + @constant kSecAssessmentUpdateOperationAdd Add a new rule to the assessment rule database. + @constant kSecAssessmentUpdateOperationRemove Remove rules from the rule database. + @constant kSecAssessmentUpdateOperationEnable (Re)enable rules in the rule database. + @constant kSecAssessmentUpdateOperationDisable Disable rules in the rule database. + @constant kSecAssessmentUpdateKeyAuthorization A CFData containing the external form of a + system AuthorizationRef used to authorize the change. The call will automatically generate + a suitable authorization if this is missing; however, if the request is on behalf of + another client, an AuthorizationRef should be created there and passed along here. + @constant kSecAssessmentUpdateKeyPriority CFNumber denoting a (floating point) priority + for the rule(s) being processed. + @constant kSecAssessmentUpdateKeyLabel CFString denoting a label string applied to the rule(s) + being processed. + @constant kSecAssessmentUpdateKeyExpires CFDate denoting an (absolute, future) expiration date + for rule(s) being processed. + @constant kSecAssessmentUpdateKeyAllow CFBoolean denoting whether a new rule allows or denies + assessment. The default is to allow; set to kCFBooleanFalse to create a negative (denial) rule. + @constant kSecAssessmentUpdateKeyRemarks CFString containing a colloquial description or comment + about a newly created rule. This is mean to be human readable and is not used when evaluating rules. */ extern const CFStringRef kSecAssessmentContextKeyUpdate; // proposed operation -extern const CFStringRef kSecAssessmentUpdateOperationAddFile; // add to policy database -extern const CFStringRef kSecAssessmentUpdateOperationRemoveFile; // remove from policy database +extern const CFStringRef kSecAssessmentUpdateOperationAdd; // add rule to policy database +extern const CFStringRef kSecAssessmentUpdateOperationRemove; // remove rule from policy database +extern const CFStringRef kSecAssessmentUpdateOperationEnable; // enable rule(s) in policy database +extern const CFStringRef kSecAssessmentUpdateOperationDisable; // disable rule(s) in policy database + +extern const CFStringRef kSecAssessmentUpdateKeyAuthorization; // [CFData] external form of governing authorization extern const CFStringRef kSecAssessmentUpdateKeyPriority; // rule priority extern const CFStringRef kSecAssessmentUpdateKeyLabel; // rule label +extern const CFStringRef kSecAssessmentUpdateKeyExpires; // rule expiration +extern const CFStringRef kSecAssessmentUpdateKeyAllow; // rule outcome (allow/deny) +extern const CFStringRef kSecAssessmentUpdateKeyRemarks; // rule remarks (human readable) -Boolean SecAssessmentUpdate(CFURLRef path, +extern const CFStringRef kSecAssessmentContextKeyCertificates; // obsolete + +Boolean SecAssessmentUpdate(CFTypeRef target, SecAssessmentFlags flags, CFDictionaryRef context, CFErrorRef *errors); @@ -204,7 +236,12 @@ Boolean SecAssessmentUpdate(CFURLRef path, /*! @function SecAssessmentControl - Miscellaneous system policy operations + Miscellaneous system policy operations. + + @param control A CFString indicating which operation is requested. + @param arguments Arguments to the operation as documented for control. + @param errors Standard CFErrorRef * argument to report errors. + @result Returns True on success. Returns False on failure (and sets *errors). */ Boolean SecAssessmentControl(CFStringRef control, void *arguments, CFErrorRef *errors); diff --git a/lib/SecCode.cpp b/lib/SecCode.cpp index fc64df4..779a7d3 100644 --- a/lib/SecCode.cpp +++ b/lib/SecCode.cpp @@ -197,7 +197,8 @@ OSStatus SecCodeCheckValidityWithErrors(SecCodeRef codeRef, SecCSFlags flags, BEGIN_CSAPI checkFlags(flags, - kSecCSConsiderExpiration); + kSecCSConsiderExpiration + | kSecCSEnforceRevocationChecks); SecPointer code = SecCode::required(codeRef); code->checkValidity(flags); if (const SecRequirement *req = SecRequirement::optional(requirementRef)) diff --git a/lib/SecStaticCode.cpp b/lib/SecStaticCode.cpp index 3669928..7cb5a17 100644 --- a/lib/SecStaticCode.cpp +++ b/lib/SecStaticCode.cpp @@ -98,7 +98,7 @@ static void validateNested(string location, const SecRequirement *req, SecCSFlag static void validate(SecStaticCode *code, const SecRequirement *req, SecCSFlags flags) { try { - code->validateDirectory(); + code->validateNonResourceComponents(); // also validates the CodeDirectory if (!(flags & kSecCSDoNotValidateExecutable)) code->validateExecutable(); if (!(flags & kSecCSDoNotValidateResources)) @@ -187,6 +187,7 @@ OSStatus SecStaticCodeCheckValidityWithErrors(SecStaticCodeRef staticCodeRef, Se | kSecCSDoNotValidateExecutable | kSecCSDoNotValidateResources | kSecCSConsiderExpiration + | kSecCSEnforceRevocationChecks | kSecCSCheckNestedCode); SecPointer code = SecStaticCode::requiredStatic(staticCodeRef); diff --git a/lib/StaticCode.cpp b/lib/StaticCode.cpp index cec0b2b..956c88f 100644 --- a/lib/StaticCode.cpp +++ b/lib/StaticCode.cpp @@ -42,6 +42,7 @@ #include #include #include +#include #include #include #include @@ -58,7 +59,7 @@ using namespace UnixPlusPlus; // SecStaticCode::SecStaticCode(DiskRep *rep) : mRep(rep), - mValidated(false), mExecutableValidated(false), + mValidated(false), mExecutableValidated(false), mResourcesValidated(false), mResourcesValidContext(NULL), mDesignatedReq(NULL), mGotResourceBase(false), mEvalDetails(NULL) { CODESIGN_STATIC_CREATE(this, rep); @@ -72,6 +73,8 @@ SecStaticCode::SecStaticCode(DiskRep *rep) SecStaticCode::~SecStaticCode() throw() try { ::free(const_cast(mDesignatedReq)); + if (mResourcesValidContext) + delete mResourcesValidContext; } catch (...) { return; } @@ -186,6 +189,11 @@ void SecStaticCode::resetValidity() CODESIGN_EVAL_STATIC_RESET(this); mValidated = false; mExecutableValidated = false; + mResourcesValidated = false; + if (mResourcesValidContext) { + delete mResourcesValidContext; + mResourcesValidContext = NULL; + } mDir = NULL; mSignature = NULL; for (unsigned n = 0; n < cdSlotCount; n++) @@ -303,10 +311,7 @@ void SecStaticCode::validateDirectory() CODESIGN_EVAL_STATIC_DIRECTORY(this); mValidationExpired = verifySignature(); component(cdInfoSlot, errSecCSInfoPlistFailed); // force load of Info Dictionary (if any) - CodeDirectory::SpecialSlot slot = codeDirectory()->nSpecialSlots; - if (slot > cdSlotMax) // might have more special slots than we know about... - slot = cdSlotMax; // ... but only process the ones we understand - for ( ; slot >= 1; --slot) + for (CodeDirectory::SpecialSlot slot = codeDirectory()->maxSpecialSlot(); slot >= 1; --slot) if (mCache[slot]) // if we already loaded that resource... validateComponent(slot); // ... then check it now mValidated = true; // we've done the deed... @@ -332,6 +337,24 @@ void SecStaticCode::validateDirectory() } +// +// Load and validate the CodeDirectory and all components *except* those related to the resource envelope. +// Those latter components are checked by validateResources(). +// +void SecStaticCode::validateNonResourceComponents() +{ + this->validateDirectory(); + for (CodeDirectory::SpecialSlot slot = codeDirectory()->maxSpecialSlot(); slot >= 1; --slot) + switch (slot) { + case cdResourceDirSlot: // validated by validateResources + break; + default: + this->component(slot); // loads and validates + break; + } +} + + // // Get the (signed) signing date from the code signature. // Sadly, we need to validate the signature to get the date (as a side benefit). @@ -373,8 +396,9 @@ bool SecStaticCode::verifySignature() MacOSError::check(CMSDecoderSetDetachedContent(cms, mDir)); MacOSError::check(CMSDecoderFinalizeMessage(cms)); MacOSError::check(CMSDecoderSetSearchKeychain(cms, cfEmptyArray())); + CFRef policy = verificationPolicy(apiFlags()); CMSSignerStatus status; - MacOSError::check(CMSDecoderCopySignerStatus(cms, 0, verificationPolicy(), + MacOSError::check(CMSDecoderCopySignerStatus(cms, 0, policy, false, &status, &mTrust.aref(), NULL)); if (status != kCMSSignerValid) MacOSError::throwMe(errSecCSSignatureFailed); @@ -447,14 +471,47 @@ bool SecStaticCode::verifySignature() // // Return the TP policy used for signature verification. -// This policy object is cached and reused. +// This may be a simple SecPolicyRef or a CFArray of policies. +// The caller owns the return value. // -SecPolicyRef SecStaticCode::verificationPolicy() +static SecPolicyRef makeCRLPolicy() +{ + CFRef policy; + MacOSError::check(SecPolicyCopy(CSSM_CERT_X_509v3, &CSSMOID_APPLE_TP_REVOCATION_CRL, &policy.aref())); + CSSM_APPLE_TP_CRL_OPTIONS options; + memset(&options, 0, sizeof(options)); + options.Version = CSSM_APPLE_TP_CRL_OPTS_VERSION; + options.CrlFlags = CSSM_TP_ACTION_FETCH_CRL_FROM_NET | CSSM_TP_ACTION_CRL_SUFFICIENT; + CSSM_DATA optData = { sizeof(options), (uint8 *)&options }; + MacOSError::check(SecPolicySetValue(policy, &optData)); + return policy.yield(); +} + +static SecPolicyRef makeOCSPPolicy() { - if (!mPolicy) - MacOSError::check(SecPolicyCopy(CSSM_CERT_X_509v3, - &CSSMOID_APPLE_TP_CODE_SIGNING, &mPolicy.aref())); - return mPolicy; + CFRef policy; + MacOSError::check(SecPolicyCopy(CSSM_CERT_X_509v3, &CSSMOID_APPLE_TP_REVOCATION_OCSP, &policy.aref())); + CSSM_APPLE_TP_OCSP_OPTIONS options; + memset(&options, 0, sizeof(options)); + options.Version = CSSM_APPLE_TP_OCSP_OPTS_VERSION; + options.Flags = CSSM_TP_ACTION_OCSP_SUFFICIENT; + CSSM_DATA optData = { sizeof(options), (uint8 *)&options }; + MacOSError::check(SecPolicySetValue(policy, &optData)); + return policy.yield(); +} + +CFTypeRef SecStaticCode::verificationPolicy(SecCSFlags flags) +{ + CFRef core; + MacOSError::check(SecPolicyCopy(CSSM_CERT_X_509v3, + &CSSMOID_APPLE_TP_CODE_SIGNING, &core.aref())); + if (flags & kSecCSEnforceRevocationChecks) { + CFRef crl = makeCRLPolicy(); + CFRef ocsp = makeOCSPPolicy(); + return makeCFArray(3, core.get(), crl.get(), ocsp.get()); + } else { + return core.yield(); + } } @@ -487,29 +544,43 @@ void SecStaticCode::validateComponent(CodeDirectory::SpecialSlot slot, OSStatus // void SecStaticCode::validateExecutable() { - DTRACK(CODESIGN_EVAL_STATIC_EXECUTABLE, this, - (char*)this->mainExecutablePath().c_str(), codeDirectory()->nCodeSlots); - const CodeDirectory *cd = this->codeDirectory(); - if (!cd) - MacOSError::throwMe(errSecCSUnsigned); - AutoFileDesc fd(mainExecutablePath(), O_RDONLY); - fd.fcntl(F_NOCACHE, true); // turn off page caching (one-pass) - if (Universal *fat = mRep->mainExecutableImage()) - fd.seek(fat->archOffset()); - size_t pageSize = cd->pageSize ? (1 << cd->pageSize) : 0; - size_t remaining = cd->codeLimit; - for (size_t slot = 0; slot < cd->nCodeSlots; ++slot) { - size_t size = min(remaining, pageSize); - if (!cd->validateSlot(fd, size, slot)) { - CODESIGN_EVAL_STATIC_EXECUTABLE_FAIL(this, slot); - mExecutableValidated = true; // we tried - mExecutableValid = false; // it failed - MacOSError::throwMe(errSecCSSignatureFailed); + if (!validatedExecutable()) { + try { + DTRACK(CODESIGN_EVAL_STATIC_EXECUTABLE, this, + (char*)this->mainExecutablePath().c_str(), codeDirectory()->nCodeSlots); + const CodeDirectory *cd = this->codeDirectory(); + if (!cd) + MacOSError::throwMe(errSecCSUnsigned); + AutoFileDesc fd(mainExecutablePath(), O_RDONLY); + fd.fcntl(F_NOCACHE, true); // turn off page caching (one-pass) + if (Universal *fat = mRep->mainExecutableImage()) + fd.seek(fat->archOffset()); + size_t pageSize = cd->pageSize ? (1 << cd->pageSize) : 0; + size_t remaining = cd->codeLimit; + for (size_t slot = 0; slot < cd->nCodeSlots; ++slot) { + size_t size = min(remaining, pageSize); + if (!cd->validateSlot(fd, size, slot)) { + CODESIGN_EVAL_STATIC_EXECUTABLE_FAIL(this, slot); + MacOSError::throwMe(errSecCSSignatureFailed); + } + remaining -= size; + } + mExecutableValidated = true; + mExecutableValidResult = noErr; + } catch (const CommonError &err) { + mExecutableValidated = true; + mExecutableValidResult = err.osStatus(); + throw; + } catch (...) { + secdebug("staticCode", "%p executable validation threw non-common exception", this); + mExecutableValidated = true; + mExecutableValidResult = errSecCSInternalError; + throw; } - remaining -= size; } - mExecutableValidated = true; // we tried - mExecutableValid = true; // it worked + assert(validatedExecutable()); + if (mExecutableValidResult != noErr) + MacOSError::throwMe(mExecutableValidResult); } @@ -522,48 +593,68 @@ void SecStaticCode::validateExecutable() // void SecStaticCode::validateResources() { - // sanity first - CFDictionaryRef sealedResources = resourceDictionary(); - if (this->resourceBase()) // disk has resources - if (sealedResources) - /* go to work below */; - else - MacOSError::throwMe(errSecCSResourcesNotFound); - else // disk has no resources - if (sealedResources) - MacOSError::throwMe(errSecCSResourcesNotFound); - else - return; // no resources, not sealed - fine (no work) - - // found resources, and they are sealed - CFDictionaryRef rules = cfget(sealedResources, "rules"); - CFDictionaryRef files = cfget(sealedResources, "files"); - DTRACK(CODESIGN_EVAL_STATIC_RESOURCES, this, - (char*)this->mainExecutablePath().c_str(), int(CFDictionaryGetCount(files))); - - // make a shallow copy of the ResourceDirectory so we can "check off" what we find - CFRef resourceMap = makeCFMutableDictionary(files); - - // scan through the resources on disk, checking each against the resourceDirectory - CollectingContext ctx(*this); // collect all failures in here - ResourceBuilder resources(cfString(this->resourceBase()), rules, codeDirectory()->hashType); - mRep->adjustResources(resources); - string path; - ResourceBuilder::Rule *rule; - - while (resources.next(path, rule)) { - validateResource(path, ctx); - CFDictionaryRemoveValue(resourceMap, CFTempString(path)); - } - - if (CFDictionaryGetCount(resourceMap) > 0) { - secdebug("staticCode", "%p sealed resource(s) not found in code", this); - CFDictionaryApplyFunction(resourceMap, SecStaticCode::checkOptionalResource, &ctx); + if (!validatedResources()) { + try { + // sanity first + CFDictionaryRef sealedResources = resourceDictionary(); + if (this->resourceBase()) // disk has resources + if (sealedResources) + /* go to work below */; + else + MacOSError::throwMe(errSecCSResourcesNotFound); + else // disk has no resources + if (sealedResources) + MacOSError::throwMe(errSecCSResourcesNotFound); + else + return; // no resources, not sealed - fine (no work) + + // found resources, and they are sealed + CFDictionaryRef rules = cfget(sealedResources, "rules"); + CFDictionaryRef files = cfget(sealedResources, "files"); + DTRACK(CODESIGN_EVAL_STATIC_RESOURCES, this, + (char*)this->mainExecutablePath().c_str(), int(CFDictionaryGetCount(files))); + + // make a shallow copy of the ResourceDirectory so we can "check off" what we find + CFRef resourceMap = makeCFMutableDictionary(files); + + // scan through the resources on disk, checking each against the resourceDirectory + mResourcesValidContext = new CollectingContext(*this); // collect all failures in here + ResourceBuilder resources(cfString(this->resourceBase()), rules, codeDirectory()->hashType); + mRep->adjustResources(resources); + string path; + ResourceBuilder::Rule *rule; + + while (resources.next(path, rule)) { + validateResource(path, *mResourcesValidContext); + CFDictionaryRemoveValue(resourceMap, CFTempString(path)); + } + + if (CFDictionaryGetCount(resourceMap) > 0) { + secdebug("staticCode", "%p sealed resource(s) not found in code", this); + CFDictionaryApplyFunction(resourceMap, SecStaticCode::checkOptionalResource, mResourcesValidContext); + } + + // now check for any errors found in the reporting context + mResourcesValidated = true; + if (mResourcesValidContext->osStatus() != noErr) + mResourcesValidContext->throwMe(); + + } catch (const CommonError &err) { + mResourcesValidated = true; + mResourcesValidResult = err.osStatus(); + throw; + } catch (...) { + secdebug("staticCode", "%p executable validation threw non-common exception", this); + mResourcesValidated = true; + mResourcesValidResult = errSecCSInternalError; + throw; + } } - - // now check for any errors found in the reporting context - if (ctx) - ctx.throwMe(); + assert(!validatedResources()); + if (mResourcesValidResult) + MacOSError::throwMe(mResourcesValidResult); + if (mResourcesValidContext->osStatus() != noErr) + mResourcesValidContext->throwMe(); } @@ -1149,7 +1240,7 @@ void SecStaticCode::CollectingContext::reportProblem(OSStatus rc, CFStringRef ty void SecStaticCode::CollectingContext::throwMe() { assert(mStatus != noErr); - throw CSError(mStatus, mCollection.yield()); + throw CSError(mStatus, mCollection.retain()); } diff --git a/lib/StaticCode.h b/lib/StaticCode.h index da616c8..edb92a1 100644 --- a/lib/StaticCode.h +++ b/lib/StaticCode.h @@ -80,6 +80,7 @@ protected: CollectingContext(SecStaticCode &c) : code(c), mStatus(noErr) { } void reportProblem(OSStatus rc, CFStringRef type, CFTypeRef value); + OSStatus osStatus() { return mStatus; } operator OSStatus () const { return mStatus; } void throwMe() __attribute__((noreturn)); @@ -135,9 +136,13 @@ public: bool validated() const { return mValidated; } bool valid() const { assert(validated()); return mValidated && (mValidationResult == noErr); } + bool validatedExecutable() const { return mExecutableValidated; } + bool validatedResources() const { return mResourcesValidated; } + void validateDirectory(); void validateComponent(CodeDirectory::SpecialSlot slot, OSStatus fail = errSecCSSignatureFailed); + void validateNonResourceComponents(); void validateResources(); void validateExecutable(); @@ -163,7 +168,7 @@ public: protected: CFDictionaryRef getDictionary(CodeDirectory::SpecialSlot slot, OSStatus fail); // component value as a dictionary bool verifySignature(); - SecPolicyRef verificationPolicy(); + CFTypeRef verificationPolicy(SecCSFlags flags); void defaultDesignatedAppleAnchor(Requirement::Maker &maker); void defaultDesignatedNonAppleAnchor(Requirement::Maker &maker); @@ -182,7 +187,12 @@ private: // static executable validation state (nested within mValidated/mValid) bool mExecutableValidated; // tried to validate executable file - bool mExecutableValid; // outcome if mExecutableValidated + OSStatus mExecutableValidResult; // outcome if mExecutableValidated + + // static resource validation state (nested within mValidated/mValid) + bool mResourcesValidated; // tried to validate resources + OSStatus mResourcesValidResult; // outcome if mResourceValidated or.. + CollectingContext *mResourcesValidContext; // other outcome // cached contents CFRef mDir; // code directory data @@ -204,9 +214,6 @@ private: CFRef mTrust; // outcome of crypto validation (valid or not) CFRef mCertChain; CSSM_TP_APPLE_EVIDENCE_INFO *mEvalDetails; - - // cached verification policy - CFRef mPolicy; }; diff --git a/lib/bundlediskrep.cpp b/lib/bundlediskrep.cpp index 757bbbc..09d5ca2 100644 --- a/lib/bundlediskrep.cpp +++ b/lib/bundlediskrep.cpp @@ -70,7 +70,7 @@ void BundleDiskRep::setup(const Context *ctx) if (ctx && ctx->version) // explicitly specified MacOSError::throwMe(errSecCSStaticCodeNotFound); } - + // conventional executable bundle: CFBundle identifies an executable for us if (mMainExecutableURL.take(CFBundleCopyExecutableURL(mBundle))) { // conventional executable bundle @@ -100,6 +100,7 @@ void BundleDiskRep::setup(const Context *ctx) CFRef infoURL = _CFBundleCopyInfoPlistURL(mBundle); if (!infoURL) MacOSError::throwMe(errSecCSBadBundleFormat); + // focus on the Info.plist (which we know exists) as the nominal "main executable" file if ((mMainExecutableURL = _CFBundleCopyInfoPlistURL(mBundle))) { diff --git a/lib/codedirectory.cpp b/lib/codedirectory.cpp index 3eedb3c..9596e8f 100644 --- a/lib/codedirectory.cpp +++ b/lib/codedirectory.cpp @@ -35,6 +35,18 @@ namespace Security { namespace CodeSigning { +// +// Highest understood special slot in this CodeDirectory. +// +CodeDirectory::SpecialSlot CodeDirectory::maxSpecialSlot() const +{ + SpecialSlot slot = this->nSpecialSlots; + if (slot > cdSlotMax) + slot = cdSlotMax; + return slot; +} + + // // Canonical filesystem names for select slot numbers. // These are variously used for filenames, extended attribute names, etc. diff --git a/lib/codedirectory.h b/lib/codedirectory.h index b6f8b0e..1028fd7 100644 --- a/lib/codedirectory.h +++ b/lib/codedirectory.h @@ -198,6 +198,8 @@ public: char *identifier() { return at(identOffset); } // main hash array access + SpecialSlot maxSpecialSlot() const; + unsigned char *operator [] (Slot slot) { assert(slot >= int(-nSpecialSlots) && slot < int(nCodeSlots)); diff --git a/lib/policydb.cpp b/lib/policydb.cpp index 72018c3..b748dea 100644 --- a/lib/policydb.cpp +++ b/lib/policydb.cpp @@ -52,6 +52,37 @@ static const char *dbPath() } +// +// Help mapping API-ish CFString keys to more convenient internal enumerations +// +typedef struct { + const CFStringRef &cstring; + uint enumeration; +} StringMap; + +static uint mapEnum(CFDictionaryRef context, CFStringRef attr, const StringMap *map, uint value = 0) +{ + if (context) + if (CFTypeRef value = CFDictionaryGetValue(context, attr)) + for (const StringMap *mp = map; mp->cstring; ++mp) + if (CFEqual(mp->cstring, value)) + return mp->enumeration; + return value; +} + +static const StringMap mapType[] = { + { kSecAssessmentOperationTypeExecute, kAuthorityExecute }, + { kSecAssessmentOperationTypeInstall, kAuthorityInstall }, + { kSecAssessmentOperationTypeOpenDocument, kAuthorityOpenDoc }, + { NULL } +}; + +AuthorityType typeFor(CFDictionaryRef context, AuthorityType type /* = kAuthorityInvalid */) +{ + return mapEnum(context, kSecAssessmentContextKeyOperation, mapType, type); +} + + // // Open the database (creating it if necessary and possible). // Note that this isn't creating the schema; we do that on first write. @@ -75,48 +106,64 @@ bool PolicyDatabase::checkCache(CFURLRef path, AuthorityType type, CFMutableDict if (type != kAuthorityExecute) return false; - SecCSFlags validationFlags = kSecCSDefaultFlags; - if (overrideAssessment()) // we'll force the verdict to 'pass' at the end, so don't sweat validating code - validationFlags = kSecCSBasicValidateOnly; - CFRef code; MacOSError::check(SecStaticCodeCreateWithPath(path, kSecCSDefaultFlags, &code.aref())); - if (SecStaticCodeCheckValidity(code, validationFlags, NULL) != noErr) + if (SecStaticCodeCheckValidity(code, kSecCSBasicValidateOnly, NULL) != noErr) return false; // quick pass - any error is a cache miss CFRef info; MacOSError::check(SecCodeCopySigningInformation(code, kSecCSDefaultFlags, &info.aref())); CFDataRef cdHash = CFDataRef(CFDictionaryGetValue(info, kSecCodeInfoUnique)); // check the cache table for a fast match - SQLite::Statement cached(*this, "SELECT allow, expires, label, authority FROM object WHERE type = ?1 and hash = ?2;"); - cached.bind(1).integer(type); - cached.bind(2) = cdHash; + SQLite::Statement cached(*this, "SELECT object.allow, authority.label, authority FROM object, authority" + " WHERE object.authority = authority.id AND object.type = :type AND object.hash = :hash AND authority.disabled = 0" + " AND JULIANDAY('now') < object.expires;"); + cached.bind(":type").integer(type); + cached.bind(":hash") = cdHash; if (cached.nextRow()) { bool allow = int(cached[0]); - const char *label = cached[2]; - SQLite::int64 auth = cached[3]; - bool valid = true; - if (SQLite3::int64 expires = cached[1]) - valid = time(NULL) <= expires; - if (valid) { - SYSPOLICY_ASSESS_CACHE_HIT(); - cfadd(result, "{%O=%B}", kSecAssessmentAssessmentVerdict, allow); - PolicyEngine::addAuthority(result, label, auth, kCFBooleanTrue); - return true; - } + const char *label = cached[1]; + SQLite::int64 auth = cached[2]; + SYSPOLICY_ASSESS_CACHE_HIT(); + + // If its allowed, lets do a full validation unless if + // we are overriding the assessement, since that force + // the verdict to 'pass' at the end + + if (allow && !overrideAssessment()) + MacOSError::check(SecStaticCodeCheckValidity(code, kSecCSDefaultFlags, NULL)); + + cfadd(result, "{%O=%B}", kSecAssessmentAssessmentVerdict, allow); + PolicyEngine::addAuthority(result, label, auth, kCFBooleanTrue); + return true; } return false; } // -// Purge the object cache of all expired entries +// Purge the object cache of all expired entries. +// These are meant to run within the caller's transaction. // -void PolicyDatabase::purge(const char *table) +void PolicyDatabase::purgeAuthority() +{ + SQLite::Statement cleaner(*this, + "DELETE FROM authority WHERE expires <= JULIANDAY('now');"); + cleaner.execute(); +} + +void PolicyDatabase::purgeObjects() +{ + SQLite::Statement cleaner(*this, + "DELETE FROM object WHERE expires <= JULIANDAY('now');"); + cleaner.execute(); +} + +void PolicyDatabase::purgeObjects(double priority) { SQLite::Statement cleaner(*this, - "DELETE FROM ?1 WHERE expires < DATE_TIME('now');"); - cleaner.bind(1) = table; + "DELETE FROM object WHERE expires <= JULIANDAY('now') OR (SELECT priority FROM authority WHERE id = object.authority) <= :priority;"); + cleaner.bind(":priority") = priority; cleaner.execute(); } diff --git a/lib/policydb.h b/lib/policydb.h index 1b06b4a..a112c16 100644 --- a/lib/policydb.h +++ b/lib/policydb.h @@ -38,6 +38,8 @@ namespace SQLite = SQLite3; static const char defaultDatabase[] = "/var/db/SystemPolicy"; static const char visibleSecurityFlagFile[] = "/var/db/.sp_visible"; +static const double never = 5000000; // expires never (i.e. in the year 8977) + typedef SHA1::SDigest ObjectHash; @@ -51,6 +53,22 @@ enum { }; +// +// Defined flags for authority flags column +// +enum { + kAuthorityFlagVirtual = 0x0001, // virtual rule (anchoring object records) + kAuthorityFlagDefault = 0x0002, // rule is part of the original default set + kAuthorityFlagInhibitCache = 0x0004, // never cache outcome of this rule +}; + + +// +// Mapping/translation to/from API space +// +AuthorityType typeFor(CFDictionaryRef context, AuthorityType type = kAuthorityInvalid); + + // // An open policy database. // Usually read-only, but can be opened for write by privileged callers. @@ -66,7 +84,9 @@ public: bool checkCache(CFURLRef path, AuthorityType type, CFMutableDictionaryRef result); public: - void purge(const char *table); + void purgeAuthority(); + void purgeObjects(); + void purgeObjects(double priority); }; diff --git a/lib/policyengine.cpp b/lib/policyengine.cpp index 99e087b..4ebe2a0 100644 --- a/lib/policyengine.cpp +++ b/lib/policyengine.cpp @@ -26,6 +26,12 @@ #include #include #include +#include +#include +#include +#include +#include +#include "quarantine++.h" #include #undef check // Macro! Yech. @@ -33,7 +39,11 @@ namespace Security { namespace CodeSigning { -static const time_t NEGATIVE_HOLD = 60; // seconds for negative cache entries +static const double NEGATIVE_HOLD = 60.0/86400; // 60 seconds to cache negative outcomes + + +static void authorizeUpdate(SecCSFlags flags, CFDictionaryRef context); +static void normalizeTarget(CFRef &target, CFDictionary &context, bool signUnsigned = false); // @@ -81,64 +91,88 @@ void PolicyEngine::evaluateCode(CFURLRef path, SecAssessmentFlags flags, CFDicti CFRef code; MacOSError::check(SecStaticCodeCreateWithPath(path, kSecCSDefaultFlags, &code.aref())); - if (flags & kSecAssessmentFlagRequestOrigin) { - CFRef info; - MacOSError::check(SecCodeCopySigningInformation(code, kSecCSSigningInformation, &info.aref())); - if (CFArrayRef chain = CFArrayRef(CFDictionaryGetValue(info, kSecCodeInfoCertificates))) - setOrigin(chain, result); - } - - SecCSFlags validationFlags = kSecCSDefaultFlags; - if (overrideAssessment()) // we'll force the verdict to 'pass' at the end, so don't sweat validating code - validationFlags = kSecCSBasicValidateOnly; + const SecCSFlags validationFlags = kSecCSEnforceRevocationChecks; SQLite::Statement query(*this, - "SELECT allow, requirement, inhibit_cache, expires, id, label FROM authority WHERE type = ?1 ORDER BY priority DESC;"); - query.bind(1).integer(type); + "SELECT allow, requirement, id, label, expires, flags, disabled FROM scan_authority" + " WHERE type = :type" + " ORDER BY priority DESC;"); + query.bind(":type").integer(type); + SQLite3::int64 latentID = 0; // first (highest priority) disabled matching ID + std::string latentLabel; // ... and associated label, if any while (query.nextRow()) { bool allow = int(query[0]); const char *reqString = query[1]; - bool inhibit_cache = query[2]; - time_t expires = SQLite::int64(query[3]); - SQLite3::int64 id = query[4]; - const char *label = query[5]; - - if (expires && expires < time(NULL)) // no longer active - continue; + SQLite3::int64 id = query[2]; + const char *label = query[3]; + double expires = query[4]; + sqlite3_int64 ruleFlags = query[5]; + SQLite3::int64 disabled = query[6]; CFRef requirement; MacOSError::check(SecRequirementCreateWithString(CFTempString(reqString), kSecCSDefaultFlags, &requirement.aref())); - switch (OSStatus rc = SecStaticCodeCheckValidity(code, validationFlags, requirement)) { + + OSStatus rc = SecStaticCodeCheckValidity(code, validationFlags | kSecCSBasicValidateOnly, requirement); + // ok, so this rule matches lets do a full validation if not overriding assessments + if (rc == noErr && !overrideAssessment()) + rc = SecStaticCodeCheckValidity(code, validationFlags, requirement); + + switch (rc) { case noErr: // success break; case errSecCSUnsigned: cfadd(result, "{%O=#F}", kSecAssessmentAssessmentVerdict); addAuthority(result, "no usable signature"); return; - case errSecCSSignatureFailed: - case errSecCSSignatureInvalid: - case errSecCSSignatureUnsupported: - case errSecCSSignatureNotVerifiable: - cfadd(result, "{%O=#F}", kSecAssessmentAssessmentVerdict); - addAuthority(result, "invalid signature"); - return; case errSecCSReqFailed: // requirement missed, but otherwise okay continue; default: // broken in some way; all tests will fail like this so bail out MacOSError::throwMe(rc); } - if (!inhibit_cache && !(flags & kSecAssessmentFlagNoCache)) // cache inhibit - this->recordOutcome(code, allow, type, expires, id, label); + if (disabled) { + if (latentID == 0) { + latentID = id; + if (label) + latentLabel = label; + } + continue; // the loop + } + + CFRef info; // as needed + if (flags & kSecAssessmentFlagRequestOrigin) { + if (!info) + MacOSError::check(SecCodeCopySigningInformation(code, kSecCSSigningInformation, &info.aref())); + if (CFArrayRef chain = CFArrayRef(CFDictionaryGetValue(info, kSecCodeInfoCertificates))) + setOrigin(chain, result); + } + if (!(ruleFlags & kAuthorityFlagInhibitCache) && !(flags & kSecAssessmentFlagNoCache)) { // cache inhibit + if (!info) + MacOSError::check(SecCodeCopySigningInformation(code, kSecCSSigningInformation, &info.aref())); + if (SecTrustRef trust = SecTrustRef(CFDictionaryGetValue(info, kSecCodeInfoTrust))) { + CFRef xinfo; + MacOSError::check(SecTrustCopyExtendedResult(trust, &xinfo.aref())); + if (CFDateRef limit = CFDateRef(CFDictionaryGetValue(xinfo, kSecTrustExpirationDate))) { + double julianLimit = CFDateGetAbsoluteTime(limit) / 86400.0 + 2451910.5; + this->recordOutcome(code, allow, type, min(expires, julianLimit), id); + } + } + } cfadd(result, "{%O=%B}", kSecAssessmentAssessmentVerdict, allow); addAuthority(result, label, id); return; } // no applicable authority. Deny by default + if (flags & kSecAssessmentFlagRequestOrigin) { + CFRef info; // as needed + MacOSError::check(SecCodeCopySigningInformation(code, kSecCSSigningInformation, &info.aref())); + if (CFArrayRef chain = CFArrayRef(CFDictionaryGetValue(info, kSecCodeInfoCertificates))) + setOrigin(chain, result); + } if (!(flags & kSecAssessmentFlagNoCache)) - this->recordOutcome(code, false, type, time(NULL) + NEGATIVE_HOLD, 0, NULL); + this->recordOutcome(code, false, type, this->julianNow() + NEGATIVE_HOLD, latentID); cfadd(result, "{%O=%B}", kSecAssessmentAssessmentVerdict, false); - addAuthority(result, NULL); + addAuthority(result, latentLabel.c_str(), latentID); } @@ -147,10 +181,14 @@ void PolicyEngine::evaluateCode(CFURLRef path, SecAssessmentFlags flags, CFDicti // Certs passed from caller (untrusted), no policy engine yet, no caching (since untrusted). // The current "policy" is to trust any proper signature. // +static CFTypeRef installerPolicy(); + void PolicyEngine::evaluateInstall(CFURLRef path, SecAssessmentFlags flags, CFDictionaryRef context, CFMutableDictionaryRef result) { const AuthorityType type = kAuthorityInstall; + SQLite3::int64 latentID = 0; // first (highest priority) disabled matching ID + std::string latentLabel; // ... and associated label, if any Xar xar(cfString(path).c_str()); if (xar) { if (!xar.isSigned()) { @@ -160,11 +198,11 @@ void PolicyEngine::evaluateInstall(CFURLRef path, SecAssessmentFlags flags, CFDi return; } if (CFRef certs = xar.copyCertChain()) { - CFRef policy = SecPolicyCreateBasicX509(); + CFRef policy = installerPolicy(); CFRef trust; MacOSError::check(SecTrustCreateWithCertificates(certs, policy, &trust.aref())); // MacOSError::check(SecTrustSetAnchorCertificates(trust, cfEmptyArray())); // no anchors - MacOSError::check(SecTrustSetOptions(trust, kSecTrustOptionImplicitAnchors)); + MacOSError::check(SecTrustSetOptions(trust, kSecTrustOptionAllowExpired | kSecTrustOptionImplicitAnchors)); SecTrustResultType trustResult; MacOSError::check(SecTrustEvaluate(trust, &trustResult)); @@ -189,18 +227,17 @@ void PolicyEngine::evaluateInstall(CFURLRef path, SecAssessmentFlags flags, CFDi } SQLite::Statement query(*this, - "SELECT allow, requirement, inhibit_cache, expires, id, label FROM authority WHERE type = ?1 ORDER BY priority DESC;"); - query.bind(1).integer(type); + "SELECT allow, requirement, id, label, flags, disabled FROM scan_authority" + " WHERE type = :type" + " ORDER BY priority DESC;"); + query.bind(":type").integer(type); while (query.nextRow()) { bool allow = int(query[0]); const char *reqString = query[1]; - bool inhibit_cache = query[2]; - time_t expires = SQLite::int64(query[3]); - SQLite3::int64 id = query[4]; - const char *label = query[5]; - - if (expires && expires < time(NULL)) // no longer active - continue; + SQLite3::int64 id = query[2]; + const char *label = query[3]; + //sqlite_uint64 ruleFlags = query[4]; + SQLite3::int64 disabled = query[5]; CFRef requirement; MacOSError::check(SecRequirementCreateWithString(CFTempString(reqString), kSecCSDefaultFlags, &requirement.aref())); @@ -212,6 +249,14 @@ void PolicyEngine::evaluateInstall(CFURLRef path, SecAssessmentFlags flags, CFDi default: // broken in some way; all tests will fail like this so bail out MacOSError::throwMe(rc); } + if (disabled) { + if (latentID == 0) { + latentID = id; + if (label) + latentLabel = label; + } + continue; // the loop + } // not adding to the object cache - we could, but it's not likely to be worth it cfadd(result, "{%O=%B}", kSecAssessmentAssessmentVerdict, allow); addAuthority(result, label, id); @@ -222,7 +267,45 @@ void PolicyEngine::evaluateInstall(CFURLRef path, SecAssessmentFlags flags, CFDi // no applicable authority. Deny by default cfadd(result, "{%O=#F}", kSecAssessmentAssessmentVerdict); - addAuthority(result, NULL); + addAuthority(result, latentLabel.c_str(), latentID); +} + + +// +// Create a suitable policy array for verification of installer signatures. +// +static SecPolicyRef makeCRLPolicy() +{ + CFRef policy; + MacOSError::check(SecPolicyCopy(CSSM_CERT_X_509v3, &CSSMOID_APPLE_TP_REVOCATION_CRL, &policy.aref())); + CSSM_APPLE_TP_CRL_OPTIONS options; + memset(&options, 0, sizeof(options)); + options.Version = CSSM_APPLE_TP_CRL_OPTS_VERSION; + options.CrlFlags = CSSM_TP_ACTION_FETCH_CRL_FROM_NET | CSSM_TP_ACTION_CRL_SUFFICIENT; + CSSM_DATA optData = { sizeof(options), (uint8 *)&options }; + MacOSError::check(SecPolicySetValue(policy, &optData)); + return policy.yield(); +} + +static SecPolicyRef makeOCSPPolicy() +{ + CFRef policy; + MacOSError::check(SecPolicyCopy(CSSM_CERT_X_509v3, &CSSMOID_APPLE_TP_REVOCATION_OCSP, &policy.aref())); + CSSM_APPLE_TP_OCSP_OPTIONS options; + memset(&options, 0, sizeof(options)); + options.Version = CSSM_APPLE_TP_OCSP_OPTS_VERSION; + options.Flags = CSSM_TP_ACTION_OCSP_SUFFICIENT; + CSSM_DATA optData = { sizeof(options), (uint8 *)&options }; + MacOSError::check(SecPolicySetValue(policy, &optData)); + return policy.yield(); +} + +static CFTypeRef installerPolicy() +{ + CFRef base = SecPolicyCreateBasicX509(); + CFRef crl = makeCRLPolicy(); + CFRef ocsp = makeOCSPPolicy(); + return makeCFArray(3, base.get(), crl.get(), ocsp.get()); } @@ -239,24 +322,31 @@ void PolicyEngine::evaluateDocOpen(CFURLRef path, SecAssessmentFlags flags, CFDi || CFEqual(riskCategory, kLSRiskCategoryUnknown) || CFEqual(riskCategory, kLSRiskCategoryMayContainUnsafeExecutable)) { cfadd(result, "{%O=#T}", kSecAssessmentAssessmentVerdict); + addAuthority(result, "_XProtect"); + } else if (FileQuarantine(cfString(path).c_str()).flag(QTN_FLAG_ASSESSMENT_OK)) { + cfadd(result, "{%O=#T}", kSecAssessmentAssessmentVerdict); + addAuthority(result, "Prior Assessment"); } else { cfadd(result, "{%O=#F}", kSecAssessmentAssessmentVerdict); + addAuthority(result, "_XProtect"); } - addAuthority(result, "_XProtect"); addToAuthority(result, kLSDownloadRiskCategoryKey, riskCategory); return; } } // insufficient information from LS - deny by default - cfadd(result, "{%O=%F}", kSecAssessmentAssessmentVerdict); - addAuthority(result, NULL); + cfadd(result, "{%O=#F}", kSecAssessmentAssessmentVerdict); + addAuthority(result, "Insufficient Context"); } +// +// Result-creation helpers +// void PolicyEngine::addAuthority(CFMutableDictionaryRef parent, const char *label, SQLite::int64 row, CFTypeRef cacheInfo) { CFRef auth = makeCFMutableDictionary(); - if (label) + if (label && label[0]) cfadd(auth, "{%O=%s}", kSecAssessmentAssessmentSource, label); if (row) CFDictionaryAddValue(auth, kSecAssessmentAssessmentAuthorityRow, CFTempNumber(row)); @@ -276,47 +366,180 @@ void PolicyEngine::addToAuthority(CFMutableDictionaryRef parent, CFStringRef key // // Add a rule to the policy database // -bool PolicyEngine::add(CFURLRef path, AuthorityType type, SecAssessmentFlags flags, CFDictionaryRef context) +bool PolicyEngine::add(CFTypeRef inTarget, AuthorityType type, SecAssessmentFlags flags, CFDictionaryRef context) { - if (type != kAuthorityExecute) - MacOSError::throwMe(errSecCSUnimplemented); + // default type to execution + if (type == kAuthorityInvalid) + type = kAuthorityExecute; - double priority = 0; - string label; - if (context) { - if (CFTypeRef pri = CFDictionaryGetValue(context, kSecAssessmentUpdateKeyPriority)) { - if (CFGetTypeID(pri) != CFNumberGetTypeID()) - MacOSError::throwMe(errSecCSBadDictionaryFormat); - CFNumberGetValue(CFNumberRef(pri), kCFNumberDoubleType, &priority); - } - if (CFTypeRef lab = CFDictionaryGetValue(context, kSecAssessmentUpdateKeyLabel)) { - if (CFGetTypeID(lab) != CFStringGetTypeID()) - MacOSError::throwMe(errSecCSBadDictionaryFormat); - label = cfString(CFStringRef(lab)); - } + authorizeUpdate(flags, context); + CFDictionary ctx(context, errSecCSInvalidAttributeValues); + CFCopyRef target = inTarget; + + if (type == kAuthorityOpenDoc) { + // handle document-open differently: use quarantine flags for whitelisting + if (!target || CFGetTypeID(target) != CFURLGetTypeID()) + MacOSError::throwMe(errSecCSInvalidObjectRef); + std::string spath = cfString(target.as()).c_str(); + FileQuarantine qtn(spath.c_str()); + qtn.setFlag(QTN_FLAG_ASSESSMENT_OK); + qtn.applyTo(spath.c_str()); + return true; } + + if (type == kAuthorityInstall) { + return cfmake("{%O=%O}", kSecAssessmentAssessmentAuthorityOverride, CFSTR("virtual install")); + } + + // resolve URLs to Requirements + normalizeTarget(target, ctx, true); + + // if we now have anything else, we're busted + if (!target || CFGetTypeID(target) != SecRequirementGetTypeID()) + MacOSError::throwMe(errSecCSInvalidObjectRef); - CFRef code; - MacOSError::check(SecStaticCodeCreateWithPath(path, kSecCSDefaultFlags, &code.aref())); - CFRef info; - MacOSError::check(SecCodeCopySigningInformation(code, kSecCSDefaultFlags, &info.aref())); - CFRef dr; - MacOSError::check(SecCodeCopyDesignatedRequirement(code, kSecCSDefaultFlags, &dr.aref())); + double priority = 0; + string label; + bool allow = true; + double expires = never; + string remarks; + + if (CFNumberRef pri = ctx.get(kSecAssessmentUpdateKeyPriority)) + CFNumberGetValue(pri, kCFNumberDoubleType, &priority); + if (CFStringRef lab = ctx.get(kSecAssessmentUpdateKeyLabel)) + label = cfString(lab); + if (CFDateRef time = ctx.get(kSecAssessmentUpdateKeyExpires)) + // we're using Julian dates here; convert from CFDate + expires = CFDateGetAbsoluteTime(time) / 86400.0 + 2451910.5; + if (CFBooleanRef allowing = ctx.get(kSecAssessmentUpdateKeyAllow)) + allow = allowing == kCFBooleanTrue; + if (CFStringRef rem = ctx.get(kSecAssessmentUpdateKeyRemarks)) + remarks = cfString(rem); CFRef requirementText; - MacOSError::check(SecRequirementCopyString(dr, kSecCSDefaultFlags, &requirementText.aref())); + MacOSError::check(SecRequirementCopyString(target.as(), kSecCSDefaultFlags, &requirementText.aref())); + SQLite::Transaction xact(*this, SQLite3::Transaction::deferred, "add_rule"); SQLite::Statement insert(*this, - "INSERT INTO authority (type, allow, requirement, priority, label) VALUES (?1, ?2, ?3, ?4, ?5);"); - insert.bind(1).integer(type); - insert.bind(2).integer(true); - insert.bind(3) = requirementText.get(); - insert.bind(4) = priority; - insert.bind(5) = label.c_str(); + "INSERT INTO authority (type, allow, requirement, priority, label, expires, remarks)" + " VALUES (:type, :allow, :requirement, :priority, :label, :expires, :remarks);"); + insert.bind(":type").integer(type); + insert.bind(":allow").integer(allow); + insert.bind(":requirement") = requirementText.get(); + insert.bind(":priority") = priority; + if (!label.empty()) + insert.bind(":label") = label.c_str(); + insert.bind(":expires") = expires; + if (!remarks.empty()) + insert.bind(":remarks") = remarks.c_str(); insert.execute(); + this->purgeObjects(priority); + xact.commit(); + notify_post(kNotifySecAssessmentUpdate); return true; } +// +// Perform an action on existing authority rule(s) +// +bool PolicyEngine::manipulateRules(const std::string &stanza, + CFTypeRef inTarget, AuthorityType type, SecAssessmentFlags flags, CFDictionaryRef context) +{ + authorizeUpdate(flags, context); + CFDictionary ctx(context, errSecCSInvalidAttributeValues); + CFCopyRef target = inTarget; + normalizeTarget(target, ctx); + + string label; + + if (CFStringRef lab = ctx.get(kSecAssessmentUpdateKeyLabel)) + label = cfString(CFStringRef(lab)); + + SQLite::Transaction xact(*this, SQLite3::Transaction::deferred, "rule_change"); + SQLite::Statement action(*this); + if (!target) { + if (label.empty()) // underspecified + MacOSError::throwMe(errSecCSInvalidObjectRef); + if (type == kAuthorityInvalid) { + action.query(stanza + " WHERE label = :label"); + } else { + action.query(stanza + " WHERE type = :type AND label = :label"); + action.bind(":type").integer(type); + } + action.bind(":label") = label.c_str(); + } else if (CFGetTypeID(target) == CFNumberGetTypeID()) { + action.query(stanza + " WHERE id = :id"); + action.bind(":id").integer(cfNumber(target.as())); + } else if (CFGetTypeID(target) == SecRequirementGetTypeID()) { + if (type == kAuthorityInvalid) + type = kAuthorityExecute; + CFRef requirementText; + MacOSError::check(SecRequirementCopyString(target.as(), kSecCSDefaultFlags, &requirementText.aref())); + action.query(stanza + " WHERE type = :type AND requirement = :requirement"); + action.bind(":type").integer(type); + action.bind(":requirement") = requirementText.get(); + } else + MacOSError::throwMe(errSecCSInvalidObjectRef); + + action.execute(); + unsigned int changes = this->changes(); // latch change count + // We MUST purge objects with priority <= MAX(priority of any changed rules); + // but for now we just get lazy and purge them ALL. + if (changes) { + this->purgeObjects(1.0E100); + xact.commit(); + notify_post(kNotifySecAssessmentUpdate); + return true; + } + // no change; return an error + MacOSError::throwMe(errSecCSNoMatches); +} + + +bool PolicyEngine::remove(CFTypeRef target, AuthorityType type, SecAssessmentFlags flags, CFDictionaryRef context) +{ + if (type == kAuthorityOpenDoc) { + // handle document-open differently: use quarantine flags for whitelisting + authorizeUpdate(flags, context); + if (!target || CFGetTypeID(target) != CFURLGetTypeID()) + MacOSError::throwMe(errSecCSInvalidObjectRef); + std::string spath = cfString(CFURLRef(target)).c_str(); + FileQuarantine qtn(spath.c_str()); + qtn.clearFlag(QTN_FLAG_ASSESSMENT_OK); + qtn.applyTo(spath.c_str()); + return true; + } + return manipulateRules("DELETE FROM authority", target, type, flags, context); +} + +bool PolicyEngine::enable(CFTypeRef target, AuthorityType type, SecAssessmentFlags flags, CFDictionaryRef context) +{ + return manipulateRules("UPDATE authority SET disabled = 0", target, type, flags, context); +} + +bool PolicyEngine::disable(CFTypeRef target, AuthorityType type, SecAssessmentFlags flags, CFDictionaryRef context) +{ + return manipulateRules("UPDATE authority SET disabled = 1", target, type, flags, context); +} + + +bool PolicyEngine::update(CFTypeRef target, SecAssessmentFlags flags, CFDictionaryRef context) +{ + AuthorityType type = typeFor(context, kAuthorityInvalid); + CFStringRef edit = CFStringRef(CFDictionaryGetValue(context, kSecAssessmentContextKeyUpdate)); + if (CFEqual(edit, kSecAssessmentUpdateOperationAdd)) + return this->add(target, type, flags, context); + else if (CFEqual(edit, kSecAssessmentUpdateOperationRemove)) + return this->remove(target, type, flags, context); + else if (CFEqual(edit, kSecAssessmentUpdateOperationEnable)) + return this->enable(target, type, flags, context); + else if (CFEqual(edit, kSecAssessmentUpdateOperationDisable)) + return this->disable(target, type, flags, context); + else + MacOSError::throwMe(errSecCSInvalidAttributeValues); +} + + // // Fill in extra information about the originator of cryptographic credentials found - if any // @@ -333,30 +556,98 @@ void PolicyEngine::setOrigin(CFArrayRef chain, CFMutableDictionaryRef result) // // Take an assessment outcome and record it in the object cache // -void PolicyEngine::recordOutcome(SecStaticCodeRef code, bool allow, AuthorityType type, time_t expires, int authority, const char *label) +void PolicyEngine::recordOutcome(SecStaticCodeRef code, bool allow, AuthorityType type, double expires, int authority) { CFRef info; MacOSError::check(SecCodeCopySigningInformation(code, kSecCSDefaultFlags, &info.aref())); CFDataRef cdHash = CFDataRef(CFDictionaryGetValue(info, kSecCodeInfoUnique)); + assert(cdHash); // was signed CFRef path; MacOSError::check(SecCodeCopyPath(code, kSecCSDefaultFlags, &path.aref())); - //@@@ should really be OR REPLACE IF EXPIRED... does it matter? @@@ + assert(expires); SQLite::Transaction xact(*this, SQLite3::Transaction::deferred, "caching"); SQLite::Statement insert(*this, - "INSERT OR REPLACE INTO object (type, allow, hash, expires, authority, label, path) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)"); - insert.bind(1).integer(type); - insert.bind(2).integer(allow); - insert.bind(3) = cdHash; - if (expires) - insert.bind(4).integer(expires); - insert.bind(5).integer(authority); - if (label) - insert.bind(6) = label; - insert.bind(7) = cfString(path).c_str(); + "INSERT OR REPLACE INTO object (type, allow, hash, expires, path, authority)" + " VALUES (:type, :allow, :hash, :expires, :path," + " CASE :authority WHEN 0 THEN (SELECT id FROM authority WHERE label = 'No Matching Rule') ELSE :authority END" + " );"); + insert.bind(":type").integer(type); + insert.bind(":allow").integer(allow); + insert.bind(":hash") = cdHash; + insert.bind(":expires") = expires; + insert.bind(":path") = cfString(path).c_str(); + insert.bind(":authority").integer(authority); insert.execute(); xact.commit(); } +// +// Perform update authorization processing. +// Throws an exception if authorization is denied. +// +static void authorizeUpdate(SecCSFlags flags, CFDictionaryRef context) +{ + AuthorizationRef authorization = NULL; + + if (context) + if (CFTypeRef authkey = CFDictionaryGetValue(context, kSecAssessmentUpdateKeyAuthorization)) + if (CFGetTypeID(authkey) == CFDataGetTypeID()) { + CFDataRef authdata = CFDataRef(authkey); + MacOSError::check(AuthorizationCreateFromExternalForm((AuthorizationExternalForm *)CFDataGetBytePtr(authdata), &authorization)); + } + if (authorization == NULL) + MacOSError::check(AuthorizationCreate(NULL, NULL, kAuthorizationFlagDefaults, &authorization)); + + AuthorizationItem right[] = { + { "com.apple.security.assessment.update", 0, NULL, 0 } + }; + AuthorizationRights rights = { sizeof(right) / sizeof(right[0]), right }; + MacOSError::check(AuthorizationCopyRights(authorization, &rights, NULL, + kAuthorizationFlagExtendRights | kAuthorizationFlagInteractionAllowed, NULL)); + + MacOSError::check(AuthorizationFree(authorization, kAuthorizationFlagDefaults)); +} + + +// +// Perform common argument normalizations for update operations +// +static void normalizeTarget(CFRef &target, CFDictionary &context, bool signUnsigned) +{ + // turn CFURLs into (designated) SecRequirements + if (target && CFGetTypeID(target) == CFURLGetTypeID()) { + CFRef code; + MacOSError::check(SecStaticCodeCreateWithPath(target.as(), kSecCSDefaultFlags, &code.aref())); + CFRef requirement; + switch (OSStatus rc = SecCodeCopyDesignatedRequirement(code, kSecCSDefaultFlags, (SecRequirementRef *)&target.aref())) { + case noErr: + break; + case errSecCSUnsigned: + if (signUnsigned) { + // Ad-hoc sign the code in the system database. This requires root privileges. + CFRef signer; + CFTemp arguments("{%O=#N, %O=#N}", kSecCodeSignerDetached, kSecCodeSignerIdentity); + MacOSError::check(SecCodeSignerCreate(arguments, kSecCSDefaultFlags, &signer.aref())); + MacOSError::check(SecCodeSignerAddSignature(signer, code, kSecCSDefaultFlags)); + MacOSError::check(SecCodeCopyDesignatedRequirement(code, kSecCSDefaultFlags, (SecRequirementRef *)&target.aref())); + break; + } + // fall through + default: + MacOSError::check(rc); + } + if (context.get(kSecAssessmentUpdateKeyRemarks) == NULL) { + // no explicit remarks; add one with the path + CFRef path; + MacOSError::check(SecCodeCopyPath(code, kSecCSDefaultFlags, &path.aref())); + CFMutableDictionaryRef dict = makeCFMutableDictionary(context.get()); + CFDictionaryAddValue(dict, kSecAssessmentUpdateKeyRemarks, CFTempString(cfString(path))); + context.take(dict); + } + } +} + + } // end namespace CodeSigning } // end namespace Security diff --git a/lib/policyengine.h b/lib/policyengine.h index 73b993a..e22f50d 100644 --- a/lib/policyengine.h +++ b/lib/policyengine.h @@ -52,8 +52,14 @@ public: public: void evaluate(CFURLRef path, AuthorityType type, SecAssessmentFlags flags, CFDictionaryRef context, CFMutableDictionaryRef result); - bool add(CFURLRef path, AuthorityType type, SecAssessmentFlags flags, CFDictionaryRef context); + bool update(CFTypeRef target, SecAssessmentFlags flags, CFDictionaryRef context); + bool add(CFTypeRef target, AuthorityType type, SecAssessmentFlags flags, CFDictionaryRef context); + bool remove(CFTypeRef target, AuthorityType type, SecAssessmentFlags flags, CFDictionaryRef context); + bool enable(CFTypeRef target, AuthorityType type, SecAssessmentFlags flags, CFDictionaryRef context); + bool disable(CFTypeRef target, AuthorityType type, SecAssessmentFlags flags, CFDictionaryRef context); + +public: static void addAuthority(CFMutableDictionaryRef parent, const char *label, SQLite::int64 row = 0, CFTypeRef cacheInfo = NULL); static void addToAuthority(CFMutableDictionaryRef parent, CFStringRef key, CFTypeRef value); @@ -61,10 +67,13 @@ private: void evaluateCode(CFURLRef path, SecAssessmentFlags flags, CFDictionaryRef context, CFMutableDictionaryRef result); void evaluateInstall(CFURLRef path, SecAssessmentFlags flags, CFDictionaryRef context, CFMutableDictionaryRef result); void evaluateDocOpen(CFURLRef path, SecAssessmentFlags flags, CFDictionaryRef context, CFMutableDictionaryRef result); + + bool manipulateRules(const std::string &stanza, + CFTypeRef target, AuthorityType type, SecAssessmentFlags flags, CFDictionaryRef context); void setOrigin(CFArrayRef chain, CFMutableDictionaryRef result); - void recordOutcome(SecStaticCodeRef code, bool allow, AuthorityType type, time_t expires, int authority, const char *label); + void recordOutcome(SecStaticCodeRef code, bool allow, AuthorityType type, double expires, int authority); }; diff --git a/lib/quarantine++.cpp b/lib/quarantine++.cpp new file mode 100644 index 0000000..866627d --- /dev/null +++ b/lib/quarantine++.cpp @@ -0,0 +1,107 @@ +/* + * 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@ + */ + +// +// xar++ - interface to XAR-format archive files +// +#include "quarantine++.h" + + +namespace Security { +namespace CodeSigning { + + +// +// Check the int result of a qtn API call. +// If the error is "not quarantined," note in the object (no error). +// Other qtn-specific errors are arbitrarily mapped to ENOSYS (this isn't +// important enough to subclass CommonError). +// +void FileQuarantine::check(int err) +{ + switch (err) { + case 0: + mQuarantined = true; + break; + case QTN_NOT_QUARANTINED: + mQuarantined = false; + return; + default: // some flavor of quarantine-not-available + UnixError::throwMe(err); + } +} + + +FileQuarantine::~FileQuarantine() +{ + if (mQtn) + qtn_file_free(mQtn); +} + + +FileQuarantine::FileQuarantine(const char *path) +{ + if (!(mQtn = qtn_file_alloc())) + UnixError::throwMe(); + check(qtn_file_init_with_path(mQtn, path)); +} + +FileQuarantine::FileQuarantine(int fd) +{ + if (!(mQtn = qtn_file_alloc())) + UnixError::throwMe(); + check(qtn_file_init_with_fd(mQtn, fd)); +} + + +void FileQuarantine::setFlags(uint32_t flags) +{ + if (mQuarantined) + check(qtn_file_set_flags(mQtn, flags)); +} + +void FileQuarantine::setFlag(uint32_t flag) +{ + if (mQuarantined) + setFlags(flags() | flag); +} + +void FileQuarantine::clearFlag(uint32_t flag) +{ + if (mQuarantined) + setFlags(flags() & ~flag); +} + +void FileQuarantine::applyTo(const char *path) +{ + check(qtn_file_apply_to_path(mQtn, path)); +} + +void FileQuarantine::applyTo(int fd) +{ + check(qtn_file_apply_to_fd(mQtn, fd)); +} + + +} // end namespace CodeSigning +} // end namespace Security diff --git a/lib/quarantine++.h b/lib/quarantine++.h new file mode 100644 index 0000000..53c5609 --- /dev/null +++ b/lib/quarantine++.h @@ -0,0 +1,77 @@ +/* + * 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@ + */ + +// +// quarantine++ - interface to XAR-format archive files +// +#ifndef _H_QUARANTINEPLUSPLUS +#define _H_QUARANTINEPLUSPLUS + +#include +#include + +extern "C" { +#include +} + +namespace Security { +namespace CodeSigning { + + +// +// A file quarantine object +// +class FileQuarantine { +public: + FileQuarantine(const char *path); + FileQuarantine(int fd); + virtual ~FileQuarantine(); + + uint32_t flags() const + { return qtn_file_get_flags(mQtn); } + bool flag(uint32_t f) const + { return this->flags() & f; } + + void setFlags(uint32_t flags); + void setFlag(uint32_t flag); + void clearFlag(uint32_t flag); + + void applyTo(const char *path); + void applyTo(int fd); + + operator bool() const { return mQtn != 0; } + bool quarantined() const { return mQuarantined; } + +private: + void check(int err); + +private: + qtn_file_t mQtn; // qtn handle + bool mQuarantined; // has quarantine information +}; + + +} // end namespace CodeSigning +} // end namespace Security + +#endif // !_H_QUARANTINEPLUSPLUS diff --git a/lib/reqdumper.cpp b/lib/reqdumper.cpp index ec6cd64..ceca150 100644 --- a/lib/reqdumper.cpp +++ b/lib/reqdumper.cpp @@ -109,7 +109,7 @@ string Dumper::dump(const Requirement *req, bool debug /* = false */) } catch (const CommonError &err) { if (debug) { char errstr[80]; - snprintf(errstr, sizeof(errstr), " !! error %ld !!", err.osStatus()); + snprintf(errstr, sizeof(errstr), " !! error %ld !!", (unsigned long)err.osStatus()); return dumper.value() + errstr; } throw; diff --git a/lib/security_codesigning.exp b/lib/security_codesigning.exp index 8558455..a2ca395 100644 --- a/lib/security_codesigning.exp +++ b/lib/security_codesigning.exp @@ -136,10 +136,16 @@ _kSecAssessmentOperationTypeExecute _kSecAssessmentOperationTypeInstall _kSecAssessmentOperationTypeOpenDocument _kSecAssessmentContextKeyUpdate -_kSecAssessmentUpdateOperationAddFile -_kSecAssessmentUpdateOperationRemoveFile +_kSecAssessmentUpdateOperationAdd +_kSecAssessmentUpdateOperationRemove +_kSecAssessmentUpdateOperationEnable +_kSecAssessmentUpdateOperationDisable +_kSecAssessmentUpdateKeyAuthorization +_kSecAssessmentUpdateKeyAllow +_kSecAssessmentUpdateKeyExpires _kSecAssessmentUpdateKeyPriority _kSecAssessmentUpdateKeyLabel +_kSecAssessmentUpdateKeyRemarks _kSecAssessmentAssessmentAuthority _kSecAssessmentAssessmentAuthorityRow _kSecAssessmentAssessmentFromCache diff --git a/lib/syspolicy.sql b/lib/syspolicy.sql index 4e2eb86..9448908 100644 --- a/lib/syspolicy.sql +++ b/lib/syspolicy.sql @@ -25,45 +25,101 @@ -- -- This is currently for sqlite3 -- +-- NOTES: +-- Dates are uniformly in julian form. We use 5000000 as the canonical "never" expiration +-- value; that's a day in the year 8977. +-- +PRAGMA user_version = 1; PRAGMA foreign_keys = true; +PRAGMA legacy_file_format = false; +PRAGMA recursive_triggers = true; + +-- +-- The feature table hold configuration features and options +-- +CREATE TABLE feature ( + id INTEGER PRIMARY KEY, -- canononical + name TEXT NOT NULL UNIQUE, -- name of option + value TEXT NULL, -- value of option, if any + remarks TEXT NOT NULL -- optional remarks string +); -- -- The primary authority. This table is conceptually scanned --- in priority order, with the highest-priority matching record +-- in priority order, with the highest-priority matching enabled record -- determining the outcome. -- CREATE TABLE authority ( - id INTEGER PRIMARY KEY, - type INTEGER NOT NULL, - requirement TEXT NOT NULL, - allow INTEGER NOT NULL, - expires INTEGER NULL, - priority REAL NOT NULL DEFAULT (0), - label TEXT NULL, - inhibit_cache INTEGER NULL, - flags INTEGER NOT NULL DEFAULT (0), + id INTEGER PRIMARY KEY AUTOINCREMENT, -- canonical + version INTEGER NOT NULL DEFAULT (1) -- semantic version of this rule + CHECK (version > 0), + type INTEGER NOT NULL, -- operation type + requirement TEXT NULL -- code requirement + CHECK ((requirement IS NULL) = ((flags & 1) != 0)), + allow INTEGER NOT NULL DEFAULT (1) -- allow (1) or deny (0) + CHECK (allow = 0 OR allow = 1), + disabled INTEGER NOT NULL DEFAULT (0) -- disable count (stacks; enabled if zero) + CHECK (disabled >= 0), + expires FLOAT NOT NULL DEFAULT (5000000), -- expiration of rule authority (Julian date) + priority REAL NOT NULL DEFAULT (0), -- rule priority (full float) + label TEXT NULL, -- text label for authority rule + flags INTEGER NOT NULL DEFAULT (0), -- amalgamated binary flags -- following fields are for documentation only - remarks TEXT NULL + ctime FLOAT NOT NULL DEFAULT (JULIANDAY('now')), -- rule creation time (Julian) + mtime FLOAT NOT NULL DEFAULT (JULIANDAY('now')), -- time rule was last changed (Julian) + user TEXT NULL, -- user requesting this rule (NULL if unknown) + remarks TEXT NULL -- optional remarks string ); --- any Apple-signed installers of any kind -insert into authority (type, allow, priority, label, requirement) - values (2, 1, -1, 'Apple Installer', 'anchor apple generic'); +-- index +CREATE INDEX authority_type ON authority (type); +CREATE INDEX authority_priority ON authority (priority); +CREATE INDEX authority_expires ON authority (expires); + +-- update mtime if a record is changed +CREATE TRIGGER authority_update AFTER UPDATE ON authority +BEGIN + UPDATE authority SET mtime = JULIANDAY('now') WHERE id = old.id; +END; + +-- rules that are actively considered +CREATE VIEW active_authority AS +SELECT * from authority +WHERE disabled = 0 AND JULIANDAY('now') < expires AND (flags & 1) = 0; + +-- rules subject to priority scan: active_authority but including disabled rules +CREATE VIEW scan_authority AS +SELECT * from authority +WHERE JULIANDAY('now') < expires AND (flags & 1) = 0; + + +-- +-- Initial canonical contents of a fresh database +-- + +-- virtual rule anchoring negative cache entries (no rule found) +insert into authority (type, allow, priority, flags, label) + values (1, 0, -1.0E100, 1, 'No Matching Rule'); + +-- any Apple-signed installers except Developer ID +insert into authority (type, allow, priority, flags, label, requirement) + values (2, 1, -1, 2, 'Apple Installer', 'anchor apple generic and ! certificate 1[field.1.2.840.113635.100.6.2.6]'); -- Apple code signing -insert into authority (type, allow, label, requirement) - values (1, 1, 'Apple', 'anchor apple'); +insert into authority (type, allow, flags, label, requirement) + values (1, 1, 2, 'Apple System', 'anchor apple'); -- Mac App Store signing -insert into authority (type, allow, label, requirement) - values (1, 1, 'Mac App Store', 'anchor apple generic and certificate leaf[field.1.2.840.113635.100.6.1.9] exists'); +insert into authority (type, allow, flags, label, requirement) + values (1, 1, 2, 'Mac App Store', 'anchor apple generic and certificate leaf[field.1.2.840.113635.100.6.1.9] exists'); -insert into authority (type, allow, label, requirement) - values (1, 1, 'Developer Seed', 'anchor apple generic and certificate 1[field.1.2.840.113635.100.6.2.6] exists and certificate leaf[field.1.2.840.113635.100.6.1.13] exists'); -insert into authority (type, allow, label, requirement) - values (2, 1, 'Developer Seed', 'anchor apple generic and certificate 1[field.1.2.840.113635.100.6.2.6] exists and certificate leaf[field.1.2.840.113635.100.6.1.14] exists'); +-- Caspian code and archive signing +insert into authority (type, allow, flags, label, requirement) + values (1, 1, 2, 'Developer ID', 'anchor apple generic and certificate 1[field.1.2.840.113635.100.6.2.6] exists and certificate leaf[field.1.2.840.113635.100.6.1.13] exists'); +insert into authority (type, allow, flags, label, requirement) + values (2, 1, 2, 'Developer ID', 'anchor apple generic and certificate 1[field.1.2.840.113635.100.6.2.6] exists and certificate leaf[field.1.2.840.113635.100.6.1.14] exists'); -- @@ -71,17 +127,50 @@ insert into authority (type, allow, label, requirement) -- for individual objects (by object hash). Entries come from -- full evaluations of authority records, or by explicitly inserting -- override rules that preempt the normal authority. +-- EACH object record must have a parent authority record from which it is derived; +-- this may be a normal authority rule or an override rule. If the parent rule is deleted, +-- all objects created from it are automatically removed (by sqlite itself). -- CREATE TABLE object ( - id INTEGER PRIMARY KEY, - type INTEGER NOT NULL, - hash CDHASH NOT NULL UNIQUE, - allow INTEGER NOT NULL, - expires INTEGER NULL, - label TEXT NULL, - authority INTEGER NULL REFERENCES authority(id), + id INTEGER PRIMARY KEY, -- canonical + type INTEGER NOT NULL, -- operation type + hash CDHASH NOT NULL, -- canonical hash of object + allow INTEGER NOT NULL, -- allow (1) or deny (0) + expires FLOAT NOT NULL DEFAULT (5000000), -- expiration of object entry + authority INTEGER NOT NULL -- governing authority rule + REFERENCES authority(id) ON DELETE CASCADE, -- following fields are for documentation only - path TEXT NULL, - created INTEGER NOT NULL default (strftime('%s','now')), - remarks TEXT NULL + path TEXT NULL, -- path of object at record creation time + ctime FLOAT NOT NULL DEFAULT (JULIANDAY('now')), -- record creation time + mtime FLOAT NOT NULL DEFAULT (JULIANDAY('now')), -- record modification time + remarks TEXT NULL -- optional remarks string ); + +-- index +CREATE INDEX object_type ON object (type); +CREATE INDEX object_expires ON object (expires); +CREATE UNIQUE INDEX object_hash ON object (hash); + +-- update mtime if a record is changed +CREATE TRIGGER object_update AFTER UPDATE ON object +BEGIN + UPDATE object SET mtime = JULIANDAY('now') WHERE id = old.id; +END; + + +-- +-- Some useful views on objects. These are for administration; they are not used by the assessor. +-- +CREATE VIEW object_state AS +SELECT object.id, object.type, object.allow, + CASE object.expires WHEN 5000000 THEN NULL ELSE STRFTIME('%Y-%m-%d %H:%M:%f', object.expires, 'localtime') END AS expiration, + (object.expires - JULIANDAY('now')) * 86400 as remaining, + authority.label, + object.authority, + object.path, + object.ctime, + authority.requirement, + authority.disabled, + object.remarks +FROM object, authority +WHERE object.authority = authority.id; diff --git a/lib/xpcengine.cpp b/lib/xpcengine.cpp index b9ace0f..64fbcf9 100644 --- a/lib/xpcengine.cpp +++ b/lib/xpcengine.cpp @@ -25,6 +25,7 @@ #include #include #include +#include namespace Security { @@ -100,7 +101,7 @@ public: static void copyCFDictionary(const void *key, const void *value, void *ctx) { CFMutableDictionaryRef target = CFMutableDictionaryRef(ctx); - if (CFEqual(key, kSecAssessmentContextKeyCertificates)) // legacy; drop it + if (CFEqual(key, kSecAssessmentContextKeyCertificates)) // obsolete return; if (CFGetTypeID(value) == CFURLGetTypeID()) { CFRef path = CFURLCopyFileSystemPath(CFURLRef(value), kCFURLPOSIXPathStyle); @@ -120,7 +121,12 @@ void xpcEngineAssess(CFURLRef path, uint flags, CFDictionaryRef context, CFMutab CFDictionaryApplyFunction(context, copyCFDictionary, ctx); CFRef contextData = makeCFData(CFDictionaryRef(ctx)); xpc_dictionary_set_data(msg, "context", CFDataGetBytePtr(contextData), CFDataGetLength(contextData)); + msg.send(); + + if (int64_t error = xpc_dictionary_get_int64(msg, "error")) + MacOSError::throwMe(error); + size_t resultLength; const void *resultData = xpc_dictionary_get_data(msg, "result", &resultLength); CFRef resultDict = makeCFDictionaryFrom(resultData, resultLength); @@ -129,6 +135,48 @@ void xpcEngineAssess(CFURLRef path, uint flags, CFDictionaryRef context, CFMutab } +bool xpcEngineUpdate(CFTypeRef target, uint flags, CFDictionaryRef context) +{ + Message msg("update"); + // target can be NULL, a CFURLRef, a SecRequirementRef, or a CFNumberRef + if (target) { + if (CFGetTypeID(target) == CFNumberGetTypeID()) + xpc_dictionary_set_uint64(msg, "rule", cfNumber(CFNumberRef(target))); + else if (CFGetTypeID(target) == CFURLGetTypeID()) + xpc_dictionary_set_string(msg, "url", cfString(CFURLRef(target)).c_str()); + else if (CFGetTypeID(target) == SecRequirementGetTypeID()) { + CFRef data; + MacOSError::check(SecRequirementCopyData(SecRequirementRef(target), kSecCSDefaultFlags, &data.aref())); + xpc_dictionary_set_data(msg, "requirement", CFDataGetBytePtr(data), CFDataGetLength(data)); + } else + MacOSError::throwMe(errSecCSInvalidObjectRef); + } + xpc_dictionary_set_int64(msg, "flags", flags); + CFRef ctx = makeCFMutableDictionary(); + if (context) + CFDictionaryApplyFunction(context, copyCFDictionary, ctx); + AuthorizationRef localAuthorization = NULL; + if (CFDictionaryGetValue(ctx, kSecAssessmentUpdateKeyAuthorization) == NULL) { // no caller-provided authorization + MacOSError::check(AuthorizationCreate(NULL, NULL, kAuthorizationFlagDefaults, &localAuthorization)); + AuthorizationExternalForm extForm; + MacOSError::check(AuthorizationMakeExternalForm(localAuthorization, &extForm)); + CFDictionaryAddValue(ctx, kSecAssessmentUpdateKeyAuthorization, CFTempData(&extForm, sizeof(extForm))); + } + CFRef contextData = makeCFData(CFDictionaryRef(ctx)); + xpc_dictionary_set_data(msg, "context", CFDataGetBytePtr(contextData), CFDataGetLength(contextData)); + + msg.send(); + + if (localAuthorization) + AuthorizationFree(localAuthorization, kAuthorizationFlagDefaults); + + if (int64_t error = xpc_dictionary_get_int64(msg, "error")) + MacOSError::throwMe(error); + + return true; +} + + bool xpcEngineControl(const char *control) { Message msg("control"); diff --git a/lib/xpcengine.h b/lib/xpcengine.h index 92a6030..8b2f115 100644 --- a/lib/xpcengine.h +++ b/lib/xpcengine.h @@ -33,6 +33,7 @@ namespace CodeSigning { void xpcEngineAssess(CFURLRef path, uint flags, CFDictionaryRef context, CFMutableDictionaryRef result); +bool xpcEngineUpdate(CFTypeRef target, uint flags, CFDictionaryRef context); bool xpcEngineControl(const char *name); diff --git a/libsecurity_codesigning.xcodeproj/project.pbxproj b/libsecurity_codesigning.xcodeproj/project.pbxproj index 111f93a..8980156 100644 --- a/libsecurity_codesigning.xcodeproj/project.pbxproj +++ b/libsecurity_codesigning.xcodeproj/project.pbxproj @@ -13,6 +13,7 @@ buildPhases = ( C26AC0F0143BCF18001C98CE /* ShellScript */, C26AC0F4143BD1C4001C98CE /* CopyFiles */, + C2F24DFE14BCBBF200309FCD /* ShellScript */, ); dependencies = ( ); @@ -184,6 +185,8 @@ C2E911E20ADEBE3200275CB2 /* resources.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C2E911E00ADEBE3200275CB2 /* resources.cpp */; }; C2EF10100A49BD89005A44BB /* renum.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C2EF100E0A49BD89005A44BB /* renum.cpp */; }; C2EF10130A49BD89005A44BB /* renum.h in Headers */ = {isa = PBXBuildFile; fileRef = C2EF100F0A49BD89005A44BB /* renum.h */; settings = {ATTRIBUTES = (Public, ); }; }; + C2F4439A14C626D4000A01E6 /* quarantine++.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C2F4439814C626D4000A01E6 /* quarantine++.cpp */; }; + C2F4439B14C626D4000A01E6 /* quarantine++.h in Headers */ = {isa = PBXBuildFile; fileRef = C2F4439914C626D4000A01E6 /* quarantine++.h */; }; C2F6566E0BCBFB250078779E /* cserror.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C2F6566C0BCBFB250078779E /* cserror.cpp */; }; C2F656930BCBFFF40078779E /* cserror.h in Headers */ = {isa = PBXBuildFile; fileRef = C2F6566D0BCBFB250078779E /* cserror.h */; settings = {ATTRIBUTES = (Public, ); }; }; FEB30C9310DAC89D00557BA2 /* SecTask.c in Sources */ = {isa = PBXBuildFile; fileRef = FEB30C9210DAC89D00557BA2 /* SecTask.c */; }; @@ -429,6 +432,8 @@ C2E911E10ADEBE3200275CB2 /* resources.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = resources.h; sourceTree = ""; }; C2EF100E0A49BD89005A44BB /* renum.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = renum.cpp; sourceTree = ""; }; C2EF100F0A49BD89005A44BB /* renum.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = renum.h; sourceTree = ""; }; + C2F4439814C626D4000A01E6 /* quarantine++.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = "quarantine++.cpp"; sourceTree = ""; }; + C2F4439914C626D4000A01E6 /* quarantine++.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "quarantine++.h"; sourceTree = ""; }; C2F6071B107D575700A83618 /* codesign-watch.d */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.dtrace; name = "codesign-watch.d"; path = "dtrace/codesign-watch.d"; sourceTree = SOURCE_ROOT; }; C2F6566C0BCBFB250078779E /* cserror.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = cserror.cpp; sourceTree = ""; }; C2F6566D0BCBFB250078779E /* cserror.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = cserror.h; sourceTree = ""; }; @@ -706,6 +711,8 @@ C2A976A80B8A2E36008B4EA0 /* csutilities.cpp */, C235340E145F1B050073F964 /* xar++.h */, C2353410145F1B110073F964 /* xar++.cpp */, + C2F4439914C626D4000A01E6 /* quarantine++.h */, + C2F4439814C626D4000A01E6 /* quarantine++.cpp */, ); name = "Local Utilities"; sourceTree = ""; @@ -811,6 +818,7 @@ C273606F1433F09000A9A5FF /* SecAssessment.h in Headers */, C2A436160F2133B2007A41A6 /* slcrep.h in Headers */, C24EABAB1421432800C16AA9 /* policydb.h in Headers */, + C2F4439B14C626D4000A01E6 /* quarantine++.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -985,7 +993,7 @@ ); runOnlyForDeploymentPostprocessing = 1; shellPath = /bin/sh; - shellScript = "mkdir -p \"$(dirname \"$SCRIPT_OUTPUT_FILE_0\")\"\nsqlite3 \"$SCRIPT_OUTPUT_FILE_0\" <$TEMPDIR/RequirementKeywords.h\n"; }; + C2F24DFE14BCBBF200309FCD /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 8; + files = ( + ); + inputPaths = ( + "$(TEMPDIR)/SystemPolicy", + ); + outputPaths = ( + "$(DSTROOT)/private/var/db/.SystemPolicy-default", + ); + runOnlyForDeploymentPostprocessing = 1; + shellPath = /bin/bash; + shellScript = "cp \"$SCRIPT_INPUT_FILE_0\" \"$SCRIPT_OUTPUT_FILE_0\"\nchmod 444 \"$SCRIPT_OUTPUT_FILE_0\""; + showEnvVarsInLog = 0; + }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -1091,6 +1115,7 @@ C27360D51436866D00A9A5FF /* xpcengine.cpp in Sources */, C2DC2DCA145F594000AD2A3A /* xar++.cpp in Sources */, C2DC2DCB145F5CD000AD2A3A /* policyengine.cpp in Sources */, + C2F4439A14C626D4000A01E6 /* quarantine++.cpp in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1199,7 +1224,7 @@ isa = XCBuildConfiguration; buildSettings = { BUILD_VARIANTS = debug; - CURRENT_PROJECT_VERSION = 55032; + CURRENT_PROJECT_VERSION = 55037.4; EXECUTABLE_PREFIX = ""; EXECUTABLE_SUFFIX = ""; FRAMEWORK_SEARCH_PATHS = ( @@ -1252,7 +1277,7 @@ normal, debug, ); - CURRENT_PROJECT_VERSION = 55032; + CURRENT_PROJECT_VERSION = 55037.4; EXECUTABLE_PREFIX = ""; EXECUTABLE_SUFFIX = ""; FRAMEWORK_SEARCH_PATHS = ( @@ -1303,7 +1328,7 @@ normal, debug, ); - CURRENT_PROJECT_VERSION = 55032; + CURRENT_PROJECT_VERSION = 55037.4; EXECUTABLE_PREFIX = ""; EXECUTABLE_SUFFIX = ""; FRAMEWORK_SEARCH_PATHS = ( diff --git a/requirements.grammar b/requirements.grammar index f6972e7..6b83f05 100644 --- a/requirements.grammar +++ b/requirements.grammar @@ -266,7 +266,7 @@ appleanchor[Maker &maker] { maker.put(opAppleAnchor); } | "generic" { maker.put(opAppleGenericAnchor); } -| | { string name; } name=identifierString + | { string name; } name=identifierString { maker.put(opNamedAnchor); maker.put(name); } ; -- 2.45.2