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