1/* 2 * Copyright (c) 2006-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 24// 25// StaticCode - SecStaticCode API objects 26// 27#include "StaticCode.h" 28#include "Code.h" 29#include "reqmaker.h" 30#include "drmaker.h" 31#include "reqdumper.h" 32#include "reqparser.h" 33#include "sigblob.h" 34#include "resources.h" 35#include "detachedrep.h" 36#include "csdatabase.h" 37#include "csutilities.h" 38#include "dirscanner.h" 39#include <CoreFoundation/CFURLAccess.h> 40#include <Security/SecPolicyPriv.h> 41#include <Security/SecTrustPriv.h> 42#include <Security/SecCertificatePriv.h> 43#include <Security/CMSPrivate.h> 44#include <Security/SecCmsContentInfo.h> 45#include <Security/SecCmsSignerInfo.h> 46#include <Security/SecCmsSignedData.h> 47#include <Security/cssmapplePriv.h> 48#include <security_utilities/unix++.h> 49#include <security_utilities/cfmunge.h> 50#include <Security/CMSDecoder.h> 51#include <security_utilities/logging.h> 52#include <dirent.h> 53#include <sstream> 54 55 56namespace Security { 57namespace CodeSigning { 58 59using namespace UnixPlusPlus; 60 61// A requirement representing a Mac or iOS dev cert, a Mac or iOS distribution cert, or a developer ID 62static const char WWDRRequirement[] = "anchor apple generic and certificate 1[field.1.2.840.113635.100.6.2.1] exists " 63 "and ( cert leaf[subject.CN] = \"Mac Developer: \"* or cert leaf[subject.CN] = \"iPhone Developer: \"* )"; 64static const char developerID[] = "anchor apple generic and certificate 1[field.1.2.840.113635.100.6.2.6] exists" 65 " and certificate leaf[field.1.2.840.113635.100.6.1.13] exists"; 66static const char distributionCertificate[] = "anchor apple generic and certificate leaf[field.1.2.840.113635.100.6.1.7] exists"; 67static const char iPhoneDistributionCert[] = "anchor apple generic and certificate leaf[field.1.2.840.113635.100.6.1.4] exists"; 68 69// 70// Map a component slot number to a suitable error code for a failure 71// 72static inline OSStatus errorForSlot(CodeDirectory::SpecialSlot slot) 73{ 74 switch (slot) { 75 case cdInfoSlot: 76 return errSecCSInfoPlistFailed; 77 case cdResourceDirSlot: 78 return errSecCSResourceDirectoryFailed; 79 default: 80 return errSecCSSignatureFailed; 81 } 82} 83 84 85// 86// Construct a SecStaticCode object given a disk representation object 87// 88SecStaticCode::SecStaticCode(DiskRep *rep) 89 : mRep(rep), 90 mValidated(false), mExecutableValidated(false), mResourcesValidated(false), mResourcesValidContext(NULL), 91 mDesignatedReq(NULL), mGotResourceBase(false), mMonitor(NULL), mEvalDetails(NULL) 92{ 93 CODESIGN_STATIC_CREATE(this, rep); 94 checkForSystemSignature(); 95} 96 97 98// 99// Clean up a SecStaticCode object 100// 101SecStaticCode::~SecStaticCode() throw() 102try { 103 ::free(const_cast<Requirement *>(mDesignatedReq)); 104 if (mResourcesValidContext) 105 delete mResourcesValidContext; 106} catch (...) { 107 return; 108} 109 110 111// 112// CF-level comparison of SecStaticCode objects compares CodeDirectory hashes if signed, 113// and falls back on comparing canonical paths if (both are) not. 114// 115bool SecStaticCode::equal(SecCFObject &secOther) 116{ 117 SecStaticCode *other = static_cast<SecStaticCode *>(&secOther); 118 CFDataRef mine = this->cdHash(); 119 CFDataRef his = other->cdHash(); 120 if (mine || his) 121 return mine && his && CFEqual(mine, his); 122 else 123 return CFEqual(CFRef<CFURLRef>(this->copyCanonicalPath()), CFRef<CFURLRef>(other->copyCanonicalPath())); 124} 125 126CFHashCode SecStaticCode::hash() 127{ 128 if (CFDataRef h = this->cdHash()) 129 return CFHash(h); 130 else 131 return CFHash(CFRef<CFURLRef>(this->copyCanonicalPath())); 132} 133 134 135// 136// Invoke a stage monitor if registered 137// 138CFTypeRef SecStaticCode::reportEvent(CFStringRef stage, CFDictionaryRef info) 139{ 140 if (mMonitor) 141 return mMonitor(this->handle(false), stage, info); 142 else 143 return NULL; 144} 145 146 147// 148// Set validation conditions for fine-tuning legacy tolerance 149// 150static void addError(CFTypeRef cfError, void* context) 151{ 152 if (CFGetTypeID(cfError) == CFNumberGetTypeID()) { 153 int64_t error; 154 CFNumberGetValue(CFNumberRef(cfError), kCFNumberSInt64Type, (void*)&error); 155 MacOSErrorSet* errors = (MacOSErrorSet*)context; 156 errors->insert(OSStatus(error)); 157 } 158} 159 160void SecStaticCode::setValidationModifiers(CFDictionaryRef conditions) 161{ 162 if (conditions) { 163 CFDictionary source(conditions, errSecCSDbCorrupt); 164 mAllowOmissions = source.get<CFArrayRef>("omissions"); 165 if (CFArrayRef errors = source.get<CFArrayRef>("errors")) 166 CFArrayApplyFunction(errors, CFRangeMake(0, CFArrayGetCount(errors)), addError, &this->mTolerateErrors); 167 } 168} 169 170 171// 172// Attach a detached signature. 173// 174void SecStaticCode::detachedSignature(CFDataRef sigData) 175{ 176 if (sigData) { 177 mDetachedSig = sigData; 178 mRep = new DetachedRep(sigData, mRep->base(), "explicit detached"); 179 CODESIGN_STATIC_ATTACH_EXPLICIT(this, mRep); 180 } else { 181 mDetachedSig = NULL; 182 mRep = mRep->base(); 183 CODESIGN_STATIC_ATTACH_EXPLICIT(this, NULL); 184 } 185} 186 187 188// 189// Consult the system detached signature database to see if it contains 190// a detached signature for this StaticCode. If it does, fetch and attach it. 191// We do this only if the code has no signature already attached. 192// 193void SecStaticCode::checkForSystemSignature() 194{ 195 if (!this->isSigned()) { 196 SignatureDatabase db; 197 if (db.isOpen()) 198 try { 199 if (RefPointer<DiskRep> dsig = db.findCode(mRep)) { 200 CODESIGN_STATIC_ATTACH_SYSTEM(this, dsig); 201 mRep = dsig; 202 } 203 } catch (...) { 204 } 205 } 206} 207 208 209// 210// Return a descriptive string identifying the source of the code signature 211// 212string SecStaticCode::signatureSource() 213{ 214 if (!isSigned()) 215 return "unsigned"; 216 if (DetachedRep *rep = dynamic_cast<DetachedRep *>(mRep.get())) 217 return rep->source(); 218 return "embedded"; 219} 220 221 222// 223// Do ::required, but convert incoming SecCodeRefs to their SecStaticCodeRefs 224// (if possible). 225// 226SecStaticCode *SecStaticCode::requiredStatic(SecStaticCodeRef ref) 227{ 228 SecCFObject *object = SecCFObject::required(ref, errSecCSInvalidObjectRef); 229 if (SecStaticCode *scode = dynamic_cast<SecStaticCode *>(object)) 230 return scode; 231 else if (SecCode *code = dynamic_cast<SecCode *>(object)) 232 return code->staticCode(); 233 else // neither (a SecSomethingElse) 234 MacOSError::throwMe(errSecCSInvalidObjectRef); 235} 236 237SecCode *SecStaticCode::optionalDynamic(SecStaticCodeRef ref) 238{ 239 SecCFObject *object = SecCFObject::required(ref, errSecCSInvalidObjectRef); 240 if (dynamic_cast<SecStaticCode *>(object)) 241 return NULL; 242 else if (SecCode *code = dynamic_cast<SecCode *>(object)) 243 return code; 244 else // neither (a SecSomethingElse) 245 MacOSError::throwMe(errSecCSInvalidObjectRef); 246} 247 248 249// 250// Void all cached validity data. 251// 252// We also throw out cached components, because the new signature data may have 253// a different idea of what components should be present. We could reconcile the 254// cached data instead, if performance seems to be impacted. 255// 256void SecStaticCode::resetValidity() 257{ 258 CODESIGN_EVAL_STATIC_RESET(this); 259 mValidated = false; 260 mExecutableValidated = mResourcesValidated = false; 261 if (mResourcesValidContext) { 262 delete mResourcesValidContext; 263 mResourcesValidContext = NULL; 264 } 265 mDir = NULL; 266 mSignature = NULL; 267 for (unsigned n = 0; n < cdSlotCount; n++) 268 mCache[n] = NULL; 269 mInfoDict = NULL; 270 mEntitlements = NULL; 271 mResourceDict = NULL; 272 mDesignatedReq = NULL; 273 mCDHash = NULL; 274 mGotResourceBase = false; 275 mTrust = NULL; 276 mCertChain = NULL; 277 mEvalDetails = NULL; 278 mRep->flush(); 279 280 // we may just have updated the system database, so check again 281 checkForSystemSignature(); 282} 283 284 285// 286// Retrieve a sealed component by special slot index. 287// If the CodeDirectory has already been validated, validate against that. 288// Otherwise, retrieve the component without validation (but cache it). Validation 289// will go through the cache and validate all cached components. 290// 291CFDataRef SecStaticCode::component(CodeDirectory::SpecialSlot slot, OSStatus fail /* = errSecCSSignatureFailed */) 292{ 293 assert(slot <= cdSlotMax); 294 295 CFRef<CFDataRef> &cache = mCache[slot]; 296 if (!cache) { 297 if (CFRef<CFDataRef> data = mRep->component(slot)) { 298 if (validated()) // if the directory has been validated... 299 if (!codeDirectory()->validateSlot(CFDataGetBytePtr(data), // ... and it's no good 300 CFDataGetLength(data), -slot)) 301 MacOSError::throwMe(errorForSlot(slot)); // ... then bail 302 cache = data; // it's okay, cache it 303 } else { // absent, mark so 304 if (validated()) // if directory has been validated... 305 if (codeDirectory()->slotIsPresent(-slot)) // ... and the slot is NOT missing 306 MacOSError::throwMe(errorForSlot(slot)); // was supposed to be there 307 cache = CFDataRef(kCFNull); // white lie 308 } 309 } 310 return (cache == CFDataRef(kCFNull)) ? NULL : cache.get(); 311} 312 313 314// 315// Get the CodeDirectory. 316// Throws (if check==true) or returns NULL (check==false) if there is none. 317// Always throws if the CodeDirectory exists but is invalid. 318// NEVER validates against the signature. 319// 320const CodeDirectory *SecStaticCode::codeDirectory(bool check /* = true */) 321{ 322 if (!mDir) { 323 if (mDir.take(mRep->codeDirectory())) { 324 const CodeDirectory *dir = reinterpret_cast<const CodeDirectory *>(CFDataGetBytePtr(mDir)); 325 dir->checkIntegrity(); 326 } 327 } 328 if (mDir) 329 return reinterpret_cast<const CodeDirectory *>(CFDataGetBytePtr(mDir)); 330 if (check) 331 MacOSError::throwMe(errSecCSUnsigned); 332 return NULL; 333} 334 335 336// 337// Get the hash of the CodeDirectory. 338// Returns NULL if there is none. 339// 340CFDataRef SecStaticCode::cdHash() 341{ 342 if (!mCDHash) { 343 if (const CodeDirectory *cd = codeDirectory(false)) { 344 SHA1 hash; 345 hash(cd, cd->length()); 346 SHA1::Digest digest; 347 hash.finish(digest); 348 mCDHash.take(makeCFData(digest, sizeof(digest))); 349 CODESIGN_STATIC_CDHASH(this, digest, sizeof(digest)); 350 } 351 } 352 return mCDHash; 353} 354 355 356// 357// Return the CMS signature blob; NULL if none found. 358// 359CFDataRef SecStaticCode::signature() 360{ 361 if (!mSignature) 362 mSignature.take(mRep->signature()); 363 if (mSignature) 364 return mSignature; 365 MacOSError::throwMe(errSecCSUnsigned); 366} 367 368 369// 370// Verify the signature on the CodeDirectory. 371// If this succeeds (doesn't throw), the CodeDirectory is statically trustworthy. 372// Any outcome (successful or not) is cached for the lifetime of the StaticCode. 373// 374void SecStaticCode::validateDirectory() 375{ 376 // echo previous outcome, if any 377 if (!validated()) 378 try { 379 // perform validation (or die trying) 380 CODESIGN_EVAL_STATIC_DIRECTORY(this); 381 mValidationExpired = verifySignature(); 382 for (CodeDirectory::SpecialSlot slot = codeDirectory()->maxSpecialSlot(); slot >= 1; --slot) 383 if (mCache[slot]) // if we already loaded that resource... 384 validateComponent(slot, errorForSlot(slot)); // ... then check it now 385 mValidated = true; // we've done the deed... 386 mValidationResult = errSecSuccess; // ... and it was good 387 } catch (const CommonError &err) { 388 mValidated = true; 389 mValidationResult = err.osStatus(); 390 throw; 391 } catch (...) { 392 secdebug("staticCode", "%p validation threw non-common exception", this); 393 mValidated = true; 394 mValidationResult = errSecCSInternalError; 395 throw; 396 } 397 assert(validated()); 398 if (mValidationResult == errSecSuccess) { 399 if (mValidationExpired) 400 if ((apiFlags() & kSecCSConsiderExpiration) 401 || (codeDirectory()->flags & kSecCodeSignatureForceExpiration)) 402 MacOSError::throwMe(CSSMERR_TP_CERT_EXPIRED); 403 } else 404 MacOSError::throwMe(mValidationResult); 405} 406 407 408// 409// Load and validate the CodeDirectory and all components *except* those related to the resource envelope. 410// Those latter components are checked by validateResources(). 411// 412void SecStaticCode::validateNonResourceComponents() 413{ 414 this->validateDirectory(); 415 for (CodeDirectory::SpecialSlot slot = codeDirectory()->maxSpecialSlot(); slot >= 1; --slot) 416 switch (slot) { 417 case cdResourceDirSlot: // validated by validateResources 418 break; 419 default: 420 this->component(slot); // loads and validates 421 break; 422 } 423} 424 425 426// 427// Get the (signed) signing date from the code signature. 428// Sadly, we need to validate the signature to get the date (as a side benefit). 429// This means that you can't get the signing time for invalidly signed code. 430// 431// We could run the decoder "almost to" verification to avoid this, but there seems 432// little practical point to such a duplication of effort. 433// 434CFAbsoluteTime SecStaticCode::signingTime() 435{ 436 validateDirectory(); 437 return mSigningTime; 438} 439 440CFAbsoluteTime SecStaticCode::signingTimestamp() 441{ 442 validateDirectory(); 443 return mSigningTimestamp; 444} 445 446 447// 448// Verify the CMS signature on the CodeDirectory. 449// This performs the cryptographic tango. It returns if the signature is valid, 450// or throws if it is not. As a side effect, a successful return sets up the 451// cached certificate chain for future use. 452// Returns true if the signature is expired (the X.509 sense), false if it's not. 453// Expiration is fatal (throws) if a secure timestamp is included, but not otherwise. 454// 455bool SecStaticCode::verifySignature() 456{ 457 // ad-hoc signed code is considered validly signed by definition 458 if (flag(kSecCodeSignatureAdhoc)) { 459 CODESIGN_EVAL_STATIC_SIGNATURE_ADHOC(this); 460 return false; 461 } 462 463 DTRACK(CODESIGN_EVAL_STATIC_SIGNATURE, this, (char*)this->mainExecutablePath().c_str()); 464 465 // decode CMS and extract SecTrust for verification 466 CFRef<CMSDecoderRef> cms; 467 MacOSError::check(CMSDecoderCreate(&cms.aref())); // create decoder 468 CFDataRef sig = this->signature(); 469 MacOSError::check(CMSDecoderUpdateMessage(cms, CFDataGetBytePtr(sig), CFDataGetLength(sig))); 470 this->codeDirectory(); // load CodeDirectory (sets mDir) 471 MacOSError::check(CMSDecoderSetDetachedContent(cms, mDir)); 472 MacOSError::check(CMSDecoderFinalizeMessage(cms)); 473 MacOSError::check(CMSDecoderSetSearchKeychain(cms, cfEmptyArray())); 474 CFRef<CFTypeRef> policy = verificationPolicy(apiFlags()); 475 CMSSignerStatus status; 476 MacOSError::check(CMSDecoderCopySignerStatus(cms, 0, policy, 477 false, &status, &mTrust.aref(), NULL)); 478 if (status != kCMSSignerValid) 479 MacOSError::throwMe(errSecCSSignatureFailed); 480 481 // internal signing time (as specified by the signer; optional) 482 mSigningTime = 0; // "not present" marker (nobody could code sign on Jan 1, 2001 :-) 483 switch (OSStatus rc = CMSDecoderCopySignerSigningTime(cms, 0, &mSigningTime)) { 484 case errSecSuccess: 485 case errSecSigningTimeMissing: 486 break; 487 default: 488 MacOSError::throwMe(rc); 489 } 490 491 // certified signing time (as specified by a TSA; optional) 492 mSigningTimestamp = 0; 493 switch (OSStatus rc = CMSDecoderCopySignerTimestamp(cms, 0, &mSigningTimestamp)) { 494 case errSecSuccess: 495 case errSecTimestampMissing: 496 break; 497 default: 498 MacOSError::throwMe(rc); 499 } 500 501 // set up the environment for SecTrust 502 MacOSError::check(SecTrustSetAnchorCertificates(mTrust, cfEmptyArray())); // no anchors 503 MacOSError::check(SecTrustSetKeychains(mTrust, cfEmptyArray())); // no keychains 504 CSSM_APPLE_TP_ACTION_DATA actionData = { 505 CSSM_APPLE_TP_ACTION_VERSION, // version of data structure 506 CSSM_TP_ACTION_IMPLICIT_ANCHORS // action flags 507 }; 508 509 for (;;) { // at most twice 510 MacOSError::check(SecTrustSetParameters(mTrust, 511 CSSM_TP_ACTION_DEFAULT, CFTempData(&actionData, sizeof(actionData)))); 512 513 // evaluate trust and extract results 514 SecTrustResultType trustResult; 515 MacOSError::check(SecTrustEvaluate(mTrust, &trustResult)); 516 MacOSError::check(SecTrustGetResult(mTrust, &trustResult, &mCertChain.aref(), &mEvalDetails)); 517 518 // if this is an Apple developer cert.... 519 if (teamID() && SecStaticCode::isAppleDeveloperCert(mCertChain)) { 520 CFRef<CFStringRef> teamIDFromCert; 521 if (CFArrayGetCount(mCertChain) > 0) { 522 /* Note that SecCertificateCopySubjectComponent sets the out paramater to NULL if there is no field present */ 523 MacOSError::check(SecCertificateCopySubjectComponent((SecCertificateRef)CFArrayGetValueAtIndex(mCertChain, Requirement::leafCert), 524 &CSSMOID_OrganizationalUnitName, 525 &teamIDFromCert.aref())); 526 527 if (teamIDFromCert) { 528 CFRef<CFStringRef> teamIDFromCD = CFStringCreateWithCString(NULL, teamID(), kCFStringEncodingUTF8); 529 if (!teamIDFromCD) { 530 MacOSError::throwMe(errSecCSInternalError); 531 } 532 533 if (CFStringCompare(teamIDFromCert, teamIDFromCD, 0) != kCFCompareEqualTo) { 534 Security::Syslog::error("Team identifier in the signing certificate (%s) does not match the team identifier (%s) in the code directory", cfString(teamIDFromCert).c_str(), teamID()); 535 MacOSError::throwMe(errSecCSSignatureInvalid); 536 } 537 } 538 } 539 } 540 541 CODESIGN_EVAL_STATIC_SIGNATURE_RESULT(this, trustResult, mCertChain ? (int)CFArrayGetCount(mCertChain) : 0); 542 switch (trustResult) { 543 case kSecTrustResultProceed: 544 case kSecTrustResultUnspecified: 545 break; // success 546 case kSecTrustResultDeny: 547 MacOSError::throwMe(CSSMERR_APPLETP_TRUST_SETTING_DENY); // user reject 548 case kSecTrustResultInvalid: 549 assert(false); // should never happen 550 MacOSError::throwMe(CSSMERR_TP_NOT_TRUSTED); 551 default: 552 { 553 OSStatus result; 554 MacOSError::check(SecTrustGetCssmResultCode(mTrust, &result)); 555 // if we have a valid timestamp, CMS validates against (that) signing time and all is well. 556 // If we don't have one, may validate against *now*, and must be able to tolerate expiration. 557 if (mSigningTimestamp == 0) // no timestamp available 558 if (((result == CSSMERR_TP_CERT_EXPIRED) || (result == CSSMERR_TP_CERT_NOT_VALID_YET)) 559 && !(actionData.ActionFlags & CSSM_TP_ACTION_ALLOW_EXPIRED)) { 560 CODESIGN_EVAL_STATIC_SIGNATURE_EXPIRED(this); 561 actionData.ActionFlags |= CSSM_TP_ACTION_ALLOW_EXPIRED; // (this also allows postdated certs) 562 continue; // retry validation while tolerating expiration 563 } 564 MacOSError::throwMe(result); 565 } 566 } 567 568 if (mSigningTimestamp) { 569 CFIndex rootix = CFArrayGetCount(mCertChain); 570 if (SecCertificateRef mainRoot = SecCertificateRef(CFArrayGetValueAtIndex(mCertChain, rootix-1))) 571 if (isAppleCA(mainRoot)) { 572 // impose policy: if the signature itself draws to Apple, then so must the timestamp signature 573 CFRef<CFArrayRef> tsCerts; 574 MacOSError::check(CMSDecoderCopySignerTimestampCertificates(cms, 0, &tsCerts.aref())); 575 CFIndex tsn = CFArrayGetCount(tsCerts); 576 bool good = tsn > 0 && isAppleCA(SecCertificateRef(CFArrayGetValueAtIndex(tsCerts, tsn-1))); 577 if (!good) 578 MacOSError::throwMe(CSSMERR_TP_NOT_TRUSTED); 579 } 580 } 581 582 return actionData.ActionFlags & CSSM_TP_ACTION_ALLOW_EXPIRED; 583 } 584} 585 586 587// 588// Return the TP policy used for signature verification. 589// This may be a simple SecPolicyRef or a CFArray of policies. 590// The caller owns the return value. 591// 592static SecPolicyRef makeCRLPolicy() 593{ 594 CFRef<SecPolicyRef> policy; 595 MacOSError::check(SecPolicyCopy(CSSM_CERT_X_509v3, &CSSMOID_APPLE_TP_REVOCATION_CRL, &policy.aref())); 596 CSSM_APPLE_TP_CRL_OPTIONS options; 597 memset(&options, 0, sizeof(options)); 598 options.Version = CSSM_APPLE_TP_CRL_OPTS_VERSION; 599 options.CrlFlags = CSSM_TP_ACTION_FETCH_CRL_FROM_NET | CSSM_TP_ACTION_CRL_SUFFICIENT; 600 CSSM_DATA optData = { sizeof(options), (uint8 *)&options }; 601 MacOSError::check(SecPolicySetValue(policy, &optData)); 602 return policy.yield(); 603} 604 605static SecPolicyRef makeOCSPPolicy() 606{ 607 CFRef<SecPolicyRef> policy; 608 MacOSError::check(SecPolicyCopy(CSSM_CERT_X_509v3, &CSSMOID_APPLE_TP_REVOCATION_OCSP, &policy.aref())); 609 CSSM_APPLE_TP_OCSP_OPTIONS options; 610 memset(&options, 0, sizeof(options)); 611 options.Version = CSSM_APPLE_TP_OCSP_OPTS_VERSION; 612 options.Flags = CSSM_TP_ACTION_OCSP_SUFFICIENT; 613 CSSM_DATA optData = { sizeof(options), (uint8 *)&options }; 614 MacOSError::check(SecPolicySetValue(policy, &optData)); 615 return policy.yield(); 616} 617 618CFTypeRef SecStaticCode::verificationPolicy(SecCSFlags flags) 619{ 620 CFRef<SecPolicyRef> core; 621 MacOSError::check(SecPolicyCopy(CSSM_CERT_X_509v3, 622 &CSSMOID_APPLE_TP_CODE_SIGNING, &core.aref())); 623 if (flags & kSecCSEnforceRevocationChecks) { 624 CFRef<SecPolicyRef> crl = makeCRLPolicy(); 625 CFRef<SecPolicyRef> ocsp = makeOCSPPolicy(); 626 return makeCFArray(3, core.get(), crl.get(), ocsp.get()); 627 } else { 628 return core.yield(); 629 } 630} 631 632 633// 634// Validate a particular sealed, cached resource against its (special) CodeDirectory slot. 635// The resource must already have been placed in the cache. 636// This does NOT perform basic validation. 637// 638void SecStaticCode::validateComponent(CodeDirectory::SpecialSlot slot, OSStatus fail /* = errSecCSSignatureFailed */) 639{ 640 assert(slot <= cdSlotMax); 641 CFDataRef data = mCache[slot]; 642 assert(data); // must be cached 643 if (data == CFDataRef(kCFNull)) { 644 if (codeDirectory()->slotIsPresent(-slot)) // was supposed to be there... 645 MacOSError::throwMe(fail); // ... and is missing 646 } else { 647 if (!codeDirectory()->validateSlot(CFDataGetBytePtr(data), CFDataGetLength(data), -slot)) 648 MacOSError::throwMe(fail); 649 } 650} 651 652 653// 654// Perform static validation of the main executable. 655// This reads the main executable from disk and validates it against the 656// CodeDirectory code slot array. 657// Note that this is NOT an in-memory validation, and is thus potentially 658// subject to timing attacks. 659// 660void SecStaticCode::validateExecutable() 661{ 662 if (!validatedExecutable()) { 663 try { 664 DTRACK(CODESIGN_EVAL_STATIC_EXECUTABLE, this, 665 (char*)this->mainExecutablePath().c_str(), codeDirectory()->nCodeSlots); 666 const CodeDirectory *cd = this->codeDirectory(); 667 if (!cd) 668 MacOSError::throwMe(errSecCSUnsigned); 669 AutoFileDesc fd(mainExecutablePath(), O_RDONLY); 670 fd.fcntl(F_NOCACHE, true); // turn off page caching (one-pass) 671 if (Universal *fat = mRep->mainExecutableImage()) 672 fd.seek(fat->archOffset()); 673 size_t pageSize = cd->pageSize ? (1 << cd->pageSize) : 0; 674 size_t remaining = cd->codeLimit; 675 for (uint32_t slot = 0; slot < cd->nCodeSlots; ++slot) { 676 size_t size = min(remaining, pageSize); 677 if (!cd->validateSlot(fd, size, slot)) { 678 CODESIGN_EVAL_STATIC_EXECUTABLE_FAIL(this, (int)slot); 679 MacOSError::throwMe(errSecCSSignatureFailed); 680 } 681 remaining -= size; 682 } 683 mExecutableValidated = true; 684 mExecutableValidResult = errSecSuccess; 685 } catch (const CommonError &err) { 686 mExecutableValidated = true; 687 mExecutableValidResult = err.osStatus(); 688 throw; 689 } catch (...) { 690 secdebug("staticCode", "%p executable validation threw non-common exception", this); 691 mExecutableValidated = true; 692 mExecutableValidResult = errSecCSInternalError; 693 throw; 694 } 695 } 696 assert(validatedExecutable()); 697 if (mExecutableValidResult != errSecSuccess) 698 MacOSError::throwMe(mExecutableValidResult); 699} 700 701 702// 703// Perform static validation of sealed resources and nested code. 704// 705// This performs a whole-code static resource scan and effectively 706// computes a concordance between what's on disk and what's in the ResourceDirectory. 707// Any unsanctioned difference causes an error. 708// 709void SecStaticCode::validateResources(SecCSFlags flags) 710{ 711 // do we have a superset of this requested validation cached? 712 bool doit = true; 713 if (mResourcesValidated) { // have cached outcome 714 if (!(flags & kSecCSCheckNestedCode) || mResourcesDeep) // was deep or need no deep scan 715 doit = false; 716 } 717 if (doit) { 718 try { 719 // sanity first 720 CFDictionaryRef sealedResources = resourceDictionary(); 721 if (this->resourceBase()) // disk has resources 722 if (sealedResources) 723 /* go to work below */; 724 else 725 MacOSError::throwMe(errSecCSResourcesNotFound); 726 else // disk has no resources 727 if (sealedResources) 728 MacOSError::throwMe(errSecCSResourcesNotFound); 729 else 730 return; // no resources, not sealed - fine (no work) 731 732 // found resources, and they are sealed 733 DTRACK(CODESIGN_EVAL_STATIC_RESOURCES, this, 734 (char*)this->mainExecutablePath().c_str(), 0); 735 736 // scan through the resources on disk, checking each against the resourceDirectory 737 mResourcesValidContext = new CollectingContext(*this); // collect all failures in here 738 CFDictionaryRef rules; 739 CFDictionaryRef files; 740 uint32_t version; 741 if (CFDictionaryGetValue(sealedResources, CFSTR("files2"))) { // have V2 signature 742 rules = cfget<CFDictionaryRef>(sealedResources, "rules2"); 743 files = cfget<CFDictionaryRef>(sealedResources, "files2"); 744 version = 2; 745 } else { // only V1 available 746 rules = cfget<CFDictionaryRef>(sealedResources, "rules"); 747 files = cfget<CFDictionaryRef>(sealedResources, "files"); 748 version = 1; 749 } 750 if (!rules || !files) 751 MacOSError::throwMe(errSecCSResourcesInvalid); 752 // check for weak resource rules 753 bool strict = flags & kSecCSStrictValidate; 754 if (strict && (version == 1 || hasWeakResourceRules(rules, mAllowOmissions))) 755 if (mTolerateErrors.find(errSecCSWeakResourceRules) == mTolerateErrors.end()) 756 MacOSError::throwMe(errSecCSWeakResourceRules); 757 __block CFRef<CFMutableDictionaryRef> resourceMap = makeCFMutableDictionary(files); 758 string base = cfString(this->resourceBase()); 759 ResourceBuilder resources(base, base, rules, codeDirectory()->hashType, strict, mTolerateErrors); 760 diskRep()->adjustResources(resources); 761 resources.scan(^(FTSENT *ent, uint32_t ruleFlags, const char *relpath, ResourceBuilder::Rule *rule) { 762 validateResource(files, relpath, ent->fts_info == FTS_SL, *mResourcesValidContext, flags, version); 763 CFDictionaryRemoveValue(resourceMap, CFTempString(relpath)); 764 }); 765 766 if (CFDictionaryGetCount(resourceMap) > 0) { 767 secdebug("staticCode", "%p sealed resource(s) not found in code", this); 768 CFDictionaryApplyFunction(resourceMap, SecStaticCode::checkOptionalResource, mResourcesValidContext); 769 } 770 771 // now check for any errors found in the reporting context 772 mResourcesValidated = true; 773 mResourcesDeep = flags & kSecCSCheckNestedCode; 774 if (mResourcesValidContext->osStatus() != errSecSuccess) 775 mResourcesValidContext->throwMe(); 776 } catch (const CommonError &err) { 777 mResourcesValidated = true; 778 mResourcesDeep = flags & kSecCSCheckNestedCode; 779 mResourcesValidResult = err.osStatus(); 780 throw; 781 } catch (...) { 782 secdebug("staticCode", "%p executable validation threw non-common exception", this); 783 mResourcesValidated = true; 784 mResourcesDeep = flags & kSecCSCheckNestedCode; 785 mResourcesValidResult = errSecCSInternalError; 786 throw; 787 } 788 } 789 assert(validatedResources()); 790 if (mResourcesValidResult) 791 MacOSError::throwMe(mResourcesValidResult); 792 if (mResourcesValidContext->osStatus() != errSecSuccess) 793 mResourcesValidContext->throwMe(); 794} 795 796 797void SecStaticCode::checkOptionalResource(CFTypeRef key, CFTypeRef value, void *context) 798{ 799 CollectingContext *ctx = static_cast<CollectingContext *>(context); 800 ResourceSeal seal(value); 801 if (!seal.optional()) { 802 if (key && CFGetTypeID(key) == CFStringGetTypeID()) { 803 ctx->reportProblem(errSecCSBadResource, kSecCFErrorResourceMissing, 804 CFTempURL(CFStringRef(key), false, ctx->code.resourceBase())); 805 } else { 806 ctx->reportProblem(errSecCSBadResource, kSecCFErrorResourceSeal, key); 807 } 808 } 809} 810 811 812static bool isOmitRule(CFTypeRef value) 813{ 814 if (CFGetTypeID(value) == CFBooleanGetTypeID()) 815 return value == kCFBooleanFalse; 816 CFDictionary rule(value, errSecCSResourceRulesInvalid); 817 return rule.get<CFBooleanRef>("omit") == kCFBooleanTrue; 818} 819 820bool SecStaticCode::hasWeakResourceRules(CFDictionaryRef rulesDict, CFArrayRef allowedOmissions) 821{ 822 // compute allowed omissions 823 CFRef<CFArrayRef> defaultOmissions = this->diskRep()->allowedResourceOmissions(); 824 assert(defaultOmissions); 825 CFRef<CFMutableArrayRef> allowed = CFArrayCreateMutableCopy(NULL, 0, defaultOmissions); 826 if (allowedOmissions) 827 CFArrayAppendArray(allowed, allowedOmissions, CFRangeMake(0, CFArrayGetCount(allowedOmissions))); 828 CFRange range = CFRangeMake(0, CFArrayGetCount(allowed)); 829 830 // check all resource rules for weakness 831 __block bool coversAll = false; 832 __block bool forbiddenOmission = false; 833 CFDictionary rules(rulesDict, errSecCSResourceRulesInvalid); 834 rules.apply(^(CFStringRef key, CFTypeRef value) { 835 string pattern = cfString(key, errSecCSResourceRulesInvalid); 836 if (pattern == "^.*" && value == kCFBooleanTrue) { 837 coversAll = true; 838 return; 839 } 840 if (isOmitRule(value)) 841 forbiddenOmission |= !CFArrayContainsValue(allowed, range, key); 842 }); 843 844 return !coversAll || forbiddenOmission; 845} 846 847 848// 849// Load, validate, cache, and return CFDictionary forms of sealed resources. 850// 851CFDictionaryRef SecStaticCode::infoDictionary() 852{ 853 if (!mInfoDict) { 854 mInfoDict.take(getDictionary(cdInfoSlot, errSecCSInfoPlistFailed)); 855 secdebug("staticCode", "%p loaded InfoDict %p", this, mInfoDict.get()); 856 } 857 return mInfoDict; 858} 859 860CFDictionaryRef SecStaticCode::entitlements() 861{ 862 if (!mEntitlements) { 863 validateDirectory(); 864 if (CFDataRef entitlementData = component(cdEntitlementSlot)) { 865 validateComponent(cdEntitlementSlot); 866 const EntitlementBlob *blob = reinterpret_cast<const EntitlementBlob *>(CFDataGetBytePtr(entitlementData)); 867 if (blob->validateBlob()) { 868 mEntitlements.take(blob->entitlements()); 869 secdebug("staticCode", "%p loaded Entitlements %p", this, mEntitlements.get()); 870 } 871 // we do not consider a different blob type to be an error. We think it's a new format we don't understand 872 } 873 } 874 return mEntitlements; 875} 876 877CFDictionaryRef SecStaticCode::resourceDictionary(bool check /* = true */) 878{ 879 if (mResourceDict) // cached 880 return mResourceDict; 881 if (CFRef<CFDictionaryRef> dict = getDictionary(cdResourceDirSlot, check)) 882 if (cfscan(dict, "{rules=%Dn,files=%Dn}")) { 883 secdebug("staticCode", "%p loaded ResourceDict %p", 884 this, mResourceDict.get()); 885 return mResourceDict = dict; 886 } 887 // bad format 888 return NULL; 889} 890 891 892// 893// Load and cache the resource directory base. 894// Note that the base is optional for each DiskRep. 895// 896CFURLRef SecStaticCode::resourceBase() 897{ 898 if (!mGotResourceBase) { 899 string base = mRep->resourcesRootPath(); 900 if (!base.empty()) 901 mResourceBase.take(makeCFURL(base, true)); 902 mGotResourceBase = true; 903 } 904 return mResourceBase; 905} 906 907 908// 909// Load a component, validate it, convert it to a CFDictionary, and return that. 910// This will force load and validation, which means that it will perform basic 911// validation if it hasn't been done yet. 912// 913CFDictionaryRef SecStaticCode::getDictionary(CodeDirectory::SpecialSlot slot, bool check /* = true */) 914{ 915 if (check) 916 validateDirectory(); 917 if (CFDataRef infoData = component(slot)) { 918 validateComponent(slot); 919 if (CFDictionaryRef dict = makeCFDictionaryFrom(infoData)) 920 return dict; 921 else 922 MacOSError::throwMe(errSecCSBadDictionaryFormat); 923 } 924 return NULL; 925} 926 927 928// 929// Load, validate, and return a sealed resource. 930// The resource data (loaded in to memory as a blob) is returned and becomes 931// the responsibility of the caller; it is NOT cached by SecStaticCode. 932// 933// A resource that is not sealed will not be returned, and an error will be thrown. 934// A missing resource will cause an error unless it's marked optional in the Directory. 935// Under no circumstances will a corrupt resource be returned. 936// NULL will only be returned for a resource that is neither sealed nor present 937// (or that is sealed, absent, and marked optional). 938// If the ResourceDictionary itself is not sealed, this function will always fail. 939// 940// There is currently no interface for partial retrieval of the resource data. 941// (Since the ResourceDirectory does not currently support segmentation, all the 942// data would have to be read anyway, but it could be read into a reusable buffer.) 943// 944CFDataRef SecStaticCode::resource(string path, ValidationContext &ctx) 945{ 946 if (CFDictionaryRef rdict = resourceDictionary()) { 947 if (CFTypeRef file = cfget(rdict, "files.%s", path.c_str())) { 948 ResourceSeal seal = file; 949 if (!resourceBase()) // no resources in DiskRep 950 MacOSError::throwMe(errSecCSResourcesNotFound); 951 if (seal.nested()) 952 MacOSError::throwMe(errSecCSResourcesNotSealed); // (it's nested code) 953 CFRef<CFURLRef> fullpath = makeCFURL(path, false, resourceBase()); 954 if (CFRef<CFDataRef> data = cfLoadFile(fullpath)) { 955 MakeHash<CodeDirectory> hasher(this->codeDirectory()); 956 hasher->update(CFDataGetBytePtr(data), CFDataGetLength(data)); 957 if (hasher->verify(seal.hash())) 958 return data.yield(); // good 959 else 960 ctx.reportProblem(errSecCSBadResource, kSecCFErrorResourceAltered, fullpath); // altered 961 } else { 962 if (!seal.optional()) 963 ctx.reportProblem(errSecCSBadResource, kSecCFErrorResourceMissing, fullpath); // was sealed but is now missing 964 else 965 return NULL; // validly missing 966 } 967 } else 968 ctx.reportProblem(errSecCSBadResource, kSecCFErrorResourceAdded, CFTempURL(path, false, resourceBase())); 969 return NULL; 970 } else 971 MacOSError::throwMe(errSecCSResourcesNotSealed); 972} 973 974CFDataRef SecStaticCode::resource(string path) 975{ 976 ValidationContext ctx; 977 return resource(path, ctx); 978} 979 980 981void SecStaticCode::validateResource(CFDictionaryRef files, string path, bool isSymlink, ValidationContext &ctx, SecCSFlags flags, uint32_t version) 982{ 983 if (!resourceBase()) // no resources in DiskRep 984 MacOSError::throwMe(errSecCSResourcesNotFound); 985 CFRef<CFURLRef> fullpath = makeCFURL(path, false, resourceBase()); 986 if (CFTypeRef file = CFDictionaryGetValue(files, CFTempString(path))) { 987 ResourceSeal seal = file; 988 if (seal.nested()) { 989 if (isSymlink) 990 return ctx.reportProblem(errSecCSBadResource, kSecCFErrorResourceAltered, fullpath); // changed type 991 string suffix = ".framework"; 992 bool isFramework = (path.length() > suffix.length()) 993 && (path.compare(path.length()-suffix.length(), suffix.length(), suffix) == 0); 994 validateNestedCode(fullpath, seal, flags, isFramework); 995 } else if (seal.link()) { 996 char target[PATH_MAX]; 997 ssize_t len = ::readlink(cfString(fullpath).c_str(), target, sizeof(target)-1); 998 if (len < 0) 999 UnixError::check(-1); 1000 target[len] = '\0'; 1001 if (cfString(seal.link()) != target) 1002 ctx.reportProblem(errSecCSBadResource, kSecCFErrorResourceAltered, fullpath); 1003 } else if (seal.hash()) { // genuine file 1004 AutoFileDesc fd(cfString(fullpath), O_RDONLY, FileDesc::modeMissingOk); // open optional file 1005 if (fd) { 1006 MakeHash<CodeDirectory> hasher(this->codeDirectory()); 1007 hashFileData(fd, hasher.get()); 1008 if (hasher->verify(seal.hash())) 1009 return; // verify good 1010 else 1011 ctx.reportProblem(errSecCSBadResource, kSecCFErrorResourceAltered, fullpath); // altered 1012 } else { 1013 if (!seal.optional()) 1014 ctx.reportProblem(errSecCSBadResource, kSecCFErrorResourceMissing, fullpath); // was sealed but is now missing 1015 else 1016 return; // validly missing 1017 } 1018 } else 1019 ctx.reportProblem(errSecCSBadResource, kSecCFErrorResourceAltered, fullpath); // changed type 1020 return; 1021 } 1022 if (version == 1) { // version 1 ignores symlinks altogether 1023 char target[PATH_MAX]; 1024 if (::readlink(cfString(fullpath).c_str(), target, sizeof(target)) > 0) 1025 return; 1026 } 1027 ctx.reportProblem(errSecCSBadResource, kSecCFErrorResourceAdded, CFTempURL(path, false, resourceBase())); 1028} 1029 1030void SecStaticCode::validateNestedCode(CFURLRef path, const ResourceSeal &seal, SecCSFlags flags, bool isFramework) 1031{ 1032 CFRef<SecRequirementRef> req; 1033 if (SecRequirementCreateWithString(seal.requirement(), kSecCSDefaultFlags, &req.aref())) 1034 MacOSError::throwMe(errSecCSResourcesInvalid); 1035 1036 // recursively verify this nested code 1037 try { 1038 if (!(flags & kSecCSCheckNestedCode)) 1039 flags |= kSecCSBasicValidateOnly; 1040 SecPointer<SecStaticCode> code = new SecStaticCode(DiskRep::bestGuess(cfString(path))); 1041 code->setMonitor(this->monitor()); 1042 code->staticValidate(flags, SecRequirement::required(req)); 1043 1044 if (isFramework && (flags & kSecCSStrictValidate)) 1045 try { 1046 validateOtherVersions(path, flags, req, code); 1047 } catch (const CSError &err) { 1048 MacOSError::throwMe(errSecCSBadFrameworkVersion); 1049 } catch (const MacOSError &err) { 1050 MacOSError::throwMe(errSecCSBadFrameworkVersion); 1051 } 1052 1053 } catch (CSError &err) { 1054 if (err.error == errSecCSReqFailed) { 1055 mResourcesValidContext->reportProblem(errSecCSBadNestedCode, kSecCFErrorResourceAltered, path); 1056 return; 1057 } 1058 err.augment(kSecCFErrorPath, path); 1059 throw; 1060 } catch (const MacOSError &err) { 1061 if (err.error == errSecCSReqFailed) { 1062 mResourcesValidContext->reportProblem(errSecCSBadNestedCode, kSecCFErrorResourceAltered, path); 1063 return; 1064 } 1065 CSError::throwMe(err.error, kSecCFErrorPath, path); 1066 } 1067} 1068 1069void SecStaticCode::validateOtherVersions(CFURLRef path, SecCSFlags flags, SecRequirementRef req, SecStaticCode *code) 1070{ 1071 // Find out what current points to and do not revalidate 1072 std::string mainPath = cfStringRelease(code->diskRep()->copyCanonicalPath()); 1073 1074 char main_path[PATH_MAX]; 1075 bool foundTarget = false; 1076 1077 /* If it failed to get the target of the symlink, do not fail. It is a performance loss, 1078 not a security hole */ 1079 if (realpath(mainPath.c_str(), main_path) != NULL) 1080 foundTarget = true; 1081 1082 std::ostringstream versionsPath; 1083 versionsPath << cfString(path) << "/Versions/"; 1084 1085 DirScanner scanner(versionsPath.str()); 1086 1087 if (scanner.initialized()) { 1088 struct dirent *entry = NULL; 1089 while ((entry = scanner.getNext()) != NULL) { 1090 std::ostringstream fullPath; 1091 1092 if (entry->d_type != DT_DIR || 1093 strcmp(entry->d_name, ".") == 0 || 1094 strcmp(entry->d_name, "..") == 0 || 1095 strcmp(entry->d_name, "Current") == 0) 1096 continue; 1097 1098 fullPath << versionsPath.str() << entry->d_name; 1099 1100 char real_full_path[PATH_MAX]; 1101 if (realpath(fullPath.str().c_str(), real_full_path) == NULL) 1102 UnixError::check(-1); 1103 1104 // Do case insensitive comparions because realpath() was called for both paths 1105 if (foundTarget && strcmp(main_path, real_full_path) == 0) 1106 continue; 1107 1108 SecPointer<SecStaticCode> frameworkVersion = new SecStaticCode(DiskRep::bestGuess(real_full_path)); 1109 frameworkVersion->setMonitor(this->monitor()); 1110 frameworkVersion->staticValidate(flags, SecRequirement::required(req)); 1111 } 1112 } 1113} 1114 1115 1116// 1117// Test a CodeDirectory flag. 1118// Returns false if there is no CodeDirectory. 1119// May throw if the CodeDirectory is present but somehow invalid. 1120// 1121bool SecStaticCode::flag(uint32_t tested) 1122{ 1123 if (const CodeDirectory *cd = this->codeDirectory(false)) 1124 return cd->flags & tested; 1125 else 1126 return false; 1127} 1128 1129 1130// 1131// Retrieve the full SuperBlob containing all internal requirements. 1132// 1133const Requirements *SecStaticCode::internalRequirements() 1134{ 1135 if (CFDataRef reqData = component(cdRequirementsSlot)) { 1136 const Requirements *req = (const Requirements *)CFDataGetBytePtr(reqData); 1137 if (!req->validateBlob()) 1138 MacOSError::throwMe(errSecCSReqInvalid); 1139 return req; 1140 } else 1141 return NULL; 1142} 1143 1144 1145// 1146// Retrieve a particular internal requirement by type. 1147// 1148const Requirement *SecStaticCode::internalRequirement(SecRequirementType type) 1149{ 1150 if (const Requirements *reqs = internalRequirements()) 1151 return reqs->find<Requirement>(type); 1152 else 1153 return NULL; 1154} 1155 1156 1157// 1158// Return the Designated Requirement (DR). This can be either explicit in the 1159// Internal Requirements component, or implicitly generated on demand here. 1160// Note that an explicit DR may have been implicitly generated at signing time; 1161// we don't distinguish this case. 1162// 1163const Requirement *SecStaticCode::designatedRequirement() 1164{ 1165 if (const Requirement *req = internalRequirement(kSecDesignatedRequirementType)) { 1166 return req; // explicit in signing data 1167 } else { 1168 if (!mDesignatedReq) 1169 mDesignatedReq = defaultDesignatedRequirement(); 1170 return mDesignatedReq; 1171 } 1172} 1173 1174 1175// 1176// Generate the default Designated Requirement (DR) for this StaticCode. 1177// Ignore any explicit DR it may contain. 1178// 1179const Requirement *SecStaticCode::defaultDesignatedRequirement() 1180{ 1181 if (flag(kSecCodeSignatureAdhoc)) { 1182 // adhoc signature: return a cdhash requirement for all architectures 1183 __block Requirement::Maker maker; 1184 Requirement::Maker::Chain chain(maker, opOr); 1185 1186 // insert cdhash requirement for all architectures 1187 chain.add(); 1188 maker.cdhash(this->cdHash()); 1189 handleOtherArchitectures(^(SecStaticCode *subcode) { 1190 if (CFDataRef cdhash = subcode->cdHash()) { 1191 chain.add(); 1192 maker.cdhash(cdhash); 1193 } 1194 }); 1195 return maker.make(); 1196 } else { 1197 // full signature: Gin up full context and let DRMaker do its thing 1198 validateDirectory(); // need the cert chain 1199 Requirement::Context context(this->certificates(), 1200 this->infoDictionary(), 1201 this->entitlements(), 1202 this->identifier(), 1203 this->codeDirectory() 1204 ); 1205 return DRMaker(context).make(); 1206 } 1207} 1208 1209 1210// 1211// Validate a SecStaticCode against the internal requirement of a particular type. 1212// 1213void SecStaticCode::validateRequirements(SecRequirementType type, SecStaticCode *target, 1214 OSStatus nullError /* = errSecSuccess */) 1215{ 1216 DTRACK(CODESIGN_EVAL_STATIC_INTREQ, this, type, target, nullError); 1217 if (const Requirement *req = internalRequirement(type)) 1218 target->validateRequirement(req, nullError ? nullError : errSecCSReqFailed); 1219 else if (nullError) 1220 MacOSError::throwMe(nullError); 1221 else 1222 /* accept it */; 1223} 1224 1225 1226// 1227// Validate this StaticCode against an external Requirement 1228// 1229bool SecStaticCode::satisfiesRequirement(const Requirement *req, OSStatus failure) 1230{ 1231 assert(req); 1232 validateDirectory(); 1233 return req->validates(Requirement::Context(mCertChain, infoDictionary(), entitlements(), codeDirectory()->identifier(), codeDirectory()), failure); 1234} 1235 1236void SecStaticCode::validateRequirement(const Requirement *req, OSStatus failure) 1237{ 1238 if (!this->satisfiesRequirement(req, failure)) 1239 MacOSError::throwMe(failure); 1240} 1241 1242 1243// 1244// Retrieve one certificate from the cert chain. 1245// Positive and negative indices can be used: 1246// [ leaf, intermed-1, ..., intermed-n, anchor ] 1247// 0 1 ... -2 -1 1248// Returns NULL if unavailable for any reason. 1249// 1250SecCertificateRef SecStaticCode::cert(int ix) 1251{ 1252 validateDirectory(); // need cert chain 1253 if (mCertChain) { 1254 CFIndex length = CFArrayGetCount(mCertChain); 1255 if (ix < 0) 1256 ix += length; 1257 if (ix >= 0 && ix < length) 1258 return SecCertificateRef(CFArrayGetValueAtIndex(mCertChain, ix)); 1259 } 1260 return NULL; 1261} 1262 1263CFArrayRef SecStaticCode::certificates() 1264{ 1265 validateDirectory(); // need cert chain 1266 return mCertChain; 1267} 1268 1269 1270// 1271// Gather (mostly) API-official information about this StaticCode. 1272// 1273// This method lives in the twilight between the API and internal layers, 1274// since it generates API objects (Sec*Refs) for return. 1275// 1276CFDictionaryRef SecStaticCode::signingInformation(SecCSFlags flags) 1277{ 1278 // 1279 // Start with the pieces that we return even for unsigned code. 1280 // This makes Sec[Static]CodeRefs useful as API-level replacements 1281 // of our internal OSXCode objects. 1282 // 1283 CFRef<CFMutableDictionaryRef> dict = makeCFMutableDictionary(1, 1284 kSecCodeInfoMainExecutable, CFTempURL(this->mainExecutablePath()).get() 1285 ); 1286 1287 // 1288 // If we're not signed, this is all you get 1289 // 1290 if (!this->isSigned()) 1291 return dict.yield(); 1292 1293 // 1294 // Add the generic attributes that we always include 1295 // 1296 CFDictionaryAddValue(dict, kSecCodeInfoIdentifier, CFTempString(this->identifier())); 1297 CFDictionaryAddValue(dict, kSecCodeInfoFlags, CFTempNumber(this->codeDirectory(false)->flags.get())); 1298 CFDictionaryAddValue(dict, kSecCodeInfoFormat, CFTempString(this->format())); 1299 CFDictionaryAddValue(dict, kSecCodeInfoSource, CFTempString(this->signatureSource())); 1300 CFDictionaryAddValue(dict, kSecCodeInfoUnique, this->cdHash()); 1301 CFDictionaryAddValue(dict, kSecCodeInfoDigestAlgorithm, CFTempNumber(this->codeDirectory(false)->hashType)); 1302 1303 // 1304 // Deliver any Info.plist only if it looks intact 1305 // 1306 try { 1307 if (CFDictionaryRef info = this->infoDictionary()) 1308 CFDictionaryAddValue(dict, kSecCodeInfoPList, info); 1309 } catch (...) { } // don't deliver Info.plist if questionable 1310 1311 // 1312 // kSecCSSigningInformation adds information about signing certificates and chains 1313 // 1314 if (flags & kSecCSSigningInformation) 1315 try { 1316 if (CFArrayRef certs = this->certificates()) 1317 CFDictionaryAddValue(dict, kSecCodeInfoCertificates, certs); 1318 if (CFDataRef sig = this->signature()) 1319 CFDictionaryAddValue(dict, kSecCodeInfoCMS, sig); 1320 if (mTrust) 1321 CFDictionaryAddValue(dict, kSecCodeInfoTrust, mTrust); 1322 if (CFAbsoluteTime time = this->signingTime()) 1323 if (CFRef<CFDateRef> date = CFDateCreate(NULL, time)) 1324 CFDictionaryAddValue(dict, kSecCodeInfoTime, date); 1325 if (CFAbsoluteTime time = this->signingTimestamp()) 1326 if (CFRef<CFDateRef> date = CFDateCreate(NULL, time)) 1327 CFDictionaryAddValue(dict, kSecCodeInfoTimestamp, date); 1328 if (const char *teamID = this->teamID()) 1329 CFDictionaryAddValue(dict, kSecCodeInfoTeamIdentifier, CFTempString(teamID)); 1330 } catch (...) { } 1331 1332 // 1333 // kSecCSRequirementInformation adds information on requirements 1334 // 1335 if (flags & kSecCSRequirementInformation) 1336 try { 1337 if (const Requirements *reqs = this->internalRequirements()) { 1338 CFDictionaryAddValue(dict, kSecCodeInfoRequirements, 1339 CFTempString(Dumper::dump(reqs))); 1340 CFDictionaryAddValue(dict, kSecCodeInfoRequirementData, CFTempData(*reqs)); 1341 } 1342 1343 const Requirement *dreq = this->designatedRequirement(); 1344 CFRef<SecRequirementRef> dreqRef = (new SecRequirement(dreq))->handle(); 1345 CFDictionaryAddValue(dict, kSecCodeInfoDesignatedRequirement, dreqRef); 1346 if (this->internalRequirement(kSecDesignatedRequirementType)) { // explicit 1347 CFRef<SecRequirementRef> ddreqRef = (new SecRequirement(this->defaultDesignatedRequirement(), true))->handle(); 1348 CFDictionaryAddValue(dict, kSecCodeInfoImplicitDesignatedRequirement, ddreqRef); 1349 } else { // implicit 1350 CFDictionaryAddValue(dict, kSecCodeInfoImplicitDesignatedRequirement, dreqRef); 1351 } 1352 } catch (...) { } 1353 1354 try { 1355 if (CFDataRef ent = this->component(cdEntitlementSlot)) { 1356 CFDictionaryAddValue(dict, kSecCodeInfoEntitlements, ent); 1357 if (CFDictionaryRef entdict = this->entitlements()) 1358 CFDictionaryAddValue(dict, kSecCodeInfoEntitlementsDict, entdict); 1359 } 1360 } catch (...) { } 1361 1362 // 1363 // kSecCSInternalInformation adds internal information meant to be for Apple internal 1364 // use (SPI), and not guaranteed to be stable. Primarily, this is data we want 1365 // to reliably transmit through the API wall so that code outside the Security.framework 1366 // can use it without having to play nasty tricks to get it. 1367 // 1368 if (flags & kSecCSInternalInformation) 1369 try { 1370 if (mDir) 1371 CFDictionaryAddValue(dict, kSecCodeInfoCodeDirectory, mDir); 1372 CFDictionaryAddValue(dict, kSecCodeInfoCodeOffset, CFTempNumber(mRep->signingBase())); 1373 if (CFRef<CFDictionaryRef> rdict = getDictionary(cdResourceDirSlot, false)) // suppress validation 1374 CFDictionaryAddValue(dict, kSecCodeInfoResourceDirectory, rdict); 1375 } catch (...) { } 1376 1377 1378 // 1379 // kSecCSContentInformation adds more information about the physical layout 1380 // of the signed code. This is (only) useful for packaging or patching-oriented 1381 // applications. 1382 // 1383 if (flags & kSecCSContentInformation) 1384 if (CFRef<CFArrayRef> files = mRep->modifiedFiles()) 1385 CFDictionaryAddValue(dict, kSecCodeInfoChangedFiles, files); 1386 1387 return dict.yield(); 1388} 1389 1390 1391// 1392// Resource validation contexts. 1393// The default context simply throws a CSError, rudely terminating the operation. 1394// 1395SecStaticCode::ValidationContext::~ValidationContext() 1396{ /* virtual */ } 1397 1398void SecStaticCode::ValidationContext::reportProblem(OSStatus rc, CFStringRef type, CFTypeRef value) 1399{ 1400 CSError::throwMe(rc, type, value); 1401} 1402 1403void SecStaticCode::CollectingContext::reportProblem(OSStatus rc, CFStringRef type, CFTypeRef value) 1404{ 1405 if (mStatus == errSecSuccess) 1406 mStatus = rc; // record first failure for eventual error return 1407 if (type) { 1408 if (!mCollection) 1409 mCollection.take(makeCFMutableDictionary()); 1410 CFMutableArrayRef element = CFMutableArrayRef(CFDictionaryGetValue(mCollection, type)); 1411 if (!element) { 1412 element = makeCFMutableArray(0); 1413 if (!element) 1414 CFError::throwMe(); 1415 CFDictionaryAddValue(mCollection, type, element); 1416 CFRelease(element); 1417 } 1418 CFArrayAppendValue(element, value); 1419 } 1420} 1421 1422void SecStaticCode::CollectingContext::throwMe() 1423{ 1424 assert(mStatus != errSecSuccess); 1425 throw CSError(mStatus, mCollection.retain()); 1426} 1427 1428 1429// 1430// Master validation driver. 1431// This is the static validation (only) driver for the API. 1432// 1433// SecStaticCode exposes an à la carte menu of topical validators applying 1434// to a given object. The static validation API pulls the together reliably, 1435// but it also adds two matrix dimensions: architecture (for "fat" Mach-O binaries) 1436// and nested code. This function will crawl a suitable cross-section of this 1437// validation matrix based on which options it is given, creating temporary 1438// SecStaticCode objects on the fly to complete the task. 1439// (The point, of course, is to do as little duplicate work as possible.) 1440// 1441void SecStaticCode::staticValidate(SecCSFlags flags, const SecRequirement *req) 1442{ 1443 // core components: once per architecture (if any) 1444 this->staticValidateCore(flags, req); 1445 if (flags & kSecCSCheckAllArchitectures) 1446 handleOtherArchitectures(^(SecStaticCode* subcode) { 1447 subcode->detachedSignature(this->mDetachedSig); // carry over explicit (but not implicit) architecture 1448 subcode->staticValidateCore(flags, req); 1449 }); 1450 1451 // allow monitor intervention in source validation phase 1452 reportEvent(CFSTR("prepared"), NULL); 1453 1454 // resources: once for all architectures 1455 if (!(flags & kSecCSDoNotValidateResources)) 1456 this->validateResources(flags); 1457 1458 // perform strict validation if desired 1459 if (flags & kSecCSStrictValidate) 1460 mRep->strictValidate(mTolerateErrors); 1461 1462 // allow monitor intervention 1463 if (CFRef<CFTypeRef> veto = reportEvent(CFSTR("validated"), NULL)) { 1464 if (CFGetTypeID(veto) == CFNumberGetTypeID()) 1465 MacOSError::throwMe(cfNumber<OSStatus>(veto.as<CFNumberRef>())); 1466 else 1467 MacOSError::throwMe(errSecCSBadCallbackValue); 1468 } 1469} 1470 1471void SecStaticCode::staticValidateCore(SecCSFlags flags, const SecRequirement *req) 1472{ 1473 try { 1474 this->validateNonResourceComponents(); // also validates the CodeDirectory 1475 if (!(flags & kSecCSDoNotValidateExecutable)) 1476 this->validateExecutable(); 1477 if (req) 1478 this->validateRequirement(req->requirement(), errSecCSReqFailed); 1479 } catch (CSError &err) { 1480 if (Universal *fat = this->diskRep()->mainExecutableImage()) // Mach-O 1481 if (MachO *mach = fat->architecture()) { 1482 err.augment(kSecCFErrorArchitecture, CFTempString(mach->architecture().displayName())); 1483 delete mach; 1484 } 1485 throw; 1486 } catch (const MacOSError &err) { 1487 // add architecture information if we can get it 1488 if (Universal *fat = this->diskRep()->mainExecutableImage()) 1489 if (MachO *mach = fat->architecture()) { 1490 CFTempString arch(mach->architecture().displayName()); 1491 delete mach; 1492 CSError::throwMe(err.error, kSecCFErrorArchitecture, arch); 1493 } 1494 throw; 1495 } 1496} 1497 1498 1499// 1500// A helper that generates SecStaticCode objects for all but the primary architecture 1501// of a fat binary and calls a block on them. 1502// If there's only one architecture (or this is an architecture-agnostic code), 1503// nothing happens quickly. 1504// 1505void SecStaticCode::handleOtherArchitectures(void (^handle)(SecStaticCode* other)) 1506{ 1507 if (Universal *fat = this->diskRep()->mainExecutableImage()) { 1508 Universal::Architectures architectures; 1509 fat->architectures(architectures); 1510 if (architectures.size() > 1) { 1511 DiskRep::Context ctx; 1512 size_t activeOffset = fat->archOffset(); 1513 for (Universal::Architectures::const_iterator arch = architectures.begin(); arch != architectures.end(); ++arch) { 1514 ctx.offset = fat->archOffset(*arch); 1515 if (ctx.offset > SIZE_MAX) 1516 MacOSError::throwMe(errSecCSInternalError); 1517 ctx.size = fat->lengthOfSlice((size_t)ctx.offset); 1518 if (ctx.offset != activeOffset) { // inactive architecture; check it 1519 SecPointer<SecStaticCode> subcode = new SecStaticCode(DiskRep::bestGuess(this->mainExecutablePath(), &ctx)); 1520 subcode->detachedSignature(this->mDetachedSig); // carry over explicit (but not implicit) detached signature 1521 if (this->teamID() == NULL || subcode->teamID() == NULL) { 1522 if (this->teamID() != subcode->teamID()) 1523 MacOSError::throwMe(errSecCSSignatureInvalid); 1524 } else if (strcmp(this->teamID(), subcode->teamID()) != 0) 1525 MacOSError::throwMe(errSecCSSignatureInvalid); 1526 handle(subcode); 1527 } 1528 } 1529 } 1530 } 1531} 1532 1533// 1534// A method that takes a certificate chain (certs) and evaluates 1535// if it is a Mac or IPhone developer cert, an app store distribution cert, 1536// or a developer ID 1537// 1538bool SecStaticCode::isAppleDeveloperCert(CFArrayRef certs) 1539{ 1540 static const std::string appleDeveloperRequirement = "(" + std::string(WWDRRequirement) + ") or (" + developerID + ") or (" + distributionCertificate + ") or (" + iPhoneDistributionCert + ")"; 1541 SecRequirement *req = new SecRequirement(parseRequirement(appleDeveloperRequirement), true); 1542 Requirement::Context ctx(certs, NULL, NULL, "", NULL); 1543 1544 return req->requirement()->validates(ctx); 1545} 1546 1547} // end namespace CodeSigning 1548} // end namespace Security 1549