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