]> git.saurik.com Git - apple/security.git/blob - libsecurity_codesigning/lib/resources.cpp
Security-55471.tar.gz
[apple/security.git] / libsecurity_codesigning / lib / resources.cpp
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"
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 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)
43 : mRoot(root), mHashType(hashType)
44 {
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;
53 CFDictionary rules(rulesDict, errSecCSResourceRulesInvalid);
54 rules.apply(this, &ResourceBuilder::addRule);
55 }
56
57 ResourceBuilder::~ResourceBuilder()
58 {
59 for (Rules::iterator it = mRules.begin(); it != mRules.end(); ++it)
60 delete *it;
61 UnixPlusPlus::checkError(fts_close(mFTS));
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;
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;
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 //
101 void ResourceBuilder::scan(Scanner next)
102 {
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
133 }
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;
144 }
145 }
146 }
147
148
149 //
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.
164 //
165 ResourceBuilder::Rule *ResourceBuilder::findRule(string path) const
166 {
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;
180 }
181 }
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;
187 }
188
189
190 //
191 // Hash a file and return a CFDataRef with the hash
192 //
193 CFDataRef ResourceBuilder::hashFile(const char *path) const
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)
209 : weight(w), flags(f), source(pattern)
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)
252 : mDict(NULL), mHash(NULL), mRequirement(NULL), mLink(NULL), mFlags(0)
253 {
254 if (it == NULL)
255 MacOSError::throwMe(errSecCSResourcesInvalid);
256 if (CFGetTypeID(it) == CFDataGetTypeID()) {
257 mHash = CFDataRef(it);
258 } else {
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)
269 MacOSError::throwMe(errSecCSResourcesInvalid);
270 if (optional)
271 mFlags |= ResourceBuilder::optional;
272 if (mRequirement)
273 mFlags |= ResourceBuilder::nested;
274 }
275 }
276
277
278 } // end namespace CodeSigning
279 } // end namespace Security