// reqinterp - Requirement language (exprOp) interpreter
//
#include "reqinterp.h"
+#include "codesigning_dtrace.h"
#include <Security/SecTrustSettingsPriv.h>
#include <Security/SecCertificatePriv.h>
#include <security_utilities/memutils.h>
-#include <security_cdsa_utilities/cssmdata.h> // for hex encoding
+#include <security_utilities/logging.h>
#include "csutilities.h"
namespace Security {
//
-// Construct an interpreter given a Requirement and an evaluation context.
+// Fragment fetching, caching, and evaluation.
//
-Requirement::Interpreter::Interpreter(const Requirement *req, const Context *ctx)
- : Reader(req), mContext(ctx)
-{
-}
+// Several language elements allow "calling" of separate requirement programs
+// stored on disk as (binary) requirement blobs. The Fragments class takes care
+// of finding, loading, caching, and evaluating them.
+//
+// This is a singleton for (process global) caching. It works fine as multiple instances,
+// at a loss of caching effectiveness.
+//
+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; // Security.framework bundle
+ Mutex mLock; // lock for all of the below...
+ FragMap mFragments; // cached fragments
+};
+
+static ModuleNexus<Fragments> fragments;
+
+
+//
+// Magic certificate features
+//
+static CFStringRef appleIntermediateCN = CFSTR("Apple Code Signing Certification Authority");
+static CFStringRef appleIntermediateO = CFSTR("Apple Inc.");
//
bool Requirement::Interpreter::evaluate()
{
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 getString() == mContext->directory->identifier();
+ return mContext->directory && getString() == mContext->directory->identifier();
case opAppleAnchor:
- if (SecCertificateRef cert = mContext->cert(anchorCert))
- return verifyAnchor(cert, appleAnchorHash())
-#if defined(TEST_APPLE_ANCHOR)
- || verifyAnchor(cert, testAppleAnchorHash())
-#endif
- ;
- else
- return false;
+ return appleSigned();
+ case opAppleGenericAnchor:
+ return appleAnchored();
case opAnchorHash:
{
SecCertificateRef cert = mContext->cert(get<int32_t>());
return infoKeyValue(key, Match(CFTempString(getString()), matchEqual));
}
case opAnd:
- return evaluate() && evaluate();
+ return evaluate() & evaluate();
case opOr:
- return evaluate() || evaluate();
+ return evaluate() | evaluate();
case opCDHash:
- {
+ if (mContext->directory) {
SHA1 hash;
hash(mContext->directory, mContext->directory->length());
return hash.verify(getHash());
- }
+ } else
+ return false;
case opNot:
return !evaluate();
case opInfoKeyField:
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>());
Match match(*this);
return certFieldValue(key, match, cert);
}
+ 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);
+ }
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);
default:
// opcode not recognized - handle generically if possible, fail otherwise
if (op & (opGenericFalse | opGenericSkip)) {
// unknown opcode, but it has a size field and can be safely bypassed
skip(get<uint32_t>());
if (op & opGenericFalse) {
- secdebug("csinterp", "opcode 0x%x interpreted as false", op);
+ CODESIGN_EVAL_REQINT_UNKNOWN_FALSE(op);
return false;
} else {
- secdebug("csinterp", "opcode 0x%x ignored; continuing", op);
+ CODESIGN_EVAL_REQINT_UNKNOWN_SKIPPED(op);
return evaluate();
}
}
}
+//
+// Evaluate an entitlement condition
+//
+bool Requirement::Interpreter::entitlementValue(const string &key, const Match &match)
+{
+ if (mContext->entitlements) // we have an Info.plist
+ 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)
{
// no cert, no chance
{ "subject.CN", &CSSMOID_CommonName },
{ "subject.D", &CSSMOID_Description },
{ "subject.L", &CSSMOID_LocalityName },
+// { "subject.C-L", &CSSMOID_CollectiveLocalityName }, // missing from Security.framework headers
{ "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 }
};
-
- // email multi-valued match (any of...)
- if (key == "email") {
- CFRef<CFArrayRef> value;
- if (IFDEBUG(OSStatus rc =) SecCertificateCopyEmailAddresses(cert, &value.aref())) {
- secdebug("csinterp", "cert %p lookup for email failed rc=%ld", cert, rc);
- return false;
- }
- return match(value);
- }
// DN-component single-value match
for (const CertField *cf = certFields; cf->name; cf++)
if (cf->name == key) {
CFRef<CFStringRef> value;
- if (IFDEBUG(OSStatus rc =) SecCertificateCopySubjectComponent(cert, cf->oid, &value.aref())) {
- secdebug("csinterp", "cert %p lookup for DN.%s failed rc=%ld", cert, key.c_str(), rc);
+ if (OSStatus rc = SecCertificateCopySubjectComponent(cert, cf->oid, &value.aref())) {
+ secdebug("csinterp", "cert %p lookup for DN.%s failed rc=%d", cert, key.c_str(), rc);
return false;
}
return match(value);
}
+ // email multi-valued match (any of...)
+ if (key == "email") {
+ CFRef<CFArrayRef> value;
+ if (OSStatus rc = SecCertificateCopyEmailAddresses(cert, &value.aref())) {
+ secdebug("csinterp", "cert %p lookup for email failed rc=%d", cert, rc);
+ return false;
+ }
+ return match(value);
+ }
+
// unrecognized key. Fail but do not abort to promote backward compatibility down the road
secdebug("csinterp", "cert field notation \"%s\" not understood", key.c_str());
return false;
}
+
+bool Requirement::Interpreter::certFieldGeneric(const string &key, const Match &match, SecCertificateRef cert)
+{
+ // the key is actually a (binary) OID value
+ 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)
+{
+ // the key is actually a (binary) OID value
+ 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);
+}
+
+
+//
+// Check the Apple-signed condition
+//
+bool Requirement::Interpreter::appleAnchored()
+{
+ if (SecCertificateRef cert = mContext->cert(anchorCert))
+ if (verifyAnchor(cert, appleAnchorHash())
+#if defined(TEST_APPLE_ANCHOR)
+ || verifyAnchor(cert, testAppleAnchorHash())
+#endif
+ )
+ return true;
+ return false;
+}
+
+bool Requirement::Interpreter::appleSigned()
+{
+ if (appleAnchored())
+ if (SecCertificateRef intermed = mContext->cert(-2)) // first intermediate
+ // first intermediate common name match (exact)
+ if (certFieldValue("subject.CN", Match(appleIntermediateCN, matchEqual), intermed)
+ && certFieldValue("subject.O", Match(appleIntermediateO, matchEqual), intermed))
+ return true;
+ return false;
+}
+
//
// Verify an anchor requirement against the context
break;
case matchEqual:
case matchContains:
- mValue = makeCFString(interp.getString());
+ case matchBeginsWith:
+ case matchEndsWith:
+ case matchLessThan:
+ case matchGreaterThan:
+ case matchLessEqual:
+ case matchGreaterEqual:
+ mValue.take(makeCFString(interp.getString()));
break;
default:
- assert(false);
+ // Assume this (unknown) match type has a single data argument.
+ // This gives us a chance to keep the instruction stream aligned.
+ interp.getString(); // discard
break;
}
}
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:
- assert(false);
+ // unrecognized match types can never match
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;
+}
+
+
+//
+// External fragments
+//
+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); // was prevalidated as Requirement
+ return req->validates(ctx);
+ }
+ return false;
+}
+
+
+CFDataRef Fragments::fragment(const char *type, const std::string &name)
+{
+ string key = name + "!!" + type; // compound key
+ StLock<Mutex> _(mLock); // lock for cache access
+ FragMap::const_iterator it = mFragments.find(key);
+ if (it == mFragments.end()) {
+ CFRef<CFDataRef> fragData; // will always be set (NULL on any errors)
+ if (CFRef<CFURLRef> fragURL = CFBundleCopyResourceURL(mMyBundle, CFTempString(name), CFSTR("csreq"), CFTempString(type)))
+ if (CFRef<CFDataRef> data = cfLoadFile(fragURL)) { // got data
+ const Requirement *req = (const Requirement *)CFDataGetBytePtr(data);
+ if (req->validateBlob(CFDataGetLength(data))) // looks like a Requirement...
+ fragData = data; // ... so accept it
+ 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; // cache it, success or failure
+ return fragData;
+ }
+ CODESIGN_EVAL_REQINT_FRAGMENT_HIT(type, name.c_str());
+ return it->second;
+}
+
+
} // CodeSigning
} // Security