]> git.saurik.com Git - apple/libsecurity_codesigning.git/blob - lib/bundlediskrep.cpp
d8785d1335811c693488f51f968354a97b79db9e
[apple/libsecurity_codesigning.git] / lib / bundlediskrep.cpp
1 /*
2 * Copyright (c) 2006-2007 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 <CoreFoundation/CFURLAccess.h>
25 #include <CoreFoundation/CFBundlePriv.h>
26 #include <security_utilities/cfmunge.h>
27 #include <copyfile.h>
28
29
30 namespace Security {
31 namespace CodeSigning {
32
33 using namespace UnixPlusPlus;
34
35
36 //
37 // We make a CFBundleRef immediately, but everything else is lazy
38 //
39 BundleDiskRep::BundleDiskRep(const char *path, const Context *ctx)
40 : mBundle(_CFBundleCreateIfMightBeBundle(NULL, CFTempURL(path)))
41 {
42 if (!mBundle)
43 MacOSError::throwMe(errSecCSBadObjectFormat);
44 mExecRep = DiskRep::bestFileGuess(this->mainExecutablePath(), ctx);
45 CODESIGN_DISKREP_CREATE_BUNDLE_PATH(this, (char*)path, (void*)ctx, mExecRep);
46 }
47
48 BundleDiskRep::BundleDiskRep(CFBundleRef ref, const Context *ctx)
49 {
50 mBundle = ref; // retains
51 mExecRep = DiskRep::bestFileGuess(this->mainExecutablePath(), ctx);
52 CODESIGN_DISKREP_CREATE_BUNDLE_REF(this, ref, (void*)ctx, mExecRep);
53 }
54
55
56 //
57 // Create a path to a bundle signing resource, by name.
58 // If the BUNDLEDISKREP_DIRECTORY directory exists in the bundle's support directory, files
59 // will be read and written there. Otherwise, they go directly into the support directory.
60 //
61 string BundleDiskRep::metaPath(const char *name)
62 {
63 if (mMetaPath.empty()) {
64 string support = cfString(CFBundleCopySupportFilesDirectoryURL(mBundle), true);
65 mMetaPath = support + "/" BUNDLEDISKREP_DIRECTORY;
66 if (::access(mMetaPath.c_str(), F_OK) == 0) {
67 mMetaExists = true;
68 } else {
69 mMetaPath = support;
70 mMetaExists = false;
71 }
72 }
73 return mMetaPath + "/" + name;
74 }
75
76
77 //
78 // Try to create the meta-file directory in our bundle.
79 // Does nothing if the directory already exists.
80 // Throws if an error occurs.
81 //
82 void BundleDiskRep::createMeta()
83 {
84 string meta = metaPath(BUNDLEDISKREP_DIRECTORY);
85 if (!mMetaExists) {
86 if (::mkdir(meta.c_str(), 0755) == 0) {
87 copyfile(cfString(canonicalPath(), true).c_str(), meta.c_str(), NULL, COPYFILE_SECURITY);
88 mMetaPath = meta;
89 mMetaExists = true;
90 } else if (errno != EEXIST)
91 UnixError::throwMe();
92 }
93 }
94
95
96 //
97 // Load and return a component, by slot number.
98 // Info.plist components come from the bundle, always (we don't look
99 // for Mach-O embedded versions).
100 // Everything else comes from the embedded blobs of a Mach-O image, or from
101 // files located in the Contents directory of the bundle.
102 //
103 CFDataRef BundleDiskRep::component(CodeDirectory::SpecialSlot slot)
104 {
105 switch (slot) {
106 // the Info.plist comes from the magic CFBundle-indicated place and ONLY from there
107 case cdInfoSlot:
108 if (CFRef<CFURLRef> info = _CFBundleCopyInfoPlistURL(mBundle))
109 return cfLoadFile(info);
110 else
111 return NULL;
112 // by default, we take components from the executable image or files
113 default:
114 if (CFDataRef data = mExecRep->component(slot))
115 return data;
116 // falling through
117 // but the following always come from files
118 case cdResourceDirSlot:
119 if (const char *name = CodeDirectory::canonicalSlotName(slot))
120 return metaData(name);
121 else
122 return NULL;
123 }
124 }
125
126
127 //
128 // The binary identifier is taken directly from the main executable.
129 //
130 CFDataRef BundleDiskRep::identification()
131 {
132 return mExecRep->identification();
133 }
134
135
136 //
137 // Various aspects of our DiskRep personality.
138 //
139 CFURLRef BundleDiskRep::canonicalPath()
140 {
141 return CFBundleCopyBundleURL(mBundle);
142 }
143
144 string BundleDiskRep::recommendedIdentifier()
145 {
146 if (CFStringRef identifier = CFBundleGetIdentifier(mBundle))
147 return cfString(identifier);
148 if (CFDictionaryRef infoDict = CFBundleGetInfoDictionary(mBundle))
149 if (CFStringRef identifier = CFStringRef(CFDictionaryGetValue(infoDict, kCFBundleNameKey)))
150 return cfString(identifier);
151
152 // fall back to using the $(basename) of the canonical path. Drop any .app suffix
153 string path = cfString(this->canonicalPath(), true);
154 if (path.substr(path.size() - 4) == ".app")
155 path = path.substr(0, path.size() - 4);
156 string::size_type p = path.rfind('/');
157 if (p == string::npos)
158 return path;
159 else
160 return path.substr(p+1);
161 }
162
163 string BundleDiskRep::mainExecutablePath()
164 {
165 if (CFURLRef exec = CFBundleCopyExecutableURL(mBundle))
166 return cfString(exec, true);
167 else
168 MacOSError::throwMe(errSecCSBadObjectFormat);
169 }
170
171 string BundleDiskRep::resourcesRootPath()
172 {
173 return cfString(CFBundleCopySupportFilesDirectoryURL(mBundle), true);
174 }
175
176 CFDictionaryRef BundleDiskRep::defaultResourceRules()
177 {
178 return cfmake<CFDictionaryRef>("{rules={"
179 "'^version.plist$' = #T"
180 "'^Resources/' = #T"
181 "'^Resources/.*\\.lproj/' = {optional=#T, weight=1000}"
182 "'^Resources/.*\\.lproj/locversion.plist$' = {omit=#T, weight=1100}"
183 "}}");
184 }
185
186 void BundleDiskRep::adjustResources(ResourceBuilder &builder)
187 {
188 // exclude entire contents of meta directory
189 builder.addExclusion("^" BUNDLEDISKREP_DIRECTORY "/");
190
191 // exclude the main executable file
192 string resources = resourcesRootPath();
193 string executable = mainExecutablePath();
194 if (!executable.compare(0, resources.length(), resources, 0, resources.length())) // is prefix
195 builder.addExclusion(string("^")
196 + ResourceBuilder::escapeRE(executable.substr(resources.length() + 1)) + "$");
197 }
198
199
200 const Requirements *BundleDiskRep::defaultRequirements(const Architecture *arch)
201 {
202 return mExecRep->defaultRequirements(arch);
203 }
204
205
206 Universal *BundleDiskRep::mainExecutableImage()
207 {
208 return mExecRep->mainExecutableImage();
209 }
210
211 size_t BundleDiskRep::pageSize()
212 {
213 return mExecRep->pageSize();
214 }
215
216 size_t BundleDiskRep::signingBase()
217 {
218 return mExecRep->signingBase();
219 }
220
221 size_t BundleDiskRep::signingLimit()
222 {
223 return mExecRep->signingLimit();
224 }
225
226 string BundleDiskRep::format()
227 {
228 return string("bundle with ") + mExecRep->format();
229 }
230
231 CFArrayRef BundleDiskRep::modifiedFiles()
232 {
233 CFMutableArrayRef files = CFArrayCreateMutableCopy(NULL, 0, mExecRep->modifiedFiles());
234 checkModifiedFile(files, cdCodeDirectorySlot);
235 checkModifiedFile(files, cdSignatureSlot);
236 checkModifiedFile(files, cdResourceDirSlot);
237 checkModifiedFile(files, cdEntitlementSlot);
238 return files;
239 }
240
241 void BundleDiskRep::checkModifiedFile(CFMutableArrayRef files, CodeDirectory::SpecialSlot slot)
242 {
243 if (CFDataRef data = mExecRep->component(slot)) // provided by executable file
244 CFRelease(data);
245 else if (const char *resourceName = CodeDirectory::canonicalSlotName(slot)) {
246 string file = metaPath(resourceName);
247 if (::access(file.c_str(), F_OK) == 0)
248 CFArrayAppendValue(files, CFTempURL(file));
249 }
250 }
251
252 FileDesc &BundleDiskRep::fd()
253 {
254 return mExecRep->fd();
255 }
256
257 void BundleDiskRep::flush()
258 {
259 mExecRep->flush();
260 }
261
262
263 //
264 // Writers
265 //
266 DiskRep::Writer *BundleDiskRep::writer()
267 {
268 return new Writer(this);
269 }
270
271 BundleDiskRep::Writer::Writer(BundleDiskRep *r)
272 : rep(r), mMadeMetaDirectory(false)
273 {
274 execWriter = rep->mExecRep->writer();
275 }
276
277
278 //
279 // Write a component.
280 // Note that this isn't concerned with Mach-O writing; this is handled at
281 // a much higher level. If we're called, we write to a file in the Bundle's meta directory.
282 //
283 void BundleDiskRep::Writer::component(CodeDirectory::SpecialSlot slot, CFDataRef data)
284 {
285 switch (slot) {
286 default:
287 if (!execWriter->attribute(writerLastResort)) // willing to take the data...
288 return execWriter->component(slot, data); // ... so hand it through
289 // execWriter doesn't want the data; store it as a resource file (below)
290 case cdResourceDirSlot:
291 // the resource directory always goes into a bundle file
292 if (const char *name = CodeDirectory::canonicalSlotName(slot)) {
293 rep->createMeta();
294 string path = rep->metaPath(name);
295 AutoFileDesc fd(path, O_WRONLY | O_CREAT | O_TRUNC, 0644);
296 fd.writeAll(CFDataGetBytePtr(data), CFDataGetLength(data));
297 if (rep->mMetaExists) {
298 // leave a symlink in the support directory for pre-10.5.3 compatibility (but ignore errors)
299 string legacy = cfString(CFBundleCopySupportFilesDirectoryURL(rep->mBundle), true) + "/" + name;
300 # if FORCE_REPLACE_SYMLINK /* replace any existing file with legacy symlink */
301 ::unlink(legacy.c_str()); // force-replace
302 #endif
303 ::symlink((string(BUNDLEDISKREP_DIRECTORY "/") + name).c_str(), legacy.c_str());
304 }
305 } else
306 MacOSError::throwMe(errSecCSBadObjectFormat);
307 }
308 }
309
310
311 //
312 // Remove all signature data
313 //
314 void BundleDiskRep::Writer::remove()
315 {
316 // remove signature from the executable
317 execWriter->remove();
318
319 // remove signature files from bundle
320 for (CodeDirectory::SpecialSlot slot = 0; slot < cdSlotCount; slot++)
321 remove(slot);
322 remove(cdSignatureSlot);
323 }
324
325 void BundleDiskRep::Writer::remove(CodeDirectory::SpecialSlot slot)
326 {
327 if (const char *name = CodeDirectory::canonicalSlotName(slot))
328 if (::unlink(rep->metaPath(name).c_str()))
329 switch (errno) {
330 case ENOENT: // not found - that's okay
331 break;
332 default:
333 UnixError::throwMe();
334 }
335 }
336
337
338 void BundleDiskRep::Writer::flush()
339 {
340 execWriter->flush();
341 }
342
343
344 } // end namespace CodeSigning
345 } // end namespace Security