//
// Generate and attach an ad-hoc opaque signature
// Use SHA-1 digests because that's what the whitelist is made with
//
static void attachOpaque(SecStaticCodeRef code, SecAssessmentFeedback feedback)
{
	CFTemp<CFDictionaryRef> rules("{"	// same resource rules as used for collection
		"rules={"
			"'^.*' = #T"
			"'^Info\\.plist$' = {omit=#T,weight=10}"
		"},rules2={"
			"'^(Frameworks|SharedFrameworks|Plugins|Plug-ins|XPCServices|Helpers|MacOS)/' = {nested=#T, weight=0}" 
			"'^.*' = #T"
			"'^Info\\.plist$' = {omit=#T,weight=10}"
			"'^[^/]+$' = {top=#T, weight=0}"
		"}"
	"}");

	CFRef<CFDataRef> signature = CFDataCreateMutable(NULL, 0);
	CFTemp<CFDictionaryRef> arguments("{%O=%O, %O=#N, %O=%d, %O=%O}",
		kSecCodeSignerDetached, signature.get(),
		kSecCodeSignerIdentity, /* kCFNull, */
		kSecCodeSignerDigestAlgorithm, kSecCodeSignatureHashSHA1,
		kSecCodeSignerResourceRules, rules.get());
	CFRef<SecCodeSignerRef> signer;
	SecCSFlags creationFlags = kSecCSSignOpaque | kSecCSSignNoV1 | kSecCSSignBundleRoot;
	SecCSFlags operationFlags = 0;

	if (feedback)
		operationFlags |= kSecCSReportProgress;
	MacOSError::check(SecStaticCodeSetCallback(code, kSecCSDefaultFlags, NULL, ^CFTypeRef(SecStaticCodeRef code, CFStringRef stage, CFDictionaryRef info) {
		if (CFEqual(stage, CFSTR("progress"))) {
			bool proceed = feedback(kSecAssessmentFeedbackProgress, info);
			if (!proceed)
				SecStaticCodeCancelValidation(code, kSecCSDefaultFlags);
		}
		return NULL;
	}));
//
// Executable code.
// Read from disk, evaluate properly, cache as indicated.
//
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);
	
	CFCopyRef<SecStaticCodeRef> code;
	MacOSError::check(SecStaticCodeCreateWithPath(path, kSecCSDefaultFlags, &code.aref()));
	
	SecCSFlags validationFlags = kSecCSEnforceRevocationChecks | kSecCSCheckAllArchitectures;
	if (!(flags & kSecAssessmentFlagAllowWeak))
		validationFlags |= kSecCSStrictValidate;
	adjustValidation(code);
	
	// 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;
	}

	// ad-hoc sign unsigned code
	if (rc == errSecCSUnsigned && handleUnsigned && (!overrideAssessment(flags) || SYSPOLICY_RECORDER_MODE_ENABLED())) {
		if (temporarySigning(code, type, path, 0)) {
			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));
	MacOSError::check(SecStaticCodeSetCallback(code, kSecCSDefaultFlags, NULL, ^CFTypeRef (SecStaticCodeRef item, CFStringRef cfStage, CFDictionaryRef info) {
		string stage = cfString(cfStage);
		if (stage == "prepared") {
			if (!CFEqual(item, code))	// genuine nested (not top) code
				adjustValidation(item);
		} else if (stage == "progress") {
			if (feedback && CFEqual(item, code)) {	// top level progress
				bool proceed = feedback(kSecAssessmentFeedbackProgress, info);
				if (!proceed)
					SecStaticCodeCancelValidation(code, kSecCSDefaultFlags);
			}
		} else if (stage == "validated") {
			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)
		}
		return NULL;
	}));