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