]> git.saurik.com Git - apple/security.git/blob - Security/libsecurity_codesigning/lib/bundlediskrep.cpp
aaf8bc0688178a34292e62e83919bacb13d26df9
[apple/security.git] / Security / libsecurity_codesigning / lib / bundlediskrep.cpp
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
33 namespace Security {
34 namespace CodeSigning {
35
36 using namespace UnixPlusPlus;
37
38
39 //
40 // Local helpers
41 //
42 static std::string findDistFile(const std::string &directory);
43
44
45 //
46 // We make a CFBundleRef immediately, but everything else is lazy
47 //
48 BundleDiskRep::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
57 BundleDiskRep::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
64 BundleDiskRep::~BundleDiskRep()
65 {
66 }
67
68 // common construction code
69 void 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 //
188 static 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 //
224 string 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 //
245 void 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 //
261 CFDataRef 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 //
291 CFDataRef 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 //
318 CFDataRef BundleDiskRep::identification()
319 {
320 return mExecRep->identification();
321 }
322
323
324 //
325 // Various aspects of our DiskRep personality.
326 //
327 CFURLRef BundleDiskRep::copyCanonicalPath()
328 {
329 if (CFURLRef url = CFBundleCopyBundleURL(mBundle))
330 return url;
331 CFError::throwMe();
332 }
333
334 string BundleDiskRep::mainExecutablePath()
335 {
336 return cfString(mMainExecutableURL);
337 }
338
339 string BundleDiskRep::resourcesRootPath()
340 {
341 return cfStringRelease(CFBundleCopySupportFilesDirectoryURL(mBundle));
342 }
343
344 void 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
366 Universal *BundleDiskRep::mainExecutableImage()
367 {
368 return mExecRep->mainExecutableImage();
369 }
370
371 size_t BundleDiskRep::signingBase()
372 {
373 return mExecRep->signingBase();
374 }
375
376 size_t BundleDiskRep::signingLimit()
377 {
378 return mExecRep->signingLimit();
379 }
380
381 string BundleDiskRep::format()
382 {
383 return mFormat;
384 }
385
386 CFArrayRef 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
396 void 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
407 FileDesc &BundleDiskRep::fd()
408 {
409 return mExecRep->fd();
410 }
411
412 void BundleDiskRep::flush()
413 {
414 mExecRep->flush();
415 }
416
417
418 //
419 // Defaults for signing operations
420 //
421 string 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
433 string 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
457 CFDictionaryRef 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
524 CFArrayRef 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
537 const Requirements *BundleDiskRep::defaultRequirements(const Architecture *arch, const SigningContext &ctx)
538 {
539 return mExecRep->defaultRequirements(arch, ctx);
540 }
541
542 size_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 //
552 void 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
561 void BundleDiskRep::recordStrictError(OSStatus error)
562 {
563 mStrictErrors.insert(error);
564 }
565
566
567 //
568 // Check framework root for unsafe symlinks and unsealed content.
569 //
570 void 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 //
606 DiskRep::Writer *BundleDiskRep::writer()
607 {
608 return new Writer(this);
609 }
610
611 BundleDiskRep::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 //
623 void 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 //
646 void 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
657 void 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
670 void BundleDiskRep::Writer::flush()
671 {
672 execWriter->flush();
673 }
674
675
676 } // end namespace CodeSigning
677 } // end namespace Security