1/*
2 * Copyright (c) 2011-2012 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 "StaticCode.h"
42
43#include <CoreServices/CoreServicesPriv.h>
44#include "SecCodePriv.h"
45#undef check // Macro! Yech.
46
47extern "C" {
48#include <OpenScriptingUtilPriv.h>
49}
50
51
52namespace Security {
53namespace CodeSigning {
54
55static const double NEGATIVE_HOLD = 60.0/86400;	// 60 seconds to cache negative outcomes
56
57static const char RECORDER_DIR[] = "/tmp/gke-";		// recorder mode destination for detached signatures
58enum {
59	recorder_code_untrusted = 0,		// signed but untrusted
60	recorder_code_adhoc = 1,			// unsigned; signature recorded
61	recorder_code_unable = 2,			// unsigned; unable to record signature
62};
63
64
65static void authorizeUpdate(SecAssessmentFlags flags, CFDictionaryRef context);
66static bool codeInvalidityExceptions(SecStaticCodeRef code, CFMutableDictionaryRef result);
67static CFTypeRef installerPolicy() CF_RETURNS_RETAINED;
68
69
70//
71// Core structure
72//
73PolicyEngine::PolicyEngine()
74	: PolicyDatabase(NULL, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE)
75{
76}
77
78PolicyEngine::~PolicyEngine()
79{ }
80
81
82//
83// Top-level evaluation driver
84//
85void PolicyEngine::evaluate(CFURLRef path, AuthorityType type, SecAssessmentFlags flags, CFDictionaryRef context, CFMutableDictionaryRef result)
86{
87	// update GKE
88	installExplicitSet(gkeAuthFile, gkeSigsFile);
89
90	switch (type) {
91	case kAuthorityExecute:
92		evaluateCode(path, kAuthorityExecute, flags, context, result, true);
93		break;
94	case kAuthorityInstall:
95		evaluateInstall(path, flags, context, result);
96		break;
97	case kAuthorityOpenDoc:
98		evaluateDocOpen(path, flags, context, result);
99		break;
100	default:
101		MacOSError::throwMe(errSecCSInvalidAttributeValues);
102		break;
103	}
104}
105
106
107static std::string createWhitelistScreen(char type, SHA1 &hash)
108{
109	SHA1::Digest digest;
110	hash.finish(digest);
111	char buffer[2*SHA1::digestLength + 2] = { type };
112	for (size_t n = 0; n < SHA1::digestLength; n++)
113		sprintf(buffer + 1 + 2*n, "%02.2x", digest[n]);
114	return buffer;
115}
116
117
118void PolicyEngine::evaluateCodeItem(SecStaticCodeRef code, CFURLRef path, AuthorityType type, SecAssessmentFlags flags, bool nested, CFMutableDictionaryRef result)
119{
120
121	SQLite::Statement query(*this,
122		"SELECT allow, requirement, id, label, expires, flags, disabled, filter_unsigned, remarks FROM scan_authority"
123		" WHERE type = :type"
124		" ORDER BY priority DESC;");
125	query.bind(":type").integer(type);
126
127	SQLite3::int64 latentID = 0;		// first (highest priority) disabled matching ID
128	std::string latentLabel;			// ... and associated label, if any
129
130	while (query.nextRow()) {
131		bool allow = int(query[0]);
132		const char *reqString = query[1];
133		SQLite3::int64 id = query[2];
134		const char *label = query[3];
135		double expires = query[4];
136		sqlite3_int64 ruleFlags = query[5];
137		SQLite3::int64 disabled = query[6];
138//		const char *filter = query[7];
139//		const char *remarks = query[8];
140
141		CFRef<SecRequirementRef> requirement;
142		MacOSError::check(SecRequirementCreateWithString(CFTempString(reqString), kSecCSDefaultFlags, &requirement.aref()));
143		switch (OSStatus rc = SecStaticCodeCheckValidity(code, kSecCSBasicValidateOnly, requirement)) {
144		case errSecSuccess:
145			break;						// rule match; process below
146		case errSecCSReqFailed:
147			continue;					// rule does not apply
148		case errSecCSVetoed:
149			return;						// nested code has failed to pass
150		default:
151			MacOSError::throwMe(rc);	// general error; pass to caller
152		}
153
154		// if this rule is disabled, skip it but record the first matching one for posterity
155		if (disabled && latentID == 0) {
156			latentID = id;
157			latentLabel = label ? label : "";
158			continue;
159		}
160
161		// current rule is first rule (in priority order) that matched. Apply it
162		if (nested)	// success, nothing to record
163			return;
164
165		CFRef<CFDictionaryRef> info;	// as needed
166		if (flags & kSecAssessmentFlagRequestOrigin) {
167			if (!info)
168				MacOSError::check(SecCodeCopySigningInformation(code, kSecCSSigningInformation, &info.aref()));
169			if (CFArrayRef chain = CFArrayRef(CFDictionaryGetValue(info, kSecCodeInfoCertificates)))
170				setOrigin(chain, result);
171		}
172		if (!(ruleFlags & kAuthorityFlagInhibitCache) && !(flags & kSecAssessmentFlagNoCache)) {	// cache inhibit
173			if (!info)
174				MacOSError::check(SecCodeCopySigningInformation(code, kSecCSSigningInformation, &info.aref()));
175			if (SecTrustRef trust = SecTrustRef(CFDictionaryGetValue(info, kSecCodeInfoTrust))) {
176				CFRef<CFDictionaryRef> xinfo;
177				MacOSError::check(SecTrustCopyExtendedResult(trust, &xinfo.aref()));
178				if (CFDateRef limit = CFDateRef(CFDictionaryGetValue(xinfo, kSecTrustExpirationDate))) {
179					this->recordOutcome(code, allow, type, min(expires, dateToJulian(limit)), id);
180				}
181			}
182		}
183		if (allow) {
184			if (SYSPOLICY_ASSESS_OUTCOME_ACCEPT_ENABLED()) {
185				if (!info)
186					MacOSError::check(SecCodeCopySigningInformation(code, kSecCSSigningInformation, &info.aref()));
187				CFDataRef cdhash = CFDataRef(CFDictionaryGetValue(info, kSecCodeInfoUnique));
188				SYSPOLICY_ASSESS_OUTCOME_ACCEPT(cfString(path).c_str(), type, label, cdhash ? CFDataGetBytePtr(cdhash) : NULL);
189			}
190		} else {
191			if (SYSPOLICY_ASSESS_OUTCOME_DENY_ENABLED() || SYSPOLICY_RECORDER_MODE_ENABLED()) {
192				if (!info)
193					MacOSError::check(SecCodeCopySigningInformation(code, kSecCSSigningInformation, &info.aref()));
194				CFDataRef cdhash = CFDataRef(CFDictionaryGetValue(info, kSecCodeInfoUnique));
195				std::string cpath = cfString(path);
196				const void *hashp = cdhash ? CFDataGetBytePtr(cdhash) : NULL;
197				SYSPOLICY_ASSESS_OUTCOME_DENY(cpath.c_str(), type, label, hashp);
198				SYSPOLICY_RECORDER_MODE(cpath.c_str(), type, label, hashp, recorder_code_untrusted);
199			}
200		}
201		cfadd(result, "{%O=%B}", kSecAssessmentAssessmentVerdict, allow);
202		addAuthority(flags, result, label, id);
203		return;
204	}
205
206	// no applicable authority (but signed, perhaps temporarily). Deny by default
207	CFRef<CFDictionaryRef> info;
208	MacOSError::check(SecCodeCopySigningInformation(code, kSecCSSigningInformation, &info.aref()));
209	if (flags & kSecAssessmentFlagRequestOrigin) {
210		if (CFArrayRef chain = CFArrayRef(CFDictionaryGetValue(info, kSecCodeInfoCertificates)))
211			setOrigin(chain, result);
212	}
213	if (SYSPOLICY_ASSESS_OUTCOME_DEFAULT_ENABLED() || SYSPOLICY_RECORDER_MODE_ENABLED()) {
214		CFDataRef cdhash = CFDataRef(CFDictionaryGetValue(info, kSecCodeInfoUnique));
215		const void *hashp = cdhash ? CFDataGetBytePtr(cdhash) : NULL;
216		std::string cpath = cfString(path);
217		SYSPOLICY_ASSESS_OUTCOME_DEFAULT(cpath.c_str(), type, latentLabel.c_str(), hashp);
218		SYSPOLICY_RECORDER_MODE(cpath.c_str(), type, latentLabel.c_str(), hashp, 0);
219	}
220	if (!(flags & kSecAssessmentFlagNoCache))
221		this->recordOutcome(code, false, type, this->julianNow() + NEGATIVE_HOLD, latentID);
222	cfadd(result, "{%O=%B}", kSecAssessmentAssessmentVerdict, false);
223	addAuthority(flags, result, latentLabel.c_str(), latentID);
224}
225
226
227void PolicyEngine::adjustValidation(SecStaticCodeRef code)
228{
229	CFRef<CFDictionaryRef> conditions = mOpaqueWhitelist.validationConditionsFor(code);
230	SecStaticCodeSetValidationConditions(code, conditions);
231}
232
233
234bool PolicyEngine::temporarySigning(SecStaticCodeRef code, AuthorityType type, CFURLRef path, SecAssessmentFlags matchFlags)
235{
236	if (matchFlags == 0) {	// playback; consult authority table for matches
237		DiskRep *rep = SecStaticCode::requiredStatic(code)->diskRep();
238		std::string screen;
239		if (CFRef<CFDataRef> info = rep->component(cdInfoSlot)) {
240			SHA1 hash;
241			hash.update(CFDataGetBytePtr(info), CFDataGetLength(info));
242			screen = createWhitelistScreen('I', hash);
243		} else if (rep->mainExecutableImage()) {
244			screen = "N";
245		} else {
246			SHA1 hash;
247			hashFileData(rep->mainExecutablePath().c_str(), &hash);
248			screen = createWhitelistScreen('M', hash);
249		}
250		SQLite::Statement query(*this,
251			"SELECT flags FROM authority "
252			"WHERE type = :type"
253			" AND NOT flags & :flag"
254			" AND CASE WHEN filter_unsigned IS NULL THEN remarks = :remarks ELSE filter_unsigned = :screen END");
255		query.bind(":type").integer(type);
256		query.bind(":flag").integer(kAuthorityFlagDefault);
257		query.bind(":screen") = screen;
258		query.bind(":remarks") = cfString(path);
259		if (!query.nextRow())	// guaranteed no matching rule
260			return false;
261		matchFlags = SQLite3::int64(query[0]);
262	}
263
264	try {
265		// ad-hoc sign the code and attach the signature
266		CFRef<CFDataRef> signature = CFDataCreateMutable(NULL, 0);
267		CFTemp<CFDictionaryRef> arguments("{%O=%O, %O=#N}", kSecCodeSignerDetached, signature.get(), kSecCodeSignerIdentity);
268		CFRef<SecCodeSignerRef> signer;
269		MacOSError::check(SecCodeSignerCreate(arguments, (matchFlags & kAuthorityFlagWhitelistV2) ? kSecCSSignOpaque : kSecCSSignV1, &signer.aref()));
270		MacOSError::check(SecCodeSignerAddSignature(signer, code, kSecCSDefaultFlags));
271		MacOSError::check(SecCodeSetDetachedSignature(code, signature, kSecCSDefaultFlags));
272
273		SecRequirementRef dr = NULL;
274		SecCodeCopyDesignatedRequirement(code, kSecCSDefaultFlags, &dr);
275		CFStringRef drs = NULL;
276		SecRequirementCopyString(dr, kSecCSDefaultFlags, &drs);
277
278		// if we're in GKE recording mode, save that signature and report its location
279		if (SYSPOLICY_RECORDER_MODE_ENABLED()) {
280			int status = recorder_code_unable;	// ephemeral signature (not recorded)
281			if (geteuid() == 0) {
282				CFRef<CFUUIDRef> uuid = CFUUIDCreate(NULL);
283				std::string sigfile = RECORDER_DIR + cfStringRelease(CFUUIDCreateString(NULL, uuid)) + ".tsig";
284				try {
285					UnixPlusPlus::AutoFileDesc fd(sigfile, O_WRONLY | O_CREAT);
286					fd.write(CFDataGetBytePtr(signature), CFDataGetLength(signature));
287					status = recorder_code_adhoc;	// recorded signature
288					SYSPOLICY_RECORDER_MODE_ADHOC_PATH(cfString(path).c_str(), type, sigfile.c_str());
289				} catch (...) { }
290			}
291
292			// now report the D probe itself
293			CFRef<CFDictionaryRef> info;
294			MacOSError::check(SecCodeCopySigningInformation(code, kSecCSDefaultFlags, &info.aref()));
295			CFDataRef cdhash = CFDataRef(CFDictionaryGetValue(info, kSecCodeInfoUnique));
296			SYSPOLICY_RECORDER_MODE(cfString(path).c_str(), type, "",
297				cdhash ? CFDataGetBytePtr(cdhash) : NULL, status);
298		}
299
300		return true;	// it worked; we're now (well) signed
301	} catch (...) { }
302
303	return false;
304}
305
306
307//
308// Executable code.
309// Read from disk, evaluate properly, cache as indicated.
310//
311void PolicyEngine::evaluateCode(CFURLRef path, AuthorityType type, SecAssessmentFlags flags, CFDictionaryRef context, CFMutableDictionaryRef result, bool handleUnsigned)
312{
313	// not really a Gatekeeper function... but reject all "hard quarantined" files because they were made from sandboxed sources without download privilege
314	FileQuarantine qtn(cfString(path).c_str());
315	if (qtn.flag(QTN_FLAG_HARD))
316		MacOSError::throwMe(errSecCSFileHardQuarantined);
317
318	CFCopyRef<SecStaticCodeRef> code;
319	MacOSError::check(SecStaticCodeCreateWithPath(path, kSecCSDefaultFlags, &code.aref()));
320
321	SecCSFlags validationFlags = kSecCSEnforceRevocationChecks | kSecCSCheckAllArchitectures;
322	if (!(flags & kSecAssessmentFlagAllowWeak))
323		validationFlags |= kSecCSStrictValidate;
324	adjustValidation(code);
325
326	// first, perform a shallow check
327	OSStatus rc = SecStaticCodeCheckValidity(code, validationFlags, NULL);
328
329	if (rc == errSecCSSignatureFailed) {
330		if (!codeInvalidityExceptions(code, result)) {	// invalidly signed, no exceptions -> error
331			if (SYSPOLICY_ASSESS_OUTCOME_BROKEN_ENABLED())
332				SYSPOLICY_ASSESS_OUTCOME_BROKEN(cfString(path).c_str(), type, false);
333			MacOSError::throwMe(rc);
334		}
335		// recognized exception - treat as unsigned
336		if (SYSPOLICY_ASSESS_OUTCOME_BROKEN_ENABLED())
337			SYSPOLICY_ASSESS_OUTCOME_BROKEN(cfString(path).c_str(), type, true);
338		rc = errSecCSUnsigned;
339	}
340
341	if (rc == errSecCSUnsigned && handleUnsigned && (!overrideAssessment(flags) || SYSPOLICY_RECORDER_MODE_ENABLED())) {
342		if (temporarySigning(code, type, path, 0)) {
343			rc = errSecSuccess;		// clear unsigned; we are now well-signed
344			validationFlags |= kSecCSBasicValidateOnly;	// no need to re-validate deep contents
345		}
346	}
347
348	MacOSError::check(SecStaticCodeSetCallback(code, kSecCSDefaultFlags, NULL, ^CFTypeRef (SecStaticCodeRef item, CFStringRef cfStage, CFDictionaryRef info) {
349		string stage = cfString(cfStage);
350		if (stage == "prepared") {
351			if (!CFEqual(item, code))	// genuine nested (not top) code
352				adjustValidation(item);
353		} else if (stage == "validated") {
354			SecStaticCodeSetCallback(item, kSecCSDefaultFlags, NULL, NULL);		// clear callback to avoid unwanted recursion
355			evaluateCodeItem(item, path, type, flags, item != code, result);
356			if (CFTypeRef verdict = CFDictionaryGetValue(result, kSecAssessmentAssessmentVerdict))
357				if (CFEqual(verdict, kCFBooleanFalse))
358					return makeCFNumber(OSStatus(errSecCSVetoed));	// (signal nested-code policy failure, picked up below)
359		}
360		return NULL;
361	}));
362	switch (rc = SecStaticCodeCheckValidity(code, validationFlags | kSecCSCheckNestedCode, NULL)) {
363	case errSecSuccess:		// continue below
364		break;
365	case errSecCSUnsigned:
366		cfadd(result, "{%O=#F}", kSecAssessmentAssessmentVerdict);
367		addAuthority(flags, result, "no usable signature");
368		return;
369	case errSecCSVetoed:		// nested code rejected by rule book; result was filled out there
370		return;
371	case errSecCSWeakResourceRules:
372	case errSecCSResourceNotSupported:
373	case errSecCSAmbiguousBundleFormat:
374	case errSecCSSignatureNotVerifiable:
375	case errSecCSRegularFile:
376	case errSecCSBadMainExecutable:
377	case errSecCSBadFrameworkVersion:
378	case errSecCSUnsealedAppRoot:
379	case errSecCSUnsealedFrameworkRoot:
380	{
381		// consult the whitelist
382		bool allow = false;
383		const char *label;
384		// we've bypassed evaluateCodeItem before we failed validation. Explicitly apply it now
385		SecStaticCodeSetCallback(code, kSecCSDefaultFlags, NULL, NULL);
386		evaluateCodeItem(code, path, type, flags | kSecAssessmentFlagNoCache, false, result);
387		if (CFTypeRef verdict = CFDictionaryGetValue(result, kSecAssessmentAssessmentVerdict)) {
388			// verdict rendered from a nested component - signature not acceptable to Gatekeeper
389			if (CFEqual(verdict, kCFBooleanFalse))	// nested code rejected by rule book; result was filled out there
390				return;
391			if (CFEqual(verdict, kCFBooleanTrue) && !(flags & kSecAssessmentFlagIgnoreWhitelist)) {
392				bool trace = CFDictionaryContainsKey(context, kSecAssessmentContextQuarantineFlags);
393				if (mOpaqueWhitelist.contains(code, rc, trace))
394					allow = true;
395			}
396		}
397		if (allow) {
398			label = "allowed cdhash";
399		} else {
400			CFDictionaryReplaceValue(result, kSecAssessmentAssessmentVerdict, kCFBooleanFalse);
401			label = "obsolete resource envelope";
402		}
403		cfadd(result, "{%O=%d}", kSecAssessmentAssessmentCodeSigningError, rc);
404		addAuthority(flags, result, label, 0, NULL, true);
405		return;
406	}
407	default:
408		MacOSError::throwMe(rc);
409	}
410}
411
412
413//
414// Installer archive.
415// Hybrid policy: If we detect an installer signature, use and validate that.
416// If we don't, check for a code signature instead.
417//
418void PolicyEngine::evaluateInstall(CFURLRef path, SecAssessmentFlags flags, CFDictionaryRef context, CFMutableDictionaryRef result)
419{
420	const AuthorityType type = kAuthorityInstall;
421
422	Xar xar(cfString(path).c_str());
423	if (!xar) {
424		// follow the code signing path
425		evaluateCode(path, type, flags, context, result, true);
426		return;
427	}
428
429	// check for recent explicit approval, using a bookmark's FileResourceIdentifierKey
430	if (CFRef<CFDataRef> bookmark = cfLoadFile(lastApprovedFile)) {
431		Boolean stale;
432		if (CFRef<CFURLRef> url = CFURLCreateByResolvingBookmarkData(NULL, bookmark,
433			kCFBookmarkResolutionWithoutUIMask | kCFBookmarkResolutionWithoutMountingMask, NULL, NULL, &stale, NULL))
434			if (CFRef<CFDataRef> savedIdent = CFDataRef(CFURLCreateResourcePropertyForKeyFromBookmarkData(NULL, kCFURLFileResourceIdentifierKey, bookmark)))
435				if (CFRef<CFDateRef> savedMod = CFDateRef(CFURLCreateResourcePropertyForKeyFromBookmarkData(NULL, kCFURLContentModificationDateKey, bookmark))) {
436					CFRef<CFDataRef> currentIdent;
437					CFRef<CFDateRef> currentMod;
438					if (CFURLCopyResourcePropertyForKey(path, kCFURLFileResourceIdentifierKey, &currentIdent.aref(), NULL))
439						if (CFURLCopyResourcePropertyForKey(path, kCFURLContentModificationDateKey, &currentMod.aref(), NULL))
440							if (CFEqual(savedIdent, currentIdent) && CFEqual(savedMod, currentMod)) {
441								cfadd(result, "{%O=#T}", kSecAssessmentAssessmentVerdict);
442								addAuthority(flags, result, "explicit preference");
443								return;
444							}
445				}
446	}
447
448	SQLite3::int64 latentID = 0;		// first (highest priority) disabled matching ID
449	std::string latentLabel;			// ... and associated label, if any
450	if (!xar.isSigned()) {
451		// unsigned xar
452		if (SYSPOLICY_ASSESS_OUTCOME_UNSIGNED_ENABLED())
453			SYSPOLICY_ASSESS_OUTCOME_UNSIGNED(cfString(path).c_str(), type);
454		cfadd(result, "{%O=#F}", kSecAssessmentAssessmentVerdict);
455		addAuthority(flags, result, "no usable signature");
456		return;
457	}
458	if (CFRef<CFArrayRef> certs = xar.copyCertChain()) {
459		CFRef<CFTypeRef> policy = installerPolicy();
460		CFRef<SecTrustRef> trust;
461		MacOSError::check(SecTrustCreateWithCertificates(certs, policy, &trust.aref()));
462//		MacOSError::check(SecTrustSetAnchorCertificates(trust, cfEmptyArray())); // no anchors
463		MacOSError::check(SecTrustSetOptions(trust, kSecTrustOptionAllowExpired | kSecTrustOptionImplicitAnchors));
464
465		SecTrustResultType trustResult;
466		MacOSError::check(SecTrustEvaluate(trust, &trustResult));
467		CFRef<CFArrayRef> chain;
468		CSSM_TP_APPLE_EVIDENCE_INFO *info;
469		MacOSError::check(SecTrustGetResult(trust, &trustResult, &chain.aref(), &info));
470
471		if (flags & kSecAssessmentFlagRequestOrigin)
472			setOrigin(chain, result);
473
474		switch (trustResult) {
475		case kSecTrustResultProceed:
476		case kSecTrustResultUnspecified:
477			break;
478		default:
479			{
480				OSStatus rc;
481				MacOSError::check(SecTrustGetCssmResultCode(trust, &rc));
482				MacOSError::throwMe(rc);
483			}
484		}
485
486		SQLite::Statement query(*this,
487			"SELECT allow, requirement, id, label, flags, disabled FROM scan_authority"
488			" WHERE type = :type"
489			" ORDER BY priority DESC;");
490		query.bind(":type").integer(type);
491		while (query.nextRow()) {
492			bool allow = int(query[0]);
493			const char *reqString = query[1];
494			SQLite3::int64 id = query[2];
495			const char *label = query[3];
496			//sqlite_uint64 ruleFlags = query[4];
497			SQLite3::int64 disabled = query[5];
498
499			CFRef<SecRequirementRef> requirement;
500			MacOSError::check(SecRequirementCreateWithString(CFTempString(reqString), kSecCSDefaultFlags, &requirement.aref()));
501			switch (OSStatus rc = SecRequirementEvaluate(requirement, chain, NULL, kSecCSDefaultFlags)) {
502			case errSecSuccess: // success
503				break;
504			case errSecCSReqFailed: // requirement missed, but otherwise okay
505				continue;
506			default: // broken in some way; all tests will fail like this so bail out
507				MacOSError::throwMe(rc);
508			}
509			if (disabled) {
510				if (latentID == 0) {
511					latentID = id;
512					if (label)
513						latentLabel = label;
514				}
515				continue;	// the loop
516			}
517
518			if (SYSPOLICY_ASSESS_OUTCOME_ACCEPT_ENABLED() || SYSPOLICY_ASSESS_OUTCOME_DENY_ENABLED()) {
519				if (allow)
520					SYSPOLICY_ASSESS_OUTCOME_ACCEPT(cfString(path).c_str(), type, label, NULL);
521				else
522					SYSPOLICY_ASSESS_OUTCOME_DENY(cfString(path).c_str(), type, label, NULL);
523			}
524
525			// not adding to the object cache - we could, but it's not likely to be worth it
526			cfadd(result, "{%O=%B}", kSecAssessmentAssessmentVerdict, allow);
527			addAuthority(flags, result, label, id);
528			return;
529		}
530	}
531	if (SYSPOLICY_ASSESS_OUTCOME_DEFAULT_ENABLED())
532		SYSPOLICY_ASSESS_OUTCOME_DEFAULT(cfString(path).c_str(), type, latentLabel.c_str(), NULL);
533
534	// no applicable authority. Deny by default
535	cfadd(result, "{%O=#F}", kSecAssessmentAssessmentVerdict);
536	addAuthority(flags, result, latentLabel.c_str(), latentID);
537}
538
539
540//
541// Create a suitable policy array for verification of installer signatures.
542//
543static SecPolicyRef makeCRLPolicy()
544{
545	CFRef<SecPolicyRef> policy;
546	MacOSError::check(SecPolicyCopy(CSSM_CERT_X_509v3, &CSSMOID_APPLE_TP_REVOCATION_CRL, &policy.aref()));
547	CSSM_APPLE_TP_CRL_OPTIONS options;
548	memset(&options, 0, sizeof(options));
549	options.Version = CSSM_APPLE_TP_CRL_OPTS_VERSION;
550	options.CrlFlags = CSSM_TP_ACTION_FETCH_CRL_FROM_NET | CSSM_TP_ACTION_CRL_SUFFICIENT;
551	CSSM_DATA optData = { sizeof(options), (uint8 *)&options };
552	MacOSError::check(SecPolicySetValue(policy, &optData));
553	return policy.yield();
554}
555
556static SecPolicyRef makeOCSPPolicy()
557{
558	CFRef<SecPolicyRef> policy;
559	MacOSError::check(SecPolicyCopy(CSSM_CERT_X_509v3, &CSSMOID_APPLE_TP_REVOCATION_OCSP, &policy.aref()));
560	CSSM_APPLE_TP_OCSP_OPTIONS options;
561	memset(&options, 0, sizeof(options));
562	options.Version = CSSM_APPLE_TP_OCSP_OPTS_VERSION;
563	options.Flags = CSSM_TP_ACTION_OCSP_SUFFICIENT;
564	CSSM_DATA optData = { sizeof(options), (uint8 *)&options };
565	MacOSError::check(SecPolicySetValue(policy, &optData));
566	return policy.yield();
567}
568
569static CFTypeRef installerPolicy()
570{
571	CFRef<SecPolicyRef> base = SecPolicyCreateBasicX509();
572	CFRef<SecPolicyRef> crl = makeCRLPolicy();
573	CFRef<SecPolicyRef> ocsp = makeOCSPPolicy();
574	return makeCFArray(3, base.get(), crl.get(), ocsp.get());
575}
576
577
578//
579// LaunchServices-layer document open.
580// We don't cache those at present. If we ever do, we need to authenticate CoreServicesUIAgent as the source of its risk assessment.
581//
582void PolicyEngine::evaluateDocOpen(CFURLRef path, SecAssessmentFlags flags, CFDictionaryRef context, CFMutableDictionaryRef result)
583{
584	if (context) {
585		if (CFStringRef riskCategory = CFStringRef(CFDictionaryGetValue(context, kLSDownloadRiskCategoryKey))) {
586			FileQuarantine qtn(cfString(path).c_str());
587
588			if (CFEqual(riskCategory, kLSRiskCategorySafe)
589				|| CFEqual(riskCategory, kLSRiskCategoryNeutral)
590				|| CFEqual(riskCategory, kLSRiskCategoryUnknown)
591				|| CFEqual(riskCategory, kLSRiskCategoryMayContainUnsafeExecutable)) {
592				cfadd(result, "{%O=#T}", kSecAssessmentAssessmentVerdict);
593				addAuthority(flags, result, "_XProtect");
594			} else if (qtn.flag(QTN_FLAG_HARD)) {
595				MacOSError::throwMe(errSecCSFileHardQuarantined);
596			} else if (qtn.flag(QTN_FLAG_ASSESSMENT_OK)) {
597				cfadd(result, "{%O=#T}", kSecAssessmentAssessmentVerdict);
598				addAuthority(flags, result, "Prior Assessment");
599			} else if (!overrideAssessment(flags)) {		// no need to do more work if we're off
600				try {
601					evaluateCode(path, kAuthorityExecute, flags, context, result, false);
602				} catch (...) {
603					// some documents can't be code signed, so this may be quite benign
604				}
605			}
606			if (CFDictionaryGetValue(result, kSecAssessmentAssessmentVerdict) == NULL) {	// no code signature to help us out
607			   cfadd(result, "{%O=#F}", kSecAssessmentAssessmentVerdict);
608			   addAuthority(flags, result, "_XProtect");
609			}
610			addToAuthority(result, kLSDownloadRiskCategoryKey, riskCategory);
611			return;
612		}
613	}
614	// insufficient information from LS - deny by default
615	cfadd(result, "{%O=#F}", kSecAssessmentAssessmentVerdict);
616	addAuthority(flags, result, "Insufficient Context");
617}
618
619
620//
621// Result-creation helpers
622//
623void PolicyEngine::addAuthority(SecAssessmentFlags flags, CFMutableDictionaryRef parent, const char *label, SQLite::int64 row, CFTypeRef cacheInfo, bool weak)
624{
625	CFRef<CFMutableDictionaryRef> auth = makeCFMutableDictionary();
626	if (label && label[0])
627		cfadd(auth, "{%O=%s}", kSecAssessmentAssessmentSource, label);
628	if (row)
629		CFDictionaryAddValue(auth, kSecAssessmentAssessmentAuthorityRow, CFTempNumber(row));
630	if (overrideAssessment(flags))
631		CFDictionaryAddValue(auth, kSecAssessmentAssessmentAuthorityOverride, kDisabledOverride);
632	if (cacheInfo)
633		CFDictionaryAddValue(auth, kSecAssessmentAssessmentFromCache, cacheInfo);
634	if (weak) {
635		CFDictionaryAddValue(auth, kSecAssessmentAssessmentWeakSignature, kCFBooleanTrue);
636		CFDictionaryReplaceValue(parent, kSecAssessmentAssessmentAuthority, auth);
637	} else {
638		CFDictionaryAddValue(parent, kSecAssessmentAssessmentAuthority, auth);
639	}
640}
641
642void PolicyEngine::addToAuthority(CFMutableDictionaryRef parent, CFStringRef key, CFTypeRef value)
643{
644	CFMutableDictionaryRef authority = CFMutableDictionaryRef(CFDictionaryGetValue(parent, kSecAssessmentAssessmentAuthority));
645	assert(authority);
646	CFDictionaryAddValue(authority, key, value);
647}
648
649
650//
651// Add a rule to the policy database
652//
653CFDictionaryRef PolicyEngine::add(CFTypeRef inTarget, AuthorityType type, SecAssessmentFlags flags, CFDictionaryRef context)
654{
655	// default type to execution
656	if (type == kAuthorityInvalid)
657		type = kAuthorityExecute;
658
659	authorizeUpdate(flags, context);
660	CFDictionary ctx(context, errSecCSInvalidAttributeValues);
661	CFCopyRef<CFTypeRef> target = inTarget;
662	CFRef<CFDataRef> bookmark = NULL;
663	std::string filter_unsigned;
664
665	switch (type) {
666	case kAuthorityExecute:
667		normalizeTarget(target, type, ctx, &filter_unsigned);
668		// bookmarks are untrusted and just a hint to callers
669		bookmark = ctx.get<CFDataRef>(kSecAssessmentRuleKeyBookmark);
670		break;
671	case kAuthorityInstall:
672		if (inTarget && CFGetTypeID(inTarget) == CFURLGetTypeID()) {
673			// no good way to turn an installer file into a requirement. Pretend to succeeed so caller proceeds
674			CFRef<CFArrayRef> properties = makeCFArray(2, kCFURLFileResourceIdentifierKey, kCFURLContentModificationDateKey);
675			CFRef<CFErrorRef> error;
676			if (CFRef<CFDataRef> bookmark = CFURLCreateBookmarkData(NULL, CFURLRef(inTarget), kCFURLBookmarkCreationMinimalBookmarkMask, properties, NULL, &error.aref())) {
677				UnixPlusPlus::AutoFileDesc fd(lastApprovedFile, O_WRONLY | O_CREAT | O_TRUNC);
678				fd.write(CFDataGetBytePtr(bookmark), CFDataGetLength(bookmark));
679				return NULL;
680			}
681		}
682		break;
683	case kAuthorityOpenDoc:
684		// handle document-open differently: use quarantine flags for whitelisting
685		if (!target || CFGetTypeID(target) != CFURLGetTypeID())	// can only "add" file paths
686			MacOSError::throwMe(errSecCSInvalidObjectRef);
687		try {
688			std::string spath = cfString(target.as<CFURLRef>());
689			FileQuarantine qtn(spath.c_str());
690			qtn.setFlag(QTN_FLAG_ASSESSMENT_OK);
691			qtn.applyTo(spath.c_str());
692		} catch (const CommonError &error) {
693			// could not set quarantine flag - report qualified success
694			return cfmake<CFDictionaryRef>("{%O=%O,'assessment:error'=%d}",
695				kSecAssessmentAssessmentAuthorityOverride, CFSTR("error setting quarantine"), error.osStatus());
696		} catch (...) {
697			return cfmake<CFDictionaryRef>("{%O=%O}", kSecAssessmentAssessmentAuthorityOverride, CFSTR("unable to set quarantine"));
698		}
699		return NULL;
700	}
701
702	// if we now have anything else, we're busted
703	if (!target || CFGetTypeID(target) != SecRequirementGetTypeID())
704		MacOSError::throwMe(errSecCSInvalidObjectRef);
705
706	double priority = 0;
707	string label;
708	bool allow = true;
709	double expires = never;
710	string remarks;
711	SQLite::uint64 dbFlags = kAuthorityFlagWhitelistV2;
712
713	if (CFNumberRef pri = ctx.get<CFNumberRef>(kSecAssessmentUpdateKeyPriority))
714		CFNumberGetValue(pri, kCFNumberDoubleType, &priority);
715	if (CFStringRef lab = ctx.get<CFStringRef>(kSecAssessmentUpdateKeyLabel))
716		label = cfString(lab);
717	if (CFDateRef time = ctx.get<CFDateRef>(kSecAssessmentUpdateKeyExpires))
718		// we're using Julian dates here; convert from CFDate
719		expires = dateToJulian(time);
720	if (CFBooleanRef allowing = ctx.get<CFBooleanRef>(kSecAssessmentUpdateKeyAllow))
721		allow = allowing == kCFBooleanTrue;
722	if (CFStringRef rem = ctx.get<CFStringRef>(kSecAssessmentUpdateKeyRemarks))
723		remarks = cfString(rem);
724
725	CFRef<CFStringRef> requirementText;
726	MacOSError::check(SecRequirementCopyString(target.as<SecRequirementRef>(), kSecCSDefaultFlags, &requirementText.aref()));
727	SQLite::Transaction xact(*this, SQLite3::Transaction::deferred, "add_rule");
728	SQLite::Statement insert(*this,
729		"INSERT INTO authority (type, allow, requirement, priority, label, expires, filter_unsigned, remarks, flags)"
730		"	VALUES (:type, :allow, :requirement, :priority, :label, :expires, :filter_unsigned, :remarks, :flags);");
731	insert.bind(":type").integer(type);
732	insert.bind(":allow").integer(allow);
733	insert.bind(":requirement") = requirementText.get();
734	insert.bind(":priority") = priority;
735	if (!label.empty())
736		insert.bind(":label") = label;
737	insert.bind(":expires") = expires;
738	insert.bind(":filter_unsigned") = filter_unsigned.empty() ? NULL : filter_unsigned.c_str();
739	if (!remarks.empty())
740		insert.bind(":remarks") = remarks;
741	insert.bind(":flags").integer(dbFlags);
742	insert.execute();
743	SQLite::int64 newRow = this->lastInsert();
744	if (bookmark) {
745		SQLite::Statement bi(*this, "INSERT INTO bookmarkhints (bookmark, authority) VALUES (:bookmark, :authority)");
746		bi.bind(":bookmark") = CFDataRef(bookmark);
747		bi.bind(":authority").integer(newRow);
748		bi.execute();
749	}
750	this->purgeObjects(priority);
751	xact.commit();
752	notify_post(kNotifySecAssessmentUpdate);
753	return cfmake<CFDictionaryRef>("{%O=%d}", kSecAssessmentUpdateKeyRow, newRow);
754}
755
756
757CFDictionaryRef PolicyEngine::remove(CFTypeRef target, AuthorityType type, SecAssessmentFlags flags, CFDictionaryRef context)
758{
759	if (type == kAuthorityOpenDoc) {
760		// handle document-open differently: use quarantine flags for whitelisting
761		authorizeUpdate(flags, context);
762		if (!target || CFGetTypeID(target) != CFURLGetTypeID())
763			MacOSError::throwMe(errSecCSInvalidObjectRef);
764		std::string spath = cfString(CFURLRef(target)).c_str();
765		FileQuarantine qtn(spath.c_str());
766		qtn.clearFlag(QTN_FLAG_ASSESSMENT_OK);
767		qtn.applyTo(spath.c_str());
768		return NULL;
769	}
770	return manipulateRules("DELETE FROM authority", target, type, flags, context);
771}
772
773CFDictionaryRef PolicyEngine::enable(CFTypeRef target, AuthorityType type, SecAssessmentFlags flags, CFDictionaryRef context)
774{
775	return manipulateRules("UPDATE authority SET disabled = 0", target, type, flags, context);
776}
777
778CFDictionaryRef PolicyEngine::disable(CFTypeRef target, AuthorityType type, SecAssessmentFlags flags, CFDictionaryRef context)
779{
780	return manipulateRules("UPDATE authority SET disabled = 1", target, type, flags, context);
781}
782
783CFDictionaryRef PolicyEngine::find(CFTypeRef target, AuthorityType type, SecAssessmentFlags flags, CFDictionaryRef context)
784{
785	SQLite::Statement query(*this);
786	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",
787		"scan_authority", target, type, flags, context,
788		" ORDER BY priority DESC");
789	CFRef<CFMutableArrayRef> found = makeCFMutableArray(0);
790	while (query.nextRow()) {
791		SQLite::int64 id = query[0];
792		int type = int(query[1]);
793		const char *requirement = query[2];
794		int allow = int(query[3]);
795		const char *label = query[4];
796		double priority = query[5];
797		const char *remarks = query[6];
798		double expires = query[7];
799		int disabled = int(query[8]);
800		CFRef<CFDataRef> bookmark = query[9].data();
801		CFRef<CFMutableDictionaryRef> rule = makeCFMutableDictionary(5,
802			kSecAssessmentRuleKeyID, CFTempNumber(id).get(),
803			kSecAssessmentRuleKeyType, CFRef<CFStringRef>(typeNameFor(type)).get(),
804			kSecAssessmentRuleKeyRequirement, CFTempString(requirement).get(),
805			kSecAssessmentRuleKeyAllow, allow ? kCFBooleanTrue : kCFBooleanFalse,
806			kSecAssessmentRuleKeyPriority, CFTempNumber(priority).get()
807			);
808		if (label)
809			CFDictionaryAddValue(rule, kSecAssessmentRuleKeyLabel, CFTempString(label));
810		if (remarks)
811			CFDictionaryAddValue(rule, kSecAssessmentRuleKeyRemarks, CFTempString(remarks));
812		if (expires != never)
813			CFDictionaryAddValue(rule, kSecAssessmentRuleKeyExpires, CFRef<CFDateRef>(julianToDate(expires)));
814		if (disabled)
815			CFDictionaryAddValue(rule, kSecAssessmentRuleKeyDisabled, CFTempNumber(disabled));
816		if (bookmark)
817			CFDictionaryAddValue(rule, kSecAssessmentRuleKeyBookmark, bookmark);
818		CFArrayAppendValue(found, rule);
819	}
820	if (CFArrayGetCount(found) == 0)
821		MacOSError::throwMe(errSecCSNoMatches);
822	return cfmake<CFDictionaryRef>("{%O=%O}", kSecAssessmentUpdateKeyFound, found.get());
823}
824
825
826CFDictionaryRef PolicyEngine::update(CFTypeRef target, SecAssessmentFlags flags, CFDictionaryRef context)
827{
828	// update GKE
829	installExplicitSet(gkeAuthFile, gkeSigsFile);
830
831	AuthorityType type = typeFor(context, kAuthorityInvalid);
832	CFStringRef edit = CFStringRef(CFDictionaryGetValue(context, kSecAssessmentContextKeyUpdate));
833	CFDictionaryRef result;
834	if (CFEqual(edit, kSecAssessmentUpdateOperationAdd))
835		result = this->add(target, type, flags, context);
836	else if (CFEqual(edit, kSecAssessmentUpdateOperationRemove))
837		result = this->remove(target, type, flags, context);
838	else if (CFEqual(edit, kSecAssessmentUpdateOperationEnable))
839		result = this->enable(target, type, flags, context);
840	else if (CFEqual(edit, kSecAssessmentUpdateOperationDisable))
841		result = this->disable(target, type, flags, context);
842	else if (CFEqual(edit, kSecAssessmentUpdateOperationFind))
843		result = this->find(target, type, flags, context);
844	else
845		MacOSError::throwMe(errSecCSInvalidAttributeValues);
846	if (result == NULL)
847		result = makeCFDictionary(0);		// success, no details
848	return result;
849}
850
851
852//
853// Construct and prepare an SQL query on the authority table, operating on some set of existing authority records.
854// In essence, this appends a suitable WHERE clause to the stanza passed and prepares it on the statement given.
855//
856void PolicyEngine::selectRules(SQLite::Statement &action, std::string phrase, std::string table,
857	CFTypeRef inTarget, AuthorityType type, SecAssessmentFlags flags, CFDictionaryRef context, std::string suffix /* = "" */)
858{
859	CFDictionary ctx(context, errSecCSInvalidAttributeValues);
860	CFCopyRef<CFTypeRef> target = inTarget;
861	std::string filter_unsigned;	// ignored; used just to trigger ad-hoc signing
862	normalizeTarget(target, type, ctx, &filter_unsigned);
863
864	string label;
865	if (CFStringRef lab = ctx.get<CFStringRef>(kSecAssessmentUpdateKeyLabel))
866		label = cfString(CFStringRef(lab));
867
868	if (!target) {
869		if (label.empty()) {
870			if (type == kAuthorityInvalid) {
871				action.query(phrase + suffix);
872			} else {
873				action.query(phrase + " WHERE " + table + ".type = :type" + suffix);
874				action.bind(":type").integer(type);
875			}
876		} else {	// have label
877			if (type == kAuthorityInvalid) {
878				action.query(phrase + " WHERE " + table + ".label = :label" + suffix);
879			} else {
880				action.query(phrase + " WHERE " + table + ".type = :type AND " + table + ".label = :label" + suffix);
881				action.bind(":type").integer(type);
882			}
883			action.bind(":label") = label;
884		}
885	} else if (CFGetTypeID(target) == CFNumberGetTypeID()) {
886		action.query(phrase + " WHERE " + table + ".id = :id" + suffix);
887		action.bind(":id").integer(cfNumber<uint64_t>(target.as<CFNumberRef>()));
888	} else if (CFGetTypeID(target) == SecRequirementGetTypeID()) {
889		if (type == kAuthorityInvalid)
890			type = kAuthorityExecute;
891		CFRef<CFStringRef> requirementText;
892		MacOSError::check(SecRequirementCopyString(target.as<SecRequirementRef>(), kSecCSDefaultFlags, &requirementText.aref()));
893		action.query(phrase + " WHERE " + table + ".type = :type AND " + table + ".requirement = :requirement" + suffix);
894		action.bind(":type").integer(type);
895		action.bind(":requirement") = requirementText.get();
896	} else
897		MacOSError::throwMe(errSecCSInvalidObjectRef);
898}
899
900
901//
902// Execute an atomic change to existing records in the authority table.
903//
904CFDictionaryRef PolicyEngine::manipulateRules(const std::string &stanza,
905	CFTypeRef inTarget, AuthorityType type, SecAssessmentFlags flags, CFDictionaryRef context)
906{
907	SQLite::Transaction xact(*this, SQLite3::Transaction::deferred, "rule_change");
908	SQLite::Statement action(*this);
909	authorizeUpdate(flags, context);
910	selectRules(action, stanza, "authority", inTarget, type, flags, context);
911	action.execute();
912	unsigned int changes = this->changes();	// latch change count
913	// We MUST purge objects with priority <= MAX(priority of any changed rules);
914	// but for now we just get lazy and purge them ALL.
915	if (changes) {
916		this->purgeObjects(1.0E100);
917		xact.commit();
918		notify_post(kNotifySecAssessmentUpdate);
919		return cfmake<CFDictionaryRef>("{%O=%d}", kSecAssessmentUpdateKeyCount, changes);
920	}
921	// no change; return an error
922	MacOSError::throwMe(errSecCSNoMatches);
923}
924
925
926//
927// Fill in extra information about the originator of cryptographic credentials found - if any
928//
929void PolicyEngine::setOrigin(CFArrayRef chain, CFMutableDictionaryRef result)
930{
931	if (chain)
932		if (CFArrayGetCount(chain) > 0)
933			if (SecCertificateRef leaf = SecCertificateRef(CFArrayGetValueAtIndex(chain, 0)))
934				if (CFStringRef summary = SecCertificateCopyLongDescription(NULL, leaf, NULL)) {
935					CFDictionarySetValue(result, kSecAssessmentAssessmentOriginator, summary);
936					CFRelease(summary);
937				}
938}
939
940
941//
942// Take an assessment outcome and record it in the object cache
943//
944void PolicyEngine::recordOutcome(SecStaticCodeRef code, bool allow, AuthorityType type, double expires, SQLite::int64 authority)
945{
946	CFRef<CFDictionaryRef> info;
947	MacOSError::check(SecCodeCopySigningInformation(code, kSecCSDefaultFlags, &info.aref()));
948	CFDataRef cdHash = CFDataRef(CFDictionaryGetValue(info, kSecCodeInfoUnique));
949	assert(cdHash);		// was signed
950	CFRef<CFURLRef> path;
951	MacOSError::check(SecCodeCopyPath(code, kSecCSDefaultFlags, &path.aref()));
952	assert(expires);
953	SQLite::Transaction xact(*this, SQLite3::Transaction::deferred, "caching");
954	SQLite::Statement insert(*this,
955		"INSERT OR REPLACE INTO object (type, allow, hash, expires, path, authority)"
956		"	VALUES (:type, :allow, :hash, :expires, :path,"
957		"	CASE :authority WHEN 0 THEN (SELECT id FROM authority WHERE label = 'No Matching Rule') ELSE :authority END"
958		"	);");
959	insert.bind(":type").integer(type);
960	insert.bind(":allow").integer(allow);
961	insert.bind(":hash") = cdHash;
962	insert.bind(":expires") = expires;
963	insert.bind(":path") = cfString(path);
964	insert.bind(":authority").integer(authority);
965	insert.execute();
966	xact.commit();
967}
968
969
970//
971// Record a UI failure record after proper validation of the caller
972//
973void PolicyEngine::recordFailure(CFDictionaryRef info)
974{
975	CFRef<CFDataRef> infoData = makeCFData(info);
976	UnixPlusPlus::AutoFileDesc fd(lastRejectFile, O_WRONLY | O_CREAT | O_TRUNC);
977	fd.write(CFDataGetBytePtr(infoData), CFDataGetLength(infoData));
978	notify_post(kNotifySecAssessmentRecordingChange);
979}
980
981
982//
983// Perform update authorization processing.
984// Throws an exception if authorization is denied.
985//
986static void authorizeUpdate(SecAssessmentFlags flags, CFDictionaryRef context)
987{
988	AuthorizationRef authorization = NULL;
989
990	if (context)
991		if (CFTypeRef authkey = CFDictionaryGetValue(context, kSecAssessmentUpdateKeyAuthorization))
992			if (CFGetTypeID(authkey) == CFDataGetTypeID()) {
993				CFDataRef authdata = CFDataRef(authkey);
994				MacOSError::check(AuthorizationCreateFromExternalForm((AuthorizationExternalForm *)CFDataGetBytePtr(authdata), &authorization));
995			}
996	if (authorization == NULL)
997		MacOSError::check(AuthorizationCreate(NULL, NULL, kAuthorizationFlagDefaults, &authorization));
998
999	AuthorizationItem right[] = {
1000		{ "com.apple.security.assessment.update", 0, NULL, 0 }
1001	};
1002	AuthorizationRights rights = { sizeof(right) / sizeof(right[0]), right };
1003	MacOSError::check(AuthorizationCopyRights(authorization, &rights, NULL,
1004		kAuthorizationFlagExtendRights | kAuthorizationFlagInteractionAllowed, NULL));
1005
1006	MacOSError::check(AuthorizationFree(authorization, kAuthorizationFlagDefaults));
1007}
1008
1009
1010//
1011// Perform common argument normalizations for update operations
1012//
1013void PolicyEngine::normalizeTarget(CFRef<CFTypeRef> &target, AuthorityType type, CFDictionary &context, std::string *signUnsigned)
1014{
1015	// turn CFURLs into (designated) SecRequirements
1016	if (target && CFGetTypeID(target) == CFURLGetTypeID()) {
1017		CFRef<SecStaticCodeRef> code;
1018		CFURLRef path = target.as<CFURLRef>();
1019		MacOSError::check(SecStaticCodeCreateWithPath(path, kSecCSDefaultFlags, &code.aref()));
1020		switch (OSStatus rc = SecCodeCopyDesignatedRequirement(code, kSecCSDefaultFlags, (SecRequirementRef *)&target.aref())) {
1021		case errSecSuccess: {
1022			// use the *default* DR to avoid unreasonably wide DRs opening up Gatekeeper to attack
1023			CFRef<CFDictionaryRef> info;
1024			MacOSError::check(SecCodeCopySigningInformation(code, kSecCSRequirementInformation, &info.aref()));
1025			target = CFDictionaryGetValue(info, kSecCodeInfoImplicitDesignatedRequirement);
1026			}
1027			break;
1028		case errSecCSUnsigned:
1029			if (signUnsigned && temporarySigning(code, type, path, kAuthorityFlagWhitelistV2)) {	// ad-hoc signed the code temporarily
1030				MacOSError::check(SecCodeCopyDesignatedRequirement(code, kSecCSDefaultFlags, (SecRequirementRef *)&target.aref()));
1031				CFRef<CFDictionaryRef> info;
1032				MacOSError::check(SecCodeCopySigningInformation(code, kSecCSInternalInformation, &info.aref()));
1033				if (CFDataRef cdData = CFDataRef(CFDictionaryGetValue(info, kSecCodeInfoCodeDirectory)))
1034					*signUnsigned = ((const CodeDirectory *)CFDataGetBytePtr(cdData))->screeningCode();
1035				break;
1036			}
1037			MacOSError::check(rc);
1038		case errSecCSSignatureFailed:
1039			// recover certain cases of broken signatures (well, try)
1040			if (codeInvalidityExceptions(code, NULL)) {
1041				// Ad-hoc sign the code in place (requiring a writable subject). This requires root privileges.
1042				CFRef<SecCodeSignerRef> signer;
1043				CFTemp<CFDictionaryRef> arguments("{%O=#N}", kSecCodeSignerIdentity);
1044				MacOSError::check(SecCodeSignerCreate(arguments, kSecCSSignOpaque, &signer.aref()));
1045				MacOSError::check(SecCodeSignerAddSignature(signer, code, kSecCSDefaultFlags));
1046				MacOSError::check(SecCodeCopyDesignatedRequirement(code, kSecCSDefaultFlags, (SecRequirementRef *)&target.aref()));
1047				break;
1048			}
1049			MacOSError::check(rc);
1050		default:
1051			MacOSError::check(rc);
1052		}
1053		if (context.get(kSecAssessmentUpdateKeyRemarks) == NULL)	{
1054			// no explicit remarks; add one with the path
1055			CFRef<CFURLRef> path;
1056			MacOSError::check(SecCodeCopyPath(code, kSecCSDefaultFlags, &path.aref()));
1057			CFMutableDictionaryRef dict = makeCFMutableDictionary(context.get());
1058			CFDictionaryAddValue(dict, kSecAssessmentUpdateKeyRemarks, CFTempString(cfString(path)));
1059			context.take(dict);
1060		}
1061		CFStringRef edit = CFStringRef(context.get(kSecAssessmentContextKeyUpdate));
1062		if (type == kAuthorityExecute && CFEqual(edit, kSecAssessmentUpdateOperationAdd)) {
1063			// implicitly whitelist the code
1064			mOpaqueWhitelist.add(code);
1065		}
1066	}
1067}
1068
1069
1070//
1071// Process special overrides for invalidly signed code.
1072// This is the (hopefully minimal) concessions we make to keep hurting our customers
1073// for our own prior mistakes...
1074//
1075static bool codeInvalidityExceptions(SecStaticCodeRef code, CFMutableDictionaryRef result)
1076{
1077	if (OSAIsRecognizedExecutableURL) {
1078		CFRef<CFDictionaryRef> info;
1079		MacOSError::check(SecCodeCopySigningInformation(code, kSecCSDefaultFlags, &info.aref()));
1080		if (CFURLRef executable = CFURLRef(CFDictionaryGetValue(info, kSecCodeInfoMainExecutable))) {
1081			SInt32 error;
1082			if (OSAIsRecognizedExecutableURL(executable, &error)) {
1083				if (result)
1084					CFDictionaryAddValue(result,
1085						kSecAssessmentAssessmentAuthorityOverride, CFSTR("ignoring known invalid applet signature"));
1086				return true;
1087			}
1088		}
1089	}
1090	return false;
1091}
1092
1093
1094} // end namespace CodeSigning
1095} // end namespace Security
1096