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