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