StaticCode.cpp
1 /* 2 * Copyright (c) 2006-2015 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 "SecTask.h" 29 #include "Code.h" 30 #include "reqmaker.h" 31 #include "machorep.h" 32 #if TARGET_OS_OSX 33 #include "drmaker.h" 34 #include "notarization.h" 35 #endif 36 #include "reqdumper.h" 37 #include "reqparser.h" 38 #include "sigblob.h" 39 #include "resources.h" 40 #include "detachedrep.h" 41 #include "signerutils.h" 42 #if TARGET_OS_OSX 43 #include "csdatabase.h" 44 #endif 45 #include "dirscanner.h" 46 #include <CoreFoundation/CFURLAccess.h> 47 #include <Security/SecPolicyPriv.h> 48 #include <Security/SecTrustPriv.h> 49 #include <Security/SecCertificatePriv.h> 50 #if TARGET_OS_OSX 51 #include <Security/CMSPrivate.h> 52 #endif 53 #import <Security/SecCMS.h> 54 #include <Security/SecCmsContentInfo.h> 55 #include <Security/SecCmsSignerInfo.h> 56 #include <Security/SecCmsSignedData.h> 57 #if TARGET_OS_OSX 58 #include <Security/cssmapplePriv.h> 59 #endif 60 #include <security_utilities/unix++.h> 61 #include <security_utilities/cfmunge.h> 62 #include <security_utilities/casts.h> 63 #include <Security/CMSDecoder.h> 64 #include <security_utilities/logging.h> 65 #include <dirent.h> 66 #include <sys/xattr.h> 67 #include <sstream> 68 #include <IOKit/storage/IOStorageDeviceCharacteristics.h> 69 #include <dispatch/private.h> 70 #include <os/assumes.h> 71 #include <os/feature_private.h> 72 #include <os/variant_private.h> 73 #include <regex.h> 74 #import <utilities/entitlements.h> 75 76 77 namespace Security { 78 namespace CodeSigning { 79 80 using namespace UnixPlusPlus; 81 82 // A requirement representing a Mac or iOS dev cert, a Mac or iOS distribution cert, or a developer ID 83 static const char WWDRRequirement[] = "anchor apple generic and certificate leaf[field.1.2.840.113635.100.6.1.2] exists"; 84 static const char MACWWDRRequirement[] = "anchor apple generic and certificate leaf[field.1.2.840.113635.100.6.1.12] exists"; 85 static const char developerID[] = "anchor apple generic and certificate 1[field.1.2.840.113635.100.6.2.6] exists" 86 " and certificate leaf[field.1.2.840.113635.100.6.1.13] exists"; 87 static const char distributionCertificate[] = "anchor apple generic and certificate leaf[field.1.2.840.113635.100.6.1.7] exists"; 88 static const char iPhoneDistributionCert[] = "anchor apple generic and certificate leaf[field.1.2.840.113635.100.6.1.4] exists"; 89 90 // 91 // Map a component slot number to a suitable error code for a failure 92 // 93 static inline OSStatus errorForSlot(CodeDirectory::SpecialSlot slot) 94 { 95 switch (slot) { 96 case cdInfoSlot: 97 return errSecCSInfoPlistFailed; 98 case cdResourceDirSlot: 99 return errSecCSResourceDirectoryFailed; 100 default: 101 return errSecCSSignatureFailed; 102 } 103 } 104 105 /// Determines if the current process is marked as platform, cached with dispatch_once. 106 static bool isCurrentProcessPlatform(void) 107 { 108 static dispatch_once_t sOnceToken; 109 static bool sIsPlatform = false; 110 111 dispatch_once(&sOnceToken, ^{ 112 SecTaskRef task = SecTaskCreateFromSelf(NULL); 113 if (task) { 114 uint32_t flags = SecTaskGetCodeSignStatus(task); 115 if (flags & kSecCodeStatusPlatform) { 116 sIsPlatform = true; 117 } 118 CFRelease(task); 119 } 120 }); 121 122 return sIsPlatform; 123 } 124 125 /// Determines if the item qualifies for a resource validity exemption based on its filesystem location. 126 static bool itemQualifiesForResourceExemption(string &item) 127 { 128 if (isOnRootFilesystem(item.c_str())) { 129 return true; 130 } 131 if (os_variant_allows_internal_security_policies("com.apple.security.codesigning")) { 132 if (isPathPrefix("/AppleInternal/", item)) { 133 return true; 134 } 135 } 136 return false; 137 } 138 139 // 140 // Construct a SecStaticCode object given a disk representation object 141 // 142 SecStaticCode::SecStaticCode(DiskRep *rep, uint32_t flags) 143 : mCheckfix30814861builder1(NULL), 144 mRep(rep), 145 mValidated(false), mExecutableValidated(false), mResourcesValidated(false), mResourcesValidContext(NULL), 146 mProgressQueue("com.apple.security.validation-progress", false, QOS_CLASS_UNSPECIFIED), 147 mOuterScope(NULL), mResourceScope(NULL), 148 mDesignatedReq(NULL), mGotResourceBase(false), mMonitor(NULL), mLimitedAsync(NULL), 149 mFlags(flags), mNotarizationChecked(false), mStaplingChecked(false), mNotarizationDate(NAN), 150 mNetworkEnabledByDefault(true), mTrustedSigningCertChain(false) 151 152 { 153 CODESIGN_STATIC_CREATE(this, rep); 154 #if TARGET_OS_OSX 155 checkForSystemSignature(); 156 157 // By default, platform code will no longer use the network. 158 if (os_feature_enabled(Security, SecCodeOCSPDefault)) { 159 if (isCurrentProcessPlatform()) { 160 mNetworkEnabledByDefault = false; 161 } 162 } 163 secinfo("staticCode", "SecStaticCode network default: %s", mNetworkEnabledByDefault ? "YES" : "NO"); 164 #endif 165 } 166 167 168 // 169 // Clean up a SecStaticCode object 170 // 171 SecStaticCode::~SecStaticCode() _NOEXCEPT 172 try { 173 ::free(const_cast<Requirement *>(mDesignatedReq)); 174 delete mResourcesValidContext; 175 delete mLimitedAsync; 176 delete mCheckfix30814861builder1; 177 } catch (...) { 178 return; 179 } 180 181 // 182 // Initialize a nested SecStaticCode object from its parent 183 // 184 void SecStaticCode::initializeFromParent(const SecStaticCode& parent) { 185 mOuterScope = &parent; 186 setMonitor(parent.monitor()); 187 if (parent.mLimitedAsync) 188 mLimitedAsync = new LimitedAsync(*parent.mLimitedAsync); 189 } 190 191 // 192 // CF-level comparison of SecStaticCode objects compares CodeDirectory hashes if signed, 193 // and falls back on comparing canonical paths if (both are) not. 194 // 195 bool SecStaticCode::equal(SecCFObject &secOther) 196 { 197 SecStaticCode *other = static_cast<SecStaticCode *>(&secOther); 198 CFDataRef mine = this->cdHash(); 199 CFDataRef his = other->cdHash(); 200 if (mine || his) 201 return mine && his && CFEqual(mine, his); 202 else 203 return CFEqual(CFRef<CFURLRef>(this->copyCanonicalPath()), CFRef<CFURLRef>(other->copyCanonicalPath())); 204 } 205 206 CFHashCode SecStaticCode::hash() 207 { 208 if (CFDataRef h = this->cdHash()) 209 return CFHash(h); 210 else 211 return CFHash(CFRef<CFURLRef>(this->copyCanonicalPath())); 212 } 213 214 215 // 216 // Invoke a stage monitor if registered 217 // 218 CFTypeRef SecStaticCode::reportEvent(CFStringRef stage, CFDictionaryRef info) 219 { 220 if (mMonitor) 221 return mMonitor(this->handle(false), stage, info); 222 else 223 return NULL; 224 } 225 226 void SecStaticCode::prepareProgress(unsigned int workload) 227 { 228 dispatch_sync(mProgressQueue, ^{ 229 mCancelPending = false; // not canceled 230 }); 231 if (mValidationFlags & kSecCSReportProgress) { 232 mCurrentWork = 0; // nothing done yet 233 mTotalWork = workload; // totally fake - we don't know how many files we'll get to chew 234 } 235 } 236 237 void SecStaticCode::reportProgress(unsigned amount /* = 1 */) 238 { 239 if (mMonitor && (mValidationFlags & kSecCSReportProgress)) { 240 // update progress and report 241 __block bool cancel = false; 242 dispatch_sync(mProgressQueue, ^{ 243 if (mCancelPending) 244 cancel = true; 245 mCurrentWork += amount; 246 mMonitor(this->handle(false), CFSTR("progress"), CFTemp<CFDictionaryRef>("{current=%d,total=%d}", mCurrentWork, mTotalWork)); 247 }); 248 // if cancellation is pending, abort now 249 if (cancel) 250 MacOSError::throwMe(errSecCSCancelled); 251 } 252 } 253 254 255 // 256 // Set validation conditions for fine-tuning legacy tolerance 257 // 258 static void addError(CFTypeRef cfError, void* context) 259 { 260 if (CFGetTypeID(cfError) == CFNumberGetTypeID()) { 261 int64_t error; 262 CFNumberGetValue(CFNumberRef(cfError), kCFNumberSInt64Type, (void*)&error); 263 MacOSErrorSet* errors = (MacOSErrorSet*)context; 264 errors->insert(OSStatus(error)); 265 } 266 } 267 268 void SecStaticCode::setValidationModifiers(CFDictionaryRef conditions) 269 { 270 if (conditions) { 271 CFDictionary source(conditions, errSecCSDbCorrupt); 272 mAllowOmissions = source.get<CFArrayRef>("omissions"); 273 if (CFArrayRef errors = source.get<CFArrayRef>("errors")) 274 CFArrayApplyFunction(errors, CFRangeMake(0, CFArrayGetCount(errors)), addError, &this->mTolerateErrors); 275 } 276 } 277 278 279 // 280 // Request cancellation of a validation in progress. 281 // We do this by posting an abort flag that is checked periodically. 282 // 283 void SecStaticCode::cancelValidation() 284 { 285 if (!(mValidationFlags & kSecCSReportProgress)) // not using progress reporting; cancel won't make it through 286 MacOSError::throwMe(errSecCSInvalidFlags); 287 dispatch_assert_queue(mProgressQueue); 288 mCancelPending = true; 289 } 290 291 292 // 293 // Attach a detached signature. 294 // 295 void SecStaticCode::detachedSignature(CFDataRef sigData) 296 { 297 if (sigData) { 298 mDetachedSig = sigData; 299 mRep = new DetachedRep(sigData, mRep->base(), "explicit detached"); 300 CODESIGN_STATIC_ATTACH_EXPLICIT(this, mRep); 301 } else { 302 mDetachedSig = NULL; 303 mRep = mRep->base(); 304 CODESIGN_STATIC_ATTACH_EXPLICIT(this, NULL); 305 } 306 } 307 308 309 // 310 // Consult the system detached signature database to see if it contains 311 // a detached signature for this StaticCode. If it does, fetch and attach it. 312 // We do this only if the code has no signature already attached. 313 // 314 void SecStaticCode::checkForSystemSignature() 315 { 316 #if TARGET_OS_OSX 317 if (!this->isSigned()) { 318 SignatureDatabase db; 319 if (db.isOpen()) 320 try { 321 if (RefPointer<DiskRep> dsig = db.findCode(mRep)) { 322 CODESIGN_STATIC_ATTACH_SYSTEM(this, dsig); 323 mRep = dsig; 324 } 325 } catch (...) { 326 } 327 } 328 #else 329 MacOSError::throwMe(errSecUnimplemented); 330 #endif 331 } 332 333 334 // 335 // Return a descriptive string identifying the source of the code signature 336 // 337 string SecStaticCode::signatureSource() 338 { 339 if (!isSigned()) 340 return "unsigned"; 341 if (DetachedRep *rep = dynamic_cast<DetachedRep *>(mRep.get())) 342 return rep->source(); 343 return "embedded"; 344 } 345 346 347 // 348 // Do ::required, but convert incoming SecCodeRefs to their SecStaticCodeRefs 349 // (if possible). 350 // 351 SecStaticCode *SecStaticCode::requiredStatic(SecStaticCodeRef ref) 352 { 353 SecCFObject *object = SecCFObject::required(ref, errSecCSInvalidObjectRef); 354 if (SecStaticCode *scode = dynamic_cast<SecStaticCode *>(object)) 355 return scode; 356 else if (SecCode *code = dynamic_cast<SecCode *>(object)) 357 return code->staticCode(); 358 else // neither (a SecSomethingElse) 359 MacOSError::throwMe(errSecCSInvalidObjectRef); 360 } 361 362 SecCode *SecStaticCode::optionalDynamic(SecStaticCodeRef ref) 363 { 364 SecCFObject *object = SecCFObject::required(ref, errSecCSInvalidObjectRef); 365 if (dynamic_cast<SecStaticCode *>(object)) 366 return NULL; 367 else if (SecCode *code = dynamic_cast<SecCode *>(object)) 368 return code; 369 else // neither (a SecSomethingElse) 370 MacOSError::throwMe(errSecCSInvalidObjectRef); 371 } 372 373 374 // 375 // Void all cached validity data. 376 // 377 // We also throw out cached components, because the new signature data may have 378 // a different idea of what components should be present. We could reconcile the 379 // cached data instead, if performance seems to be impacted. 380 // 381 void SecStaticCode::resetValidity() 382 { 383 CODESIGN_EVAL_STATIC_RESET(this); 384 mValidated = false; 385 mExecutableValidated = mResourcesValidated = false; 386 if (mResourcesValidContext) { 387 delete mResourcesValidContext; 388 mResourcesValidContext = NULL; 389 } 390 mDir = NULL; 391 mCodeDirectories.clear(); 392 mSignature = NULL; 393 for (unsigned n = 0; n < cdSlotCount; n++) 394 mCache[n] = NULL; 395 mInfoDict = NULL; 396 mEntitlements = NULL; 397 mResourceDict = NULL; 398 mDesignatedReq = NULL; 399 mCDHash = NULL; 400 mGotResourceBase = false; 401 mTrust = NULL; 402 mCertChain = NULL; 403 mNotarizationChecked = false; 404 mStaplingChecked = false; 405 mNotarizationDate = NAN; 406 mRep->flush(); 407 408 #if TARGET_OS_OSX 409 // we may just have updated the system database, so check again 410 checkForSystemSignature(); 411 #endif 412 } 413 414 415 // 416 // Retrieve a sealed component by special slot index. 417 // If the CodeDirectory has already been validated, validate against that. 418 // Otherwise, retrieve the component without validation (but cache it). Validation 419 // will go through the cache and validate all cached components. 420 // 421 CFDataRef SecStaticCode::component(CodeDirectory::SpecialSlot slot, OSStatus fail /* = errSecCSSignatureFailed */) 422 { 423 assert(slot <= cdSlotMax); 424 425 CFRef<CFDataRef> &cache = mCache[slot]; 426 if (!cache) { 427 if (CFRef<CFDataRef> data = mRep->component(slot)) { 428 if (validated()) { // if the directory has been validated... 429 if (!codeDirectory()->slotIsPresent(-slot)) 430 return NULL; 431 432 if (!codeDirectory()->validateSlot(CFDataGetBytePtr(data), // ... and it's no good 433 CFDataGetLength(data), -slot, false)) 434 MacOSError::throwMe(errorForSlot(slot)); // ... then bail 435 } 436 cache = data; // it's okay, cache it 437 } else { // absent, mark so 438 if (validated()) // if directory has been validated... 439 if (codeDirectory()->slotIsPresent(-slot)) // ... and the slot is NOT missing 440 MacOSError::throwMe(errorForSlot(slot)); // was supposed to be there 441 cache = CFDataRef(kCFNull); // white lie 442 } 443 } 444 return (cache == CFDataRef(kCFNull)) ? NULL : cache.get(); 445 } 446 447 448 // 449 // Get the CodeDirectories. 450 // Throws (if check==true) or returns NULL (check==false) if there are none. 451 // Always throws if the CodeDirectories exist but are invalid. 452 // NEVER validates against the signature. 453 // 454 const SecStaticCode::CodeDirectoryMap * 455 SecStaticCode::codeDirectories(bool check /* = true */) const 456 { 457 if (mCodeDirectories.empty()) { 458 try { 459 loadCodeDirectories(mCodeDirectories); 460 } catch (...) { 461 if (check) 462 throw; 463 // We wanted a NON-checked peek and failed to safely decode the existing CodeDirectories. 464 // Pretend this is unsigned, but make sure we didn't somehow cache an invalid CodeDirectory. 465 if (!mCodeDirectories.empty()) { 466 assert(false); 467 Syslog::warning("code signing internal problem: mCodeDirectories set despite exception exit"); 468 MacOSError::throwMe(errSecCSInternalError); 469 } 470 } 471 } else { 472 return &mCodeDirectories; 473 } 474 if (!mCodeDirectories.empty()) { 475 return &mCodeDirectories; 476 } 477 if (check) { 478 MacOSError::throwMe(errSecCSUnsigned); 479 } 480 return NULL; 481 } 482 483 // 484 // Get the CodeDirectory. 485 // Throws (if check==true) or returns NULL (check==false) if there is none. 486 // Always throws if the CodeDirectory exists but is invalid. 487 // NEVER validates against the signature. 488 // 489 const CodeDirectory *SecStaticCode::codeDirectory(bool check /* = true */) const 490 { 491 if (!mDir) { 492 // pick our favorite CodeDirectory from the choices we've got 493 try { 494 CodeDirectoryMap const *candidates = codeDirectories(check); 495 if (candidates != NULL) { 496 CodeDirectory::HashAlgorithm type = CodeDirectory::bestHashOf(mHashAlgorithms); 497 mDir = candidates->at(type); // and the winner is... 498 } 499 } catch (...) { 500 if (check) 501 throw; 502 // We wanted a NON-checked peek and failed to safely decode the existing CodeDirectory. 503 // Pretend this is unsigned, but make sure we didn't somehow cache an invalid CodeDirectory. 504 if (mDir) { 505 assert(false); 506 Syslog::warning("code signing internal problem: mDir set despite exception exit"); 507 MacOSError::throwMe(errSecCSInternalError); 508 } 509 } 510 } 511 if (mDir) 512 return reinterpret_cast<const CodeDirectory *>(CFDataGetBytePtr(mDir)); 513 if (check) 514 MacOSError::throwMe(errSecCSUnsigned); 515 return NULL; 516 } 517 518 519 // 520 // Fetch an array of all available CodeDirectories. 521 // Returns false if unsigned (no classic CD slot), true otherwise. 522 // 523 bool SecStaticCode::loadCodeDirectories(CodeDirectoryMap& cdMap) const 524 { 525 __block CodeDirectoryMap candidates; 526 __block CodeDirectory::HashAlgorithms hashAlgorithms; 527 __block CFRef<CFDataRef> baseDir; 528 auto add = ^bool (CodeDirectory::SpecialSlot slot){ 529 CFRef<CFDataRef> cdData = diskRep()->component(slot); 530 if (!cdData) 531 return false; 532 const CodeDirectory* cd = reinterpret_cast<const CodeDirectory*>(CFDataGetBytePtr(cdData)); 533 if (!cd->validateBlob(CFDataGetLength(cdData))) 534 MacOSError::throwMe(errSecCSSignatureFailed); // no recovery - any suspect CD fails 535 cd->checkIntegrity(); 536 auto result = candidates.insert(make_pair(cd->hashType, cdData.get())); 537 if (!result.second) 538 MacOSError::throwMe(errSecCSSignatureInvalid); // duplicate hashType, go to heck 539 hashAlgorithms.insert(cd->hashType); 540 if (slot == cdCodeDirectorySlot) 541 baseDir = cdData; 542 return true; 543 }; 544 if (!add(cdCodeDirectorySlot)) 545 return false; // no classic slot CodeDirectory -> unsigned 546 for (CodeDirectory::SpecialSlot slot = cdAlternateCodeDirectorySlots; slot < cdAlternateCodeDirectoryLimit; slot++) 547 if (!add(slot)) // no CodeDirectory at this slot -> end of alternates 548 break; 549 if (candidates.empty()) 550 MacOSError::throwMe(errSecCSSignatureFailed); // no viable CodeDirectory in sight 551 // commit to cached values 552 cdMap.swap(candidates); 553 mHashAlgorithms.swap(hashAlgorithms); 554 mBaseDir = baseDir; 555 return true; 556 } 557 558 559 // 560 // Get the hash of the CodeDirectory. 561 // Returns NULL if there is none. 562 // 563 CFDataRef SecStaticCode::cdHash() 564 { 565 if (!mCDHash) { 566 if (const CodeDirectory *cd = codeDirectory(false)) { 567 mCDHash.take(cd->cdhash()); 568 CODESIGN_STATIC_CDHASH(this, CFDataGetBytePtr(mCDHash), (unsigned int)CFDataGetLength(mCDHash)); 569 } 570 } 571 return mCDHash; 572 } 573 574 575 // 576 // Get an array of the cdhashes for all digest types in this signature 577 // The array is sorted by cd->hashType. 578 // 579 CFArrayRef SecStaticCode::cdHashes() 580 { 581 if (!mCDHashes) { 582 CFRef<CFMutableArrayRef> cdList = makeCFMutableArray(0); 583 for (auto it = mCodeDirectories.begin(); it != mCodeDirectories.end(); ++it) { 584 const CodeDirectory *cd = (const CodeDirectory *)CFDataGetBytePtr(it->second); 585 if (CFRef<CFDataRef> hash = cd->cdhash()) 586 CFArrayAppendValue(cdList, hash); 587 } 588 mCDHashes = cdList.get(); 589 } 590 return mCDHashes; 591 } 592 593 // 594 // Get a dictionary of untruncated cdhashes for all digest types in this signature. 595 // 596 CFDictionaryRef SecStaticCode::cdHashesFull() 597 { 598 if (!mCDHashFullDict) { 599 CFRef<CFMutableDictionaryRef> cdDict = makeCFMutableDictionary(); 600 for (auto const &it : mCodeDirectories) { 601 CodeDirectory::HashAlgorithm alg = it.first; 602 const CodeDirectory *cd = (const CodeDirectory *)CFDataGetBytePtr(it.second); 603 CFRef<CFDataRef> hash = cd->cdhash(false); 604 if (hash) { 605 CFDictionaryAddValue(cdDict, CFTempNumber(alg), hash); 606 } 607 } 608 mCDHashFullDict = cdDict.get(); 609 } 610 return mCDHashFullDict; 611 } 612 613 614 // 615 // Return the CMS signature blob; NULL if none found. 616 // 617 CFDataRef SecStaticCode::signature() 618 { 619 if (!mSignature) 620 mSignature.take(mRep->signature()); 621 if (mSignature) 622 return mSignature; 623 MacOSError::throwMe(errSecCSUnsigned); 624 } 625 626 627 // 628 // Verify the signature on the CodeDirectory. 629 // If this succeeds (doesn't throw), the CodeDirectory is statically trustworthy. 630 // Any outcome (successful or not) is cached for the lifetime of the StaticCode. 631 // 632 void SecStaticCode::validateDirectory() 633 { 634 // echo previous outcome, if any 635 // track revocation separately, as it may not have been checked 636 // during the initial validation 637 if (!validated() || ((mValidationFlags & kSecCSEnforceRevocationChecks) && !revocationChecked())) 638 try { 639 // perform validation (or die trying) 640 CODESIGN_EVAL_STATIC_DIRECTORY(this); 641 mValidationExpired = verifySignature(); 642 if (mValidationFlags & kSecCSEnforceRevocationChecks) 643 mRevocationChecked = true; 644 645 for (CodeDirectory::SpecialSlot slot = codeDirectory()->maxSpecialSlot(); slot >= 1; --slot) 646 if (mCache[slot]) // if we already loaded that resource... 647 validateComponent(slot, errorForSlot(slot)); // ... then check it now 648 mValidated = true; // we've done the deed... 649 mValidationResult = errSecSuccess; // ... and it was good 650 } catch (const CommonError &err) { 651 mValidated = true; 652 mValidationResult = err.osStatus(); 653 throw; 654 } catch (...) { 655 secinfo("staticCode", "%p validation threw non-common exception", this); 656 mValidated = true; 657 Syslog::notice("code signing internal problem: unknown exception thrown by validation"); 658 mValidationResult = errSecCSInternalError; 659 throw; 660 } 661 assert(validated()); 662 // XXX: Embedded doesn't have CSSMERR_TP_CERT_EXPIRED so we can't throw it 663 // XXX: This should be implemented for embedded once we implement 664 // XXX: verifySignature and see how we're going to handle expired certs 665 #if TARGET_OS_OSX 666 if (mValidationResult == errSecSuccess) { 667 if (mValidationExpired) 668 if ((mValidationFlags & kSecCSConsiderExpiration) 669 || (codeDirectory()->flags & kSecCodeSignatureForceExpiration)) 670 MacOSError::throwMe(CSSMERR_TP_CERT_EXPIRED); 671 } else 672 MacOSError::throwMe(mValidationResult); 673 #endif 674 } 675 676 677 // 678 // Load and validate the CodeDirectory and all components *except* those related to the resource envelope. 679 // Those latter components are checked by validateResources(). 680 // 681 void SecStaticCode::validateNonResourceComponents() 682 { 683 this->validateDirectory(); 684 for (CodeDirectory::SpecialSlot slot = codeDirectory()->maxSpecialSlot(); slot >= 1; --slot) 685 switch (slot) { 686 case cdResourceDirSlot: // validated by validateResources 687 break; 688 default: 689 this->component(slot); // loads and validates 690 break; 691 } 692 } 693 694 695 // 696 // Check that any "top index" sealed into the signature conforms to what's actually here. 697 // 698 void SecStaticCode::validateTopDirectory() 699 { 700 assert(mDir); // must already have loaded CodeDirectories 701 if (CFDataRef topDirectory = component(cdTopDirectorySlot)) { 702 const auto topData = (const Endian<uint32_t> *)CFDataGetBytePtr(topDirectory); 703 const auto topDataEnd = topData + CFDataGetLength(topDirectory) / sizeof(*topData); 704 std::vector<uint32_t> signedVector(topData, topDataEnd); 705 706 std::vector<uint32_t> foundVector; 707 foundVector.push_back(cdCodeDirectorySlot); // mandatory 708 for (CodeDirectory::Slot slot = 1; slot <= cdSlotMax; ++slot) 709 if (component(slot)) 710 foundVector.push_back(slot); 711 int alternateCount = int(mCodeDirectories.size() - 1); // one will go into cdCodeDirectorySlot 712 for (int n = 0; n < alternateCount; n++) 713 foundVector.push_back(cdAlternateCodeDirectorySlots + n); 714 foundVector.push_back(cdSignatureSlot); // mandatory (may be empty) 715 716 if (signedVector != foundVector) 717 MacOSError::throwMe(errSecCSSignatureFailed); 718 } 719 } 720 721 722 // 723 // Get the (signed) signing date from the code signature. 724 // Sadly, we need to validate the signature to get the date (as a side benefit). 725 // This means that you can't get the signing time for invalidly signed code. 726 // 727 // We could run the decoder "almost to" verification to avoid this, but there seems 728 // little practical point to such a duplication of effort. 729 // 730 CFAbsoluteTime SecStaticCode::signingTime() 731 { 732 validateDirectory(); 733 return mSigningTime; 734 } 735 736 CFAbsoluteTime SecStaticCode::signingTimestamp() 737 { 738 validateDirectory(); 739 return mSigningTimestamp; 740 } 741 742 #if TARGET_OS_OSX 743 #define kSecSHA256HashSize 32 744 // subject:/C=US/ST=California/L=San Jose/O=Adobe Systems Incorporated/OU=Information Systems/OU=Digital ID Class 3 - Microsoft Software Validation v2/CN=Adobe Systems Incorporated 745 // issuer :/C=US/O=VeriSign, Inc./OU=VeriSign Trust Network/OU=Terms of use at https://www.verisign.com/rpa (c)10/CN=VeriSign Class 3 Code Signing 2010 CA 746 // Not Before: Dec 15 00:00:00 2010 GMT 747 // Not After : Dec 14 23:59:59 2012 GMT 748 static const unsigned char ASI_CS_12[] = { 749 0x77,0x82,0x9C,0x64,0x33,0x45,0x2E,0x4A,0xD3,0xA8,0xE4,0x6F,0x00,0x6C,0x27,0xEA, 750 0xFB,0xD3,0xF2,0x6D,0x50,0xF3,0x6F,0xE0,0xE9,0x6D,0x06,0x59,0x19,0xB5,0x46,0xFF 751 }; 752 753 bool SecStaticCode::checkfix41082220(OSStatus cssmTrustResult) 754 { 755 // only applicable to revoked results 756 if (cssmTrustResult != CSSMERR_TP_CERT_REVOKED) { 757 return false; 758 } 759 760 // only this leaf certificate 761 if (CFArrayGetCount(mCertChain) == 0) { 762 return false; 763 } 764 CFRef<CFDataRef> leafHash(SecCertificateCopySHA256Digest((SecCertificateRef)CFArrayGetValueAtIndex(mCertChain, 0))); 765 if (memcmp(ASI_CS_12, CFDataGetBytePtr(leafHash), kSecSHA256HashSize) != 0) { 766 return false; 767 } 768 769 // detached dmg signature 770 if (!isDetached() || format() != std::string("disk image")) { 771 return false; 772 } 773 774 // sha-1 signed 775 if (hashAlgorithms().size() != 1 || hashAlgorithm() != kSecCodeSignatureHashSHA1) { 776 return false; 777 } 778 779 // not a privileged binary - no TeamID and no entitlements 780 if (component(cdEntitlementSlot) || teamID()) { 781 return false; 782 } 783 784 // no flags and old version 785 if (codeDirectory()->version != 0x20100 || codeDirectory()->flags != 0) { 786 return false; 787 } 788 789 Security::Syslog::warning("CodeSigning: Check-fix enabled for dmg '%s' with identifier '%s' signed with revoked certificates", 790 mainExecutablePath().c_str(), identifier().c_str()); 791 return true; 792 } 793 #endif // TARGET_OS_OSX 794 795 // 796 // Verify the CMS signature. 797 // This performs the cryptographic tango. It returns if the signature is valid, 798 // or throws if it is not. As a side effect, a successful return sets up the 799 // cached certificate chain for future use. 800 // Returns true if the signature is expired (the X.509 sense), false if it's not. 801 // Expiration is fatal (throws) if a secure timestamp is included, but not otherwise. 802 // 803 bool SecStaticCode::verifySignature() 804 { 805 // ad-hoc signed code is considered validly signed by definition 806 if (flag(kSecCodeSignatureAdhoc)) { 807 CODESIGN_EVAL_STATIC_SIGNATURE_ADHOC(this); 808 return false; 809 } 810 811 DTRACK(CODESIGN_EVAL_STATIC_SIGNATURE, this, (char*)this->mainExecutablePath().c_str()); 812 #if TARGET_OS_OSX 813 if (!(mValidationFlags & kSecCSApplyEmbeddedPolicy)) { 814 // decode CMS and extract SecTrust for verification 815 CFRef<CMSDecoderRef> cms; 816 MacOSError::check(CMSDecoderCreate(&cms.aref())); // create decoder 817 CFDataRef sig = this->signature(); 818 MacOSError::check(CMSDecoderUpdateMessage(cms, CFDataGetBytePtr(sig), CFDataGetLength(sig))); 819 this->codeDirectory(); // load CodeDirectory (sets mDir) 820 MacOSError::check(CMSDecoderSetDetachedContent(cms, mBaseDir)); 821 MacOSError::check(CMSDecoderFinalizeMessage(cms)); 822 MacOSError::check(CMSDecoderSetSearchKeychain(cms, cfEmptyArray())); 823 CFRef<CFArrayRef> vf_policies(createVerificationPolicies()); 824 CFRef<CFArrayRef> ts_policies(createTimeStampingAndRevocationPolicies()); 825 826 CMSSignerStatus status; 827 MacOSError::check(CMSDecoderCopySignerStatus(cms, 0, vf_policies, 828 false, &status, &mTrust.aref(), NULL)); 829 830 if (status != kCMSSignerValid) { 831 const char *reason; 832 switch (status) { 833 case kCMSSignerUnsigned: reason="kCMSSignerUnsigned"; break; 834 case kCMSSignerNeedsDetachedContent: reason="kCMSSignerNeedsDetachedContent"; break; 835 case kCMSSignerInvalidSignature: reason="kCMSSignerInvalidSignature"; break; 836 case kCMSSignerInvalidCert: reason="kCMSSignerInvalidCert"; break; 837 case kCMSSignerInvalidIndex: reason="kCMSSignerInvalidIndex"; break; 838 default: reason="unknown"; break; 839 } 840 Security::Syslog::error("CMSDecoderCopySignerStatus failed with %s error (%d)", 841 reason, (int)status); 842 MacOSError::throwMe(errSecCSSignatureFailed); 843 } 844 845 // retrieve auxiliary v1 data bag and verify against current state 846 CFRef<CFDataRef> hashAgilityV1; 847 switch (OSStatus rc = CMSDecoderCopySignerAppleCodesigningHashAgility(cms, 0, &hashAgilityV1.aref())) { 848 case noErr: 849 if (hashAgilityV1) { 850 CFRef<CFDictionaryRef> hashDict = makeCFDictionaryFrom(hashAgilityV1); 851 CFArrayRef cdList = CFArrayRef(CFDictionaryGetValue(hashDict, CFSTR("cdhashes"))); 852 CFArrayRef myCdList = this->cdHashes(); 853 854 /* Note that this is not very "agile": There's no way to calculate the exact 855 * list for comparison if it contains hash algorithms we don't know yet... */ 856 if (cdList == NULL || !CFEqual(cdList, myCdList)) 857 MacOSError::throwMe(errSecCSSignatureFailed); 858 } 859 break; 860 case -1: /* CMS used to return this for "no attribute found", so tolerate it. Now returning noErr/NULL */ 861 break; 862 default: 863 MacOSError::throwMe(rc); 864 } 865 866 // retrieve auxiliary v2 data bag and verify against current state 867 CFRef<CFDictionaryRef> hashAgilityV2; 868 switch (OSStatus rc = CMSDecoderCopySignerAppleCodesigningHashAgilityV2(cms, 0, &hashAgilityV2.aref())) { 869 case noErr: 870 if (hashAgilityV2) { 871 /* Require number of code directoris and entries in the hash agility 872 * dict to be the same size (no stripping out code directories). 873 */ 874 if (CFDictionaryGetCount(hashAgilityV2) != mCodeDirectories.size()) { 875 MacOSError::throwMe(errSecCSSignatureFailed); 876 } 877 878 /* Require every cdhash of every code directory whose hash 879 * algorithm we know to be in the agility dictionary. 880 * 881 * We check untruncated cdhashes here because we can. 882 */ 883 bool foundOurs = false; 884 for (auto& entry : mCodeDirectories) { 885 SECOidTag tag = CodeDirectorySet::SECOidTagForAlgorithm(entry.first); 886 887 if (tag == SEC_OID_UNKNOWN) { 888 // Unknown hash algorithm, ignore. 889 continue; 890 } 891 892 CFRef<CFNumberRef> key = makeCFNumber(int(tag)); 893 CFRef<CFDataRef> entryCdhash; 894 entryCdhash = (CFDataRef)CFDictionaryGetValue(hashAgilityV2, (void*)key.get()); 895 896 CodeDirectory const *cd = (CodeDirectory const*)CFDataGetBytePtr(entry.second); 897 CFRef<CFDataRef> ourCdhash = cd->cdhash(false); // Untruncated cdhash! 898 if (!CFEqual(entryCdhash, ourCdhash)) { 899 MacOSError::throwMe(errSecCSSignatureFailed); 900 } 901 902 if (entry.first == this->hashAlgorithm()) { 903 foundOurs = true; 904 } 905 } 906 907 /* Require the cdhash of our chosen code directory to be in the dictionary. 908 * In theory, the dictionary could be full of unsupported cdhashes, but we 909 * really want ours, which is bound to be supported, to be covered. 910 */ 911 if (!foundOurs) { 912 MacOSError::throwMe(errSecCSSignatureFailed); 913 } 914 } 915 break; 916 case -1: /* CMS used to return this for "no attribute found", so tolerate it. Now returning noErr/NULL */ 917 break; 918 default: 919 MacOSError::throwMe(rc); 920 } 921 922 // internal signing time (as specified by the signer; optional) 923 mSigningTime = 0; // "not present" marker (nobody could code sign on Jan 1, 2001 :-) 924 switch (OSStatus rc = CMSDecoderCopySignerSigningTime(cms, 0, &mSigningTime)) { 925 case errSecSuccess: 926 case errSecSigningTimeMissing: 927 break; 928 default: 929 Security::Syslog::error("Could not get signing time (error %d)", (int)rc); 930 MacOSError::throwMe(rc); 931 } 932 933 // certified signing time (as specified by a TSA; optional) 934 mSigningTimestamp = 0; 935 switch (OSStatus rc = CMSDecoderCopySignerTimestampWithPolicy(cms, ts_policies, 0, &mSigningTimestamp)) { 936 case errSecSuccess: 937 case errSecTimestampMissing: 938 break; 939 default: 940 Security::Syslog::error("Could not get timestamp (error %d)", (int)rc); 941 MacOSError::throwMe(rc); 942 } 943 944 // set up the environment for SecTrust 945 if (validationCannotUseNetwork()) { 946 MacOSError::check(SecTrustSetNetworkFetchAllowed(mTrust, false)); // no network? 947 } 948 MacOSError::check(SecTrustSetKeychainsAllowed(mTrust, false)); 949 950 CSSM_APPLE_TP_ACTION_DATA actionData = { 951 CSSM_APPLE_TP_ACTION_VERSION, // version of data structure 952 0 // action flags 953 }; 954 955 if (!(mValidationFlags & kSecCSCheckTrustedAnchors)) { 956 /* no need to evaluate anchor trust when building cert chain */ 957 MacOSError::check(SecTrustSetAnchorCertificates(mTrust, cfEmptyArray())); // no anchors 958 actionData.ActionFlags |= CSSM_TP_ACTION_IMPLICIT_ANCHORS; // action flags 959 } 960 961 for (;;) { // at most twice 962 MacOSError::check(SecTrustSetParameters(mTrust, 963 CSSM_TP_ACTION_DEFAULT, CFTempData(&actionData, sizeof(actionData)))); 964 965 // evaluate trust and extract results 966 SecTrustResultType trustResult; 967 MacOSError::check(SecTrustEvaluate(mTrust, &trustResult)); 968 mCertChain.take(copyCertChain(mTrust)); 969 970 // if this is an Apple developer cert.... 971 if (teamID() && SecStaticCode::isAppleDeveloperCert(mCertChain)) { 972 CFRef<CFStringRef> teamIDFromCert; 973 if (CFArrayGetCount(mCertChain) > 0) { 974 SecCertificateRef leaf = (SecCertificateRef)CFArrayGetValueAtIndex(mCertChain, Requirement::leafCert); 975 CFArrayRef organizationalUnits = SecCertificateCopyOrganizationalUnit(leaf); 976 if (organizationalUnits) { 977 teamIDFromCert.take((CFStringRef)CFRetain(CFArrayGetValueAtIndex(organizationalUnits, 0))); 978 CFRelease(organizationalUnits); 979 } else { 980 teamIDFromCert = NULL; 981 } 982 983 if (teamIDFromCert) { 984 CFRef<CFStringRef> teamIDFromCD = CFStringCreateWithCString(NULL, teamID(), kCFStringEncodingUTF8); 985 if (!teamIDFromCD) { 986 Security::Syslog::error("Could not get team identifier (%s)", teamID()); 987 MacOSError::throwMe(errSecCSInvalidTeamIdentifier); 988 } 989 990 if (CFStringCompare(teamIDFromCert, teamIDFromCD, 0) != kCFCompareEqualTo) { 991 Security::Syslog::error("Team identifier in the signing certificate (%s) does not match the team identifier (%s) in the code directory", 992 cfString(teamIDFromCert).c_str(), teamID()); 993 MacOSError::throwMe(errSecCSBadTeamIdentifier); 994 } 995 } 996 } 997 } 998 999 CODESIGN_EVAL_STATIC_SIGNATURE_RESULT(this, trustResult, mCertChain ? (int)CFArrayGetCount(mCertChain) : 0); 1000 switch (trustResult) { 1001 case kSecTrustResultProceed: 1002 case kSecTrustResultUnspecified: 1003 break; // success 1004 case kSecTrustResultDeny: 1005 MacOSError::throwMe(CSSMERR_APPLETP_TRUST_SETTING_DENY); // user reject 1006 case kSecTrustResultInvalid: 1007 assert(false); // should never happen 1008 MacOSError::throwMe(CSSMERR_TP_NOT_TRUSTED); 1009 default: 1010 { 1011 OSStatus result; 1012 MacOSError::check(SecTrustGetCssmResultCode(mTrust, &result)); 1013 // if we have a valid timestamp, CMS validates against (that) signing time and all is well. 1014 // If we don't have one, may validate against *now*, and must be able to tolerate expiration. 1015 if (mSigningTimestamp == 0) { // no timestamp available 1016 if (((result == CSSMERR_TP_CERT_EXPIRED) || (result == CSSMERR_TP_CERT_NOT_VALID_YET)) 1017 && !(actionData.ActionFlags & CSSM_TP_ACTION_ALLOW_EXPIRED)) { 1018 CODESIGN_EVAL_STATIC_SIGNATURE_EXPIRED(this); 1019 actionData.ActionFlags |= CSSM_TP_ACTION_ALLOW_EXPIRED; // (this also allows postdated certs) 1020 continue; // retry validation while tolerating expiration 1021 } 1022 } 1023 if (checkfix41082220(result)) { 1024 break; // success 1025 } 1026 Security::Syslog::error("SecStaticCode: verification failed (trust result %d, error %d)", trustResult, (int)result); 1027 MacOSError::throwMe(result); 1028 } 1029 } 1030 1031 if (mSigningTimestamp) { 1032 CFIndex rootix = CFArrayGetCount(mCertChain); 1033 if (SecCertificateRef mainRoot = SecCertificateRef(CFArrayGetValueAtIndex(mCertChain, rootix-1))) 1034 if (isAppleCA(mainRoot)) { 1035 // impose policy: if the signature itself draws to Apple, then so must the timestamp signature 1036 CFRef<CFArrayRef> tsCerts; 1037 OSStatus result = CMSDecoderCopySignerTimestampCertificates(cms, 0, &tsCerts.aref()); 1038 if (result) { 1039 Security::Syslog::error("SecStaticCode: could not get timestamp certificates (error %d)", (int)result); 1040 MacOSError::check(result); 1041 } 1042 CFIndex tsn = CFArrayGetCount(tsCerts); 1043 bool good = tsn > 0 && isAppleCA(SecCertificateRef(CFArrayGetValueAtIndex(tsCerts, tsn-1))); 1044 if (!good) { 1045 result = CSSMERR_TP_NOT_TRUSTED; 1046 Security::Syslog::error("SecStaticCode: timestamp policy verification failed (error %d)", (int)result); 1047 MacOSError::throwMe(result); 1048 } 1049 } 1050 } 1051 1052 return actionData.ActionFlags & CSSM_TP_ACTION_ALLOW_EXPIRED; 1053 } 1054 1055 } else 1056 #endif 1057 { 1058 // Do some pre-verification initialization 1059 CFDataRef sig = this->signature(); 1060 this->codeDirectory(); // load CodeDirectory (sets mDir) 1061 mSigningTime = 0; // "not present" marker (nobody could code sign on Jan 1, 2001 :-) 1062 1063 CFRef<CFDictionaryRef> attrs; 1064 CFRef<CFArrayRef> vf_policies(createVerificationPolicies()); 1065 1066 // Verify the CMS signature against mBaseDir (SHA1) 1067 MacOSError::check(SecCMSVerifyCopyDataAndAttributes(sig, mBaseDir, vf_policies, &mTrust.aref(), NULL, &attrs.aref())); 1068 1069 // Copy the signing time 1070 mSigningTime = SecTrustGetVerifyTime(mTrust); 1071 1072 // Validate the cert chain 1073 SecTrustResultType trustResult; 1074 MacOSError::check(SecTrustEvaluate(mTrust, &trustResult)); 1075 1076 // retrieve auxiliary data bag and verify against current state 1077 CFRef<CFDataRef> hashBag; 1078 hashBag = CFDataRef(CFDictionaryGetValue(attrs, kSecCMSHashAgility)); 1079 if (hashBag) { 1080 CFRef<CFDictionaryRef> hashDict = makeCFDictionaryFrom(hashBag); 1081 CFArrayRef cdList = CFArrayRef(CFDictionaryGetValue(hashDict, CFSTR("cdhashes"))); 1082 CFArrayRef myCdList = this->cdHashes(); 1083 if (cdList == NULL || !CFEqual(cdList, myCdList)) 1084 MacOSError::throwMe(errSecCSSignatureFailed); 1085 } 1086 1087 /* 1088 * Populate mCertChain with the certs. If we failed validation, the 1089 * signer's cert will be checked installed provisioning profiles as an 1090 * alternative to verification against the policy for store-signed binaries 1091 */ 1092 mCertChain.take(copyCertChain(mTrust)); 1093 1094 // Did we implicitly trust the signer? 1095 mTrustedSigningCertChain = (trustResult == kSecTrustResultUnspecified || trustResult == kSecTrustResultProceed); 1096 1097 return false; // XXX: Not checking for expired certs 1098 } 1099 } 1100 1101 #if TARGET_OS_OSX 1102 // 1103 // Return the TP policy used for signature verification. 1104 // This may be a simple SecPolicyRef or a CFArray of policies. 1105 // The caller owns the return value. 1106 // 1107 static SecPolicyRef makeRevocationPolicy(CFOptionFlags flags) 1108 { 1109 CFRef<SecPolicyRef> policy(SecPolicyCreateRevocation(flags)); 1110 return policy.yield(); 1111 } 1112 #endif 1113 1114 bool SecStaticCode::validationCannotUseNetwork() 1115 { 1116 bool blockNetwork = false; 1117 bool validationEnablesNetwork = ((mValidationFlags & kSecCSAllowNetworkAccess) != 0); 1118 bool validationDisablesNetwork = ((mValidationFlags & kSecCSNoNetworkAccess) != 0); 1119 1120 if (mNetworkEnabledByDefault) { 1121 // If network is enabled by default, block it only if the flags explicitly block. 1122 blockNetwork = validationDisablesNetwork; 1123 } else { 1124 // If network is disabled by default, block it if the flags don't explicitly enable it. 1125 blockNetwork = !validationEnablesNetwork; 1126 } 1127 secinfo("staticCode", "SecStaticCode network allowed: %s", blockNetwork ? "NO" : "YES"); 1128 return blockNetwork; 1129 } 1130 1131 CFArrayRef SecStaticCode::createVerificationPolicies() 1132 { 1133 if (mValidationFlags & kSecCSUseSoftwareSigningCert) { 1134 CFRef<SecPolicyRef> ssRef = SecPolicyCreateAppleSoftwareSigning(); 1135 return makeCFArray(1, ssRef.get()); 1136 } 1137 #if TARGET_OS_OSX 1138 if (mValidationFlags & kSecCSApplyEmbeddedPolicy) { 1139 CFRef<SecPolicyRef> iOSRef = SecPolicyCreateiPhoneApplicationSigning(); 1140 return makeCFArray(1, iOSRef.get()); 1141 } 1142 1143 CFRef<SecPolicyRef> core; 1144 MacOSError::check(SecPolicyCopy(CSSM_CERT_X_509v3, 1145 &CSSMOID_APPLE_TP_CODE_SIGNING, &core.aref())); 1146 if (validationCannotUseNetwork()) { 1147 // Skips all revocation since they require network connectivity 1148 // therefore annihilates kSecCSEnforceRevocationChecks if present 1149 CFRef<SecPolicyRef> no_revoc = makeRevocationPolicy(kSecRevocationNetworkAccessDisabled); 1150 return makeCFArray(2, core.get(), no_revoc.get()); 1151 } 1152 else if (mValidationFlags & kSecCSEnforceRevocationChecks) { 1153 // Add CRL and OCSP policies 1154 CFRef<SecPolicyRef> revoc = makeRevocationPolicy(kSecRevocationUseAnyAvailableMethod); 1155 return makeCFArray(2, core.get(), revoc.get()); 1156 } else { 1157 return makeCFArray(1, core.get()); 1158 } 1159 #elif TARGET_OS_TV 1160 CFRef<SecPolicyRef> tvOSRef = SecPolicyCreateAppleTVOSApplicationSigning(); 1161 return makeCFArray(1, tvOSRef.get()); 1162 #else 1163 CFRef<SecPolicyRef> iOSRef = SecPolicyCreateiPhoneApplicationSigning(); 1164 return makeCFArray(1, iOSRef.get()); 1165 #endif 1166 1167 } 1168 1169 CFArrayRef SecStaticCode::createTimeStampingAndRevocationPolicies() 1170 { 1171 CFRef<SecPolicyRef> tsPolicy = SecPolicyCreateAppleTimeStamping(); 1172 #if TARGET_OS_OSX 1173 if (validationCannotUseNetwork()) { 1174 // Skips all revocation since they require network connectivity 1175 // therefore annihilates kSecCSEnforceRevocationChecks if present 1176 CFRef<SecPolicyRef> no_revoc = makeRevocationPolicy(kSecRevocationNetworkAccessDisabled); 1177 return makeCFArray(2, tsPolicy.get(), no_revoc.get()); 1178 } 1179 else if (mValidationFlags & kSecCSEnforceRevocationChecks) { 1180 // Add CRL and OCSP policies 1181 CFRef<SecPolicyRef> revoc = makeRevocationPolicy(kSecRevocationUseAnyAvailableMethod); 1182 return makeCFArray(2, tsPolicy.get(), revoc.get()); 1183 } 1184 else { 1185 return makeCFArray(1, tsPolicy.get()); 1186 } 1187 #else 1188 return makeCFArray(1, tsPolicy.get()); 1189 #endif 1190 1191 } 1192 1193 CFArrayRef SecStaticCode::copyCertChain(SecTrustRef trust) 1194 { 1195 SecCertificateRef leafCert = SecTrustGetCertificateAtIndex(trust, 0); 1196 if (leafCert != NULL) { 1197 CFIndex count = SecTrustGetCertificateCount(trust); 1198 1199 CFMutableArrayRef certs = CFArrayCreateMutable(kCFAllocatorDefault, count, 1200 &kCFTypeArrayCallBacks); 1201 1202 CFArrayAppendValue(certs, leafCert); 1203 for (CFIndex i = 1; i < count; ++i) { 1204 CFArrayAppendValue(certs, SecTrustGetCertificateAtIndex(trust, i)); 1205 } 1206 1207 return certs; 1208 } 1209 return NULL; 1210 } 1211 1212 1213 // 1214 // Validate a particular sealed, cached resource against its (special) CodeDirectory slot. 1215 // The resource must already have been placed in the cache. 1216 // This does NOT perform basic validation. 1217 // 1218 void SecStaticCode::validateComponent(CodeDirectory::SpecialSlot slot, OSStatus fail /* = errSecCSSignatureFailed */) 1219 { 1220 assert(slot <= cdSlotMax); 1221 CFDataRef data = mCache[slot]; 1222 assert(data); // must be cached 1223 if (data == CFDataRef(kCFNull)) { 1224 if (codeDirectory()->slotIsPresent(-slot)) // was supposed to be there... 1225 MacOSError::throwMe(fail); // ... and is missing 1226 } else { 1227 if (!codeDirectory()->validateSlot(CFDataGetBytePtr(data), CFDataGetLength(data), -slot, false)) 1228 MacOSError::throwMe(fail); 1229 } 1230 } 1231 1232 1233 // 1234 // Perform static validation of the main executable. 1235 // This reads the main executable from disk and validates it against the 1236 // CodeDirectory code slot array. 1237 // Note that this is NOT an in-memory validation, and is thus potentially 1238 // subject to timing attacks. 1239 // 1240 void SecStaticCode::validateExecutable() 1241 { 1242 if (!validatedExecutable()) { 1243 try { 1244 DTRACK(CODESIGN_EVAL_STATIC_EXECUTABLE, this, 1245 (char*)this->mainExecutablePath().c_str(), codeDirectory()->nCodeSlots); 1246 const CodeDirectory *cd = this->codeDirectory(); 1247 if (!cd) 1248 MacOSError::throwMe(errSecCSUnsigned); 1249 AutoFileDesc fd(mainExecutablePath(), O_RDONLY); 1250 fd.fcntl(F_NOCACHE, true); // turn off page caching (one-pass) 1251 if (Universal *fat = mRep->mainExecutableImage()) 1252 fd.seek(fat->archOffset()); 1253 size_t pageSize = cd->pageSize ? (1 << cd->pageSize) : 0; 1254 size_t remaining = cd->signingLimit(); 1255 for (uint32_t slot = 0; slot < cd->nCodeSlots; ++slot) { 1256 size_t thisPage = remaining; 1257 if (pageSize) 1258 thisPage = min(thisPage, pageSize); 1259 __block bool good = true; 1260 CodeDirectory::multipleHashFileData(fd, thisPage, hashAlgorithms(), ^(CodeDirectory::HashAlgorithm type, Security::DynamicHash *hasher) { 1261 const CodeDirectory* cd = (const CodeDirectory*)CFDataGetBytePtr(mCodeDirectories[type]); 1262 if (!hasher->verify(cd->getSlot(slot, 1263 mValidationFlags & kSecCSValidatePEH))) 1264 good = false; 1265 }); 1266 if (!good) { 1267 CODESIGN_EVAL_STATIC_EXECUTABLE_FAIL(this, (int)slot); 1268 MacOSError::throwMe(errSecCSSignatureFailed); 1269 } 1270 remaining -= thisPage; 1271 } 1272 assert(remaining == 0); 1273 mExecutableValidated = true; 1274 mExecutableValidResult = errSecSuccess; 1275 } catch (const CommonError &err) { 1276 mExecutableValidated = true; 1277 mExecutableValidResult = err.osStatus(); 1278 throw; 1279 } catch (...) { 1280 secinfo("staticCode", "%p executable validation threw non-common exception", this); 1281 mExecutableValidated = true; 1282 mExecutableValidResult = errSecCSInternalError; 1283 Syslog::notice("code signing internal problem: unknown exception thrown by validation"); 1284 throw; 1285 } 1286 } 1287 assert(validatedExecutable()); 1288 if (mExecutableValidResult != errSecSuccess) 1289 MacOSError::throwMe(mExecutableValidResult); 1290 } 1291 1292 // 1293 // Perform static validation of sealed resources and nested code. 1294 // 1295 // This performs a whole-code static resource scan and effectively 1296 // computes a concordance between what's on disk and what's in the ResourceDirectory. 1297 // Any unsanctioned difference causes an error. 1298 // 1299 unsigned SecStaticCode::estimateResourceWorkload() 1300 { 1301 // workload estimate = number of sealed files 1302 CFDictionaryRef sealedResources = resourceDictionary(); 1303 CFDictionaryRef files = cfget<CFDictionaryRef>(sealedResources, "files2"); 1304 if (files == NULL) 1305 files = cfget<CFDictionaryRef>(sealedResources, "files"); 1306 return files ? unsigned(CFDictionaryGetCount(files)) : 0; 1307 } 1308 1309 void SecStaticCode::validateResources(SecCSFlags flags) 1310 { 1311 // do we have a superset of this requested validation cached? 1312 bool doit = true; 1313 if (mResourcesValidated) { // have cached outcome 1314 if (!(flags & kSecCSCheckNestedCode) || mResourcesDeep) // was deep or need no deep scan 1315 doit = false; 1316 } 1317 1318 if (doit) { 1319 string root = cfStringRelease(copyCanonicalPath()); 1320 bool itemIsOnRootFS = itemQualifiesForResourceExemption(root); 1321 bool skipRootVolumeExceptions = (mValidationFlags & kSecCSSkipRootVolumeExceptions); 1322 bool useRootFSPolicy = itemIsOnRootFS && !skipRootVolumeExceptions; 1323 1324 bool itemMightUseXattrFiles = pathFileSystemUsesXattrFiles(root.c_str()); 1325 bool skipXattrFiles = itemMightUseXattrFiles && (mValidationFlags & kSecCSSkipXattrFiles); 1326 1327 secinfo("staticCode", "performing resource validation for %s (%d, %d, %d, %d, %d)", root.c_str(), 1328 itemIsOnRootFS, skipRootVolumeExceptions, useRootFSPolicy, itemMightUseXattrFiles, skipXattrFiles); 1329 1330 if (mLimitedAsync == NULL) { 1331 bool runMultiThreaded = ((flags & kSecCSSingleThreaded) == kSecCSSingleThreaded) ? false : 1332 (diskRep()->fd().mediumType() == kIOPropertyMediumTypeSolidStateKey); 1333 mLimitedAsync = new LimitedAsync(runMultiThreaded); 1334 } 1335 1336 try { 1337 CFDictionaryRef rules; 1338 CFDictionaryRef files; 1339 uint32_t version; 1340 if (!loadResources(rules, files, version)) 1341 return; // validly no resources; nothing to do (ok) 1342 1343 // found resources, and they are sealed 1344 DTRACK(CODESIGN_EVAL_STATIC_RESOURCES, this, 1345 (char*)this->mainExecutablePath().c_str(), 0); 1346 1347 // scan through the resources on disk, checking each against the resourceDirectory 1348 mResourcesValidContext = new CollectingContext(*this); // collect all failures in here 1349 1350 // check for weak resource rules 1351 bool strict = flags & kSecCSStrictValidate; 1352 if (!useRootFSPolicy) { 1353 if (strict) { 1354 if (hasWeakResourceRules(rules, version, mAllowOmissions)) 1355 if (mTolerateErrors.find(errSecCSWeakResourceRules) == mTolerateErrors.end()) 1356 MacOSError::throwMe(errSecCSWeakResourceRules); 1357 if (version == 1) 1358 if (mTolerateErrors.find(errSecCSWeakResourceEnvelope) == mTolerateErrors.end()) 1359 MacOSError::throwMe(errSecCSWeakResourceEnvelope); 1360 } 1361 } 1362 1363 Dispatch::Group group; 1364 Dispatch::Group &groupRef = group; // (into block) 1365 1366 // scan through the resources on disk, checking each against the resourceDirectory 1367 __block CFRef<CFMutableDictionaryRef> resourceMap = makeCFMutableDictionary(files); 1368 string base = cfString(this->resourceBase()); 1369 ResourceBuilder resources(base, base, rules, strict, mTolerateErrors); 1370 this->mResourceScope = &resources; 1371 diskRep()->adjustResources(resources); 1372 1373 void (^unhandledScanner)(FTSENT *, uint32_t , const string, ResourceBuilder::Rule *) = nil; 1374 1375 if (isFlagSet(flags, kSecCSEnforceRevocationChecks)) { 1376 unhandledScanner = ^(FTSENT *ent, uint32_t ruleFlags, const string relpath, ResourceBuilder::Rule *rule) { 1377 bool userControlledRule = ((ruleFlags & ResourceBuilder::user_controlled) == ResourceBuilder::user_controlled); 1378 secinfo("staticCode", "Visiting unhandled file: %d, %s", userControlledRule, relpath.c_str()); 1379 if (!userControlledRule) { 1380 // No need to look at exemptions added by the runtime rule adjustments (ex. main executable). 1381 return; 1382 } 1383 1384 CFRef<CFURLRef> validationURL; 1385 bool doValidation = false; 1386 switch (ent->fts_info) { 1387 case FTS_SL: 1388 char resolved[PATH_MAX]; 1389 if (realpath(ent->fts_path, resolved)) { 1390 doValidation = true; 1391 validationURL.take(makeCFURL(resolved)); 1392 secinfo("staticCode", "Checking symlink target: %s", resolved); 1393 } else { 1394 secerror("realpath failed checking symlink: %d", errno); 1395 } 1396 break; 1397 case FTS_F: 1398 doValidation = true; 1399 validationURL.take(makeCFURL(relpath, false, resourceBase())); 1400 break; 1401 default: 1402 // Unexpected type for the unhandled scanner. 1403 doValidation = false; 1404 secerror("Unexpected scan input: %d, %s", ent->fts_info, relpath.c_str()); 1405 break; 1406 } 1407 1408 if (doValidation) { 1409 // Here we yield our reference to hand over to the block's CFRef object, which will 1410 // hold it until the block is complete and also handle releasing in case of an exception. 1411 CFURLRef transferURL = validationURL.yield(); 1412 1413 void (^validate)() = ^{ 1414 CFRef<CFURLRef> localURL = transferURL; 1415 AutoFileDesc fd(cfString(localURL), O_RDONLY, FileDesc::modeMissingOk); 1416 checkRevocationOnNestedBinary(fd, localURL, flags); 1417 }; 1418 mLimitedAsync->perform(groupRef, validate); 1419 } 1420 }; 1421 } 1422 1423 void (^validationScanner)(FTSENT *, uint32_t , const string, ResourceBuilder::Rule *) = ^(FTSENT *ent, uint32_t ruleFlags, const string relpath, ResourceBuilder::Rule *rule) { 1424 CFDictionaryRemoveValue(resourceMap, CFTempString(relpath)); 1425 bool isSymlink = (ent->fts_info == FTS_SL); 1426 1427 void (^validate)() = ^{ 1428 bool needsValidation = true; 1429 1430 if (skipXattrFiles && pathIsValidXattrFile(cfString(resourceBase()) + "/" + relpath, "staticCode")) { 1431 secinfo("staticCode", "resource validation on xattr file skipped: %s", relpath.c_str()); 1432 needsValidation = false; 1433 } 1434 1435 if (useRootFSPolicy) { 1436 CFRef<CFURLRef> itemURL = makeCFURL(relpath, false, resourceBase()); 1437 string itemPath = cfString(itemURL); 1438 if (itemQualifiesForResourceExemption(itemPath)) { 1439 secinfo("staticCode", "resource validation on root volume skipped: %s", itemPath.c_str()); 1440 needsValidation = false; 1441 } 1442 } 1443 1444 if (needsValidation) { 1445 secinfo("staticCode", "performing resource validation on item: %s", relpath.c_str()); 1446 validateResource(files, relpath, isSymlink, *mResourcesValidContext, flags, version); 1447 } 1448 reportProgress(); 1449 }; 1450 1451 mLimitedAsync->perform(groupRef, validate); 1452 }; 1453 1454 resources.scan(validationScanner, unhandledScanner); 1455 group.wait(); // wait until all async resources have been validated as well 1456 1457 if (useRootFSPolicy) { 1458 // It's ok to allow leftovers on the root filesystem for now. 1459 } else { 1460 // Look through the leftovers and make sure they're all properly optional resources. 1461 unsigned leftovers = unsigned(CFDictionaryGetCount(resourceMap)); 1462 if (leftovers > 0) { 1463 secinfo("staticCode", "%d sealed resource(s) not found in code", int(leftovers)); 1464 CFDictionaryApplyFunction(resourceMap, SecStaticCode::checkOptionalResource, mResourcesValidContext); 1465 } 1466 } 1467 1468 // now check for any errors found in the reporting context 1469 mResourcesValidated = true; 1470 mResourcesDeep = flags & kSecCSCheckNestedCode; 1471 if (mResourcesValidContext->osStatus() != errSecSuccess) 1472 mResourcesValidContext->throwMe(); 1473 } catch (const CommonError &err) { 1474 mResourcesValidated = true; 1475 mResourcesDeep = flags & kSecCSCheckNestedCode; 1476 mResourcesValidResult = err.osStatus(); 1477 throw; 1478 } catch (...) { 1479 secinfo("staticCode", "%p executable validation threw non-common exception", this); 1480 mResourcesValidated = true; 1481 mResourcesDeep = flags & kSecCSCheckNestedCode; 1482 mResourcesValidResult = errSecCSInternalError; 1483 Syslog::notice("code signing internal problem: unknown exception thrown by validation"); 1484 throw; 1485 } 1486 } 1487 assert(validatedResources()); 1488 if (mResourcesValidResult) 1489 MacOSError::throwMe(mResourcesValidResult); 1490 if (mResourcesValidContext->osStatus() != errSecSuccess) 1491 mResourcesValidContext->throwMe(); 1492 } 1493 1494 1495 bool SecStaticCode::loadResources(CFDictionaryRef& rules, CFDictionaryRef& files, uint32_t& version) 1496 { 1497 // sanity first 1498 CFDictionaryRef sealedResources = resourceDictionary(); 1499 if (this->resourceBase()) { // disk has resources 1500 if (sealedResources) 1501 /* go to work below */; 1502 else 1503 MacOSError::throwMe(errSecCSResourcesNotFound); 1504 } else { // disk has no resources 1505 if (sealedResources) 1506 MacOSError::throwMe(errSecCSResourcesNotFound); 1507 else 1508 return false; // no resources, not sealed - fine (no work) 1509 } 1510 1511 // use V2 resource seal if available, otherwise fall back to V1 1512 if (CFDictionaryGetValue(sealedResources, CFSTR("files2"))) { // have V2 signature 1513 rules = cfget<CFDictionaryRef>(sealedResources, "rules2"); 1514 files = cfget<CFDictionaryRef>(sealedResources, "files2"); 1515 version = 2; 1516 } else { // only V1 available 1517 rules = cfget<CFDictionaryRef>(sealedResources, "rules"); 1518 files = cfget<CFDictionaryRef>(sealedResources, "files"); 1519 version = 1; 1520 } 1521 if (!rules || !files) 1522 MacOSError::throwMe(errSecCSResourcesInvalid); 1523 return true; 1524 } 1525 1526 1527 void SecStaticCode::checkOptionalResource(CFTypeRef key, CFTypeRef value, void *context) 1528 { 1529 ValidationContext *ctx = static_cast<ValidationContext *>(context); 1530 ResourceSeal seal(value); 1531 if (!seal.optional()) { 1532 if (key && CFGetTypeID(key) == CFStringGetTypeID()) { 1533 CFTempURL tempURL(CFStringRef(key), false, ctx->code.resourceBase()); 1534 if (!tempURL.get()) { 1535 ctx->reportProblem(errSecCSBadDictionaryFormat, kSecCFErrorResourceSeal, key); 1536 } else { 1537 ctx->reportProblem(errSecCSBadResource, kSecCFErrorResourceMissing, tempURL); 1538 } 1539 } else { 1540 ctx->reportProblem(errSecCSBadResource, kSecCFErrorResourceSeal, key); 1541 } 1542 } 1543 } 1544 1545 1546 static bool isOmitRule(CFTypeRef value) 1547 { 1548 if (CFGetTypeID(value) == CFBooleanGetTypeID()) 1549 return value == kCFBooleanFalse; 1550 CFDictionary rule(value, errSecCSResourceRulesInvalid); 1551 return rule.get<CFBooleanRef>("omit") == kCFBooleanTrue; 1552 } 1553 1554 bool SecStaticCode::hasWeakResourceRules(CFDictionaryRef rulesDict, uint32_t version, CFArrayRef allowedOmissions) 1555 { 1556 // compute allowed omissions 1557 CFRef<CFArrayRef> defaultOmissions = this->diskRep()->allowedResourceOmissions(); 1558 if (!defaultOmissions) { 1559 Syslog::notice("code signing internal problem: diskRep returned no allowedResourceOmissions"); 1560 MacOSError::throwMe(errSecCSInternalError); 1561 } 1562 CFRef<CFMutableArrayRef> allowed = CFArrayCreateMutableCopy(NULL, 0, defaultOmissions); 1563 if (allowedOmissions) 1564 CFArrayAppendArray(allowed, allowedOmissions, CFRangeMake(0, CFArrayGetCount(allowedOmissions))); 1565 CFRange range = CFRangeMake(0, CFArrayGetCount(allowed)); 1566 1567 // check all resource rules for weakness 1568 string catchAllRule = (version == 1) ? "^Resources/" : "^.*"; 1569 __block bool coversAll = false; 1570 __block bool forbiddenOmission = false; 1571 CFArrayRef allowedRef = allowed.get(); // (into block) 1572 CFDictionary rules(rulesDict, errSecCSResourceRulesInvalid); 1573 rules.apply(^(CFStringRef key, CFTypeRef value) { 1574 string pattern = cfString(key, errSecCSResourceRulesInvalid); 1575 if (pattern == catchAllRule && value == kCFBooleanTrue) { 1576 coversAll = true; 1577 return; 1578 } 1579 if (isOmitRule(value)) 1580 forbiddenOmission |= !CFArrayContainsValue(allowedRef, range, key); 1581 }); 1582 1583 return !coversAll || forbiddenOmission; 1584 } 1585 1586 1587 // 1588 // Load, validate, cache, and return CFDictionary forms of sealed resources. 1589 // 1590 CFDictionaryRef SecStaticCode::infoDictionary() 1591 { 1592 if (!mInfoDict) { 1593 mInfoDict.take(getDictionary(cdInfoSlot, errSecCSInfoPlistFailed)); 1594 secinfo("staticCode", "%p loaded InfoDict %p", this, mInfoDict.get()); 1595 } 1596 return mInfoDict; 1597 } 1598 1599 CFDictionaryRef SecStaticCode::entitlements() 1600 { 1601 if (!mEntitlements) { 1602 validateDirectory(); 1603 if (CFDataRef entitlementData = component(cdEntitlementSlot)) { 1604 validateComponent(cdEntitlementSlot); 1605 const EntitlementBlob *blob = reinterpret_cast<const EntitlementBlob *>(CFDataGetBytePtr(entitlementData)); 1606 if (blob->validateBlob()) { 1607 mEntitlements.take(blob->entitlements()); 1608 secinfo("staticCode", "%p loaded Entitlements %p", this, mEntitlements.get()); 1609 } 1610 // we do not consider a different blob type to be an error. We think it's a new format we don't understand 1611 } 1612 } 1613 return mEntitlements; 1614 } 1615 1616 CFDictionaryRef SecStaticCode::resourceDictionary(bool check /* = true */) 1617 { 1618 if (mResourceDict) // cached 1619 return mResourceDict; 1620 if (CFRef<CFDictionaryRef> dict = getDictionary(cdResourceDirSlot, check)) 1621 if (cfscan(dict, "{rules=%Dn,files=%Dn}")) { 1622 secinfo("staticCode", "%p loaded ResourceDict %p", 1623 this, mResourceDict.get()); 1624 return mResourceDict = dict; 1625 } 1626 // bad format 1627 return NULL; 1628 } 1629 1630 1631 CFDataRef SecStaticCode::copyComponent(CodeDirectory::SpecialSlot slot, CFDataRef hash) 1632 { 1633 const CodeDirectory* cd = this->codeDirectory(); 1634 if (CFCopyRef<CFDataRef> component = this->component(slot)) { 1635 if (hash) { 1636 const void *slotHash = cd->getSlot(slot, false); 1637 if (cd->hashSize != CFDataGetLength(hash) || 0 != memcmp(slotHash, CFDataGetBytePtr(hash), cd->hashSize)) { 1638 Syslog::notice("copyComponent hash mismatch slot %d length %d", slot, int(CFDataGetLength(hash))); 1639 return NULL; // mismatch 1640 } 1641 } 1642 return component.yield(); 1643 } 1644 return NULL; 1645 } 1646 1647 1648 1649 // 1650 // Load and cache the resource directory base. 1651 // Note that the base is optional for each DiskRep. 1652 // 1653 CFURLRef SecStaticCode::resourceBase() 1654 { 1655 if (!mGotResourceBase) { 1656 string base = mRep->resourcesRootPath(); 1657 if (!base.empty()) 1658 mResourceBase.take(makeCFURL(base, true)); 1659 mGotResourceBase = true; 1660 } 1661 return mResourceBase; 1662 } 1663 1664 1665 // 1666 // Load a component, validate it, convert it to a CFDictionary, and return that. 1667 // This will force load and validation, which means that it will perform basic 1668 // validation if it hasn't been done yet. 1669 // 1670 CFDictionaryRef SecStaticCode::getDictionary(CodeDirectory::SpecialSlot slot, bool check /* = true */) 1671 { 1672 if (check) 1673 validateDirectory(); 1674 if (CFDataRef infoData = component(slot)) { 1675 validateComponent(slot); 1676 if (CFDictionaryRef dict = makeCFDictionaryFrom(infoData)) 1677 return dict; 1678 else 1679 MacOSError::throwMe(errSecCSBadDictionaryFormat); 1680 } 1681 return NULL; 1682 } 1683 1684 // 1685 // 1686 // 1687 CFDictionaryRef SecStaticCode::copyDiskRepInformation() 1688 { 1689 return mRep->copyDiskRepInformation(); 1690 } 1691 1692 bool SecStaticCode::checkfix30814861(string path, bool addition) { 1693 // <rdar://problem/30814861> v2 resource rules don't match v1 resource rules 1694 1695 //// Condition 1: Is the app an iOS app that was built with an SDK lower than 9.0? 1696 1697 // We started signing correctly in 2014, 9.0 was first seeded mid-2016. 1698 1699 CFRef<CFDictionaryRef> inf = copyDiskRepInformation(); 1700 try { 1701 CFDictionary info(inf.get(), errSecCSNotSupported); 1702 uint32_t platform = 1703 cfNumber(info.get<CFNumberRef>(kSecCodeInfoDiskRepVersionPlatform, errSecCSNotSupported), 0); 1704 uint32_t sdkVersion = 1705 cfNumber(info.get<CFNumberRef>(kSecCodeInfoDiskRepVersionSDK, errSecCSNotSupported), 0); 1706 1707 if (platform != PLATFORM_IOS || sdkVersion >= 0x00090000) { 1708 return false; 1709 } 1710 } catch (const MacOSError &error) { 1711 return false; 1712 } 1713 1714 //// Condition 2: Is it a .sinf/.supf/.supp file at the right location? 1715 1716 static regex_t pathre_sinf; 1717 static regex_t pathre_supp_supf; 1718 static dispatch_once_t once; 1719 1720 dispatch_once(&once, ^{ 1721 os_assert_zero(regcomp(&pathre_sinf, 1722 "^(Frameworks/[^/]+\\.framework/|PlugIns/[^/]+\\.appex/|())SC_Info/[^/]+\\.sinf$", 1723 REG_EXTENDED | REG_NOSUB)); 1724 os_assert_zero(regcomp(&pathre_supp_supf, 1725 "^(Frameworks/[^/]+\\.framework/|PlugIns/[^/]+\\.appex/|())SC_Info/[^/]+\\.(supf|supp)$", 1726 REG_EXTENDED | REG_NOSUB)); 1727 }); 1728 1729 // .sinf is added, .supf/.supp are modified. 1730 const regex_t &pathre = addition ? pathre_sinf : pathre_supp_supf; 1731 1732 const int result = regexec(&pathre, path.c_str(), 0, NULL, 0); 1733 1734 if (result == REG_NOMATCH) { 1735 return false; 1736 } else if (result != 0) { 1737 // Huh? 1738 secerror("unexpected regexec result %d for path '%s'", result, path.c_str()); 1739 return false; 1740 } 1741 1742 //// Condition 3: Do the v1 rules actually exclude the file? 1743 1744 dispatch_once(&mCheckfix30814861builder1_once, ^{ 1745 // Create the v1 resource builder lazily. 1746 CFDictionaryRef rules1 = cfget<CFDictionaryRef>(resourceDictionary(), "rules"); 1747 const string base = cfString(resourceBase()); 1748 1749 mCheckfix30814861builder1 = new ResourceBuilder(base, base, rules1, false, mTolerateErrors); 1750 }); 1751 1752 ResourceBuilder::Rule const * const matchingRule = mCheckfix30814861builder1->findRule(path); 1753 1754 if (matchingRule == NULL || !(matchingRule->flags & ResourceBuilder::omitted)) { 1755 return false; 1756 } 1757 1758 //// All matched, this file is a check-fixed sinf/supf/supp. 1759 1760 return true; 1761 1762 } 1763 1764 void SecStaticCode::validateResource(CFDictionaryRef files, string path, bool isSymlink, ValidationContext &ctx, SecCSFlags flags, uint32_t version) 1765 { 1766 if (!resourceBase()) // no resources in DiskRep 1767 MacOSError::throwMe(errSecCSResourcesNotFound); 1768 CFRef<CFURLRef> fullpath = makeCFURL(path, false, resourceBase()); 1769 if (version > 1 && ((flags & (kSecCSStrictValidate|kSecCSRestrictSidebandData)) == (kSecCSStrictValidate|kSecCSRestrictSidebandData))) { 1770 AutoFileDesc fd(cfString(fullpath)); 1771 if (fd.hasExtendedAttribute(XATTR_RESOURCEFORK_NAME) || fd.hasExtendedAttribute(XATTR_FINDERINFO_NAME)) 1772 ctx.reportProblem(errSecCSInvalidAssociatedFileData, kSecCFErrorResourceSideband, fullpath); 1773 } 1774 if (CFTypeRef file = CFDictionaryGetValue(files, CFTempString(path))) { 1775 ResourceSeal seal(file); 1776 const ResourceSeal& rseal = seal; 1777 if (seal.nested()) { 1778 if (isSymlink) { 1779 return ctx.reportProblem(errSecCSBadResource, kSecCFErrorResourceAltered, fullpath); // changed type 1780 } 1781 string suffix = ".framework"; 1782 bool isFramework = (path.length() > suffix.length()) && 1783 (path.compare(path.length()-suffix.length(), suffix.length(), suffix) == 0); 1784 validateNestedCode(fullpath, seal, flags, isFramework); 1785 } else if (seal.link()) { 1786 if (!isSymlink) { 1787 return ctx.reportProblem(errSecCSBadResource, kSecCFErrorResourceAltered, fullpath); // changed type 1788 } 1789 validateSymlinkResource(cfString(fullpath), cfString(seal.link()), ctx, flags); 1790 } else if (seal.hash(hashAlgorithm())) { // genuine file 1791 if (isSymlink) { 1792 return ctx.reportProblem(errSecCSBadResource, kSecCFErrorResourceAltered, fullpath); // changed type 1793 } 1794 AutoFileDesc fd(cfString(fullpath), O_RDONLY, FileDesc::modeMissingOk); // open optional file 1795 if (fd) { 1796 __block bool good = true; 1797 CodeDirectory::multipleHashFileData(fd, 0, hashAlgorithms(), ^(CodeDirectory::HashAlgorithm type, Security::DynamicHash *hasher) { 1798 if (!hasher->verify(rseal.hash(type))) 1799 good = false; 1800 }); 1801 if (!good) { 1802 if (version == 2 && checkfix30814861(path, false)) { 1803 secinfo("validateResource", "%s check-fixed (altered).", path.c_str()); 1804 } else { 1805 ctx.reportProblem(errSecCSBadResource, kSecCFErrorResourceAltered, fullpath); // altered 1806 } 1807 } 1808 1809 if (good && isFlagSet(flags, kSecCSEnforceRevocationChecks)) { 1810 checkRevocationOnNestedBinary(fd, fullpath, flags); 1811 } 1812 } else { 1813 if (!seal.optional()) { 1814 ctx.reportProblem(errSecCSBadResource, kSecCFErrorResourceMissing, fullpath); // was sealed but is now missing 1815 } else { 1816 return; // validly missing 1817 } 1818 } 1819 } else { 1820 ctx.reportProblem(errSecCSBadResource, kSecCFErrorResourceAltered, fullpath); // changed type 1821 } 1822 return; 1823 } 1824 if (version == 1) { // version 1 ignores symlinks altogether 1825 char target[PATH_MAX]; 1826 if (::readlink(cfString(fullpath).c_str(), target, sizeof(target)) > 0) 1827 return; 1828 } 1829 if (version == 2 && checkfix30814861(path, true)) { 1830 secinfo("validateResource", "%s check-fixed (added).", path.c_str()); 1831 } else { 1832 ctx.reportProblem(errSecCSBadResource, kSecCFErrorResourceAdded, CFTempURL(path, false, resourceBase())); 1833 } 1834 } 1835 1836 void SecStaticCode::validatePlainMemoryResource(string path, CFDataRef fileData, SecCSFlags flags) 1837 { 1838 CFDictionaryRef rules; 1839 CFDictionaryRef files; 1840 uint32_t version; 1841 if (!loadResources(rules, files, version)) 1842 MacOSError::throwMe(errSecCSResourcesNotFound); // no resources sealed; this can't be right 1843 if (CFTypeRef file = CFDictionaryGetValue(files, CFTempString(path))) { 1844 ResourceSeal seal(file); 1845 const Byte *sealHash = seal.hash(hashAlgorithm()); 1846 if (sealHash) { 1847 if (codeDirectory()->verifyMemoryContent(fileData, sealHash)) 1848 return; // success 1849 } 1850 } 1851 MacOSError::throwMe(errSecCSBadResource); 1852 } 1853 1854 void SecStaticCode::validateSymlinkResource(std::string fullpath, std::string seal, ValidationContext &ctx, SecCSFlags flags) 1855 { 1856 static const char* const allowedDestinations[] = { 1857 "/System/", 1858 "/Library/", 1859 NULL 1860 }; 1861 char target[PATH_MAX]; 1862 ssize_t len = ::readlink(fullpath.c_str(), target, sizeof(target)-1); 1863 if (len < 0) 1864 UnixError::check(-1); 1865 target[len] = '\0'; 1866 std::string fulltarget = target; 1867 if (target[0] != '/') { 1868 size_t lastSlash = fullpath.rfind('/'); 1869 fulltarget = fullpath.substr(0, lastSlash) + '/' + target; 1870 } 1871 if (seal != target) { 1872 ctx.reportProblem(errSecCSBadResource, kSecCFErrorResourceAltered, CFTempString(fullpath)); 1873 return; 1874 } 1875 if ((mValidationFlags & (kSecCSStrictValidate|kSecCSRestrictSymlinks)) == (kSecCSStrictValidate|kSecCSRestrictSymlinks)) { 1876 char resolved[PATH_MAX]; 1877 if (realpath(fulltarget.c_str(), resolved)) { 1878 assert(resolved[0] == '/'); 1879 size_t rlen = strlen(resolved); 1880 if (target[0] == '/') { 1881 // absolute symlink; only allow absolute links to system locations 1882 for (const char* const* pathp = allowedDestinations; *pathp; pathp++) { 1883 size_t dlen = strlen(*pathp); 1884 if (rlen > dlen && strncmp(resolved, *pathp, dlen) == 0) 1885 return; // target inside /System, deemed okay 1886 } 1887 } else { 1888 // everything else must be inside the bundle(s) 1889 for (const SecStaticCode* code = this; code; code = code->mOuterScope) { 1890 string root = code->mResourceScope->root(); 1891 if (strncmp(resolved, root.c_str(), root.size()) == 0) { 1892 if (code->mResourceScope->includes(resolved + root.length() + 1)) 1893 return; // located in resource stack && included in envelope 1894 else 1895 break; // located but excluded from envelope (deny) 1896 } 1897 } 1898 } 1899 } 1900 // if we fell through, flag a symlink error 1901 if (mTolerateErrors.find(errSecCSInvalidSymlink) == mTolerateErrors.end()) 1902 ctx.reportProblem(errSecCSInvalidSymlink, kSecCFErrorResourceAltered, CFTempString(fullpath)); 1903 } 1904 } 1905 1906 /// Uses the provided file descriptor to check if the file is macho, and if so validates the file at the url as a binary to check for a revoked certificate. 1907 void SecStaticCode::checkRevocationOnNestedBinary(UnixPlusPlus::FileDesc &fd, CFURLRef url, SecCSFlags flags) 1908 { 1909 #if TARGET_OS_OSX 1910 secinfo("staticCode", "validating embedded resource: %@", url); 1911 1912 try { 1913 SecPointer<SecStaticCode> code; 1914 1915 if (MachORep::candidate(fd)) { 1916 DiskRep *rep = new MachORep(cfString(url).c_str(), NULL); 1917 if (rep) { 1918 code = new SecStaticCode(rep); 1919 } 1920 } 1921 1922 if (code) { 1923 code->initializeFromParent(*this); 1924 code->setValidationFlags(flags); 1925 // Validate just the code directory, which performs signature validation. 1926 code->validateDirectory(); 1927 secinfo("staticCode", "successfully validated nested resource binary: %@", url); 1928 } 1929 } catch (const MacOSError &err) { 1930 if (err.error == CSSMERR_TP_CERT_REVOKED) { 1931 secerror("Rejecting binary with revoked certificate: %@", url); 1932 throw; 1933 } else { 1934 // Any other errors, but only revocation checks are fatal so just continue. 1935 secinfo("staticCode", "Found unexpected error other error validating resource binary: %d, %@", err.error, url); 1936 } 1937 } 1938 #else 1939 // This type of resource checking doesn't make sense on embedded devices right now, so just do nothing. 1940 return; 1941 #endif // TARGET_OS_OSX 1942 } 1943 1944 void SecStaticCode::validateNestedCode(CFURLRef path, const ResourceSeal &seal, SecCSFlags flags, bool isFramework) 1945 { 1946 CFRef<SecRequirementRef> req; 1947 if (SecRequirementCreateWithString(seal.requirement(), kSecCSDefaultFlags, &req.aref())) 1948 MacOSError::throwMe(errSecCSResourcesInvalid); 1949 1950 // recursively verify this nested code 1951 try { 1952 if (!(flags & kSecCSCheckNestedCode)) { 1953 flags |= kSecCSBasicValidateOnly | kSecCSQuickCheck; 1954 } 1955 SecPointer<SecStaticCode> code = new SecStaticCode(DiskRep::bestGuess(cfString(path))); 1956 code->initializeFromParent(*this); 1957 code->staticValidate(flags & (~kSecCSRestrictToAppLike), SecRequirement::required(req)); 1958 1959 if (isFramework && (flags & kSecCSStrictValidate)) 1960 try { 1961 validateOtherVersions(path, flags & (~kSecCSRestrictToAppLike), req, code); 1962 } catch (const CSError &err) { 1963 MacOSError::throwMe(errSecCSBadFrameworkVersion); 1964 } catch (const MacOSError &err) { 1965 MacOSError::throwMe(errSecCSBadFrameworkVersion); 1966 } 1967 1968 } catch (CSError &err) { 1969 if (err.error == errSecCSReqFailed) { 1970 mResourcesValidContext->reportProblem(errSecCSBadNestedCode, kSecCFErrorResourceAltered, path); 1971 return; 1972 } 1973 err.augment(kSecCFErrorPath, path); 1974 throw; 1975 } catch (const MacOSError &err) { 1976 if (err.error == errSecCSReqFailed) { 1977 mResourcesValidContext->reportProblem(errSecCSBadNestedCode, kSecCFErrorResourceAltered, path); 1978 return; 1979 } 1980 CSError::throwMe(err.error, kSecCFErrorPath, path); 1981 } 1982 } 1983 1984 void SecStaticCode::validateOtherVersions(CFURLRef path, SecCSFlags flags, SecRequirementRef req, SecStaticCode *code) 1985 { 1986 // Find out what current points to and do not revalidate 1987 std::string mainPath = cfStringRelease(code->diskRep()->copyCanonicalPath()); 1988 1989 char main_path[PATH_MAX]; 1990 bool foundTarget = false; 1991 1992 /* If it failed to get the target of the symlink, do not fail. It is a performance loss, 1993 not a security hole */ 1994 if (realpath(mainPath.c_str(), main_path) != NULL) 1995 foundTarget = true; 1996 1997 std::ostringstream versionsPath; 1998 versionsPath << cfString(path) << "/Versions/"; 1999 2000 DirScanner scanner(versionsPath.str()); 2001 2002 if (scanner.initialized()) { 2003 struct dirent *entry = NULL; 2004 while ((entry = scanner.getNext()) != NULL) { 2005 std::ostringstream fullPath; 2006 2007 if (entry->d_type != DT_DIR || strcmp(entry->d_name, "Current") == 0) 2008 continue; 2009 2010 fullPath << versionsPath.str() << entry->d_name; 2011 2012 char real_full_path[PATH_MAX]; 2013 if (realpath(fullPath.str().c_str(), real_full_path) == NULL) 2014 UnixError::check(-1); 2015 2016 // Do case insensitive comparions because realpath() was called for both paths 2017 if (foundTarget && strcmp(main_path, real_full_path) == 0) 2018 continue; 2019 2020 SecPointer<SecStaticCode> frameworkVersion = new SecStaticCode(DiskRep::bestGuess(real_full_path)); 2021 frameworkVersion->initializeFromParent(*this); 2022 frameworkVersion->staticValidate(flags, SecRequirement::required(req)); 2023 } 2024 } 2025 } 2026 2027 2028 // 2029 // Test a CodeDirectory flag. 2030 // Returns false if there is no CodeDirectory. 2031 // May throw if the CodeDirectory is present but somehow invalid. 2032 // 2033 bool SecStaticCode::flag(uint32_t tested) 2034 { 2035 if (const CodeDirectory *cd = this->codeDirectory(false)) 2036 return cd->flags & tested; 2037 else 2038 return false; 2039 } 2040 2041 2042 // 2043 // Retrieve the full SuperBlob containing all internal requirements. 2044 // 2045 const Requirements *SecStaticCode::internalRequirements() 2046 { 2047 if (CFDataRef reqData = component(cdRequirementsSlot)) { 2048 const Requirements *req = (const Requirements *)CFDataGetBytePtr(reqData); 2049 if (!req->validateBlob()) 2050 MacOSError::throwMe(errSecCSReqInvalid); 2051 return req; 2052 } else 2053 return NULL; 2054 } 2055 2056 2057 // 2058 // Retrieve a particular internal requirement by type. 2059 // 2060 const Requirement *SecStaticCode::internalRequirement(SecRequirementType type) 2061 { 2062 if (const Requirements *reqs = internalRequirements()) 2063 return reqs->find<Requirement>(type); 2064 else 2065 return NULL; 2066 } 2067 2068 2069 // 2070 // Return the Designated Requirement (DR). This can be either explicit in the 2071 // Internal Requirements component, or implicitly generated on demand here. 2072 // Note that an explicit DR may have been implicitly generated at signing time; 2073 // we don't distinguish this case. 2074 // 2075 const Requirement *SecStaticCode::designatedRequirement() 2076 { 2077 if (const Requirement *req = internalRequirement(kSecDesignatedRequirementType)) { 2078 return req; // explicit in signing data 2079 } else { 2080 if (!mDesignatedReq) 2081 mDesignatedReq = defaultDesignatedRequirement(); 2082 return mDesignatedReq; 2083 } 2084 } 2085 2086 2087 // 2088 // Generate the default Designated Requirement (DR) for this StaticCode. 2089 // Ignore any explicit DR it may contain. 2090 // 2091 const Requirement *SecStaticCode::defaultDesignatedRequirement() 2092 { 2093 if (flag(kSecCodeSignatureAdhoc)) { 2094 // adhoc signature: return a cdhash requirement for all architectures 2095 __block Requirement::Maker maker; 2096 Requirement::Maker::Chain chain(maker, opOr); 2097 2098 // insert cdhash requirement for all architectures 2099 __block CFRef<CFMutableArrayRef> allHashes = CFArrayCreateMutableCopy(NULL, 0, this->cdHashes()); 2100 handleOtherArchitectures(^(SecStaticCode *other) { 2101 CFArrayRef hashes = other->cdHashes(); 2102 CFArrayAppendArray(allHashes, hashes, CFRangeMake(0, CFArrayGetCount(hashes))); 2103 }); 2104 CFIndex count = CFArrayGetCount(allHashes); 2105 for (CFIndex n = 0; n < count; ++n) { 2106 chain.add(); 2107 maker.cdhash(CFDataRef(CFArrayGetValueAtIndex(allHashes, n))); 2108 } 2109 return maker.make(); 2110 } else { 2111 #if TARGET_OS_OSX 2112 // full signature: Gin up full context and let DRMaker do its thing 2113 validateDirectory(); // need the cert chain 2114 CFRef<CFDateRef> secureTimestamp; 2115 if (CFAbsoluteTime time = this->signingTimestamp()) { 2116 secureTimestamp.take(CFDateCreate(NULL, time)); 2117 } 2118 Requirement::Context context(this->certificates(), 2119 this->infoDictionary(), 2120 this->entitlements(), 2121 this->identifier(), 2122 this->codeDirectory(), 2123 NULL, 2124 kSecCodeSignatureNoHash, 2125 false, 2126 secureTimestamp, 2127 this->teamID() 2128 ); 2129 return DRMaker(context).make(); 2130 #else 2131 MacOSError::throwMe(errSecCSUnimplemented); 2132 #endif 2133 } 2134 } 2135 2136 2137 // 2138 // Validate a SecStaticCode against the internal requirement of a particular type. 2139 // 2140 void SecStaticCode::validateRequirements(SecRequirementType type, SecStaticCode *target, 2141 OSStatus nullError /* = errSecSuccess */) 2142 { 2143 DTRACK(CODESIGN_EVAL_STATIC_INTREQ, this, type, target, nullError); 2144 if (const Requirement *req = internalRequirement(type)) 2145 target->validateRequirement(req, nullError ? nullError : errSecCSReqFailed); 2146 else if (nullError) 2147 MacOSError::throwMe(nullError); 2148 else 2149 /* accept it */; 2150 } 2151 2152 // 2153 // Validate this StaticCode against an external Requirement 2154 // 2155 bool SecStaticCode::satisfiesRequirement(const Requirement *req, OSStatus failure) 2156 { 2157 bool result = false; 2158 assert(req); 2159 validateDirectory(); 2160 CFRef<CFDateRef> secureTimestamp; 2161 if (CFAbsoluteTime time = this->signingTimestamp()) { 2162 secureTimestamp.take(CFDateCreate(NULL, time)); 2163 } 2164 result = req->validates(Requirement::Context(mCertChain, infoDictionary(), entitlements(), 2165 codeDirectory()->identifier(), codeDirectory(), 2166 NULL, kSecCodeSignatureNoHash, mRep->appleInternalForcePlatform(), 2167 secureTimestamp, teamID()), 2168 failure); 2169 return result; 2170 } 2171 2172 void SecStaticCode::validateRequirement(const Requirement *req, OSStatus failure) 2173 { 2174 if (!this->satisfiesRequirement(req, failure)) 2175 MacOSError::throwMe(failure); 2176 } 2177 2178 // 2179 // Retrieve one certificate from the cert chain. 2180 // Positive and negative indices can be used: 2181 // [ leaf, intermed-1, ..., intermed-n, anchor ] 2182 // 0 1 ... -2 -1 2183 // Returns NULL if unavailable for any reason. 2184 // 2185 SecCertificateRef SecStaticCode::cert(int ix) 2186 { 2187 validateDirectory(); // need cert chain 2188 if (mCertChain) { 2189 CFIndex length = CFArrayGetCount(mCertChain); 2190 if (ix < 0) 2191 ix += length; 2192 if (ix >= 0 && ix < length) 2193 return SecCertificateRef(CFArrayGetValueAtIndex(mCertChain, ix)); 2194 } 2195 return NULL; 2196 } 2197 2198 CFArrayRef SecStaticCode::certificates() 2199 { 2200 validateDirectory(); // need cert chain 2201 return mCertChain; 2202 } 2203 2204 2205 // 2206 // Gather (mostly) API-official information about this StaticCode. 2207 // 2208 // This method lives in the twilight between the API and internal layers, 2209 // since it generates API objects (Sec*Refs) for return. 2210 // 2211 CFDictionaryRef SecStaticCode::signingInformation(SecCSFlags flags) 2212 { 2213 // 2214 // Start with the pieces that we return even for unsigned code. 2215 // This makes Sec[Static]CodeRefs useful as API-level replacements 2216 // of our internal OSXCode objects. 2217 // 2218 CFRef<CFMutableDictionaryRef> dict = makeCFMutableDictionary(1, 2219 kSecCodeInfoMainExecutable, CFTempURL(this->mainExecutablePath()).get() 2220 ); 2221 2222 // 2223 // If we're not signed, this is all you get 2224 // 2225 if (!this->isSigned()) 2226 return dict.yield(); 2227 2228 // 2229 // Add the generic attributes that we always include 2230 // 2231 CFDictionaryAddValue(dict, kSecCodeInfoIdentifier, CFTempString(this->identifier())); 2232 CFDictionaryAddValue(dict, kSecCodeInfoFlags, CFTempNumber(this->codeDirectory(false)->flags.get())); 2233 CFDictionaryAddValue(dict, kSecCodeInfoFormat, CFTempString(this->format())); 2234 CFDictionaryAddValue(dict, kSecCodeInfoSource, CFTempString(this->signatureSource())); 2235 CFDictionaryAddValue(dict, kSecCodeInfoUnique, this->cdHash()); 2236 CFDictionaryAddValue(dict, kSecCodeInfoCdHashes, this->cdHashes()); 2237 CFDictionaryAddValue(dict, kSecCodeInfoCdHashesFull, this->cdHashesFull()); 2238 const CodeDirectory* cd = this->codeDirectory(false); 2239 CFDictionaryAddValue(dict, kSecCodeInfoDigestAlgorithm, CFTempNumber(cd->hashType)); 2240 CFRef<CFArrayRef> digests = makeCFArrayFrom(^CFTypeRef(CodeDirectory::HashAlgorithm type) { return CFTempNumber(type); }, hashAlgorithms()); 2241 CFDictionaryAddValue(dict, kSecCodeInfoDigestAlgorithms, digests); 2242 if (cd->platform) 2243 CFDictionaryAddValue(dict, kSecCodeInfoPlatformIdentifier, CFTempNumber(cd->platform)); 2244 if (cd->runtimeVersion()) { 2245 CFDictionaryAddValue(dict, kSecCodeInfoRuntimeVersion, CFTempNumber(cd->runtimeVersion())); 2246 } 2247 2248 // 2249 // Deliver any Info.plist only if it looks intact 2250 // 2251 try { 2252 if (CFDictionaryRef info = this->infoDictionary()) 2253 CFDictionaryAddValue(dict, kSecCodeInfoPList, info); 2254 } catch (...) { } // don't deliver Info.plist if questionable 2255 2256 // 2257 // kSecCSSigningInformation adds information about signing certificates and chains 2258 // 2259 if (flags & kSecCSSigningInformation) 2260 try { 2261 if (CFDataRef sig = this->signature()) 2262 CFDictionaryAddValue(dict, kSecCodeInfoCMS, sig); 2263 if (const char *teamID = this->teamID()) 2264 CFDictionaryAddValue(dict, kSecCodeInfoTeamIdentifier, CFTempString(teamID)); 2265 if (mTrust) 2266 CFDictionaryAddValue(dict, kSecCodeInfoTrust, mTrust); 2267 if (CFArrayRef certs = this->certificates()) 2268 CFDictionaryAddValue(dict, kSecCodeInfoCertificates, certs); 2269 if (CFAbsoluteTime time = this->signingTime()) 2270 if (CFRef<CFDateRef> date = CFDateCreate(NULL, time)) 2271 CFDictionaryAddValue(dict, kSecCodeInfoTime, date); 2272 if (CFAbsoluteTime time = this->signingTimestamp()) 2273 if (CFRef<CFDateRef> date = CFDateCreate(NULL, time)) 2274 CFDictionaryAddValue(dict, kSecCodeInfoTimestamp, date); 2275 } catch (...) { } 2276 2277 // 2278 // kSecCSRequirementInformation adds information on requirements 2279 // 2280 if (flags & kSecCSRequirementInformation) 2281 2282 //DR not currently supported on iOS 2283 #if TARGET_OS_OSX 2284 try { 2285 if (const Requirements *reqs = this->internalRequirements()) { 2286 CFDictionaryAddValue(dict, kSecCodeInfoRequirements, 2287 CFTempString(Dumper::dump(reqs))); 2288 CFDictionaryAddValue(dict, kSecCodeInfoRequirementData, CFTempData(*reqs)); 2289 } 2290 2291 const Requirement *dreq = this->designatedRequirement(); 2292 CFRef<SecRequirementRef> dreqRef = (new SecRequirement(dreq))->handle(); 2293 CFDictionaryAddValue(dict, kSecCodeInfoDesignatedRequirement, dreqRef); 2294 if (this->internalRequirement(kSecDesignatedRequirementType)) { // explicit 2295 CFRef<SecRequirementRef> ddreqRef = (new SecRequirement(this->defaultDesignatedRequirement(), true))->handle(); 2296 CFDictionaryAddValue(dict, kSecCodeInfoImplicitDesignatedRequirement, ddreqRef); 2297 } else { // implicit 2298 CFDictionaryAddValue(dict, kSecCodeInfoImplicitDesignatedRequirement, dreqRef); 2299 } 2300 } catch (...) { } 2301 #endif 2302 2303 try { 2304 if (CFDataRef ent = this->component(cdEntitlementSlot)) { 2305 CFDictionaryAddValue(dict, kSecCodeInfoEntitlements, ent); 2306 if (CFDictionaryRef entdict = this->entitlements()) { 2307 if (needsCatalystEntitlementFixup(entdict)) { 2308 // If this entitlement dictionary needs catalyst entitlements, make a copy and stick that into the 2309 // output dictionary instead. 2310 secinfo("staticCode", "%p fixed catalyst entitlements", this); 2311 CFRef<CFMutableDictionaryRef> tempEntitlements = makeCFMutableDictionary(entdict); 2312 updateCatalystEntitlements(tempEntitlements); 2313 CFRef<CFDictionaryRef> newEntitlements = CFDictionaryCreateCopy(NULL, tempEntitlements); 2314 if (newEntitlements) { 2315 CFDictionaryAddValue(dict, kSecCodeInfoEntitlementsDict, newEntitlements.get()); 2316 } else { 2317 secerror("%p unable to fixup entitlement dictionary", this); 2318 CFDictionaryAddValue(dict, kSecCodeInfoEntitlementsDict, entdict); 2319 } 2320 } else { 2321 CFDictionaryAddValue(dict, kSecCodeInfoEntitlementsDict, entdict); 2322 } 2323 } 2324 } 2325 } catch (...) { } 2326 2327 // 2328 // kSecCSInternalInformation adds internal information meant to be for Apple internal 2329 // use (SPI), and not guaranteed to be stable. Primarily, this is data we want 2330 // to reliably transmit through the API wall so that code outside the Security.framework 2331 // can use it without having to play nasty tricks to get it. 2332 // 2333 if (flags & kSecCSInternalInformation) { 2334 try { 2335 if (mDir) 2336 CFDictionaryAddValue(dict, kSecCodeInfoCodeDirectory, mDir); 2337 CFDictionaryAddValue(dict, kSecCodeInfoCodeOffset, CFTempNumber(mRep->signingBase())); 2338 if (!(flags & kSecCSSkipResourceDirectory)) { 2339 if (CFRef<CFDictionaryRef> rdict = getDictionary(cdResourceDirSlot, false)) // suppress validation 2340 CFDictionaryAddValue(dict, kSecCodeInfoResourceDirectory, rdict); 2341 } 2342 if (CFRef<CFDictionaryRef> ddict = copyDiskRepInformation()) 2343 CFDictionaryAddValue(dict, kSecCodeInfoDiskRepInfo, ddict); 2344 } catch (...) { } 2345 if (mNotarizationChecked && !isnan(mNotarizationDate)) { 2346 CFRef<CFDateRef> date = CFDateCreate(NULL, mNotarizationDate); 2347 if (date) { 2348 CFDictionaryAddValue(dict, kSecCodeInfoNotarizationDate, date.get()); 2349 } else { 2350 secerror("Error creating date from timestamp: %f", mNotarizationDate); 2351 } 2352 } 2353 if (this->codeDirectory()) { 2354 uint32_t version = this->codeDirectory()->version; 2355 CFDictionaryAddValue(dict, kSecCodeInfoSignatureVersion, CFTempNumber(version)); 2356 } 2357 } 2358 2359 if (flags & kSecCSCalculateCMSDigest) { 2360 try { 2361 CFDictionaryAddValue(dict, kSecCodeInfoCMSDigestHashType, CFTempNumber(cmsDigestHashType())); 2362 2363 CFRef<CFDataRef> cmsDigest = createCmsDigest(); 2364 if (cmsDigest) { 2365 CFDictionaryAddValue(dict, kSecCodeInfoCMSDigest, cmsDigest.get()); 2366 } 2367 } catch (...) { } 2368 } 2369 2370 // 2371 // kSecCSContentInformation adds more information about the physical layout 2372 // of the signed code. This is (only) useful for packaging or patching-oriented 2373 // applications. 2374 // 2375 if (flags & kSecCSContentInformation && !(flags & kSecCSSkipResourceDirectory)) 2376 if (CFRef<CFArrayRef> files = mRep->modifiedFiles()) 2377 CFDictionaryAddValue(dict, kSecCodeInfoChangedFiles, files); 2378 2379 return dict.yield(); 2380 } 2381 2382 2383 // 2384 // Resource validation contexts. 2385 // The default context simply throws a CSError, rudely terminating the operation. 2386 // 2387 SecStaticCode::ValidationContext::~ValidationContext() 2388 { /* virtual */ } 2389 2390 void SecStaticCode::ValidationContext::reportProblem(OSStatus rc, CFStringRef type, CFTypeRef value) 2391 { 2392 CSError::throwMe(rc, type, value); 2393 } 2394 2395 void SecStaticCode::CollectingContext::reportProblem(OSStatus rc, CFStringRef type, CFTypeRef value) 2396 { 2397 StLock<Mutex> _(mLock); 2398 if (mStatus == errSecSuccess) 2399 mStatus = rc; // record first failure for eventual error return 2400 if (type) { 2401 if (!mCollection) 2402 mCollection.take(makeCFMutableDictionary()); 2403 CFMutableArrayRef element = CFMutableArrayRef(CFDictionaryGetValue(mCollection, type)); 2404 if (!element) { 2405 element = makeCFMutableArray(0); 2406 if (!element) 2407 CFError::throwMe(); 2408 CFDictionaryAddValue(mCollection, type, element); 2409 CFRelease(element); 2410 } 2411 CFArrayAppendValue(element, value); 2412 } 2413 } 2414 2415 void SecStaticCode::CollectingContext::throwMe() 2416 { 2417 assert(mStatus != errSecSuccess); 2418 throw CSError(mStatus, mCollection.retain()); 2419 } 2420 2421 2422 // 2423 // Master validation driver. 2424 // This is the static validation (only) driver for the API. 2425 // 2426 // SecStaticCode exposes an a la carte menu of topical validators applying 2427 // to a given object. The static validation API pulls them together reliably, 2428 // but it also adds three matrix dimensions: architecture (for "fat" Mach-O binaries), 2429 // nested code, and multiple digests. This function will crawl a suitable cross-section of this 2430 // validation matrix based on which options it is given, creating temporary 2431 // SecStaticCode objects on the fly to complete the task. 2432 // (The point, of course, is to do as little duplicate work as possible.) 2433 // 2434 void SecStaticCode::staticValidate(SecCSFlags flags, const SecRequirement *req) 2435 { 2436 setValidationFlags(flags); 2437 2438 #if TARGET_OS_OSX 2439 if (!mStaplingChecked) { 2440 mRep->registerStapledTicket(); 2441 mStaplingChecked = true; 2442 } 2443 2444 if (isFlagSet(mFlags, kSecCSForceOnlineNotarizationCheck) && !validationCannotUseNetwork()) { 2445 if (!mNotarizationChecked) { 2446 if (this->cdHash()) { 2447 bool is_revoked = checkNotarizationServiceForRevocation(this->cdHash(), (SecCSDigestAlgorithm)this->hashAlgorithm(), &mNotarizationDate); 2448 if (is_revoked) { 2449 MacOSError::throwMe(errSecCSRevokedNotarization); 2450 } 2451 } 2452 mNotarizationChecked = true; 2453 } 2454 } 2455 #endif // TARGET_OS_OSX 2456 2457 // initialize progress/cancellation state 2458 if (flags & kSecCSReportProgress) { 2459 prepareProgress(estimateResourceWorkload() + 2); // +1 head, +1 tail 2460 } 2461 2462 // core components: once per architecture (if any) 2463 this->staticValidateCore(flags, req); 2464 if (flags & kSecCSCheckAllArchitectures) { 2465 handleOtherArchitectures(^(SecStaticCode* subcode) { 2466 if (flags & kSecCSCheckGatekeeperArchitectures) { 2467 Universal *fat = subcode->diskRep()->mainExecutableImage(); 2468 assert(fat && fat->narrowed()); // handleOtherArchitectures gave us a focused architecture slice 2469 Architecture arch = fat->bestNativeArch(); // actually, the ONLY one 2470 if ((arch.cpuType() & ~CPU_ARCH_MASK) == CPU_TYPE_POWERPC) 2471 return; // irrelevant to Gatekeeper 2472 } 2473 subcode->detachedSignature(this->mDetachedSig); // carry over explicit (but not implicit) detached signature 2474 subcode->staticValidateCore(flags, req); 2475 }); 2476 } 2477 reportProgress(); 2478 2479 // allow monitor intervention in source validation phase 2480 reportEvent(CFSTR("prepared"), NULL); 2481 2482 // resources: once for all architectures 2483 if (!(flags & kSecCSDoNotValidateResources)) { 2484 this->validateResources(flags); 2485 } 2486 2487 // perform strict validation if desired 2488 if (flags & kSecCSStrictValidate) { 2489 mRep->strictValidate(codeDirectory(), mTolerateErrors, mValidationFlags); 2490 reportProgress(); 2491 } else if (flags & kSecCSStrictValidateStructure) { 2492 mRep->strictValidateStructure(codeDirectory(), mTolerateErrors, mValidationFlags); 2493 } 2494 2495 // allow monitor intervention 2496 if (CFRef<CFTypeRef> veto = reportEvent(CFSTR("validated"), NULL)) { 2497 if (CFGetTypeID(veto) == CFNumberGetTypeID()) 2498 MacOSError::throwMe(cfNumber<OSStatus>(veto.as<CFNumberRef>())); 2499 else 2500 MacOSError::throwMe(errSecCSBadCallbackValue); 2501 } 2502 } 2503 2504 void SecStaticCode::staticValidateCore(SecCSFlags flags, const SecRequirement *req) 2505 { 2506 try { 2507 this->validateNonResourceComponents(); // also validates the CodeDirectory 2508 this->validateTopDirectory(); 2509 if (!(flags & kSecCSDoNotValidateExecutable)) 2510 this->validateExecutable(); 2511 if (req) 2512 this->validateRequirement(req->requirement(), errSecCSReqFailed); 2513 } catch (CSError &err) { 2514 if (Universal *fat = this->diskRep()->mainExecutableImage()) // Mach-O 2515 if (MachO *mach = fat->architecture()) { 2516 err.augment(kSecCFErrorArchitecture, CFTempString(mach->architecture().displayName())); 2517 delete mach; 2518 } 2519 throw; 2520 } catch (const MacOSError &err) { 2521 // add architecture information if we can get it 2522 if (Universal *fat = this->diskRep()->mainExecutableImage()) 2523 if (MachO *mach = fat->architecture()) { 2524 CFTempString arch(mach->architecture().displayName()); 2525 delete mach; 2526 CSError::throwMe(err.error, kSecCFErrorArchitecture, arch); 2527 } 2528 throw; 2529 } 2530 } 2531 2532 void SecStaticCode::staticValidateResource(string resourcePath, SecCSFlags flags, const SecRequirement *req) 2533 { 2534 // resourcePath is always the absolute path to the resource, each analysis can make a relative path 2535 // if it needs one. Passing through relative paths but then needing to re-create the full path is 2536 // more complicated in the case where a subpath is no longer contained within the resource envelope 2537 // of the next subcode. 2538 2539 // Validate the resource is inside the outer bundle by finding the bundle's resource path in the string 2540 // of the full resource. If its a prefix match this will also compute the remaining relative path 2541 // that we'll need later. 2542 string baseResourcePath; 2543 string relativePath; 2544 2545 if (this->mainExecutablePath() == resourcePath) { 2546 // Nothing to do here, we're just validating the main executable so proceed 2547 // to the validation below. 2548 } else { 2549 baseResourcePath = cfString(resourceBase()); 2550 relativePath = pathRemaining(resourcePath, baseResourcePath); 2551 if (relativePath == "") { 2552 // The resource is not a prefix match with the bundle or the arguments are bad. 2553 secerror("Requested resource was not within the code object: %s, %s", resourcePath.c_str(), baseResourcePath.c_str()); 2554 MacOSError::throwMe(errSecParam); 2555 } 2556 } 2557 2558 // In general, we never want to be validating executables of the bundles as we traverse, so just ensure the 2559 // bit is always set to skip them as we go. 2560 flags = addFlags(flags, kSecCSDoNotValidateExecutable); 2561 2562 // First special case is the main executable, which means we're about to validate it as part of the 2563 // static validation here. 2564 bool needsAdditionalValidation = true; 2565 if (this->mainExecutablePath() == resourcePath) { 2566 needsAdditionalValidation = false; 2567 2568 // If the caller did not request fast validation of an executable, ensure we clear the 'do 2569 // not validate' bit here before validating. 2570 if (!isFlagSet(flags, kSecCSFastExecutableValidation)) { 2571 flags = clearFlags(flags, kSecCSDoNotValidateExecutable); 2572 } 2573 } 2574 2575 // The Info.plist is covered by the core validation, so there's no more work to be done. 2576 if (relativePath == "Info.plist") { 2577 needsAdditionalValidation = false; 2578 } 2579 2580 // Perform basic validation of the code object itself, since thats required for the rest of the comparison 2581 // to be valid. 2582 this->staticValidateCore(flags, NULL); 2583 if (req) { 2584 // If we have an explicit requirement we must meet and fail, then it should actually 2585 // be recorded as the resource being modified. 2586 this->validateRequirement(req->requirement(), errSecCSBadResource); 2587 } 2588 2589 if (!needsAdditionalValidation) { 2590 // staticValidateCore has already validated the main executable so we're all done! 2591 return; 2592 } 2593 2594 if (!isFlagSet(flags, kSecCSSkipRootVolumeExceptions)) { 2595 if (itemQualifiesForResourceExemption(resourcePath)) { 2596 secinfo("staticCode", "Requested resource was on root filesystem: %s", resourcePath.c_str()); 2597 return; 2598 } 2599 } 2600 2601 // We need to load resource rules to be able to do a single file resource comparison against. 2602 CFDictionaryRef rules; 2603 CFDictionaryRef files; 2604 uint32_t version; 2605 if (!loadResources(rules, files, version)) { 2606 MacOSError::throwMe(errSecCSResourcesNotFound); 2607 } 2608 2609 // Load up a full resource builder so we can properly parse all the rules. 2610 bool strict = (flags & kSecCSStrictValidate); 2611 MacOSErrorSet toleratedErrors; 2612 ResourceBuilder resources(baseResourcePath, baseResourcePath, rules, strict, toleratedErrors); 2613 diskRep()->adjustResources(resources); 2614 2615 // First, check if the path itself is inside of an omission or exclusion hole. 2616 ResourceBuilder::Rule *rule = resources.findRule(relativePath); 2617 if (rule) { 2618 if (rule->flags & (ResourceBuilder::omitted | ResourceBuilder::exclusion)) { 2619 secerror("Requested resource was not sealed: %d", rule->flags); 2620 MacOSError::throwMe(errSecCSResourcesNotSealed); 2621 } 2622 } 2623 2624 // Otherwise look for an exact file match, or find the most deeply nested code. 2625 CFTypeRef file = CFDictionaryGetValue(files, CFTempString(relativePath)); 2626 if (file) { 2627 // This item matched a file rule exactly, so just validate it directly with this object. 2628 AutoFileDesc fd = AutoFileDesc(resourcePath); 2629 bool isSymlink = fd.isA(S_IFLNK); 2630 2631 // Since this is a direct file match, if its for a nested bundle then we want to enable executable 2632 // validation based on whether fast executable validation was requested. 2633 if (!isFlagSet(flags, kSecCSFastExecutableValidation)) { 2634 flags = clearFlags(flags, kSecCSDoNotValidateExecutable); 2635 } 2636 2637 ResourceSeal seal(file); 2638 if (seal.nested()) { 2639 CFRef<SecRequirementRef> req; 2640 if (SecRequirementCreateWithString(seal.requirement(), kSecCSDefaultFlags, &req.aref())) { 2641 MacOSError::throwMe(errSecCSResourcesInvalid); 2642 } 2643 2644 // If the resource seal indicates this is nested code, create a new code object for this 2645 // nested code and then validate the resource within that object. 2646 SecPointer<SecStaticCode> subcode = new SecStaticCode(DiskRep::bestGuess(resourcePath)); 2647 subcode->initializeFromParent(*this); 2648 // If there was an exact match but its nested code, then the ask is really to validate the 2649 // main executable of the nested code. 2650 subcode->staticValidateResource(subcode->mainExecutablePath(), flags, SecRequirement::required(req)); 2651 } else { 2652 // For other resource types, just a single file resource validation with a ValidationContext that 2653 // will immediately throw an error if any issues are encountered. 2654 ValidationContext *context = new ValidationContext(*this); 2655 validateResource(files, relativePath, isSymlink, *context, flags, version); 2656 } 2657 } else { 2658 // It wasn't a simple file resource within the current code signature, so we're looking for a nested code. 2659 __block bool itemFound = false; 2660 2661 // Iterate through the largest possible chunks of paths looking for nested code matches. 2662 iterateLargestSubpaths(relativePath, ^bool(string subpath) { 2663 CFTypeRef file = CFDictionaryGetValue(files, CFTempString(subpath)); 2664 if (file) { 2665 itemFound = true; 2666 2667 ResourceSeal seal(file); 2668 if (seal.nested()) { 2669 CFRef<SecRequirementRef> req; 2670 if (SecRequirementCreateWithString(seal.requirement(), kSecCSDefaultFlags, &req.aref())) { 2671 MacOSError::throwMe(errSecCSResourcesInvalid); 2672 } 2673 2674 // If the resource seal indicates this is nested code, create a new code object for this 2675 // nested code and then validate the resource within that object. 2676 CFRef<CFURLRef> itemURL = makeCFURL(subpath, false, resourceBase()); 2677 string fullPath = cfString(itemURL); 2678 SecPointer<SecStaticCode> subcode = new SecStaticCode(DiskRep::bestGuess(fullPath)); 2679 subcode->initializeFromParent(*this); 2680 subcode->staticValidateResource(resourcePath, flags, SecRequirement::required(req)); 2681 } else { 2682 // Any other type of nested resource is not ok, so just bail. 2683 secerror("Unexpected item hit traversing resource: %@", file); 2684 MacOSError::throwMe(errSecCSBadResource); 2685 } 2686 // If we find a match, stop walking up for further matching. 2687 return true; 2688 } 2689 return false; 2690 }); 2691 2692 // If we finished everything and didn't find the item, its not a valid resource. 2693 if (!itemFound) { 2694 secerror("Requested resource was not found: %s", resourcePath.c_str()); 2695 MacOSError::throwMe(errSecCSBadResource); 2696 } 2697 } 2698 } 2699 2700 // 2701 // A helper that generates SecStaticCode objects for all but the primary architecture 2702 // of a fat binary and calls a block on them. 2703 // If there's only one architecture (or this is an architecture-agnostic code), 2704 // nothing happens quickly. 2705 // 2706 void SecStaticCode::handleOtherArchitectures(void (^handle)(SecStaticCode* other)) 2707 { 2708 if (Universal *fat = this->diskRep()->mainExecutableImage()) { 2709 Universal::Architectures architectures; 2710 fat->architectures(architectures); 2711 if (architectures.size() > 1) { 2712 DiskRep::Context ctx; 2713 off_t activeOffset = fat->archOffset(); 2714 for (Universal::Architectures::const_iterator arch = architectures.begin(); arch != architectures.end(); ++arch) { 2715 try { 2716 ctx.offset = int_cast<size_t, off_t>(fat->archOffset(*arch)); 2717 ctx.size = fat->lengthOfSlice(int_cast<off_t,size_t>(ctx.offset)); 2718 if (ctx.offset != activeOffset) { // inactive architecture; check it 2719 SecPointer<SecStaticCode> subcode = new SecStaticCode(DiskRep::bestGuess(this->mainExecutablePath(), &ctx)); 2720 2721 // There may not actually be a full validation happening, but any operations that do occur should respect the 2722 // same network settings as the existing validation, so propagate those flags forward here. 2723 SecCSFlags flagsToPropagate = (kSecCSAllowNetworkAccess | kSecCSNoNetworkAccess); 2724 subcode->setValidationFlags(mValidationFlags & flagsToPropagate); 2725 2726 subcode->detachedSignature(this->mDetachedSig); // carry over explicit (but not implicit) detached signature 2727 if (this->teamID() == NULL || subcode->teamID() == NULL) { 2728 if (this->teamID() != subcode->teamID()) 2729 MacOSError::throwMe(errSecCSSignatureInvalid); 2730 } else if (strcmp(this->teamID(), subcode->teamID()) != 0) 2731 MacOSError::throwMe(errSecCSSignatureInvalid); 2732 handle(subcode); 2733 } 2734 } catch(std::out_of_range e) { 2735 // some of our int_casts fell over. 2736 MacOSError::throwMe(errSecCSBadObjectFormat); 2737 } 2738 } 2739 } 2740 } 2741 } 2742 2743 // 2744 // A method that takes a certificate chain (certs) and evaluates 2745 // if it is a Mac or IPhone developer cert, an app store distribution cert, 2746 // or a developer ID 2747 // 2748 bool SecStaticCode::isAppleDeveloperCert(CFArrayRef certs) 2749 { 2750 static const std::string appleDeveloperRequirement = "(" + std::string(WWDRRequirement) + ") or (" + MACWWDRRequirement + ") or (" + developerID + ") or (" + distributionCertificate + ") or (" + iPhoneDistributionCert + ")"; 2751 SecPointer<SecRequirement> req = new SecRequirement(parseRequirement(appleDeveloperRequirement), true); 2752 Requirement::Context ctx(certs, NULL, NULL, "", NULL, NULL, kSecCodeSignatureNoHash, false, NULL, ""); 2753 2754 return req->requirement()->validates(ctx); 2755 } 2756 2757 CFDataRef SecStaticCode::createCmsDigest() 2758 { 2759 /* 2760 * The CMS digest is a hash of the primary (first, most compatible) code directory, 2761 * but its hash algorithm is fixed and not related to the code directory's 2762 * hash algorithm. 2763 */ 2764 2765 auto it = codeDirectories()->begin(); 2766 2767 if (it == codeDirectories()->end()) { 2768 return NULL; 2769 } 2770 2771 CodeDirectory const * const cd = reinterpret_cast<CodeDirectory const*>(CFDataGetBytePtr(it->second)); 2772 2773 RefPointer<DynamicHash> hash = cd->hashFor(mCMSDigestHashType); 2774 CFMutableDataRef data = CFDataCreateMutable(NULL, hash->digestLength()); 2775 CFDataSetLength(data, hash->digestLength()); 2776 hash->update(cd, cd->length()); 2777 hash->finish(CFDataGetMutableBytePtr(data)); 2778 2779 return data; 2780 } 2781 2782 } // end namespace CodeSigning 2783 } // end namespace Security