X-Git-Url: https://git.saurik.com/apple/security.git/blobdiff_plain/5c19dc3ae3bd8e40a9c028b0deddd50ff337692c..0e1db9d189370fed9f1993183ec38d748a8812f7:/OSX/libsecurity_codesigning/lib/policyengine.cpp diff --git a/OSX/libsecurity_codesigning/lib/policyengine.cpp b/OSX/libsecurity_codesigning/lib/policyengine.cpp index f5b9cb29..f3436493 100644 --- a/OSX/libsecurity_codesigning/lib/policyengine.cpp +++ b/OSX/libsecurity_codesigning/lib/policyengine.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011-2014 Apple Inc. All Rights Reserved. + * Copyright (c) 2011-2016 Apple Inc. All Rights Reserved. * * @APPLE_LICENSE_HEADER_START@ * @@ -38,6 +38,7 @@ #include "diskrep.h" #include "codedirectory.h" #include "csutilities.h" +#include "notarization.h" #include "StaticCode.h" #include @@ -73,10 +74,18 @@ static CFTypeRef installerPolicy() CF_RETURNS_RETAINED; PolicyEngine::PolicyEngine() : PolicyDatabase(NULL, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE) { + try { + mOpaqueWhitelist = new OpaqueWhitelist(); + } catch (...) { + mOpaqueWhitelist = NULL; + secerror("Failed opening the gkopaque database."); + } } PolicyEngine::~PolicyEngine() -{ } +{ + delete mOpaqueWhitelist; +} // @@ -92,8 +101,7 @@ void PolicyEngine::evaluate(CFURLRef path, AuthorityType type, SecAssessmentFlag // perform the evaluation EvaluationTask *evaluationTask = evaluationManager->evaluationTask(this, path, type, flags, context, result); - evaluationManager->waitForCompletion(evaluationTask, flags, result); - evaluationManager->removeTask(evaluationTask); + evaluationManager->finalizeTask(evaluationTask, flags, result); // if rejected, reset the automatic rearm timer if (CFDictionaryGetValue(result, kSecAssessmentAssessmentVerdict) == kCFBooleanFalse) @@ -101,16 +109,56 @@ void PolicyEngine::evaluate(CFURLRef path, AuthorityType type, SecAssessmentFlag } -static std::string createWhitelistScreen(char type, SHA1 &hash) +// +// Create GKE whitelist filter screens. +// These are strings that are used to determine quickly whether unsigned code may +// have a GKE-style whitelist entry in the authority database. The idea is to make +// up a decent hash quickly. +// +// Note: We continue to use SHA1 here for compatibility of existing GKE entries. +// These are a prescreen, backed up by code signature checks later on. Use of SHA1 here is not a security problem. +// +static std::string createWhitelistScreen(char type, const Byte *digest, size_t length) { - SHA1::Digest digest; - hash.finish(digest); - char buffer[2*SHA1::digestLength + 2] = { type }; - for (size_t n = 0; n < SHA1::digestLength; n++) + char buffer[2*length + 2]; + buffer[0] = type; + for (size_t n = 0; n < length; n++) sprintf(buffer + 1 + 2*n, "%02.2x", digest[n]); return buffer; } +static std::string createWhitelistScreen(SecStaticCodeRef code) +{ + DiskRep *rep = SecStaticCode::requiredStatic(code)->diskRep(); + std::string screen; + if (CFRef info = rep->component(cdInfoSlot)) { + // has an Info.plist - hash it + SHA1 hash; + hash.update(CFDataGetBytePtr(info), CFDataGetLength(info)); + SHA1::Digest digest; + hash.finish(digest); + return createWhitelistScreen('I', digest, sizeof(digest)); + } else if (CFRef repSpecific = rep->component(cdRepSpecificSlot)) { + // has a rep-specific slot - hash that (this catches disk images cheaply) + // got invented after SHA-1 deprecation, so we'll use SHA256, which is the new default + CCHashInstance hash(kCCDigestSHA256); + hash.update(CFDataGetBytePtr(repSpecific), CFDataGetLength(repSpecific)); + Byte digest[256/8]; + hash.finish(digest); + return createWhitelistScreen('R', digest, sizeof(digest)); + } else if (rep->mainExecutableImage()) { + // stand-alone Mach-O executables are always candidates + return "N"; + } else { + // if everything else fails, hash the (single) file + SHA1 hash; + hashFileData(rep->mainExecutablePath().c_str(), &hash); + SHA1::Digest digest; + hash.finish(digest); + return createWhitelistScreen('M', digest, sizeof(digest)); + } +} + void PolicyEngine::evaluateCodeItem(SecStaticCodeRef code, CFURLRef path, AuthorityType type, SecAssessmentFlags flags, bool nested, CFMutableDictionaryRef result) { @@ -124,6 +172,7 @@ void PolicyEngine::evaluateCodeItem(SecStaticCodeRef code, CFURLRef path, Author SQLite3::int64 latentID = 0; // first (highest priority) disabled matching ID std::string latentLabel; // ... and associated label, if any + secdebug("gk", "evaluateCodeItem type=%d flags=0x%x nested=%d path=%s", type, int(flags), nested, cfString(path).c_str()); while (query.nextRow()) { bool allow = int(query[0]); const char *reqString = query[1]; @@ -134,7 +183,8 @@ void PolicyEngine::evaluateCodeItem(SecStaticCodeRef code, CFURLRef path, Author SQLite3::int64 disabled = query[6]; // const char *filter = query[7]; // const char *remarks = query[8]; - + + secdebug("gk", "considering rule %d(%s) requirement %s", int(id), label ? label : "UNLABELED", reqString); CFRef requirement; MacOSError::check(SecRequirementCreateWithString(CFTempString(reqString), kSecCSDefaultFlags, &requirement.aref())); switch (OSStatus rc = SecStaticCodeCheckValidity(code, kSecCSBasicValidateOnly | kSecCSCheckGatekeeperArchitectures, requirement)) { @@ -148,15 +198,20 @@ void PolicyEngine::evaluateCodeItem(SecStaticCodeRef code, CFURLRef path, Author MacOSError::throwMe(rc); // general error; pass to caller } - // if this rule is disabled, skip it but record the first matching one for posterity - if (disabled && latentID == 0) { - latentID = id; - latentLabel = label ? label : ""; + // If this rule is disabled, do not continue any further and just continue iterating + // until we find one that is enabled. + if (disabled) { + // ...but always record the first matching rule for informational purposes. + if (latentID == 0) { + latentID = id; + latentLabel = label ? label : ""; + } continue; } - + // current rule is first rule (in priority order) that matched. Apply it - if (nested) // success, nothing to record + secnotice("gk", "rule %d applies - allow=%d", int(id), allow); + if (nested && allow) // success, nothing to record return; CFRef info; // as needed @@ -196,11 +251,12 @@ void PolicyEngine::evaluateCodeItem(SecStaticCodeRef code, CFURLRef path, Author } } cfadd(result, "{%O=%B}", kSecAssessmentAssessmentVerdict, allow); - addAuthority(flags, result, label, id); + addAuthority(flags, result, label, id, NULL, false, ruleFlags); return; } // no applicable authority (but signed, perhaps temporarily). Deny by default + secnotice("gk", "rejecting due to lack of matching active rule"); CFRef info; MacOSError::check(SecCodeCopySigningInformation(code, kSecCSSigningInformation, &info.aref())); if (flags & kSecAssessmentFlagRequestOrigin) { @@ -219,49 +275,61 @@ void PolicyEngine::evaluateCodeItem(SecStaticCodeRef code, CFURLRef path, Author cfadd(result, "{%O=%B}", kSecAssessmentAssessmentVerdict, false); addAuthority(flags, result, latentLabel.c_str(), latentID); } - + +CFDictionaryRef PolicyEngine::opaqueWhitelistValidationConditionsFor(SecStaticCodeRef code) +{ + return (mOpaqueWhitelist != NULL) ? mOpaqueWhitelist->validationConditionsFor(code) : NULL; +} + +bool PolicyEngine::opaqueWhiteListContains(SecStaticCodeRef code, SecAssessmentFeedback feedback, OSStatus reason) +{ + return (mOpaqueWhitelist != NULL) ? mOpaqueWhitelist->contains(code, feedback, reason) : false; +} + +void PolicyEngine::opaqueWhitelistAdd(SecStaticCodeRef code) +{ + if (mOpaqueWhitelist) { + mOpaqueWhitelist->add(code); + } +} void PolicyEngine::adjustValidation(SecStaticCodeRef code) { - CFRef conditions = mOpaqueWhitelist.validationConditionsFor(code); + CFRef conditions = opaqueWhitelistValidationConditionsFor(code); SecStaticCodeSetValidationConditions(code, conditions); } bool PolicyEngine::temporarySigning(SecStaticCodeRef code, AuthorityType type, CFURLRef path, SecAssessmentFlags matchFlags) { - if (matchFlags == 0) { // playback; consult authority table for matches - DiskRep *rep = SecStaticCode::requiredStatic(code)->diskRep(); - std::string screen; - if (CFRef info = rep->component(cdInfoSlot)) { - SHA1 hash; - hash.update(CFDataGetBytePtr(info), CFDataGetLength(info)); - screen = createWhitelistScreen('I', hash); - } else if (rep->mainExecutableImage()) { - screen = "N"; - } else { - SHA1 hash; - hashFileData(rep->mainExecutablePath().c_str(), &hash); - screen = createWhitelistScreen('M', hash); - } - SQLite::Statement query(*this, - "SELECT flags FROM authority " - "WHERE type = :type" - " AND NOT flags & :flag" - " AND CASE WHEN filter_unsigned IS NULL THEN remarks = :remarks ELSE filter_unsigned = :screen END"); - query.bind(":type").integer(type); - query.bind(":flag").integer(kAuthorityFlagDefault); - query.bind(":screen") = screen; - query.bind(":remarks") = cfString(path); - if (!query.nextRow()) // guaranteed no matching rule - return false; - matchFlags = SQLite3::int64(query[0]); - } + secnotice("gk", "temporarySigning type=%d matchFlags=0x%x path=%s", type, int(matchFlags), cfString(path).c_str()); + + // see if we have a screened record to take matchFlags from + std::string screen = createWhitelistScreen(code); + SQLite::Statement query(*this, + "SELECT flags FROM authority " + "WHERE type = :type" + " AND NOT flags & :flag" + " AND CASE WHEN filter_unsigned IS NULL THEN remarks = :remarks ELSE filter_unsigned = :screen END"); + query.bind(":type").integer(type); + query.bind(":flag").integer(kAuthorityFlagDefault); + query.bind(":screen") = screen; + query.bind(":remarks") = cfString(path); + secdebug("gk", "match screen=%s", screen.c_str()); + if (query.nextRow()) // got a matching rule + matchFlags = SQLite3::int64(query[0]); + else if (matchFlags == 0) // lazy and no match + return false; + secdebug("gk", "matchFlags found=0x%x", int(matchFlags)); try { // ad-hoc sign the code and attach the signature CFRef signature = CFDataCreateMutable(NULL, 0); - CFTemp arguments("{%O=%O, %O=#N}", kSecCodeSignerDetached, signature.get(), kSecCodeSignerIdentity); + CFTemp arguments("{%O=%O, %O=#N, %O=%d}", kSecCodeSignerDetached, signature.get(), kSecCodeSignerIdentity, + kSecCodeSignerDigestAlgorithm, (matchFlags & kAuthorityFlagWhitelistSHA256) ? kSecCodeSignatureHashSHA256 : kSecCodeSignatureHashSHA1); + // for modern whitelist entries, neuter the identifier since it may be derived from the filename + if (matchFlags & kAuthorityFlagWhitelistSHA256) + CFDictionaryAddValue(arguments, kSecCodeSignerIdentifier, CFSTR("ADHOC")); CFRef signer; MacOSError::check(SecCodeSignerCreate(arguments, (matchFlags & kAuthorityFlagWhitelistV2) ? kSecCSSignOpaque : kSecCSSignV1, &signer.aref())); MacOSError::check(SecCodeSignerAddSignature(signer, code, kSecCSDefaultFlags)); @@ -271,7 +339,8 @@ bool PolicyEngine::temporarySigning(SecStaticCodeRef code, AuthorityType type, C SecCodeCopyDesignatedRequirement(code, kSecCSDefaultFlags, &dr); CFStringRef drs = NULL; SecRequirementCopyString(dr, kSecCSDefaultFlags, &drs); - + secnotice("gk", "successfully created temporary signature - requirement=%s", cfString(drs).c_str()); + // if we're in GKE recording mode, save that signature and report its location if (SYSPOLICY_RECORDER_MODE_ENABLED()) { int status = recorder_code_unable; // ephemeral signature (not recorded) @@ -308,12 +377,23 @@ bool PolicyEngine::temporarySigning(SecStaticCodeRef code, AuthorityType type, C void PolicyEngine::evaluateCode(CFURLRef path, AuthorityType type, SecAssessmentFlags flags, CFDictionaryRef context, CFMutableDictionaryRef result, bool handleUnsigned) { // not really a Gatekeeper function... but reject all "hard quarantined" files because they were made from sandboxed sources without download privilege - FileQuarantine qtn(cfString(path).c_str()); - if (qtn.flag(QTN_FLAG_HARD)) - MacOSError::throwMe(errSecCSFileHardQuarantined); + if (type == kAuthorityExecute) { + FileQuarantine qtn(cfString(path).c_str()); + if (qtn.flag(QTN_FLAG_HARD)) + MacOSError::throwMe(errSecCSFileHardQuarantined); + } + + // hack: if caller passed a UTI, use that to turn off app-only checks for some well-known ones + bool appOk = false; + if (CFStringRef uti = CFStringRef(CFDictionaryGetValue(context, kSecAssessmentContextKeyUTI))) { + appOk = CFEqual(uti, CFSTR("com.apple.systempreference.prefpane")) + || CFEqual(uti, CFSTR("com.apple.systempreference.screen-saver")) + || CFEqual(uti, CFSTR("com.apple.systempreference.screen-slide-saver")) + || CFEqual(uti, CFSTR("com.apple.menu-extra")); + } CFCopyRef code; - MacOSError::check(SecStaticCodeCreateWithPath(path, kSecCSDefaultFlags, &code.aref())); + MacOSError::check(SecStaticCodeCreateWithPath(path, kSecCSDefaultFlags | kSecCSForceOnlineNotarizationCheck, &code.aref())); SecCSFlags validationFlags = kSecCSEnforceRevocationChecks | kSecCSCheckAllArchitectures; if (!(flags & kSecAssessmentFlagAllowWeak)) @@ -335,8 +415,10 @@ void PolicyEngine::evaluateCode(CFURLRef path, AuthorityType type, SecAssessment } // ad-hoc sign unsigned code + bool wasAdhocSigned = false; if (rc == errSecCSUnsigned && handleUnsigned && (!overrideAssessment(flags) || SYSPOLICY_RECORDER_MODE_ENABLED())) { if (temporarySigning(code, type, path, 0)) { + wasAdhocSigned = true; rc = errSecSuccess; // clear unsigned; we are now well-signed validationFlags |= kSecCSBasicValidateOnly; // no need to re-validate deep contents } @@ -344,6 +426,7 @@ void PolicyEngine::evaluateCode(CFURLRef path, AuthorityType type, SecAssessment // prepare for deep traversal of (hopefully) good signatures SecAssessmentFeedback feedback = SecAssessmentFeedback(CFDictionaryGetValue(context, kSecAssessmentContextKeyFeedback)); + __block CFRef nestedFailure = NULL; // save a nested failure for later MacOSError::check(SecStaticCodeSetCallback(code, kSecCSDefaultFlags, NULL, ^CFTypeRef (SecStaticCodeRef item, CFStringRef cfStage, CFDictionaryRef info) { string stage = cfString(cfStage); if (stage == "prepared") { @@ -359,14 +442,24 @@ void PolicyEngine::evaluateCode(CFURLRef path, AuthorityType type, SecAssessment SecStaticCodeSetCallback(item, kSecCSDefaultFlags, NULL, NULL); // clear callback to avoid unwanted recursion evaluateCodeItem(item, path, type, flags, item != code, result); if (CFTypeRef verdict = CFDictionaryGetValue(result, kSecAssessmentAssessmentVerdict)) - if (CFEqual(verdict, kCFBooleanFalse)) - return makeCFNumber(OSStatus(errSecCSVetoed)); // (signal nested-code policy failure, picked up below) + if (CFEqual(verdict, kCFBooleanFalse)) { + if (item == code) + return makeCFNumber(OSStatus(errSecCSVetoed)); // (signal nested-code policy failure, picked up below) + // nested code policy failure; save, reset, and continue + if (!nestedFailure) + nestedFailure = CFMutableDictionaryRef(CFDictionaryGetValue(result, kSecAssessmentAssessmentAuthority)); + CFDictionaryRemoveValue(result, kSecAssessmentAssessmentAuthority); + CFDictionaryRemoveValue(result, kSecAssessmentAssessmentVerdict); + } } return NULL; })); - + // go for it! - switch (rc = SecStaticCodeCheckValidity(code, validationFlags | kSecCSCheckNestedCode | kSecCSRestrictSymlinks | kSecCSReportProgress, NULL)) { + SecCSFlags topFlags = validationFlags | kSecCSCheckNestedCode | kSecCSRestrictSymlinks | kSecCSReportProgress; + if (type == kAuthorityExecute && !appOk) + topFlags |= kSecCSRestrictToAppLike; + switch (rc = SecStaticCodeCheckValidity(code, topFlags, NULL)) { case errSecSuccess: // continue below break; case errSecCSUnsigned: @@ -374,6 +467,8 @@ void PolicyEngine::evaluateCode(CFURLRef path, AuthorityType type, SecAssessment addAuthority(flags, result, "no usable signature"); return; case errSecCSVetoed: // nested code rejected by rule book; result was filled out there + if (wasAdhocSigned) + addToAuthority(result, kSecAssessmentAssessmentSource, CFSTR("no usable signature")); // ad-hoc signature proved useless return; case errSecCSWeakResourceRules: case errSecCSWeakResourceEnvelope: @@ -386,6 +481,7 @@ void PolicyEngine::evaluateCode(CFURLRef path, AuthorityType type, SecAssessment case errSecCSUnsealedAppRoot: case errSecCSUnsealedFrameworkRoot: case errSecCSInvalidSymlink: + case errSecCSNotAppLike: { // consult the whitelist bool allow = false; @@ -398,8 +494,9 @@ void PolicyEngine::evaluateCode(CFURLRef path, AuthorityType type, SecAssessment if (CFEqual(verdict, kCFBooleanFalse)) // nested code rejected by rule book; result was filled out there return; if (CFEqual(verdict, kCFBooleanTrue) && !(flags & kSecAssessmentFlagIgnoreWhitelist)) - if (mOpaqueWhitelist.contains(code, feedback, rc)) + if (opaqueWhiteListContains(code, feedback, rc)) { allow = true; + } } if (allow) { label = "allowed cdhash"; @@ -414,6 +511,29 @@ void PolicyEngine::evaluateCode(CFURLRef path, AuthorityType type, SecAssessment default: MacOSError::throwMe(rc); } + + // Copy notarization date, if present, from code signing information + CFRef info; + OSStatus status = SecCodeCopySigningInformation(code, kSecCSInternalInformation, &info.aref()); + if (status == 0 && info) { + CFDateRef date = (CFDateRef)CFDictionaryGetValue(info, kSecCodeInfoNotarizationDate); + if (date) { + cfadd(result, "{%O=%O}", kSecAssessmentAssessmentNotarizationDate, date); + } + } else { + secerror("Unable to copy signing information: %d", (int)status); + } + + if (nestedFailure && CFEqual(CFDictionaryGetValue(result, kSecAssessmentAssessmentVerdict), kCFBooleanTrue)) { + // structure intact, top level approved, nested code failed policy + CFMutableDictionaryRef authority = CFMutableDictionaryRef(CFDictionaryGetValue(result, kSecAssessmentAssessmentAuthority)); + uint64_t ruleFlags = cfNumber(CFNumberRef(CFDictionaryGetValue(authority, kSecAssessmentAssessmentAuthorityFlags))); + if (ruleFlags & kAuthorityFlagDefault) { + // default rule requires positive match at each nested code - reinstate failure + CFDictionaryReplaceValue(result, kSecAssessmentAssessmentVerdict, kCFBooleanFalse); + CFDictionaryReplaceValue(result, kSecAssessmentAssessmentAuthority, nestedFailure); + } + } } @@ -465,6 +585,8 @@ void PolicyEngine::evaluateInstall(CFURLRef path, SecAssessmentFlags flags, CFDi if (CFRef certs = xar.copyCertChain()) { CFRef policy = installerPolicy(); CFRef trust; + CFRef checksum; + CFRef requirementContext = makeCFMutableDictionary(); MacOSError::check(SecTrustCreateWithCertificates(certs, policy, &trust.aref())); // MacOSError::check(SecTrustSetAnchorCertificates(trust, cfEmptyArray())); // no anchors MacOSError::check(SecTrustSetOptions(trust, kSecTrustOptionAllowExpired | kSecTrustOptionImplicitAnchors)); @@ -490,6 +612,30 @@ void PolicyEngine::evaluateInstall(CFURLRef path, SecAssessmentFlags flags, CFDi } } + xar.registerStapledNotarization(); + checksum.take(xar.createPackageChecksum()); + if (checksum) { + double notarizationDate = NAN; + + // Force a single online check for the checksum, which is always SHA1. + bool is_revoked = checkNotarizationServiceForRevocation(checksum, kSecCodeSignatureHashSHA1, ¬arizationDate); + if (is_revoked) { + MacOSError::throwMe(errSecCSRevokedNotarization); + } + + // Create the appropriate requirement context entry to allow notarized requirement check. + CFRef algorithm = makeCFNumber((uint32_t)xar.checksumDigestAlgorithm()); + cfadd(requirementContext, "{%O=%O}", kSecRequirementKeyPackageChecksum, checksum.get()); + cfadd(requirementContext, "{%O=%O}", kSecRequirementKeyChecksumAlgorithm, algorithm.get()); + + if (!isnan(notarizationDate)) { + CFRef date = CFDateCreate(NULL, notarizationDate); + if (date) { + cfadd(result, "{%O=%O}", kSecAssessmentAssessmentNotarizationDate, date.get()); + } + } + } + SQLite::Statement query(*this, "SELECT allow, requirement, id, label, flags, disabled FROM scan_authority" " WHERE type = :type" @@ -502,10 +648,10 @@ void PolicyEngine::evaluateInstall(CFURLRef path, SecAssessmentFlags flags, CFDi 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())); - switch (OSStatus rc = SecRequirementEvaluate(requirement, chain, NULL, kSecCSDefaultFlags)) { + switch (OSStatus rc = SecRequirementEvaluate(requirement, chain, requirementContext.get(), kSecCSDefaultFlags)) { case errSecSuccess: // success break; case errSecCSReqFailed: // requirement missed, but otherwise okay @@ -547,38 +693,17 @@ void PolicyEngine::evaluateInstall(CFURLRef path, SecAssessmentFlags flags, CFDi // // 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() +static SecPolicyRef makeRevocationPolicy() { - 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)); + CFRef policy(SecPolicyCreateRevocation(kSecRevocationUseAnyAvailableMethod)); return policy.yield(); } static CFTypeRef installerPolicy() { CFRef base = SecPolicyCreateBasicX509(); - CFRef crl = makeCRLPolicy(); - CFRef ocsp = makeOCSPPolicy(); - return makeCFArray(3, base.get(), crl.get(), ocsp.get()); + CFRef revoc = makeRevocationPolicy(); + return makeCFArray(2, base.get(), revoc.get()); } @@ -589,8 +714,21 @@ static CFTypeRef installerPolicy() void PolicyEngine::evaluateDocOpen(CFURLRef path, SecAssessmentFlags flags, CFDictionaryRef context, CFMutableDictionaryRef result) { if (context) { + FileQuarantine qtn(cfString(path).c_str()); + if (CFDictionaryGetValue(context, kSecAssessmentContextKeyPrimarySignature) == kCFBooleanTrue) { + // Client requests that we focus on the code signature on this document and report on that. + // On this path, we care about the (code) signature on the document, not its risk assessment, + // and any exception is reported as a primary error. + if (qtn.flag(QTN_FLAG_ASSESSMENT_OK)) { + // previously added by user - hacked to say no/no usable signature to trigger proper DMG processing in XProtect + cfadd(result, "{%O=#F}", kSecAssessmentAssessmentVerdict); + addAuthority(flags, result, "no usable signature"); + return; + } + evaluateCode(path, kAuthorityOpenDoc, flags, context, result, true); + return; + } if (CFStringRef riskCategory = CFStringRef(CFDictionaryGetValue(context, kLSDownloadRiskCategoryKey))) { - FileQuarantine qtn(cfString(path).c_str()); if (CFEqual(riskCategory, kLSRiskCategorySafe) || CFEqual(riskCategory, kLSRiskCategoryNeutral) @@ -601,11 +739,12 @@ void PolicyEngine::evaluateDocOpen(CFURLRef path, SecAssessmentFlags flags, CFDi } else if (qtn.flag(QTN_FLAG_HARD)) { MacOSError::throwMe(errSecCSFileHardQuarantined); } else if (qtn.flag(QTN_FLAG_ASSESSMENT_OK)) { + // previously added by user cfadd(result, "{%O=#T}", kSecAssessmentAssessmentVerdict); addAuthority(flags, result, "Prior Assessment"); } else if (!overrideAssessment(flags)) { // no need to do more work if we're off try { - evaluateCode(path, kAuthorityExecute, flags, context, result, false); + evaluateCode(path, kAuthorityOpenDoc, flags, context, result, true); } catch (...) { // some documents can't be code signed, so this may be quite benign } @@ -627,7 +766,7 @@ void PolicyEngine::evaluateDocOpen(CFURLRef path, SecAssessmentFlags flags, CFDi // // Result-creation helpers // -void PolicyEngine::addAuthority(SecAssessmentFlags flags, CFMutableDictionaryRef parent, const char *label, SQLite::int64 row, CFTypeRef cacheInfo, bool weak) +void PolicyEngine::addAuthority(SecAssessmentFlags flags, CFMutableDictionaryRef parent, const char *label, SQLite::int64 row, CFTypeRef cacheInfo, bool weak, uint64_t ruleFlags) { CFRef auth = makeCFMutableDictionary(); if (label && label[0]) @@ -638,6 +777,7 @@ void PolicyEngine::addAuthority(SecAssessmentFlags flags, CFMutableDictionaryRef CFDictionaryAddValue(auth, kSecAssessmentAssessmentAuthorityOverride, kDisabledOverride); if (cacheInfo) CFDictionaryAddValue(auth, kSecAssessmentAssessmentFromCache, cacheInfo); + CFDictionaryAddValue(auth, kSecAssessmentAssessmentAuthorityFlags, CFTempNumber(ruleFlags)); if (weak) { CFDictionaryAddValue(auth, kSecAssessmentAssessmentWeakSignature, kCFBooleanTrue); CFDictionaryReplaceValue(parent, kSecAssessmentAssessmentAuthority, auth); @@ -716,7 +856,7 @@ CFDictionaryRef PolicyEngine::add(CFTypeRef inTarget, AuthorityType type, SecAss bool allow = true; double expires = never; string remarks; - SQLite::uint64 dbFlags = kAuthorityFlagWhitelistV2; + SQLite::uint64 dbFlags = kAuthorityFlagWhitelistV2 | kAuthorityFlagWhitelistSHA256; if (CFNumberRef pri = ctx.get(kSecAssessmentUpdateKeyPriority)) CFNumberGetValue(pri, kCFNumberDoubleType, &priority); @@ -790,6 +930,9 @@ CFDictionaryRef PolicyEngine::disable(CFTypeRef target, AuthorityType type, SecA CFDictionaryRef PolicyEngine::find(CFTypeRef target, AuthorityType type, SecAssessmentFlags flags, CFDictionaryRef context) { + //for privacy reasons we only want to allow the admin to list the database + authorizeUpdate(flags, context); + SQLite::Statement query(*this); selectRules(query, "SELECT scan_authority.id, scan_authority.type, scan_authority.requirement, scan_authority.allow, scan_authority.label, scan_authority.priority, scan_authority.remarks, scan_authority.expires, scan_authority.disabled, bookmarkhints.bookmark FROM scan_authority LEFT OUTER JOIN bookmarkhints ON scan_authority.id = bookmarkhints.authority", "scan_authority", target, type, flags, context, @@ -1037,12 +1180,9 @@ void PolicyEngine::normalizeTarget(CFRef &target, AuthorityType type, } break; case errSecCSUnsigned: - if (signUnsigned && temporarySigning(code, type, path, kAuthorityFlagWhitelistV2)) { // ad-hoc signed the code temporarily + if (signUnsigned && temporarySigning(code, type, path, kAuthorityFlagWhitelistV2 | kAuthorityFlagWhitelistSHA256)) { // ad-hoc sign the code temporarily MacOSError::check(SecCodeCopyDesignatedRequirement(code, kSecCSDefaultFlags, (SecRequirementRef *)&target.aref())); - CFRef info; - MacOSError::check(SecCodeCopySigningInformation(code, kSecCSInternalInformation, &info.aref())); - if (CFDataRef cdData = CFDataRef(CFDictionaryGetValue(info, kSecCodeInfoCodeDirectory))) - *signUnsigned = ((const CodeDirectory *)CFDataGetBytePtr(cdData))->screeningCode(); + *signUnsigned = createWhitelistScreen(code); break; } MacOSError::check(rc); @@ -1072,7 +1212,7 @@ void PolicyEngine::normalizeTarget(CFRef &target, AuthorityType type, CFStringRef edit = CFStringRef(context.get(kSecAssessmentContextKeyUpdate)); if (type == kAuthorityExecute && CFEqual(edit, kSecAssessmentUpdateOperationAdd)) { // implicitly whitelist the code - mOpaqueWhitelist.add(code); + opaqueWhitelistAdd(code); } } } @@ -1085,17 +1225,15 @@ void PolicyEngine::normalizeTarget(CFRef &target, AuthorityType type, // static bool codeInvalidityExceptions(SecStaticCodeRef code, CFMutableDictionaryRef result) { - if (OSAIsRecognizedExecutableURL) { - CFRef info; - MacOSError::check(SecCodeCopySigningInformation(code, kSecCSDefaultFlags, &info.aref())); - if (CFURLRef executable = CFURLRef(CFDictionaryGetValue(info, kSecCodeInfoMainExecutable))) { - SInt32 error; - if (OSAIsRecognizedExecutableURL(executable, &error)) { - if (result) - CFDictionaryAddValue(result, - kSecAssessmentAssessmentAuthorityOverride, CFSTR("ignoring known invalid applet signature")); - return true; - } + CFRef info; + MacOSError::check(SecCodeCopySigningInformation(code, kSecCSDefaultFlags, &info.aref())); + if (CFURLRef executable = CFURLRef(CFDictionaryGetValue(info, kSecCodeInfoMainExecutable))) { + SInt32 error; + if (OSAIsRecognizedExecutableURL(executable, &error)) { + if (result) + CFDictionaryAddValue(result, + kSecAssessmentAssessmentAuthorityOverride, CFSTR("ignoring known invalid applet signature")); + return true; } } return false;