#include "policyengine.h"
#include "xar++.h"
#include <security_utilities/cfmunge.h>
#include <Security/Security.h>
#include <Security/SecRequirementPriv.h>
#include <Security/SecPolicyPriv.h>
#include <CoreServices/CoreServicesPriv.h>
#undef check // Macro! Yech.
namespace Security {
namespace CodeSigning {
static const time_t NEGATIVE_HOLD = 60;
PolicyEngine::PolicyEngine()
: PolicyDatabase(NULL, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE)
{
}
PolicyEngine::~PolicyEngine()
{ }
void PolicyEngine::evaluate(CFURLRef path, AuthorityType type, SecAssessmentFlags flags, CFDictionaryRef context, CFMutableDictionaryRef result)
{
switch (type) {
case kAuthorityExecute:
evaluateCode(path, flags, context, result);
break;
case kAuthorityInstall:
evaluateInstall(path, flags, context, result);
break;
case kAuthorityOpenDoc:
evaluateDocOpen(path, flags, context, result);
break;
default:
MacOSError::throwMe(errSecCSInvalidAttributeValues);
break;
}
}
void PolicyEngine::evaluateCode(CFURLRef path, SecAssessmentFlags flags, CFDictionaryRef context, CFMutableDictionaryRef result)
{
const AuthorityType type = kAuthorityExecute;
CFRef<SecStaticCodeRef> code;
MacOSError::check(SecStaticCodeCreateWithPath(path, kSecCSDefaultFlags, &code.aref()));
if (flags & kSecAssessmentFlagRequestOrigin) {
CFRef<CFDictionaryRef> info;
MacOSError::check(SecCodeCopySigningInformation(code, kSecCSSigningInformation, &info.aref()));
if (CFArrayRef chain = CFArrayRef(CFDictionaryGetValue(info, kSecCodeInfoCertificates)))
setOrigin(chain, result);
}
SecCSFlags validationFlags = kSecCSDefaultFlags;
if (overrideAssessment()) validationFlags = kSecCSBasicValidateOnly;
SQLite::Statement query(*this,
"SELECT allow, requirement, inhibit_cache, expires, id, label FROM authority WHERE type = ?1 ORDER BY priority DESC;");
query.bind(1).integer(type);
while (query.nextRow()) {
bool allow = int(query[0]);
const char *reqString = query[1];
bool inhibit_cache = query[2];
time_t expires = SQLite::int64(query[3]);
SQLite3::int64 id = query[4];
const char *label = query[5];
if (expires && expires < time(NULL)) continue;
CFRef<SecRequirementRef> requirement;
MacOSError::check(SecRequirementCreateWithString(CFTempString(reqString), kSecCSDefaultFlags, &requirement.aref()));
switch (OSStatus rc = SecStaticCodeCheckValidity(code, validationFlags, requirement)) {
case noErr: break;
case errSecCSUnsigned:
cfadd(result, "{%O=#F}", kSecAssessmentAssessmentVerdict);
addAuthority(result, "no usable signature");
return;
case errSecCSSignatureFailed:
case errSecCSSignatureInvalid:
case errSecCSSignatureUnsupported:
case errSecCSSignatureNotVerifiable:
cfadd(result, "{%O=#F}", kSecAssessmentAssessmentVerdict);
addAuthority(result, "invalid signature");
return;
case errSecCSReqFailed: continue;
default: MacOSError::throwMe(rc);
}
if (!inhibit_cache && !(flags & kSecAssessmentFlagNoCache)) this->recordOutcome(code, allow, type, expires, id, label);
cfadd(result, "{%O=%B}", kSecAssessmentAssessmentVerdict, allow);
addAuthority(result, label, id);
return;
}
if (!(flags & kSecAssessmentFlagNoCache))
this->recordOutcome(code, false, type, time(NULL) + NEGATIVE_HOLD, 0, NULL);
cfadd(result, "{%O=%B}", kSecAssessmentAssessmentVerdict, false);
addAuthority(result, NULL);
}
void PolicyEngine::evaluateInstall(CFURLRef path, SecAssessmentFlags flags, CFDictionaryRef context, CFMutableDictionaryRef result)
{
const AuthorityType type = kAuthorityInstall;
Xar xar(cfString(path).c_str());
if (xar) {
if (!xar.isSigned()) {
cfadd(result, "{%O=%B}", kSecAssessmentAssessmentVerdict, false);
addAuthority(result, "no usable signature");
return;
}
if (CFRef<CFArrayRef> certs = xar.copyCertChain()) {
CFRef<SecPolicyRef> policy = SecPolicyCreateBasicX509();
CFRef<SecTrustRef> trust;
MacOSError::check(SecTrustCreateWithCertificates(certs, policy, &trust.aref()));
MacOSError::check(SecTrustSetOptions(trust, kSecTrustOptionImplicitAnchors));
SecTrustResultType trustResult;
MacOSError::check(SecTrustEvaluate(trust, &trustResult));
CFRef<CFArrayRef> chain;
CSSM_TP_APPLE_EVIDENCE_INFO *info;
MacOSError::check(SecTrustGetResult(trust, &trustResult, &chain.aref(), &info));
if (flags & kSecAssessmentFlagRequestOrigin)
setOrigin(chain, result);
switch (trustResult) {
case kSecTrustResultProceed:
case kSecTrustResultConfirm:
case kSecTrustResultUnspecified:
break;
default:
{
OSStatus rc;
MacOSError::check(SecTrustGetCssmResultCode(trust, &rc));
MacOSError::throwMe(rc);
}
}
SQLite::Statement query(*this,
"SELECT allow, requirement, inhibit_cache, expires, id, label FROM authority WHERE type = ?1 ORDER BY priority DESC;");
query.bind(1).integer(type);
while (query.nextRow()) {
bool allow = int(query[0]);
const char *reqString = query[1];
bool inhibit_cache = query[2];
time_t expires = SQLite::int64(query[3]);
SQLite3::int64 id = query[4];
const char *label = query[5];
if (expires && expires < time(NULL)) continue;
CFRef<SecRequirementRef> requirement;
MacOSError::check(SecRequirementCreateWithString(CFTempString(reqString), kSecCSDefaultFlags, &requirement.aref()));
switch (OSStatus rc = SecRequirementEvaluate(requirement, chain, NULL, kSecCSDefaultFlags)) {
case noErr: break;
case errSecCSReqFailed: continue;
default: MacOSError::throwMe(rc);
}
cfadd(result, "{%O=%B}", kSecAssessmentAssessmentVerdict, allow);
addAuthority(result, label, id);
return;
}
}
}
cfadd(result, "{%O=#F}", kSecAssessmentAssessmentVerdict);
addAuthority(result, NULL);
}
void PolicyEngine::evaluateDocOpen(CFURLRef path, SecAssessmentFlags flags, CFDictionaryRef context, CFMutableDictionaryRef result)
{
if (context) {
if (CFStringRef riskCategory = CFStringRef(CFDictionaryGetValue(context, kLSDownloadRiskCategoryKey))) {
if (CFEqual(riskCategory, kLSRiskCategorySafe)
|| CFEqual(riskCategory, kLSRiskCategoryNeutral)
|| CFEqual(riskCategory, kLSRiskCategoryUnknown)
|| CFEqual(riskCategory, kLSRiskCategoryMayContainUnsafeExecutable)) {
cfadd(result, "{%O=#T}", kSecAssessmentAssessmentVerdict);
} else {
cfadd(result, "{%O=#F}", kSecAssessmentAssessmentVerdict);
}
addAuthority(result, "_XProtect");
addToAuthority(result, kLSDownloadRiskCategoryKey, riskCategory);
return;
}
}
cfadd(result, "{%O=%F}", kSecAssessmentAssessmentVerdict);
addAuthority(result, NULL);
}
void PolicyEngine::addAuthority(CFMutableDictionaryRef parent, const char *label, SQLite::int64 row, CFTypeRef cacheInfo)
{
CFRef<CFMutableDictionaryRef> auth = makeCFMutableDictionary();
if (label)
cfadd(auth, "{%O=%s}", kSecAssessmentAssessmentSource, label);
if (row)
CFDictionaryAddValue(auth, kSecAssessmentAssessmentAuthorityRow, CFTempNumber(row));
if (cacheInfo)
CFDictionaryAddValue(auth, kSecAssessmentAssessmentFromCache, cacheInfo);
CFDictionaryAddValue(parent, kSecAssessmentAssessmentAuthority, auth);
}
void PolicyEngine::addToAuthority(CFMutableDictionaryRef parent, CFStringRef key, CFTypeRef value)
{
CFMutableDictionaryRef authority = CFMutableDictionaryRef(CFDictionaryGetValue(parent, kSecAssessmentAssessmentAuthority));
assert(authority);
CFDictionaryAddValue(authority, key, value);
}
bool PolicyEngine::add(CFURLRef path, AuthorityType type, SecAssessmentFlags flags, CFDictionaryRef context)
{
if (type != kAuthorityExecute)
MacOSError::throwMe(errSecCSUnimplemented);
double priority = 0;
string label;
if (context) {
if (CFTypeRef pri = CFDictionaryGetValue(context, kSecAssessmentUpdateKeyPriority)) {
if (CFGetTypeID(pri) != CFNumberGetTypeID())
MacOSError::throwMe(errSecCSBadDictionaryFormat);
CFNumberGetValue(CFNumberRef(pri), kCFNumberDoubleType, &priority);
}
if (CFTypeRef lab = CFDictionaryGetValue(context, kSecAssessmentUpdateKeyLabel)) {
if (CFGetTypeID(lab) != CFStringGetTypeID())
MacOSError::throwMe(errSecCSBadDictionaryFormat);
label = cfString(CFStringRef(lab));
}
}
CFRef<SecStaticCodeRef> code;
MacOSError::check(SecStaticCodeCreateWithPath(path, kSecCSDefaultFlags, &code.aref()));
CFRef<CFDictionaryRef> info;
MacOSError::check(SecCodeCopySigningInformation(code, kSecCSDefaultFlags, &info.aref()));
CFRef<SecRequirementRef> dr;
MacOSError::check(SecCodeCopyDesignatedRequirement(code, kSecCSDefaultFlags, &dr.aref()));
CFRef<CFStringRef> requirementText;
MacOSError::check(SecRequirementCopyString(dr, kSecCSDefaultFlags, &requirementText.aref()));
SQLite::Statement insert(*this,
"INSERT INTO authority (type, allow, requirement, priority, label) VALUES (?1, ?2, ?3, ?4, ?5);");
insert.bind(1).integer(type);
insert.bind(2).integer(true);
insert.bind(3) = requirementText.get();
insert.bind(4) = priority;
insert.bind(5) = label.c_str();
insert.execute();
return true;
}
void PolicyEngine::setOrigin(CFArrayRef chain, CFMutableDictionaryRef result)
{
if (chain)
if (CFArrayGetCount(chain) > 0)
if (SecCertificateRef leaf = SecCertificateRef(CFArrayGetValueAtIndex(chain, 0)))
if (CFStringRef summary = SecCertificateCopyLongDescription(NULL, leaf, NULL))
CFDictionarySetValue(result, kSecAssessmentAssessmentOriginator, summary);
}
void PolicyEngine::recordOutcome(SecStaticCodeRef code, bool allow, AuthorityType type, time_t expires, int authority, const char *label)
{
CFRef<CFDictionaryRef> info;
MacOSError::check(SecCodeCopySigningInformation(code, kSecCSDefaultFlags, &info.aref()));
CFDataRef cdHash = CFDataRef(CFDictionaryGetValue(info, kSecCodeInfoUnique));
CFRef<CFURLRef> path;
MacOSError::check(SecCodeCopyPath(code, kSecCSDefaultFlags, &path.aref()));
SQLite::Transaction xact(*this, SQLite3::Transaction::deferred, "caching");
SQLite::Statement insert(*this,
"INSERT OR REPLACE INTO object (type, allow, hash, expires, authority, label, path) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)");
insert.bind(1).integer(type);
insert.bind(2).integer(allow);
insert.bind(3) = cdHash;
if (expires)
insert.bind(4).integer(expires);
insert.bind(5).integer(authority);
if (label)
insert.bind(6) = label;
insert.bind(7) = cfString(path).c_str();
insert.execute();
xact.commit();
}
} }