+++ /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