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