]>
Commit | Line | Data |
---|---|---|
b1ab9ed8 A |
1 | /* |
2 | * Copyright (c) 2011-2012 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 "quarantine++.h" | |
26 | #include "codesigning_dtrace.h" | |
27 | #include <security_utilities/cfmunge.h> | |
28 | #include <Security/Security.h> | |
29 | #include <Security/SecCodePriv.h> | |
30 | #include <Security/SecRequirementPriv.h> | |
31 | #include <Security/SecPolicyPriv.h> | |
32 | #include <Security/SecTrustPriv.h> | |
33 | #include <Security/SecCodeSigner.h> | |
34 | #include <Security/cssmapplePriv.h> | |
35 | #include <security_utilities/unix++.h> | |
36 | #include <notify.h> | |
37 | ||
313fa17b A |
38 | #include "diskrep.h" |
39 | #include "codedirectory.h" | |
40 | #include "csutilities.h" | |
41 | #include "StaticCode.h" | |
42 | ||
b1ab9ed8 A |
43 | #include <CoreServices/CoreServicesPriv.h> |
44 | #include "SecCodePriv.h" | |
45 | #undef check // Macro! Yech. | |
46 | ||
47 | extern "C" { | |
48 | #include <OpenScriptingUtilPriv.h> | |
49 | } | |
50 | ||
51 | ||
52 | namespace Security { | |
53 | namespace CodeSigning { | |
54 | ||
55 | static const double NEGATIVE_HOLD = 60.0/86400; // 60 seconds to cache negative outcomes | |
56 | ||
57 | static const char RECORDER_DIR[] = "/tmp/gke-"; // recorder mode destination for detached signatures | |
58 | enum { | |
59 | recorder_code_untrusted = 0, // signed but untrusted | |
60 | recorder_code_adhoc = 1, // unsigned; signature recorded | |
61 | recorder_code_unable = 2, // unsigned; unable to record signature | |
62 | }; | |
63 | ||
64 | ||
65 | static void authorizeUpdate(SecAssessmentFlags flags, CFDictionaryRef context); | |
313fa17b | 66 | static void normalizeTarget(CFRef<CFTypeRef> &target, CFDictionary &context, std::string *signUnsigned = NULL); |
b1ab9ed8 A |
67 | static bool codeInvalidityExceptions(SecStaticCodeRef code, CFMutableDictionaryRef result); |
68 | static CFTypeRef installerPolicy() CF_RETURNS_RETAINED; | |
69 | ||
70 | ||
71 | // | |
72 | // Core structure | |
73 | // | |
74 | PolicyEngine::PolicyEngine() | |
75 | : PolicyDatabase(NULL, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE) | |
76 | { | |
77 | } | |
78 | ||
79 | PolicyEngine::~PolicyEngine() | |
80 | { } | |
81 | ||
82 | ||
83 | // | |
84 | // Top-level evaluation driver | |
85 | // | |
86 | void PolicyEngine::evaluate(CFURLRef path, AuthorityType type, SecAssessmentFlags flags, CFDictionaryRef context, CFMutableDictionaryRef result) | |
87 | { | |
313fa17b A |
88 | // update GKE |
89 | installExplicitSet(gkeAuthFile, gkeSigsFile); | |
90 | ||
b1ab9ed8 A |
91 | switch (type) { |
92 | case kAuthorityExecute: | |
93 | evaluateCode(path, kAuthorityExecute, flags, context, result); | |
94 | break; | |
95 | case kAuthorityInstall: | |
96 | evaluateInstall(path, flags, context, result); | |
97 | break; | |
98 | case kAuthorityOpenDoc: | |
99 | evaluateDocOpen(path, flags, context, result); | |
100 | break; | |
101 | default: | |
102 | MacOSError::throwMe(errSecCSInvalidAttributeValues); | |
103 | break; | |
104 | } | |
105 | } | |
106 | ||
107 | ||
313fa17b A |
108 | // |
109 | // Whitelist pre-screen processing. | |
110 | // Whitelist-matching unsigned code is expensive since we have to generate a full code signature | |
111 | // just to see if we match a whitelist authority entry. This class generates a light(er)-weight | |
112 | // prescreen and matches it against a hint in an authority record generated from the (detached recorded) | |
113 | // code signature at time of whitelist recording. | |
114 | // This is just a heuristic to cheaply rule out guaranteed mismatches. When in doubt, we go ahead | |
115 | // and do the full work. | |
116 | // | |
117 | class WhitelistPrescreen { | |
118 | public: | |
119 | WhitelistPrescreen(SecStaticCodeRef code) | |
120 | : mRep(SecStaticCode::requiredStatic(code)->diskRep()) { } | |
121 | ||
122 | bool reject(const char *screen, const char *remarks); | |
123 | ||
124 | private: | |
125 | std::string create(char type, SHA1 &hash); | |
126 | ||
127 | RefPointer<DiskRep> mRep; // DiskRep representing the code | |
128 | std::string mScreen; // calculated screen (on demand) | |
129 | }; | |
130 | ||
131 | bool WhitelistPrescreen::reject(const char *screen, const char *remarks) | |
132 | { | |
133 | if (!screen) { // authority record has no screen to match - apply heuristic | |
134 | if (remarks && mRep->mainExecutablePath() != remarks) // not an allow record (or moved) | |
135 | return true; | |
136 | else | |
137 | return false; // can't rule out; proceed | |
138 | } | |
139 | ||
140 | if (mScreen.empty()) { | |
141 | if (CFRef<CFDataRef> info = mRep->component(cdInfoSlot)) { | |
142 | SHA1 hash; | |
143 | hash.update(CFDataGetBytePtr(info), CFDataGetLength(info)); | |
144 | mScreen = create('I', hash); | |
145 | } else if (mRep->mainExecutableImage()) { | |
146 | mScreen = "N"; | |
147 | } else { | |
148 | SHA1 hash; | |
149 | hashFileData(mRep->mainExecutablePath().c_str(), &hash); | |
150 | mScreen = create('M', hash); | |
151 | } | |
152 | } | |
153 | ||
154 | return screen != mScreen; | |
155 | } | |
156 | ||
157 | std::string WhitelistPrescreen::create(char type, SHA1 &hash) | |
158 | { | |
159 | SHA1::Digest digest; | |
160 | hash.finish(digest); | |
161 | char buffer[2*SHA1::digestLength + 2] = { type }; | |
162 | for (size_t n = 0; n < SHA1::digestLength; n++) | |
163 | sprintf(buffer + 1 + 2*n, "%02.2x", digest[n]); | |
164 | return buffer; | |
165 | } | |
166 | ||
167 | ||
b1ab9ed8 A |
168 | // |
169 | // Executable code. | |
170 | // Read from disk, evaluate properly, cache as indicated. The whole thing, so far. | |
171 | // | |
313fa17b A |
172 | void PolicyEngine::evaluateCode(CFURLRef path, AuthorityType type, SecAssessmentFlags flags, CFDictionaryRef context, CFMutableDictionaryRef result, |
173 | bool handleUnsignedCode /* = true */) | |
b1ab9ed8 A |
174 | { |
175 | FileQuarantine qtn(cfString(path).c_str()); | |
176 | if (qtn.flag(QTN_FLAG_HARD)) | |
177 | MacOSError::throwMe(errSecCSFileHardQuarantined); | |
178 | ||
179 | CFRef<SecStaticCodeRef> code; | |
180 | MacOSError::check(SecStaticCodeCreateWithPath(path, kSecCSDefaultFlags, &code.aref())); | |
313fa17b | 181 | OSStatus rc = noErr; // last validation error |
b1ab9ed8 A |
182 | |
183 | const SecCSFlags validationFlags = kSecCSEnforceRevocationChecks; | |
313fa17b A |
184 | |
185 | WhitelistPrescreen whitelistScreen(code); // pre-screening filter for whitelist pre-screening (only) | |
b1ab9ed8 A |
186 | |
187 | SQLite::Statement query(*this, | |
313fa17b | 188 | "SELECT allow, requirement, id, label, expires, flags, disabled, filter_unsigned, remarks FROM scan_authority" |
b1ab9ed8 A |
189 | " WHERE type = :type" |
190 | " ORDER BY priority DESC;"); | |
191 | query.bind(":type").integer(type); | |
192 | SQLite3::int64 latentID = 0; // first (highest priority) disabled matching ID | |
193 | std::string latentLabel; // ... and associated label, if any | |
194 | while (query.nextRow()) { | |
195 | bool allow = int(query[0]); | |
196 | const char *reqString = query[1]; | |
197 | SQLite3::int64 id = query[2]; | |
198 | const char *label = query[3]; | |
199 | double expires = query[4]; | |
200 | sqlite3_int64 ruleFlags = query[5]; | |
201 | SQLite3::int64 disabled = query[6]; | |
313fa17b A |
202 | const char *filter = query[7]; |
203 | const char *remarks = query[8]; | |
b1ab9ed8 A |
204 | |
205 | CFRef<SecRequirementRef> requirement; | |
206 | MacOSError::check(SecRequirementCreateWithString(CFTempString(reqString), kSecCSDefaultFlags, &requirement.aref())); | |
313fa17b | 207 | rc = SecStaticCodeCheckValidity(code, validationFlags, requirement); |
b1ab9ed8 | 208 | |
313fa17b A |
209 | // ad-hoc sign unsigned code, skip of Gatekeeper is off or the rule is disabled; but always do it for whitelist recording |
210 | if (rc == errSecCSUnsigned && handleUnsignedCode && (!(disabled || overrideAssessment()) || SYSPOLICY_RECORDER_MODE_ENABLED())) { | |
211 | if (!SYSPOLICY_RECORDER_MODE_ENABLED()) { | |
212 | // apply whitelist pre-screening to speed things up for non-matches | |
213 | if (ruleFlags & kAuthorityFlagDefault) // can't ever match standard rules with unsigned code | |
214 | continue; | |
215 | if (whitelistScreen.reject(filter, remarks)) // apply whitelist pre-filter | |
216 | continue; | |
217 | } | |
b1ab9ed8 A |
218 | try { |
219 | // ad-hoc sign the code and attach the signature | |
220 | CFRef<CFDataRef> signature = CFDataCreateMutable(NULL, 0); | |
221 | CFTemp<CFDictionaryRef> arguments("{%O=%O, %O=#N}", kSecCodeSignerDetached, signature.get(), kSecCodeSignerIdentity); | |
222 | CFRef<SecCodeSignerRef> signer; | |
223 | MacOSError::check(SecCodeSignerCreate(arguments, kSecCSDefaultFlags, &signer.aref())); | |
224 | MacOSError::check(SecCodeSignerAddSignature(signer, code, kSecCSDefaultFlags)); | |
225 | MacOSError::check(SecCodeSetDetachedSignature(code, signature, kSecCSDefaultFlags)); | |
313fa17b | 226 | |
b1ab9ed8 A |
227 | // if we're in GKE recording mode, save that signature and report its location |
228 | if (SYSPOLICY_RECORDER_MODE_ENABLED()) { | |
229 | int status = recorder_code_unable; // ephemeral signature (not recorded) | |
230 | if (geteuid() == 0) { | |
231 | CFRef<CFUUIDRef> uuid = CFUUIDCreate(NULL); | |
232 | std::string sigfile = RECORDER_DIR + cfStringRelease(CFUUIDCreateString(NULL, uuid)) + ".tsig"; | |
233 | try { | |
234 | UnixPlusPlus::AutoFileDesc fd(sigfile, O_WRONLY | O_CREAT); | |
235 | fd.write(CFDataGetBytePtr(signature), CFDataGetLength(signature)); | |
236 | status = recorder_code_adhoc; // recorded signature | |
237 | SYSPOLICY_RECORDER_MODE_ADHOC_PATH(cfString(path).c_str(), type, sigfile.c_str()); | |
238 | } catch (...) { } | |
239 | } | |
240 | ||
241 | // now report the D probe itself | |
242 | CFRef<CFDictionaryRef> info; | |
243 | MacOSError::check(SecCodeCopySigningInformation(code, kSecCSDefaultFlags, &info.aref())); | |
244 | CFDataRef cdhash = CFDataRef(CFDictionaryGetValue(info, kSecCodeInfoUnique)); | |
245 | SYSPOLICY_RECORDER_MODE(cfString(path).c_str(), type, "", | |
246 | cdhash ? CFDataGetBytePtr(cdhash) : NULL, status); | |
247 | } | |
248 | ||
249 | // rerun the validation to update state | |
250 | rc = SecStaticCodeCheckValidity(code, validationFlags | kSecCSBasicValidateOnly, requirement); | |
251 | } catch (...) { } | |
252 | } | |
253 | ||
254 | switch (rc) { | |
255 | case noErr: // well signed and satisfies requirement... | |
256 | break; // ... continue below | |
257 | case errSecCSSignatureFailed: | |
258 | if (!codeInvalidityExceptions(code, result)) { | |
259 | if (SYSPOLICY_ASSESS_OUTCOME_BROKEN_ENABLED()) | |
260 | SYSPOLICY_ASSESS_OUTCOME_BROKEN(cfString(path).c_str(), type, false); | |
261 | MacOSError::throwMe(rc); | |
262 | } | |
263 | if (SYSPOLICY_ASSESS_OUTCOME_BROKEN_ENABLED()) | |
264 | SYSPOLICY_ASSESS_OUTCOME_BROKEN(cfString(path).c_str(), type, true); | |
265 | // treat as unsigned to fix problems in the field | |
266 | case errSecCSUnsigned: | |
313fa17b A |
267 | if (handleUnsignedCode) { |
268 | cfadd(result, "{%O=#F}", kSecAssessmentAssessmentVerdict); | |
269 | addAuthority(result, "no usable signature"); | |
270 | } | |
b1ab9ed8 A |
271 | return; |
272 | case errSecCSReqFailed: // requirement missed, but otherwise okay | |
273 | continue; | |
274 | default: // broken in some way; all tests will fail like this so bail out | |
275 | MacOSError::throwMe(rc); | |
276 | } | |
277 | if (disabled) { | |
278 | if (latentID == 0) { | |
279 | latentID = id; | |
280 | if (label) | |
281 | latentLabel = label; | |
282 | } | |
283 | continue; // the loop | |
284 | } | |
285 | ||
286 | CFRef<CFDictionaryRef> info; // as needed | |
287 | if (flags & kSecAssessmentFlagRequestOrigin) { | |
288 | if (!info) | |
289 | MacOSError::check(SecCodeCopySigningInformation(code, kSecCSSigningInformation, &info.aref())); | |
290 | if (CFArrayRef chain = CFArrayRef(CFDictionaryGetValue(info, kSecCodeInfoCertificates))) | |
291 | setOrigin(chain, result); | |
292 | } | |
293 | if (!(ruleFlags & kAuthorityFlagInhibitCache) && !(flags & kSecAssessmentFlagNoCache)) { // cache inhibit | |
294 | if (!info) | |
295 | MacOSError::check(SecCodeCopySigningInformation(code, kSecCSSigningInformation, &info.aref())); | |
296 | if (SecTrustRef trust = SecTrustRef(CFDictionaryGetValue(info, kSecCodeInfoTrust))) { | |
297 | CFRef<CFDictionaryRef> xinfo; | |
298 | MacOSError::check(SecTrustCopyExtendedResult(trust, &xinfo.aref())); | |
299 | if (CFDateRef limit = CFDateRef(CFDictionaryGetValue(xinfo, kSecTrustExpirationDate))) { | |
300 | this->recordOutcome(code, allow, type, min(expires, dateToJulian(limit)), id); | |
301 | } | |
302 | } | |
303 | } | |
304 | if (allow) { | |
305 | if (SYSPOLICY_ASSESS_OUTCOME_ACCEPT_ENABLED()) { | |
306 | if (!info) | |
307 | MacOSError::check(SecCodeCopySigningInformation(code, kSecCSSigningInformation, &info.aref())); | |
308 | CFDataRef cdhash = CFDataRef(CFDictionaryGetValue(info, kSecCodeInfoUnique)); | |
309 | SYSPOLICY_ASSESS_OUTCOME_ACCEPT(cfString(path).c_str(), type, label, cdhash ? CFDataGetBytePtr(cdhash) : NULL); | |
310 | } | |
311 | } else { | |
312 | if (SYSPOLICY_ASSESS_OUTCOME_DENY_ENABLED() || SYSPOLICY_RECORDER_MODE_ENABLED()) { | |
313 | if (!info) | |
314 | MacOSError::check(SecCodeCopySigningInformation(code, kSecCSSigningInformation, &info.aref())); | |
315 | CFDataRef cdhash = CFDataRef(CFDictionaryGetValue(info, kSecCodeInfoUnique)); | |
316 | std::string cpath = cfString(path); | |
317 | const void *hashp = cdhash ? CFDataGetBytePtr(cdhash) : NULL; | |
318 | SYSPOLICY_ASSESS_OUTCOME_DENY(cpath.c_str(), type, label, hashp); | |
319 | SYSPOLICY_RECORDER_MODE(cpath.c_str(), type, label, hashp, recorder_code_untrusted); | |
320 | } | |
321 | } | |
322 | cfadd(result, "{%O=%B}", kSecAssessmentAssessmentVerdict, allow); | |
323 | addAuthority(result, label, id); | |
324 | return; | |
325 | } | |
313fa17b A |
326 | |
327 | if (rc == errSecCSUnsigned) { // skipped all applicable rules due to pre-screening | |
328 | cfadd(result, "{%O=#F}", kSecAssessmentAssessmentVerdict); | |
329 | addAuthority(result, "no usable signature"); | |
330 | return; | |
331 | } | |
b1ab9ed8 | 332 | |
313fa17b | 333 | // no applicable authority (but signed, perhaps temporarily). Deny by default |
b1ab9ed8 A |
334 | CFRef<CFDictionaryRef> info; |
335 | MacOSError::check(SecCodeCopySigningInformation(code, kSecCSSigningInformation, &info.aref())); | |
336 | if (flags & kSecAssessmentFlagRequestOrigin) { | |
337 | if (CFArrayRef chain = CFArrayRef(CFDictionaryGetValue(info, kSecCodeInfoCertificates))) | |
338 | setOrigin(chain, result); | |
339 | } | |
340 | if (SYSPOLICY_ASSESS_OUTCOME_DEFAULT_ENABLED() || SYSPOLICY_RECORDER_MODE_ENABLED()) { | |
341 | CFDataRef cdhash = CFDataRef(CFDictionaryGetValue(info, kSecCodeInfoUnique)); | |
342 | const void *hashp = cdhash ? CFDataGetBytePtr(cdhash) : NULL; | |
343 | std::string cpath = cfString(path); | |
344 | SYSPOLICY_ASSESS_OUTCOME_DEFAULT(cpath.c_str(), type, latentLabel.c_str(), hashp); | |
345 | SYSPOLICY_RECORDER_MODE(cpath.c_str(), type, latentLabel.c_str(), hashp, 0); | |
346 | } | |
347 | if (!(flags & kSecAssessmentFlagNoCache)) | |
348 | this->recordOutcome(code, false, type, this->julianNow() + NEGATIVE_HOLD, latentID); | |
349 | cfadd(result, "{%O=%B}", kSecAssessmentAssessmentVerdict, false); | |
350 | addAuthority(result, latentLabel.c_str(), latentID); | |
351 | } | |
352 | ||
353 | ||
354 | // | |
355 | // Installer archive. | |
356 | // Hybrid policy: If we detect an installer signature, use and validate that. | |
357 | // If we don't, check for a code signature instead. | |
358 | // | |
359 | void PolicyEngine::evaluateInstall(CFURLRef path, SecAssessmentFlags flags, CFDictionaryRef context, CFMutableDictionaryRef result) | |
360 | { | |
361 | const AuthorityType type = kAuthorityInstall; | |
362 | ||
363 | Xar xar(cfString(path).c_str()); | |
364 | if (!xar) { | |
365 | // follow the code signing path | |
366 | evaluateCode(path, type, flags, context, result); | |
367 | return; | |
368 | } | |
369 | ||
370 | SQLite3::int64 latentID = 0; // first (highest priority) disabled matching ID | |
371 | std::string latentLabel; // ... and associated label, if any | |
372 | if (!xar.isSigned()) { | |
373 | // unsigned xar | |
374 | if (SYSPOLICY_ASSESS_OUTCOME_UNSIGNED_ENABLED()) | |
375 | SYSPOLICY_ASSESS_OUTCOME_UNSIGNED(cfString(path).c_str(), type); | |
376 | cfadd(result, "{%O=%B}", kSecAssessmentAssessmentVerdict, false); | |
377 | addAuthority(result, "no usable signature"); | |
378 | return; | |
379 | } | |
380 | if (CFRef<CFArrayRef> certs = xar.copyCertChain()) { | |
381 | CFRef<CFTypeRef> policy = installerPolicy(); | |
382 | CFRef<SecTrustRef> trust; | |
383 | MacOSError::check(SecTrustCreateWithCertificates(certs, policy, &trust.aref())); | |
384 | // MacOSError::check(SecTrustSetAnchorCertificates(trust, cfEmptyArray())); // no anchors | |
385 | MacOSError::check(SecTrustSetOptions(trust, kSecTrustOptionAllowExpired | kSecTrustOptionImplicitAnchors)); | |
386 | ||
387 | SecTrustResultType trustResult; | |
388 | MacOSError::check(SecTrustEvaluate(trust, &trustResult)); | |
389 | CFRef<CFArrayRef> chain; | |
390 | CSSM_TP_APPLE_EVIDENCE_INFO *info; | |
391 | MacOSError::check(SecTrustGetResult(trust, &trustResult, &chain.aref(), &info)); | |
392 | ||
393 | if (flags & kSecAssessmentFlagRequestOrigin) | |
394 | setOrigin(chain, result); | |
395 | ||
396 | switch (trustResult) { | |
397 | case kSecTrustResultProceed: | |
398 | case kSecTrustResultUnspecified: | |
399 | break; | |
400 | default: | |
401 | { | |
402 | OSStatus rc; | |
403 | MacOSError::check(SecTrustGetCssmResultCode(trust, &rc)); | |
404 | MacOSError::throwMe(rc); | |
405 | } | |
406 | } | |
407 | ||
408 | SQLite::Statement query(*this, | |
409 | "SELECT allow, requirement, id, label, flags, disabled FROM scan_authority" | |
410 | " WHERE type = :type" | |
411 | " ORDER BY priority DESC;"); | |
412 | query.bind(":type").integer(type); | |
413 | while (query.nextRow()) { | |
414 | bool allow = int(query[0]); | |
415 | const char *reqString = query[1]; | |
416 | SQLite3::int64 id = query[2]; | |
417 | const char *label = query[3]; | |
418 | //sqlite_uint64 ruleFlags = query[4]; | |
419 | SQLite3::int64 disabled = query[5]; | |
420 | ||
421 | CFRef<SecRequirementRef> requirement; | |
422 | MacOSError::check(SecRequirementCreateWithString(CFTempString(reqString), kSecCSDefaultFlags, &requirement.aref())); | |
423 | switch (OSStatus rc = SecRequirementEvaluate(requirement, chain, NULL, kSecCSDefaultFlags)) { | |
424 | case noErr: // success | |
425 | break; | |
426 | case errSecCSReqFailed: // requirement missed, but otherwise okay | |
427 | continue; | |
428 | default: // broken in some way; all tests will fail like this so bail out | |
429 | MacOSError::throwMe(rc); | |
430 | } | |
431 | if (disabled) { | |
432 | if (latentID == 0) { | |
433 | latentID = id; | |
434 | if (label) | |
435 | latentLabel = label; | |
436 | } | |
437 | continue; // the loop | |
438 | } | |
439 | ||
440 | if (SYSPOLICY_ASSESS_OUTCOME_ACCEPT_ENABLED() || SYSPOLICY_ASSESS_OUTCOME_DENY_ENABLED()) { | |
441 | if (allow) | |
442 | SYSPOLICY_ASSESS_OUTCOME_ACCEPT(cfString(path).c_str(), type, label, NULL); | |
443 | else | |
444 | SYSPOLICY_ASSESS_OUTCOME_DENY(cfString(path).c_str(), type, label, NULL); | |
445 | } | |
446 | ||
447 | // not adding to the object cache - we could, but it's not likely to be worth it | |
448 | cfadd(result, "{%O=%B}", kSecAssessmentAssessmentVerdict, allow); | |
449 | addAuthority(result, label, id); | |
450 | return; | |
451 | } | |
452 | } | |
453 | if (SYSPOLICY_ASSESS_OUTCOME_DEFAULT_ENABLED()) | |
454 | SYSPOLICY_ASSESS_OUTCOME_DEFAULT(cfString(path).c_str(), type, latentLabel.c_str(), NULL); | |
455 | ||
456 | // no applicable authority. Deny by default | |
457 | cfadd(result, "{%O=#F}", kSecAssessmentAssessmentVerdict); | |
458 | addAuthority(result, latentLabel.c_str(), latentID); | |
459 | } | |
460 | ||
461 | ||
462 | // | |
463 | // Create a suitable policy array for verification of installer signatures. | |
464 | // | |
465 | static SecPolicyRef makeCRLPolicy() | |
466 | { | |
467 | CFRef<SecPolicyRef> policy; | |
468 | MacOSError::check(SecPolicyCopy(CSSM_CERT_X_509v3, &CSSMOID_APPLE_TP_REVOCATION_CRL, &policy.aref())); | |
469 | CSSM_APPLE_TP_CRL_OPTIONS options; | |
470 | memset(&options, 0, sizeof(options)); | |
471 | options.Version = CSSM_APPLE_TP_CRL_OPTS_VERSION; | |
472 | options.CrlFlags = CSSM_TP_ACTION_FETCH_CRL_FROM_NET | CSSM_TP_ACTION_CRL_SUFFICIENT; | |
473 | CSSM_DATA optData = { sizeof(options), (uint8 *)&options }; | |
474 | MacOSError::check(SecPolicySetValue(policy, &optData)); | |
475 | return policy.yield(); | |
476 | } | |
477 | ||
478 | static SecPolicyRef makeOCSPPolicy() | |
479 | { | |
480 | CFRef<SecPolicyRef> policy; | |
481 | MacOSError::check(SecPolicyCopy(CSSM_CERT_X_509v3, &CSSMOID_APPLE_TP_REVOCATION_OCSP, &policy.aref())); | |
482 | CSSM_APPLE_TP_OCSP_OPTIONS options; | |
483 | memset(&options, 0, sizeof(options)); | |
484 | options.Version = CSSM_APPLE_TP_OCSP_OPTS_VERSION; | |
485 | options.Flags = CSSM_TP_ACTION_OCSP_SUFFICIENT; | |
486 | CSSM_DATA optData = { sizeof(options), (uint8 *)&options }; | |
487 | MacOSError::check(SecPolicySetValue(policy, &optData)); | |
488 | return policy.yield(); | |
489 | } | |
490 | ||
491 | static CFTypeRef installerPolicy() | |
492 | { | |
493 | CFRef<SecPolicyRef> base = SecPolicyCreateBasicX509(); | |
494 | CFRef<SecPolicyRef> crl = makeCRLPolicy(); | |
495 | CFRef<SecPolicyRef> ocsp = makeOCSPPolicy(); | |
496 | return makeCFArray(3, base.get(), crl.get(), ocsp.get()); | |
497 | } | |
498 | ||
499 | ||
500 | // | |
501 | // LaunchServices-layer document open. | |
502 | // We don't cache those at present. If we ever do, we need to authenticate CoreServicesUIAgent as the source of its risk assessment. | |
503 | // | |
504 | void PolicyEngine::evaluateDocOpen(CFURLRef path, SecAssessmentFlags flags, CFDictionaryRef context, CFMutableDictionaryRef result) | |
505 | { | |
506 | if (context) { | |
507 | if (CFStringRef riskCategory = CFStringRef(CFDictionaryGetValue(context, kLSDownloadRiskCategoryKey))) { | |
508 | FileQuarantine qtn(cfString(path).c_str()); | |
509 | ||
510 | if (CFEqual(riskCategory, kLSRiskCategorySafe) | |
511 | || CFEqual(riskCategory, kLSRiskCategoryNeutral) | |
512 | || CFEqual(riskCategory, kLSRiskCategoryUnknown) | |
513 | || CFEqual(riskCategory, kLSRiskCategoryMayContainUnsafeExecutable)) { | |
514 | cfadd(result, "{%O=#T}", kSecAssessmentAssessmentVerdict); | |
515 | addAuthority(result, "_XProtect"); | |
516 | } else if (qtn.flag(QTN_FLAG_HARD)) { | |
517 | MacOSError::throwMe(errSecCSFileHardQuarantined); | |
518 | } else if (qtn.flag(QTN_FLAG_ASSESSMENT_OK)) { | |
519 | cfadd(result, "{%O=#T}", kSecAssessmentAssessmentVerdict); | |
520 | addAuthority(result, "Prior Assessment"); | |
313fa17b A |
521 | } else if (!overrideAssessment()) { // no need to do more work if we're off |
522 | try { | |
523 | evaluateCode(path, kAuthorityExecute, flags, context, result, false); | |
524 | } catch (...) { | |
525 | // some documents can't be code signed, so this may be quite benign | |
526 | } | |
c2a06e24 A |
527 | } |
528 | if (CFDictionaryGetValue(result, kSecAssessmentAssessmentVerdict) == NULL) { // no code signature to help us out | |
529 | cfadd(result, "{%O=#F}", kSecAssessmentAssessmentVerdict); | |
530 | addAuthority(result, "_XProtect"); | |
b1ab9ed8 A |
531 | } |
532 | addToAuthority(result, kLSDownloadRiskCategoryKey, riskCategory); | |
533 | return; | |
534 | } | |
535 | } | |
536 | // insufficient information from LS - deny by default | |
537 | cfadd(result, "{%O=#F}", kSecAssessmentAssessmentVerdict); | |
538 | addAuthority(result, "Insufficient Context"); | |
539 | } | |
540 | ||
541 | ||
542 | // | |
543 | // Result-creation helpers | |
544 | // | |
545 | void PolicyEngine::addAuthority(CFMutableDictionaryRef parent, const char *label, SQLite::int64 row, CFTypeRef cacheInfo) | |
546 | { | |
547 | CFRef<CFMutableDictionaryRef> auth = makeCFMutableDictionary(); | |
548 | if (label && label[0]) | |
549 | cfadd(auth, "{%O=%s}", kSecAssessmentAssessmentSource, label); | |
550 | if (row) | |
551 | CFDictionaryAddValue(auth, kSecAssessmentAssessmentAuthorityRow, CFTempNumber(row)); | |
552 | if (overrideAssessment()) | |
553 | CFDictionaryAddValue(auth, kSecAssessmentAssessmentAuthorityOverride, kDisabledOverride); | |
554 | if (cacheInfo) | |
555 | CFDictionaryAddValue(auth, kSecAssessmentAssessmentFromCache, cacheInfo); | |
556 | CFDictionaryAddValue(parent, kSecAssessmentAssessmentAuthority, auth); | |
557 | } | |
558 | ||
559 | void PolicyEngine::addToAuthority(CFMutableDictionaryRef parent, CFStringRef key, CFTypeRef value) | |
560 | { | |
561 | CFMutableDictionaryRef authority = CFMutableDictionaryRef(CFDictionaryGetValue(parent, kSecAssessmentAssessmentAuthority)); | |
562 | assert(authority); | |
563 | CFDictionaryAddValue(authority, key, value); | |
564 | } | |
565 | ||
566 | ||
567 | // | |
568 | // Add a rule to the policy database | |
569 | // | |
570 | CFDictionaryRef PolicyEngine::add(CFTypeRef inTarget, AuthorityType type, SecAssessmentFlags flags, CFDictionaryRef context) | |
571 | { | |
572 | // default type to execution | |
573 | if (type == kAuthorityInvalid) | |
574 | type = kAuthorityExecute; | |
575 | ||
576 | authorizeUpdate(flags, context); | |
577 | CFDictionary ctx(context, errSecCSInvalidAttributeValues); | |
578 | CFCopyRef<CFTypeRef> target = inTarget; | |
579 | CFRef<CFDataRef> bookmark = NULL; | |
313fa17b | 580 | std::string filter_unsigned; |
b1ab9ed8 A |
581 | |
582 | switch (type) { | |
583 | case kAuthorityExecute: | |
313fa17b | 584 | normalizeTarget(target, ctx, &filter_unsigned); |
b1ab9ed8 A |
585 | // bookmarks are untrusted and just a hint to callers |
586 | bookmark = ctx.get<CFDataRef>(kSecAssessmentRuleKeyBookmark); | |
587 | break; | |
588 | case kAuthorityInstall: | |
589 | if (inTarget && CFGetTypeID(inTarget) == CFURLGetTypeID()) { | |
590 | // no good way to turn an installer file into a requirement. Pretend to succeeed so caller proceeds | |
591 | return cfmake<CFDictionaryRef>("{%O=%O}", kSecAssessmentAssessmentAuthorityOverride, CFSTR("virtual install")); | |
592 | } | |
593 | break; | |
594 | case kAuthorityOpenDoc: | |
595 | // handle document-open differently: use quarantine flags for whitelisting | |
596 | if (!target || CFGetTypeID(target) != CFURLGetTypeID()) // can only "add" file paths | |
597 | MacOSError::throwMe(errSecCSInvalidObjectRef); | |
598 | try { | |
599 | std::string spath = cfString(target.as<CFURLRef>()); | |
600 | FileQuarantine qtn(spath.c_str()); | |
601 | qtn.setFlag(QTN_FLAG_ASSESSMENT_OK); | |
602 | qtn.applyTo(spath.c_str()); | |
603 | } catch (const CommonError &error) { | |
604 | // could not set quarantine flag - report qualified success | |
605 | return cfmake<CFDictionaryRef>("{%O=%O,'assessment:error'=%d}", | |
606 | kSecAssessmentAssessmentAuthorityOverride, CFSTR("error setting quarantine"), error.osStatus()); | |
607 | } catch (...) { | |
608 | return cfmake<CFDictionaryRef>("{%O=%O}", kSecAssessmentAssessmentAuthorityOverride, CFSTR("unable to set quarantine")); | |
609 | } | |
610 | return NULL; | |
611 | } | |
612 | ||
613 | // if we now have anything else, we're busted | |
614 | if (!target || CFGetTypeID(target) != SecRequirementGetTypeID()) | |
615 | MacOSError::throwMe(errSecCSInvalidObjectRef); | |
616 | ||
617 | double priority = 0; | |
618 | string label; | |
619 | bool allow = true; | |
620 | double expires = never; | |
621 | string remarks; | |
622 | ||
623 | if (CFNumberRef pri = ctx.get<CFNumberRef>(kSecAssessmentUpdateKeyPriority)) | |
624 | CFNumberGetValue(pri, kCFNumberDoubleType, &priority); | |
625 | if (CFStringRef lab = ctx.get<CFStringRef>(kSecAssessmentUpdateKeyLabel)) | |
626 | label = cfString(lab); | |
627 | if (CFDateRef time = ctx.get<CFDateRef>(kSecAssessmentUpdateKeyExpires)) | |
628 | // we're using Julian dates here; convert from CFDate | |
629 | expires = dateToJulian(time); | |
630 | if (CFBooleanRef allowing = ctx.get<CFBooleanRef>(kSecAssessmentUpdateKeyAllow)) | |
631 | allow = allowing == kCFBooleanTrue; | |
632 | if (CFStringRef rem = ctx.get<CFStringRef>(kSecAssessmentUpdateKeyRemarks)) | |
633 | remarks = cfString(rem); | |
634 | ||
635 | CFRef<CFStringRef> requirementText; | |
636 | MacOSError::check(SecRequirementCopyString(target.as<SecRequirementRef>(), kSecCSDefaultFlags, &requirementText.aref())); | |
637 | SQLite::Transaction xact(*this, SQLite3::Transaction::deferred, "add_rule"); | |
638 | SQLite::Statement insert(*this, | |
313fa17b A |
639 | "INSERT INTO authority (type, allow, requirement, priority, label, expires, filter_unsigned, remarks)" |
640 | " VALUES (:type, :allow, :requirement, :priority, :label, :expires, :filter_unsigned, :remarks);"); | |
b1ab9ed8 A |
641 | insert.bind(":type").integer(type); |
642 | insert.bind(":allow").integer(allow); | |
643 | insert.bind(":requirement") = requirementText.get(); | |
644 | insert.bind(":priority") = priority; | |
645 | if (!label.empty()) | |
646 | insert.bind(":label") = label; | |
647 | insert.bind(":expires") = expires; | |
313fa17b | 648 | insert.bind(":filter_unsigned") = filter_unsigned.empty() ? NULL : filter_unsigned.c_str(); |
b1ab9ed8 A |
649 | if (!remarks.empty()) |
650 | insert.bind(":remarks") = remarks; | |
651 | insert.execute(); | |
652 | SQLite::int64 newRow = this->lastInsert(); | |
653 | if (bookmark) { | |
654 | SQLite::Statement bi(*this, "INSERT INTO bookmarkhints (bookmark, authority) VALUES (:bookmark, :authority)"); | |
655 | bi.bind(":bookmark") = CFDataRef(bookmark); | |
656 | bi.bind(":authority").integer(newRow); | |
657 | bi.execute(); | |
658 | } | |
659 | this->purgeObjects(priority); | |
660 | xact.commit(); | |
661 | notify_post(kNotifySecAssessmentUpdate); | |
662 | return cfmake<CFDictionaryRef>("{%O=%d}", kSecAssessmentUpdateKeyRow, newRow); | |
663 | } | |
664 | ||
665 | ||
666 | CFDictionaryRef PolicyEngine::remove(CFTypeRef target, AuthorityType type, SecAssessmentFlags flags, CFDictionaryRef context) | |
667 | { | |
668 | if (type == kAuthorityOpenDoc) { | |
669 | // handle document-open differently: use quarantine flags for whitelisting | |
670 | authorizeUpdate(flags, context); | |
671 | if (!target || CFGetTypeID(target) != CFURLGetTypeID()) | |
672 | MacOSError::throwMe(errSecCSInvalidObjectRef); | |
673 | std::string spath = cfString(CFURLRef(target)).c_str(); | |
674 | FileQuarantine qtn(spath.c_str()); | |
675 | qtn.clearFlag(QTN_FLAG_ASSESSMENT_OK); | |
676 | qtn.applyTo(spath.c_str()); | |
677 | return NULL; | |
678 | } | |
679 | return manipulateRules("DELETE FROM authority", target, type, flags, context); | |
680 | } | |
681 | ||
682 | CFDictionaryRef PolicyEngine::enable(CFTypeRef target, AuthorityType type, SecAssessmentFlags flags, CFDictionaryRef context) | |
683 | { | |
684 | return manipulateRules("UPDATE authority SET disabled = 0", target, type, flags, context); | |
685 | } | |
686 | ||
687 | CFDictionaryRef PolicyEngine::disable(CFTypeRef target, AuthorityType type, SecAssessmentFlags flags, CFDictionaryRef context) | |
688 | { | |
689 | return manipulateRules("UPDATE authority SET disabled = 1", target, type, flags, context); | |
690 | } | |
691 | ||
692 | CFDictionaryRef PolicyEngine::find(CFTypeRef target, AuthorityType type, SecAssessmentFlags flags, CFDictionaryRef context) | |
693 | { | |
694 | SQLite::Statement query(*this); | |
695 | selectRules(query, "SELECT scan_authority.id, scan_authority.type, scan_authority.requirement, scan_authority.allow, scan_authority.label, scan_authority.priority, scan_authority.remarks, scan_authority.expires, scan_authority.disabled, bookmarkhints.bookmark FROM scan_authority LEFT OUTER JOIN bookmarkhints ON scan_authority.id = bookmarkhints.authority", | |
696 | "scan_authority", target, type, flags, context, | |
697 | " ORDER BY priority DESC"); | |
698 | CFRef<CFMutableArrayRef> found = makeCFMutableArray(0); | |
699 | while (query.nextRow()) { | |
700 | SQLite::int64 id = query[0]; | |
701 | int type = int(query[1]); | |
702 | const char *requirement = query[2]; | |
703 | int allow = int(query[3]); | |
704 | const char *label = query[4]; | |
705 | double priority = query[5]; | |
706 | const char *remarks = query[6]; | |
707 | double expires = query[7]; | |
708 | int disabled = int(query[8]); | |
709 | CFRef<CFDataRef> bookmark = query[9].data(); | |
710 | CFRef<CFMutableDictionaryRef> rule = makeCFMutableDictionary(5, | |
711 | kSecAssessmentRuleKeyID, CFTempNumber(id).get(), | |
712 | kSecAssessmentRuleKeyType, CFRef<CFStringRef>(typeNameFor(type)).get(), | |
713 | kSecAssessmentRuleKeyRequirement, CFTempString(requirement).get(), | |
714 | kSecAssessmentRuleKeyAllow, allow ? kCFBooleanTrue : kCFBooleanFalse, | |
715 | kSecAssessmentRuleKeyPriority, CFTempNumber(priority).get() | |
716 | ); | |
717 | if (label) | |
718 | CFDictionaryAddValue(rule, kSecAssessmentRuleKeyLabel, CFTempString(label)); | |
719 | if (remarks) | |
720 | CFDictionaryAddValue(rule, kSecAssessmentRuleKeyRemarks, CFTempString(remarks)); | |
721 | if (expires != never) | |
722 | CFDictionaryAddValue(rule, kSecAssessmentRuleKeyExpires, CFRef<CFDateRef>(julianToDate(expires))); | |
723 | if (disabled) | |
724 | CFDictionaryAddValue(rule, kSecAssessmentRuleKeyDisabled, CFTempNumber(disabled)); | |
725 | if (bookmark) | |
726 | CFDictionaryAddValue(rule, kSecAssessmentRuleKeyBookmark, bookmark); | |
727 | CFArrayAppendValue(found, rule); | |
728 | } | |
729 | if (CFArrayGetCount(found) == 0) | |
730 | MacOSError::throwMe(errSecCSNoMatches); | |
731 | return cfmake<CFDictionaryRef>("{%O=%O}", kSecAssessmentUpdateKeyFound, found.get()); | |
732 | } | |
733 | ||
734 | ||
735 | CFDictionaryRef PolicyEngine::update(CFTypeRef target, SecAssessmentFlags flags, CFDictionaryRef context) | |
736 | { | |
313fa17b A |
737 | // update GKE |
738 | installExplicitSet(gkeAuthFile, gkeSigsFile); | |
739 | ||
b1ab9ed8 A |
740 | AuthorityType type = typeFor(context, kAuthorityInvalid); |
741 | CFStringRef edit = CFStringRef(CFDictionaryGetValue(context, kSecAssessmentContextKeyUpdate)); | |
742 | CFDictionaryRef result; | |
743 | if (CFEqual(edit, kSecAssessmentUpdateOperationAdd)) | |
744 | result = this->add(target, type, flags, context); | |
745 | else if (CFEqual(edit, kSecAssessmentUpdateOperationRemove)) | |
746 | result = this->remove(target, type, flags, context); | |
747 | else if (CFEqual(edit, kSecAssessmentUpdateOperationEnable)) | |
748 | result = this->enable(target, type, flags, context); | |
749 | else if (CFEqual(edit, kSecAssessmentUpdateOperationDisable)) | |
750 | result = this->disable(target, type, flags, context); | |
751 | else if (CFEqual(edit, kSecAssessmentUpdateOperationFind)) | |
752 | result = this->find(target, type, flags, context); | |
753 | else | |
754 | MacOSError::throwMe(errSecCSInvalidAttributeValues); | |
755 | if (result == NULL) | |
756 | result = makeCFDictionary(0); // success, no details | |
757 | return result; | |
758 | } | |
759 | ||
760 | ||
761 | // | |
762 | // Construct and prepare an SQL query on the authority table, operating on some set of existing authority records. | |
763 | // In essence, this appends a suitable WHERE clause to the stanza passed and prepares it on the statement given. | |
764 | // | |
765 | void PolicyEngine::selectRules(SQLite::Statement &action, std::string phrase, std::string table, | |
766 | CFTypeRef inTarget, AuthorityType type, SecAssessmentFlags flags, CFDictionaryRef context, std::string suffix /* = "" */) | |
767 | { | |
768 | CFDictionary ctx(context, errSecCSInvalidAttributeValues); | |
769 | CFCopyRef<CFTypeRef> target = inTarget; | |
313fa17b A |
770 | std::string filter_unsigned; // ignored; used just to trigger ad-hoc signing |
771 | normalizeTarget(target, ctx, &filter_unsigned); | |
b1ab9ed8 A |
772 | |
773 | string label; | |
774 | if (CFStringRef lab = ctx.get<CFStringRef>(kSecAssessmentUpdateKeyLabel)) | |
775 | label = cfString(CFStringRef(lab)); | |
776 | ||
777 | if (!target) { | |
778 | if (label.empty()) { | |
779 | if (type == kAuthorityInvalid) { | |
780 | action.query(phrase + suffix); | |
781 | } else { | |
782 | action.query(phrase + " WHERE " + table + ".type = :type" + suffix); | |
783 | action.bind(":type").integer(type); | |
784 | } | |
785 | } else { // have label | |
786 | if (type == kAuthorityInvalid) { | |
787 | action.query(phrase + " WHERE " + table + ".label = :label" + suffix); | |
788 | } else { | |
789 | action.query(phrase + " WHERE " + table + ".type = :type AND " + table + ".label = :label" + suffix); | |
790 | action.bind(":type").integer(type); | |
791 | } | |
792 | action.bind(":label") = label; | |
793 | } | |
794 | } else if (CFGetTypeID(target) == CFNumberGetTypeID()) { | |
795 | action.query(phrase + " WHERE " + table + ".id = :id" + suffix); | |
796 | action.bind(":id").integer(cfNumber<uint64_t>(target.as<CFNumberRef>())); | |
797 | } else if (CFGetTypeID(target) == SecRequirementGetTypeID()) { | |
798 | if (type == kAuthorityInvalid) | |
799 | type = kAuthorityExecute; | |
800 | CFRef<CFStringRef> requirementText; | |
801 | MacOSError::check(SecRequirementCopyString(target.as<SecRequirementRef>(), kSecCSDefaultFlags, &requirementText.aref())); | |
802 | action.query(phrase + " WHERE " + table + ".type = :type AND " + table + ".requirement = :requirement" + suffix); | |
803 | action.bind(":type").integer(type); | |
804 | action.bind(":requirement") = requirementText.get(); | |
805 | } else | |
806 | MacOSError::throwMe(errSecCSInvalidObjectRef); | |
807 | } | |
808 | ||
809 | ||
810 | // | |
811 | // Execute an atomic change to existing records in the authority table. | |
812 | // | |
813 | CFDictionaryRef PolicyEngine::manipulateRules(const std::string &stanza, | |
814 | CFTypeRef inTarget, AuthorityType type, SecAssessmentFlags flags, CFDictionaryRef context) | |
815 | { | |
816 | SQLite::Transaction xact(*this, SQLite3::Transaction::deferred, "rule_change"); | |
817 | SQLite::Statement action(*this); | |
818 | authorizeUpdate(flags, context); | |
819 | selectRules(action, stanza, "authority", inTarget, type, flags, context); | |
820 | action.execute(); | |
821 | unsigned int changes = this->changes(); // latch change count | |
822 | // We MUST purge objects with priority <= MAX(priority of any changed rules); | |
823 | // but for now we just get lazy and purge them ALL. | |
824 | if (changes) { | |
825 | this->purgeObjects(1.0E100); | |
826 | xact.commit(); | |
827 | notify_post(kNotifySecAssessmentUpdate); | |
828 | return cfmake<CFDictionaryRef>("{%O=%d}", kSecAssessmentUpdateKeyCount, changes); | |
829 | } | |
830 | // no change; return an error | |
831 | MacOSError::throwMe(errSecCSNoMatches); | |
832 | } | |
833 | ||
834 | ||
835 | // | |
836 | // Fill in extra information about the originator of cryptographic credentials found - if any | |
837 | // | |
838 | void PolicyEngine::setOrigin(CFArrayRef chain, CFMutableDictionaryRef result) | |
839 | { | |
840 | if (chain) | |
841 | if (CFArrayGetCount(chain) > 0) | |
842 | if (SecCertificateRef leaf = SecCertificateRef(CFArrayGetValueAtIndex(chain, 0))) | |
843 | if (CFStringRef summary = SecCertificateCopyLongDescription(NULL, leaf, NULL)) { | |
844 | CFDictionarySetValue(result, kSecAssessmentAssessmentOriginator, summary); | |
845 | CFRelease(summary); | |
846 | } | |
847 | } | |
848 | ||
849 | ||
850 | // | |
851 | // Take an assessment outcome and record it in the object cache | |
852 | // | |
853 | void PolicyEngine::recordOutcome(SecStaticCodeRef code, bool allow, AuthorityType type, double expires, SQLite::int64 authority) | |
854 | { | |
855 | CFRef<CFDictionaryRef> info; | |
856 | MacOSError::check(SecCodeCopySigningInformation(code, kSecCSDefaultFlags, &info.aref())); | |
857 | CFDataRef cdHash = CFDataRef(CFDictionaryGetValue(info, kSecCodeInfoUnique)); | |
858 | assert(cdHash); // was signed | |
859 | CFRef<CFURLRef> path; | |
860 | MacOSError::check(SecCodeCopyPath(code, kSecCSDefaultFlags, &path.aref())); | |
861 | assert(expires); | |
862 | SQLite::Transaction xact(*this, SQLite3::Transaction::deferred, "caching"); | |
863 | SQLite::Statement insert(*this, | |
864 | "INSERT OR REPLACE INTO object (type, allow, hash, expires, path, authority)" | |
865 | " VALUES (:type, :allow, :hash, :expires, :path," | |
866 | " CASE :authority WHEN 0 THEN (SELECT id FROM authority WHERE label = 'No Matching Rule') ELSE :authority END" | |
867 | " );"); | |
868 | insert.bind(":type").integer(type); | |
869 | insert.bind(":allow").integer(allow); | |
870 | insert.bind(":hash") = cdHash; | |
871 | insert.bind(":expires") = expires; | |
872 | insert.bind(":path") = cfString(path); | |
873 | insert.bind(":authority").integer(authority); | |
874 | insert.execute(); | |
875 | xact.commit(); | |
876 | } | |
877 | ||
878 | ||
879 | // | |
880 | // Perform update authorization processing. | |
881 | // Throws an exception if authorization is denied. | |
882 | // | |
883 | static void authorizeUpdate(SecAssessmentFlags flags, CFDictionaryRef context) | |
884 | { | |
885 | AuthorizationRef authorization = NULL; | |
886 | ||
887 | if (context) | |
888 | if (CFTypeRef authkey = CFDictionaryGetValue(context, kSecAssessmentUpdateKeyAuthorization)) | |
889 | if (CFGetTypeID(authkey) == CFDataGetTypeID()) { | |
890 | CFDataRef authdata = CFDataRef(authkey); | |
891 | MacOSError::check(AuthorizationCreateFromExternalForm((AuthorizationExternalForm *)CFDataGetBytePtr(authdata), &authorization)); | |
892 | } | |
893 | if (authorization == NULL) | |
894 | MacOSError::check(AuthorizationCreate(NULL, NULL, kAuthorizationFlagDefaults, &authorization)); | |
895 | ||
896 | AuthorizationItem right[] = { | |
897 | { "com.apple.security.assessment.update", 0, NULL, 0 } | |
898 | }; | |
899 | AuthorizationRights rights = { sizeof(right) / sizeof(right[0]), right }; | |
900 | MacOSError::check(AuthorizationCopyRights(authorization, &rights, NULL, | |
901 | kAuthorizationFlagExtendRights | kAuthorizationFlagInteractionAllowed, NULL)); | |
902 | ||
903 | MacOSError::check(AuthorizationFree(authorization, kAuthorizationFlagDefaults)); | |
904 | } | |
905 | ||
906 | ||
907 | // | |
908 | // Perform common argument normalizations for update operations | |
909 | // | |
313fa17b | 910 | static void normalizeTarget(CFRef<CFTypeRef> &target, CFDictionary &context, std::string *signUnsigned) |
b1ab9ed8 A |
911 | { |
912 | // turn CFURLs into (designated) SecRequirements | |
913 | if (target && CFGetTypeID(target) == CFURLGetTypeID()) { | |
914 | CFRef<SecStaticCodeRef> code; | |
915 | MacOSError::check(SecStaticCodeCreateWithPath(target.as<CFURLRef>(), kSecCSDefaultFlags, &code.aref())); | |
916 | switch (OSStatus rc = SecCodeCopyDesignatedRequirement(code, kSecCSDefaultFlags, (SecRequirementRef *)&target.aref())) { | |
917 | case noErr: { | |
918 | // use the *default* DR to avoid unreasonably wide DRs opening up Gatekeeper to attack | |
919 | CFRef<CFDictionaryRef> info; | |
920 | MacOSError::check(SecCodeCopySigningInformation(code, kSecCSRequirementInformation, &info.aref())); | |
921 | target = CFDictionaryGetValue(info, kSecCodeInfoImplicitDesignatedRequirement); | |
922 | } | |
923 | break; | |
924 | case errSecCSUnsigned: | |
925 | if (signUnsigned) { | |
926 | // Ad-hoc sign the code temporarily so we can get its code requirement | |
927 | CFRef<CFDataRef> signature = CFDataCreateMutable(NULL, 0); | |
928 | CFRef<SecCodeSignerRef> signer; | |
929 | CFTemp<CFDictionaryRef> arguments("{%O=%O, %O=#N}", kSecCodeSignerDetached, signature.get(), kSecCodeSignerIdentity); | |
930 | MacOSError::check(SecCodeSignerCreate(arguments, kSecCSDefaultFlags, &signer.aref())); | |
931 | MacOSError::check(SecCodeSignerAddSignature(signer, code, kSecCSDefaultFlags)); | |
932 | MacOSError::check(SecCodeSetDetachedSignature(code, signature, kSecCSDefaultFlags)); | |
933 | MacOSError::check(SecCodeCopyDesignatedRequirement(code, kSecCSDefaultFlags, (SecRequirementRef *)&target.aref())); | |
313fa17b A |
934 | CFRef<CFDictionaryRef> info; |
935 | MacOSError::check(SecCodeCopySigningInformation(code, kSecCSInternalInformation, &info.aref())); | |
936 | if (CFDataRef cdData = CFDataRef(CFDictionaryGetValue(info, kSecCodeInfoCodeDirectory))) | |
937 | *signUnsigned = ((const CodeDirectory *)CFDataGetBytePtr(cdData))->screeningCode(); | |
b1ab9ed8 A |
938 | break; |
939 | } | |
940 | MacOSError::check(rc); | |
941 | case errSecCSSignatureFailed: | |
942 | // recover certain cases of broken signatures (well, try) | |
943 | if (codeInvalidityExceptions(code, NULL)) { | |
944 | // Ad-hoc sign the code in place (requiring a writable subject). This requires root privileges. | |
945 | CFRef<SecCodeSignerRef> signer; | |
946 | CFTemp<CFDictionaryRef> arguments("{%O=#N}", kSecCodeSignerIdentity); | |
947 | MacOSError::check(SecCodeSignerCreate(arguments, kSecCSDefaultFlags, &signer.aref())); | |
948 | MacOSError::check(SecCodeSignerAddSignature(signer, code, kSecCSDefaultFlags)); | |
949 | MacOSError::check(SecCodeCopyDesignatedRequirement(code, kSecCSDefaultFlags, (SecRequirementRef *)&target.aref())); | |
950 | break; | |
951 | } | |
952 | MacOSError::check(rc); | |
953 | default: | |
954 | MacOSError::check(rc); | |
955 | } | |
956 | if (context.get(kSecAssessmentUpdateKeyRemarks) == NULL) { | |
957 | // no explicit remarks; add one with the path | |
958 | CFRef<CFURLRef> path; | |
959 | MacOSError::check(SecCodeCopyPath(code, kSecCSDefaultFlags, &path.aref())); | |
960 | CFMutableDictionaryRef dict = makeCFMutableDictionary(context.get()); | |
961 | CFDictionaryAddValue(dict, kSecAssessmentUpdateKeyRemarks, CFTempString(cfString(path))); | |
962 | context.take(dict); | |
963 | } | |
964 | } | |
965 | } | |
966 | ||
967 | ||
968 | // | |
969 | // Process special overrides for invalidly signed code. | |
970 | // This is the (hopefully minimal) concessions we make to keep hurting our customers | |
971 | // for our own prior mistakes... | |
972 | // | |
973 | static bool codeInvalidityExceptions(SecStaticCodeRef code, CFMutableDictionaryRef result) | |
974 | { | |
975 | if (OSAIsRecognizedExecutableURL) { | |
976 | CFRef<CFDictionaryRef> info; | |
977 | MacOSError::check(SecCodeCopySigningInformation(code, kSecCSDefaultFlags, &info.aref())); | |
978 | if (CFURLRef executable = CFURLRef(CFDictionaryGetValue(info, kSecCodeInfoMainExecutable))) { | |
979 | SInt32 error; | |
980 | if (OSAIsRecognizedExecutableURL(executable, &error)) { | |
981 | if (result) | |
982 | CFDictionaryAddValue(result, | |
983 | kSecAssessmentAssessmentAuthorityOverride, CFSTR("ignoring known invalid applet signature")); | |
984 | return true; | |
985 | } | |
986 | } | |
987 | } | |
988 | return false; | |
989 | } | |
990 | ||
991 | ||
992 | } // end namespace CodeSigning | |
993 | } // end namespace Security |