#include "reqinterp.h"
#include "codesigning_dtrace.h"
#include <Security/SecTrustSettingsPriv.h>
#include <Security/SecCertificatePriv.h>
#include <security_utilities/memutils.h>
#include <security_utilities/logging.h>
#include <sys/csr.h>
#include <IOKit/IOKitLib.h>
#include <IOKit/IOCFUnserialize.h>
#include "csutilities.h"
namespace Security {
namespace CodeSigning {
class Fragments {
public:
Fragments();
bool named(const std::string &name, const Requirement::Context &ctx)
{ return evalNamed("subreq", name, ctx); }
bool namedAnchor(const std::string &name, const Requirement::Context &ctx)
{ return evalNamed("anchorreq", name, ctx); }
private:
bool evalNamed(const char *type, const std::string &name, const Requirement::Context &ctx);
CFDataRef fragment(const char *type, const std::string &name);
typedef std::map<std::string, CFRef<CFDataRef> > FragMap;
private:
CFBundleRef mMyBundle; Mutex mLock; FragMap mFragments; };
static ModuleNexus<Fragments> fragments;
static CFStringRef appleIntermediateCN = CFSTR("Apple Code Signing Certification Authority");
static CFStringRef appleIntermediateO = CFSTR("Apple Inc.");
bool Requirement::Interpreter::evaluate()
{ return eval(stackLimit); }
bool Requirement::Interpreter::eval(int depth)
{
if (--depth <= 0) MacOSError::throwMe(errSecCSReqInvalid);
ExprOp op = ExprOp(get<uint32_t>());
CODESIGN_EVAL_REQINT_OP(op, this->pc() - sizeof(uint32_t));
switch (op & ~opFlagMask) {
case opFalse:
return false;
case opTrue:
return true;
case opIdent:
return mContext->directory && getString() == mContext->directory->identifier();
case opAppleAnchor:
return appleSigned();
case opAppleGenericAnchor:
return appleAnchored();
case opAnchorHash:
{
SecCertificateRef cert = mContext->cert(get<int32_t>());
return verifyAnchor(cert, getSHA1());
}
case opInfoKeyValue: {
string key = getString();
return infoKeyValue(key, Match(CFTempString(getString()), matchEqual));
}
case opAnd:
return eval(depth) & eval(depth);
case opOr:
return eval(depth) | eval(depth);
case opCDHash:
if (mContext->directory) {
CFRef<CFDataRef> cdhash = mContext->directory->cdhash();
CFRef<CFDataRef> required = getHash();
return CFEqual(cdhash, required);
} else
return false;
case opNot:
return !eval(depth);
case opInfoKeyField:
{
string key = getString();
Match match(*this);
return infoKeyValue(key, match);
}
case opEntitlementField:
{
string key = getString();
Match match(*this);
return entitlementValue(key, match);
}
case opCertField:
{
SecCertificateRef cert = mContext->cert(get<int32_t>());
string key = getString();
Match match(*this);
return certFieldValue(key, match, cert);
}
#if TARGET_OS_OSX
case opCertGeneric:
{
SecCertificateRef cert = mContext->cert(get<int32_t>());
string key = getString();
Match match(*this);
return certFieldGeneric(key, match, cert);
}
case opCertPolicy:
{
SecCertificateRef cert = mContext->cert(get<int32_t>());
string key = getString();
Match match(*this);
return certFieldPolicy(key, match, cert);
}
#endif
case opTrustedCert:
return trustedCert(get<int32_t>());
case opTrustedCerts:
return trustedCerts();
case opNamedAnchor:
return fragments().namedAnchor(getString(), *mContext);
case opNamedCode:
return fragments().named(getString(), *mContext);
case opPlatform:
{
int32_t targetPlatform = get<int32_t>();
return mContext->directory && mContext->directory->platform == targetPlatform;
}
default:
if (op & (opGenericFalse | opGenericSkip)) {
skip(get<uint32_t>());
if (op & opGenericFalse) {
CODESIGN_EVAL_REQINT_UNKNOWN_FALSE(op);
return false;
} else {
CODESIGN_EVAL_REQINT_UNKNOWN_SKIPPED(op);
return eval(depth);
}
}
secinfo("csinterp", "opcode 0x%x cannot be handled; aborting", op);
MacOSError::throwMe(errSecCSUnimplemented);
}
}
bool Requirement::Interpreter::infoKeyValue(const string &key, const Match &match)
{
if (mContext->info) if (CFTypeRef value = CFDictionaryGetValue(mContext->info, CFTempString(key)))
return match(value);
return false;
}
bool Requirement::Interpreter::entitlementValue(const string &key, const Match &match)
{
if (mContext->entitlements) if (CFTypeRef value = CFDictionaryGetValue(mContext->entitlements, CFTempString(key)))
return match(value);
return false;
}
bool Requirement::Interpreter::certFieldValue(const string &key, const Match &match, SecCertificateRef cert)
{
#if TARGET_OS_OSX
if (cert == NULL)
return false;
static const struct CertField {
const char *name;
const CSSM_OID *oid;
} certFields[] = {
{ "subject.C", &CSSMOID_CountryName },
{ "subject.CN", &CSSMOID_CommonName },
{ "subject.D", &CSSMOID_Description },
{ "subject.L", &CSSMOID_LocalityName },
{ "subject.O", &CSSMOID_OrganizationName },
{ "subject.C-O", &CSSMOID_CollectiveOrganizationName },
{ "subject.OU", &CSSMOID_OrganizationalUnitName },
{ "subject.C-OU", &CSSMOID_CollectiveOrganizationalUnitName },
{ "subject.ST", &CSSMOID_StateProvinceName },
{ "subject.C-ST", &CSSMOID_CollectiveStateProvinceName },
{ "subject.STREET", &CSSMOID_StreetAddress },
{ "subject.C-STREET", &CSSMOID_CollectiveStreetAddress },
{ "subject.UID", &CSSMOID_UserID },
{ NULL, NULL }
};
for (const CertField *cf = certFields; cf->name; cf++)
if (cf->name == key) {
CFRef<CFStringRef> value;
OSStatus rc = SecCertificateCopySubjectComponent(cert, cf->oid, &value.aref());
if (rc) {
secinfo("csinterp", "cert %p lookup for DN.%s failed rc=%d", cert, key.c_str(), (int)rc);
return false;
}
return match(value);
}
if (key == "email") {
CFRef<CFArrayRef> value;
OSStatus rc = SecCertificateCopyEmailAddresses(cert, &value.aref());
if (rc) {
secinfo("csinterp", "cert %p lookup for email failed rc=%d", cert, (int)rc);
return false;
}
return match(value);
}
secinfo("csinterp", "cert field notation \"%s\" not understood", key.c_str());
#endif
return false;
}
#if TARGET_OS_OSX
bool Requirement::Interpreter::certFieldGeneric(const string &key, const Match &match, SecCertificateRef cert)
{
CssmOid oid((char *)key.data(), key.length());
return certFieldGeneric(oid, match, cert);
}
bool Requirement::Interpreter::certFieldGeneric(const CssmOid &oid, const Match &match, SecCertificateRef cert)
{
return cert && certificateHasField(cert, oid) && match(kCFBooleanTrue);
}
bool Requirement::Interpreter::certFieldPolicy(const string &key, const Match &match, SecCertificateRef cert)
{
CssmOid oid((char *)key.data(), key.length());
return certFieldPolicy(oid, match, cert);
}
bool Requirement::Interpreter::certFieldPolicy(const CssmOid &oid, const Match &match, SecCertificateRef cert)
{
return cert && certificateHasPolicy(cert, oid) && match(kCFBooleanTrue);
}
#endif
bool Requirement::Interpreter::appleAnchored()
{
if (SecCertificateRef cert = mContext->cert(anchorCert))
if (isAppleCA(cert))
return true;
return false;
}
static CFStringRef kAMFINVRAMTrustedKeys = CFSTR("AMFITrustedKeys");
CFArrayRef Requirement::Interpreter::getAdditionalTrustedAnchors()
{
__block CFRef<CFMutableArrayRef> keys = makeCFMutableArray(0);
try {
io_registry_entry_t entry = IORegistryEntryFromPath(kIOMasterPortDefault, "IODeviceTree:/options");
if (entry == IO_OBJECT_NULL)
return NULL;
CFRef<CFDataRef> configData = (CFDataRef)IORegistryEntryCreateCFProperty(entry, kAMFINVRAMTrustedKeys, kCFAllocatorDefault, 0);
IOObjectRelease(entry);
if (!configData)
return NULL;
CFRef<CFDictionaryRef> configDict = CFDictionaryRef(IOCFUnserializeWithSize((const char *)CFDataGetBytePtr(configData),
(size_t)CFDataGetLength(configData),
kCFAllocatorDefault, 0, NULL));
if (!configDict)
return NULL;
CFArrayRef trustedKeys = CFArrayRef(CFDictionaryGetValue(configDict, CFSTR("trustedKeys")));
if (!trustedKeys && CFGetTypeID(trustedKeys) != CFArrayGetTypeID())
return NULL;
cfArrayApplyBlock(trustedKeys, ^(const void *value) {
CFDictionaryRef key = CFDictionaryRef(value);
if (!key && CFGetTypeID(key) != CFDictionaryGetTypeID())
return;
CFDataRef hash = CFDataRef(CFDictionaryGetValue(key, CFSTR("certDigest")));
if (!hash && CFGetTypeID(hash) != CFDataGetTypeID())
return;
CFArrayAppendValue(keys, hash);
});
} catch (...) {
}
if (CFArrayGetCount(keys) == 0)
return NULL;
return keys.yield();
}
bool Requirement::Interpreter::appleLocalAnchored()
{
static CFArrayRef additionalTrustedCertificates = NULL;
if (csr_check(CSR_ALLOW_APPLE_INTERNAL))
return false;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
additionalTrustedCertificates = getAdditionalTrustedAnchors();
});
if (additionalTrustedCertificates == NULL)
return false;
CFRef<CFDataRef> hash = SecCertificateCopySHA256Digest(mContext->cert(leafCert));
if (!hash)
return false;
if (CFArrayContainsValue(additionalTrustedCertificates, CFRangeMake(0, CFArrayGetCount(additionalTrustedCertificates)), hash))
return true;
return false;
}
bool Requirement::Interpreter::appleSigned()
{
if (appleAnchored()) {
if (SecCertificateRef intermed = mContext->cert(-2)) if (certFieldValue("subject.CN", Match(appleIntermediateCN, matchEqual), intermed)
&& certFieldValue("subject.O", Match(appleIntermediateO, matchEqual), intermed))
return true;
} else if (appleLocalAnchored()) {
return true;
}
return false;
}
bool Requirement::Interpreter::verifyAnchor(SecCertificateRef cert, const unsigned char *digest)
{
if (cert) {
SHA1 hasher;
#if TARGET_OS_OSX
CSSM_DATA certData;
MacOSError::check(SecCertificateGetData(cert, &certData));
hasher(certData.Data, certData.Length);
#else
hasher(SecCertificateGetBytePtr(cert), SecCertificateGetLength(cert));
#endif
return hasher.verify(digest);
}
return false;
}
bool Requirement::Interpreter::trustedCerts()
{
int anchor = mContext->certCount() - 1;
for (int slot = 0; slot <= anchor; slot++)
if (SecCertificateRef cert = mContext->cert(slot))
switch (trustSetting(cert, slot == anchor)) {
case kSecTrustSettingsResultTrustRoot:
case kSecTrustSettingsResultTrustAsRoot:
return true;
case kSecTrustSettingsResultDeny:
return false;
case kSecTrustSettingsResultUnspecified:
break;
default:
assert(false);
return false;
}
else
return false;
return false;
}
bool Requirement::Interpreter::trustedCert(int slot)
{
if (SecCertificateRef cert = mContext->cert(slot)) {
int anchorSlot = mContext->certCount() - 1;
switch (trustSetting(cert, slot == anchorCert || slot == anchorSlot)) {
case kSecTrustSettingsResultTrustRoot:
case kSecTrustSettingsResultTrustAsRoot:
return true;
case kSecTrustSettingsResultDeny:
case kSecTrustSettingsResultUnspecified:
return false;
default:
assert(false);
return false;
}
} else
return false;
}
SecTrustSettingsResult Requirement::Interpreter::trustSetting(SecCertificateRef cert, bool isAnchor)
{
#if TARGET_OS_OSX
assert(cert);
SHA1::Digest digest;
hashOfCertificate(cert, digest);
string Certhex = CssmData(digest, sizeof(digest)).toHex();
for (string::iterator it = Certhex.begin(); it != Certhex.end(); ++it)
if (islower(*it))
*it = toupper(*it);
SecTrustSettingsDomain domain;
SecTrustSettingsResult result;
CSSM_RETURN *errors = NULL;
uint32 errorCount = 0;
bool foundMatch, foundAny;
switch (OSStatus rc = SecTrustSettingsEvaluateCert(
CFTempString(Certhex), &CSSMOID_APPLE_TP_CODE_SIGNING, NULL, 0, kSecTrustSettingsKeyUseAny, isAnchor,
&domain, &errors, &errorCount, &result, &foundMatch, &foundAny )) {
case errSecSuccess:
::free(errors);
if (foundMatch)
return result;
else
return kSecTrustSettingsResultUnspecified;
default:
::free(errors);
MacOSError::throwMe(rc);
}
#else
return kSecTrustSettingsResultUnspecified;
#endif
}
Requirement::Interpreter::Match::Match(Interpreter &interp)
{
switch (mOp = interp.get<MatchOperation>()) {
case matchExists:
break;
case matchEqual:
case matchContains:
case matchBeginsWith:
case matchEndsWith:
case matchLessThan:
case matchGreaterThan:
case matchLessEqual:
case matchGreaterEqual:
mValue.take(makeCFString(interp.getString()));
break;
default:
interp.getString(); break;
}
}
bool Requirement::Interpreter::Match::operator () (CFTypeRef candidate) const
{
if (!candidate)
return false;
if (CFGetTypeID(candidate) == CFArrayGetTypeID()) {
CFArrayRef array = CFArrayRef(candidate);
CFIndex count = CFArrayGetCount(array);
for (CFIndex n = 0; n < count; n++)
if ((*this)(CFArrayGetValueAtIndex(array, n))) return true;
}
switch (mOp) {
case matchExists: return !CFEqual(candidate, kCFBooleanFalse);
case matchEqual: return CFEqual(candidate, mValue);
case matchContains:
if (CFGetTypeID(candidate) == CFStringGetTypeID()) {
CFStringRef value = CFStringRef(candidate);
if (CFStringFindWithOptions(value, mValue, CFRangeMake(0, CFStringGetLength(value)), 0, NULL))
return true;
}
return false;
case matchBeginsWith:
if (CFGetTypeID(candidate) == CFStringGetTypeID()) {
CFStringRef value = CFStringRef(candidate);
if (CFStringFindWithOptions(value, mValue, CFRangeMake(0, CFStringGetLength(mValue)), 0, NULL))
return true;
}
return false;
case matchEndsWith:
if (CFGetTypeID(candidate) == CFStringGetTypeID()) {
CFStringRef value = CFStringRef(candidate);
CFIndex matchLength = CFStringGetLength(mValue);
CFIndex start = CFStringGetLength(value) - matchLength;
if (start >= 0)
if (CFStringFindWithOptions(value, mValue, CFRangeMake(start, matchLength), 0, NULL))
return true;
}
return false;
case matchLessThan:
return inequality(candidate, kCFCompareNumerically, kCFCompareLessThan, true);
case matchGreaterThan:
return inequality(candidate, kCFCompareNumerically, kCFCompareGreaterThan, true);
case matchLessEqual:
return inequality(candidate, kCFCompareNumerically, kCFCompareGreaterThan, false);
case matchGreaterEqual:
return inequality(candidate, kCFCompareNumerically, kCFCompareLessThan, false);
default:
return false;
}
}
bool Requirement::Interpreter::Match::inequality(CFTypeRef candidate, CFStringCompareFlags flags,
CFComparisonResult outcome, bool negate) const
{
if (CFGetTypeID(candidate) == CFStringGetTypeID()) {
CFStringRef value = CFStringRef(candidate);
if ((CFStringCompare(value, mValue, flags) == outcome) == negate)
return true;
}
return false;
}
Fragments::Fragments()
{
mMyBundle = CFBundleGetBundleWithIdentifier(CFSTR("com.apple.security"));
}
bool Fragments::evalNamed(const char *type, const std::string &name, const Requirement::Context &ctx)
{
if (CFDataRef fragData = fragment(type, name)) {
const Requirement *req = (const Requirement *)CFDataGetBytePtr(fragData); return req->validates(ctx);
}
return false;
}
CFDataRef Fragments::fragment(const char *type, const std::string &name)
{
string key = name + "!!" + type; StLock<Mutex> _(mLock); FragMap::const_iterator it = mFragments.find(key);
if (it == mFragments.end()) {
CFRef<CFDataRef> fragData; if (CFRef<CFURLRef> fragURL = CFBundleCopyResourceURL(mMyBundle, CFTempString(name), CFSTR("csreq"), CFTempString(type)))
if (CFRef<CFDataRef> data = cfLoadFile(fragURL)) { const Requirement *req = (const Requirement *)CFDataGetBytePtr(data);
if (req->validateBlob(CFDataGetLength(data))) fragData = data; else
Syslog::warning("Invalid sub-requirement at %s", cfString(fragURL).c_str());
}
if (CODESIGN_EVAL_REQINT_FRAGMENT_LOAD_ENABLED())
CODESIGN_EVAL_REQINT_FRAGMENT_LOAD(type, name.c_str(), fragData ? CFDataGetBytePtr(fragData) : NULL);
mFragments[key] = fragData; return fragData;
}
CODESIGN_EVAL_REQINT_FRAGMENT_HIT(type, name.c_str());
return it->second;
}
} }