1/* 2 * Copyright (c) 2006-2014 Apple Inc. All Rights Reserved. 3 * 4 * @APPLE_LICENSE_HEADER_START@ 5 * 6 * This file contains Original Code and/or Modifications of Original Code 7 * as defined in and that are subject to the Apple Public Source License 8 * Version 2.0 (the 'License'). You may not use this file except in 9 * compliance with the License. Please obtain a copy of the License at 10 * http://www.opensource.apple.com/apsl/ and read it before using this 11 * file. 12 * 13 * The Original Code and all software distributed under the License are 14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER 15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, 16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, 17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. 18 * Please see the License for the specific language governing rights and 19 * limitations under the License. 20 * 21 * @APPLE_LICENSE_HEADER_END@ 22 */ 23#include "bundlediskrep.h" 24#include "filediskrep.h" 25#include "dirscanner.h" 26#include <CoreFoundation/CFURLAccess.h> 27#include <CoreFoundation/CFBundlePriv.h> 28#include <security_utilities/cfmunge.h> 29#include <copyfile.h> 30#include <fts.h> 31#include <sstream> 32 33namespace Security { 34namespace CodeSigning { 35 36using namespace UnixPlusPlus; 37 38 39// 40// Local helpers 41// 42static std::string findDistFile(const std::string &directory); 43 44 45// 46// We make a CFBundleRef immediately, but everything else is lazy 47// 48BundleDiskRep::BundleDiskRep(const char *path, const Context *ctx) 49 : mBundle(CFBundleCreate(NULL, CFTempURL(path))) 50{ 51 if (!mBundle) 52 MacOSError::throwMe(errSecCSBadBundleFormat); 53 setup(ctx); 54 CODESIGN_DISKREP_CREATE_BUNDLE_PATH(this, (char*)path, (void*)ctx, mExecRep); 55} 56 57BundleDiskRep::BundleDiskRep(CFBundleRef ref, const Context *ctx) 58{ 59 mBundle = ref; // retains 60 setup(ctx); 61 CODESIGN_DISKREP_CREATE_BUNDLE_REF(this, ref, (void*)ctx, mExecRep); 62} 63 64BundleDiskRep::~BundleDiskRep() 65{ 66} 67 68// common construction code 69void BundleDiskRep::setup(const Context *ctx) 70{ 71 mInstallerPackage = false; // default 72 73 // capture the path of the main executable before descending into a specific version 74 CFRef<CFURLRef> mainExecBefore = CFBundleCopyExecutableURL(mBundle); 75 76 // validate the bundle root; fish around for the desired framework version 77 string root = cfStringRelease(copyCanonicalPath()); 78 string contents = root + "/Contents"; 79 string version = root + "/Versions/" 80 + ((ctx && ctx->version) ? ctx->version : "Current") 81 + "/."; 82 if (::access(contents.c_str(), F_OK) == 0) { // not shallow 83 DirValidator val; 84 val.require("^Contents$", DirValidator::directory); // duh 85 val.allow("^(\\.LSOverride|\\.DS_Store|Icon\r|\\.SoftwareDepot\\.tracking)$", DirValidator::file | DirValidator::noexec); 86 try { 87 val.validate(root, errSecCSUnsealedAppRoot); 88 } catch (const MacOSError &err) { 89 recordStrictError(err.error); 90 } 91 } else if (::access(version.c_str(), F_OK) == 0) { // versioned bundle 92 if (CFBundleRef versionBundle = CFBundleCreate(NULL, CFTempURL(version))) 93 mBundle.take(versionBundle); // replace top bundle ref 94 else 95 MacOSError::throwMe(errSecCSStaticCodeNotFound); 96 validateFrameworkRoot(root); 97 } else { 98 if (ctx && ctx->version) // explicitly specified 99 MacOSError::throwMe(errSecCSStaticCodeNotFound); 100 } 101 102 CFDictionaryRef infoDict = CFBundleGetInfoDictionary(mBundle); 103 assert(infoDict); // CFBundle will always make one up for us 104 CFTypeRef mainHTML = CFDictionaryGetValue(infoDict, CFSTR("MainHTML")); 105 CFTypeRef packageVersion = CFDictionaryGetValue(infoDict, CFSTR("IFMajorVersion")); 106 107 // conventional executable bundle: CFBundle identifies an executable for us 108 if (CFRef<CFURLRef> mainExec = CFBundleCopyExecutableURL(mBundle)) // if CFBundle claims an executable... 109 if (mainHTML == NULL) { // ... and it's not a widget 110 111 // Note that this check is skipped if there is a specific framework version checked. 112 // That's because you know what you are doing if you are looking at a specific version. 113 // This check is designed to stop someone who did a verification on an app root, from mistakenly 114 // verifying a framework 115 if (mainExecBefore && (!ctx || !ctx->version)) { 116 char main_exec_before[PATH_MAX]; 117 char main_exec[PATH_MAX]; 118 // The realpath call is important because alot of Framework bundles have a symlink 119 // to their "Current" version binary in the main bundle 120 if (realpath(cfString(mainExecBefore).c_str(), main_exec_before) == NULL || 121 realpath(cfString(mainExec).c_str(), main_exec) == NULL) 122 MacOSError::throwMe(errSecCSInternalError); 123 124 if (strcmp(main_exec_before, main_exec) != 0) 125 recordStrictError(errSecCSAmbiguousBundleFormat); 126 } 127 128 mMainExecutableURL = mainExec; 129 mExecRep = DiskRep::bestFileGuess(this->mainExecutablePath(), ctx); 130 if (!mExecRep->fd().isPlainFile(this->mainExecutablePath())) 131 recordStrictError(errSecCSRegularFile); 132 mFormat = "bundle with " + mExecRep->format(); 133 return; 134 } 135 136 // widget 137 if (mainHTML) { 138 if (CFGetTypeID(mainHTML) != CFStringGetTypeID()) 139 MacOSError::throwMe(errSecCSBadBundleFormat); 140 mMainExecutableURL.take(makeCFURL(cfString(CFStringRef(mainHTML)), false, 141 CFRef<CFURLRef>(CFBundleCopySupportFilesDirectoryURL(mBundle)))); 142 if (!mMainExecutableURL) 143 MacOSError::throwMe(errSecCSBadBundleFormat); 144 mExecRep = new FileDiskRep(this->mainExecutablePath().c_str()); 145 if (!mExecRep->fd().isPlainFile(this->mainExecutablePath())) 146 recordStrictError(errSecCSRegularFile); 147 mFormat = "widget bundle"; 148 return; 149 } 150 151 // do we have a real Info.plist here? 152 if (CFRef<CFURLRef> infoURL = _CFBundleCopyInfoPlistURL(mBundle)) { 153 // focus on the Info.plist (which we know exists) as the nominal "main executable" file 154 mMainExecutableURL = infoURL; 155 mExecRep = new FileDiskRep(this->mainExecutablePath().c_str()); 156 if (!mExecRep->fd().isPlainFile(this->mainExecutablePath())) 157 recordStrictError(errSecCSRegularFile); 158 if (packageVersion) { 159 mInstallerPackage = true; 160 mFormat = "installer package bundle"; 161 } else { 162 mFormat = "bundle"; 163 } 164 return; 165 } 166 167 // we're getting desperate here. Perhaps an oldish-style installer package? Look for a *.dist file 168 std::string distFile = findDistFile(this->resourcesRootPath()); 169 if (!distFile.empty()) { 170 mMainExecutableURL = makeCFURL(distFile); 171 mExecRep = new FileDiskRep(this->mainExecutablePath().c_str()); 172 if (!mExecRep->fd().isPlainFile(this->mainExecutablePath())) 173 recordStrictError(errSecCSRegularFile); 174 mInstallerPackage = true; 175 mFormat = "installer package bundle"; 176 return; 177 } 178 179 // this bundle cannot be signed 180 MacOSError::throwMe(errSecCSBadBundleFormat); 181} 182 183 184// 185// Return the full path to the one-and-only file named something.dist in a directory. 186// Return empty string if none; throw an exception if multiple. Do not descend into subdirectories. 187// 188static std::string findDistFile(const std::string &directory) 189{ 190 std::string found; 191 char *paths[] = {(char *)directory.c_str(), NULL}; 192 FTS *fts = fts_open(paths, FTS_PHYSICAL | FTS_NOCHDIR | FTS_NOSTAT, NULL); 193 bool root = true; 194 while (FTSENT *ent = fts_read(fts)) { 195 switch (ent->fts_info) { 196 case FTS_F: 197 case FTS_NSOK: 198 if (!strcmp(ent->fts_path + ent->fts_pathlen - 5, ".dist")) { // found plain file foo.dist 199 if (found.empty()) // first found 200 found = ent->fts_path; 201 else // multiple *.dist files (bad) 202 MacOSError::throwMe(errSecCSBadBundleFormat); 203 } 204 break; 205 case FTS_D: 206 if (!root) 207 fts_set(fts, ent, FTS_SKIP); // don't descend 208 root = false; 209 break; 210 default: 211 break; 212 } 213 } 214 fts_close(fts); 215 return found; 216} 217 218 219// 220// Create a path to a bundle signing resource, by name. 221// If the BUNDLEDISKREP_DIRECTORY directory exists in the bundle's support directory, files 222// will be read and written there. Otherwise, they go directly into the support directory. 223// 224string BundleDiskRep::metaPath(const char *name) 225{ 226 if (mMetaPath.empty()) { 227 string support = cfStringRelease(CFBundleCopySupportFilesDirectoryURL(mBundle)); 228 mMetaPath = support + "/" BUNDLEDISKREP_DIRECTORY; 229 if (::access(mMetaPath.c_str(), F_OK) == 0) { 230 mMetaExists = true; 231 } else { 232 mMetaPath = support; 233 mMetaExists = false; 234 } 235 } 236 return mMetaPath + "/" + name; 237} 238 239 240// 241// Try to create the meta-file directory in our bundle. 242// Does nothing if the directory already exists. 243// Throws if an error occurs. 244// 245void BundleDiskRep::createMeta() 246{ 247 string meta = metaPath(BUNDLEDISKREP_DIRECTORY); 248 if (!mMetaExists) { 249 if (::mkdir(meta.c_str(), 0755) == 0) { 250 copyfile(cfStringRelease(copyCanonicalPath()).c_str(), meta.c_str(), NULL, COPYFILE_SECURITY); 251 mMetaPath = meta; 252 mMetaExists = true; 253 } else if (errno != EEXIST) 254 UnixError::throwMe(); 255 } 256} 257 258// 259// Load's a CFURL and makes sure that it is a regular file and not a symlink (or fifo, etc.) 260// 261CFDataRef BundleDiskRep::loadRegularFile(CFURLRef url) 262{ 263 assert(url); 264 265 CFDataRef data = NULL; 266 267 std::string path(cfString(url)); 268 269 AutoFileDesc fd(path); 270 271 if (!fd.isPlainFile(path)) 272 recordStrictError(errSecCSRegularFile); 273 274 data = cfLoadFile(fd, fd.fileSize()); 275 276 if (!data) { 277 secdebug(__PRETTY_FUNCTION__, "failed to load %s", cfString(url).c_str()); 278 MacOSError::throwMe(errSecCSInternalError); 279 } 280 281 return data; 282} 283 284// 285// Load and return a component, by slot number. 286// Info.plist components come from the bundle, always (we don't look 287// for Mach-O embedded versions). 288// Everything else comes from the embedded blobs of a Mach-O image, or from 289// files located in the Contents directory of the bundle. 290// 291CFDataRef BundleDiskRep::component(CodeDirectory::SpecialSlot slot) 292{ 293 switch (slot) { 294 // the Info.plist comes from the magic CFBundle-indicated place and ONLY from there 295 case cdInfoSlot: 296 if (CFRef<CFURLRef> info = _CFBundleCopyInfoPlistURL(mBundle)) 297 return loadRegularFile(info); 298 else 299 return NULL; 300 // by default, we take components from the executable image or files 301 default: 302 if (CFDataRef data = mExecRep->component(slot)) 303 return data; 304 // falling through 305 // but the following always come from files 306 case cdResourceDirSlot: 307 if (const char *name = CodeDirectory::canonicalSlotName(slot)) 308 return metaData(name); 309 else 310 return NULL; 311 } 312} 313 314 315// 316// The binary identifier is taken directly from the main executable. 317// 318CFDataRef BundleDiskRep::identification() 319{ 320 return mExecRep->identification(); 321} 322 323 324// 325// Various aspects of our DiskRep personality. 326// 327CFURLRef BundleDiskRep::copyCanonicalPath() 328{ 329 if (CFURLRef url = CFBundleCopyBundleURL(mBundle)) 330 return url; 331 CFError::throwMe(); 332} 333 334string BundleDiskRep::mainExecutablePath() 335{ 336 return cfString(mMainExecutableURL); 337} 338 339string BundleDiskRep::resourcesRootPath() 340{ 341 return cfStringRelease(CFBundleCopySupportFilesDirectoryURL(mBundle)); 342} 343 344void BundleDiskRep::adjustResources(ResourceBuilder &builder) 345{ 346 // exclude entire contents of meta directory 347 builder.addExclusion("^" BUNDLEDISKREP_DIRECTORY "$"); 348 builder.addExclusion("^" CODERESOURCES_LINK "$"); // ancient-ish symlink into it 349 350 // exclude the store manifest directory 351 builder.addExclusion("^" STORE_RECEIPT_DIRECTORY "$"); 352 353 // exclude the main executable file 354 string resources = resourcesRootPath(); 355 if (resources.compare(resources.size() - 2, 2, "/.") == 0) // chop trailing /. 356 resources = resources.substr(0, resources.size()-2); 357 string executable = mainExecutablePath(); 358 if (!executable.compare(0, resources.length(), resources, 0, resources.length()) 359 && executable[resources.length()] == '/') // is proper directory prefix 360 builder.addExclusion(string("^") 361 + ResourceBuilder::escapeRE(executable.substr(resources.length()+1)) + "$"); 362} 363 364 365 366Universal *BundleDiskRep::mainExecutableImage() 367{ 368 return mExecRep->mainExecutableImage(); 369} 370 371size_t BundleDiskRep::signingBase() 372{ 373 return mExecRep->signingBase(); 374} 375 376size_t BundleDiskRep::signingLimit() 377{ 378 return mExecRep->signingLimit(); 379} 380 381string BundleDiskRep::format() 382{ 383 return mFormat; 384} 385 386CFArrayRef BundleDiskRep::modifiedFiles() 387{ 388 CFMutableArrayRef files = CFArrayCreateMutableCopy(NULL, 0, mExecRep->modifiedFiles()); 389 checkModifiedFile(files, cdCodeDirectorySlot); 390 checkModifiedFile(files, cdSignatureSlot); 391 checkModifiedFile(files, cdResourceDirSlot); 392 checkModifiedFile(files, cdEntitlementSlot); 393 return files; 394} 395 396void BundleDiskRep::checkModifiedFile(CFMutableArrayRef files, CodeDirectory::SpecialSlot slot) 397{ 398 if (CFDataRef data = mExecRep->component(slot)) // provided by executable file 399 CFRelease(data); 400 else if (const char *resourceName = CodeDirectory::canonicalSlotName(slot)) { 401 string file = metaPath(resourceName); 402 if (::access(file.c_str(), F_OK) == 0) 403 CFArrayAppendValue(files, CFTempURL(file)); 404 } 405} 406 407FileDesc &BundleDiskRep::fd() 408{ 409 return mExecRep->fd(); 410} 411 412void BundleDiskRep::flush() 413{ 414 mExecRep->flush(); 415} 416 417 418// 419// Defaults for signing operations 420// 421string BundleDiskRep::recommendedIdentifier(const SigningContext &) 422{ 423 if (CFStringRef identifier = CFBundleGetIdentifier(mBundle)) 424 return cfString(identifier); 425 if (CFDictionaryRef infoDict = CFBundleGetInfoDictionary(mBundle)) 426 if (CFStringRef identifier = CFStringRef(CFDictionaryGetValue(infoDict, kCFBundleNameKey))) 427 return cfString(identifier); 428 429 // fall back to using the canonical path 430 return canonicalIdentifier(cfStringRelease(this->copyCanonicalPath())); 431} 432 433string BundleDiskRep::resourcesRelativePath() 434{ 435 // figure out the resource directory base. Clean up some gunk inserted by CFBundle in frameworks 436 string rbase = this->resourcesRootPath(); 437 size_t pos = rbase.find("/./"); // gratuitously inserted by CFBundle in some frameworks 438 while (pos != std::string::npos) { 439 rbase = rbase.replace(pos, 2, "", 0); 440 pos = rbase.find("/./"); 441 } 442 if (rbase.substr(rbase.length()-2, 2) == "/.") // produced by versioned bundle implicit "Current" case 443 rbase = rbase.substr(0, rbase.length()-2); // ... so take it off for this 444 445 // find the resources directory relative to the resource base 446 string resources = cfStringRelease(CFBundleCopyResourcesDirectoryURL(mBundle)); 447 if (resources == rbase) 448 resources = ""; 449 else if (resources.compare(0, rbase.length(), rbase, 0, rbase.length()) != 0) // Resources not in resource root 450 MacOSError::throwMe(errSecCSBadBundleFormat); 451 else 452 resources = resources.substr(rbase.length() + 1) + "/"; // differential path segment 453 454 return resources; 455} 456 457CFDictionaryRef BundleDiskRep::defaultResourceRules(const SigningContext &ctx) 458{ 459 string resources = this->resourcesRelativePath(); 460 461 // installer package rules 462 if (mInstallerPackage) 463 return cfmake<CFDictionaryRef>("{rules={" 464 "'^.*' = #T" // include everything, but... 465 "%s = {optional=#T, weight=1000}" // make localizations optional 466 "'^.*/.*\\.pkg/' = {omit=#T, weight=10000}" // and exclude all nested packages (by name) 467 "}}", 468 (string("^") + resources + ".*\\.lproj/").c_str() 469 ); 470 471 // old (V1) executable bundle rules - compatible with before 472 if (ctx.signingFlags() & kSecCSSignV1) // *** must be exactly the same as before *** 473 return cfmake<CFDictionaryRef>("{rules={" 474 "'^version.plist$' = #T" // include version.plist 475 "%s = #T" // include Resources 476 "%s = {optional=#T, weight=1000}" // make localizations optional 477 "%s = {omit=#T, weight=1100}" // exclude all locversion.plist files 478 "}}", 479 (string("^") + resources).c_str(), 480 (string("^") + resources + ".*\\.lproj/").c_str(), 481 (string("^") + resources + ".*\\.lproj/locversion.plist$").c_str() 482 ); 483 484 // FMJ (everything is a resource) rules 485 if (ctx.signingFlags() & kSecCSSignOpaque) // Full Metal Jacket - everything is a resource file 486 return cfmake<CFDictionaryRef>("{rules={" 487 "'^.*' = #T" // everything is a resource 488 "'^Info\\.plist$' = {omit=#T,weight=10}" // explicitly exclude this for backward compatibility 489 "}}"); 490 491 // new (V2) executable bundle rules 492 return cfmake<CFDictionaryRef>("{" // *** the new (V2) world *** 493 "rules={" // old (V1; legacy) version 494 "'^version.plist$' = #T" // include version.plist 495 "%s = #T" // include Resources 496 "%s = {optional=#T, weight=1000}" // make localizations optional 497 "%s = {omit=#T, weight=1100}" // exclude all locversion.plist files 498 "},rules2={" 499 "'^.*' = #T" // include everything as a resource, with the following exceptions 500 "'^[^/]+$' = {nested=#T, weight=10}" // files directly in Contents 501 "'^(Frameworks|SharedFrameworks|PlugIns|Plug-ins|XPCServices|Helpers|MacOS|Library/(Automator|Spotlight|LoginItems))/' = {nested=#T, weight=10}" // dynamic repositories 502 "'.*\\.dSYM($|/)' = {weight=11}" // but allow dSYM directories in code locations (parallel to their code) 503 "'^(.*/)?\\.DS_Store$' = {omit=#T,weight=2000}" // ignore .DS_Store files 504 "'^Info\\.plist$' = {omit=#T, weight=20}" // excluded automatically now, but old systems need to be told 505 "'^version\\.plist$' = {weight=20}" // include version.plist as resource 506 "'^embedded\\.provisionprofile$' = {weight=20}" // include embedded.provisionprofile as resource 507 "'^PkgInfo$' = {omit=#T, weight=20}" // traditionally not included 508 "%s = {weight=20}" // Resources override default nested (widgets) 509 "%s = {optional=#T, weight=1000}" // make localizations optional 510 "%s = {omit=#T, weight=1100}" // exclude all locversion.plist files 511 "}}", 512 513 (string("^") + resources).c_str(), 514 (string("^") + resources + ".*\\.lproj/").c_str(), 515 (string("^") + resources + ".*\\.lproj/locversion.plist$").c_str(), 516 517 (string("^") + resources).c_str(), 518 (string("^") + resources + ".*\\.lproj/").c_str(), 519 (string("^") + resources + ".*\\.lproj/locversion.plist$").c_str() 520 ); 521} 522 523 524CFArrayRef BundleDiskRep::allowedResourceOmissions() 525{ 526 return cfmake<CFArrayRef>("[" 527 "'^(.*/)?\\.DS_Store$'" 528 "'^Info\\.plist$'" 529 "'^PkgInfo$'" 530 "%s" 531 "]", 532 (string("^") + this->resourcesRelativePath() + ".*\\.lproj/locversion.plist$").c_str() 533 ); 534} 535 536 537const Requirements *BundleDiskRep::defaultRequirements(const Architecture *arch, const SigningContext &ctx) 538{ 539 return mExecRep->defaultRequirements(arch, ctx); 540} 541 542size_t BundleDiskRep::pageSize(const SigningContext &ctx) 543{ 544 return mExecRep->pageSize(ctx); 545} 546 547 548// 549// Strict validation. 550// Takes an array of CFNumbers of errors to tolerate. 551// 552void BundleDiskRep::strictValidate(const ToleratedErrors& tolerated) 553{ 554 std::vector<OSStatus> fatalErrors; 555 set_difference(mStrictErrors.begin(), mStrictErrors.end(), tolerated.begin(), tolerated.end(), back_inserter(fatalErrors)); 556 if (!fatalErrors.empty()) 557 MacOSError::throwMe(fatalErrors[0]); 558 mExecRep->strictValidate(tolerated); 559} 560 561void BundleDiskRep::recordStrictError(OSStatus error) 562{ 563 mStrictErrors.insert(error); 564} 565 566 567// 568// Check framework root for unsafe symlinks and unsealed content. 569// 570void BundleDiskRep::validateFrameworkRoot(string root) 571{ 572 // build regex element that matches either the "Current" symlink, or the name of the current version 573 string current = "Current"; 574 char currentVersion[PATH_MAX]; 575 ssize_t len = ::readlink((root + "/Versions/Current").c_str(), currentVersion, sizeof(currentVersion)-1); 576 if (len > 0) { 577 currentVersion[len] = '\0'; 578 current = string("(Current|") + ResourceBuilder::escapeRE(currentVersion) + ")"; 579 } 580 581 DirValidator val; 582 val.require("^Versions$", DirValidator::directory | DirValidator::descend); // descend into Versions directory 583 val.require("^Versions/[^/]+$", DirValidator::directory); // require at least one version 584 val.require("^Versions/Current$", DirValidator::symlink, // require Current symlink... 585 "^(\\./)?(\\.\\.[^/]+|\\.?[^\\./][^/]*)$"); // ...must point to a version 586 val.allow("^(Versions/)?\\.DS_Store$", DirValidator::file | DirValidator::noexec); // allow .DS_Store files 587 val.allow("^[^/]+$", DirValidator::symlink, ^ string (const string &name, const string &target) { 588 // top-level symlinks must point to namesake in current version 589 return string("^(\\./)?Versions/") + current + "/" + ResourceBuilder::escapeRE(name) + "$"; 590 }); 591 // module.map must be regular non-executable file, or symlink to module.map in current version 592 val.allow("^module\\.map$", DirValidator::file | DirValidator::noexec | DirValidator::symlink, 593 string("^(\\./)?Versions/") + current + "/module\\.map$"); 594 595 try { 596 val.validate(root, errSecCSUnsealedFrameworkRoot); 597 } catch (const MacOSError &err) { 598 recordStrictError(err.error); 599 } 600} 601 602 603// 604// Writers 605// 606DiskRep::Writer *BundleDiskRep::writer() 607{ 608 return new Writer(this); 609} 610 611BundleDiskRep::Writer::Writer(BundleDiskRep *r) 612 : rep(r), mMadeMetaDirectory(false) 613{ 614 execWriter = rep->mExecRep->writer(); 615} 616 617 618// 619// Write a component. 620// Note that this isn't concerned with Mach-O writing; this is handled at 621// a much higher level. If we're called, we write to a file in the Bundle's meta directory. 622// 623void BundleDiskRep::Writer::component(CodeDirectory::SpecialSlot slot, CFDataRef data) 624{ 625 switch (slot) { 626 default: 627 if (!execWriter->attribute(writerLastResort)) // willing to take the data... 628 return execWriter->component(slot, data); // ... so hand it through 629 // execWriter doesn't want the data; store it as a resource file (below) 630 case cdResourceDirSlot: 631 // the resource directory always goes into a bundle file 632 if (const char *name = CodeDirectory::canonicalSlotName(slot)) { 633 rep->createMeta(); 634 string path = rep->metaPath(name); 635 AutoFileDesc fd(path, O_WRONLY | O_CREAT | O_TRUNC, 0644); 636 fd.writeAll(CFDataGetBytePtr(data), CFDataGetLength(data)); 637 } else 638 MacOSError::throwMe(errSecCSBadBundleFormat); 639 } 640} 641 642 643// 644// Remove all signature data 645// 646void BundleDiskRep::Writer::remove() 647{ 648 // remove signature from the executable 649 execWriter->remove(); 650 651 // remove signature files from bundle 652 for (CodeDirectory::SpecialSlot slot = 0; slot < cdSlotCount; slot++) 653 remove(slot); 654 remove(cdSignatureSlot); 655} 656 657void BundleDiskRep::Writer::remove(CodeDirectory::SpecialSlot slot) 658{ 659 if (const char *name = CodeDirectory::canonicalSlotName(slot)) 660 if (::unlink(rep->metaPath(name).c_str())) 661 switch (errno) { 662 case ENOENT: // not found - that's okay 663 break; 664 default: 665 UnixError::throwMe(); 666 } 667} 668 669 670void BundleDiskRep::Writer::flush() 671{ 672 execWriter->flush(); 673} 674 675 676} // end namespace CodeSigning 677} // end namespace Security 678