1/*
2 * Copyright (c) 2011 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 "cs.h"
24#include "SecAssessment.h"
25#include "policydb.h"
26#include "policyengine.h"
27#include "xpcengine.h"
28#include "csutilities.h"
29#include <CoreFoundation/CFRuntime.h>
30#include <security_utilities/globalizer.h>
31#include <security_utilities/unix++.h>
32#include <security_utilities/cfmunge.h>
33#include <notify.h>
34#include <esp.h>
35
36using namespace CodeSigning;
37
38
39static void esp_do_check(const char *op, CFDictionaryRef dict)
40{
41	OSStatus result = __esp_check_ns(op, (void *)(CFDictionaryRef)dict);
42	if (result != noErr)
43		MacOSError::throwMe(result);
44}
45
46//
47// CF Objects
48//
49struct _SecAssessment : private CFRuntimeBase {
50public:
51	_SecAssessment(CFURLRef p, AuthorityType typ, CFDictionaryRef c, CFDictionaryRef r) : path(p), context(c), type(typ), result(r) { }
52
53	CFCopyRef<CFURLRef> path;
54	CFCopyRef<CFDictionaryRef> context;
55	AuthorityType type;
56	CFRef<CFDictionaryRef> result;
57
58public:
59	static _SecAssessment &ref(SecAssessmentRef r)
60		{ return *(_SecAssessment *)r; }
61
62	// CF Boiler-plate
63	void *operator new (size_t size)
64	{
65		return (void *)_CFRuntimeCreateInstance(NULL, SecAssessmentGetTypeID(),
66			sizeof(_SecAssessment) - sizeof(CFRuntimeBase), NULL);
67	}
68
69	static void finalize(CFTypeRef obj)
70	{ ((_SecAssessment *)obj)->~_SecAssessment(); }
71};
72
73typedef _SecAssessment SecAssessment;
74
75
76static const CFRuntimeClass assessmentClass = {
77	0,								// version
78	"SecAssessment",				// name
79	NULL,							// init
80	NULL,							// copy
81	SecAssessment::finalize,		// finalize
82	NULL,							// equal
83	NULL,							// hash
84	NULL,							// formatting
85	NULL							// debug string
86};
87
88
89static dispatch_once_t assessmentOnce;
90CFTypeID assessmentType = _kCFRuntimeNotATypeID;
91
92CFTypeID SecAssessmentGetTypeID()
93{
94	dispatch_once(&assessmentOnce, ^void() {
95		if ((assessmentType = _CFRuntimeRegisterClass(&assessmentClass)) == _kCFRuntimeNotATypeID)
96			abort();
97	});
98	return assessmentType;
99}
100
101
102//
103// Common dictionary constants
104//
105CFStringRef kSecAssessmentContextKeyOperation = CFSTR("operation");
106CFStringRef kSecAssessmentOperationTypeExecute = CFSTR("operation:execute");
107CFStringRef kSecAssessmentOperationTypeInstall = CFSTR("operation:install");
108CFStringRef kSecAssessmentOperationTypeOpenDocument = CFSTR("operation:lsopen");
109
110CFStringRef kSecAssessmentContextQuarantineFlags = CFSTR("context:qtnflags");
111
112
113//
114// Read-only in-process access to the policy database
115//
116class ReadPolicy : public PolicyDatabase {
117public:
118	ReadPolicy() : PolicyDatabase(defaultDatabase) { }
119};
120ModuleNexus<ReadPolicy> gDatabase;
121
122
123//
124// An on-demand instance of the policy engine
125//
126ModuleNexus<PolicyEngine> gEngine;
127
128
129//
130// Policy evaluation ("assessment") operations
131//
132CFStringRef kSecAssessmentAssessmentVerdict = CFSTR("assessment:verdict");
133CFStringRef kSecAssessmentAssessmentOriginator = CFSTR("assessment:originator");
134CFStringRef kSecAssessmentAssessmentAuthority = CFSTR("assessment:authority");
135CFStringRef kSecAssessmentAssessmentSource = CFSTR("assessment:authority:source");
136CFStringRef kSecAssessmentAssessmentAuthorityRow = CFSTR("assessment:authority:row");
137CFStringRef kSecAssessmentAssessmentAuthorityOverride = CFSTR("assessment:authority:override");
138CFStringRef kSecAssessmentAssessmentAuthorityOriginalVerdict = CFSTR("assessment:authority:verdict");
139CFStringRef kSecAssessmentAssessmentFromCache = CFSTR("assessment:authority:cached");
140CFStringRef kSecAssessmentAssessmentWeakSignature = CFSTR("assessment:authority:weak");
141CFStringRef kSecAssessmentAssessmentCodeSigningError = CFSTR("assessment:cserror");
142
143CFStringRef kDisabledOverride = CFSTR("security disabled");
144
145SecAssessmentRef SecAssessmentCreate(CFURLRef path,
146	SecAssessmentFlags flags,
147	CFDictionaryRef context,
148	CFErrorRef *errors)
149{
150	BEGIN_CSAPI
151
152	if (flags & kSecAssessmentFlagAsynchronous)
153		MacOSError::throwMe(errSecCSUnimplemented);
154
155	AuthorityType type = typeFor(context, kAuthorityExecute);
156	CFRef<CFMutableDictionaryRef> result = makeCFMutableDictionary();
157
158	SYSPOLICY_ASSESS_API(cfString(path).c_str(), int(type), flags);
159
160	try {
161		if (__esp_enabled() && (flags & kSecAssessmentFlagDirect)) {
162			CFTemp<CFDictionaryRef> dict("{path=%O, flags=%d, context=%O, override=%d}", path, flags, context, overrideAssessment());
163			esp_do_check("cs-assessment-evaluate", dict);
164		}
165
166		// check the object cache first unless caller denied that or we need extended processing
167		if (!(flags & (kSecAssessmentFlagRequestOrigin | kSecAssessmentFlagIgnoreCache))) {
168			if (gDatabase().checkCache(path, type, flags, result))
169				return new SecAssessment(path, type, context, result.yield());
170		}
171
172		if (flags & kSecAssessmentFlagDirect) {
173			// ask the engine right here to do its thing
174			SYSPOLICY_ASSESS_LOCAL();
175			gEngine().evaluate(path, type, flags, context, result);
176		} else {
177			// relay the question to our daemon for consideration
178			SYSPOLICY_ASSESS_REMOTE();
179			xpcEngineAssess(path, flags, context, result);
180		}
181	} catch (CommonError &error) {
182		switch (error.osStatus()) {
183		case CSSMERR_TP_CERT_REVOKED:
184			throw;
185		default:
186			if (!overrideAssessment(flags))
187				throw;		// let it go as an error
188			break;
189		}
190		// record the error we would have returned
191		cfadd(result, "{%O=#F,'assessment:error'=%d}}", kSecAssessmentAssessmentVerdict, error.osStatus());
192	} catch (...) {
193		// catch stray errors not conforming to the CommonError scheme
194		if (!overrideAssessment(flags))
195			throw;		// let it go as an error
196		cfadd(result, "{%O=#F}", kSecAssessmentAssessmentVerdict);
197	}
198
199	if (__esp_enabled() && (flags & kSecAssessmentFlagDirect)) {
200		CFTemp<CFDictionaryRef> dict("{path=%O, flags=%d, context=%O, override=%d, result=%O}", path, flags, context, overrideAssessment(), (CFDictionaryRef)result);
201		__esp_notify_ns("cs-assessment-evaluate", (void *)(CFDictionaryRef)dict);
202	}
203
204	return new SecAssessment(path, type, context, result.yield());
205
206	END_CSAPI_ERRORS1(NULL)
207}
208
209
210static void traceResult(CFURLRef target, MessageTrace &trace, std::string &sanitized)
211{
212	static const char *interestingBundles[] = {
213		"UNBUNDLED",
214		"com.apple.",
215		"com.install4j.",
216		"com.MindVision.",
217		"com.yourcompany.",
218
219		"com.adobe.flashplayer.installmanager",
220		"com.adobe.Installers.Setup",
221		"com.adobe.PDApp.setup",
222		"com.bittorrent.uTorrent",
223		"com.divx.divx6formacinstaller",
224		"com.getdropbox.dropbox",
225		"com.google.Chrome",
226		"com.Google.GoogleEarthPlugin.plugin",
227		"com.Google.GoogleEarthPlus",
228		"com.hp.Installer",
229		"com.macpaw.CleanMyMac",
230		"com.microsoft.SilverlightInstaller",
231		"com.paragon-software.filesystems.NTFS.pkg",
232		"com.RealNetworks.RealPlayer",
233		"com.skype.skype",
234		"it.alfanet.squared5.MPEGStreamclip",
235		"org.mozilla.firefox",
236		"org.videolan.vlc",
237
238		NULL	// sentinel
239	};
240
241	string identifier = "UNBUNDLED";
242	string version = "UNKNOWN";
243	if (CFRef<CFBundleRef> bundle = CFBundleCreate(NULL, target)) {
244		if (CFStringRef ident = CFBundleGetIdentifier(bundle))
245			identifier = cfString(ident);
246		if (CFStringRef vers = CFStringRef(CFBundleGetValueForInfoDictionaryKey(bundle, CFSTR("CFBundleShortVersionString"))))
247			version = cfString(vers);
248	}
249
250	CFRef<CFURLRef> url = CFURLCopyAbsoluteURL(target);
251	sanitized = cfString(url);
252	string::size_type rslash = sanitized.rfind('/');
253	if (rslash != string::npos)
254		sanitized = sanitized.substr(rslash+1);
255	bool keepFilename = false;
256	for (const char **pfx = interestingBundles; *pfx; pfx++) {
257		size_t pfxlen = strlen(*pfx);
258		if (identifier.compare(0, pfxlen, *pfx, pfxlen) == 0)
259			if (pfxlen == identifier.size() || (*pfx)[pfxlen-1] == '.') {
260				keepFilename = true;
261				break;
262			}
263	}
264	if (!keepFilename) {
265		string::size_type dot = sanitized.rfind('.');
266		if (dot != string::npos)
267			sanitized = sanitized.substr(dot);
268		else
269			sanitized = "(none)";
270	}
271
272	trace.add("signature2", "bundle:%s", identifier.c_str());
273	trace.add("signature3", "%s", sanitized.c_str());
274	trace.add("signature5", "%s", version.c_str());
275}
276
277static void traceAssessment(SecAssessment &assessment, AuthorityType type, CFDictionaryRef result)
278{
279	if (CFDictionaryGetValue(result, CFSTR("assessment:remote")))
280		return;		// just traced in syspolicyd
281
282	string authority = "UNSPECIFIED";
283	bool overridden = false;
284	bool old_overridden = false;
285	if (CFDictionaryRef authdict = CFDictionaryRef(CFDictionaryGetValue(result, kSecAssessmentAssessmentAuthority))) {
286		if (CFStringRef auth = CFStringRef(CFDictionaryGetValue(authdict, kSecAssessmentAssessmentSource)))
287			authority = cfString(auth);
288		else
289			authority = "no authority";
290		if (CFTypeRef override = CFDictionaryGetValue(authdict, kSecAssessmentAssessmentAuthorityOverride))
291			if (CFEqual(override, kDisabledOverride)) {
292				old_overridden = true;
293				if (CFDictionaryGetValue(authdict, kSecAssessmentAssessmentAuthorityOriginalVerdict) == kCFBooleanFalse)
294					overridden = true;
295			}
296	}
297
298	MessageTrace trace("com.apple.security.assessment.outcome2", NULL);
299	std::string sanitized;
300	traceResult(assessment.path, trace, sanitized);
301	trace.add("signature4", "%d", type);
302
303	if (CFDictionaryGetValue(result, kSecAssessmentAssessmentVerdict) == kCFBooleanFalse) {
304		trace.add("signature", "denied:%s", authority.c_str());
305		trace.send("assessment denied for %s", sanitized.c_str());
306	} else if (overridden) {		// would have failed except for override
307		trace.add("signature", "defeated:%s", authority.c_str());
308		trace.send("assessment denied for %s but overridden", sanitized.c_str());
309	} else if (old_overridden) {	// would have succeeded even without override
310		trace.add("signature", "override:%s", authority.c_str());
311		trace.send("assessment granted for %s and overridden", sanitized.c_str());
312	} else {
313		trace.add("signature", "granted:%s", authority.c_str());
314		trace.send("assessment granted for %s by %s", sanitized.c_str(), authority.c_str());
315	}
316}
317
318static void traceUpdate(CFTypeRef target, CFDictionaryRef context, CFDictionaryRef result)
319{
320	// only trace add operations on URL targets
321	if (target == NULL || CFGetTypeID(target) != CFURLGetTypeID())
322		return;
323	CFStringRef edit = CFStringRef(CFDictionaryGetValue(context, kSecAssessmentContextKeyUpdate));
324	if (!CFEqual(edit, kSecAssessmentUpdateOperationAdd))
325		return;
326	MessageTrace trace("com.apple.security.assessment.update", NULL);
327	std::string sanitized;
328	traceResult(CFURLRef(target), trace, sanitized);
329	trace.send("added rule for %s", sanitized.c_str());
330}
331
332
333//
334// At present, CopyResult simply retrieves the result already formed by Create.
335// In the future, this will be more lazy.
336//
337CFDictionaryRef SecAssessmentCopyResult(SecAssessmentRef assessmentRef,
338	SecAssessmentFlags flags,
339	CFErrorRef *errors)
340{
341	BEGIN_CSAPI
342
343	SecAssessment &assessment = SecAssessment::ref(assessmentRef);
344	CFCopyRef<CFDictionaryRef> result = assessment.result;
345	if (overrideAssessment(flags)) {
346		// turn rejections into approvals, but note that we did that
347		CFTypeRef verdict = CFDictionaryGetValue(result, kSecAssessmentAssessmentVerdict);
348		if (verdict == kCFBooleanFalse) {
349			CFRef<CFMutableDictionaryRef> adulterated = makeCFMutableDictionary(result.get());
350			CFDictionarySetValue(adulterated, kSecAssessmentAssessmentVerdict, kCFBooleanTrue);
351			if (CFDictionaryRef authority = CFDictionaryRef(CFDictionaryGetValue(adulterated, kSecAssessmentAssessmentAuthority))) {
352				CFRef<CFMutableDictionaryRef> authority2 = makeCFMutableDictionary(authority);
353				CFDictionarySetValue(authority2, kSecAssessmentAssessmentAuthorityOverride, kDisabledOverride);
354				CFDictionarySetValue(authority2, kSecAssessmentAssessmentAuthorityOriginalVerdict, verdict);
355				CFDictionarySetValue(adulterated, kSecAssessmentAssessmentAuthority, authority2);
356			} else {
357				cfadd(adulterated, "{%O={%O=%O}}",
358					kSecAssessmentAssessmentAuthority, kSecAssessmentAssessmentAuthorityOverride, kDisabledOverride);
359			}
360			result = adulterated.get();
361		}
362	}
363	bool trace = CFDictionaryContainsKey(assessment.context, kSecAssessmentContextQuarantineFlags);
364	if (trace)
365		traceAssessment(assessment, assessment.type, result);
366	return result.yield();
367
368	END_CSAPI_ERRORS1(NULL)
369}
370
371
372//
373// Policy editing operations.
374// These all make permanent changes to the system-wide authority records.
375//
376CFStringRef kSecAssessmentContextKeyUpdate = CFSTR("update");
377CFStringRef kSecAssessmentUpdateOperationAdd = CFSTR("update:add");
378CFStringRef kSecAssessmentUpdateOperationRemove = CFSTR("update:remove");
379CFStringRef kSecAssessmentUpdateOperationEnable = CFSTR("update:enable");
380CFStringRef kSecAssessmentUpdateOperationDisable = CFSTR("update:disable");
381CFStringRef kSecAssessmentUpdateOperationFind = CFSTR("update:find");
382
383CFStringRef kSecAssessmentUpdateKeyAuthorization = CFSTR("update:authorization");
384CFStringRef kSecAssessmentUpdateKeyPriority = CFSTR("update:priority");
385CFStringRef kSecAssessmentUpdateKeyLabel = CFSTR("update:label");
386CFStringRef kSecAssessmentUpdateKeyExpires = CFSTR("update:expires");
387CFStringRef kSecAssessmentUpdateKeyAllow = CFSTR("update:allow");
388CFStringRef kSecAssessmentUpdateKeyRemarks = CFSTR("update:remarks");
389
390CFStringRef kSecAssessmentUpdateKeyRow = CFSTR("update:row");
391CFStringRef kSecAssessmentUpdateKeyCount = CFSTR("update:count");
392CFStringRef kSecAssessmentUpdateKeyFound = CFSTR("update:found");
393
394CFStringRef kSecAssessmentRuleKeyID = CFSTR("rule:id");
395CFStringRef kSecAssessmentRuleKeyPriority = CFSTR("rule:priority");
396CFStringRef kSecAssessmentRuleKeyAllow = CFSTR("rule:allow");
397CFStringRef kSecAssessmentRuleKeyLabel = CFSTR("rule:label");
398CFStringRef kSecAssessmentRuleKeyRemarks = CFSTR("rule:remarks");
399CFStringRef kSecAssessmentRuleKeyRequirement = CFSTR("rule:requirement");
400CFStringRef kSecAssessmentRuleKeyType = CFSTR("rule:type");
401CFStringRef kSecAssessmentRuleKeyExpires = CFSTR("rule:expires");
402CFStringRef kSecAssessmentRuleKeyDisabled = CFSTR("rule:disabled");
403CFStringRef kSecAssessmentRuleKeyBookmark = CFSTR("rule:bookmark");
404
405
406Boolean SecAssessmentUpdate(CFTypeRef target,
407	SecAssessmentFlags flags,
408	CFDictionaryRef context,
409	CFErrorRef *errors)
410{
411	if (CFDictionaryRef outcome = SecAssessmentCopyUpdate(target, flags, context, errors)) {
412		CFRelease(outcome);
413		return true;
414	} else {
415		return false;
416	}
417}
418
419CFDictionaryRef SecAssessmentCopyUpdate(CFTypeRef target,
420	SecAssessmentFlags flags,
421	CFDictionaryRef context,
422	CFErrorRef *errors)
423{
424	BEGIN_CSAPI
425
426	CFDictionary ctx(context, errSecCSInvalidAttributeValues);
427	CFRef<CFDictionaryRef> result;
428
429	if (flags & kSecAssessmentFlagDirect) {
430		if (__esp_enabled()) {
431			CFTemp<CFDictionaryRef> dict("{target=%O, flags=%d, context=%O}", target, flags, context);
432			OSStatus esp_result = __esp_check_ns("cs-assessment-update", (void *)(CFDictionaryRef)dict);
433			if (esp_result != noErr)
434				return NULL;
435		}
436
437		// ask the engine right here to do its thing
438		result = gEngine().update(target, flags, ctx);
439	} else {
440		// relay the question to our daemon for consideration
441		result = xpcEngineUpdate(target, flags, ctx);
442	}
443
444	if (__esp_enabled() && (flags & kSecAssessmentFlagDirect)) {
445		CFTemp<CFDictionaryRef> dict("{target=%O, flags=%d, context=%O, outcome=%O}", target, flags, context, (CFDictionaryRef)result);
446		__esp_notify_ns("cs-assessment-update", (void *)(CFDictionaryRef)dict);
447	}
448
449	traceUpdate(target, context, result);
450	return result.yield();
451
452	END_CSAPI_ERRORS1(false)
453}
454
455
456//
457// The fcntl of System Policies.
458// For those very special requests.
459//
460Boolean SecAssessmentControl(CFStringRef control, void *arguments, CFErrorRef *errors)
461{
462	BEGIN_CSAPI
463
464	CFTemp<CFDictionaryRef> dict("{control=%O}", control);
465	esp_do_check("cs-assessment-control", dict);
466
467	if (CFEqual(control, CFSTR("ui-enable"))) {
468		setAssessment(true);
469		MessageTrace trace("com.apple.security.assessment.state", "enable");
470		trace.send("enable assessment outcomes");
471		return true;
472	} else if (CFEqual(control, CFSTR("ui-disable"))) {
473		setAssessment(false);
474		MessageTrace trace("com.apple.security.assessment.state", "disable");
475		trace.send("disable assessment outcomes");
476		return true;
477	} else if (CFEqual(control, CFSTR("ui-status"))) {
478		CFBooleanRef &result = *(CFBooleanRef*)(arguments);
479		if (overrideAssessment())
480			result = kCFBooleanFalse;
481		else
482			result = kCFBooleanTrue;
483		return true;
484	} else if (CFEqual(control, CFSTR("ui-enable-devid"))) {
485		CFTemp<CFDictionaryRef> ctx("{%O=%s}", kSecAssessmentUpdateKeyLabel, "Developer ID");
486		if (CFDictionaryRef result = gEngine().enable(NULL, kAuthorityInvalid, kSecCSDefaultFlags, ctx))
487			CFRelease(result);
488		MessageTrace trace("com.apple.security.assessment.state", "enable-devid");
489		trace.send("enable Developer ID approval");
490		return true;
491	} else if (CFEqual(control, CFSTR("ui-disable-devid"))) {
492		CFTemp<CFDictionaryRef> ctx("{%O=%s}", kSecAssessmentUpdateKeyLabel, "Developer ID");
493		if (CFDictionaryRef result = gEngine().disable(NULL, kAuthorityInvalid, kSecCSDefaultFlags, ctx))
494			CFRelease(result);
495		MessageTrace trace("com.apple.security.assessment.state", "disable-devid");
496		trace.send("disable Developer ID approval");
497		return true;
498	} else if (CFEqual(control, CFSTR("ui-get-devid"))) {
499		CFBooleanRef &result = *(CFBooleanRef*)(arguments);
500		if (gEngine().value<int>("SELECT disabled FROM authority WHERE label = 'Developer ID';", true))
501			result = kCFBooleanFalse;
502		else
503			result = kCFBooleanTrue;
504		return true;
505	} else if (CFEqual(control, CFSTR("ui-record-reject"))) {
506		// send this through syspolicyd for update validation
507		xpcEngineRecord(CFDictionaryRef(arguments));
508		return true;
509	} else if (CFEqual(control, CFSTR("ui-record-reject-local"))) {
510		// perform the local operation (requires root)
511		gEngine().recordFailure(CFDictionaryRef(arguments));
512		return true;
513	} else if (CFEqual(control, CFSTR("ui-recall-reject"))) {
514		// no special privileges required for this, so read directly
515		CFDictionaryRef &result = *(CFDictionaryRef*)(arguments);
516		CFRef<CFDataRef> infoData = cfLoadFile(lastRejectFile);
517		if (infoData)
518			result = makeCFDictionaryFrom(infoData);
519		else
520			result = NULL;
521		return true;
522	} else
523		MacOSError::throwMe(errSecCSInvalidAttributeValues);
524
525	END_CSAPI_ERRORS1(false)
526}
527