]> git.saurik.com Git - apple/libsecurity_codesigning.git/blob - lib/policyengine.cpp
99e087b3862cc6c0071152dda828fcd999feb28a
[apple/libsecurity_codesigning.git] / lib / policyengine.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 "policyengine.h"
24 #include "xar++.h"
25 #include <security_utilities/cfmunge.h>
26 #include <Security/Security.h>
27 #include <Security/SecRequirementPriv.h>
28 #include <Security/SecPolicyPriv.h>
29
30 #include <CoreServices/CoreServicesPriv.h>
31 #undef check // Macro! Yech.
32
33 namespace Security {
34 namespace CodeSigning {
35
36 static const time_t NEGATIVE_HOLD = 60; // seconds for negative cache entries
37
38
39 //
40 // Core structure
41 //
42 PolicyEngine::PolicyEngine()
43 : PolicyDatabase(NULL, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE)
44 {
45 }
46
47 PolicyEngine::~PolicyEngine()
48 { }
49
50
51 //
52 // Top-level evaluation driver
53 //
54 void PolicyEngine::evaluate(CFURLRef path, AuthorityType type, SecAssessmentFlags flags, CFDictionaryRef context, CFMutableDictionaryRef result)
55 {
56 switch (type) {
57 case kAuthorityExecute:
58 evaluateCode(path, flags, context, result);
59 break;
60 case kAuthorityInstall:
61 evaluateInstall(path, flags, context, result);
62 break;
63 case kAuthorityOpenDoc:
64 evaluateDocOpen(path, flags, context, result);
65 break;
66 default:
67 MacOSError::throwMe(errSecCSInvalidAttributeValues);
68 break;
69 }
70 }
71
72
73 //
74 // Executable code.
75 // Read from disk, evaluate properly, cache as indicated. The whole thing, so far.
76 //
77 void PolicyEngine::evaluateCode(CFURLRef path, SecAssessmentFlags flags, CFDictionaryRef context, CFMutableDictionaryRef result)
78 {
79 const AuthorityType type = kAuthorityExecute;
80
81 CFRef<SecStaticCodeRef> code;
82 MacOSError::check(SecStaticCodeCreateWithPath(path, kSecCSDefaultFlags, &code.aref()));
83
84 if (flags & kSecAssessmentFlagRequestOrigin) {
85 CFRef<CFDictionaryRef> info;
86 MacOSError::check(SecCodeCopySigningInformation(code, kSecCSSigningInformation, &info.aref()));
87 if (CFArrayRef chain = CFArrayRef(CFDictionaryGetValue(info, kSecCodeInfoCertificates)))
88 setOrigin(chain, result);
89 }
90
91 SecCSFlags validationFlags = kSecCSDefaultFlags;
92 if (overrideAssessment()) // we'll force the verdict to 'pass' at the end, so don't sweat validating code
93 validationFlags = kSecCSBasicValidateOnly;
94
95 SQLite::Statement query(*this,
96 "SELECT allow, requirement, inhibit_cache, expires, id, label FROM authority WHERE type = ?1 ORDER BY priority DESC;");
97 query.bind(1).integer(type);
98 while (query.nextRow()) {
99 bool allow = int(query[0]);
100 const char *reqString = query[1];
101 bool inhibit_cache = query[2];
102 time_t expires = SQLite::int64(query[3]);
103 SQLite3::int64 id = query[4];
104 const char *label = query[5];
105
106 if (expires && expires < time(NULL)) // no longer active
107 continue;
108
109 CFRef<SecRequirementRef> requirement;
110 MacOSError::check(SecRequirementCreateWithString(CFTempString(reqString), kSecCSDefaultFlags, &requirement.aref()));
111 switch (OSStatus rc = SecStaticCodeCheckValidity(code, validationFlags, requirement)) {
112 case noErr: // success
113 break;
114 case errSecCSUnsigned:
115 cfadd(result, "{%O=#F}", kSecAssessmentAssessmentVerdict);
116 addAuthority(result, "no usable signature");
117 return;
118 case errSecCSSignatureFailed:
119 case errSecCSSignatureInvalid:
120 case errSecCSSignatureUnsupported:
121 case errSecCSSignatureNotVerifiable:
122 cfadd(result, "{%O=#F}", kSecAssessmentAssessmentVerdict);
123 addAuthority(result, "invalid signature");
124 return;
125 case errSecCSReqFailed: // requirement missed, but otherwise okay
126 continue;
127 default: // broken in some way; all tests will fail like this so bail out
128 MacOSError::throwMe(rc);
129 }
130 if (!inhibit_cache && !(flags & kSecAssessmentFlagNoCache)) // cache inhibit
131 this->recordOutcome(code, allow, type, expires, id, label);
132 cfadd(result, "{%O=%B}", kSecAssessmentAssessmentVerdict, allow);
133 addAuthority(result, label, id);
134 return;
135 }
136
137 // no applicable authority. Deny by default
138 if (!(flags & kSecAssessmentFlagNoCache))
139 this->recordOutcome(code, false, type, time(NULL) + NEGATIVE_HOLD, 0, NULL);
140 cfadd(result, "{%O=%B}", kSecAssessmentAssessmentVerdict, false);
141 addAuthority(result, NULL);
142 }
143
144
145 //
146 // Installer archive.
147 // Certs passed from caller (untrusted), no policy engine yet, no caching (since untrusted).
148 // The current "policy" is to trust any proper signature.
149 //
150 void PolicyEngine::evaluateInstall(CFURLRef path, SecAssessmentFlags flags, CFDictionaryRef context, CFMutableDictionaryRef result)
151 {
152 const AuthorityType type = kAuthorityInstall;
153
154 Xar xar(cfString(path).c_str());
155 if (xar) {
156 if (!xar.isSigned()) {
157 // unsigned xar
158 cfadd(result, "{%O=%B}", kSecAssessmentAssessmentVerdict, false);
159 addAuthority(result, "no usable signature");
160 return;
161 }
162 if (CFRef<CFArrayRef> certs = xar.copyCertChain()) {
163 CFRef<SecPolicyRef> policy = SecPolicyCreateBasicX509();
164 CFRef<SecTrustRef> trust;
165 MacOSError::check(SecTrustCreateWithCertificates(certs, policy, &trust.aref()));
166 // MacOSError::check(SecTrustSetAnchorCertificates(trust, cfEmptyArray())); // no anchors
167 MacOSError::check(SecTrustSetOptions(trust, kSecTrustOptionImplicitAnchors));
168
169 SecTrustResultType trustResult;
170 MacOSError::check(SecTrustEvaluate(trust, &trustResult));
171 CFRef<CFArrayRef> chain;
172 CSSM_TP_APPLE_EVIDENCE_INFO *info;
173 MacOSError::check(SecTrustGetResult(trust, &trustResult, &chain.aref(), &info));
174
175 if (flags & kSecAssessmentFlagRequestOrigin)
176 setOrigin(chain, result);
177
178 switch (trustResult) {
179 case kSecTrustResultProceed:
180 case kSecTrustResultConfirm:
181 case kSecTrustResultUnspecified:
182 break;
183 default:
184 {
185 OSStatus rc;
186 MacOSError::check(SecTrustGetCssmResultCode(trust, &rc));
187 MacOSError::throwMe(rc);
188 }
189 }
190
191 SQLite::Statement query(*this,
192 "SELECT allow, requirement, inhibit_cache, expires, id, label FROM authority WHERE type = ?1 ORDER BY priority DESC;");
193 query.bind(1).integer(type);
194 while (query.nextRow()) {
195 bool allow = int(query[0]);
196 const char *reqString = query[1];
197 bool inhibit_cache = query[2];
198 time_t expires = SQLite::int64(query[3]);
199 SQLite3::int64 id = query[4];
200 const char *label = query[5];
201
202 if (expires && expires < time(NULL)) // no longer active
203 continue;
204
205 CFRef<SecRequirementRef> requirement;
206 MacOSError::check(SecRequirementCreateWithString(CFTempString(reqString), kSecCSDefaultFlags, &requirement.aref()));
207 switch (OSStatus rc = SecRequirementEvaluate(requirement, chain, NULL, kSecCSDefaultFlags)) {
208 case noErr: // success
209 break;
210 case errSecCSReqFailed: // requirement missed, but otherwise okay
211 continue;
212 default: // broken in some way; all tests will fail like this so bail out
213 MacOSError::throwMe(rc);
214 }
215 // not adding to the object cache - we could, but it's not likely to be worth it
216 cfadd(result, "{%O=%B}", kSecAssessmentAssessmentVerdict, allow);
217 addAuthority(result, label, id);
218 return;
219 }
220 }
221 }
222
223 // no applicable authority. Deny by default
224 cfadd(result, "{%O=#F}", kSecAssessmentAssessmentVerdict);
225 addAuthority(result, NULL);
226 }
227
228
229 //
230 // LaunchServices-layer document open.
231 // We don't cache those at present. If we ever do, we need to authenticate CoreServicesUIAgent as the source of its risk assessment.
232 //
233 void PolicyEngine::evaluateDocOpen(CFURLRef path, SecAssessmentFlags flags, CFDictionaryRef context, CFMutableDictionaryRef result)
234 {
235 if (context) {
236 if (CFStringRef riskCategory = CFStringRef(CFDictionaryGetValue(context, kLSDownloadRiskCategoryKey))) {
237 if (CFEqual(riskCategory, kLSRiskCategorySafe)
238 || CFEqual(riskCategory, kLSRiskCategoryNeutral)
239 || CFEqual(riskCategory, kLSRiskCategoryUnknown)
240 || CFEqual(riskCategory, kLSRiskCategoryMayContainUnsafeExecutable)) {
241 cfadd(result, "{%O=#T}", kSecAssessmentAssessmentVerdict);
242 } else {
243 cfadd(result, "{%O=#F}", kSecAssessmentAssessmentVerdict);
244 }
245 addAuthority(result, "_XProtect");
246 addToAuthority(result, kLSDownloadRiskCategoryKey, riskCategory);
247 return;
248 }
249 }
250 // insufficient information from LS - deny by default
251 cfadd(result, "{%O=%F}", kSecAssessmentAssessmentVerdict);
252 addAuthority(result, NULL);
253 }
254
255
256 void PolicyEngine::addAuthority(CFMutableDictionaryRef parent, const char *label, SQLite::int64 row, CFTypeRef cacheInfo)
257 {
258 CFRef<CFMutableDictionaryRef> auth = makeCFMutableDictionary();
259 if (label)
260 cfadd(auth, "{%O=%s}", kSecAssessmentAssessmentSource, label);
261 if (row)
262 CFDictionaryAddValue(auth, kSecAssessmentAssessmentAuthorityRow, CFTempNumber(row));
263 if (cacheInfo)
264 CFDictionaryAddValue(auth, kSecAssessmentAssessmentFromCache, cacheInfo);
265 CFDictionaryAddValue(parent, kSecAssessmentAssessmentAuthority, auth);
266 }
267
268 void PolicyEngine::addToAuthority(CFMutableDictionaryRef parent, CFStringRef key, CFTypeRef value)
269 {
270 CFMutableDictionaryRef authority = CFMutableDictionaryRef(CFDictionaryGetValue(parent, kSecAssessmentAssessmentAuthority));
271 assert(authority);
272 CFDictionaryAddValue(authority, key, value);
273 }
274
275
276 //
277 // Add a rule to the policy database
278 //
279 bool PolicyEngine::add(CFURLRef path, AuthorityType type, SecAssessmentFlags flags, CFDictionaryRef context)
280 {
281 if (type != kAuthorityExecute)
282 MacOSError::throwMe(errSecCSUnimplemented);
283
284 double priority = 0;
285 string label;
286 if (context) {
287 if (CFTypeRef pri = CFDictionaryGetValue(context, kSecAssessmentUpdateKeyPriority)) {
288 if (CFGetTypeID(pri) != CFNumberGetTypeID())
289 MacOSError::throwMe(errSecCSBadDictionaryFormat);
290 CFNumberGetValue(CFNumberRef(pri), kCFNumberDoubleType, &priority);
291 }
292 if (CFTypeRef lab = CFDictionaryGetValue(context, kSecAssessmentUpdateKeyLabel)) {
293 if (CFGetTypeID(lab) != CFStringGetTypeID())
294 MacOSError::throwMe(errSecCSBadDictionaryFormat);
295 label = cfString(CFStringRef(lab));
296 }
297 }
298
299 CFRef<SecStaticCodeRef> code;
300 MacOSError::check(SecStaticCodeCreateWithPath(path, kSecCSDefaultFlags, &code.aref()));
301 CFRef<CFDictionaryRef> info;
302 MacOSError::check(SecCodeCopySigningInformation(code, kSecCSDefaultFlags, &info.aref()));
303 CFRef<SecRequirementRef> dr;
304 MacOSError::check(SecCodeCopyDesignatedRequirement(code, kSecCSDefaultFlags, &dr.aref()));
305
306 CFRef<CFStringRef> requirementText;
307 MacOSError::check(SecRequirementCopyString(dr, kSecCSDefaultFlags, &requirementText.aref()));
308 SQLite::Statement insert(*this,
309 "INSERT INTO authority (type, allow, requirement, priority, label) VALUES (?1, ?2, ?3, ?4, ?5);");
310 insert.bind(1).integer(type);
311 insert.bind(2).integer(true);
312 insert.bind(3) = requirementText.get();
313 insert.bind(4) = priority;
314 insert.bind(5) = label.c_str();
315 insert.execute();
316 return true;
317 }
318
319
320 //
321 // Fill in extra information about the originator of cryptographic credentials found - if any
322 //
323 void PolicyEngine::setOrigin(CFArrayRef chain, CFMutableDictionaryRef result)
324 {
325 if (chain)
326 if (CFArrayGetCount(chain) > 0)
327 if (SecCertificateRef leaf = SecCertificateRef(CFArrayGetValueAtIndex(chain, 0)))
328 if (CFStringRef summary = SecCertificateCopyLongDescription(NULL, leaf, NULL))
329 CFDictionarySetValue(result, kSecAssessmentAssessmentOriginator, summary);
330 }
331
332
333 //
334 // Take an assessment outcome and record it in the object cache
335 //
336 void PolicyEngine::recordOutcome(SecStaticCodeRef code, bool allow, AuthorityType type, time_t expires, int authority, const char *label)
337 {
338 CFRef<CFDictionaryRef> info;
339 MacOSError::check(SecCodeCopySigningInformation(code, kSecCSDefaultFlags, &info.aref()));
340 CFDataRef cdHash = CFDataRef(CFDictionaryGetValue(info, kSecCodeInfoUnique));
341 CFRef<CFURLRef> path;
342 MacOSError::check(SecCodeCopyPath(code, kSecCSDefaultFlags, &path.aref()));
343 //@@@ should really be OR REPLACE IF EXPIRED... does it matter? @@@
344 SQLite::Transaction xact(*this, SQLite3::Transaction::deferred, "caching");
345 SQLite::Statement insert(*this,
346 "INSERT OR REPLACE INTO object (type, allow, hash, expires, authority, label, path) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)");
347 insert.bind(1).integer(type);
348 insert.bind(2).integer(allow);
349 insert.bind(3) = cdHash;
350 if (expires)
351 insert.bind(4).integer(expires);
352 insert.bind(5).integer(authority);
353 if (label)
354 insert.bind(6) = label;
355 insert.bind(7) = cfString(path).c_str();
356 insert.execute();
357 xact.commit();
358 }
359
360
361 } // end namespace CodeSigning
362 } // end namespace Security