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