/*
- * Copyright (c) 2011-2014 Apple Inc. All Rights Reserved.
+ * Copyright (c) 2011-2016 Apple Inc. All Rights Reserved.
*
* @APPLE_LICENSE_HEADER_START@
*
#include "diskrep.h"
#include "codedirectory.h"
#include "csutilities.h"
+#include "notarization.h"
#include "StaticCode.h"
#include <CoreServices/CoreServicesPriv.h>
#include "SecCodePriv.h"
#undef check // Macro! Yech.
-extern "C" {
-#include <OpenScriptingUtilPriv.h>
-}
-
-
namespace Security {
namespace CodeSigning {
static void authorizeUpdate(SecAssessmentFlags flags, CFDictionaryRef context);
-static bool codeInvalidityExceptions(SecStaticCodeRef code, CFMutableDictionaryRef result);
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;
+}
//
}
-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<CFDataRef> 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<CFDataRef> 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)
{
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];
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<SecRequirementRef> requirement;
MacOSError::check(SecRequirementCreateWithString(CFTempString(reqString), kSecCSDefaultFlags, &requirement.aref()));
switch (OSStatus rc = SecStaticCodeCheckValidity(code, kSecCSBasicValidateOnly | kSecCSCheckGatekeeperArchitectures, requirement)) {
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<CFDictionaryRef> info; // as needed
}
}
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<CFDictionaryRef> info;
MacOSError::check(SecCodeCopySigningInformation(code, kSecCSSigningInformation, &info.aref()));
if (flags & kSecAssessmentFlagRequestOrigin) {
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<CFDictionaryRef> conditions = mOpaqueWhitelist.validationConditionsFor(code);
+ CFRef<CFDictionaryRef> 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<CFDataRef> 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<CFDataRef> signature = CFDataCreateMutable(NULL, 0);
- CFTemp<CFDictionaryRef> arguments("{%O=%O, %O=#N}", kSecCodeSignerDetached, signature.get(), kSecCodeSignerIdentity);
+ CFTemp<CFMutableDictionaryRef> 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<SecCodeSignerRef> signer;
MacOSError::check(SecCodeSignerCreate(arguments, (matchFlags & kAuthorityFlagWhitelistV2) ? kSecCSSignOpaque : kSecCSSignV1, &signer.aref()));
MacOSError::check(SecCodeSignerAddSignature(signer, code, kSecCSDefaultFlags));
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)
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<SecStaticCodeRef> code;
- MacOSError::check(SecStaticCodeCreateWithPath(path, kSecCSDefaultFlags, &code.aref()));
+ MacOSError::check(SecStaticCodeCreateWithPath(path, kSecCSDefaultFlags | kSecCSForceOnlineNotarizationCheck, &code.aref()));
SecCSFlags validationFlags = kSecCSEnforceRevocationChecks | kSecCSCheckAllArchitectures;
if (!(flags & kSecAssessmentFlagAllowWeak))
// deal with a very special case (broken 10.6/10.7 Applet bundles)
OSStatus rc = SecStaticCodeCheckValidity(code, validationFlags | kSecCSBasicValidateOnly, NULL);
if (rc == errSecCSSignatureFailed) {
- if (!codeInvalidityExceptions(code, result)) { // invalidly signed, no exceptions -> error
- if (SYSPOLICY_ASSESS_OUTCOME_BROKEN_ENABLED())
- SYSPOLICY_ASSESS_OUTCOME_BROKEN(cfString(path).c_str(), type, false);
- MacOSError::throwMe(rc);
- }
- // recognized exception - treat as unsigned
- if (SYSPOLICY_ASSESS_OUTCOME_BROKEN_ENABLED())
- SYSPOLICY_ASSESS_OUTCOME_BROKEN(cfString(path).c_str(), type, true);
- rc = errSecCSUnsigned;
+ MacOSError::throwMe(rc);
}
// 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
}
// prepare for deep traversal of (hopefully) good signatures
SecAssessmentFeedback feedback = SecAssessmentFeedback(CFDictionaryGetValue(context, kSecAssessmentContextKeyFeedback));
+ __block CFRef<CFMutableDictionaryRef> 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") {
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:
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:
case errSecCSUnsealedAppRoot:
case errSecCSUnsealedFrameworkRoot:
case errSecCSInvalidSymlink:
+ case errSecCSNotAppLike:
{
// consult the whitelist
bool allow = false;
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";
default:
MacOSError::throwMe(rc);
}
+
+ // Copy notarization date, if present, from code signing information
+ CFRef<CFDictionaryRef> 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<uint64_t>(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);
+ }
+ }
}
if (CFRef<CFArrayRef> certs = xar.copyCertChain()) {
CFRef<CFTypeRef> policy = installerPolicy();
CFRef<SecTrustRef> trust;
+ CFRef<CFDataRef> checksum;
+ CFRef<CFStringRef> teamID;
+ CFRef<CFMutableDictionaryRef> requirementContext = makeCFMutableDictionary();
MacOSError::check(SecTrustCreateWithCertificates(certs, policy, &trust.aref()));
// MacOSError::check(SecTrustSetAnchorCertificates(trust, cfEmptyArray())); // no anchors
MacOSError::check(SecTrustSetOptions(trust, kSecTrustOptionAllowExpired | kSecTrustOptionImplicitAnchors));
}
}
+ 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);
+ }
+
+ // Extract a team identifier from the certificates. This isn't validated and could be spoofed,
+ // but since the 'legacy' keyword is only used in addition to the Developer ID requirement,
+ // this is still stafe for now.
+ SecCertificateRef leaf = SecCertificateRef(CFArrayGetValueAtIndex(certs, 0));
+ CFRef<CFArrayRef> orgUnits = SecCertificateCopyOrganizationalUnit(leaf);
+ if (orgUnits.get() && CFArrayGetCount(orgUnits) == 1) {
+ teamID = (CFStringRef)CFArrayGetValueAtIndex(orgUnits, 0);
+ }
+
+ // Create the appropriate requirement context entry to allow notarized requirement check.
+ CFRef<CFNumberRef> algorithm = makeCFNumber((uint32_t)xar.checksumDigestAlgorithm());
+ cfadd(requirementContext, "{%O=%O}", kSecRequirementKeyPackageChecksum, checksum.get());
+ cfadd(requirementContext, "{%O=%O}", kSecRequirementKeyChecksumAlgorithm, algorithm.get());
+ if (teamID.get()) {
+ cfadd(requirementContext, "{%O=%O}", kSecRequirementKeyTeamIdentifier, teamID.get());
+ }
+
+ if (!isnan(notarizationDate)) {
+ CFRef<CFDateRef> 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"
const char *label = query[3];
//sqlite_uint64 ruleFlags = query[4];
SQLite3::int64 disabled = query[5];
-
+
CFRef<SecRequirementRef> 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
//
// Create a suitable policy array for verification of installer signatures.
//
-static SecPolicyRef makeCRLPolicy()
+static SecPolicyRef makeRevocationPolicy()
{
- CFRef<SecPolicyRef> 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<SecPolicyRef> 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<SecPolicyRef> policy(SecPolicyCreateRevocation(kSecRevocationUseAnyAvailableMethod));
return policy.yield();
}
static CFTypeRef installerPolicy()
{
CFRef<SecPolicyRef> base = SecPolicyCreateBasicX509();
- CFRef<SecPolicyRef> crl = makeCRLPolicy();
- CFRef<SecPolicyRef> ocsp = makeOCSPPolicy();
- return makeCFArray(3, base.get(), crl.get(), ocsp.get());
+ CFRef<SecPolicyRef> revoc = makeRevocationPolicy();
+ return makeCFArray(2, base.get(), revoc.get());
}
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)
} 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
}
//
// 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<CFMutableDictionaryRef> auth = makeCFMutableDictionary();
if (label && label[0])
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);
bool allow = true;
double expires = never;
string remarks;
- SQLite::uint64 dbFlags = kAuthorityFlagWhitelistV2;
+ SQLite::uint64 dbFlags = kAuthorityFlagWhitelistV2 | kAuthorityFlagWhitelistSHA256;
if (CFNumberRef pri = ctx.get<CFNumberRef>(kSecAssessmentUpdateKeyPriority))
CFNumberGetValue(pri, kCFNumberDoubleType, &priority);
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,
}
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<CFDictionaryRef> 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);
case errSecCSSignatureFailed:
- // recover certain cases of broken signatures (well, try)
- if (codeInvalidityExceptions(code, NULL)) {
- // Ad-hoc sign the code in place (requiring a writable subject). This requires root privileges.
- CFRef<SecCodeSignerRef> signer;
- CFTemp<CFDictionaryRef> arguments("{%O=#N}", kSecCodeSignerIdentity);
- MacOSError::check(SecCodeSignerCreate(arguments, kSecCSSignOpaque, &signer.aref()));
- MacOSError::check(SecCodeSignerAddSignature(signer, code, kSecCSDefaultFlags));
- MacOSError::check(SecCodeCopyDesignatedRequirement(code, kSecCSDefaultFlags, (SecRequirementRef *)&target.aref()));
- break;
- }
- MacOSError::check(rc);
default:
MacOSError::check(rc);
}
CFStringRef edit = CFStringRef(context.get(kSecAssessmentContextKeyUpdate));
if (type == kAuthorityExecute && CFEqual(edit, kSecAssessmentUpdateOperationAdd)) {
// implicitly whitelist the code
- mOpaqueWhitelist.add(code);
+ opaqueWhitelistAdd(code);
}
}
}
-
-//
-// Process special overrides for invalidly signed code.
-// This is the (hopefully minimal) concessions we make to keep hurting our customers
-// for our own prior mistakes...
-//
-static bool codeInvalidityExceptions(SecStaticCodeRef code, CFMutableDictionaryRef result)
-{
- if (OSAIsRecognizedExecutableURL) {
- CFRef<CFDictionaryRef> 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;
-}
-
-
} // end namespace CodeSigning
} // end namespace Security