--- /dev/null
+/*
+ * Copyright (c) 2006,2011-2014 Apple Inc. All Rights Reserved.
+ *
+ * @APPLE_LICENSE_HEADER_START@
+ *
+ * This file contains Original Code and/or Modifications of Original Code
+ * as defined in and that are subject to the Apple Public Source License
+ * Version 2.0 (the 'License'). You may not use this file except in
+ * compliance with the License. Please obtain a copy of the License at
+ * http://www.opensource.apple.com/apsl/ and read it before using this
+ * file.
+ *
+ * The Original Code and all software distributed under the License are
+ * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
+ * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
+ * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
+ * Please see the License for the specific language governing rights and
+ * limitations under the License.
+ *
+ * @APPLE_LICENSE_HEADER_END@
+ */
+
+//
+// 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_utilities/logging.h>
+#include "csutilities.h"
+
+namespace Security {
+namespace CodeSigning {
+
+
+//
+// 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;
+
+
+//
+// Magic certificate features
+//
+static CFStringRef appleIntermediateCN = CFSTR("Apple Code Signing Certification Authority");
+static CFStringRef appleIntermediateO = CFSTR("Apple Inc.");
+
+
+//
+// Main interpreter function.
+//
+// ExprOp code is in Polish Notation (operator followed by operands),
+// and this engine uses opportunistic evaluation.
+//
+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 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: // [legacy; use opInfoKeyField]
+ {
+ string key = getString();
+ return infoKeyValue(key, Match(CFTempString(getString()), matchEqual));
+ }
+ case opAnd:
+ return evaluate() & evaluate();
+ case opOr:
+ 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:
+ {
+ 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);
+ }
+ 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) {
+ CODESIGN_EVAL_REQINT_UNKNOWN_FALSE(op);
+ return false;
+ } else {
+ CODESIGN_EVAL_REQINT_UNKNOWN_SKIPPED(op);
+ return evaluate();
+ }
+ }
+ // unrecognized opcode and no way to interpret it
+ secdebug("csinterp", "opcode 0x%x cannot be handled; aborting", op);
+ MacOSError::throwMe(errSecCSUnimplemented);
+ }
+}
+
+
+//
+// Evaluate an Info.plist key condition
+//
+bool Requirement::Interpreter::infoKeyValue(const string &key, const Match &match)
+{
+ if (mContext->info) // we have an Info.plist
+ if (CFTypeRef value = CFDictionaryGetValue(mContext->info, CFTempString(key)))
+ return match(value);
+ return false;
+}
+
+
+//
+// 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
+ if (cert == NULL)
+ return false;
+
+ // a table of recognized keys for the "certificate[foo]" syntax
+ 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.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 }
+ };
+
+ // DN-component single-value match
+ for (const CertField *cf = certFields; cf->name; cf++)
+ if (cf->name == key) {
+ CFRef<CFStringRef> value;
+ if (OSStatus rc = SecCertificateCopySubjectComponent(cert, cf->oid, &value.aref())) {
+ secdebug("csinterp", "cert %p lookup for DN.%s failed rc=%d", cert, key.c_str(), (int)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, (int)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 (isAppleCA(cert)
+#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
+//
+bool Requirement::Interpreter::verifyAnchor(SecCertificateRef cert, const unsigned char *digest)
+{
+ // get certificate bytes
+ if (cert) {
+ CSSM_DATA certData;
+ MacOSError::check(SecCertificateGetData(cert, &certData));
+
+ // verify hash
+ //@@@ should get SHA1(cert(-1).data) precalculated during chain verification
+ SHA1 hasher;
+ hasher(certData.Data, certData.Length);
+ return hasher.verify(digest);
+ }
+ return false;
+}
+
+
+//
+// Check one or all certificate(s) in the cert chain against the Trust Settings database.
+//
+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;
+}
+
+
+//
+// Explicitly check one certificate against the Trust Settings database and report
+// the findings. This is a helper for the various Trust Settings evaluators.
+//
+SecTrustSettingsResult Requirement::Interpreter::trustSetting(SecCertificateRef cert, bool isAnchor)
+{
+ // the SPI input is the uppercase hex form of the SHA-1 of the certificate...
+ 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);
+
+ // call Trust Settings and see what it finds
+ SecTrustSettingsDomain domain;
+ SecTrustSettingsResult result;
+ CSSM_RETURN *errors = NULL;
+ uint32 errorCount = 0;
+ bool foundMatch, foundAny;
+ switch (OSStatus rc = SecTrustSettingsEvaluateCert(
+ CFTempString(Certhex), // settings index
+ &CSSMOID_APPLE_TP_CODE_SIGNING, // standard code signing policy
+ NULL, 0, // policy string (unused)
+ kSecTrustSettingsKeyUseAny, // no restriction on key usage @@@
+ isAnchor, // consult system default anchor set
+
+ &domain, // domain of found setting
+ &errors, &errorCount, // error set and maximum count
+ &result, // the actual setting
+ &foundMatch, &foundAny // optimization hints (not used)
+ )) {
+ case errSecSuccess:
+ ::free(errors);
+ if (foundMatch)
+ return result;
+ else
+ return kSecTrustSettingsResultUnspecified;
+ default:
+ ::free(errors);
+ MacOSError::throwMe(rc);
+ }
+}
+
+
+//
+// Create a Match object from the interpreter stream
+//
+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:
+ // 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;
+ }
+}
+
+
+//
+// Execute a match against a candidate value
+//
+bool Requirement::Interpreter::Match::operator () (CFTypeRef candidate) const
+{
+ // null candidates always fail
+ if (!candidate)
+ return false;
+
+ // interpret an array as matching alternatives (any one succeeds)
+ if (CFGetTypeID(candidate) == CFArrayGetTypeID()) {
+ CFArrayRef array = CFArrayRef(candidate);
+ CFIndex count = CFArrayGetCount(array);
+ for (CFIndex n = 0; n < count; n++)
+ if ((*this)(CFArrayGetValueAtIndex(array, n))) // yes, it's recursive
+ return true;
+ }
+
+ switch (mOp) {
+ case matchExists: // anything but NULL and boolean false "exists"
+ return !CFEqual(candidate, kCFBooleanFalse);
+ case matchEqual: // equality works for all CF types
+ 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:
+ // 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