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