X-Git-Url: https://git.saurik.com/apple/security.git/blobdiff_plain/72a12576750f52947eb043106ba5c12c0d07decf..b1ab9ed8d0e0f1c3b66d7daa8fd5564444c56195:/libsecurity_codesigning/lib/reqinterp.cpp diff --git a/libsecurity_codesigning/lib/reqinterp.cpp b/libsecurity_codesigning/lib/reqinterp.cpp new file mode 100644 index 00000000..e3e82338 --- /dev/null +++ b/libsecurity_codesigning/lib/reqinterp.cpp @@ -0,0 +1,573 @@ +/* + * Copyright (c) 2006 Apple Computer, 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 (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 +// +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 noErr: + ::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