]>
Commit | Line | Data |
---|---|---|
b1ab9ed8 A |
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 | ||
35 | using namespace CodeSigning; | |
36 | ||
37 | ||
38 | // | |
39 | // CF Objects | |
40 | // | |
41 | struct _SecAssessment : private CFRuntimeBase { | |
42 | public: | |
43 | _SecAssessment(CFURLRef p, CFDictionaryRef r) : path(p), result(r) { } | |
44 | ||
45 | CFCopyRef<CFURLRef> path; | |
46 | CFRef<CFDictionaryRef> result; | |
47 | ||
48 | public: | |
49 | static _SecAssessment &ref(SecAssessmentRef r) | |
50 | { return *(_SecAssessment *)r; } | |
51 | ||
52 | // CF Boiler-plate | |
53 | void *operator new (size_t size) | |
54 | { | |
55 | return (void *)_CFRuntimeCreateInstance(NULL, SecAssessmentGetTypeID(), | |
56 | sizeof(_SecAssessment) - sizeof(CFRuntimeBase), NULL); | |
57 | } | |
58 | ||
59 | static void finalize(CFTypeRef obj) | |
60 | { ((_SecAssessment *)obj)->~_SecAssessment(); } | |
61 | }; | |
62 | ||
63 | typedef _SecAssessment SecAssessment; | |
64 | ||
65 | ||
66 | static const CFRuntimeClass assessmentClass = { | |
67 | 0, // version | |
68 | "SecAssessment", // name | |
69 | NULL, // init | |
70 | NULL, // copy | |
71 | SecAssessment::finalize, // finalize | |
72 | NULL, // equal | |
73 | NULL, // hash | |
74 | NULL, // formatting | |
75 | NULL // debug string | |
76 | }; | |
77 | ||
78 | ||
79 | static dispatch_once_t assessmentOnce; | |
80 | CFTypeID assessmentType = _kCFRuntimeNotATypeID; | |
81 | ||
82 | CFTypeID SecAssessmentGetTypeID() | |
83 | { | |
84 | ||
85 | dispatch_once(&assessmentOnce, ^void() { | |
86 | if ((assessmentType = _CFRuntimeRegisterClass(&assessmentClass)) == _kCFRuntimeNotATypeID) | |
87 | abort(); | |
88 | }); | |
89 | return assessmentType; | |
90 | } | |
91 | ||
92 | ||
93 | // | |
94 | // Common dictionary constants | |
95 | // | |
96 | CFStringRef kSecAssessmentContextKeyOperation = CFSTR("operation"); | |
97 | CFStringRef kSecAssessmentOperationTypeExecute = CFSTR("operation:execute"); | |
98 | CFStringRef kSecAssessmentOperationTypeInstall = CFSTR("operation:install"); | |
99 | CFStringRef kSecAssessmentOperationTypeOpenDocument = CFSTR("operation:lsopen"); | |
100 | ||
101 | ||
102 | // | |
103 | // Read-only in-process access to the policy database | |
104 | // | |
105 | class ReadPolicy : public PolicyDatabase { | |
106 | public: | |
107 | ReadPolicy() : PolicyDatabase(defaultDatabase) { } | |
108 | }; | |
109 | ModuleNexus<ReadPolicy> gDatabase; | |
110 | ||
111 | ||
112 | // | |
113 | // An on-demand instance of the policy engine | |
114 | // | |
115 | ModuleNexus<PolicyEngine> gEngine; | |
116 | ||
117 | ||
118 | // | |
119 | // Policy evaluation ("assessment") operations | |
120 | // | |
121 | CFStringRef kSecAssessmentAssessmentVerdict = CFSTR("assessment:verdict"); | |
122 | CFStringRef kSecAssessmentAssessmentOriginator = CFSTR("assessment:originator"); | |
123 | CFStringRef kSecAssessmentAssessmentAuthority = CFSTR("assessment:authority"); | |
124 | CFStringRef kSecAssessmentAssessmentSource = CFSTR("assessment:authority:source"); | |
125 | CFStringRef kSecAssessmentAssessmentAuthorityRow = CFSTR("assessment:authority:row"); | |
126 | CFStringRef kSecAssessmentAssessmentAuthorityOverride = CFSTR("assessment:authority:override"); | |
127 | CFStringRef kSecAssessmentAssessmentFromCache = CFSTR("assessment:authority:cached"); | |
128 | ||
129 | CFStringRef kDisabledOverride = CFSTR("security disabled"); | |
130 | ||
131 | CFStringRef kSecAssessmentContextKeyCertificates = CFSTR("context:certificates"); // obsolete | |
132 | ||
133 | SecAssessmentRef SecAssessmentCreate(CFURLRef path, | |
134 | SecAssessmentFlags flags, | |
135 | CFDictionaryRef context, | |
136 | CFErrorRef *errors) | |
137 | { | |
138 | BEGIN_CSAPI | |
139 | ||
140 | if (flags & kSecAssessmentFlagAsynchronous) | |
141 | MacOSError::throwMe(errSecCSUnimplemented); | |
142 | ||
143 | AuthorityType type = typeFor(context, kAuthorityExecute); | |
144 | CFRef<CFMutableDictionaryRef> result = makeCFMutableDictionary(); | |
145 | ||
146 | SYSPOLICY_ASSESS_API(cfString(path).c_str(), int(type), flags); | |
147 | ||
148 | try { | |
149 | // check the object cache first unless caller denied that or we need extended processing | |
150 | if (!(flags & (kSecAssessmentFlagRequestOrigin | kSecAssessmentFlagIgnoreCache))) { | |
151 | if (gDatabase().checkCache(path, type, result)) | |
152 | return new SecAssessment(path, result.yield()); | |
153 | } | |
154 | ||
155 | if (flags & kSecAssessmentFlagDirect) { | |
156 | // ask the engine right here to do its thing | |
157 | SYSPOLICY_ASSESS_LOCAL(); | |
158 | gEngine().evaluate(path, type, flags, context, result); | |
159 | } else { | |
160 | // relay the question to our daemon for consideration | |
161 | SYSPOLICY_ASSESS_REMOTE(); | |
162 | xpcEngineAssess(path, flags, context, result); | |
163 | } | |
164 | } catch (CommonError &error) { | |
165 | switch (error.osStatus()) { | |
166 | case CSSMERR_TP_CERT_REVOKED: | |
167 | throw; | |
168 | default: | |
169 | if (!overrideAssessment()) | |
170 | throw; // let it go as an error | |
171 | break; | |
172 | } | |
173 | // record the error we would have returned | |
174 | cfadd(result, "{%O=#F,'assessment:error'=%d}}", kSecAssessmentAssessmentVerdict, error.osStatus()); | |
175 | } catch (...) { | |
176 | // catch stray errors not conforming to the CommonError scheme | |
177 | if (!overrideAssessment()) | |
178 | throw; // let it go as an error | |
179 | cfadd(result, "{%O=#F}", kSecAssessmentAssessmentVerdict); | |
180 | } | |
181 | return new SecAssessment(path, result.yield()); | |
182 | ||
183 | END_CSAPI_ERRORS1(NULL) | |
184 | } | |
185 | ||
186 | ||
187 | static void traceResult(SecAssessment &assessment, CFDictionaryRef result) | |
188 | { | |
189 | if (CFDictionaryGetValue(result, CFSTR("assessment:remote"))) | |
190 | return; // just traced in syspolicyd | |
191 | ||
192 | CFRef<CFURLRef> url = CFURLCopyAbsoluteURL(assessment.path); | |
193 | string sanitized = cfString(url); | |
194 | string::size_type rslash = sanitized.rfind('/'); | |
195 | if (rslash != string::npos) | |
196 | sanitized = sanitized.substr(rslash+1); | |
197 | string::size_type dot = sanitized.rfind('.'); | |
198 | if (dot != string::npos) | |
199 | sanitized = sanitized.substr(dot+1); | |
200 | else | |
201 | sanitized = "(none)"; | |
202 | ||
203 | string identifier = "UNBUNDLED"; | |
204 | if (CFRef<CFBundleRef> bundle = CFBundleCreate(NULL, assessment.path)) | |
205 | if (CFStringRef ident = CFBundleGetIdentifier(bundle)) | |
206 | identifier = cfString(ident); | |
207 | ||
208 | string authority = "UNSPECIFIED"; | |
209 | bool overridden = false; | |
210 | if (CFDictionaryRef authdict = CFDictionaryRef(CFDictionaryGetValue(result, kSecAssessmentAssessmentAuthority))) { | |
211 | if (CFStringRef auth = CFStringRef(CFDictionaryGetValue(authdict, kSecAssessmentAssessmentSource))) | |
212 | authority = cfString(auth); | |
213 | else | |
214 | authority = "no authority"; | |
215 | if (CFTypeRef override = CFDictionaryGetValue(authdict, kSecAssessmentAssessmentAuthorityOverride)) | |
216 | if (CFEqual(override, kDisabledOverride)) | |
217 | overridden = true; | |
218 | } | |
219 | ||
220 | MessageTrace trace("com.apple.security.assessment.outcome", NULL); | |
221 | trace.add("signature2", "bundle:%s", identifier.c_str()); | |
222 | if (CFDictionaryGetValue(result, kSecAssessmentAssessmentVerdict) == kCFBooleanFalse) { | |
223 | trace.add("signature", "denied:%s", authority.c_str()); | |
224 | trace.add("signature3", sanitized.c_str()); | |
225 | trace.send("assessment denied for %s", sanitized.c_str()); | |
226 | } else if (overridden) { | |
227 | trace.add("signature", "override:%s", authority.c_str()); | |
228 | trace.add("signature3", sanitized.c_str()); | |
229 | trace.send("assessment denied for %s but overridden", sanitized.c_str()); | |
230 | } else { | |
231 | trace.add("signature", "granted:%s", authority.c_str()); | |
232 | trace.add("signature3", sanitized.c_str()); | |
233 | trace.send("assessment granted for %s by %s", sanitized.c_str(), authority.c_str()); | |
234 | } | |
235 | } | |
236 | ||
237 | ||
238 | // | |
239 | // At present, CopyResult simply retrieves the result already formed by Create. | |
240 | // In the future, this will be more lazy. | |
241 | // | |
242 | CFDictionaryRef SecAssessmentCopyResult(SecAssessmentRef assessmentRef, | |
243 | SecAssessmentFlags flags, | |
244 | CFErrorRef *errors) | |
245 | { | |
246 | BEGIN_CSAPI | |
247 | ||
248 | SecAssessment &assessment = SecAssessment::ref(assessmentRef); | |
249 | CFCopyRef<CFDictionaryRef> result = assessment.result; | |
250 | if (!(flags & kSecAssessmentFlagEnforce) && overrideAssessment()) { | |
251 | // turn rejections into approvals, but note that we did that | |
252 | if (CFDictionaryGetValue(result, kSecAssessmentAssessmentVerdict) == kCFBooleanFalse) { | |
253 | CFRef<CFMutableDictionaryRef> adulterated = makeCFMutableDictionary(result.get()); | |
254 | CFDictionarySetValue(adulterated, kSecAssessmentAssessmentVerdict, kCFBooleanTrue); | |
255 | if (CFDictionaryRef authority = CFDictionaryRef(CFDictionaryGetValue(adulterated, kSecAssessmentAssessmentAuthority))) { | |
256 | CFRef<CFMutableDictionaryRef> authority2 = makeCFMutableDictionary(authority); | |
257 | CFDictionarySetValue(authority2, kSecAssessmentAssessmentAuthorityOverride, kDisabledOverride); | |
258 | CFDictionarySetValue(adulterated, kSecAssessmentAssessmentAuthority, authority2); | |
259 | } else { | |
260 | cfadd(adulterated, "{%O={%O=%O}}", | |
261 | kSecAssessmentAssessmentAuthority, kSecAssessmentAssessmentAuthorityOverride, kDisabledOverride); | |
262 | } | |
263 | result = adulterated.get(); | |
264 | } | |
265 | } | |
266 | traceResult(assessment, result); | |
267 | return result.yield(); | |
268 | ||
269 | END_CSAPI_ERRORS1(NULL) | |
270 | } | |
271 | ||
272 | ||
273 | // | |
274 | // Policy editing operations. | |
275 | // These all make permanent changes to the system-wide authority records. | |
276 | // | |
277 | CFStringRef kSecAssessmentContextKeyUpdate = CFSTR("update"); | |
278 | CFStringRef kSecAssessmentUpdateOperationAdd = CFSTR("update:add"); | |
279 | CFStringRef kSecAssessmentUpdateOperationRemove = CFSTR("update:remove"); | |
280 | CFStringRef kSecAssessmentUpdateOperationEnable = CFSTR("update:enable"); | |
281 | CFStringRef kSecAssessmentUpdateOperationDisable = CFSTR("update:disable"); | |
282 | CFStringRef kSecAssessmentUpdateOperationFind = CFSTR("update:find"); | |
283 | ||
284 | CFStringRef kSecAssessmentUpdateKeyAuthorization = CFSTR("update:authorization"); | |
285 | CFStringRef kSecAssessmentUpdateKeyPriority = CFSTR("update:priority"); | |
286 | CFStringRef kSecAssessmentUpdateKeyLabel = CFSTR("update:label"); | |
287 | CFStringRef kSecAssessmentUpdateKeyExpires = CFSTR("update:expires"); | |
288 | CFStringRef kSecAssessmentUpdateKeyAllow = CFSTR("update:allow"); | |
289 | CFStringRef kSecAssessmentUpdateKeyRemarks = CFSTR("update:remarks"); | |
290 | ||
291 | CFStringRef kSecAssessmentUpdateKeyRow = CFSTR("update:row"); | |
292 | CFStringRef kSecAssessmentUpdateKeyCount = CFSTR("update:count"); | |
293 | CFStringRef kSecAssessmentUpdateKeyFound = CFSTR("update:found"); | |
294 | ||
295 | CFStringRef kSecAssessmentRuleKeyID = CFSTR("rule:id"); | |
296 | CFStringRef kSecAssessmentRuleKeyPriority = CFSTR("rule:priority"); | |
297 | CFStringRef kSecAssessmentRuleKeyAllow = CFSTR("rule:allow"); | |
298 | CFStringRef kSecAssessmentRuleKeyLabel = CFSTR("rule:label"); | |
299 | CFStringRef kSecAssessmentRuleKeyRemarks = CFSTR("rule:remarks"); | |
300 | CFStringRef kSecAssessmentRuleKeyRequirement = CFSTR("rule:requirement"); | |
301 | CFStringRef kSecAssessmentRuleKeyType = CFSTR("rule:type"); | |
302 | CFStringRef kSecAssessmentRuleKeyExpires = CFSTR("rule:expires"); | |
303 | CFStringRef kSecAssessmentRuleKeyDisabled = CFSTR("rule:disabled"); | |
304 | CFStringRef kSecAssessmentRuleKeyBookmark = CFSTR("rule:bookmark"); | |
305 | ||
306 | ||
307 | Boolean SecAssessmentUpdate(CFTypeRef target, | |
308 | SecAssessmentFlags flags, | |
309 | CFDictionaryRef context, | |
310 | CFErrorRef *errors) | |
311 | { | |
312 | if (CFDictionaryRef outcome = SecAssessmentCopyUpdate(target, flags, context, errors)) { | |
313 | CFRelease(outcome); | |
314 | return true; | |
315 | } else { | |
316 | return false; | |
317 | } | |
318 | } | |
319 | ||
320 | CFDictionaryRef SecAssessmentCopyUpdate(CFTypeRef target, | |
321 | SecAssessmentFlags flags, | |
322 | CFDictionaryRef context, | |
323 | CFErrorRef *errors) | |
324 | { | |
325 | BEGIN_CSAPI | |
326 | ||
327 | CFDictionary ctx(context, errSecCSInvalidAttributeValues); | |
328 | ||
329 | if (flags & kSecAssessmentFlagDirect) { | |
330 | // ask the engine right here to do its thing | |
331 | return gEngine().update(target, flags, ctx); | |
332 | } else { | |
333 | // relay the question to our daemon for consideration | |
334 | return xpcEngineUpdate(target, flags, ctx); | |
335 | } | |
336 | ||
337 | END_CSAPI_ERRORS1(false) | |
338 | } | |
339 | ||
340 | ||
341 | // | |
342 | // The fcntl of System Policies. | |
343 | // For those very special requests. | |
344 | // | |
345 | Boolean SecAssessmentControl(CFStringRef control, void *arguments, CFErrorRef *errors) | |
346 | { | |
347 | BEGIN_CSAPI | |
348 | ||
349 | if (CFEqual(control, CFSTR("ui-enable"))) { | |
350 | setAssessment(true); | |
351 | MessageTrace trace("com.apple.security.assessment.state", "enable"); | |
352 | trace.send("enable assessment outcomes"); | |
353 | return true; | |
354 | } else if (CFEqual(control, CFSTR("ui-disable"))) { | |
355 | setAssessment(false); | |
356 | MessageTrace trace("com.apple.security.assessment.state", "disable"); | |
357 | trace.send("disable assessment outcomes"); | |
358 | return true; | |
359 | } else if (CFEqual(control, CFSTR("ui-status"))) { | |
360 | CFBooleanRef &result = *(CFBooleanRef*)(arguments); | |
361 | if (overrideAssessment()) | |
362 | result = kCFBooleanFalse; | |
363 | else | |
364 | result = kCFBooleanTrue; | |
365 | return true; | |
366 | } else if (CFEqual(control, CFSTR("ui-enable-devid"))) { | |
367 | CFTemp<CFDictionaryRef> ctx("{%O=%s}", kSecAssessmentUpdateKeyLabel, "Developer ID"); | |
368 | if (CFDictionaryRef result = gEngine().enable(NULL, kAuthorityInvalid, kSecCSDefaultFlags, ctx)) | |
369 | CFRelease(result); | |
370 | return true; | |
371 | } else if (CFEqual(control, CFSTR("ui-disable-devid"))) { | |
372 | CFTemp<CFDictionaryRef> ctx("{%O=%s}", kSecAssessmentUpdateKeyLabel, "Developer ID"); | |
373 | if (CFDictionaryRef result = gEngine().disable(NULL, kAuthorityInvalid, kSecCSDefaultFlags, ctx)) | |
374 | CFRelease(result); | |
375 | return true; | |
376 | } else if (CFEqual(control, CFSTR("ui-get-devid"))) { | |
377 | CFBooleanRef &result = *(CFBooleanRef*)(arguments); | |
378 | if (gEngine().value<int>("SELECT disabled FROM authority WHERE label = 'Developer ID';", true)) | |
379 | result = kCFBooleanFalse; | |
380 | else | |
381 | result = kCFBooleanTrue; | |
382 | return true; | |
383 | } else | |
384 | MacOSError::throwMe(errSecCSInvalidAttributeValues); | |
385 | ||
386 | END_CSAPI_ERRORS1(false) | |
387 | } |