opaquewhitelist.cpp [plain text]
#include "opaquewhitelist.h"
#include "csutilities.h"
#include "StaticCode.h"
#include <CoreFoundation/CoreFoundation.h>
#include <Security/SecCodePriv.h>
#include <Security/SecCodeSigner.h>
#include <Security/SecStaticCode.h>
#include <security_utilities/cfutilities.h>
#include <security_utilities/cfmunge.h>
#include <CoreFoundation/CFBundlePriv.h>
#include <spawn.h>
namespace Security {
namespace CodeSigning {
using namespace SQLite;
static std::string hashString(CFDataRef hash);
static void attachOpaque(SecStaticCodeRef code, SecAssessmentFeedback feedback);
OpaqueWhitelist::OpaqueWhitelist(const char *path, int flags)
: SQLite::Database(path ? path : opaqueDatabase, flags)
{
SQLite::Statement createConditions(*this,
"CREATE TABLE IF NOT EXISTS conditions ("
" label text,"
" weight real not null unique,"
" source text,"
" identifier text,"
" version text,"
" conditions text not null);"
);
createConditions.execute();
mOverrideQueue = dispatch_queue_create("com.apple.security.assessment.whitelist-override", DISPATCH_QUEUE_SERIAL);
}
OpaqueWhitelist::~OpaqueWhitelist()
{
dispatch_release(mOverrideQueue);
}
bool OpaqueWhitelist::contains(SecStaticCodeRef codeRef, SecAssessmentFeedback feedback, OSStatus reason)
{
SecPointer<SecStaticCode> code = new SecStaticCode(SecStaticCode::requiredStatic(codeRef)->diskRep());
CFCopyRef<CFDataRef> current = code->cdHash(); CFDataRef opaque = NULL; bool match = false;
if (!current)
return false;
CFRef<CFDictionaryRef> info;
std::string team = "";
CFStringRef cfVersion = NULL, cfShortVersion = NULL, cfExecutable = NULL;
if (errSecSuccess == SecCodeCopySigningInformation(code->handle(false), kSecCSSigningInformation, &info.aref())) {
if (CFStringRef cfTeam = CFStringRef(CFDictionaryGetValue(info, kSecCodeInfoTeamIdentifier)))
team = cfString(cfTeam);
if (CFDictionaryRef infoPlist = CFDictionaryRef(CFDictionaryGetValue(info, kSecCodeInfoPList))) {
if (CFTypeRef version = CFDictionaryGetValue(infoPlist, kCFBundleVersionKey))
if (CFGetTypeID(version) == CFStringGetTypeID())
cfVersion = CFStringRef(version);
if (CFTypeRef shortVersion = CFDictionaryGetValue(infoPlist, _kCFBundleShortVersionStringKey))
if (CFGetTypeID(shortVersion) == CFStringGetTypeID())
cfShortVersion = CFStringRef(shortVersion);
if (CFTypeRef executable = CFDictionaryGetValue(infoPlist, kCFBundleExecutableKey))
if (CFGetTypeID(executable) == CFStringGetTypeID())
cfExecutable = CFStringRef(executable);
}
}
attachOpaque(code->handle(false), feedback);
opaque = code->cdHash();
if (opaque) {
SQLite::Statement lookup(*this, "SELECT opaque FROM whitelist WHERE current=:current"
" AND opaque != 'disable override'");
lookup.bind(":current") = current.get();
while (lookup.nextRow()) {
CFRef<CFDataRef> expected = lookup[0].data();
if (CFEqual(opaque, expected)) {
match = true; break;
}
}
}
std::string currentHash = hashString(current);
std::string opaqueHash = opaque ? hashString(opaque) : "none";
MessageTrace trace("com.apple.security.assessment.whitelist2", code->identifier().c_str());
trace.add("signature2", "%s", currentHash.c_str());
trace.add("signature3", "%s", opaqueHash.c_str());
trace.add("result", match ? "pass" : "fail");
trace.add("reason", "%d", (int)reason);
if (!team.empty())
trace.add("teamid", "%s", team.c_str());
if (cfVersion)
trace.add("version", "%s", cfString(cfVersion).c_str());
if (cfShortVersion)
trace.add("version2", "%s", cfString(cfShortVersion).c_str());
if (cfExecutable)
trace.add("execname", "%s", cfString(cfExecutable).c_str());
trace.send("");
return match;
}
CFDictionaryRef OpaqueWhitelist::validationConditionsFor(SecStaticCodeRef code)
{
std::string team = "UNKNOWN";
CFStringRef cfId = NULL;
CFStringRef cfVersion = NULL;
CFRef<CFDictionaryRef> info; if (errSecSuccess == SecCodeCopySigningInformation(code, kSecCSSigningInformation, &info.aref())) {
if (CFStringRef cfTeam = CFStringRef(CFDictionaryGetValue(info, kSecCodeInfoTeamIdentifier)))
team = cfString(cfTeam);
cfId = CFStringRef(CFDictionaryGetValue(info, kSecCodeInfoIdentifier));
if (CFDictionaryRef infoPlist = CFDictionaryRef(CFDictionaryGetValue(info, kSecCodeInfoPList)))
if (CFTypeRef version = CFDictionaryGetValue(infoPlist, _kCFBundleShortVersionStringKey))
if (CFGetTypeID(version) == CFStringGetTypeID())
cfVersion = CFStringRef(version);
}
if (cfId == NULL) return NULL;
SQLite::Statement matches(*this,
"SELECT conditions FROM conditions"
" WHERE (source = :source or source IS NULL)"
" AND (identifier = :identifier or identifier is NULL)"
" AND ((:version IS NULL AND version IS NULL) OR (version = :version OR version IS NULL))"
" ORDER BY weight DESC"
" LIMIT 1"
);
matches.bind(":source") = team;
matches.bind(":identifier") = cfString(cfId);
if (cfVersion)
matches.bind(":version") = cfString(cfVersion);
if (matches.nextRow()) {
CFTemp<CFDictionaryRef> conditions((const char*)matches[0]);
return conditions.yield();
}
return NULL;
}
static std::string hashString(CFDataRef hash)
{
if (CFDataGetLength(hash) != sizeof(SHA1::Digest)) {
return std::string();
} else {
const UInt8 *bytes = CFDataGetBytePtr(hash);
char s[2 * SHA1::digestLength + 1];
for (unsigned n = 0; n < SHA1::digestLength; n++)
sprintf(&s[2*n], "%2.2x", bytes[n]);
return std::string(s);
}
}
void OpaqueWhitelist::add(SecStaticCodeRef codeRef)
{
SecPointer<SecStaticCode> code = new SecStaticCode(SecStaticCode::requiredStatic(codeRef)->diskRep());
CFCopyRef<CFDataRef> current = code->cdHash();
attachOpaque(code->handle(false), NULL); CFDataRef opaque = code->cdHash();
SQLite::Statement insert(*this, "INSERT OR REPLACE INTO whitelist (current,opaque) VALUES (:current, :opaque)");
insert.bind(":current") = current.get();
insert.bind(":opaque") = opaque;
insert.execute();
}
static void attachOpaque(SecStaticCodeRef code, SecAssessmentFeedback feedback)
{
CFTemp<CFDictionaryRef> rules("{" "rules={"
"'^.*' = #T"
"'^Info\\.plist$' = {omit=#T,weight=10}"
"},rules2={"
"'^(Frameworks|SharedFrameworks|Plugins|Plug-ins|XPCServices|Helpers|MacOS)/' = {nested=#T, weight=0}"
"'^.*' = #T"
"'^Info\\.plist$' = {omit=#T,weight=10}"
"'^[^/]+$' = {top=#T, weight=0}"
"}"
"}");
CFRef<CFDataRef> signature = CFDataCreateMutable(NULL, 0);
CFTemp<CFDictionaryRef> arguments("{%O=%O, %O=#N, %O=%d, %O=%O}",
kSecCodeSignerDetached, signature.get(),
kSecCodeSignerIdentity,
kSecCodeSignerDigestAlgorithm, kSecCodeSignatureHashSHA1,
kSecCodeSignerResourceRules, rules.get());
CFRef<SecCodeSignerRef> signer;
SecCSFlags creationFlags = kSecCSSignOpaque | kSecCSSignNoV1 | kSecCSSignBundleRoot;
SecCSFlags operationFlags = 0;
if (feedback)
operationFlags |= kSecCSReportProgress;
MacOSError::check(SecStaticCodeSetCallback(code, kSecCSDefaultFlags, NULL, ^CFTypeRef(SecStaticCodeRef code, CFStringRef stage, CFDictionaryRef info) {
if (CFEqual(stage, CFSTR("progress"))) {
bool proceed = feedback(kSecAssessmentFeedbackProgress, info);
if (!proceed)
SecStaticCodeCancelValidation(code, kSecCSDefaultFlags);
}
return NULL;
}));
MacOSError::check(SecCodeSignerCreate(arguments, creationFlags, &signer.aref()));
MacOSError::check(SecCodeSignerAddSignature(signer, code, operationFlags));
MacOSError::check(SecCodeSetDetachedSignature(code, signature, kSecCSDefaultFlags));
}
} }