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