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