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