X-Git-Url: https://git.saurik.com/apple/libsecurity_codesigning.git/blobdiff_plain/f684277642892bee670ae661b409d00168204e90..935e692843d9c528f9a4c5eee98e00961ca5f4a4:/lib/reqinterp.cpp diff --git a/lib/reqinterp.cpp b/lib/reqinterp.cpp index b661b17..5e1567d 100644 --- a/lib/reqinterp.cpp +++ b/lib/reqinterp.cpp @@ -25,27 +25,56 @@ // reqinterp - Requirement language (exprOp) interpreter // #include "reqinterp.h" +#include "codesigning_dtrace.h" #include #include #include -#include // for hex encoding +#include #include "csutilities.h" namespace Security { namespace CodeSigning { -static CFStringRef appleIntermediateCN = CFSTR("Apple Code Signing Certification Authority"); -static CFStringRef appleIntermediateO = CFSTR("Apple Inc."); +// +// Fragment fetching, caching, and evaluation. +// +// 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 > FragMap; + +private: + CFBundleRef mMyBundle; // Security.framework bundle + Mutex mLock; // lock for all of the below... + FragMap mFragments; // cached fragments +}; + +static ModuleNexus fragments; // -// Construct an interpreter given a Requirement and an evaluation context. +// Magic certificate features // -Requirement::Interpreter::Interpreter(const Requirement *req, const Context *ctx) - : Reader(req), mContext(ctx) -{ -} +static CFStringRef appleIntermediateCN = CFSTR("Apple Code Signing Certification Authority"); +static CFStringRef appleIntermediateO = CFSTR("Apple Inc."); // @@ -57,15 +86,18 @@ Requirement::Interpreter::Interpreter(const Requirement *req, const Context *ctx bool Requirement::Interpreter::evaluate() { ExprOp op = ExprOp(get()); + 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: return appleSigned(); + case opAppleGenericAnchor: + return appleAnchored(); case opAnchorHash: { SecCertificateRef cert = mContext->cert(get()); @@ -77,15 +109,16 @@ bool Requirement::Interpreter::evaluate() 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: @@ -94,6 +127,12 @@ bool Requirement::Interpreter::evaluate() 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()); @@ -101,20 +140,38 @@ bool Requirement::Interpreter::evaluate() Match match(*this); return certFieldValue(key, match, cert); } + case opCertGeneric: + { + SecCertificateRef cert = mContext->cert(get()); + string key = getString(); + Match match(*this); + return certFieldGeneric(key, match, cert); + } + case opCertPolicy: + { + SecCertificateRef cert = mContext->cert(get()); + string key = getString(); + Match match(*this); + return certFieldPolicy(key, match, cert); + } case opTrustedCert: return trustedCert(get()); 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()); 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(); } } @@ -137,6 +194,18 @@ bool Requirement::Interpreter::infoKeyValue(const string &key, const Match &matc } +// +// 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 @@ -152,44 +221,75 @@ bool Requirement::Interpreter::certFieldValue(const string &key, const Match &ma { "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 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 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 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::appleSigned() +bool Requirement::Interpreter::appleAnchored() { if (SecCertificateRef cert = mContext->cert(anchorCert)) if (verifyAnchor(cert, appleAnchorHash()) @@ -197,11 +297,18 @@ bool Requirement::Interpreter::appleSigned() || verifyAnchor(cert, testAppleAnchorHash()) #endif ) - if (SecCertificateRef intermed = mContext->cert(-2)) // first intermediate - // first intermediate common name match (exact) - if (certFieldValue("subject.CN", Match(appleIntermediateCN, matchEqual), intermed) + 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 true; return false; } @@ -327,10 +434,18 @@ Requirement::Interpreter::Match::Match(Interpreter &interp) 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; } } @@ -366,12 +481,93 @@ bool Requirement::Interpreter::Match::operator () (CFTypeRef candidate) const 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 _(mLock); // lock for cache access + FragMap::const_iterator it = mFragments.find(key); + if (it == mFragments.end()) { + CFRef fragData; // will always be set (NULL on any errors) + if (CFRef fragURL = CFBundleCopyResourceURL(mMyBundle, CFTempString(name), CFSTR("csreq"), CFTempString(type))) + if (CFRef 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