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