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