]> git.saurik.com Git - apple/libsecurity_codesigning.git/blobdiff - lib/reqinterp.cpp
libsecurity_codesigning-55037.6.tar.gz
[apple/libsecurity_codesigning.git] / lib / reqinterp.cpp
index b661b178ba9910af053f99be03cb020354236f10..5e1567d58961fdcda7646f4a6094f907e198a644 100644 (file)
 // 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 {
 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<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;
 
 
 //
-// 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<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:
                return appleSigned();
+       case opAppleGenericAnchor:
+               return appleAnchored();
        case opAnchorHash:
                {
                        SecCertificateRef cert = mContext->cert(get<int32_t>());
@@ -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<int32_t>());
@@ -101,20 +140,38 @@ bool Requirement::Interpreter::evaluate()
                        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();
                        }
                }
@@ -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<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::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<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