/ OSX / libsecurity_codesigning / lib / policyengine.cpp
policyengine.cpp
   1  /*
   2   * Copyright (c) 2011-2016 Apple Inc. All Rights Reserved.
   3   * 
   4   * @APPLE_LICENSE_HEADER_START@
   5   * 
   6   * This file contains Original Code and/or Modifications of Original Code
   7   * as defined in and that are subject to the Apple Public Source License
   8   * Version 2.0 (the 'License'). You may not use this file except in
   9   * compliance with the License. Please obtain a copy of the License at
  10   * http://www.opensource.apple.com/apsl/ and read it before using this
  11   * file.
  12   * 
  13   * The Original Code and all software distributed under the License are
  14   * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
  15   * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
  16   * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
  17   * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
  18   * Please see the License for the specific language governing rights and
  19   * limitations under the License.
  20   * 
  21   * @APPLE_LICENSE_HEADER_END@
  22   */
  23  #include "policyengine.h"
  24  #include "xar++.h"
  25  #include "quarantine++.h"
  26  #include "codesigning_dtrace.h"
  27  #include <security_utilities/cfmunge.h>
  28  #include <Security/Security.h>
  29  #include <Security/SecCodePriv.h>
  30  #include <Security/SecRequirementPriv.h>
  31  #include <Security/SecPolicyPriv.h>
  32  #include <Security/SecTrustPriv.h>
  33  #include <Security/SecCodeSigner.h>
  34  #include <Security/cssmapplePriv.h>
  35  #include <security_utilities/unix++.h>
  36  #include <notify.h>
  37  
  38  #include "diskrep.h"
  39  #include "codedirectory.h"
  40  #include "csutilities.h"
  41  #include "notarization.h"
  42  #include "StaticCode.h"
  43  
  44  #include <CoreServices/CoreServicesPriv.h>
  45  #include "SecCodePriv.h"
  46  #undef check // Macro! Yech.
  47  
  48  namespace Security {
  49  namespace CodeSigning {
  50  
  51  static const double NEGATIVE_HOLD = 60.0/86400;	// 60 seconds to cache negative outcomes
  52  
  53  static const char RECORDER_DIR[] = "/tmp/gke-";		// recorder mode destination for detached signatures
  54  enum {
  55  	recorder_code_untrusted = 0,		// signed but untrusted
  56  	recorder_code_adhoc = 1,			// unsigned; signature recorded
  57  	recorder_code_unable = 2,			// unsigned; unable to record signature
  58  };
  59  
  60  
  61  static void authorizeUpdate(SecAssessmentFlags flags, CFDictionaryRef context);
  62  static CFTypeRef installerPolicy() CF_RETURNS_RETAINED;
  63  
  64  
  65  //
  66  // Core structure
  67  //
  68  PolicyEngine::PolicyEngine()
  69  	: PolicyDatabase(NULL, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE)
  70  {
  71  	try {
  72  		mOpaqueWhitelist = new OpaqueWhitelist();
  73  	} catch (...) {
  74  		mOpaqueWhitelist = NULL;
  75  		secerror("Failed opening the gkopaque database.");
  76  	}
  77  }
  78  
  79  PolicyEngine::~PolicyEngine()
  80  {
  81  	delete mOpaqueWhitelist;
  82  }
  83  
  84  
  85  //
  86  // Top-level evaluation driver
  87  //
  88  void PolicyEngine::evaluate(CFURLRef path, AuthorityType type, SecAssessmentFlags flags, CFDictionaryRef context, CFMutableDictionaryRef result)
  89  {
  90      // update GKE
  91      installExplicitSet(gkeAuthFile, gkeSigsFile);
  92  
  93      // find the global evaluation manager
  94      EvaluationManager *evaluationManager = EvaluationManager::globalManager();
  95  
  96      // perform the evaluation
  97      EvaluationTask *evaluationTask = evaluationManager->evaluationTask(this, path, type, flags, context, result);
  98      evaluationManager->finalizeTask(evaluationTask, flags, result);
  99  
 100      // if rejected, reset the automatic rearm timer
 101      if (CFDictionaryGetValue(result, kSecAssessmentAssessmentVerdict) == kCFBooleanFalse)
 102          resetRearmTimer("reject");
 103  }
 104  
 105  
 106  //
 107  // Create GKE whitelist filter screens.
 108  // These are strings that are used to determine quickly whether unsigned code may
 109  // have a GKE-style whitelist entry in the authority database. The idea is to make
 110  // up a decent hash quickly.
 111  //
 112  // Note: We continue to use SHA1 here for compatibility of existing GKE entries.
 113  // These are a prescreen, backed up by code signature checks later on. Use of SHA1 here is not a security problem.
 114  //
 115  static std::string createWhitelistScreen(char type, const Byte *digest, size_t length)
 116  {
 117  	char buffer[2*length + 2];
 118  	buffer[0] = type;
 119  	for (size_t n = 0; n < length; n++)
 120  		sprintf(buffer + 1 + 2*n, "%02.2x", digest[n]);
 121  	return buffer;
 122  }
 123  
 124  static std::string createWhitelistScreen(SecStaticCodeRef code)
 125  {
 126  	DiskRep *rep = SecStaticCode::requiredStatic(code)->diskRep();
 127  	std::string screen;
 128  	if (CFRef<CFDataRef> info = rep->component(cdInfoSlot)) {
 129  		// has an Info.plist - hash it
 130  		SHA1 hash;
 131  		hash.update(CFDataGetBytePtr(info), CFDataGetLength(info));
 132  		SHA1::Digest digest;
 133  		hash.finish(digest);
 134  		return createWhitelistScreen('I', digest, sizeof(digest));
 135  	} else if (CFRef<CFDataRef> repSpecific = rep->component(cdRepSpecificSlot)) {
 136  		// has a rep-specific slot - hash that (this catches disk images cheaply)
 137  		// got invented after SHA-1 deprecation, so we'll use SHA256, which is the new default
 138  		CCHashInstance hash(kCCDigestSHA256);
 139  		hash.update(CFDataGetBytePtr(repSpecific), CFDataGetLength(repSpecific));
 140  		Byte digest[256/8];
 141  		hash.finish(digest);
 142  		return createWhitelistScreen('R', digest, sizeof(digest));
 143  	} else if (rep->mainExecutableImage()) {
 144  		// stand-alone Mach-O executables are always candidates
 145  		return "N";
 146  	} else {
 147  		// if everything else fails, hash the (single) file
 148  		SHA1 hash;
 149  		hashFileData(rep->mainExecutablePath().c_str(), &hash);
 150  		SHA1::Digest digest;
 151  		hash.finish(digest);
 152  		return createWhitelistScreen('M', digest, sizeof(digest));
 153  	}
 154  }
 155  
 156  
 157  void PolicyEngine::evaluateCodeItem(SecStaticCodeRef code, CFURLRef path, AuthorityType type, SecAssessmentFlags flags, bool nested, CFMutableDictionaryRef result)
 158  {
 159  	
 160  	SQLite::Statement query(*this,
 161  		"SELECT allow, requirement, id, label, expires, flags, disabled, filter_unsigned, remarks FROM scan_authority"
 162  		" WHERE type = :type"
 163  		" ORDER BY priority DESC;");
 164  	query.bind(":type").integer(type);
 165  	
 166  	SQLite3::int64 latentID = 0;		// first (highest priority) disabled matching ID
 167  	std::string latentLabel;			// ... and associated label, if any
 168  
 169      secdebug("gk", "evaluateCodeItem type=%d flags=0x%x nested=%d path=%s", type, int(flags), nested, cfString(path).c_str());
 170  	while (query.nextRow()) {
 171  		bool allow = int(query[0]);
 172  		const char *reqString = query[1];
 173  		SQLite3::int64 id = query[2];
 174  		const char *label = query[3];
 175  		double expires = query[4];
 176  		sqlite3_int64 ruleFlags = query[5];
 177  		SQLite3::int64 disabled = query[6];
 178  //		const char *filter = query[7];
 179  //		const char *remarks = query[8];
 180  
 181  		secdebug("gk", "considering rule %d(%s) requirement %s", int(id), label ? label : "UNLABELED", reqString);
 182  		CFRef<SecRequirementRef> requirement;
 183  		MacOSError::check(SecRequirementCreateWithString(CFTempString(reqString), kSecCSDefaultFlags, &requirement.aref()));
 184  		switch (OSStatus rc = SecStaticCodeCheckValidity(code, kSecCSBasicValidateOnly | kSecCSCheckGatekeeperArchitectures, requirement)) {
 185  		case errSecSuccess:
 186  			break;						// rule match; process below
 187  		case errSecCSReqFailed:
 188  			continue;					// rule does not apply
 189  		case errSecCSVetoed:
 190  			return;						// nested code has failed to pass
 191  		default:
 192  			MacOSError::throwMe(rc);	// general error; pass to caller
 193  		}
 194  		
 195  		// If this rule is disabled, do not continue any further and just continue iterating
 196  		// until we find one that is enabled.
 197  		if (disabled) {
 198  			// ...but always record the first matching rule for informational purposes.
 199  			if (latentID == 0) {
 200  				latentID = id;
 201  				latentLabel = label ? label : "";
 202  			}
 203  			continue;
 204  		}
 205  
 206  		// current rule is first rule (in priority order) that matched. Apply it
 207          secnotice("gk", "rule %d applies - allow=%d", int(id), allow);
 208  		if (nested && allow)			// success, nothing to record
 209  			return;
 210  
 211  		CFRef<CFDictionaryRef> info;	// as needed
 212  		if (flags & kSecAssessmentFlagRequestOrigin) {
 213  			if (!info)
 214  				MacOSError::check(SecCodeCopySigningInformation(code, kSecCSSigningInformation, &info.aref()));
 215  			if (CFArrayRef chain = CFArrayRef(CFDictionaryGetValue(info, kSecCodeInfoCertificates)))
 216  				setOrigin(chain, result);
 217  		}
 218  		if (!(ruleFlags & kAuthorityFlagInhibitCache) && !(flags & kSecAssessmentFlagNoCache)) {	// cache inhibit
 219  			if (!info)
 220  				MacOSError::check(SecCodeCopySigningInformation(code, kSecCSSigningInformation, &info.aref()));
 221  			if (SecTrustRef trust = SecTrustRef(CFDictionaryGetValue(info, kSecCodeInfoTrust))) {
 222  				CFRef<CFDictionaryRef> xinfo;
 223  				MacOSError::check(SecTrustCopyExtendedResult(trust, &xinfo.aref()));
 224  				if (CFDateRef limit = CFDateRef(CFDictionaryGetValue(xinfo, kSecTrustExpirationDate))) {
 225  					this->recordOutcome(code, allow, type, min(expires, dateToJulian(limit)), id);
 226  				}
 227  			}
 228  		}
 229  		if (allow) {
 230  			if (SYSPOLICY_ASSESS_OUTCOME_ACCEPT_ENABLED()) {
 231  				if (!info)
 232  					MacOSError::check(SecCodeCopySigningInformation(code, kSecCSSigningInformation, &info.aref()));
 233  				CFDataRef cdhash = CFDataRef(CFDictionaryGetValue(info, kSecCodeInfoUnique));
 234  				SYSPOLICY_ASSESS_OUTCOME_ACCEPT(cfString(path).c_str(), type, label, cdhash ? CFDataGetBytePtr(cdhash) : NULL);
 235  			}
 236  		} else {
 237  			if (SYSPOLICY_ASSESS_OUTCOME_DENY_ENABLED() || SYSPOLICY_RECORDER_MODE_ENABLED()) {
 238  				if (!info)
 239  					MacOSError::check(SecCodeCopySigningInformation(code, kSecCSSigningInformation, &info.aref()));
 240  				CFDataRef cdhash = CFDataRef(CFDictionaryGetValue(info, kSecCodeInfoUnique));
 241  				std::string cpath = cfString(path);
 242  				const void *hashp = cdhash ? CFDataGetBytePtr(cdhash) : NULL;
 243  				SYSPOLICY_ASSESS_OUTCOME_DENY(cpath.c_str(), type, label, hashp);
 244  				SYSPOLICY_RECORDER_MODE(cpath.c_str(), type, label, hashp, recorder_code_untrusted);
 245  			}
 246  		}
 247  		cfadd(result, "{%O=%B}", kSecAssessmentAssessmentVerdict, allow);
 248  		addAuthority(flags, result, label, id, NULL, false, ruleFlags);
 249  		return;
 250  	}
 251  	
 252  	// no applicable authority (but signed, perhaps temporarily). Deny by default
 253      secnotice("gk", "rejecting due to lack of matching active rule");
 254  	CFRef<CFDictionaryRef> info;
 255  	MacOSError::check(SecCodeCopySigningInformation(code, kSecCSSigningInformation, &info.aref()));
 256  	if (flags & kSecAssessmentFlagRequestOrigin) {
 257  		if (CFArrayRef chain = CFArrayRef(CFDictionaryGetValue(info, kSecCodeInfoCertificates)))
 258  			setOrigin(chain, result);
 259  	}
 260  	if (SYSPOLICY_ASSESS_OUTCOME_DEFAULT_ENABLED() || SYSPOLICY_RECORDER_MODE_ENABLED()) {
 261  		CFDataRef cdhash = CFDataRef(CFDictionaryGetValue(info, kSecCodeInfoUnique));
 262  		const void *hashp = cdhash ? CFDataGetBytePtr(cdhash) : NULL;
 263  		std::string cpath = cfString(path);
 264  		SYSPOLICY_ASSESS_OUTCOME_DEFAULT(cpath.c_str(), type, latentLabel.c_str(), hashp);
 265  		SYSPOLICY_RECORDER_MODE(cpath.c_str(), type, latentLabel.c_str(), hashp, 0);
 266  	}
 267  	if (!(flags & kSecAssessmentFlagNoCache))
 268  		this->recordOutcome(code, false, type, this->julianNow() + NEGATIVE_HOLD, latentID);
 269  	cfadd(result, "{%O=%B}", kSecAssessmentAssessmentVerdict, false);
 270  	addAuthority(flags, result, latentLabel.c_str(), latentID);
 271  }
 272  
 273  CFDictionaryRef PolicyEngine::opaqueWhitelistValidationConditionsFor(SecStaticCodeRef code)
 274  {
 275  	 return (mOpaqueWhitelist != NULL) ? mOpaqueWhitelist->validationConditionsFor(code) : NULL;
 276  }
 277  
 278  bool PolicyEngine::opaqueWhiteListContains(SecStaticCodeRef code, SecAssessmentFeedback feedback, OSStatus reason)
 279  {
 280  	return (mOpaqueWhitelist != NULL) ? mOpaqueWhitelist->contains(code, feedback, reason) : false;
 281  }
 282  
 283  void PolicyEngine::opaqueWhitelistAdd(SecStaticCodeRef code)
 284  {
 285  	if (mOpaqueWhitelist) {
 286  		mOpaqueWhitelist->add(code);
 287  	}
 288  }
 289  
 290  void PolicyEngine::adjustValidation(SecStaticCodeRef code)
 291  {
 292  	CFRef<CFDictionaryRef> conditions = opaqueWhitelistValidationConditionsFor(code);
 293  	SecStaticCodeSetValidationConditions(code, conditions);
 294  }
 295  
 296  
 297  bool PolicyEngine::temporarySigning(SecStaticCodeRef code, AuthorityType type, CFURLRef path, SecAssessmentFlags matchFlags)
 298  {
 299      secnotice("gk", "temporarySigning type=%d matchFlags=0x%x path=%s", type, int(matchFlags), cfString(path).c_str());
 300  
 301      // see if we have a screened record to take matchFlags from
 302      std::string screen = createWhitelistScreen(code);
 303      SQLite::Statement query(*this,
 304          "SELECT flags FROM authority "
 305          "WHERE type = :type"
 306          " AND NOT flags & :flag"
 307          " AND CASE WHEN filter_unsigned IS NULL THEN remarks = :remarks ELSE filter_unsigned = :screen END");
 308      query.bind(":type").integer(type);
 309      query.bind(":flag").integer(kAuthorityFlagDefault);
 310      query.bind(":screen") = screen;
 311      query.bind(":remarks") = cfString(path);
 312      secdebug("gk", "match screen=%s", screen.c_str());
 313      if (query.nextRow())	// got a matching rule
 314          matchFlags = SQLite3::int64(query[0]);
 315      else if (matchFlags == 0)  // lazy and no match
 316          return false;
 317      secdebug("gk", "matchFlags found=0x%x", int(matchFlags));
 318  
 319  	try {
 320  		// ad-hoc sign the code and attach the signature
 321  		CFRef<CFDataRef> signature = CFDataCreateMutable(NULL, 0);
 322  		CFTemp<CFMutableDictionaryRef> arguments("{%O=%O, %O=#N, %O=%d}", kSecCodeSignerDetached, signature.get(), kSecCodeSignerIdentity,
 323  			kSecCodeSignerDigestAlgorithm, (matchFlags & kAuthorityFlagWhitelistSHA256) ? kSecCodeSignatureHashSHA256 : kSecCodeSignatureHashSHA1);
 324  		// for modern whitelist entries, neuter the identifier since it may be derived from the filename
 325  		if (matchFlags & kAuthorityFlagWhitelistSHA256)
 326  			CFDictionaryAddValue(arguments, kSecCodeSignerIdentifier, CFSTR("ADHOC"));
 327  		CFRef<SecCodeSignerRef> signer;
 328  		MacOSError::check(SecCodeSignerCreate(arguments, (matchFlags & kAuthorityFlagWhitelistV2) ? kSecCSSignOpaque : kSecCSSignV1, &signer.aref()));
 329  		MacOSError::check(SecCodeSignerAddSignature(signer, code, kSecCSDefaultFlags));
 330  		MacOSError::check(SecCodeSetDetachedSignature(code, signature, kSecCSDefaultFlags));
 331  		
 332  		SecRequirementRef dr = NULL;
 333  		SecCodeCopyDesignatedRequirement(code, kSecCSDefaultFlags, &dr);
 334  		CFStringRef drs = NULL;
 335  		SecRequirementCopyString(dr, kSecCSDefaultFlags, &drs);
 336          secnotice("gk", "successfully created temporary signature - requirement=%s", cfString(drs).c_str());
 337          
 338  		// if we're in GKE recording mode, save that signature and report its location
 339  		if (SYSPOLICY_RECORDER_MODE_ENABLED()) {
 340  			int status = recorder_code_unable;	// ephemeral signature (not recorded)
 341  			if (geteuid() == 0) {
 342  				CFRef<CFUUIDRef> uuid = CFUUIDCreate(NULL);
 343  				std::string sigfile = RECORDER_DIR + cfStringRelease(CFUUIDCreateString(NULL, uuid)) + ".tsig";
 344  				try {
 345  					UnixPlusPlus::AutoFileDesc fd(sigfile, O_WRONLY | O_CREAT);
 346  					fd.write(CFDataGetBytePtr(signature), CFDataGetLength(signature));
 347  					status = recorder_code_adhoc;	// recorded signature
 348  					SYSPOLICY_RECORDER_MODE_ADHOC_PATH(cfString(path).c_str(), type, sigfile.c_str());
 349  				} catch (...) { }
 350  			}
 351  
 352  			// now report the D probe itself
 353  			CFRef<CFDictionaryRef> info;
 354  			MacOSError::check(SecCodeCopySigningInformation(code, kSecCSDefaultFlags, &info.aref()));
 355  			CFDataRef cdhash = CFDataRef(CFDictionaryGetValue(info, kSecCodeInfoUnique));
 356  			SYSPOLICY_RECORDER_MODE(cfString(path).c_str(), type, "",
 357  				cdhash ? CFDataGetBytePtr(cdhash) : NULL, status);
 358  		}
 359  		
 360  		return true;	// it worked; we're now (well) signed
 361  	} catch (...) { }
 362  	
 363  	return false;
 364  }
 365  
 366  
 367  //
 368  // Executable code.
 369  // Read from disk, evaluate properly, cache as indicated.
 370  //
 371  void PolicyEngine::evaluateCode(CFURLRef path, AuthorityType type, SecAssessmentFlags flags, CFDictionaryRef context, CFMutableDictionaryRef result, bool handleUnsigned)
 372  {
 373  	// not really a Gatekeeper function... but reject all "hard quarantined" files because they were made from sandboxed sources without download privilege
 374      if (type == kAuthorityExecute) {
 375          FileQuarantine qtn(cfString(path).c_str());
 376          if (qtn.flag(QTN_FLAG_HARD))
 377              MacOSError::throwMe(errSecCSFileHardQuarantined);
 378      }
 379  	
 380  	// hack: if caller passed a UTI, use that to turn off app-only checks for some well-known ones
 381  	bool appOk = false;
 382  	if (CFStringRef uti = CFStringRef(CFDictionaryGetValue(context, kSecAssessmentContextKeyUTI))) {
 383  		appOk = CFEqual(uti, CFSTR("com.apple.systempreference.prefpane"))
 384  			|| CFEqual(uti, CFSTR("com.apple.systempreference.screen-saver"))
 385  			|| CFEqual(uti, CFSTR("com.apple.systempreference.screen-slide-saver"))
 386  			|| CFEqual(uti, CFSTR("com.apple.menu-extra"));
 387  	}
 388  	
 389  	CFCopyRef<SecStaticCodeRef> code;
 390  	MacOSError::check(SecStaticCodeCreateWithPath(path, kSecCSDefaultFlags | kSecCSForceOnlineNotarizationCheck, &code.aref()));
 391  	
 392  	SecCSFlags validationFlags = kSecCSEnforceRevocationChecks | kSecCSCheckAllArchitectures | kSecCSAllowNetworkAccess;
 393  	if (!(flags & kSecAssessmentFlagAllowWeak))
 394  		validationFlags |= kSecCSStrictValidate;
 395  	adjustValidation(code);
 396  	
 397  	// deal with a very special case (broken 10.6/10.7 Applet bundles)
 398  	OSStatus rc = SecStaticCodeCheckValidity(code, validationFlags | kSecCSBasicValidateOnly, NULL);
 399  	if (rc == errSecCSSignatureFailed) {
 400  		MacOSError::throwMe(rc);
 401  	}
 402  
 403  	// ad-hoc sign unsigned code
 404      bool wasAdhocSigned = false;
 405  	if (rc == errSecCSUnsigned && handleUnsigned && (!overrideAssessment(flags) || SYSPOLICY_RECORDER_MODE_ENABLED())) {
 406  		if (temporarySigning(code, type, path, 0)) {
 407              wasAdhocSigned = true;
 408  			rc = errSecSuccess;		// clear unsigned; we are now well-signed
 409  			validationFlags |= kSecCSBasicValidateOnly;	// no need to re-validate deep contents
 410  		}
 411  	}
 412  	
 413  	// prepare for deep traversal of (hopefully) good signatures
 414  	SecAssessmentFeedback feedback = SecAssessmentFeedback(CFDictionaryGetValue(context, kSecAssessmentContextKeyFeedback));
 415  	__block CFRef<CFMutableDictionaryRef> nestedFailure = NULL;	// save a nested failure for later
 416  	MacOSError::check(SecStaticCodeSetCallback(code, kSecCSDefaultFlags, NULL, ^CFTypeRef (SecStaticCodeRef item, CFStringRef cfStage, CFDictionaryRef info) {
 417  		string stage = cfString(cfStage);
 418  		if (stage == "prepared") {
 419  			if (!CFEqual(item, code))	// genuine nested (not top) code
 420  				adjustValidation(item);
 421  		} else if (stage == "progress") {
 422  			if (feedback && CFEqual(item, code)) {	// top level progress
 423  				bool proceed = feedback(kSecAssessmentFeedbackProgress, info);
 424  				if (!proceed)
 425  					SecStaticCodeCancelValidation(code, kSecCSDefaultFlags);
 426  			}
 427  		} else if (stage == "validated") {
 428  			SecStaticCodeSetCallback(item, kSecCSDefaultFlags, NULL, NULL);		// clear callback to avoid unwanted recursion
 429  			evaluateCodeItem(item, path, type, flags, item != code, result);
 430  			if (CFTypeRef verdict = CFDictionaryGetValue(result, kSecAssessmentAssessmentVerdict))
 431  				if (CFEqual(verdict, kCFBooleanFalse)) {
 432  					if (item == code)
 433  						return makeCFNumber(OSStatus(errSecCSVetoed));  // (signal nested-code policy failure, picked up below)
 434  					// nested code policy failure; save, reset, and continue
 435  					if (!nestedFailure)
 436  						nestedFailure = CFMutableDictionaryRef(CFDictionaryGetValue(result, kSecAssessmentAssessmentAuthority));
 437  					CFDictionaryRemoveValue(result, kSecAssessmentAssessmentAuthority);
 438  					CFDictionaryRemoveValue(result, kSecAssessmentAssessmentVerdict);
 439  				}
 440  		}
 441  		return NULL;
 442  	}));
 443  
 444  	// go for it!
 445  	SecCSFlags topFlags = validationFlags | kSecCSCheckNestedCode | kSecCSRestrictSymlinks | kSecCSReportProgress;
 446  	if (type == kAuthorityExecute && !appOk)
 447  		topFlags |= kSecCSRestrictToAppLike;
 448  	switch (rc = SecStaticCodeCheckValidity(code, topFlags, NULL)) {
 449  	case errSecSuccess:		// continue below
 450  		break;
 451  	case errSecCSUnsigned:
 452  		cfadd(result, "{%O=#F}", kSecAssessmentAssessmentVerdict);
 453  		addAuthority(flags, result, "no usable signature");
 454  		return;
 455  	case errSecCSVetoed:		// nested code rejected by rule book; result was filled out there
 456          if (wasAdhocSigned)
 457              addToAuthority(result, kSecAssessmentAssessmentSource, CFSTR("no usable signature"));   // ad-hoc signature proved useless
 458  		return;
 459  	case errSecCSWeakResourceRules:
 460  	case errSecCSWeakResourceEnvelope:
 461  	case errSecCSResourceNotSupported:
 462  	case errSecCSAmbiguousBundleFormat:
 463  	case errSecCSSignatureNotVerifiable:
 464  	case errSecCSRegularFile:
 465  	case errSecCSBadMainExecutable:
 466  	case errSecCSBadFrameworkVersion:
 467  	case errSecCSUnsealedAppRoot:
 468  	case errSecCSUnsealedFrameworkRoot:
 469  	case errSecCSInvalidSymlink:
 470  	case errSecCSNotAppLike:
 471  	{
 472  		// consult the whitelist
 473  		bool allow = false;
 474  		const char *label;
 475  		// we've bypassed evaluateCodeItem before we failed validation. Explicitly apply it now
 476  		SecStaticCodeSetCallback(code, kSecCSDefaultFlags, NULL, NULL);
 477  		evaluateCodeItem(code, path, type, flags | kSecAssessmentFlagNoCache, false, result);
 478  		if (CFTypeRef verdict = CFDictionaryGetValue(result, kSecAssessmentAssessmentVerdict)) {
 479  			// verdict rendered from a nested component - signature not acceptable to Gatekeeper
 480  			if (CFEqual(verdict, kCFBooleanFalse))	// nested code rejected by rule book; result was filled out there
 481  				return;
 482  			if (CFEqual(verdict, kCFBooleanTrue) && !(flags & kSecAssessmentFlagIgnoreWhitelist))
 483  				if (opaqueWhiteListContains(code, feedback, rc)) {
 484  					allow = true;
 485  				}
 486  		}
 487  		if (allow) {
 488  			label = "allowed cdhash";
 489  		} else {
 490  			CFDictionaryReplaceValue(result, kSecAssessmentAssessmentVerdict, kCFBooleanFalse);
 491  			label = "obsolete resource envelope";
 492  		}
 493  		cfadd(result, "{%O=%d}", kSecAssessmentAssessmentCodeSigningError, rc);
 494  		addAuthority(flags, result, label, 0, NULL, true);
 495  		return;
 496  	}
 497  	default:
 498  		MacOSError::throwMe(rc);
 499  	}
 500  
 501  	// Copy notarization date, if present, from code signing information
 502  	CFRef<CFDictionaryRef> info;
 503  	OSStatus status = SecCodeCopySigningInformation(code, kSecCSInternalInformation, &info.aref());
 504  	if (status == 0 && info) {
 505  		CFDateRef date = (CFDateRef)CFDictionaryGetValue(info, kSecCodeInfoNotarizationDate);
 506  		if (date) {
 507  			cfadd(result, "{%O=%O}", kSecAssessmentAssessmentNotarizationDate, date);
 508  		}
 509  	} else {
 510  		secerror("Unable to copy signing information: %d", (int)status);
 511  	}
 512  	
 513  	if (nestedFailure && CFEqual(CFDictionaryGetValue(result, kSecAssessmentAssessmentVerdict), kCFBooleanTrue)) {
 514  		// structure intact, top level approved, nested code failed policy
 515  		CFMutableDictionaryRef authority = CFMutableDictionaryRef(CFDictionaryGetValue(result, kSecAssessmentAssessmentAuthority));
 516  		uint64_t ruleFlags = cfNumber<uint64_t>(CFNumberRef(CFDictionaryGetValue(authority, kSecAssessmentAssessmentAuthorityFlags)));
 517  		if (ruleFlags & kAuthorityFlagDefault) {
 518  			// default rule requires positive match at each nested code - reinstate failure
 519  			CFDictionaryReplaceValue(result, kSecAssessmentAssessmentVerdict, kCFBooleanFalse);
 520  			CFDictionaryReplaceValue(result, kSecAssessmentAssessmentAuthority, nestedFailure);
 521  		}
 522  	}
 523  }
 524  
 525  
 526  //
 527  // Installer archive.
 528  // Hybrid policy: If we detect an installer signature, use and validate that.
 529  // If we don't, check for a code signature instead.
 530  //
 531  void PolicyEngine::evaluateInstall(CFURLRef path, SecAssessmentFlags flags, CFDictionaryRef context, CFMutableDictionaryRef result)
 532  {
 533  	const AuthorityType type = kAuthorityInstall;
 534  
 535  	// check for recent explicit approval, using a bookmark's FileResourceIdentifierKey
 536  	if (CFRef<CFDataRef> bookmark = cfLoadFile(lastApprovedFile)) {
 537  		Boolean stale;
 538  		if (CFRef<CFURLRef> url = CFURLCreateByResolvingBookmarkData(NULL, bookmark,
 539  			kCFBookmarkResolutionWithoutUIMask | kCFBookmarkResolutionWithoutMountingMask, NULL, NULL, &stale, NULL))
 540  			if (CFRef<CFDataRef> savedIdent = CFDataRef(CFURLCreateResourcePropertyForKeyFromBookmarkData(NULL, kCFURLFileResourceIdentifierKey, bookmark)))
 541  				if (CFRef<CFDateRef> savedMod = CFDateRef(CFURLCreateResourcePropertyForKeyFromBookmarkData(NULL, kCFURLContentModificationDateKey, bookmark))) {
 542  					CFRef<CFDataRef> currentIdent;
 543  					CFRef<CFDateRef> currentMod;
 544  					if (CFURLCopyResourcePropertyForKey(path, kCFURLFileResourceIdentifierKey, &currentIdent.aref(), NULL))
 545  						if (CFURLCopyResourcePropertyForKey(path, kCFURLContentModificationDateKey, &currentMod.aref(), NULL))
 546  							if (CFEqual(savedIdent, currentIdent) && CFEqual(savedMod, currentMod)) {
 547  								cfadd(result, "{%O=#T}", kSecAssessmentAssessmentVerdict);
 548  								addAuthority(flags, result, "explicit preference");
 549  								return;
 550  							}
 551  				}
 552  	}
 553  	
 554  	Xar xar(cfString(path).c_str());
 555  	if (!xar) {
 556  		// follow the code signing path
 557  		evaluateCode(path, type, flags, context, result, true);
 558  		return;
 559  	}
 560  
 561  	SQLite3::int64 latentID = 0;		// first (highest priority) disabled matching ID
 562  	std::string latentLabel;			// ... and associated label, if any
 563  	if (!xar.isSigned()) {
 564  		// unsigned xar
 565  		if (SYSPOLICY_ASSESS_OUTCOME_UNSIGNED_ENABLED())
 566  			SYSPOLICY_ASSESS_OUTCOME_UNSIGNED(cfString(path).c_str(), type);
 567  		cfadd(result, "{%O=#F}", kSecAssessmentAssessmentVerdict);
 568  		addAuthority(flags, result, "no usable signature");
 569  		return;
 570  	}
 571  	if (CFRef<CFArrayRef> certs = xar.copyCertChain()) {
 572  		CFRef<CFTypeRef> policy = installerPolicy();
 573  		CFRef<SecTrustRef> trust;
 574  		CFRef<CFDataRef> checksum;
 575  		CFRef<CFStringRef> teamID;
 576  		CFRef<CFMutableDictionaryRef> requirementContext = makeCFMutableDictionary();
 577  		MacOSError::check(SecTrustCreateWithCertificates(certs, policy, &trust.aref()));
 578  //		MacOSError::check(SecTrustSetAnchorCertificates(trust, cfEmptyArray())); // no anchors
 579  		MacOSError::check(SecTrustSetOptions(trust, kSecTrustOptionAllowExpired | kSecTrustOptionImplicitAnchors));
 580  
 581  		SecTrustResultType trustResult;
 582  		MacOSError::check(SecTrustEvaluate(trust, &trustResult));
 583  		CFRef<CFArrayRef> chain;
 584  		CSSM_TP_APPLE_EVIDENCE_INFO *info;
 585  		MacOSError::check(SecTrustGetResult(trust, &trustResult, &chain.aref(), &info));
 586  			
 587  		if (flags & kSecAssessmentFlagRequestOrigin)
 588  			setOrigin(chain, result);
 589  		
 590  		switch (trustResult) {
 591  		case kSecTrustResultProceed:
 592  		case kSecTrustResultUnspecified:
 593  			break;
 594  		default:
 595  			{
 596  				OSStatus rc;
 597  				MacOSError::check(SecTrustGetCssmResultCode(trust, &rc));
 598  				MacOSError::throwMe(rc);
 599  			}
 600  		}
 601  
 602  		xar.registerStapledNotarization();
 603  		checksum.take(xar.createPackageChecksum());
 604  		if (checksum) {
 605  			double notarizationDate = NAN;
 606  
 607  			// Force a single online check for the checksum, which is always SHA1.
 608  			bool is_revoked = checkNotarizationServiceForRevocation(checksum, kSecCodeSignatureHashSHA1, &notarizationDate);
 609  			if (is_revoked) {
 610  				MacOSError::throwMe(errSecCSRevokedNotarization);
 611  			}
 612  
 613  			// Extract a team identifier from the certificates.  This isn't validated and could be spoofed,
 614  			// but since the 'legacy' keyword is only used in addition to the Developer ID requirement,
 615  			// this is still stafe for now.
 616  			SecCertificateRef leaf = SecCertificateRef(CFArrayGetValueAtIndex(certs, 0));
 617  			CFRef<CFArrayRef> orgUnits = SecCertificateCopyOrganizationalUnit(leaf);
 618  			if (orgUnits.get() && CFArrayGetCount(orgUnits) == 1) {
 619  				teamID = (CFStringRef)CFArrayGetValueAtIndex(orgUnits, 0);
 620  			}
 621  
 622  			// Create the appropriate requirement context entry to allow notarized requirement check.
 623  			CFRef<CFNumberRef> algorithm = makeCFNumber((uint32_t)xar.checksumDigestAlgorithm());
 624  			cfadd(requirementContext, "{%O=%O}", kSecRequirementKeyPackageChecksum, checksum.get());
 625  			cfadd(requirementContext, "{%O=%O}", kSecRequirementKeyChecksumAlgorithm, algorithm.get());
 626  			if (teamID.get()) {
 627  				cfadd(requirementContext, "{%O=%O}", kSecRequirementKeyTeamIdentifier, teamID.get());
 628  			}
 629  
 630  			if (!isnan(notarizationDate)) {
 631  				CFRef<CFDateRef> date = CFDateCreate(NULL, notarizationDate);
 632  				if (date) {
 633  					cfadd(result, "{%O=%O}", kSecAssessmentAssessmentNotarizationDate, date.get());
 634  				}
 635  			}
 636  		}
 637  
 638  		SQLite::Statement query(*this,
 639  			"SELECT allow, requirement, id, label, flags, disabled FROM scan_authority"
 640  			" WHERE type = :type"
 641  			" ORDER BY priority DESC;");
 642  		query.bind(":type").integer(type);
 643  		while (query.nextRow()) {
 644  			bool allow = int(query[0]);
 645  			const char *reqString = query[1];
 646  			SQLite3::int64 id = query[2];
 647  			const char *label = query[3];
 648  			//sqlite_uint64 ruleFlags = query[4];
 649  			SQLite3::int64 disabled = query[5];
 650  
 651  			CFRef<SecRequirementRef> requirement;
 652  			MacOSError::check(SecRequirementCreateWithString(CFTempString(reqString), kSecCSDefaultFlags, &requirement.aref()));
 653  			switch (OSStatus rc = SecRequirementEvaluate(requirement, chain, requirementContext.get(), kSecCSDefaultFlags)) {
 654  			case errSecSuccess: // success
 655  				break;
 656  			case errSecCSReqFailed: // requirement missed, but otherwise okay
 657  				continue;
 658  			default: // broken in some way; all tests will fail like this so bail out
 659  				MacOSError::throwMe(rc);
 660  			}
 661  			if (disabled) {
 662  				if (latentID == 0) {
 663  					latentID = id;
 664  					if (label)
 665  						latentLabel = label;
 666  				}
 667  				continue;	// the loop
 668  			}
 669  
 670  			if (SYSPOLICY_ASSESS_OUTCOME_ACCEPT_ENABLED() || SYSPOLICY_ASSESS_OUTCOME_DENY_ENABLED()) {
 671  				if (allow)
 672  					SYSPOLICY_ASSESS_OUTCOME_ACCEPT(cfString(path).c_str(), type, label, NULL);
 673  				else
 674  					SYSPOLICY_ASSESS_OUTCOME_DENY(cfString(path).c_str(), type, label, NULL);
 675  			}
 676  
 677  			// not adding to the object cache - we could, but it's not likely to be worth it
 678  			cfadd(result, "{%O=%B}", kSecAssessmentAssessmentVerdict, allow);
 679  			addAuthority(flags, result, label, id);
 680  			return;
 681  		}
 682  	}
 683  	if (SYSPOLICY_ASSESS_OUTCOME_DEFAULT_ENABLED())
 684  		SYSPOLICY_ASSESS_OUTCOME_DEFAULT(cfString(path).c_str(), type, latentLabel.c_str(), NULL);
 685  	
 686  	// no applicable authority. Deny by default
 687  	cfadd(result, "{%O=#F}", kSecAssessmentAssessmentVerdict);
 688  	addAuthority(flags, result, latentLabel.c_str(), latentID);
 689  }
 690  
 691  
 692  //
 693  // Create a suitable policy array for verification of installer signatures.
 694  //
 695  static SecPolicyRef makeRevocationPolicy()
 696  {
 697  	CFRef<SecPolicyRef> policy(SecPolicyCreateRevocation(kSecRevocationUseAnyAvailableMethod));
 698  	return policy.yield();
 699  }
 700  
 701  static CFTypeRef installerPolicy()
 702  {
 703  	CFRef<SecPolicyRef> base = SecPolicyCreateBasicX509();
 704  	CFRef<SecPolicyRef> revoc = makeRevocationPolicy();
 705  	return makeCFArray(2, base.get(), revoc.get());
 706  }
 707  
 708  
 709  //
 710  // LaunchServices-layer document open.
 711  // We don't cache those at present. If we ever do, we need to authenticate CoreServicesUIAgent as the source of its risk assessment.
 712  //
 713  void PolicyEngine::evaluateDocOpen(CFURLRef path, SecAssessmentFlags flags, CFDictionaryRef context, CFMutableDictionaryRef result)
 714  {
 715  	if (context) {
 716  		FileQuarantine qtn(cfString(path).c_str());
 717  		if (CFDictionaryGetValue(context, kSecAssessmentContextKeyPrimarySignature) == kCFBooleanTrue) {
 718  			// Client requests that we focus on the code signature on this document and report on that.
 719  			// On this path, we care about the (code) signature on the document, not its risk assessment,
 720  			// and any exception is reported as a primary error.
 721  			if (qtn.flag(QTN_FLAG_ASSESSMENT_OK)) {
 722  				// previously added by user - hacked to say no/no usable signature to trigger proper DMG processing in XProtect
 723  				cfadd(result, "{%O=#F}", kSecAssessmentAssessmentVerdict);
 724                  addAuthority(flags, result, "no usable signature");
 725  				return;
 726  			}
 727  			evaluateCode(path, kAuthorityOpenDoc, flags, context, result, true);
 728  			return;
 729  		}
 730  		if (CFStringRef riskCategory = CFStringRef(CFDictionaryGetValue(context, kLSDownloadRiskCategoryKey))) {
 731  
 732  			if (CFEqual(riskCategory, kLSRiskCategorySafe)
 733  				|| CFEqual(riskCategory, kLSRiskCategoryNeutral)
 734  				|| CFEqual(riskCategory, kLSRiskCategoryUnknown)
 735  				|| CFEqual(riskCategory, kLSRiskCategoryMayContainUnsafeExecutable)) {
 736  				cfadd(result, "{%O=#T}", kSecAssessmentAssessmentVerdict);
 737  				addAuthority(flags, result, "_XProtect");
 738  			} else if (qtn.flag(QTN_FLAG_HARD)) {
 739  				MacOSError::throwMe(errSecCSFileHardQuarantined);
 740  			} else if (qtn.flag(QTN_FLAG_ASSESSMENT_OK)) {
 741  				// previously added by user
 742  				cfadd(result, "{%O=#T}", kSecAssessmentAssessmentVerdict);
 743  				addAuthority(flags, result, "Prior Assessment");
 744  			} else if (!overrideAssessment(flags)) {		// no need to do more work if we're off
 745  				try {
 746  					evaluateCode(path, kAuthorityOpenDoc, flags, context, result, true);
 747  				} catch (...) {
 748  					// some documents can't be code signed, so this may be quite benign
 749  				}
 750  			}
 751  			if (CFDictionaryGetValue(result, kSecAssessmentAssessmentVerdict) == NULL) {	// no code signature to help us out
 752  			   cfadd(result, "{%O=#F}", kSecAssessmentAssessmentVerdict);
 753  			   addAuthority(flags, result, "_XProtect");
 754  			}
 755  			addToAuthority(result, kLSDownloadRiskCategoryKey, riskCategory);
 756  			return;
 757  		}
 758  	}
 759  	// insufficient information from LS - deny by default
 760  	cfadd(result, "{%O=#F}", kSecAssessmentAssessmentVerdict);
 761  	addAuthority(flags, result, "Insufficient Context");
 762  }
 763  
 764  
 765  //
 766  // Result-creation helpers
 767  //
 768  void PolicyEngine::addAuthority(SecAssessmentFlags flags, CFMutableDictionaryRef parent, const char *label, SQLite::int64 row, CFTypeRef cacheInfo, bool weak, uint64_t ruleFlags)
 769  {
 770  	CFRef<CFMutableDictionaryRef> auth = makeCFMutableDictionary();
 771  	if (label && label[0])
 772  		cfadd(auth, "{%O=%s}", kSecAssessmentAssessmentSource, label);
 773  	if (row)
 774  		CFDictionaryAddValue(auth, kSecAssessmentAssessmentAuthorityRow, CFTempNumber(row));
 775  	if (overrideAssessment(flags))
 776  		CFDictionaryAddValue(auth, kSecAssessmentAssessmentAuthorityOverride, kDisabledOverride);
 777  	if (cacheInfo)
 778  		CFDictionaryAddValue(auth, kSecAssessmentAssessmentFromCache, cacheInfo);
 779  	CFDictionaryAddValue(auth, kSecAssessmentAssessmentAuthorityFlags, CFTempNumber(ruleFlags));
 780  	if (weak) {
 781  		CFDictionaryAddValue(auth, kSecAssessmentAssessmentWeakSignature, kCFBooleanTrue);
 782  		CFDictionaryReplaceValue(parent, kSecAssessmentAssessmentAuthority, auth);
 783  	} else {
 784  		CFDictionaryAddValue(parent, kSecAssessmentAssessmentAuthority, auth);
 785  	}
 786  }
 787  
 788  void PolicyEngine::addToAuthority(CFMutableDictionaryRef parent, CFStringRef key, CFTypeRef value)
 789  {
 790  	CFMutableDictionaryRef authority = CFMutableDictionaryRef(CFDictionaryGetValue(parent, kSecAssessmentAssessmentAuthority));
 791  	assert(authority);
 792  	CFDictionaryAddValue(authority, key, value);
 793  }
 794  
 795  
 796  //
 797  // Add a rule to the policy database
 798  //
 799  CFDictionaryRef PolicyEngine::add(CFTypeRef inTarget, AuthorityType type, SecAssessmentFlags flags, CFDictionaryRef context)
 800  {
 801  	// default type to execution
 802  	if (type == kAuthorityInvalid)
 803  		type = kAuthorityExecute;
 804  
 805  	authorizeUpdate(flags, context);
 806  	CFDictionary ctx(context, errSecCSInvalidAttributeValues);
 807  	CFCopyRef<CFTypeRef> target = inTarget;
 808  	CFRef<CFDataRef> bookmark = NULL;
 809  	std::string filter_unsigned;
 810  
 811  	switch (type) {
 812  	case kAuthorityExecute:
 813  		normalizeTarget(target, type, ctx, &filter_unsigned);
 814  		// bookmarks are untrusted and just a hint to callers
 815  		bookmark = ctx.get<CFDataRef>(kSecAssessmentRuleKeyBookmark);
 816  		break;
 817  	case kAuthorityInstall:
 818  		if (inTarget && CFGetTypeID(inTarget) == CFURLGetTypeID()) {
 819  			// no good way to turn an installer file into a requirement. Pretend to succeeed so caller proceeds
 820  			CFRef<CFArrayRef> properties = makeCFArray(2, kCFURLFileResourceIdentifierKey, kCFURLContentModificationDateKey);
 821  			CFRef<CFErrorRef> error;
 822  			CFURLBookmarkCreationOptions options = kCFURLBookmarkCreationDoNotIncludeSandboxExtensionsMask | kCFURLBookmarkCreationMinimalBookmarkMask;
 823  			if (CFRef<CFDataRef> bookmark = CFURLCreateBookmarkData(NULL, CFURLRef(inTarget), options, properties, NULL, &error.aref())) {
 824  				UnixPlusPlus::AutoFileDesc fd(lastApprovedFile, O_WRONLY | O_CREAT | O_TRUNC);
 825  				fd.write(CFDataGetBytePtr(bookmark), CFDataGetLength(bookmark));
 826  				return NULL;
 827  			}
 828  		}
 829  		break;
 830  	case kAuthorityOpenDoc:
 831  		// handle document-open differently: use quarantine flags for whitelisting
 832  		if (!target || CFGetTypeID(target) != CFURLGetTypeID())	// can only "add" file paths
 833  			MacOSError::throwMe(errSecCSInvalidObjectRef);
 834  		try {
 835  			std::string spath = cfString(target.as<CFURLRef>());
 836  			FileQuarantine qtn(spath.c_str());
 837  			qtn.setFlag(QTN_FLAG_ASSESSMENT_OK);
 838  			qtn.applyTo(spath.c_str());
 839  		} catch (const CommonError &error) {
 840  			// could not set quarantine flag - report qualified success
 841  			return cfmake<CFDictionaryRef>("{%O=%O,'assessment:error'=%d}",
 842  				kSecAssessmentAssessmentAuthorityOverride, CFSTR("error setting quarantine"), error.osStatus());
 843  		} catch (...) {
 844  			return cfmake<CFDictionaryRef>("{%O=%O}", kSecAssessmentAssessmentAuthorityOverride, CFSTR("unable to set quarantine"));
 845  		}
 846  		return NULL;
 847  	}
 848  	
 849  	// if we now have anything else, we're busted
 850  	if (!target || CFGetTypeID(target) != SecRequirementGetTypeID())
 851  		MacOSError::throwMe(errSecCSInvalidObjectRef);
 852  
 853  	double priority = 0;
 854  	string label;
 855  	bool allow = true;
 856  	double expires = never;
 857  	string remarks;
 858  	SQLite::uint64 dbFlags = kAuthorityFlagWhitelistV2 | kAuthorityFlagWhitelistSHA256;
 859  	
 860  	if (CFNumberRef pri = ctx.get<CFNumberRef>(kSecAssessmentUpdateKeyPriority))
 861  		CFNumberGetValue(pri, kCFNumberDoubleType, &priority);
 862  	if (CFStringRef lab = ctx.get<CFStringRef>(kSecAssessmentUpdateKeyLabel))
 863  		label = cfString(lab);
 864  	if (CFDateRef time = ctx.get<CFDateRef>(kSecAssessmentUpdateKeyExpires))
 865  		// we're using Julian dates here; convert from CFDate
 866  		expires = dateToJulian(time);
 867  	if (CFBooleanRef allowing = ctx.get<CFBooleanRef>(kSecAssessmentUpdateKeyAllow))
 868  		allow = allowing == kCFBooleanTrue;
 869  	if (CFStringRef rem = ctx.get<CFStringRef>(kSecAssessmentUpdateKeyRemarks))
 870  		remarks = cfString(rem);
 871  
 872  	CFRef<CFStringRef> requirementText;
 873  	MacOSError::check(SecRequirementCopyString(target.as<SecRequirementRef>(), kSecCSDefaultFlags, &requirementText.aref()));
 874  	SQLite::Transaction xact(*this, SQLite3::Transaction::deferred, "add_rule");
 875  	SQLite::Statement insert(*this,
 876  		"INSERT INTO authority (type, allow, requirement, priority, label, expires, filter_unsigned, remarks, flags)"
 877  		"	VALUES (:type, :allow, :requirement, :priority, :label, :expires, :filter_unsigned, :remarks, :flags);");
 878  	insert.bind(":type").integer(type);
 879  	insert.bind(":allow").integer(allow);
 880  	insert.bind(":requirement") = requirementText.get();
 881  	insert.bind(":priority") = priority;
 882  	if (!label.empty())
 883  		insert.bind(":label") = label;
 884  	insert.bind(":expires") = expires;
 885  	insert.bind(":filter_unsigned") = filter_unsigned.empty() ? NULL : filter_unsigned.c_str();
 886  	if (!remarks.empty())
 887  		insert.bind(":remarks") = remarks;
 888  	insert.bind(":flags").integer(dbFlags);
 889  	insert.execute();
 890  	SQLite::int64 newRow = this->lastInsert();
 891  	if (bookmark) {
 892  		SQLite::Statement bi(*this, "INSERT INTO bookmarkhints (bookmark, authority) VALUES (:bookmark, :authority)");
 893  		bi.bind(":bookmark") = CFDataRef(bookmark);
 894  		bi.bind(":authority").integer(newRow);
 895  		bi.execute();
 896  	}
 897  	this->purgeObjects(priority);
 898  	xact.commit();
 899  	notify_post(kNotifySecAssessmentUpdate);
 900  	return cfmake<CFDictionaryRef>("{%O=%d}", kSecAssessmentUpdateKeyRow, newRow);
 901  }
 902  
 903  
 904  CFDictionaryRef PolicyEngine::remove(CFTypeRef target, AuthorityType type, SecAssessmentFlags flags, CFDictionaryRef context)
 905  {
 906  	if (type == kAuthorityOpenDoc) {
 907  		// handle document-open differently: use quarantine flags for whitelisting
 908  		authorizeUpdate(flags, context);
 909  		if (!target || CFGetTypeID(target) != CFURLGetTypeID())
 910  			MacOSError::throwMe(errSecCSInvalidObjectRef);
 911  		std::string spath = cfString(CFURLRef(target)).c_str();
 912  		FileQuarantine qtn(spath.c_str());
 913  		qtn.clearFlag(QTN_FLAG_ASSESSMENT_OK);
 914  		qtn.applyTo(spath.c_str());
 915  		return NULL;
 916  	}
 917  	return manipulateRules("DELETE FROM authority", target, type, flags, context, true);
 918  }
 919  
 920  CFDictionaryRef PolicyEngine::enable(CFTypeRef target, AuthorityType type, SecAssessmentFlags flags, CFDictionaryRef context, bool authorize)
 921  {
 922  	return manipulateRules("UPDATE authority SET disabled = 0", target, type, flags, context, authorize);
 923  }
 924  
 925  CFDictionaryRef PolicyEngine::disable(CFTypeRef target, AuthorityType type, SecAssessmentFlags flags, CFDictionaryRef context, bool authorize)
 926  {
 927  	return manipulateRules("UPDATE authority SET disabled = 1", target, type, flags, context, authorize);
 928  }
 929  
 930  CFDictionaryRef PolicyEngine::find(CFTypeRef target, AuthorityType type, SecAssessmentFlags flags, CFDictionaryRef context)
 931  {
 932      //for privacy reasons we only want to allow the admin to list the database
 933      authorizeUpdate(flags, context);
 934  
 935  	SQLite::Statement query(*this);
 936  	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",
 937  		"scan_authority", target, type, flags, context,
 938  		" ORDER BY priority DESC");
 939  	CFRef<CFMutableArrayRef> found = makeCFMutableArray(0);
 940  	while (query.nextRow()) {
 941  		SQLite::int64 id = query[0];
 942  		int type = int(query[1]);
 943  		const char *requirement = query[2];
 944  		int allow = int(query[3]);
 945  		const char *label = query[4];
 946  		double priority = query[5];
 947  		const char *remarks = query[6];
 948  		double expires = query[7];
 949  		int disabled = int(query[8]);
 950  		CFRef<CFDataRef> bookmark = query[9].data();
 951  		CFRef<CFMutableDictionaryRef> rule = makeCFMutableDictionary(5,
 952  			kSecAssessmentRuleKeyID, CFTempNumber(id).get(),
 953  			kSecAssessmentRuleKeyType, CFRef<CFStringRef>(typeNameFor(type)).get(),
 954  			kSecAssessmentRuleKeyRequirement, CFTempString(requirement).get(),
 955  			kSecAssessmentRuleKeyAllow, allow ? kCFBooleanTrue : kCFBooleanFalse,
 956  			kSecAssessmentRuleKeyPriority, CFTempNumber(priority).get()
 957  			);
 958  		if (label)
 959  			CFDictionaryAddValue(rule, kSecAssessmentRuleKeyLabel, CFTempString(label));
 960  		if (remarks)
 961  			CFDictionaryAddValue(rule, kSecAssessmentRuleKeyRemarks, CFTempString(remarks));
 962  		if (expires != never)
 963  			CFDictionaryAddValue(rule, kSecAssessmentRuleKeyExpires, CFRef<CFDateRef>(julianToDate(expires)));
 964  		if (disabled)
 965  			CFDictionaryAddValue(rule, kSecAssessmentRuleKeyDisabled, CFTempNumber(disabled));
 966  		if (bookmark)
 967  			CFDictionaryAddValue(rule, kSecAssessmentRuleKeyBookmark, bookmark);
 968  		CFArrayAppendValue(found, rule);
 969  	}
 970  	if (CFArrayGetCount(found) == 0)
 971  		MacOSError::throwMe(errSecCSNoMatches);
 972  	return cfmake<CFDictionaryRef>("{%O=%O}", kSecAssessmentUpdateKeyFound, found.get());
 973  }
 974  
 975  
 976  CFDictionaryRef PolicyEngine::update(CFTypeRef target, SecAssessmentFlags flags, CFDictionaryRef context)
 977  {
 978  	// update GKE
 979  	installExplicitSet(gkeAuthFile, gkeSigsFile);
 980  
 981  	AuthorityType type = typeFor(context, kAuthorityInvalid);
 982  	CFStringRef edit = CFStringRef(CFDictionaryGetValue(context, kSecAssessmentContextKeyUpdate));
 983  	CFDictionaryRef result;
 984  	if (CFEqual(edit, kSecAssessmentUpdateOperationAdd))
 985  		result = this->add(target, type, flags, context);
 986  	else if (CFEqual(edit, kSecAssessmentUpdateOperationRemove))
 987  		result = this->remove(target, type, flags, context);
 988  	else if (CFEqual(edit, kSecAssessmentUpdateOperationEnable))
 989  		result = this->enable(target, type, flags, context, true);
 990  	else if (CFEqual(edit, kSecAssessmentUpdateOperationDisable))
 991  		result = this->disable(target, type, flags, context, true);
 992  	else if (CFEqual(edit, kSecAssessmentUpdateOperationFind))
 993  		result = this->find(target, type, flags, context);
 994  	else
 995  		MacOSError::throwMe(errSecCSInvalidAttributeValues);
 996  	if (result == NULL)
 997  		result = makeCFDictionary(0);		// success, no details
 998  	return result;
 999  }
1000  
1001  
1002  //
1003  // Construct and prepare an SQL query on the authority table, operating on some set of existing authority records.
1004  // In essence, this appends a suitable WHERE clause to the stanza passed and prepares it on the statement given.
1005  //
1006  void PolicyEngine::selectRules(SQLite::Statement &action, std::string phrase, std::string table,
1007  	CFTypeRef inTarget, AuthorityType type, SecAssessmentFlags flags, CFDictionaryRef context, std::string suffix /* = "" */)
1008  {
1009  	CFDictionary ctx(context, errSecCSInvalidAttributeValues);
1010  	CFCopyRef<CFTypeRef> target = inTarget;
1011  	std::string filter_unsigned;	// ignored; used just to trigger ad-hoc signing
1012  	normalizeTarget(target, type, ctx, &filter_unsigned);
1013  
1014  	string label;
1015  	if (CFStringRef lab = ctx.get<CFStringRef>(kSecAssessmentUpdateKeyLabel))
1016  		label = cfString(CFStringRef(lab));
1017  
1018  	if (!target) {
1019  		if (label.empty()) {
1020  			if (type == kAuthorityInvalid) {
1021  				action.query(phrase + suffix);
1022  			} else {
1023  				action.query(phrase + " WHERE " + table + ".type = :type" + suffix);
1024  				action.bind(":type").integer(type);
1025  			}
1026  		} else {	// have label
1027  			if (type == kAuthorityInvalid) {
1028  				action.query(phrase + " WHERE " + table + ".label = :label" + suffix);
1029  			} else {
1030  				action.query(phrase + " WHERE " + table + ".type = :type AND " + table + ".label = :label" + suffix);
1031  				action.bind(":type").integer(type);
1032  			}
1033  			action.bind(":label") = label;
1034  		}
1035  	} else if (CFGetTypeID(target) == CFNumberGetTypeID()) {
1036  		action.query(phrase + " WHERE " + table + ".id = :id" + suffix);
1037  		action.bind(":id").integer(cfNumber<uint64_t>(target.as<CFNumberRef>()));
1038  	} else if (CFGetTypeID(target) == SecRequirementGetTypeID()) {
1039  		if (type == kAuthorityInvalid)
1040  			type = kAuthorityExecute;
1041  		CFRef<CFStringRef> requirementText;
1042  		MacOSError::check(SecRequirementCopyString(target.as<SecRequirementRef>(), kSecCSDefaultFlags, &requirementText.aref()));
1043  		action.query(phrase + " WHERE " + table + ".type = :type AND " + table + ".requirement = :requirement" + suffix);
1044  		action.bind(":type").integer(type);
1045  		action.bind(":requirement") = requirementText.get();
1046  	} else
1047  		MacOSError::throwMe(errSecCSInvalidObjectRef);
1048  }
1049  
1050  
1051  //
1052  // Execute an atomic change to existing records in the authority table.
1053  //
1054  CFDictionaryRef PolicyEngine::manipulateRules(const std::string &stanza,
1055  	CFTypeRef inTarget, AuthorityType type, SecAssessmentFlags flags, CFDictionaryRef context, bool authorize)
1056  {
1057  	SQLite::Transaction xact(*this, SQLite3::Transaction::deferred, "rule_change");
1058  	SQLite::Statement action(*this);
1059  	if (authorize)
1060  		authorizeUpdate(flags, context);
1061  	selectRules(action, stanza, "authority", inTarget, type, flags, context);
1062  	action.execute();
1063  	unsigned int changes = this->changes();	// latch change count
1064  	// We MUST purge objects with priority <= MAX(priority of any changed rules);
1065  	// but for now we just get lazy and purge them ALL.
1066  	if (changes) {
1067  		this->purgeObjects(1.0E100);
1068  		xact.commit();
1069  		notify_post(kNotifySecAssessmentUpdate);
1070  		return cfmake<CFDictionaryRef>("{%O=%d}", kSecAssessmentUpdateKeyCount, changes);
1071  	}
1072  	// no change; return an error
1073  	MacOSError::throwMe(errSecCSNoMatches);
1074  }
1075  
1076  
1077  //
1078  // Fill in extra information about the originator of cryptographic credentials found - if any
1079  //
1080  void PolicyEngine::setOrigin(CFArrayRef chain, CFMutableDictionaryRef result)
1081  {
1082  	if (chain)
1083  		if (CFArrayGetCount(chain) > 0)
1084  			if (SecCertificateRef leaf = SecCertificateRef(CFArrayGetValueAtIndex(chain, 0)))
1085  				if (CFStringRef summary = SecCertificateCopyLongDescription(NULL, leaf, NULL)) {
1086  					CFDictionarySetValue(result, kSecAssessmentAssessmentOriginator, summary);
1087  					CFRelease(summary);
1088  				}
1089  }
1090  
1091  
1092  //
1093  // Take an assessment outcome and record it in the object cache
1094  //
1095  void PolicyEngine::recordOutcome(SecStaticCodeRef code, bool allow, AuthorityType type, double expires, SQLite::int64 authority)
1096  {
1097  	CFRef<CFDictionaryRef> info;
1098  	MacOSError::check(SecCodeCopySigningInformation(code, kSecCSDefaultFlags, &info.aref()));
1099  	CFDataRef cdHash = CFDataRef(CFDictionaryGetValue(info, kSecCodeInfoUnique));
1100  	assert(cdHash);		// was signed
1101  	CFRef<CFURLRef> path;
1102  	MacOSError::check(SecCodeCopyPath(code, kSecCSDefaultFlags, &path.aref()));
1103  	assert(expires);
1104  	SQLite::Transaction xact(*this, SQLite3::Transaction::deferred, "caching");
1105  	SQLite::Statement insert(*this,
1106  		"INSERT OR REPLACE INTO object (type, allow, hash, expires, path, authority)"
1107  		"	VALUES (:type, :allow, :hash, :expires, :path,"
1108  		"	CASE :authority WHEN 0 THEN (SELECT id FROM authority WHERE label = 'No Matching Rule') ELSE :authority END"
1109  		"	);");
1110  	insert.bind(":type").integer(type);
1111  	insert.bind(":allow").integer(allow);
1112  	insert.bind(":hash") = cdHash;
1113  	insert.bind(":expires") = expires;
1114  	insert.bind(":path") = cfString(path);
1115  	insert.bind(":authority").integer(authority);
1116  	insert.execute();
1117  	xact.commit();
1118  }
1119  
1120  
1121  //
1122  // Record a UI failure record after proper validation of the caller
1123  //
1124  void PolicyEngine::recordFailure(CFDictionaryRef info)
1125  {
1126  	CFRef<CFDataRef> infoData = makeCFData(info);
1127  	UnixPlusPlus::AutoFileDesc fd(lastRejectFile, O_WRONLY | O_CREAT | O_TRUNC);
1128  	fd.write(CFDataGetBytePtr(infoData), CFDataGetLength(infoData));
1129  	notify_post(kNotifySecAssessmentRecordingChange);
1130  }
1131  
1132  
1133  //
1134  // Perform update authorization processing.
1135  // Throws an exception if authorization is denied.
1136  //
1137  static void authorizeUpdate(SecAssessmentFlags flags, CFDictionaryRef context)
1138  {
1139  	AuthorizationRef authorization = NULL;
1140  	
1141  	if (context)
1142  		if (CFTypeRef authkey = CFDictionaryGetValue(context, kSecAssessmentUpdateKeyAuthorization))
1143  			if (CFGetTypeID(authkey) == CFDataGetTypeID()) {
1144  				CFDataRef authdata = CFDataRef(authkey);
1145  				if (CFDataGetLength(authdata) != sizeof(AuthorizationExternalForm))
1146  					MacOSError::throwMe(errSecCSInvalidObjectRef);
1147  				MacOSError::check(AuthorizationCreateFromExternalForm((AuthorizationExternalForm *)CFDataGetBytePtr(authdata), &authorization));
1148  			}
1149  	if (authorization == NULL)
1150  		MacOSError::throwMe(errSecCSDBDenied);
1151  	
1152  	AuthorizationItem right[] = {
1153  		{ "com.apple.security.assessment.update", 0, NULL, 0 }
1154  	};
1155  	AuthorizationRights rights = { sizeof(right) / sizeof(right[0]), right };
1156  	MacOSError::check(AuthorizationCopyRights(authorization, &rights, NULL,
1157  		kAuthorizationFlagExtendRights | kAuthorizationFlagInteractionAllowed, NULL));
1158  	
1159  	MacOSError::check(AuthorizationFree(authorization, kAuthorizationFlagDefaults));
1160  }
1161  
1162  
1163  //
1164  // Perform common argument normalizations for update operations
1165  //
1166  void PolicyEngine::normalizeTarget(CFRef<CFTypeRef> &target, AuthorityType type, CFDictionary &context, std::string *signUnsigned)
1167  {
1168  	// turn CFURLs into (designated) SecRequirements
1169  	if (target && CFGetTypeID(target) == CFURLGetTypeID()) {
1170  		CFRef<SecStaticCodeRef> code;
1171  		CFURLRef path = target.as<CFURLRef>();
1172  		MacOSError::check(SecStaticCodeCreateWithPath(path, kSecCSDefaultFlags, &code.aref()));
1173  		switch (OSStatus rc = SecCodeCopyDesignatedRequirement(code, kSecCSDefaultFlags, (SecRequirementRef *)&target.aref())) {
1174  		case errSecSuccess: {
1175  			// use the *default* DR to avoid unreasonably wide DRs opening up Gatekeeper to attack
1176  			CFRef<CFDictionaryRef> info;
1177  			MacOSError::check(SecCodeCopySigningInformation(code, kSecCSRequirementInformation, &info.aref()));
1178  			target = CFDictionaryGetValue(info, kSecCodeInfoImplicitDesignatedRequirement);
1179  			}
1180  			break;
1181  		case errSecCSUnsigned:
1182  			if (signUnsigned && temporarySigning(code, type, path, kAuthorityFlagWhitelistV2 | kAuthorityFlagWhitelistSHA256)) {	// ad-hoc sign the code temporarily
1183  				MacOSError::check(SecCodeCopyDesignatedRequirement(code, kSecCSDefaultFlags, (SecRequirementRef *)&target.aref()));
1184  				*signUnsigned = createWhitelistScreen(code);
1185  				break;
1186  			}
1187  			MacOSError::check(rc);
1188  		case errSecCSSignatureFailed:
1189  		default:
1190  			MacOSError::check(rc);
1191  		}
1192  		if (context.get(kSecAssessmentUpdateKeyRemarks) == NULL)	{
1193  			// no explicit remarks; add one with the path
1194  			CFRef<CFURLRef> path;
1195  			MacOSError::check(SecCodeCopyPath(code, kSecCSDefaultFlags, &path.aref()));
1196  			CFMutableDictionaryRef dict = makeCFMutableDictionary(context.get());
1197  			CFDictionaryAddValue(dict, kSecAssessmentUpdateKeyRemarks, CFTempString(cfString(path)));
1198  			context.take(dict);
1199  		}
1200  		CFStringRef edit = CFStringRef(context.get(kSecAssessmentContextKeyUpdate));
1201  		if (type == kAuthorityExecute && CFEqual(edit, kSecAssessmentUpdateOperationAdd)) {
1202  			// implicitly whitelist the code
1203  			opaqueWhitelistAdd(code);
1204  		}
1205  	}
1206  }
1207  
1208  } // end namespace CodeSigning
1209  } // end namespace Security