2 * Copyright (c) 2006-2010 Apple Inc. All Rights Reserved.
4 * @APPLE_LICENSE_HEADER_START@
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
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.
21 * @APPLE_LICENSE_HEADER_END@
25 // resource directory construction and verification
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>
35 // These are pretty nasty, but are a quick safe fix
36 // to pass information down to the gatekeeper collection tool
38 int GKBIS_DS_Store_Present
;
39 int GKBIS_Dot_underbar_Present
;
40 int GKBIS_Num_localizations
;
43 int GKBIS_Num_symlinks
;
47 namespace CodeSigning
{
51 // Construction and maintainance
53 ResourceBuilder::ResourceBuilder(const std::string
&root
, CFDictionaryRef rulesDict
, CodeDirectory::HashAlgorithm hashType
)
54 : mRoot(root
), mHashType(hashType
)
56 assert(!mRoot
.empty());
57 if (mRoot
.substr(mRoot
.length()-2, 2) == "/.") // produced by versioned bundle implicit "Current" case
58 mRoot
= mRoot
.substr(0, mRoot
.length()-2); // ... so take it off for this
59 const char * paths
[2] = { mRoot
.c_str(), NULL
};
60 mFTS
= fts_open((char * const *)paths
, FTS_PHYSICAL
| FTS_COMFOLLOW
| FTS_NOCHDIR
, NULL
);
63 mRawRules
= rulesDict
;
64 CFDictionary
rules(rulesDict
, errSecCSResourceRulesInvalid
);
65 rules
.apply(this, &ResourceBuilder::addRule
);
68 ResourceBuilder::~ResourceBuilder()
70 for (Rules::iterator it
= mRules
.begin(); it
!= mRules
.end(); ++it
)
72 UnixPlusPlus::checkError(fts_close(mFTS
));
77 // Parse and add one matching rule
79 void ResourceBuilder::addRule(CFTypeRef key
, CFTypeRef value
)
81 string pattern
= cfString(key
, errSecCSResourceRulesInvalid
);
84 if (CFGetTypeID(value
) == CFBooleanGetTypeID()) {
85 if (value
== kCFBooleanFalse
)
88 CFDictionary
rule(value
, errSecCSResourceRulesInvalid
);
89 if (CFNumberRef weightRef
= rule
.get
<CFNumberRef
>("weight"))
90 weight
= cfNumber
<unsigned int>(weightRef
);
91 if (CFBooleanRef omitRef
= rule
.get
<CFBooleanRef
>("omit"))
92 if (omitRef
== kCFBooleanTrue
)
94 if (CFBooleanRef optRef
= rule
.get
<CFBooleanRef
>("optional"))
95 if (optRef
== kCFBooleanTrue
)
97 if (CFBooleanRef nestRef
= rule
.get
<CFBooleanRef
>("nested"))
98 if (nestRef
== kCFBooleanTrue
)
100 if (CFBooleanRef topRef
= rule
.get
<CFBooleanRef
>("top"))
101 if (topRef
== kCFBooleanTrue
)
104 addRule(new Rule(pattern
, weight
, flags
));
107 static bool findStringEndingNoCase(const char *path
, const char * end
)
109 size_t len_path
= strlen(path
);
110 size_t len_end
= strlen(end
);
112 if (len_path
>= len_end
) {
113 return strcasecmp(path
+ (len_path
- len_end
), end
) == 0;
119 // Locate the next non-ignored file, look up its rule, and return it.
120 // Returns NULL when we're out of files.
122 void ResourceBuilder::scan(Scanner next
)
126 while (FTSENT
*ent
= fts_read(mFTS
)) {
127 const char *relpath
= ent
->fts_path
+ mRoot
.size() + 1; // skip prefix + "/"
128 switch (ent
->fts_info
) {
130 secdebug("rdirenum", "file %s", ent
->fts_path
);
133 // These are checks for the gatekeeper collection
134 static const char underbar
[] = "._";
135 if (strncasecmp(ent
->fts_name
, underbar
, strlen(underbar
)) == 0)
136 GKBIS_Dot_underbar_Present
++;
138 static const char ds_store
[] = ".DS_Store";
139 if (strcasecmp(ent
->fts_name
, ds_store
) == 0)
140 GKBIS_DS_Store_Present
++;
142 if (Rule
*rule
= findRule(relpath
))
143 if (!(rule
->flags
& (omitted
| exclusion
)))
144 next(ent
, rule
->flags
, relpath
, rule
);
147 // symlinks cannot ever be nested code, so quietly convert to resource file
148 secdebug("rdirenum", "symlink %s", ent
->fts_path
);
149 GKBIS_Num_symlinks
++;
151 if (Rule
*rule
= findRule(relpath
))
152 if (!(rule
->flags
& (omitted
| exclusion
)))
153 next(ent
, rule
->flags
& ~nested
, relpath
, rule
);
156 secdebug("rdirenum", "entering %s", ent
->fts_path
);
159 if (!first
) { // skip root directory (relpath invalid)
160 if (Rule
*rule
= findRule(relpath
)) {
161 if (rule
->flags
& nested
) {
162 if (strchr(ent
->fts_name
, '.')) { // nested, has extension -> treat as nested bundle
163 next(ent
, rule
->flags
, relpath
, rule
);
164 fts_set(mFTS
, ent
, FTS_SKIP
);
166 } else if (rule
->flags
& exclusion
) { // exclude the whole directory
167 fts_set(mFTS
, ent
, FTS_SKIP
);
169 // else treat as normal directory and descend into it
172 // Report the number of localizations
173 if (findStringEndingNoCase(ent
->fts_name
, ".lproj"))
174 GKBIS_Num_localizations
++;
179 secdebug("rdirenum", "leaving %s", ent
->fts_path
);
182 secdebug("rdirenum", "type %d (errno %d): %s",
183 ent
->fts_info
, ent
->fts_errno
, ent
->fts_path
);
191 // Check a single for for inclusion in the resource envelope
193 bool ResourceBuilder::includes(string path
) const
195 if (Rule
*rule
= findRule(path
))
196 return !(rule
->flags
& (omitted
| exclusion
));
203 // Find the best-matching resource rule for an alleged resource file.
204 // Returns NULL if no rule matches, or an exclusion rule applies.
206 ResourceBuilder::Rule
*ResourceBuilder::findRule(string path
) const
208 Rule
*bestRule
= NULL
;
209 secdebug("rscan", "test %s", path
.c_str());
210 for (Rules::const_iterator it
= mRules
.begin(); it
!= mRules
.end(); ++it
) {
212 secdebug("rscan", "try %s", rule
->source
.c_str());
213 if (rule
->match(path
.c_str())) {
214 secdebug("rscan", "match");
215 if (rule
->flags
& exclusion
) {
216 secdebug("rscan", "excluded");
219 if (!bestRule
|| rule
->weight
> bestRule
->weight
)
223 secdebug("rscan", "choosing %s (%d,0x%x)",
224 bestRule
? bestRule
->source
.c_str() : "NOTHING",
225 bestRule
? bestRule
->weight
: 0,
226 bestRule
? bestRule
->flags
: 0);
232 // Hash a file and return a CFDataRef with the hash
234 CFDataRef
ResourceBuilder::hashFile(const char *path
) const
236 UnixPlusPlus::AutoFileDesc
fd(path
);
237 fd
.fcntl(F_NOCACHE
, true); // turn off page caching (one-pass)
238 MakeHash
<ResourceBuilder
> hasher(this);
239 hashFileData(fd
, hasher
.get());
240 Hashing::Byte digest
[hasher
->digestLength()];
241 hasher
->finish(digest
);
242 return CFDataCreate(NULL
, digest
, sizeof(digest
));
247 // Regex matching objects
249 ResourceBuilder::Rule::Rule(const std::string
&pattern
, unsigned w
, uint32_t f
)
250 : weight(w
), flags(f
), source(pattern
)
252 if (::regcomp(this, pattern
.c_str(), REG_EXTENDED
| REG_NOSUB
)) //@@@ REG_ICASE?
253 MacOSError::throwMe(errSecCSResourceRulesInvalid
);
254 secdebug("csresource", "%p rule %s added (weight %d, flags 0x%x)",
255 this, pattern
.c_str(), w
, f
);
258 ResourceBuilder::Rule::~Rule()
263 bool ResourceBuilder::Rule::match(const char *s
) const
265 switch (::regexec(this, s
, 0, NULL
, 0)) {
271 MacOSError::throwMe(errSecCSResourceRulesInvalid
);
276 std::string
ResourceBuilder::escapeRE(const std::string
&s
)
279 for (string::const_iterator it
= s
.begin(); it
!= s
.end(); ++it
) {
281 if (strchr("\\[]{}().+*", c
))
292 ResourceSeal::ResourceSeal(CFTypeRef it
)
293 : mDict(NULL
), mHash(NULL
), mRequirement(NULL
), mLink(NULL
), mFlags(0)
296 MacOSError::throwMe(errSecCSResourcesInvalid
);
297 if (CFGetTypeID(it
) == CFDataGetTypeID()) {
298 mHash
= CFDataRef(it
);
301 mDict
= CFDictionaryRef(it
);
303 if (CFDictionaryGetValue(mDict
, CFSTR("requirement")))
304 err
= !cfscan(mDict
, "{requirement=%SO,?optional=%B}", &mRequirement
, &optional
);
305 else if (CFDictionaryGetValue(mDict
, CFSTR("symlink")))
306 err
= !cfscan(mDict
, "{symlink=%SO,?optional=%B}", &mLink
, &optional
);
308 err
= !cfscan(mDict
, "{hash=%XO,?optional=%B}", &mHash
, &optional
);
310 MacOSError::throwMe(errSecCSResourcesInvalid
);
312 mFlags
|= ResourceBuilder::optional
;
314 mFlags
|= ResourceBuilder::nested
;
319 } // end namespace CodeSigning
320 } // end namespace Security