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