]> git.saurik.com Git - apple/security.git/blob - libsecurity_codesigning/lib/bundlediskrep.cpp
Security-55471.14.tar.gz
[apple/security.git] / libsecurity_codesigning / lib / bundlediskrep.cpp
1 /*
2 * Copyright (c) 2006-2011 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 <CoreFoundation/CFURLAccess.h>
26 #include <CoreFoundation/CFBundlePriv.h>
27 #include <security_utilities/cfmunge.h>
28 #include <copyfile.h>
29 #include <fts.h>
30
31
32 namespace Security {
33 namespace CodeSigning {
34
35 using namespace UnixPlusPlus;
36
37
38 //
39 // Local helpers
40 //
41 static std::string findDistFile(const std::string &directory);
42
43
44 //
45 // We make a CFBundleRef immediately, but everything else is lazy
46 //
47 BundleDiskRep::BundleDiskRep(const char *path, const Context *ctx)
48 : mBundle(CFBundleCreate(NULL, CFTempURL(path)))
49 {
50 if (!mBundle)
51 MacOSError::throwMe(errSecCSBadBundleFormat);
52 setup(ctx);
53 CODESIGN_DISKREP_CREATE_BUNDLE_PATH(this, (char*)path, (void*)ctx, mExecRep);
54 }
55
56 BundleDiskRep::BundleDiskRep(CFBundleRef ref, const Context *ctx)
57 {
58 mBundle = ref; // retains
59 setup(ctx);
60 CODESIGN_DISKREP_CREATE_BUNDLE_REF(this, ref, (void*)ctx, mExecRep);
61 }
62
63 BundleDiskRep::~BundleDiskRep()
64 {
65 }
66
67 // common construction code
68 void BundleDiskRep::setup(const Context *ctx)
69 {
70 mInstallerPackage = false; // default
71
72 // deal with versioned bundles (aka Frameworks)
73 string version = resourcesRootPath()
74 + "/Versions/"
75 + ((ctx && ctx->version) ? ctx->version : "Current")
76 + "/.";
77 if (::access(version.c_str(), F_OK) == 0) { // versioned bundle
78 if (CFBundleRef versionBundle = CFBundleCreate(NULL, CFTempURL(version)))
79 mBundle.take(versionBundle); // replace top bundle ref
80 else
81 MacOSError::throwMe(errSecCSStaticCodeNotFound);
82 } else {
83 if (ctx && ctx->version) // explicitly specified
84 MacOSError::throwMe(errSecCSStaticCodeNotFound);
85 }
86
87 CFDictionaryRef infoDict = CFBundleGetInfoDictionary(mBundle);
88 assert(infoDict); // CFBundle will always make one up for us
89 CFTypeRef mainHTML = CFDictionaryGetValue(infoDict, CFSTR("MainHTML"));
90 CFTypeRef packageVersion = CFDictionaryGetValue(infoDict, CFSTR("IFMajorVersion"));
91
92 // conventional executable bundle: CFBundle identifies an executable for us
93 if (CFRef<CFURLRef> mainExec = CFBundleCopyExecutableURL(mBundle)) // if CFBundle claims an executable...
94 if (mainHTML == NULL) { // ... and it's not a widget
95 mMainExecutableURL = mainExec;
96 mExecRep = DiskRep::bestFileGuess(this->mainExecutablePath(), ctx);
97 mFormat = "bundle with " + mExecRep->format();
98 return;
99 }
100
101 // widget
102 if (mainHTML) {
103 if (CFGetTypeID(mainHTML) != CFStringGetTypeID())
104 MacOSError::throwMe(errSecCSBadBundleFormat);
105 mMainExecutableURL.take(makeCFURL(cfString(CFStringRef(mainHTML)), false,
106 CFRef<CFURLRef>(CFBundleCopySupportFilesDirectoryURL(mBundle))));
107 if (!mMainExecutableURL)
108 MacOSError::throwMe(errSecCSBadBundleFormat);
109 mExecRep = new FileDiskRep(this->mainExecutablePath().c_str());
110 mFormat = "widget bundle";
111 return;
112 }
113
114 // do we have a real Info.plist here?
115 if (CFRef<CFURLRef> infoURL = _CFBundleCopyInfoPlistURL(mBundle)) {
116 // focus on the Info.plist (which we know exists) as the nominal "main executable" file
117 mMainExecutableURL = infoURL;
118 mExecRep = new FileDiskRep(this->mainExecutablePath().c_str());
119 if (packageVersion) {
120 mInstallerPackage = true;
121 mFormat = "installer package bundle";
122 } else {
123 mFormat = "bundle";
124 }
125 return;
126 }
127
128 // we're getting desperate here. Perhaps an oldish-style installer package? Look for a *.dist file
129 std::string distFile = findDistFile(this->resourcesRootPath());
130 if (!distFile.empty()) {
131 mMainExecutableURL = makeCFURL(distFile);
132 mExecRep = new FileDiskRep(this->mainExecutablePath().c_str());
133 mInstallerPackage = true;
134 mFormat = "installer package bundle";
135 return;
136 }
137
138 // this bundle cannot be signed
139 MacOSError::throwMe(errSecCSBadBundleFormat);
140 }
141
142
143 //
144 // Return the full path to the one-and-only file named something.dist in a directory.
145 // Return empty string if none; throw an exception if multiple. Do not descend into subdirectories.
146 //
147 static std::string findDistFile(const std::string &directory)
148 {
149 std::string found;
150 char *paths[] = {(char *)directory.c_str(), NULL};
151 FTS *fts = fts_open(paths, FTS_PHYSICAL | FTS_NOCHDIR | FTS_NOSTAT, NULL);
152 bool root = true;
153 while (FTSENT *ent = fts_read(fts)) {
154 switch (ent->fts_info) {
155 case FTS_F:
156 case FTS_NSOK:
157 if (!strcmp(ent->fts_path + ent->fts_pathlen - 5, ".dist")) { // found plain file foo.dist
158 if (found.empty()) // first found
159 found = ent->fts_path;
160 else // multiple *.dist files (bad)
161 MacOSError::throwMe(errSecCSBadBundleFormat);
162 }
163 break;
164 case FTS_D:
165 if (!root)
166 fts_set(fts, ent, FTS_SKIP); // don't descend
167 root = false;
168 break;
169 default:
170 break;
171 }
172 }
173 fts_close(fts);
174 return found;
175 }
176
177
178 //
179 // Create a path to a bundle signing resource, by name.
180 // If the BUNDLEDISKREP_DIRECTORY directory exists in the bundle's support directory, files
181 // will be read and written there. Otherwise, they go directly into the support directory.
182 //
183 string BundleDiskRep::metaPath(const char *name)
184 {
185 if (mMetaPath.empty()) {
186 string support = cfStringRelease(CFBundleCopySupportFilesDirectoryURL(mBundle));
187 mMetaPath = support + "/" BUNDLEDISKREP_DIRECTORY;
188 if (::access(mMetaPath.c_str(), F_OK) == 0) {
189 mMetaExists = true;
190 } else {
191 mMetaPath = support;
192 mMetaExists = false;
193 }
194 }
195 return mMetaPath + "/" + name;
196 }
197
198
199 //
200 // Try to create the meta-file directory in our bundle.
201 // Does nothing if the directory already exists.
202 // Throws if an error occurs.
203 //
204 void BundleDiskRep::createMeta()
205 {
206 string meta = metaPath(BUNDLEDISKREP_DIRECTORY);
207 if (!mMetaExists) {
208 if (::mkdir(meta.c_str(), 0755) == 0) {
209 copyfile(cfString(canonicalPath(), true).c_str(), meta.c_str(), NULL, COPYFILE_SECURITY);
210 mMetaPath = meta;
211 mMetaExists = true;
212 } else if (errno != EEXIST)
213 UnixError::throwMe();
214 }
215 }
216
217
218 //
219 // Load and return a component, by slot number.
220 // Info.plist components come from the bundle, always (we don't look
221 // for Mach-O embedded versions).
222 // Everything else comes from the embedded blobs of a Mach-O image, or from
223 // files located in the Contents directory of the bundle.
224 //
225 CFDataRef BundleDiskRep::component(CodeDirectory::SpecialSlot slot)
226 {
227 switch (slot) {
228 // the Info.plist comes from the magic CFBundle-indicated place and ONLY from there
229 case cdInfoSlot:
230 if (CFRef<CFURLRef> info = _CFBundleCopyInfoPlistURL(mBundle))
231 return cfLoadFile(info);
232 else
233 return NULL;
234 // by default, we take components from the executable image or files
235 default:
236 if (CFDataRef data = mExecRep->component(slot))
237 return data;
238 // falling through
239 // but the following always come from files
240 case cdResourceDirSlot:
241 if (const char *name = CodeDirectory::canonicalSlotName(slot))
242 return metaData(name);
243 else
244 return NULL;
245 }
246 }
247
248
249 //
250 // The binary identifier is taken directly from the main executable.
251 //
252 CFDataRef BundleDiskRep::identification()
253 {
254 return mExecRep->identification();
255 }
256
257
258 //
259 // Various aspects of our DiskRep personality.
260 //
261 CFURLRef BundleDiskRep::canonicalPath()
262 {
263 if (CFURLRef url = CFBundleCopyBundleURL(mBundle))
264 return url;
265 CFError::throwMe();
266 }
267
268 string BundleDiskRep::mainExecutablePath()
269 {
270 return cfString(mMainExecutableURL);
271 }
272
273 string BundleDiskRep::resourcesRootPath()
274 {
275 return cfStringRelease(CFBundleCopySupportFilesDirectoryURL(mBundle));
276 }
277
278 void BundleDiskRep::adjustResources(ResourceBuilder &builder)
279 {
280 // exclude entire contents of meta directory
281 builder.addExclusion("^" BUNDLEDISKREP_DIRECTORY "$");
282 builder.addExclusion("^" CODERESOURCES_LINK "$"); // ancient-ish symlink into it
283
284 // exclude the store manifest directory
285 builder.addExclusion("^" STORE_RECEIPT_DIRECTORY "$");
286
287 // exclude the main executable file
288 string resources = resourcesRootPath();
289 if (resources.compare(resources.size() - 2, 2, "/.") == 0) // chop trailing /.
290 resources = resources.substr(0, resources.size()-2);
291 string executable = mainExecutablePath();
292 if (!executable.compare(0, resources.length(), resources, 0, resources.length())
293 && executable[resources.length()] == '/') // is proper directory prefix
294 builder.addExclusion(string("^")
295 + ResourceBuilder::escapeRE(executable.substr(resources.length()+1)) + "$");
296 }
297
298
299
300 Universal *BundleDiskRep::mainExecutableImage()
301 {
302 return mExecRep->mainExecutableImage();
303 }
304
305 size_t BundleDiskRep::signingBase()
306 {
307 return mExecRep->signingBase();
308 }
309
310 size_t BundleDiskRep::signingLimit()
311 {
312 return mExecRep->signingLimit();
313 }
314
315 string BundleDiskRep::format()
316 {
317 return mFormat;
318 }
319
320 CFArrayRef BundleDiskRep::modifiedFiles()
321 {
322 CFMutableArrayRef files = CFArrayCreateMutableCopy(NULL, 0, mExecRep->modifiedFiles());
323 checkModifiedFile(files, cdCodeDirectorySlot);
324 checkModifiedFile(files, cdSignatureSlot);
325 checkModifiedFile(files, cdResourceDirSlot);
326 checkModifiedFile(files, cdEntitlementSlot);
327 return files;
328 }
329
330 void BundleDiskRep::checkModifiedFile(CFMutableArrayRef files, CodeDirectory::SpecialSlot slot)
331 {
332 if (CFDataRef data = mExecRep->component(slot)) // provided by executable file
333 CFRelease(data);
334 else if (const char *resourceName = CodeDirectory::canonicalSlotName(slot)) {
335 string file = metaPath(resourceName);
336 if (::access(file.c_str(), F_OK) == 0)
337 CFArrayAppendValue(files, CFTempURL(file));
338 }
339 }
340
341 FileDesc &BundleDiskRep::fd()
342 {
343 return mExecRep->fd();
344 }
345
346 void BundleDiskRep::flush()
347 {
348 mExecRep->flush();
349 }
350
351
352 //
353 // Defaults for signing operations
354 //
355 string BundleDiskRep::recommendedIdentifier(const SigningContext &)
356 {
357 if (CFStringRef identifier = CFBundleGetIdentifier(mBundle))
358 return cfString(identifier);
359 if (CFDictionaryRef infoDict = CFBundleGetInfoDictionary(mBundle))
360 if (CFStringRef identifier = CFStringRef(CFDictionaryGetValue(infoDict, kCFBundleNameKey)))
361 return cfString(identifier);
362
363 // fall back to using the canonical path
364 return canonicalIdentifier(cfString(this->canonicalPath()));
365 }
366
367 CFDictionaryRef BundleDiskRep::defaultResourceRules(const SigningContext &ctx)
368 {
369 // figure out the resource directory base. Clean up some gunk inserted by CFBundle in frameworks
370 string rbase = this->resourcesRootPath();
371 size_t pos = rbase.find("/./"); // gratuitously inserted by CFBundle in some frameworks
372 while (pos != std::string::npos) {
373 rbase = rbase.replace(pos, 2, "", 0);
374 pos = rbase.find("/./");
375 }
376 if (rbase.substr(rbase.length()-2, 2) == "/.") // produced by versioned bundle implicit "Current" case
377 rbase = rbase.substr(0, rbase.length()-2); // ... so take it off for this
378
379 // find the resources directory relative to the resource base
380 string resources = cfStringRelease(CFBundleCopyResourcesDirectoryURL(mBundle));
381 if (resources == rbase)
382 resources = "";
383 else if (resources.compare(0, rbase.length(), rbase, 0, rbase.length()) != 0) // Resources not in resource root
384 MacOSError::throwMe(errSecCSBadBundleFormat);
385 else
386 resources = resources.substr(rbase.length() + 1) + "/"; // differential path segment
387
388 // installer package rules
389 if (mInstallerPackage)
390 return cfmake<CFDictionaryRef>("{rules={"
391 "'^.*' = #T" // include everything, but...
392 "%s = {optional=#T, weight=1000}" // make localizations optional
393 "'^.*/.*\\.pkg/' = {omit=#T, weight=10000}" // and exclude all nested packages (by name)
394 "}}",
395 (string("^") + resources + ".*\\.lproj/").c_str()
396 );
397
398 // old (V1) executable bundle rules - compatible with before
399 if (ctx.signingFlags() & kSecCSSignV1) // *** must be exactly the same as before ***
400 return cfmake<CFDictionaryRef>("{rules={"
401 "'^version.plist$' = #T" // include version.plist
402 "%s = #T" // include Resources
403 "%s = {optional=#T, weight=1000}" // make localizations optional
404 "%s = {omit=#T, weight=1100}" // exclude all locversion.plist files
405 "}}",
406 (string("^") + resources).c_str(),
407 (string("^") + resources + ".*\\.lproj/").c_str(),
408 (string("^") + resources + ".*\\.lproj/locversion.plist$").c_str()
409 );
410
411 // FMJ (everything is a resource) rules
412 if (ctx.signingFlags() & kSecCSSignOpaque) // Full Metal Jacket - everything is a resource file
413 return cfmake<CFDictionaryRef>("{rules={"
414 "'^.*' = #T" // everything is a resource
415 "'^Info\\.plist$' = {omit=#T,weight=10}" // explicitly exclude this for backward compatibility
416 "}}");
417
418 // new (V2) executable bundle rules
419 return cfmake<CFDictionaryRef>("{" // *** the new (V2) world ***
420 "rules={" // old (V1; legacy) version
421 "'^version.plist$' = #T" // include version.plist
422 "%s = #T" // include Resources
423 "%s = {optional=#T, weight=1000}" // make localizations optional
424 "%s = {omit=#T, weight=1100}" // exclude all locversion.plist files
425 "},rules2={"
426 "'^.*' = #T" // include everything as a resource, with the following exceptions
427 "'^[^/]+$' = {nested=#T, weight=10}" // files directly in Contents
428 "'^(Frameworks|SharedFrameworks|PlugIns|Plug-ins|XPCServices|Helpers|MacOS|Library/(Automator|Spotlight|LoginItems))/' = {nested=#T, weight=10}" // dynamic repositories
429 "'.*\\.dSYM($|/)' = {weight=11}" // but allow dSYM directories in code locations (parallel to their code)
430 "'^(.*/)?\\.DS_Store$' = {omit=#T,weight=2000}" // ignore .DS_Store files
431 "'^Info\\.plist$' = {omit=#T, weight=20}" // excluded automatically now, but old systems need to be told
432 "'^version\\.plist$' = {weight=20}" // include version.plist as resource
433 "'^embedded\\.provisionprofile$' = {weight=20}" // include embedded.provisionprofile as resource
434 "'^PkgInfo$' = {omit=#T, weight=20}" // traditionally not included
435 "%s = {weight=20}" // Resources override default nested (widgets)
436 "%s = {optional=#T, weight=1000}" // make localizations optional
437 "%s = {omit=#T, weight=1100}" // exclude all locversion.plist files
438 "}}",
439
440 (string("^") + resources).c_str(),
441 (string("^") + resources + ".*\\.lproj/").c_str(),
442 (string("^") + resources + ".*\\.lproj/locversion.plist$").c_str(),
443
444 (string("^") + resources).c_str(),
445 (string("^") + resources + ".*\\.lproj/").c_str(),
446 (string("^") + resources + ".*\\.lproj/locversion.plist$").c_str()
447 );
448 }
449
450 const Requirements *BundleDiskRep::defaultRequirements(const Architecture *arch, const SigningContext &ctx)
451 {
452 return mExecRep->defaultRequirements(arch, ctx);
453 }
454
455 size_t BundleDiskRep::pageSize(const SigningContext &ctx)
456 {
457 return mExecRep->pageSize(ctx);
458 }
459
460
461 //
462 // Writers
463 //
464 DiskRep::Writer *BundleDiskRep::writer()
465 {
466 return new Writer(this);
467 }
468
469 BundleDiskRep::Writer::Writer(BundleDiskRep *r)
470 : rep(r), mMadeMetaDirectory(false)
471 {
472 execWriter = rep->mExecRep->writer();
473 }
474
475
476 //
477 // Write a component.
478 // Note that this isn't concerned with Mach-O writing; this is handled at
479 // a much higher level. If we're called, we write to a file in the Bundle's meta directory.
480 //
481 void BundleDiskRep::Writer::component(CodeDirectory::SpecialSlot slot, CFDataRef data)
482 {
483 switch (slot) {
484 default:
485 if (!execWriter->attribute(writerLastResort)) // willing to take the data...
486 return execWriter->component(slot, data); // ... so hand it through
487 // execWriter doesn't want the data; store it as a resource file (below)
488 case cdResourceDirSlot:
489 // the resource directory always goes into a bundle file
490 if (const char *name = CodeDirectory::canonicalSlotName(slot)) {
491 rep->createMeta();
492 string path = rep->metaPath(name);
493 AutoFileDesc fd(path, O_WRONLY | O_CREAT | O_TRUNC, 0644);
494 fd.writeAll(CFDataGetBytePtr(data), CFDataGetLength(data));
495 } else
496 MacOSError::throwMe(errSecCSBadBundleFormat);
497 }
498 }
499
500
501 //
502 // Remove all signature data
503 //
504 void BundleDiskRep::Writer::remove()
505 {
506 // remove signature from the executable
507 execWriter->remove();
508
509 // remove signature files from bundle
510 for (CodeDirectory::SpecialSlot slot = 0; slot < cdSlotCount; slot++)
511 remove(slot);
512 remove(cdSignatureSlot);
513 }
514
515 void BundleDiskRep::Writer::remove(CodeDirectory::SpecialSlot slot)
516 {
517 if (const char *name = CodeDirectory::canonicalSlotName(slot))
518 if (::unlink(rep->metaPath(name).c_str()))
519 switch (errno) {
520 case ENOENT: // not found - that's okay
521 break;
522 default:
523 UnixError::throwMe();
524 }
525 }
526
527
528 void BundleDiskRep::Writer::flush()
529 {
530 execWriter->flush();
531 }
532
533
534 } // end namespace CodeSigning
535 } // end namespace Security