]> git.saurik.com Git - apple/security.git/blob - OSX/libsecurity_codesigning/lib/policyengine.cpp
Security-58286.70.7.tar.gz
[apple/security.git] / OSX / libsecurity_codesigning / lib / policyengine.cpp
1 /*
2 * Copyright (c) 2011-2016 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
38 #include "diskrep.h"
39 #include "codedirectory.h"
40 #include "csutilities.h"
41 #include "StaticCode.h"
42
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);
66 static bool codeInvalidityExceptions(SecStaticCodeRef code, CFMutableDictionaryRef result);
67 static CFTypeRef installerPolicy() CF_RETURNS_RETAINED;
68
69
70 //
71 // Core structure
72 //
73 PolicyEngine::PolicyEngine()
74 : PolicyDatabase(NULL, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE)
75 {
76 try {
77 mOpaqueWhitelist = new OpaqueWhitelist();
78 } catch (...) {
79 mOpaqueWhitelist = NULL;
80 secerror("Failed opening the gkopaque database.");
81 }
82 }
83
84 PolicyEngine::~PolicyEngine()
85 {
86 delete mOpaqueWhitelist;
87 }
88
89
90 //
91 // Top-level evaluation driver
92 //
93 void PolicyEngine::evaluate(CFURLRef path, AuthorityType type, SecAssessmentFlags flags, CFDictionaryRef context, CFMutableDictionaryRef result)
94 {
95 // update GKE
96 installExplicitSet(gkeAuthFile, gkeSigsFile);
97
98 // find the global evaluation manager
99 EvaluationManager *evaluationManager = EvaluationManager::globalManager();
100
101 // perform the evaluation
102 EvaluationTask *evaluationTask = evaluationManager->evaluationTask(this, path, type, flags, context, result);
103 evaluationManager->finalizeTask(evaluationTask, flags, result);
104
105 // if rejected, reset the automatic rearm timer
106 if (CFDictionaryGetValue(result, kSecAssessmentAssessmentVerdict) == kCFBooleanFalse)
107 resetRearmTimer("reject");
108 }
109
110
111 //
112 // Create GKE whitelist filter screens.
113 // These are strings that are used to determine quickly whether unsigned code may
114 // have a GKE-style whitelist entry in the authority database. The idea is to make
115 // up a decent hash quickly.
116 //
117 // Note: We continue to use SHA1 here for compatibility of existing GKE entries.
118 // These are a prescreen, backed up by code signature checks later on. Use of SHA1 here is not a security problem.
119 //
120 static std::string createWhitelistScreen(char type, const Byte *digest, size_t length)
121 {
122 char buffer[2*length + 2];
123 buffer[0] = type;
124 for (size_t n = 0; n < length; n++)
125 sprintf(buffer + 1 + 2*n, "%02.2x", digest[n]);
126 return buffer;
127 }
128
129 static std::string createWhitelistScreen(SecStaticCodeRef code)
130 {
131 DiskRep *rep = SecStaticCode::requiredStatic(code)->diskRep();
132 std::string screen;
133 if (CFRef<CFDataRef> info = rep->component(cdInfoSlot)) {
134 // has an Info.plist - hash it
135 SHA1 hash;
136 hash.update(CFDataGetBytePtr(info), CFDataGetLength(info));
137 SHA1::Digest digest;
138 hash.finish(digest);
139 return createWhitelistScreen('I', digest, sizeof(digest));
140 } else if (CFRef<CFDataRef> repSpecific = rep->component(cdRepSpecificSlot)) {
141 // has a rep-specific slot - hash that (this catches disk images cheaply)
142 // got invented after SHA-1 deprecation, so we'll use SHA256, which is the new default
143 CCHashInstance hash(kCCDigestSHA256);
144 hash.update(CFDataGetBytePtr(repSpecific), CFDataGetLength(repSpecific));
145 Byte digest[256/8];
146 hash.finish(digest);
147 return createWhitelistScreen('R', digest, sizeof(digest));
148 } else if (rep->mainExecutableImage()) {
149 // stand-alone Mach-O executables are always candidates
150 return "N";
151 } else {
152 // if everything else fails, hash the (single) file
153 SHA1 hash;
154 hashFileData(rep->mainExecutablePath().c_str(), &hash);
155 SHA1::Digest digest;
156 hash.finish(digest);
157 return createWhitelistScreen('M', digest, sizeof(digest));
158 }
159 }
160
161
162 void PolicyEngine::evaluateCodeItem(SecStaticCodeRef code, CFURLRef path, AuthorityType type, SecAssessmentFlags flags, bool nested, CFMutableDictionaryRef result)
163 {
164
165 SQLite::Statement query(*this,
166 "SELECT allow, requirement, id, label, expires, flags, disabled, filter_unsigned, remarks FROM scan_authority"
167 " WHERE type = :type"
168 " ORDER BY priority DESC;");
169 query.bind(":type").integer(type);
170
171 SQLite3::int64 latentID = 0; // first (highest priority) disabled matching ID
172 std::string latentLabel; // ... and associated label, if any
173
174 secdebug("gk", "evaluateCodeItem type=%d flags=0x%x nested=%d path=%s", type, int(flags), nested, cfString(path).c_str());
175 while (query.nextRow()) {
176 bool allow = int(query[0]);
177 const char *reqString = query[1];
178 SQLite3::int64 id = query[2];
179 const char *label = query[3];
180 double expires = query[4];
181 sqlite3_int64 ruleFlags = query[5];
182 SQLite3::int64 disabled = query[6];
183 // const char *filter = query[7];
184 // const char *remarks = query[8];
185
186 secdebug("gk", "considering rule %d(%s) requirement %s", int(id), label ? label : "UNLABELED", reqString);
187 CFRef<SecRequirementRef> requirement;
188 MacOSError::check(SecRequirementCreateWithString(CFTempString(reqString), kSecCSDefaultFlags, &requirement.aref()));
189 switch (OSStatus rc = SecStaticCodeCheckValidity(code, kSecCSBasicValidateOnly | kSecCSCheckGatekeeperArchitectures, requirement)) {
190 case errSecSuccess:
191 break; // rule match; process below
192 case errSecCSReqFailed:
193 continue; // rule does not apply
194 case errSecCSVetoed:
195 return; // nested code has failed to pass
196 default:
197 MacOSError::throwMe(rc); // general error; pass to caller
198 }
199
200 // if this rule is disabled, skip it but record the first matching one for posterity
201 if (disabled && latentID == 0) {
202 latentID = id;
203 latentLabel = label ? label : "";
204 continue;
205 }
206
207 // current rule is first rule (in priority order) that matched. Apply it
208 secnotice("gk", "rule %d applies - allow=%d", int(id), allow);
209 if (nested && allow) // success, nothing to record
210 return;
211
212 CFRef<CFDictionaryRef> info; // as needed
213 if (flags & kSecAssessmentFlagRequestOrigin) {
214 if (!info)
215 MacOSError::check(SecCodeCopySigningInformation(code, kSecCSSigningInformation, &info.aref()));
216 if (CFArrayRef chain = CFArrayRef(CFDictionaryGetValue(info, kSecCodeInfoCertificates)))
217 setOrigin(chain, result);
218 }
219 if (!(ruleFlags & kAuthorityFlagInhibitCache) && !(flags & kSecAssessmentFlagNoCache)) { // cache inhibit
220 if (!info)
221 MacOSError::check(SecCodeCopySigningInformation(code, kSecCSSigningInformation, &info.aref()));
222 if (SecTrustRef trust = SecTrustRef(CFDictionaryGetValue(info, kSecCodeInfoTrust))) {
223 CFRef<CFDictionaryRef> xinfo;
224 MacOSError::check(SecTrustCopyExtendedResult(trust, &xinfo.aref()));
225 if (CFDateRef limit = CFDateRef(CFDictionaryGetValue(xinfo, kSecTrustExpirationDate))) {
226 this->recordOutcome(code, allow, type, min(expires, dateToJulian(limit)), id);
227 }
228 }
229 }
230 if (allow) {
231 if (SYSPOLICY_ASSESS_OUTCOME_ACCEPT_ENABLED()) {
232 if (!info)
233 MacOSError::check(SecCodeCopySigningInformation(code, kSecCSSigningInformation, &info.aref()));
234 CFDataRef cdhash = CFDataRef(CFDictionaryGetValue(info, kSecCodeInfoUnique));
235 SYSPOLICY_ASSESS_OUTCOME_ACCEPT(cfString(path).c_str(), type, label, cdhash ? CFDataGetBytePtr(cdhash) : NULL);
236 }
237 } else {
238 if (SYSPOLICY_ASSESS_OUTCOME_DENY_ENABLED() || SYSPOLICY_RECORDER_MODE_ENABLED()) {
239 if (!info)
240 MacOSError::check(SecCodeCopySigningInformation(code, kSecCSSigningInformation, &info.aref()));
241 CFDataRef cdhash = CFDataRef(CFDictionaryGetValue(info, kSecCodeInfoUnique));
242 std::string cpath = cfString(path);
243 const void *hashp = cdhash ? CFDataGetBytePtr(cdhash) : NULL;
244 SYSPOLICY_ASSESS_OUTCOME_DENY(cpath.c_str(), type, label, hashp);
245 SYSPOLICY_RECORDER_MODE(cpath.c_str(), type, label, hashp, recorder_code_untrusted);
246 }
247 }
248 cfadd(result, "{%O=%B}", kSecAssessmentAssessmentVerdict, allow);
249 addAuthority(flags, result, label, id, NULL, false, ruleFlags);
250 return;
251 }
252
253 // no applicable authority (but signed, perhaps temporarily). Deny by default
254 secnotice("gk", "rejecting due to lack of matching active rule");
255 CFRef<CFDictionaryRef> info;
256 MacOSError::check(SecCodeCopySigningInformation(code, kSecCSSigningInformation, &info.aref()));
257 if (flags & kSecAssessmentFlagRequestOrigin) {
258 if (CFArrayRef chain = CFArrayRef(CFDictionaryGetValue(info, kSecCodeInfoCertificates)))
259 setOrigin(chain, result);
260 }
261 if (SYSPOLICY_ASSESS_OUTCOME_DEFAULT_ENABLED() || SYSPOLICY_RECORDER_MODE_ENABLED()) {
262 CFDataRef cdhash = CFDataRef(CFDictionaryGetValue(info, kSecCodeInfoUnique));
263 const void *hashp = cdhash ? CFDataGetBytePtr(cdhash) : NULL;
264 std::string cpath = cfString(path);
265 SYSPOLICY_ASSESS_OUTCOME_DEFAULT(cpath.c_str(), type, latentLabel.c_str(), hashp);
266 SYSPOLICY_RECORDER_MODE(cpath.c_str(), type, latentLabel.c_str(), hashp, 0);
267 }
268 if (!(flags & kSecAssessmentFlagNoCache))
269 this->recordOutcome(code, false, type, this->julianNow() + NEGATIVE_HOLD, latentID);
270 cfadd(result, "{%O=%B}", kSecAssessmentAssessmentVerdict, false);
271 addAuthority(flags, result, latentLabel.c_str(), latentID);
272 }
273
274 CFDictionaryRef PolicyEngine::opaqueWhitelistValidationConditionsFor(SecStaticCodeRef code)
275 {
276 return (mOpaqueWhitelist != NULL) ? mOpaqueWhitelist->validationConditionsFor(code) : NULL;
277 }
278
279 bool PolicyEngine::opaqueWhiteListContains(SecStaticCodeRef code, SecAssessmentFeedback feedback, OSStatus reason)
280 {
281 return (mOpaqueWhitelist != NULL) ? mOpaqueWhitelist->contains(code, feedback, reason) : false;
282 }
283
284 void PolicyEngine::opaqueWhitelistAdd(SecStaticCodeRef code)
285 {
286 if (mOpaqueWhitelist) {
287 mOpaqueWhitelist->add(code);
288 }
289 }
290
291 void PolicyEngine::adjustValidation(SecStaticCodeRef code)
292 {
293 CFRef<CFDictionaryRef> conditions = opaqueWhitelistValidationConditionsFor(code);
294 SecStaticCodeSetValidationConditions(code, conditions);
295 }
296
297
298 bool PolicyEngine::temporarySigning(SecStaticCodeRef code, AuthorityType type, CFURLRef path, SecAssessmentFlags matchFlags)
299 {
300 secnotice("gk", "temporarySigning type=%d matchFlags=0x%x path=%s", type, int(matchFlags), cfString(path).c_str());
301
302 // see if we have a screened record to take matchFlags from
303 std::string screen = createWhitelistScreen(code);
304 SQLite::Statement query(*this,
305 "SELECT flags FROM authority "
306 "WHERE type = :type"
307 " AND NOT flags & :flag"
308 " AND CASE WHEN filter_unsigned IS NULL THEN remarks = :remarks ELSE filter_unsigned = :screen END");
309 query.bind(":type").integer(type);
310 query.bind(":flag").integer(kAuthorityFlagDefault);
311 query.bind(":screen") = screen;
312 query.bind(":remarks") = cfString(path);
313 secdebug("gk", "match screen=%s", screen.c_str());
314 if (query.nextRow()) // got a matching rule
315 matchFlags = SQLite3::int64(query[0]);
316 else if (matchFlags == 0) // lazy and no match
317 return false;
318 secdebug("gk", "matchFlags found=0x%x", int(matchFlags));
319
320 try {
321 // ad-hoc sign the code and attach the signature
322 CFRef<CFDataRef> signature = CFDataCreateMutable(NULL, 0);
323 CFTemp<CFMutableDictionaryRef> arguments("{%O=%O, %O=#N, %O=%d}", kSecCodeSignerDetached, signature.get(), kSecCodeSignerIdentity,
324 kSecCodeSignerDigestAlgorithm, (matchFlags & kAuthorityFlagWhitelistSHA256) ? kSecCodeSignatureHashSHA256 : kSecCodeSignatureHashSHA1);
325 // for modern whitelist entries, neuter the identifier since it may be derived from the filename
326 if (matchFlags & kAuthorityFlagWhitelistSHA256)
327 CFDictionaryAddValue(arguments, kSecCodeSignerIdentifier, CFSTR("ADHOC"));
328 CFRef<SecCodeSignerRef> signer;
329 MacOSError::check(SecCodeSignerCreate(arguments, (matchFlags & kAuthorityFlagWhitelistV2) ? kSecCSSignOpaque : kSecCSSignV1, &signer.aref()));
330 MacOSError::check(SecCodeSignerAddSignature(signer, code, kSecCSDefaultFlags));
331 MacOSError::check(SecCodeSetDetachedSignature(code, signature, kSecCSDefaultFlags));
332
333 SecRequirementRef dr = NULL;
334 SecCodeCopyDesignatedRequirement(code, kSecCSDefaultFlags, &dr);
335 CFStringRef drs = NULL;
336 SecRequirementCopyString(dr, kSecCSDefaultFlags, &drs);
337 secnotice("gk", "successfully created temporary signature - requirement=%s", cfString(drs).c_str());
338
339 // if we're in GKE recording mode, save that signature and report its location
340 if (SYSPOLICY_RECORDER_MODE_ENABLED()) {
341 int status = recorder_code_unable; // ephemeral signature (not recorded)
342 if (geteuid() == 0) {
343 CFRef<CFUUIDRef> uuid = CFUUIDCreate(NULL);
344 std::string sigfile = RECORDER_DIR + cfStringRelease(CFUUIDCreateString(NULL, uuid)) + ".tsig";
345 try {
346 UnixPlusPlus::AutoFileDesc fd(sigfile, O_WRONLY | O_CREAT);
347 fd.write(CFDataGetBytePtr(signature), CFDataGetLength(signature));
348 status = recorder_code_adhoc; // recorded signature
349 SYSPOLICY_RECORDER_MODE_ADHOC_PATH(cfString(path).c_str(), type, sigfile.c_str());
350 } catch (...) { }
351 }
352
353 // now report the D probe itself
354 CFRef<CFDictionaryRef> info;
355 MacOSError::check(SecCodeCopySigningInformation(code, kSecCSDefaultFlags, &info.aref()));
356 CFDataRef cdhash = CFDataRef(CFDictionaryGetValue(info, kSecCodeInfoUnique));
357 SYSPOLICY_RECORDER_MODE(cfString(path).c_str(), type, "",
358 cdhash ? CFDataGetBytePtr(cdhash) : NULL, status);
359 }
360
361 return true; // it worked; we're now (well) signed
362 } catch (...) { }
363
364 return false;
365 }
366
367
368 //
369 // Executable code.
370 // Read from disk, evaluate properly, cache as indicated.
371 //
372 void PolicyEngine::evaluateCode(CFURLRef path, AuthorityType type, SecAssessmentFlags flags, CFDictionaryRef context, CFMutableDictionaryRef result, bool handleUnsigned)
373 {
374 // not really a Gatekeeper function... but reject all "hard quarantined" files because they were made from sandboxed sources without download privilege
375 if (type == kAuthorityExecute) {
376 FileQuarantine qtn(cfString(path).c_str());
377 if (qtn.flag(QTN_FLAG_HARD))
378 MacOSError::throwMe(errSecCSFileHardQuarantined);
379 }
380
381 // hack: if caller passed a UTI, use that to turn off app-only checks for some well-known ones
382 bool appOk = false;
383 if (CFStringRef uti = CFStringRef(CFDictionaryGetValue(context, kSecAssessmentContextKeyUTI))) {
384 appOk = CFEqual(uti, CFSTR("com.apple.systempreference.prefpane"))
385 || CFEqual(uti, CFSTR("com.apple.systempreference.screen-saver"))
386 || CFEqual(uti, CFSTR("com.apple.systempreference.screen-slide-saver"))
387 || CFEqual(uti, CFSTR("com.apple.menu-extra"));
388 }
389
390 CFCopyRef<SecStaticCodeRef> code;
391 MacOSError::check(SecStaticCodeCreateWithPath(path, kSecCSDefaultFlags, &code.aref()));
392
393 SecCSFlags validationFlags = kSecCSEnforceRevocationChecks | kSecCSCheckAllArchitectures;
394 if (!(flags & kSecAssessmentFlagAllowWeak))
395 validationFlags |= kSecCSStrictValidate;
396 adjustValidation(code);
397
398 // deal with a very special case (broken 10.6/10.7 Applet bundles)
399 OSStatus rc = SecStaticCodeCheckValidity(code, validationFlags | kSecCSBasicValidateOnly, NULL);
400 if (rc == errSecCSSignatureFailed) {
401 if (!codeInvalidityExceptions(code, result)) { // invalidly signed, no exceptions -> error
402 if (SYSPOLICY_ASSESS_OUTCOME_BROKEN_ENABLED())
403 SYSPOLICY_ASSESS_OUTCOME_BROKEN(cfString(path).c_str(), type, false);
404 MacOSError::throwMe(rc);
405 }
406 // recognized exception - treat as unsigned
407 if (SYSPOLICY_ASSESS_OUTCOME_BROKEN_ENABLED())
408 SYSPOLICY_ASSESS_OUTCOME_BROKEN(cfString(path).c_str(), type, true);
409 rc = errSecCSUnsigned;
410 }
411
412 // ad-hoc sign unsigned code
413 bool wasAdhocSigned = false;
414 if (rc == errSecCSUnsigned && handleUnsigned && (!overrideAssessment(flags) || SYSPOLICY_RECORDER_MODE_ENABLED())) {
415 if (temporarySigning(code, type, path, 0)) {
416 wasAdhocSigned = true;
417 rc = errSecSuccess; // clear unsigned; we are now well-signed
418 validationFlags |= kSecCSBasicValidateOnly; // no need to re-validate deep contents
419 }
420 }
421
422 // prepare for deep traversal of (hopefully) good signatures
423 SecAssessmentFeedback feedback = SecAssessmentFeedback(CFDictionaryGetValue(context, kSecAssessmentContextKeyFeedback));
424 __block CFRef<CFMutableDictionaryRef> nestedFailure = NULL; // save a nested failure for later
425 MacOSError::check(SecStaticCodeSetCallback(code, kSecCSDefaultFlags, NULL, ^CFTypeRef (SecStaticCodeRef item, CFStringRef cfStage, CFDictionaryRef info) {
426 string stage = cfString(cfStage);
427 if (stage == "prepared") {
428 if (!CFEqual(item, code)) // genuine nested (not top) code
429 adjustValidation(item);
430 } else if (stage == "progress") {
431 if (feedback && CFEqual(item, code)) { // top level progress
432 bool proceed = feedback(kSecAssessmentFeedbackProgress, info);
433 if (!proceed)
434 SecStaticCodeCancelValidation(code, kSecCSDefaultFlags);
435 }
436 } else if (stage == "validated") {
437 SecStaticCodeSetCallback(item, kSecCSDefaultFlags, NULL, NULL); // clear callback to avoid unwanted recursion
438 evaluateCodeItem(item, path, type, flags, item != code, result);
439 if (CFTypeRef verdict = CFDictionaryGetValue(result, kSecAssessmentAssessmentVerdict))
440 if (CFEqual(verdict, kCFBooleanFalse)) {
441 if (item == code)
442 return makeCFNumber(OSStatus(errSecCSVetoed)); // (signal nested-code policy failure, picked up below)
443 // nested code policy failure; save, reset, and continue
444 if (!nestedFailure)
445 nestedFailure = CFMutableDictionaryRef(CFDictionaryGetValue(result, kSecAssessmentAssessmentAuthority));
446 CFDictionaryRemoveValue(result, kSecAssessmentAssessmentAuthority);
447 CFDictionaryRemoveValue(result, kSecAssessmentAssessmentVerdict);
448 }
449 }
450 return NULL;
451 }));
452
453 // go for it!
454 SecCSFlags topFlags = validationFlags | kSecCSCheckNestedCode | kSecCSRestrictSymlinks | kSecCSReportProgress;
455 if (type == kAuthorityExecute && !appOk)
456 topFlags |= kSecCSRestrictToAppLike;
457 switch (rc = SecStaticCodeCheckValidity(code, topFlags, NULL)) {
458 case errSecSuccess: // continue below
459 break;
460 case errSecCSUnsigned:
461 cfadd(result, "{%O=#F}", kSecAssessmentAssessmentVerdict);
462 addAuthority(flags, result, "no usable signature");
463 return;
464 case errSecCSVetoed: // nested code rejected by rule book; result was filled out there
465 if (wasAdhocSigned)
466 addToAuthority(result, kSecAssessmentAssessmentSource, CFSTR("no usable signature")); // ad-hoc signature proved useless
467 return;
468 case errSecCSWeakResourceRules:
469 case errSecCSWeakResourceEnvelope:
470 case errSecCSResourceNotSupported:
471 case errSecCSAmbiguousBundleFormat:
472 case errSecCSSignatureNotVerifiable:
473 case errSecCSRegularFile:
474 case errSecCSBadMainExecutable:
475 case errSecCSBadFrameworkVersion:
476 case errSecCSUnsealedAppRoot:
477 case errSecCSUnsealedFrameworkRoot:
478 case errSecCSInvalidSymlink:
479 case errSecCSNotAppLike:
480 {
481 // consult the whitelist
482 bool allow = false;
483 const char *label;
484 // we've bypassed evaluateCodeItem before we failed validation. Explicitly apply it now
485 SecStaticCodeSetCallback(code, kSecCSDefaultFlags, NULL, NULL);
486 evaluateCodeItem(code, path, type, flags | kSecAssessmentFlagNoCache, false, result);
487 if (CFTypeRef verdict = CFDictionaryGetValue(result, kSecAssessmentAssessmentVerdict)) {
488 // verdict rendered from a nested component - signature not acceptable to Gatekeeper
489 if (CFEqual(verdict, kCFBooleanFalse)) // nested code rejected by rule book; result was filled out there
490 return;
491 if (CFEqual(verdict, kCFBooleanTrue) && !(flags & kSecAssessmentFlagIgnoreWhitelist))
492 if (opaqueWhiteListContains(code, feedback, rc)) {
493 allow = true;
494 }
495 }
496 if (allow) {
497 label = "allowed cdhash";
498 } else {
499 CFDictionaryReplaceValue(result, kSecAssessmentAssessmentVerdict, kCFBooleanFalse);
500 label = "obsolete resource envelope";
501 }
502 cfadd(result, "{%O=%d}", kSecAssessmentAssessmentCodeSigningError, rc);
503 addAuthority(flags, result, label, 0, NULL, true);
504 return;
505 }
506 default:
507 MacOSError::throwMe(rc);
508 }
509
510 if (nestedFailure && CFEqual(CFDictionaryGetValue(result, kSecAssessmentAssessmentVerdict), kCFBooleanTrue)) {
511 // structure intact, top level approved, nested code failed policy
512 CFMutableDictionaryRef authority = CFMutableDictionaryRef(CFDictionaryGetValue(result, kSecAssessmentAssessmentAuthority));
513 uint64_t ruleFlags = cfNumber<uint64_t>(CFNumberRef(CFDictionaryGetValue(authority, kSecAssessmentAssessmentAuthorityFlags)));
514 if (ruleFlags & kAuthorityFlagDefault) {
515 // default rule requires positive match at each nested code - reinstate failure
516 CFDictionaryReplaceValue(result, kSecAssessmentAssessmentVerdict, kCFBooleanFalse);
517 CFDictionaryReplaceValue(result, kSecAssessmentAssessmentAuthority, nestedFailure);
518 }
519 }
520 }
521
522
523 //
524 // Installer archive.
525 // Hybrid policy: If we detect an installer signature, use and validate that.
526 // If we don't, check for a code signature instead.
527 //
528 void PolicyEngine::evaluateInstall(CFURLRef path, SecAssessmentFlags flags, CFDictionaryRef context, CFMutableDictionaryRef result)
529 {
530 const AuthorityType type = kAuthorityInstall;
531
532 // check for recent explicit approval, using a bookmark's FileResourceIdentifierKey
533 if (CFRef<CFDataRef> bookmark = cfLoadFile(lastApprovedFile)) {
534 Boolean stale;
535 if (CFRef<CFURLRef> url = CFURLCreateByResolvingBookmarkData(NULL, bookmark,
536 kCFBookmarkResolutionWithoutUIMask | kCFBookmarkResolutionWithoutMountingMask, NULL, NULL, &stale, NULL))
537 if (CFRef<CFDataRef> savedIdent = CFDataRef(CFURLCreateResourcePropertyForKeyFromBookmarkData(NULL, kCFURLFileResourceIdentifierKey, bookmark)))
538 if (CFRef<CFDateRef> savedMod = CFDateRef(CFURLCreateResourcePropertyForKeyFromBookmarkData(NULL, kCFURLContentModificationDateKey, bookmark))) {
539 CFRef<CFDataRef> currentIdent;
540 CFRef<CFDateRef> currentMod;
541 if (CFURLCopyResourcePropertyForKey(path, kCFURLFileResourceIdentifierKey, &currentIdent.aref(), NULL))
542 if (CFURLCopyResourcePropertyForKey(path, kCFURLContentModificationDateKey, &currentMod.aref(), NULL))
543 if (CFEqual(savedIdent, currentIdent) && CFEqual(savedMod, currentMod)) {
544 cfadd(result, "{%O=#T}", kSecAssessmentAssessmentVerdict);
545 addAuthority(flags, result, "explicit preference");
546 return;
547 }
548 }
549 }
550
551 Xar xar(cfString(path).c_str());
552 if (!xar) {
553 // follow the code signing path
554 evaluateCode(path, type, flags, context, result, true);
555 return;
556 }
557
558 SQLite3::int64 latentID = 0; // first (highest priority) disabled matching ID
559 std::string latentLabel; // ... and associated label, if any
560 if (!xar.isSigned()) {
561 // unsigned xar
562 if (SYSPOLICY_ASSESS_OUTCOME_UNSIGNED_ENABLED())
563 SYSPOLICY_ASSESS_OUTCOME_UNSIGNED(cfString(path).c_str(), type);
564 cfadd(result, "{%O=#F}", kSecAssessmentAssessmentVerdict);
565 addAuthority(flags, result, "no usable signature");
566 return;
567 }
568 if (CFRef<CFArrayRef> certs = xar.copyCertChain()) {
569 CFRef<CFTypeRef> policy = installerPolicy();
570 CFRef<SecTrustRef> trust;
571 MacOSError::check(SecTrustCreateWithCertificates(certs, policy, &trust.aref()));
572 // MacOSError::check(SecTrustSetAnchorCertificates(trust, cfEmptyArray())); // no anchors
573 MacOSError::check(SecTrustSetOptions(trust, kSecTrustOptionAllowExpired | kSecTrustOptionImplicitAnchors));
574
575 SecTrustResultType trustResult;
576 MacOSError::check(SecTrustEvaluate(trust, &trustResult));
577 CFRef<CFArrayRef> chain;
578 CSSM_TP_APPLE_EVIDENCE_INFO *info;
579 MacOSError::check(SecTrustGetResult(trust, &trustResult, &chain.aref(), &info));
580
581 if (flags & kSecAssessmentFlagRequestOrigin)
582 setOrigin(chain, result);
583
584 switch (trustResult) {
585 case kSecTrustResultProceed:
586 case kSecTrustResultUnspecified:
587 break;
588 default:
589 {
590 OSStatus rc;
591 MacOSError::check(SecTrustGetCssmResultCode(trust, &rc));
592 MacOSError::throwMe(rc);
593 }
594 }
595
596 SQLite::Statement query(*this,
597 "SELECT allow, requirement, id, label, flags, disabled FROM scan_authority"
598 " WHERE type = :type"
599 " ORDER BY priority DESC;");
600 query.bind(":type").integer(type);
601 while (query.nextRow()) {
602 bool allow = int(query[0]);
603 const char *reqString = query[1];
604 SQLite3::int64 id = query[2];
605 const char *label = query[3];
606 //sqlite_uint64 ruleFlags = query[4];
607 SQLite3::int64 disabled = query[5];
608
609 CFRef<SecRequirementRef> requirement;
610 MacOSError::check(SecRequirementCreateWithString(CFTempString(reqString), kSecCSDefaultFlags, &requirement.aref()));
611 switch (OSStatus rc = SecRequirementEvaluate(requirement, chain, NULL, kSecCSDefaultFlags)) {
612 case errSecSuccess: // success
613 break;
614 case errSecCSReqFailed: // requirement missed, but otherwise okay
615 continue;
616 default: // broken in some way; all tests will fail like this so bail out
617 MacOSError::throwMe(rc);
618 }
619 if (disabled) {
620 if (latentID == 0) {
621 latentID = id;
622 if (label)
623 latentLabel = label;
624 }
625 continue; // the loop
626 }
627
628 if (SYSPOLICY_ASSESS_OUTCOME_ACCEPT_ENABLED() || SYSPOLICY_ASSESS_OUTCOME_DENY_ENABLED()) {
629 if (allow)
630 SYSPOLICY_ASSESS_OUTCOME_ACCEPT(cfString(path).c_str(), type, label, NULL);
631 else
632 SYSPOLICY_ASSESS_OUTCOME_DENY(cfString(path).c_str(), type, label, NULL);
633 }
634
635 // not adding to the object cache - we could, but it's not likely to be worth it
636 cfadd(result, "{%O=%B}", kSecAssessmentAssessmentVerdict, allow);
637 addAuthority(flags, result, label, id);
638 return;
639 }
640 }
641 if (SYSPOLICY_ASSESS_OUTCOME_DEFAULT_ENABLED())
642 SYSPOLICY_ASSESS_OUTCOME_DEFAULT(cfString(path).c_str(), type, latentLabel.c_str(), NULL);
643
644 // no applicable authority. Deny by default
645 cfadd(result, "{%O=#F}", kSecAssessmentAssessmentVerdict);
646 addAuthority(flags, result, latentLabel.c_str(), latentID);
647 }
648
649
650 //
651 // Create a suitable policy array for verification of installer signatures.
652 //
653 static SecPolicyRef makeRevocationPolicy()
654 {
655 CFRef<SecPolicyRef> policy(SecPolicyCreateRevocation(kSecRevocationUseAnyAvailableMethod));
656 return policy.yield();
657 }
658
659 static CFTypeRef installerPolicy()
660 {
661 CFRef<SecPolicyRef> base = SecPolicyCreateBasicX509();
662 CFRef<SecPolicyRef> revoc = makeRevocationPolicy();
663 return makeCFArray(2, base.get(), revoc.get());
664 }
665
666
667 //
668 // LaunchServices-layer document open.
669 // We don't cache those at present. If we ever do, we need to authenticate CoreServicesUIAgent as the source of its risk assessment.
670 //
671 void PolicyEngine::evaluateDocOpen(CFURLRef path, SecAssessmentFlags flags, CFDictionaryRef context, CFMutableDictionaryRef result)
672 {
673 if (context) {
674 FileQuarantine qtn(cfString(path).c_str());
675 if (CFDictionaryGetValue(context, kSecAssessmentContextKeyPrimarySignature) == kCFBooleanTrue) {
676 // Client requests that we focus on the code signature on this document and report on that.
677 // On this path, we care about the (code) signature on the document, not its risk assessment,
678 // and any exception is reported as a primary error.
679 if (qtn.flag(QTN_FLAG_ASSESSMENT_OK)) {
680 // previously added by user - hacked to say no/no usable signature to trigger proper DMG processing in XProtect
681 cfadd(result, "{%O=#F}", kSecAssessmentAssessmentVerdict);
682 addAuthority(flags, result, "no usable signature");
683 return;
684 }
685 evaluateCode(path, kAuthorityOpenDoc, flags, context, result, true);
686 return;
687 }
688 if (CFStringRef riskCategory = CFStringRef(CFDictionaryGetValue(context, kLSDownloadRiskCategoryKey))) {
689
690 if (CFEqual(riskCategory, kLSRiskCategorySafe)
691 || CFEqual(riskCategory, kLSRiskCategoryNeutral)
692 || CFEqual(riskCategory, kLSRiskCategoryUnknown)
693 || CFEqual(riskCategory, kLSRiskCategoryMayContainUnsafeExecutable)) {
694 cfadd(result, "{%O=#T}", kSecAssessmentAssessmentVerdict);
695 addAuthority(flags, result, "_XProtect");
696 } else if (qtn.flag(QTN_FLAG_HARD)) {
697 MacOSError::throwMe(errSecCSFileHardQuarantined);
698 } else if (qtn.flag(QTN_FLAG_ASSESSMENT_OK)) {
699 // previously added by user
700 cfadd(result, "{%O=#T}", kSecAssessmentAssessmentVerdict);
701 addAuthority(flags, result, "Prior Assessment");
702 } else if (!overrideAssessment(flags)) { // no need to do more work if we're off
703 try {
704 evaluateCode(path, kAuthorityOpenDoc, flags, context, result, true);
705 } catch (...) {
706 // some documents can't be code signed, so this may be quite benign
707 }
708 }
709 if (CFDictionaryGetValue(result, kSecAssessmentAssessmentVerdict) == NULL) { // no code signature to help us out
710 cfadd(result, "{%O=#F}", kSecAssessmentAssessmentVerdict);
711 addAuthority(flags, result, "_XProtect");
712 }
713 addToAuthority(result, kLSDownloadRiskCategoryKey, riskCategory);
714 return;
715 }
716 }
717 // insufficient information from LS - deny by default
718 cfadd(result, "{%O=#F}", kSecAssessmentAssessmentVerdict);
719 addAuthority(flags, result, "Insufficient Context");
720 }
721
722
723 //
724 // Result-creation helpers
725 //
726 void PolicyEngine::addAuthority(SecAssessmentFlags flags, CFMutableDictionaryRef parent, const char *label, SQLite::int64 row, CFTypeRef cacheInfo, bool weak, uint64_t ruleFlags)
727 {
728 CFRef<CFMutableDictionaryRef> auth = makeCFMutableDictionary();
729 if (label && label[0])
730 cfadd(auth, "{%O=%s}", kSecAssessmentAssessmentSource, label);
731 if (row)
732 CFDictionaryAddValue(auth, kSecAssessmentAssessmentAuthorityRow, CFTempNumber(row));
733 if (overrideAssessment(flags))
734 CFDictionaryAddValue(auth, kSecAssessmentAssessmentAuthorityOverride, kDisabledOverride);
735 if (cacheInfo)
736 CFDictionaryAddValue(auth, kSecAssessmentAssessmentFromCache, cacheInfo);
737 CFDictionaryAddValue(auth, kSecAssessmentAssessmentAuthorityFlags, CFTempNumber(ruleFlags));
738 if (weak) {
739 CFDictionaryAddValue(auth, kSecAssessmentAssessmentWeakSignature, kCFBooleanTrue);
740 CFDictionaryReplaceValue(parent, kSecAssessmentAssessmentAuthority, auth);
741 } else {
742 CFDictionaryAddValue(parent, kSecAssessmentAssessmentAuthority, auth);
743 }
744 }
745
746 void PolicyEngine::addToAuthority(CFMutableDictionaryRef parent, CFStringRef key, CFTypeRef value)
747 {
748 CFMutableDictionaryRef authority = CFMutableDictionaryRef(CFDictionaryGetValue(parent, kSecAssessmentAssessmentAuthority));
749 assert(authority);
750 CFDictionaryAddValue(authority, key, value);
751 }
752
753
754 //
755 // Add a rule to the policy database
756 //
757 CFDictionaryRef PolicyEngine::add(CFTypeRef inTarget, AuthorityType type, SecAssessmentFlags flags, CFDictionaryRef context)
758 {
759 // default type to execution
760 if (type == kAuthorityInvalid)
761 type = kAuthorityExecute;
762
763 authorizeUpdate(flags, context);
764 CFDictionary ctx(context, errSecCSInvalidAttributeValues);
765 CFCopyRef<CFTypeRef> target = inTarget;
766 CFRef<CFDataRef> bookmark = NULL;
767 std::string filter_unsigned;
768
769 switch (type) {
770 case kAuthorityExecute:
771 normalizeTarget(target, type, ctx, &filter_unsigned);
772 // bookmarks are untrusted and just a hint to callers
773 bookmark = ctx.get<CFDataRef>(kSecAssessmentRuleKeyBookmark);
774 break;
775 case kAuthorityInstall:
776 if (inTarget && CFGetTypeID(inTarget) == CFURLGetTypeID()) {
777 // no good way to turn an installer file into a requirement. Pretend to succeeed so caller proceeds
778 CFRef<CFArrayRef> properties = makeCFArray(2, kCFURLFileResourceIdentifierKey, kCFURLContentModificationDateKey);
779 CFRef<CFErrorRef> error;
780 CFURLBookmarkCreationOptions options = kCFURLBookmarkCreationDoNotIncludeSandboxExtensionsMask | kCFURLBookmarkCreationMinimalBookmarkMask;
781 if (CFRef<CFDataRef> bookmark = CFURLCreateBookmarkData(NULL, CFURLRef(inTarget), options, properties, NULL, &error.aref())) {
782 UnixPlusPlus::AutoFileDesc fd(lastApprovedFile, O_WRONLY | O_CREAT | O_TRUNC);
783 fd.write(CFDataGetBytePtr(bookmark), CFDataGetLength(bookmark));
784 return NULL;
785 }
786 }
787 break;
788 case kAuthorityOpenDoc:
789 // handle document-open differently: use quarantine flags for whitelisting
790 if (!target || CFGetTypeID(target) != CFURLGetTypeID()) // can only "add" file paths
791 MacOSError::throwMe(errSecCSInvalidObjectRef);
792 try {
793 std::string spath = cfString(target.as<CFURLRef>());
794 FileQuarantine qtn(spath.c_str());
795 qtn.setFlag(QTN_FLAG_ASSESSMENT_OK);
796 qtn.applyTo(spath.c_str());
797 } catch (const CommonError &error) {
798 // could not set quarantine flag - report qualified success
799 return cfmake<CFDictionaryRef>("{%O=%O,'assessment:error'=%d}",
800 kSecAssessmentAssessmentAuthorityOverride, CFSTR("error setting quarantine"), error.osStatus());
801 } catch (...) {
802 return cfmake<CFDictionaryRef>("{%O=%O}", kSecAssessmentAssessmentAuthorityOverride, CFSTR("unable to set quarantine"));
803 }
804 return NULL;
805 }
806
807 // if we now have anything else, we're busted
808 if (!target || CFGetTypeID(target) != SecRequirementGetTypeID())
809 MacOSError::throwMe(errSecCSInvalidObjectRef);
810
811 double priority = 0;
812 string label;
813 bool allow = true;
814 double expires = never;
815 string remarks;
816 SQLite::uint64 dbFlags = kAuthorityFlagWhitelistV2 | kAuthorityFlagWhitelistSHA256;
817
818 if (CFNumberRef pri = ctx.get<CFNumberRef>(kSecAssessmentUpdateKeyPriority))
819 CFNumberGetValue(pri, kCFNumberDoubleType, &priority);
820 if (CFStringRef lab = ctx.get<CFStringRef>(kSecAssessmentUpdateKeyLabel))
821 label = cfString(lab);
822 if (CFDateRef time = ctx.get<CFDateRef>(kSecAssessmentUpdateKeyExpires))
823 // we're using Julian dates here; convert from CFDate
824 expires = dateToJulian(time);
825 if (CFBooleanRef allowing = ctx.get<CFBooleanRef>(kSecAssessmentUpdateKeyAllow))
826 allow = allowing == kCFBooleanTrue;
827 if (CFStringRef rem = ctx.get<CFStringRef>(kSecAssessmentUpdateKeyRemarks))
828 remarks = cfString(rem);
829
830 CFRef<CFStringRef> requirementText;
831 MacOSError::check(SecRequirementCopyString(target.as<SecRequirementRef>(), kSecCSDefaultFlags, &requirementText.aref()));
832 SQLite::Transaction xact(*this, SQLite3::Transaction::deferred, "add_rule");
833 SQLite::Statement insert(*this,
834 "INSERT INTO authority (type, allow, requirement, priority, label, expires, filter_unsigned, remarks, flags)"
835 " VALUES (:type, :allow, :requirement, :priority, :label, :expires, :filter_unsigned, :remarks, :flags);");
836 insert.bind(":type").integer(type);
837 insert.bind(":allow").integer(allow);
838 insert.bind(":requirement") = requirementText.get();
839 insert.bind(":priority") = priority;
840 if (!label.empty())
841 insert.bind(":label") = label;
842 insert.bind(":expires") = expires;
843 insert.bind(":filter_unsigned") = filter_unsigned.empty() ? NULL : filter_unsigned.c_str();
844 if (!remarks.empty())
845 insert.bind(":remarks") = remarks;
846 insert.bind(":flags").integer(dbFlags);
847 insert.execute();
848 SQLite::int64 newRow = this->lastInsert();
849 if (bookmark) {
850 SQLite::Statement bi(*this, "INSERT INTO bookmarkhints (bookmark, authority) VALUES (:bookmark, :authority)");
851 bi.bind(":bookmark") = CFDataRef(bookmark);
852 bi.bind(":authority").integer(newRow);
853 bi.execute();
854 }
855 this->purgeObjects(priority);
856 xact.commit();
857 notify_post(kNotifySecAssessmentUpdate);
858 return cfmake<CFDictionaryRef>("{%O=%d}", kSecAssessmentUpdateKeyRow, newRow);
859 }
860
861
862 CFDictionaryRef PolicyEngine::remove(CFTypeRef target, AuthorityType type, SecAssessmentFlags flags, CFDictionaryRef context)
863 {
864 if (type == kAuthorityOpenDoc) {
865 // handle document-open differently: use quarantine flags for whitelisting
866 authorizeUpdate(flags, context);
867 if (!target || CFGetTypeID(target) != CFURLGetTypeID())
868 MacOSError::throwMe(errSecCSInvalidObjectRef);
869 std::string spath = cfString(CFURLRef(target)).c_str();
870 FileQuarantine qtn(spath.c_str());
871 qtn.clearFlag(QTN_FLAG_ASSESSMENT_OK);
872 qtn.applyTo(spath.c_str());
873 return NULL;
874 }
875 return manipulateRules("DELETE FROM authority", target, type, flags, context, true);
876 }
877
878 CFDictionaryRef PolicyEngine::enable(CFTypeRef target, AuthorityType type, SecAssessmentFlags flags, CFDictionaryRef context, bool authorize)
879 {
880 return manipulateRules("UPDATE authority SET disabled = 0", target, type, flags, context, authorize);
881 }
882
883 CFDictionaryRef PolicyEngine::disable(CFTypeRef target, AuthorityType type, SecAssessmentFlags flags, CFDictionaryRef context, bool authorize)
884 {
885 return manipulateRules("UPDATE authority SET disabled = 1", target, type, flags, context, authorize);
886 }
887
888 CFDictionaryRef PolicyEngine::find(CFTypeRef target, AuthorityType type, SecAssessmentFlags flags, CFDictionaryRef context)
889 {
890 //for privacy reasons we only want to allow the admin to list the database
891 authorizeUpdate(flags, context);
892
893 SQLite::Statement query(*this);
894 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",
895 "scan_authority", target, type, flags, context,
896 " ORDER BY priority DESC");
897 CFRef<CFMutableArrayRef> found = makeCFMutableArray(0);
898 while (query.nextRow()) {
899 SQLite::int64 id = query[0];
900 int type = int(query[1]);
901 const char *requirement = query[2];
902 int allow = int(query[3]);
903 const char *label = query[4];
904 double priority = query[5];
905 const char *remarks = query[6];
906 double expires = query[7];
907 int disabled = int(query[8]);
908 CFRef<CFDataRef> bookmark = query[9].data();
909 CFRef<CFMutableDictionaryRef> rule = makeCFMutableDictionary(5,
910 kSecAssessmentRuleKeyID, CFTempNumber(id).get(),
911 kSecAssessmentRuleKeyType, CFRef<CFStringRef>(typeNameFor(type)).get(),
912 kSecAssessmentRuleKeyRequirement, CFTempString(requirement).get(),
913 kSecAssessmentRuleKeyAllow, allow ? kCFBooleanTrue : kCFBooleanFalse,
914 kSecAssessmentRuleKeyPriority, CFTempNumber(priority).get()
915 );
916 if (label)
917 CFDictionaryAddValue(rule, kSecAssessmentRuleKeyLabel, CFTempString(label));
918 if (remarks)
919 CFDictionaryAddValue(rule, kSecAssessmentRuleKeyRemarks, CFTempString(remarks));
920 if (expires != never)
921 CFDictionaryAddValue(rule, kSecAssessmentRuleKeyExpires, CFRef<CFDateRef>(julianToDate(expires)));
922 if (disabled)
923 CFDictionaryAddValue(rule, kSecAssessmentRuleKeyDisabled, CFTempNumber(disabled));
924 if (bookmark)
925 CFDictionaryAddValue(rule, kSecAssessmentRuleKeyBookmark, bookmark);
926 CFArrayAppendValue(found, rule);
927 }
928 if (CFArrayGetCount(found) == 0)
929 MacOSError::throwMe(errSecCSNoMatches);
930 return cfmake<CFDictionaryRef>("{%O=%O}", kSecAssessmentUpdateKeyFound, found.get());
931 }
932
933
934 CFDictionaryRef PolicyEngine::update(CFTypeRef target, SecAssessmentFlags flags, CFDictionaryRef context)
935 {
936 // update GKE
937 installExplicitSet(gkeAuthFile, gkeSigsFile);
938
939 AuthorityType type = typeFor(context, kAuthorityInvalid);
940 CFStringRef edit = CFStringRef(CFDictionaryGetValue(context, kSecAssessmentContextKeyUpdate));
941 CFDictionaryRef result;
942 if (CFEqual(edit, kSecAssessmentUpdateOperationAdd))
943 result = this->add(target, type, flags, context);
944 else if (CFEqual(edit, kSecAssessmentUpdateOperationRemove))
945 result = this->remove(target, type, flags, context);
946 else if (CFEqual(edit, kSecAssessmentUpdateOperationEnable))
947 result = this->enable(target, type, flags, context, true);
948 else if (CFEqual(edit, kSecAssessmentUpdateOperationDisable))
949 result = this->disable(target, type, flags, context, true);
950 else if (CFEqual(edit, kSecAssessmentUpdateOperationFind))
951 result = this->find(target, type, flags, context);
952 else
953 MacOSError::throwMe(errSecCSInvalidAttributeValues);
954 if (result == NULL)
955 result = makeCFDictionary(0); // success, no details
956 return result;
957 }
958
959
960 //
961 // Construct and prepare an SQL query on the authority table, operating on some set of existing authority records.
962 // In essence, this appends a suitable WHERE clause to the stanza passed and prepares it on the statement given.
963 //
964 void PolicyEngine::selectRules(SQLite::Statement &action, std::string phrase, std::string table,
965 CFTypeRef inTarget, AuthorityType type, SecAssessmentFlags flags, CFDictionaryRef context, std::string suffix /* = "" */)
966 {
967 CFDictionary ctx(context, errSecCSInvalidAttributeValues);
968 CFCopyRef<CFTypeRef> target = inTarget;
969 std::string filter_unsigned; // ignored; used just to trigger ad-hoc signing
970 normalizeTarget(target, type, ctx, &filter_unsigned);
971
972 string label;
973 if (CFStringRef lab = ctx.get<CFStringRef>(kSecAssessmentUpdateKeyLabel))
974 label = cfString(CFStringRef(lab));
975
976 if (!target) {
977 if (label.empty()) {
978 if (type == kAuthorityInvalid) {
979 action.query(phrase + suffix);
980 } else {
981 action.query(phrase + " WHERE " + table + ".type = :type" + suffix);
982 action.bind(":type").integer(type);
983 }
984 } else { // have label
985 if (type == kAuthorityInvalid) {
986 action.query(phrase + " WHERE " + table + ".label = :label" + suffix);
987 } else {
988 action.query(phrase + " WHERE " + table + ".type = :type AND " + table + ".label = :label" + suffix);
989 action.bind(":type").integer(type);
990 }
991 action.bind(":label") = label;
992 }
993 } else if (CFGetTypeID(target) == CFNumberGetTypeID()) {
994 action.query(phrase + " WHERE " + table + ".id = :id" + suffix);
995 action.bind(":id").integer(cfNumber<uint64_t>(target.as<CFNumberRef>()));
996 } else if (CFGetTypeID(target) == SecRequirementGetTypeID()) {
997 if (type == kAuthorityInvalid)
998 type = kAuthorityExecute;
999 CFRef<CFStringRef> requirementText;
1000 MacOSError::check(SecRequirementCopyString(target.as<SecRequirementRef>(), kSecCSDefaultFlags, &requirementText.aref()));
1001 action.query(phrase + " WHERE " + table + ".type = :type AND " + table + ".requirement = :requirement" + suffix);
1002 action.bind(":type").integer(type);
1003 action.bind(":requirement") = requirementText.get();
1004 } else
1005 MacOSError::throwMe(errSecCSInvalidObjectRef);
1006 }
1007
1008
1009 //
1010 // Execute an atomic change to existing records in the authority table.
1011 //
1012 CFDictionaryRef PolicyEngine::manipulateRules(const std::string &stanza,
1013 CFTypeRef inTarget, AuthorityType type, SecAssessmentFlags flags, CFDictionaryRef context, bool authorize)
1014 {
1015 SQLite::Transaction xact(*this, SQLite3::Transaction::deferred, "rule_change");
1016 SQLite::Statement action(*this);
1017 if (authorize)
1018 authorizeUpdate(flags, context);
1019 selectRules(action, stanza, "authority", inTarget, type, flags, context);
1020 action.execute();
1021 unsigned int changes = this->changes(); // latch change count
1022 // We MUST purge objects with priority <= MAX(priority of any changed rules);
1023 // but for now we just get lazy and purge them ALL.
1024 if (changes) {
1025 this->purgeObjects(1.0E100);
1026 xact.commit();
1027 notify_post(kNotifySecAssessmentUpdate);
1028 return cfmake<CFDictionaryRef>("{%O=%d}", kSecAssessmentUpdateKeyCount, changes);
1029 }
1030 // no change; return an error
1031 MacOSError::throwMe(errSecCSNoMatches);
1032 }
1033
1034
1035 //
1036 // Fill in extra information about the originator of cryptographic credentials found - if any
1037 //
1038 void PolicyEngine::setOrigin(CFArrayRef chain, CFMutableDictionaryRef result)
1039 {
1040 if (chain)
1041 if (CFArrayGetCount(chain) > 0)
1042 if (SecCertificateRef leaf = SecCertificateRef(CFArrayGetValueAtIndex(chain, 0)))
1043 if (CFStringRef summary = SecCertificateCopyLongDescription(NULL, leaf, NULL)) {
1044 CFDictionarySetValue(result, kSecAssessmentAssessmentOriginator, summary);
1045 CFRelease(summary);
1046 }
1047 }
1048
1049
1050 //
1051 // Take an assessment outcome and record it in the object cache
1052 //
1053 void PolicyEngine::recordOutcome(SecStaticCodeRef code, bool allow, AuthorityType type, double expires, SQLite::int64 authority)
1054 {
1055 CFRef<CFDictionaryRef> info;
1056 MacOSError::check(SecCodeCopySigningInformation(code, kSecCSDefaultFlags, &info.aref()));
1057 CFDataRef cdHash = CFDataRef(CFDictionaryGetValue(info, kSecCodeInfoUnique));
1058 assert(cdHash); // was signed
1059 CFRef<CFURLRef> path;
1060 MacOSError::check(SecCodeCopyPath(code, kSecCSDefaultFlags, &path.aref()));
1061 assert(expires);
1062 SQLite::Transaction xact(*this, SQLite3::Transaction::deferred, "caching");
1063 SQLite::Statement insert(*this,
1064 "INSERT OR REPLACE INTO object (type, allow, hash, expires, path, authority)"
1065 " VALUES (:type, :allow, :hash, :expires, :path,"
1066 " CASE :authority WHEN 0 THEN (SELECT id FROM authority WHERE label = 'No Matching Rule') ELSE :authority END"
1067 " );");
1068 insert.bind(":type").integer(type);
1069 insert.bind(":allow").integer(allow);
1070 insert.bind(":hash") = cdHash;
1071 insert.bind(":expires") = expires;
1072 insert.bind(":path") = cfString(path);
1073 insert.bind(":authority").integer(authority);
1074 insert.execute();
1075 xact.commit();
1076 }
1077
1078
1079 //
1080 // Record a UI failure record after proper validation of the caller
1081 //
1082 void PolicyEngine::recordFailure(CFDictionaryRef info)
1083 {
1084 CFRef<CFDataRef> infoData = makeCFData(info);
1085 UnixPlusPlus::AutoFileDesc fd(lastRejectFile, O_WRONLY | O_CREAT | O_TRUNC);
1086 fd.write(CFDataGetBytePtr(infoData), CFDataGetLength(infoData));
1087 notify_post(kNotifySecAssessmentRecordingChange);
1088 }
1089
1090
1091 //
1092 // Perform update authorization processing.
1093 // Throws an exception if authorization is denied.
1094 //
1095 static void authorizeUpdate(SecAssessmentFlags flags, CFDictionaryRef context)
1096 {
1097 AuthorizationRef authorization = NULL;
1098
1099 if (context)
1100 if (CFTypeRef authkey = CFDictionaryGetValue(context, kSecAssessmentUpdateKeyAuthorization))
1101 if (CFGetTypeID(authkey) == CFDataGetTypeID()) {
1102 CFDataRef authdata = CFDataRef(authkey);
1103 if (CFDataGetLength(authdata) != sizeof(AuthorizationExternalForm))
1104 MacOSError::throwMe(errSecCSInvalidObjectRef);
1105 MacOSError::check(AuthorizationCreateFromExternalForm((AuthorizationExternalForm *)CFDataGetBytePtr(authdata), &authorization));
1106 }
1107 if (authorization == NULL)
1108 MacOSError::throwMe(errSecCSDBDenied);
1109
1110 AuthorizationItem right[] = {
1111 { "com.apple.security.assessment.update", 0, NULL, 0 }
1112 };
1113 AuthorizationRights rights = { sizeof(right) / sizeof(right[0]), right };
1114 MacOSError::check(AuthorizationCopyRights(authorization, &rights, NULL,
1115 kAuthorizationFlagExtendRights | kAuthorizationFlagInteractionAllowed, NULL));
1116
1117 MacOSError::check(AuthorizationFree(authorization, kAuthorizationFlagDefaults));
1118 }
1119
1120
1121 //
1122 // Perform common argument normalizations for update operations
1123 //
1124 void PolicyEngine::normalizeTarget(CFRef<CFTypeRef> &target, AuthorityType type, CFDictionary &context, std::string *signUnsigned)
1125 {
1126 // turn CFURLs into (designated) SecRequirements
1127 if (target && CFGetTypeID(target) == CFURLGetTypeID()) {
1128 CFRef<SecStaticCodeRef> code;
1129 CFURLRef path = target.as<CFURLRef>();
1130 MacOSError::check(SecStaticCodeCreateWithPath(path, kSecCSDefaultFlags, &code.aref()));
1131 switch (OSStatus rc = SecCodeCopyDesignatedRequirement(code, kSecCSDefaultFlags, (SecRequirementRef *)&target.aref())) {
1132 case errSecSuccess: {
1133 // use the *default* DR to avoid unreasonably wide DRs opening up Gatekeeper to attack
1134 CFRef<CFDictionaryRef> info;
1135 MacOSError::check(SecCodeCopySigningInformation(code, kSecCSRequirementInformation, &info.aref()));
1136 target = CFDictionaryGetValue(info, kSecCodeInfoImplicitDesignatedRequirement);
1137 }
1138 break;
1139 case errSecCSUnsigned:
1140 if (signUnsigned && temporarySigning(code, type, path, kAuthorityFlagWhitelistV2 | kAuthorityFlagWhitelistSHA256)) { // ad-hoc sign the code temporarily
1141 MacOSError::check(SecCodeCopyDesignatedRequirement(code, kSecCSDefaultFlags, (SecRequirementRef *)&target.aref()));
1142 *signUnsigned = createWhitelistScreen(code);
1143 break;
1144 }
1145 MacOSError::check(rc);
1146 case errSecCSSignatureFailed:
1147 // recover certain cases of broken signatures (well, try)
1148 if (codeInvalidityExceptions(code, NULL)) {
1149 // Ad-hoc sign the code in place (requiring a writable subject). This requires root privileges.
1150 CFRef<SecCodeSignerRef> signer;
1151 CFTemp<CFDictionaryRef> arguments("{%O=#N}", kSecCodeSignerIdentity);
1152 MacOSError::check(SecCodeSignerCreate(arguments, kSecCSSignOpaque, &signer.aref()));
1153 MacOSError::check(SecCodeSignerAddSignature(signer, code, kSecCSDefaultFlags));
1154 MacOSError::check(SecCodeCopyDesignatedRequirement(code, kSecCSDefaultFlags, (SecRequirementRef *)&target.aref()));
1155 break;
1156 }
1157 MacOSError::check(rc);
1158 default:
1159 MacOSError::check(rc);
1160 }
1161 if (context.get(kSecAssessmentUpdateKeyRemarks) == NULL) {
1162 // no explicit remarks; add one with the path
1163 CFRef<CFURLRef> path;
1164 MacOSError::check(SecCodeCopyPath(code, kSecCSDefaultFlags, &path.aref()));
1165 CFMutableDictionaryRef dict = makeCFMutableDictionary(context.get());
1166 CFDictionaryAddValue(dict, kSecAssessmentUpdateKeyRemarks, CFTempString(cfString(path)));
1167 context.take(dict);
1168 }
1169 CFStringRef edit = CFStringRef(context.get(kSecAssessmentContextKeyUpdate));
1170 if (type == kAuthorityExecute && CFEqual(edit, kSecAssessmentUpdateOperationAdd)) {
1171 // implicitly whitelist the code
1172 opaqueWhitelistAdd(code);
1173 }
1174 }
1175 }
1176
1177
1178 //
1179 // Process special overrides for invalidly signed code.
1180 // This is the (hopefully minimal) concessions we make to keep hurting our customers
1181 // for our own prior mistakes...
1182 //
1183 static bool codeInvalidityExceptions(SecStaticCodeRef code, CFMutableDictionaryRef result)
1184 {
1185 CFRef<CFDictionaryRef> info;
1186 MacOSError::check(SecCodeCopySigningInformation(code, kSecCSDefaultFlags, &info.aref()));
1187 if (CFURLRef executable = CFURLRef(CFDictionaryGetValue(info, kSecCodeInfoMainExecutable))) {
1188 SInt32 error;
1189 if (OSAIsRecognizedExecutableURL(executable, &error)) {
1190 if (result)
1191 CFDictionaryAddValue(result,
1192 kSecAssessmentAssessmentAuthorityOverride, CFSTR("ignoring known invalid applet signature"));
1193 return true;
1194 }
1195 }
1196 return false;
1197 }
1198
1199
1200 } // end namespace CodeSigning
1201 } // end namespace Security