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