X-Git-Url: https://git.saurik.com/apple/security.git/blobdiff_plain/5dd5f9ec28f304ca377c42fd7f711d6cf12b90e1..5c19dc3ae3bd8e40a9c028b0deddd50ff337692c:/Security/libsecurity_codesigning/lib/reqinterp.cpp diff --git a/Security/libsecurity_codesigning/lib/reqinterp.cpp b/Security/libsecurity_codesigning/lib/reqinterp.cpp deleted file mode 100644 index b492acd0..00000000 --- a/Security/libsecurity_codesigning/lib/reqinterp.cpp +++ /dev/null @@ -1,573 +0,0 @@ -/* - * 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 -#include -#include -#include -#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 > FragMap; - -private: - CFBundleRef mMyBundle; // Security.framework bundle - Mutex mLock; // lock for all of the below... - FragMap mFragments; // cached fragments -}; - -static ModuleNexus 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()); - 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()); - 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()); - string key = getString(); - 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) { - 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 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 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()) { - 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 _(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