]> git.saurik.com Git - apple/libsecurity_codesigning.git/blob - lib/bundlediskrep.cpp
757bbbc25d32237ec4a4f1bc72b425ef6776d173
[apple/libsecurity_codesigning.git] / 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
30
31 namespace Security {
32 namespace CodeSigning {
33
34 using namespace UnixPlusPlus;
35
36
37 //
38 // We make a CFBundleRef immediately, but everything else is lazy
39 //
40 BundleDiskRep::BundleDiskRep(const char *path, const Context *ctx)
41 : mBundle(CFBundleCreate(NULL, CFTempURL(path)))
42 {
43 if (!mBundle)
44 MacOSError::throwMe(errSecCSBadBundleFormat);
45 setup(ctx);
46 CODESIGN_DISKREP_CREATE_BUNDLE_PATH(this, (char*)path, (void*)ctx, mExecRep);
47 }
48
49 BundleDiskRep::BundleDiskRep(CFBundleRef ref, const Context *ctx)
50 {
51 mBundle = ref; // retains
52 setup(ctx);
53 CODESIGN_DISKREP_CREATE_BUNDLE_REF(this, ref, (void*)ctx, mExecRep);
54 }
55
56 // common construction code
57 void BundleDiskRep::setup(const Context *ctx)
58 {
59 // deal with versioned bundles (aka Frameworks)
60 string version = resourcesRootPath()
61 + "/Versions/"
62 + ((ctx && ctx->version) ? ctx->version : "Current")
63 + "/.";
64 if (::access(version.c_str(), F_OK) == 0) { // versioned bundle
65 if (CFBundleRef versionBundle = CFBundleCreate(NULL, CFTempURL(version)))
66 mBundle.take(versionBundle); // replace top bundle ref
67 else
68 MacOSError::throwMe(errSecCSStaticCodeNotFound);
69 } else {
70 if (ctx && ctx->version) // explicitly specified
71 MacOSError::throwMe(errSecCSStaticCodeNotFound);
72 }
73
74 // conventional executable bundle: CFBundle identifies an executable for us
75 if (mMainExecutableURL.take(CFBundleCopyExecutableURL(mBundle))) {
76 // conventional executable bundle
77 mExecRep = DiskRep::bestFileGuess(this->mainExecutablePath(), ctx);
78 mFormat = string("bundle with ") + mExecRep->format();
79 return;
80 }
81
82 CFDictionaryRef infoDict = CFBundleGetInfoDictionary(mBundle);
83 assert(infoDict); // CFBundle will always make one up for us
84
85 if (CFTypeRef main = CFDictionaryGetValue(infoDict, CFSTR("MainHTML"))) {
86 // widget
87 if (CFGetTypeID(main) != CFStringGetTypeID())
88 MacOSError::throwMe(errSecCSBadBundleFormat);
89 mMainExecutableURL = makeCFURL(cfString(CFStringRef(main)), false, CFRef<CFURLRef>(CFBundleCopySupportFilesDirectoryURL(mBundle)));
90 if (!mMainExecutableURL)
91 MacOSError::throwMe(errSecCSBadBundleFormat);
92 mExecRep = new FileDiskRep(this->mainExecutablePath().c_str());
93 mFormat = "widget bundle";
94 return;
95 }
96
97 // generic bundle case - impose our own "minimal signable bundle" standard
98
99 // we MUST have an actual Info.plist in here
100 CFRef<CFURLRef> infoURL = _CFBundleCopyInfoPlistURL(mBundle);
101 if (!infoURL)
102 MacOSError::throwMe(errSecCSBadBundleFormat);
103
104 // focus on the Info.plist (which we know exists) as the nominal "main executable" file
105 if ((mMainExecutableURL = _CFBundleCopyInfoPlistURL(mBundle))) {
106 mExecRep = new FileDiskRep(this->mainExecutablePath().c_str());
107 mFormat = "bundle";
108 return;
109 }
110
111 // this bundle cannot be signed
112 MacOSError::throwMe(errSecCSBadBundleFormat);
113 }
114
115
116 //
117 // Create a path to a bundle signing resource, by name.
118 // If the BUNDLEDISKREP_DIRECTORY directory exists in the bundle's support directory, files
119 // will be read and written there. Otherwise, they go directly into the support directory.
120 //
121 string BundleDiskRep::metaPath(const char *name)
122 {
123 if (mMetaPath.empty()) {
124 string support = cfString(CFBundleCopySupportFilesDirectoryURL(mBundle), true);
125 mMetaPath = support + "/" BUNDLEDISKREP_DIRECTORY;
126 if (::access(mMetaPath.c_str(), F_OK) == 0) {
127 mMetaExists = true;
128 } else {
129 mMetaPath = support;
130 mMetaExists = false;
131 }
132 }
133 return mMetaPath + "/" + name;
134 }
135
136
137 //
138 // Try to create the meta-file directory in our bundle.
139 // Does nothing if the directory already exists.
140 // Throws if an error occurs.
141 //
142 void BundleDiskRep::createMeta()
143 {
144 string meta = metaPath(BUNDLEDISKREP_DIRECTORY);
145 if (!mMetaExists) {
146 if (::mkdir(meta.c_str(), 0755) == 0) {
147 copyfile(cfString(canonicalPath(), true).c_str(), meta.c_str(), NULL, COPYFILE_SECURITY);
148 mMetaPath = meta;
149 mMetaExists = true;
150 } else if (errno != EEXIST)
151 UnixError::throwMe();
152 }
153 }
154
155
156 //
157 // Load and return a component, by slot number.
158 // Info.plist components come from the bundle, always (we don't look
159 // for Mach-O embedded versions).
160 // Everything else comes from the embedded blobs of a Mach-O image, or from
161 // files located in the Contents directory of the bundle.
162 //
163 CFDataRef BundleDiskRep::component(CodeDirectory::SpecialSlot slot)
164 {
165 switch (slot) {
166 // the Info.plist comes from the magic CFBundle-indicated place and ONLY from there
167 case cdInfoSlot:
168 if (CFRef<CFURLRef> info = _CFBundleCopyInfoPlistURL(mBundle))
169 return cfLoadFile(info);
170 else
171 return NULL;
172 // by default, we take components from the executable image or files
173 default:
174 if (CFDataRef data = mExecRep->component(slot))
175 return data;
176 // falling through
177 // but the following always come from files
178 case cdResourceDirSlot:
179 if (const char *name = CodeDirectory::canonicalSlotName(slot))
180 return metaData(name);
181 else
182 return NULL;
183 }
184 }
185
186
187 //
188 // The binary identifier is taken directly from the main executable.
189 //
190 CFDataRef BundleDiskRep::identification()
191 {
192 return mExecRep->identification();
193 }
194
195
196 //
197 // Various aspects of our DiskRep personality.
198 //
199 CFURLRef BundleDiskRep::canonicalPath()
200 {
201 return CFBundleCopyBundleURL(mBundle);
202 }
203
204 string BundleDiskRep::mainExecutablePath()
205 {
206 return cfString(mMainExecutableURL);
207 }
208
209 string BundleDiskRep::resourcesRootPath()
210 {
211 return cfString(CFBundleCopySupportFilesDirectoryURL(mBundle), true);
212 }
213
214 void BundleDiskRep::adjustResources(ResourceBuilder &builder)
215 {
216 // exclude entire contents of meta directory
217 builder.addExclusion("^" BUNDLEDISKREP_DIRECTORY "/");
218
219 // exclude the store manifest directory
220 builder.addExclusion("^" STORE_RECEIPT_DIRECTORY "/");
221
222 // exclude the main executable file
223 string resources = resourcesRootPath();
224 string executable = mainExecutablePath();
225 if (!executable.compare(0, resources.length(), resources, 0, resources.length())) // is prefix
226 builder.addExclusion(string("^")
227 + ResourceBuilder::escapeRE(executable.substr(resources.length() + 1)) + "$");
228 }
229
230
231
232 Universal *BundleDiskRep::mainExecutableImage()
233 {
234 return mExecRep->mainExecutableImage();
235 }
236
237 size_t BundleDiskRep::signingBase()
238 {
239 return mExecRep->signingBase();
240 }
241
242 size_t BundleDiskRep::signingLimit()
243 {
244 return mExecRep->signingLimit();
245 }
246
247 string BundleDiskRep::format()
248 {
249 return mFormat;
250 }
251
252 CFArrayRef BundleDiskRep::modifiedFiles()
253 {
254 CFMutableArrayRef files = CFArrayCreateMutableCopy(NULL, 0, mExecRep->modifiedFiles());
255 checkModifiedFile(files, cdCodeDirectorySlot);
256 checkModifiedFile(files, cdSignatureSlot);
257 checkModifiedFile(files, cdResourceDirSlot);
258 checkModifiedFile(files, cdEntitlementSlot);
259 return files;
260 }
261
262 void BundleDiskRep::checkModifiedFile(CFMutableArrayRef files, CodeDirectory::SpecialSlot slot)
263 {
264 if (CFDataRef data = mExecRep->component(slot)) // provided by executable file
265 CFRelease(data);
266 else if (const char *resourceName = CodeDirectory::canonicalSlotName(slot)) {
267 string file = metaPath(resourceName);
268 if (::access(file.c_str(), F_OK) == 0)
269 CFArrayAppendValue(files, CFTempURL(file));
270 }
271 }
272
273 FileDesc &BundleDiskRep::fd()
274 {
275 return mExecRep->fd();
276 }
277
278 void BundleDiskRep::flush()
279 {
280 mExecRep->flush();
281 }
282
283
284 //
285 // Defaults for signing operations
286 //
287 string BundleDiskRep::recommendedIdentifier(const SigningContext &)
288 {
289 if (CFStringRef identifier = CFBundleGetIdentifier(mBundle))
290 return cfString(identifier);
291 if (CFDictionaryRef infoDict = CFBundleGetInfoDictionary(mBundle))
292 if (CFStringRef identifier = CFStringRef(CFDictionaryGetValue(infoDict, kCFBundleNameKey)))
293 return cfString(identifier);
294
295 // fall back to using the canonical path
296 return canonicalIdentifier(cfString(this->canonicalPath()));
297 }
298
299 CFDictionaryRef BundleDiskRep::defaultResourceRules(const SigningContext &)
300 {
301 // consider the bundle's structure
302 string rbase = this->resourcesRootPath();
303 if (rbase.substr(rbase.length()-2, 2) == "/.") // produced by versioned bundle implicit "Current" case
304 rbase = rbase.substr(0, rbase.length()-2); // ... so take it off for this
305 string resources = cfString(CFBundleCopyResourcesDirectoryURL(mBundle), true);
306 if (resources == rbase)
307 resources = "";
308 else if (resources.compare(0, rbase.length(), rbase, 0, rbase.length()) != 0) // Resources not in resource root
309 MacOSError::throwMe(errSecCSBadBundleFormat);
310 else
311 resources = resources.substr(rbase.length() + 1) + "/"; // differential path segment
312
313 return cfmake<CFDictionaryRef>("{rules={"
314 "'^version.plist$' = #T"
315 "%s = #T"
316 "%s = {optional=#T, weight=1000}"
317 "%s = {omit=#T, weight=1100}"
318 "}}",
319 (string("^") + resources).c_str(),
320 (string("^") + resources + ".*\\.lproj/").c_str(),
321 (string("^") + resources + ".*\\.lproj/locversion.plist$").c_str()
322 );
323 }
324
325 const Requirements *BundleDiskRep::defaultRequirements(const Architecture *arch, const SigningContext &ctx)
326 {
327 return mExecRep->defaultRequirements(arch, ctx);
328 }
329
330 size_t BundleDiskRep::pageSize(const SigningContext &ctx)
331 {
332 return mExecRep->pageSize(ctx);
333 }
334
335
336 //
337 // Writers
338 //
339 DiskRep::Writer *BundleDiskRep::writer()
340 {
341 return new Writer(this);
342 }
343
344 BundleDiskRep::Writer::Writer(BundleDiskRep *r)
345 : rep(r), mMadeMetaDirectory(false)
346 {
347 execWriter = rep->mExecRep->writer();
348 }
349
350
351 //
352 // Write a component.
353 // Note that this isn't concerned with Mach-O writing; this is handled at
354 // a much higher level. If we're called, we write to a file in the Bundle's meta directory.
355 //
356 void BundleDiskRep::Writer::component(CodeDirectory::SpecialSlot slot, CFDataRef data)
357 {
358 switch (slot) {
359 default:
360 if (!execWriter->attribute(writerLastResort)) // willing to take the data...
361 return execWriter->component(slot, data); // ... so hand it through
362 // execWriter doesn't want the data; store it as a resource file (below)
363 case cdResourceDirSlot:
364 // the resource directory always goes into a bundle file
365 if (const char *name = CodeDirectory::canonicalSlotName(slot)) {
366 rep->createMeta();
367 string path = rep->metaPath(name);
368 AutoFileDesc fd(path, O_WRONLY | O_CREAT | O_TRUNC, 0644);
369 fd.writeAll(CFDataGetBytePtr(data), CFDataGetLength(data));
370 } else
371 MacOSError::throwMe(errSecCSBadBundleFormat);
372 }
373 }
374
375
376 //
377 // Remove all signature data
378 //
379 void BundleDiskRep::Writer::remove()
380 {
381 // remove signature from the executable
382 execWriter->remove();
383
384 // remove signature files from bundle
385 for (CodeDirectory::SpecialSlot slot = 0; slot < cdSlotCount; slot++)
386 remove(slot);
387 remove(cdSignatureSlot);
388 }
389
390 void BundleDiskRep::Writer::remove(CodeDirectory::SpecialSlot slot)
391 {
392 if (const char *name = CodeDirectory::canonicalSlotName(slot))
393 if (::unlink(rep->metaPath(name).c_str()))
394 switch (errno) {
395 case ENOENT: // not found - that's okay
396 break;
397 default:
398 UnixError::throwMe();
399 }
400 }
401
402
403 void BundleDiskRep::Writer::flush()
404 {
405 execWriter->flush();
406 }
407
408
409 } // end namespace CodeSigning
410 } // end namespace Security