]> git.saurik.com Git - apple/security.git/blob - libsecurity_codesigning/lib/resources.cpp
0ac68ecb7be5abeca10f313a359e3eb8493e7e62
[apple/security.git] / libsecurity_codesigning / lib / resources.cpp
1 /*
2 * Copyright (c) 2006-2010 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
24 //
25 // resource directory construction and verification
26 //
27 #include "resources.h"
28 #include "csutilities.h"
29 #include <security_utilities/unix++.h>
30 #include <security_utilities/debugging.h>
31 #include <Security/CSCommon.h>
32 #include <security_utilities/unix++.h>
33 #include <security_utilities/cfmunge.h>
34
35 // These are pretty nasty, but are a quick safe fix
36 // to pass information down to the gatekeeper collection tool
37 extern "C" {
38 int GKBIS_DS_Store_Present;
39 int GKBIS_Dot_underbar_Present;
40 int GKBIS_Num_localizations;
41 int GKBIS_Num_files;
42 int GKBIS_Num_dirs;
43 int GKBIS_Num_symlinks;
44 }
45
46 namespace Security {
47 namespace CodeSigning {
48
49
50 //
51 // Construction and maintainance
52 //
53 ResourceBuilder::ResourceBuilder(const std::string &root, CFDictionaryRef rulesDict, CodeDirectory::HashAlgorithm hashType)
54 : mRoot(root), mHashType(hashType)
55 {
56 assert(!mRoot.empty());
57 if (mRoot.substr(mRoot.length()-2, 2) == "/.") // produced by versioned bundle implicit "Current" case
58 mRoot = mRoot.substr(0, mRoot.length()-2); // ... so take it off for this
59 const char * paths[2] = { mRoot.c_str(), NULL };
60 mFTS = fts_open((char * const *)paths, FTS_PHYSICAL | FTS_COMFOLLOW | FTS_NOCHDIR, NULL);
61 if (!mFTS)
62 UnixError::throwMe();
63 mRawRules = rulesDict;
64 CFDictionary rules(rulesDict, errSecCSResourceRulesInvalid);
65 rules.apply(this, &ResourceBuilder::addRule);
66 }
67
68 ResourceBuilder::~ResourceBuilder()
69 {
70 for (Rules::iterator it = mRules.begin(); it != mRules.end(); ++it)
71 delete *it;
72 UnixPlusPlus::checkError(fts_close(mFTS));
73 }
74
75
76 //
77 // Parse and add one matching rule
78 //
79 void ResourceBuilder::addRule(CFTypeRef key, CFTypeRef value)
80 {
81 string pattern = cfString(key, errSecCSResourceRulesInvalid);
82 unsigned weight = 1;
83 uint32_t flags = 0;
84 if (CFGetTypeID(value) == CFBooleanGetTypeID()) {
85 if (value == kCFBooleanFalse)
86 flags |= omitted;
87 } else {
88 CFDictionary rule(value, errSecCSResourceRulesInvalid);
89 if (CFNumberRef weightRef = rule.get<CFNumberRef>("weight"))
90 weight = cfNumber<unsigned int>(weightRef);
91 if (CFBooleanRef omitRef = rule.get<CFBooleanRef>("omit"))
92 if (omitRef == kCFBooleanTrue)
93 flags |= omitted;
94 if (CFBooleanRef optRef = rule.get<CFBooleanRef>("optional"))
95 if (optRef == kCFBooleanTrue)
96 flags |= optional;
97 if (CFBooleanRef nestRef = rule.get<CFBooleanRef>("nested"))
98 if (nestRef == kCFBooleanTrue)
99 flags |= nested;
100 if (CFBooleanRef topRef = rule.get<CFBooleanRef>("top"))
101 if (topRef == kCFBooleanTrue)
102 flags |= top;
103 }
104 addRule(new Rule(pattern, weight, flags));
105 }
106
107 static bool findStringEndingNoCase(const char *path, const char * end)
108 {
109 size_t len_path = strlen(path);
110 size_t len_end = strlen(end);
111
112 if (len_path >= len_end) {
113 return strcasecmp(path + (len_path - len_end), end) == 0;
114 } else
115 return false;
116 }
117
118 //
119 // Locate the next non-ignored file, look up its rule, and return it.
120 // Returns NULL when we're out of files.
121 //
122 void ResourceBuilder::scan(Scanner next)
123 {
124 bool first = true;
125
126 while (FTSENT *ent = fts_read(mFTS)) {
127 const char *relpath = ent->fts_path + mRoot.size() + 1; // skip prefix + "/"
128 switch (ent->fts_info) {
129 case FTS_F:
130 secdebug("rdirenum", "file %s", ent->fts_path);
131 GKBIS_Num_files++;
132
133 // These are checks for the gatekeeper collection
134 static const char underbar[] = "._";
135 if (strncasecmp(ent->fts_name, underbar, strlen(underbar)) == 0)
136 GKBIS_Dot_underbar_Present++;
137
138 static const char ds_store[] = ".DS_Store";
139 if (strcasecmp(ent->fts_name, ds_store) == 0)
140 GKBIS_DS_Store_Present++;
141
142 if (Rule *rule = findRule(relpath))
143 if (!(rule->flags & (omitted | exclusion)))
144 next(ent, rule->flags, relpath, rule);
145 break;
146 case FTS_SL:
147 // symlinks cannot ever be nested code, so quietly convert to resource file
148 secdebug("rdirenum", "symlink %s", ent->fts_path);
149 GKBIS_Num_symlinks++;
150
151 if (Rule *rule = findRule(relpath))
152 if (!(rule->flags & (omitted | exclusion)))
153 next(ent, rule->flags & ~nested, relpath, rule);
154 break;
155 case FTS_D:
156 secdebug("rdirenum", "entering %s", ent->fts_path);
157 GKBIS_Num_dirs++;
158
159 if (!first) { // skip root directory (relpath invalid)
160 if (Rule *rule = findRule(relpath)) {
161 if (rule->flags & nested) {
162 if (strchr(ent->fts_name, '.')) { // nested, has extension -> treat as nested bundle
163 next(ent, rule->flags, relpath, rule);
164 fts_set(mFTS, ent, FTS_SKIP);
165 }
166 } else if (rule->flags & exclusion) { // exclude the whole directory
167 fts_set(mFTS, ent, FTS_SKIP);
168 }
169 // else treat as normal directory and descend into it
170 }
171 }
172 // Report the number of localizations
173 if (findStringEndingNoCase(ent->fts_name, ".lproj"))
174 GKBIS_Num_localizations++;
175 first = false;
176
177 break;
178 case FTS_DP:
179 secdebug("rdirenum", "leaving %s", ent->fts_path);
180 break;
181 default:
182 secdebug("rdirenum", "type %d (errno %d): %s",
183 ent->fts_info, ent->fts_errno, ent->fts_path);
184 break;
185 }
186 }
187 }
188
189
190 //
191 // Check a single for for inclusion in the resource envelope
192 //
193 bool ResourceBuilder::includes(string path) const
194 {
195 if (Rule *rule = findRule(path))
196 return !(rule->flags & (omitted | exclusion));
197 else
198 return false;
199 }
200
201
202 //
203 // Find the best-matching resource rule for an alleged resource file.
204 // Returns NULL if no rule matches, or an exclusion rule applies.
205 //
206 ResourceBuilder::Rule *ResourceBuilder::findRule(string path) const
207 {
208 Rule *bestRule = NULL;
209 secdebug("rscan", "test %s", path.c_str());
210 for (Rules::const_iterator it = mRules.begin(); it != mRules.end(); ++it) {
211 Rule *rule = *it;
212 secdebug("rscan", "try %s", rule->source.c_str());
213 if (rule->match(path.c_str())) {
214 secdebug("rscan", "match");
215 if (rule->flags & exclusion) {
216 secdebug("rscan", "excluded");
217 return rule;
218 }
219 if (!bestRule || rule->weight > bestRule->weight)
220 bestRule = rule;
221 }
222 }
223 secdebug("rscan", "choosing %s (%d,0x%x)",
224 bestRule ? bestRule->source.c_str() : "NOTHING",
225 bestRule ? bestRule->weight : 0,
226 bestRule ? bestRule->flags : 0);
227 return bestRule;
228 }
229
230
231 //
232 // Hash a file and return a CFDataRef with the hash
233 //
234 CFDataRef ResourceBuilder::hashFile(const char *path) const
235 {
236 UnixPlusPlus::AutoFileDesc fd(path);
237 fd.fcntl(F_NOCACHE, true); // turn off page caching (one-pass)
238 MakeHash<ResourceBuilder> hasher(this);
239 hashFileData(fd, hasher.get());
240 Hashing::Byte digest[hasher->digestLength()];
241 hasher->finish(digest);
242 return CFDataCreate(NULL, digest, sizeof(digest));
243 }
244
245
246 //
247 // Regex matching objects
248 //
249 ResourceBuilder::Rule::Rule(const std::string &pattern, unsigned w, uint32_t f)
250 : weight(w), flags(f), source(pattern)
251 {
252 if (::regcomp(this, pattern.c_str(), REG_EXTENDED | REG_NOSUB)) //@@@ REG_ICASE?
253 MacOSError::throwMe(errSecCSResourceRulesInvalid);
254 secdebug("csresource", "%p rule %s added (weight %d, flags 0x%x)",
255 this, pattern.c_str(), w, f);
256 }
257
258 ResourceBuilder::Rule::~Rule()
259 {
260 ::regfree(this);
261 }
262
263 bool ResourceBuilder::Rule::match(const char *s) const
264 {
265 switch (::regexec(this, s, 0, NULL, 0)) {
266 case 0:
267 return true;
268 case REG_NOMATCH:
269 return false;
270 default:
271 MacOSError::throwMe(errSecCSResourceRulesInvalid);
272 }
273 }
274
275
276 std::string ResourceBuilder::escapeRE(const std::string &s)
277 {
278 string r;
279 for (string::const_iterator it = s.begin(); it != s.end(); ++it) {
280 char c = *it;
281 if (strchr("\\[]{}().+*", c))
282 r.push_back('\\');
283 r.push_back(c);
284 }
285 return r;
286 }
287
288
289 //
290 // Resource Seals
291 //
292 ResourceSeal::ResourceSeal(CFTypeRef it)
293 : mDict(NULL), mHash(NULL), mRequirement(NULL), mLink(NULL), mFlags(0)
294 {
295 if (it == NULL)
296 MacOSError::throwMe(errSecCSResourcesInvalid);
297 if (CFGetTypeID(it) == CFDataGetTypeID()) {
298 mHash = CFDataRef(it);
299 } else {
300 int optional = 0;
301 mDict = CFDictionaryRef(it);
302 bool err;
303 if (CFDictionaryGetValue(mDict, CFSTR("requirement")))
304 err = !cfscan(mDict, "{requirement=%SO,?optional=%B}", &mRequirement, &optional);
305 else if (CFDictionaryGetValue(mDict, CFSTR("symlink")))
306 err = !cfscan(mDict, "{symlink=%SO,?optional=%B}", &mLink, &optional);
307 else
308 err = !cfscan(mDict, "{hash=%XO,?optional=%B}", &mHash, &optional);
309 if (err)
310 MacOSError::throwMe(errSecCSResourcesInvalid);
311 if (optional)
312 mFlags |= ResourceBuilder::optional;
313 if (mRequirement)
314 mFlags |= ResourceBuilder::nested;
315 }
316 }
317
318
319 } // end namespace CodeSigning
320 } // end namespace Security