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