]> git.saurik.com Git - apple/security.git/blob - Security/libsecurity_codesigning/lib/SecAssessment.cpp
Security-57031.30.12.tar.gz
[apple/security.git] / Security / libsecurity_codesigning / lib / SecAssessment.cpp
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
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 kSecAssessmentContextKeyFeedback = CFSTR("context:feedback");
130 CFStringRef kSecAssessmentFeedbackProgress = CFSTR("feedback:progress");
131 CFStringRef kSecAssessmentFeedbackInfoCurrent = CFSTR("current");
132 CFStringRef kSecAssessmentFeedbackInfoTotal = CFSTR("total");
133
134 CFStringRef kSecAssessmentAssessmentVerdict = CFSTR("assessment:verdict");
135 CFStringRef kSecAssessmentAssessmentOriginator = CFSTR("assessment:originator");
136 CFStringRef kSecAssessmentAssessmentAuthority = CFSTR("assessment:authority");
137 CFStringRef kSecAssessmentAssessmentSource = CFSTR("assessment:authority:source");
138 CFStringRef kSecAssessmentAssessmentAuthorityRow = CFSTR("assessment:authority:row");
139 CFStringRef kSecAssessmentAssessmentAuthorityOverride = CFSTR("assessment:authority:override");
140 CFStringRef kSecAssessmentAssessmentAuthorityOriginalVerdict = CFSTR("assessment:authority:verdict");
141 CFStringRef kSecAssessmentAssessmentFromCache = CFSTR("assessment:authority:cached");
142 CFStringRef kSecAssessmentAssessmentWeakSignature = CFSTR("assessment:authority:weak");
143 CFStringRef kSecAssessmentAssessmentCodeSigningError = CFSTR("assessment:cserror");
144
145 CFStringRef kDisabledOverride = CFSTR("security disabled");
146
147 SecAssessmentRef 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
206 static 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
273 static 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
314 static 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 //
333 CFDictionaryRef 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 //
370 CFStringRef kSecAssessmentContextKeyUpdate = CFSTR("update");
371 CFStringRef kSecAssessmentUpdateOperationAdd = CFSTR("update:add");
372 CFStringRef kSecAssessmentUpdateOperationRemove = CFSTR("update:remove");
373 CFStringRef kSecAssessmentUpdateOperationEnable = CFSTR("update:enable");
374 CFStringRef kSecAssessmentUpdateOperationDisable = CFSTR("update:disable");
375 CFStringRef kSecAssessmentUpdateOperationFind = CFSTR("update:find");
376
377 CFStringRef kSecAssessmentUpdateKeyAuthorization = CFSTR("update:authorization");
378 CFStringRef kSecAssessmentUpdateKeyPriority = CFSTR("update:priority");
379 CFStringRef kSecAssessmentUpdateKeyLabel = CFSTR("update:label");
380 CFStringRef kSecAssessmentUpdateKeyExpires = CFSTR("update:expires");
381 CFStringRef kSecAssessmentUpdateKeyAllow = CFSTR("update:allow");
382 CFStringRef kSecAssessmentUpdateKeyRemarks = CFSTR("update:remarks");
383
384 CFStringRef kSecAssessmentUpdateKeyRow = CFSTR("update:row");
385 CFStringRef kSecAssessmentUpdateKeyCount = CFSTR("update:count");
386 CFStringRef kSecAssessmentUpdateKeyFound = CFSTR("update:found");
387
388 CFStringRef kSecAssessmentRuleKeyID = CFSTR("rule:id");
389 CFStringRef kSecAssessmentRuleKeyPriority = CFSTR("rule:priority");
390 CFStringRef kSecAssessmentRuleKeyAllow = CFSTR("rule:allow");
391 CFStringRef kSecAssessmentRuleKeyLabel = CFSTR("rule:label");
392 CFStringRef kSecAssessmentRuleKeyRemarks = CFSTR("rule:remarks");
393 CFStringRef kSecAssessmentRuleKeyRequirement = CFSTR("rule:requirement");
394 CFStringRef kSecAssessmentRuleKeyType = CFSTR("rule:type");
395 CFStringRef kSecAssessmentRuleKeyExpires = CFSTR("rule:expires");
396 CFStringRef kSecAssessmentRuleKeyDisabled = CFSTR("rule:disabled");
397 CFStringRef kSecAssessmentRuleKeyBookmark = CFSTR("rule:bookmark");
398
399
400 Boolean 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
413 CFDictionaryRef 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 //
454 Boolean 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 }