#include "resources.h"
#include "csutilities.h"
#include <security_utilities/unix++.h>
#include <security_utilities/debugging.h>
#include <Security/CSCommon.h>
#include <security_utilities/unix++.h>
#include <security_utilities/cfmunge.h>
extern "C" {
int GKBIS_DS_Store_Present;
int GKBIS_Dot_underbar_Present;
int GKBIS_Num_localizations;
int GKBIS_Num_files;
int GKBIS_Num_dirs;
int GKBIS_Num_symlinks;
}
namespace Security {
namespace CodeSigning {
static string removeTrailingSlash(string path)
{
if (path.substr(path.length()-2, 2) == "/.")
return path.substr(0, path.length()-2);
else if (path.substr(path.length()-1, 1) == "/")
return path.substr(0, path.length()-1);
else
return path;
}
ResourceBuilder::ResourceBuilder(const std::string &root, const std::string &relBase,
CFDictionaryRef rulesDict, bool strict, const MacOSErrorSet& toleratedErrors)
: mCheckUnreadable(strict && toleratedErrors.find(errSecCSSignatureNotVerifiable) == toleratedErrors.end()),
mCheckUnknownType(strict && toleratedErrors.find(errSecCSResourceNotSupported) == toleratedErrors.end())
{
assert(!root.empty());
char realroot[PATH_MAX];
if (realpath(root.c_str(), realroot) == NULL)
UnixError::throwMe();
mRoot = realroot;
if (realpath(removeTrailingSlash(relBase).c_str(), realroot) == NULL)
UnixError::throwMe();
mRelBase = realroot;
if (mRoot != mRelBase && mRelBase != mRoot + "/Contents")
MacOSError::throwMe(errSecCSBadBundleFormat);
const char * paths[2] = { mRoot.c_str(), NULL };
mFTS = fts_open((char * const *)paths, FTS_PHYSICAL | FTS_COMFOLLOW | FTS_NOCHDIR, NULL);
if (!mFTS)
UnixError::throwMe();
mRawRules = rulesDict;
CFDictionary rules(rulesDict, errSecCSResourceRulesInvalid);
rules.apply(this, &ResourceBuilder::addRule);
}
ResourceBuilder::~ResourceBuilder()
{
for (Rules::iterator it = mRules.begin(); it != mRules.end(); ++it)
delete *it;
fts_close(mFTS); }
void ResourceBuilder::addRule(CFTypeRef key, CFTypeRef value)
{
string pattern = cfString(key, errSecCSResourceRulesInvalid);
unsigned weight = 1;
uint32_t flags = 0;
if (CFGetTypeID(value) == CFBooleanGetTypeID()) {
if (value == kCFBooleanFalse)
flags |= omitted;
} else {
CFDictionary rule(value, errSecCSResourceRulesInvalid);
if (CFNumberRef weightRef = rule.get<CFNumberRef>("weight"))
weight = cfNumber<unsigned int>(weightRef);
if (CFBooleanRef omitRef = rule.get<CFBooleanRef>("omit"))
if (omitRef == kCFBooleanTrue)
flags |= omitted;
if (CFBooleanRef optRef = rule.get<CFBooleanRef>("optional"))
if (optRef == kCFBooleanTrue)
flags |= optional;
if (CFBooleanRef nestRef = rule.get<CFBooleanRef>("nested"))
if (nestRef == kCFBooleanTrue)
flags |= nested;
}
addRule(new Rule(pattern, weight, flags));
}
static bool findStringEndingNoCase(const char *path, const char * end)
{
size_t len_path = strlen(path);
size_t len_end = strlen(end);
if (len_path >= len_end) {
return strcasecmp(path + (len_path - len_end), end) == 0;
} else
return false;
}
void ResourceBuilder::scan(Scanner next)
{
bool first = true;
while (FTSENT *ent = fts_read(mFTS)) {
static const char ds_store[] = ".DS_Store";
const char *relpath = ent->fts_path + mRoot.size() + 1; std::string rp;
if (mRelBase != mRoot) {
assert(mRelBase == mRoot + "/Contents");
rp = "../" + string(relpath);
if (rp.substr(0, 12) == "../Contents/")
rp = rp.substr(12);
relpath = rp.c_str();
}
switch (ent->fts_info) {
case FTS_F:
secinfo("rdirenum", "file %s", ent->fts_path);
GKBIS_Num_files++;
static const char underbar[] = "._";
if (strncasecmp(ent->fts_name, underbar, strlen(underbar)) == 0)
GKBIS_Dot_underbar_Present++;
if (strcasecmp(ent->fts_name, ds_store) == 0)
GKBIS_DS_Store_Present++;
if (Rule *rule = findRule(relpath))
if (!(rule->flags & (omitted | exclusion)))
next(ent, rule->flags, string(relpath), rule);
break;
case FTS_SL:
secinfo("rdirenum", "symlink %s", ent->fts_path);
GKBIS_Num_symlinks++;
if (strcasecmp(ent->fts_name, ds_store) == 0)
MacOSError::throwMe(errSecCSDSStoreSymlink);
if (Rule *rule = findRule(relpath))
if (!(rule->flags & (omitted | exclusion)))
next(ent, rule->flags & ~nested, string(relpath), rule);
break;
case FTS_D:
secinfo("rdirenum", "entering %s", ent->fts_path);
GKBIS_Num_dirs++;
if (!first) { if (Rule *rule = findRule(relpath)) {
if (rule->flags & nested) {
if (strchr(ent->fts_name, '.')) { next(ent, rule->flags, string(relpath), rule);
fts_set(mFTS, ent, FTS_SKIP);
}
} else if (rule->flags & exclusion) { fts_set(mFTS, ent, FTS_SKIP);
}
}
}
if (findStringEndingNoCase(ent->fts_name, ".lproj"))
GKBIS_Num_localizations++;
first = false;
break;
case FTS_DP:
secinfo("rdirenum", "leaving %s", ent->fts_path);
break;
case FTS_DNR:
secinfo("rdirenum", "cannot read directory %s", ent->fts_path);
if (mCheckUnreadable)
MacOSError::throwMe(errSecCSSignatureNotVerifiable);
break;
default:
secinfo("rdirenum", "type %d (errno %d): %s",
ent->fts_info, ent->fts_errno, ent->fts_path);
if (mCheckUnknownType)
MacOSError::throwMe(errSecCSResourceNotSupported);
break;
}
}
}
bool ResourceBuilder::includes(string path) const
{
size_t firstslash = path.find('/');
if (firstslash != string::npos)
if (Rule *rule = findRule(path.substr(0, firstslash)))
if (rule->flags & exclusion)
return rule->flags & softTarget;
if (Rule *rule = findRule(path))
return !(rule->flags & (omitted | exclusion)) || (rule->flags & softTarget);
else
return false;
}
ResourceBuilder::Rule *ResourceBuilder::findRule(string path) const
{
Rule *bestRule = NULL;
secinfo("rscan", "test %s", path.c_str());
for (Rules::const_iterator it = mRules.begin(); it != mRules.end(); ++it) {
Rule *rule = *it;
secinfo("rscan", "try %s", rule->source.c_str());
if (rule->match(path.c_str())) {
secinfo("rscan", "match");
if (rule->flags & exclusion) {
secinfo("rscan", "excluded");
return rule;
}
if (!bestRule || rule->weight > bestRule->weight)
bestRule = rule;
#if TARGET_OS_WATCH
if (bestRule && bestRule->weight == rule->weight && !(bestRule->flags & omitted) && (rule->flags & omitted))
bestRule = rule;
#endif
}
}
secinfo("rscan", "choosing %s (%d,0x%x)",
bestRule ? bestRule->source.c_str() : "NOTHING",
bestRule ? bestRule->weight : 0,
bestRule ? bestRule->flags : 0);
return bestRule;
}
CFDataRef ResourceBuilder::hashFile(const char *path, CodeDirectory::HashAlgorithm type)
{
UnixPlusPlus::AutoFileDesc fd(path);
fd.fcntl(F_NOCACHE, true); RefPointer<DynamicHash> hasher(CodeDirectory::hashFor(type));
hashFileData(fd, hasher.get());
Hashing::Byte digest[hasher->digestLength()];
hasher->finish(digest);
return CFDataCreate(NULL, digest, sizeof(digest));
}
CFMutableDictionaryRef ResourceBuilder::hashFile(const char *path, CodeDirectory::HashAlgorithms types, bool strictCheck)
{
UnixPlusPlus::AutoFileDesc fd(path);
fd.fcntl(F_NOCACHE, true); if (strictCheck)
if (fd.hasExtendedAttribute(XATTR_RESOURCEFORK_NAME) || fd.hasExtendedAttribute(XATTR_FINDERINFO_NAME))
MacOSError::throwMe(errSecCSInvalidAssociatedFileData);
CFRef<CFMutableDictionaryRef> result = makeCFMutableDictionary();
CFMutableDictionaryRef resultRef = result;
CodeDirectory::multipleHashFileData(fd, 0, types, ^(CodeDirectory::HashAlgorithm type, Security::DynamicHash *hasher) {
size_t length = hasher->digestLength();
Hashing::Byte digest[length];
hasher->finish(digest);
CFDictionaryAddValue(resultRef, CFTempString(hashName(type)), CFTempData(digest, length));
});
return result.yield();
}
std::string ResourceBuilder::hashName(CodeDirectory::HashAlgorithm type)
{
switch (type) {
case kSecCodeSignatureHashSHA1:
return "hash";
default:
char name[20];
snprintf(name, sizeof(name), "hash%d", int(type));
return name;
}
}
ResourceBuilder::Rule::Rule(const std::string &pattern, unsigned w, uint32_t f)
: weight(w), flags(f), source(pattern)
{
if (::regcomp(this, pattern.c_str(), REG_EXTENDED | REG_NOSUB)) MacOSError::throwMe(errSecCSResourceRulesInvalid);
secinfo("csresource", "%p rule %s added (weight %d, flags 0x%x)",
this, pattern.c_str(), w, f);
}
ResourceBuilder::Rule::~Rule()
{
::regfree(this);
}
bool ResourceBuilder::Rule::match(const char *s) const
{
switch (::regexec(this, s, 0, NULL, 0)) {
case 0:
return true;
case REG_NOMATCH:
return false;
default:
MacOSError::throwMe(errSecCSResourceRulesInvalid);
}
}
std::string ResourceBuilder::escapeRE(const std::string &s)
{
string r;
for (string::const_iterator it = s.begin(); it != s.end(); ++it) {
char c = *it;
if (strchr("\\[]{}().+*?^$|", c))
r.push_back('\\');
r.push_back(c);
}
return r;
}
ResourceSeal::ResourceSeal(CFTypeRef it)
: mDict(NULL), mRequirement(NULL), mLink(NULL), mFlags(0)
{
if (it == NULL)
MacOSError::throwMe(errSecCSResourcesInvalid);
if (CFGetTypeID(it) == CFDataGetTypeID()) mDict.take(cfmake<CFDictionaryRef>("{hash=%O}", it));
else if (CFGetTypeID(it) == CFDictionaryGetTypeID())
mDict = CFDictionaryRef(it);
else
MacOSError::throwMe(errSecCSResourcesInvalid);
int optional = 0;
bool err;
if (CFDictionaryGetValue(mDict, CFSTR("requirement")))
err = !cfscan(mDict, "{requirement=%SO,?optional=%B}", &mRequirement, &optional);
else if (CFDictionaryGetValue(mDict, CFSTR("symlink")))
err = !cfscan(mDict, "{symlink=%SO,?optional=%B}", &mLink, &optional);
else
err = !cfscan(mDict, "{?optional=%B}", &optional);
if (err)
MacOSError::throwMe(errSecCSResourcesInvalid);
if (optional)
mFlags |= ResourceBuilder::optional;
if (mRequirement)
mFlags |= ResourceBuilder::nested;
}
const Hashing::Byte *ResourceSeal::hash(CodeDirectory::HashAlgorithm type) const
{
std::string name = ResourceBuilder::hashName(type);
CFTypeRef hash = CFDictionaryGetValue(mDict, CFTempString(name));
if (hash == NULL) hash = CFDictionaryGetValue(mDict, CFSTR("hash"));
if (hash == NULL || CFGetTypeID(hash) != CFDataGetTypeID())
MacOSError::throwMe(errSecCSResourcesInvalid);
return CFDataGetBytePtr(CFDataRef(hash));
}
} }