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