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