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