]> git.saurik.com Git - apple/security.git/blobdiff - Security/libsecurity_codesigning/lib/reqinterp.cpp
Security-57031.1.35.tar.gz
[apple/security.git] / Security / libsecurity_codesigning / lib / reqinterp.cpp
diff --git a/Security/libsecurity_codesigning/lib/reqinterp.cpp b/Security/libsecurity_codesigning/lib/reqinterp.cpp
new file mode 100644 (file)
index 0000000..b492acd
--- /dev/null
@@ -0,0 +1,573 @@
+/*
+ * 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