]> git.saurik.com Git - apple/libsecurity_codesigning.git/commitdiff
libsecurity_codesigning-33803.tar.gz mac-os-x-1053 mac-os-x-1054 mac-os-x-1055 mac-os-x-1056 v33803
authorApple <opensource@apple.com>
Fri, 25 Apr 2008 21:49:35 +0000 (21:49 +0000)
committerApple <opensource@apple.com>
Fri, 25 Apr 2008 21:49:35 +0000 (21:49 +0000)
32 files changed:
lib/CSCommon.h
lib/CodeSigner.cpp
lib/CodeSigner.h
lib/SecCode.cpp
lib/SecCode.h
lib/SecCodeSigner.cpp
lib/SecCodeSigner.h
lib/StaticCode.cpp
lib/StaticCode.h
lib/bundlediskrep.cpp
lib/bundlediskrep.h
lib/codedirectory.cpp
lib/codedirectory.h
lib/csutilities.cpp
lib/csutilities.h
lib/diskrep.cpp
lib/diskrep.h
lib/filediskrep.cpp
lib/reqdumper.cpp
lib/reqinterp.cpp
lib/reqinterp.h
lib/reqmaker.cpp
lib/reqmaker.h
lib/requirement.h
lib/resources.cpp
lib/resources.h
lib/security_codesigning.exp
lib/sigblob.cpp
lib/sigblob.h
lib/signer.cpp
libsecurity_codesigning.xcodeproj/project.pbxproj
requirements.grammar

index b1fd1869fcd795befce747c8a5e3bc136c6f303d..5b24b602a2772e3305972ec4a5d12b0755c250c0 100644 (file)
@@ -76,6 +76,8 @@ enum {
        errSecCSHostProtocolNotProxy,           /* host protocol violation - proxy hosting not engaged */
        errSecCSHostProtocolStateError,         /* host protocol violation - invalid guest state change request */
        errSecCSHostProtocolUnrelated,          /* host protocol violation - the given guest is not a guest of the given host */
+       errSecCSInvalidOperation,                       /* requested operation is not valid */
+       errSecCSNotSupported,                           /* operation not supported for this type of code */
 };
 
 
index da3628499b48d5ef2ffe5238ebc4d655939ba45a..1503ea1f1f0bdabe59de4f871214e22dfdb6b582 100644 (file)
@@ -193,6 +193,7 @@ SecCodeSigner::Parser::Parser(SecCodeSigner &state, CFDictionaryRef parameters)
        state.mResourceRules = get<CFDictionaryRef>(kSecCodeSignerResourceRules);
        
        state.mApplicationData = get<CFDataRef>(kSecCodeSignerApplicationData);
+       state.mEntitlementData = get<CFDataRef>(kSecCodeSignerEntitlements);
 }
 
 
index 150d528cc67cc7977933dfe66f831f230d848cc6..f94feb3d860e85a2a45702ad8adac0870cb8e7a2 100644 (file)
@@ -67,6 +67,7 @@ private:
        CFRef<CFDictionaryRef> mResourceRules; // explicit resource collection rules (override)
        CFRef<CFDateRef> mSigningTime;  // signing time desired (kCFNull for none)
        CFRef<CFDataRef> mApplicationData; // contents of application slot
+       CFRef<CFDataRef> mEntitlementData; // entitlement configuration data
        const Requirements *mRequirements; // internal code requirements
        size_t mCMSSize;                                // size estimate for CMS blob
        uint32_t mCdFlags;                              // CodeDirectory flags
index 9eaa69f29b433fd548817366c89e2f97ea2cd796..eccd39693f53d73e1dbc9d8f8646d49ed6e25af1 100644 (file)
@@ -199,6 +199,7 @@ const CFStringRef kSecCodeInfoCertificates =        CFSTR("certificates");
 const CFStringRef kSecCodeInfoChangedFiles =   CFSTR("changed-files");
 const CFStringRef kSecCodeInfoCMS =                            CFSTR("cms");
 const CFStringRef kSecCodeInfoDesignatedRequirement = CFSTR("designated-requirement");
+const CFStringRef kSecCodeInfoEntitlements =   CFSTR("entitlements");
 const CFStringRef kSecCodeInfoTime =                   CFSTR("signing-time");
 const CFStringRef kSecCodeInfoFormat =                 CFSTR("format");
 const CFStringRef kSecCodeInfoIdentifier =             CFSTR("identifier");
@@ -206,6 +207,7 @@ const CFStringRef kSecCodeInfoImplicitDesignatedRequirement = CFSTR("implicit-re
 const CFStringRef kSecCodeInfoMainExecutable = CFSTR("main-executable");
 const CFStringRef kSecCodeInfoPList =                  CFSTR("info-plist");
 const CFStringRef kSecCodeInfoRequirements =   CFSTR("requirements");
+const CFStringRef kSecCodeInfoRequirementData =        CFSTR("requirement-data");
 const CFStringRef kSecCodeInfoStatus =                 CFSTR("status");
 const CFStringRef kSecCodeInfoTrust =                  CFSTR("trust");
 
index 471de304ea3ee2579c7394c19e224e573ec672dd..697c89de36caa98c1d7ac58915a93ae73d472d36 100644 (file)
@@ -291,17 +291,19 @@ OSStatus SecCodeCopyDesignatedRequirement(SecStaticCodeRef code, SecCSFlags flag
                regardless; you may specify kSecCSDefaultFlags for just those.
        @param information A CFDictionary containing information about the code is stored
                here on successful completion. The contents of the dictionary depend on
-               the flags passed. Regardless of flags, the kSecCodeInfoIdentifier key is always present
-               if the code is signed, and absent if the code is entirely unsigned.
+               the flags passed. Regardless of flags, the kSecCodeInfoIdentifier key is
+               always present if the code is signed, and always absent if the code is
+               unsigned.
        @result On success, noErr. On error, an OSStatus value
        documented in CSCommon.h or certain other Security framework headers.
        
        @constant kSecCSSigningInformation Return cryptographic signing information,
-               including the certificate chain and CMS data.
+               including the certificate chain and CMS data (if any). For ad-hoc signed
+               code, there are no certificates and the CMS data is empty.
        @constant kSecCSRequirementInformation Return information about internal code
-               requirements embedded in the code.
+               requirements embedded in the code. This includes the Designated Requirement.
        @constant kSecCSInternalInformation Return internal code signing information.
-               This information is for use by Apple, and is subject to change.
+               This information is for use by Apple, and is subject to change without notice.
                It will not be further documented here.
        @constant kSecCSDynamicInformation Return dynamic validity information about
                the Code. The subject code must be a SecCodeRef (not a SecStaticCodeRef).
@@ -324,12 +326,14 @@ extern const CFStringRef kSecCodeInfoChangedFiles;        /* Content */
 extern const CFStringRef kSecCodeInfoCMS;                      /* Signing */
 extern const CFStringRef kSecCodeInfoTime;                     /* Signing */
 extern const CFStringRef kSecCodeInfoDesignatedRequirement; /* Requirement */
+extern const CFStringRef kSecCodeInfoEntitlements;     /* Requirement */
 extern const CFStringRef kSecCodeInfoFormat;           /* generic */
 extern const CFStringRef kSecCodeInfoIdentifier;       /* generic */
 extern const CFStringRef kSecCodeInfoImplicitDesignatedRequirement; /* Requirement */
 extern const CFStringRef kSecCodeInfoMainExecutable; /* generic */
 extern const CFStringRef kSecCodeInfoPList;                    /* generic */
 extern const CFStringRef kSecCodeInfoRequirements;     /* Requirement */
+extern const CFStringRef kSecCodeInfoRequirementData; /* Requirement */
 extern const CFStringRef kSecCodeInfoStatus;           /* Dynamic */
 extern const CFStringRef kSecCodeInfoTrust;                    /* Signing */
 
index b6d3b4569a2c6a945f27a65cfb5cad73a2a175c0..6e752a408e80b7380310a20101b21900e7fb0858 100644 (file)
@@ -41,6 +41,7 @@ using namespace CodeSigning;
 const CFStringRef kSecCodeSignerApplicationData = CFSTR("application-specific");
 const CFStringRef kSecCodeSignerDetached =             CFSTR("detached");
 const CFStringRef kSecCodeSignerDryRun =               CFSTR("dryrun");
+const CFStringRef kSecCodeSignerEntitlements = CFSTR("entitlements");
 const CFStringRef kSecCodeSignerFlags =                        CFSTR("flags");
 const CFStringRef kSecCodeSignerIdentifier =   CFSTR("identifier");
 const CFStringRef kSecCodeSignerIdentifierPrefix = CFSTR("identifier-prefix");
index 5d72ce47a3f3b12d1b0fd8fc41c65d409af0952a..32c0abb1c33b0ed3e9714ba1467b604ab91e1391 100644 (file)
@@ -61,6 +61,7 @@ CFTypeID SecCodeSignerGetTypeID(void);
 extern const CFStringRef kSecCodeSignerApplicationData;
 extern const CFStringRef kSecCodeSignerDetached;
 extern const CFStringRef kSecCodeSignerDryRun;
+extern const CFStringRef kSecCodeSignerEntitlements;
 extern const CFStringRef kSecCodeSignerFlags;
 extern const CFStringRef kSecCodeSignerIdentifier;
 extern const CFStringRef kSecCodeSignerIdentifierPrefix;
index 78d7208d07e750e93fd880986fc6364cb68347b2..9984fac71ac0fb199b68c8ee016be469f238a10c 100644 (file)
@@ -35,6 +35,7 @@
 #include <CoreFoundation/CFURLAccess.h>
 #include <Security/SecPolicyPriv.h>
 #include <Security/SecTrustPriv.h>
+#include <Security/SecCertificatePriv.h>
 #include <Security/CMSPrivate.h>
 #include <Security/SecCmsContentInfo.h>
 #include <Security/SecCmsSignerInfo.h>
@@ -162,6 +163,7 @@ void SecStaticCode::resetValidity()
        for (unsigned n = 0; n < cdSlotCount; n++)
                mCache[n] = NULL;
        mInfoDict = NULL;
+       mEntitlements = NULL;
        mResourceDict = NULL;
        mDesignatedReq = NULL;
        mTrust = NULL;
@@ -491,6 +493,7 @@ void SecStaticCode::validateResources()
        // scan through the resources on disk, checking each against the resourceDirectory
        CollectingContext ctx(*this);           // collect all failures in here
        ResourceBuilder resources(cfString(this->resourceBase()), rules);
+       mRep->adjustResources(resources);
        string path;
        ResourceBuilder::Rule *rule;
 
@@ -539,6 +542,23 @@ CFDictionaryRef SecStaticCode::infoDictionary()
        return mInfoDict;
 }
 
+CFDictionaryRef SecStaticCode::entitlements()
+{
+       if (!mEntitlements) {
+               validateDirectory();
+               if (CFDataRef entitlementData = component(cdEntitlementSlot)) {
+                       validateComponent(cdEntitlementSlot);
+                       const EntitlementBlob *blob = reinterpret_cast<const EntitlementBlob *>(CFDataGetBytePtr(entitlementData));
+                       if (blob->validateBlob()) {
+                               mEntitlements.take(blob->entitlements());
+                               secdebug("staticCode", "%p loaded Entitlements %p", this, mEntitlements.get());
+                       }
+                       // we do not consider a different blob type to be an error. We think it's a new format we don't understand
+               }
+       }
+       return mEntitlements;
+}
+
 CFDictionaryRef SecStaticCode::resourceDictionary()
 {
        if (mResourceDict)      // cached
@@ -727,19 +747,86 @@ const Requirement *SecStaticCode::defaultDesignatedRequirement()
 #if    defined(TEST_APPLE_ANCHOR)
                        || !memcmp(anchorHash, Requirement::testAppleAnchorHash(), SHA1::digestLength)
 #endif
-                       ) {
-                       maker.anchor();         // canonical Apple anchor
-               } else {
-                       // we don't know anything more, so we'll (conservatively) pick the leaf
-                       SHA1::Digest leafHash;
-                       hashOfCertificate(cert(Requirement::leafCert), leafHash);
-                       maker.anchor(Requirement::leafCert, leafHash);
-               }
+                       )
+                       defaultDesignatedAppleAnchor(maker);
+               else
+                       defaultDesignatedNonAppleAnchor(maker);
        }
                
        return maker();
 }
 
+static const uint8_t adcSdkMarker[] = { APPLE_EXTENSION_OID, 2, 1 };
+static const CSSM_DATA adcSdkMarkerOID = { sizeof(adcSdkMarker), (uint8_t *)adcSdkMarker };
+
+void SecStaticCode::defaultDesignatedAppleAnchor(Requirement::Maker &maker)
+{
+       if (isAppleSDKSignature()) {
+               // get the Common Name DN element for the leaf
+               CFRef<CFStringRef> leafCN;
+               MacOSError::check(SecCertificateCopySubjectComponent(cert(Requirement::leafCert),
+                       &CSSMOID_CommonName, &leafCN.aref()));
+               
+               // apple anchor generic and ...
+               maker.put(opAnd);
+               maker.anchorGeneric();                  // apple generic anchor and...
+               // ... leaf[subject.CN] = <leaf's subject> and ...
+               maker.put(opAnd);
+               maker.put(opCertField);                 // certificate
+               maker.put(0);                                   // leaf
+               maker.put("subject.CN");                // [subject.CN]
+               maker.put(matchEqual);                  // =
+               maker.putData(leafCN);                  // <leaf CN>
+               // ... cert 1[field.<marker>] exists
+               maker.put(opCertGeneric);               // certificate
+               maker.put(1);                                   // 1
+               maker.putData(adcSdkMarkerOID.Data, adcSdkMarkerOID.Length); // [field.<marker>]
+               maker.put(matchExists);                 // exists
+               return;
+       }
+
+       // otherwise, claim this program for Apple
+       maker.anchor();
+}
+
+bool SecStaticCode::isAppleSDKSignature()
+{
+       if (CFArrayRef certChain = certificates())              // got cert chain
+               if (CFArrayGetCount(certChain) == 3)            // leaf, one intermediate, anchor
+                       if (SecCertificateRef intermediate = cert(1)) // get intermediate
+                               if (certificateHasField(intermediate, CssmOid::overlay(adcSdkMarkerOID)))
+                                       return true;
+       return false;
+}
+
+
+void SecStaticCode::defaultDesignatedNonAppleAnchor(Requirement::Maker &maker)
+{
+       // get the Organization DN element for the leaf
+       CFRef<CFStringRef> leafOrganization;
+       MacOSError::check(SecCertificateCopySubjectComponent(cert(Requirement::leafCert),
+               &CSSMOID_OrganizationName, &leafOrganization.aref()));
+
+       // now step up the cert chain looking for the first cert with a different one
+       int slot = Requirement::leafCert;                                               // start at leaf
+       if (leafOrganization) {
+               while (SecCertificateRef ca = cert(slot+1)) {           // NULL if you over-run the anchor slot
+                       CFRef<CFStringRef> caOrganization;
+                       MacOSError::check(SecCertificateCopySubjectComponent(ca, &CSSMOID_OrganizationName, &caOrganization.aref()));
+                       if (CFStringCompare(leafOrganization, caOrganization, 0) != kCFCompareEqualTo)
+                               break;
+                       slot++;
+               }
+               if (slot == CFArrayGetCount(mCertChain) - 1)            // went all the way to the anchor...
+                       slot = Requirement::anchorCert;                                 // ... so say that
+       }
+               
+       // nail the last cert with the leaf's Organization value
+       SHA1::Digest authorityHash;
+       hashOfCertificate(cert(slot), authorityHash);
+       maker.anchor(slot, authorityHash);
+}
+
 
 //
 // Validate a SecStaticCode against the internal requirement of a particular type.
@@ -768,7 +855,7 @@ void SecStaticCode::validateRequirements(const Requirement *req, OSStatus failur
 {
        assert(req);
        validateDirectory();
-       req->validate(Requirement::Context(mCertChain, infoDictionary(), codeDirectory()), failure);
+       req->validate(Requirement::Context(mCertChain, infoDictionary(), entitlements(), codeDirectory()), failure);
 }
 
 
@@ -783,10 +870,11 @@ SecCertificateRef SecStaticCode::cert(int ix)
 {
        validateDirectory();            // need cert chain
        if (mCertChain) {
+               CFIndex length = CFArrayGetCount(mCertChain);
                if (ix < 0)
-                       ix += CFArrayGetCount(mCertChain);
-               if (CFTypeRef element = CFArrayGetValueAtIndex(mCertChain, ix))
-                       return SecCertificateRef(element);
+                       ix += length;
+               if (ix >= 0 && ix < length)
+                       return SecCertificateRef(CFArrayGetValueAtIndex(mCertChain, ix));
        }
        return NULL;
 }
@@ -852,10 +940,11 @@ CFDictionaryRef SecStaticCode::signingInformation(SecCSFlags flags)
                if (const Requirements *reqs = internalRequirements()) {
                        CFDictionaryAddValue(dict, kSecCodeInfoRequirements,
                                CFTempString(Dumper::dump(reqs)));
+                       CFDictionaryAddValue(dict, kSecCodeInfoRequirementData, CFTempData(*reqs));
                }
+               const Requirement *dreq = designatedRequirement();
                const Requirement *ddreq = defaultDesignatedRequirement();
                CFRef<SecRequirementRef> ddreqRef = (new SecRequirement(ddreq))->handle();
-               const Requirement *dreq = designatedRequirement();
                if (dreq == ddreq) {
                        CFDictionaryAddValue(dict, kSecCodeInfoDesignatedRequirement, ddreqRef);
                        CFDictionaryAddValue(dict, kSecCodeInfoImplicitDesignatedRequirement, ddreqRef);
@@ -864,6 +953,9 @@ CFDictionaryRef SecStaticCode::signingInformation(SecCSFlags flags)
                                CFRef<SecRequirementRef>((new SecRequirement(dreq))->handle()));
                        CFDictionaryAddValue(dict, kSecCodeInfoImplicitDesignatedRequirement, ddreqRef);
                }
+               
+               if (CFDataRef ent = component(cdEntitlementSlot))
+                       CFDictionaryAddValue(dict, kSecCodeInfoEntitlements, ent);
        }
        
        //
@@ -875,7 +967,9 @@ CFDictionaryRef SecStaticCode::signingInformation(SecCSFlags flags)
        if (flags & kSecCSInternalInformation) {
                if (mDir)
                        CFDictionaryAddValue(dict, CFSTR("CodeDirectory"), mDir);
-                       CFDictionaryAddValue(dict, CFSTR("CodeOffset"), CFTempNumber(mRep->signingBase()));
+               CFDictionaryAddValue(dict, CFSTR("CodeOffset"), CFTempNumber(mRep->signingBase()));
+               if (CFDictionaryRef resources = resourceDictionary())
+                       CFDictionaryAddValue(dict, CFSTR("ResourceDirectory"), resources);
        }
        
        
index 49d7d51cbfad014a46f3c82c5ed919cd6586ef58..7aed254ac86396caa678fcc85ee3a485de64076a 100644 (file)
@@ -100,6 +100,7 @@ public:
        std::string format() const { return mRep->format(); }
        CFDataRef component(CodeDirectory::SpecialSlot slot);
        CFDictionaryRef infoDictionary();
+       CFDictionaryRef entitlements();
 
        CFDictionaryRef resourceDictionary();
        CFURLRef resourceBase();
@@ -138,6 +139,10 @@ protected:
        bool verifySignature();
        SecPolicyRef verificationPolicy();
 
+       void defaultDesignatedAppleAnchor(Requirement::Maker &maker);
+       void defaultDesignatedNonAppleAnchor(Requirement::Maker &maker);
+       bool isAppleSDKSignature();
+
        static void checkOptionalResource(CFTypeRef key, CFTypeRef value, void *context);
 
 private:
@@ -160,6 +165,7 @@ private:
        
        // alternative cache forms (storage may depend on cached contents above)
        CFRef<CFDictionaryRef> mInfoDict;       // derived from mCache slot
+       CFRef<CFDictionaryRef> mEntitlements; // derived from mCache slot
        CFRef<CFDictionaryRef> mResourceDict; // derived from mCache slot
        const Requirement *mDesignatedReq;      // cached designated req if we made one up
        
index ad4d8195f42f29d3bcea4c55ce160ad21c7ee4f4..ffaa4789a94fca1b556a8eee1ae6ef8726a914a7 100644 (file)
@@ -24,6 +24,7 @@
 #include <CoreFoundation/CFURLAccess.h>
 #include <CoreFoundation/CFBundlePriv.h>
 #include <security_codesigning/cfmunge.h>
+#include <copyfile.h>
 
 
 namespace Security {
@@ -36,7 +37,7 @@ using namespace UnixPlusPlus;
 // We make a CFBundleRef immediately, but everything else is lazy
 //
 BundleDiskRep::BundleDiskRep(const char *path)
-       : mBundle(_CFBundleCreateIfLooksLikeBundle(NULL, CFTempURL(path)))
+       : mBundle(_CFBundleCreateIfMightBeBundle(NULL, CFTempURL(path)))
 {
        if (!mBundle)
                MacOSError::throwMe(errSecCSBadObjectFormat);
@@ -52,31 +53,40 @@ BundleDiskRep::BundleDiskRep(CFBundleRef ref)
 
 //
 // Create a path to a bundle signing resource, by name.
-// Note that these are stored in the bundle's Content directory,
-// not its Resources directory.
+// If the BUNDLEDISKREP_DIRECTORY directory exists in the bundle's support directory, files
+// will be read and written there. Otherwise, they go directly into the support directory.
 //
-string BundleDiskRep::resourcePath(const char *name)
+string BundleDiskRep::metaPath(const char *name)
 {
-       if (mResourcePath.empty())
-               mResourcePath = cfString(CFBundleCopySupportFilesDirectoryURL(mBundle), true);
-       return mResourcePath + "/" + name;
+       if (mMetaPath.empty()) {
+               string support = cfString(CFBundleCopySupportFilesDirectoryURL(mBundle), true);
+               mMetaPath = support + "/" BUNDLEDISKREP_DIRECTORY;
+               if (::access(mMetaPath.c_str(), F_OK) == 0) {
+                       mMetaExists = true;
+               } else {
+                       mMetaPath = support;
+                       mMetaExists = false;
+               }
+       }
+       return mMetaPath + "/" + name;
 }
 
 
 //
-// Load the data for a signing resource, by URL.
+// Try to create the meta-file directory in our bundle.
+// Does nothing if the directory already exists.
+// Throws if an error occurs.
 //
-CFDataRef BundleDiskRep::resourceData(CFURLRef url)
+void BundleDiskRep::createMeta()
 {
-       CFDataRef data;
-       SInt32 error;
-       if (CFURLCreateDataAndPropertiesFromResource(NULL, url,
-               &data, NULL, NULL, &error)) {
-               return data;
-       } else {
-               secdebug("bundlerep", "failed to fetch %s error=%d",
-                       cfString(url).c_str(), int(error));
-               return NULL;
+       string meta = metaPath(BUNDLEDISKREP_DIRECTORY);
+       if (!mMetaExists) {
+               if (::mkdir(meta.c_str(), 0755) == 0) {
+                       copyfile(cfString(canonicalPath(), true).c_str(), meta.c_str(), NULL, COPYFILE_SECURITY);
+                       mMetaPath = meta;
+                       mMetaExists = true;
+               } else if (errno != EEXIST)
+                       UnixError::throwMe();
        }
 }
 
@@ -94,7 +104,7 @@ CFDataRef BundleDiskRep::component(CodeDirectory::SpecialSlot slot)
        // the Info.plist comes from the magic CFBundle-indicated place and ONLY from there
        case cdInfoSlot:
                if (CFRef<CFURLRef> info = _CFBundleCopyInfoPlistURL(mBundle))
-                       return resourceData(info);
+                       return cfLoadFile(info);
                else
                        return NULL;
        // by default, we take components from the executable image or files
@@ -105,7 +115,7 @@ CFDataRef BundleDiskRep::component(CodeDirectory::SpecialSlot slot)
        // but the following always come from files
        case cdResourceDirSlot:
                if (const char *name = CodeDirectory::canonicalSlotName(slot))
-                       return resourceData(name);
+                       return metaData(name);
                else
                        return NULL;
        }
@@ -129,7 +139,7 @@ string BundleDiskRep::recommendedIdentifier()
                        return cfString(identifier);
        
        // fall back to using the $(basename) of the canonical path. Drop any .app suffix
-       string path = cfString(this->canonicalPath());
+       string path = cfString(this->canonicalPath(), true);
        if (path.substr(path.size() - 4) == ".app")
                path = path.substr(0, path.size() - 4);
        string::size_type p = path.rfind('/');
@@ -162,6 +172,19 @@ CFDictionaryRef BundleDiskRep::defaultResourceRules()
                "}}");
 }
 
+void BundleDiskRep::adjustResources(ResourceBuilder &builder)
+{
+       // exclude entire contents of meta directory
+       builder.addExclusion("^" BUNDLEDISKREP_DIRECTORY "/");
+       
+       // exclude the main executable file
+       string resources = resourcesRootPath();
+       string executable = mainExecutablePath();
+       if (!executable.compare(0, resources.length(), resources, 0, resources.length()))       // is prefix
+               builder.addExclusion(string("^") + executable.substr(resources.length() + 1) + "$");
+}
+
+
 const Requirements *BundleDiskRep::defaultRequirements(const Architecture *arch)
 {
        return mExecRep->defaultRequirements(arch);
@@ -199,6 +222,7 @@ CFArrayRef BundleDiskRep::modifiedFiles()
        checkModifiedFile(files, cdCodeDirectorySlot);
        checkModifiedFile(files, cdSignatureSlot);
        checkModifiedFile(files, cdResourceDirSlot);
+       checkModifiedFile(files, cdEntitlementSlot);
        return files;
 }
 
@@ -206,10 +230,11 @@ void BundleDiskRep::checkModifiedFile(CFMutableArrayRef files, CodeDirectory::Sp
 {
        if (CFDataRef data = mExecRep->component(slot)) // provided by executable file
                CFRelease(data);
-       else if (const char *resourceName = CodeDirectory::canonicalSlotName(slot)) // bundle file
-               CFArrayAppendValue(files, CFTempURL(resourcePath(resourceName)));
-       else
-               /* we don't have that one */;
+       else if (const char *resourceName = CodeDirectory::canonicalSlotName(slot)) {
+               string file = metaPath(resourceName);
+               if (::access(file.c_str(), F_OK) == 0)
+                       CFArrayAppendValue(files, CFTempURL(file));
+       }
 }
 
 FileDesc &BundleDiskRep::fd()
@@ -232,7 +257,7 @@ DiskRep::Writer *BundleDiskRep::writer()
 }
 
 BundleDiskRep::Writer::Writer(BundleDiskRep *r)
-       : rep(r)
+       : rep(r), mMadeMetaDirectory(false)
 {
        execWriter = rep->mExecRep->writer();
 }
@@ -241,7 +266,7 @@ BundleDiskRep::Writer::Writer(BundleDiskRep *r)
 //
 // Write a component.
 // Note that this isn't concerned with Mach-O writing; this is handled at
-// a much higher level. If we're called, we write to a file in the Bundle's contents directory.
+// a much higher level. If we're called, we write to a file in the Bundle's meta directory.
 //
 void BundleDiskRep::Writer::component(CodeDirectory::SpecialSlot slot, CFDataRef data)
 {
@@ -251,11 +276,18 @@ void BundleDiskRep::Writer::component(CodeDirectory::SpecialSlot slot, CFDataRef
                        return execWriter->component(slot, data);       // ... so hand it through
                // execWriter doesn't want the data; store it as a resource file (below)
        case cdResourceDirSlot:
-       case cdRequirementsSlot:
                // the resource directory always goes into a bundle file
                if (const char *name = CodeDirectory::canonicalSlotName(slot)) {
-                       AutoFileDesc fd(rep->resourcePath(name), O_WRONLY | O_CREAT | O_TRUNC);
+                       rep->createMeta();
+                       string path = rep->metaPath(name);
+                       AutoFileDesc fd(path, O_WRONLY | O_CREAT | O_TRUNC, 0644);
                        fd.writeAll(CFDataGetBytePtr(data), CFDataGetLength(data));
+                       if (rep->mMetaExists) {
+                               // leave a symlink in the support directory for pre-10.5.3 compatibility (but ignore errors)
+                               string legacy = cfString(CFBundleCopySupportFilesDirectoryURL(rep->mBundle), true) + "/" + name;
+//                             ::unlink(legacy.c_str());               // force-replace
+                               ::symlink((string(BUNDLEDISKREP_DIRECTORY "/") + name).c_str(), legacy.c_str());
+                       }
                } else
                        MacOSError::throwMe(errSecCSBadObjectFormat);
        }
index 4aae953e2a8356df38b48d7d27b589f12430b3d9..a6ffe2b258f164297228621d5ca51fdc2b695beb 100644 (file)
@@ -34,6 +34,9 @@ namespace Security {
 namespace CodeSigning {
 
 
+#define BUNDLEDISKREP_DIRECTORY                "_CodeSignature"
+
+
 //
 // A BundleDiskRep represents a standard Mac OS X bundle on disk.
 // The bundle is expected to have an Info.plist, and a "main executable file"
@@ -53,6 +56,7 @@ public:
        std::string recommendedIdentifier();
        std::string resourcesRootPath();
        CFDictionaryRef defaultResourceRules();
+       void adjustResources(ResourceBuilder &builder);
        const Requirements *defaultRequirements(const Architecture *arch);
        Universal *mainExecutableImage();
        size_t pageSize();
@@ -71,18 +75,18 @@ public:
        friend class Writer;
        
 protected:
-       CFDataRef resourceData(CFURLRef url);
-       CFDataRef resourceData(const char *name) { return resourceData(CFTempURL(resourcePath(name))); }
-       
-       std::string resourcePath(const char *name);
+       std::string metaPath(const char *name);
+       CFDataRef metaData(const char *name) { return cfLoadFile(CFTempURL(metaPath(name))); }
+       void createMeta();                                              // (try to) create the meta-file directory
        
 private:
        void checkModifiedFile(CFMutableArrayRef files, CodeDirectory::SpecialSlot slot);
 
 private:
        CFRef<CFBundleRef> mBundle;
-       std::string mResourcePath;
-       RefPointer<DiskRep> mExecRep;
+       std::string mMetaPath;                                  // path to directory containing signing files
+       bool mMetaExists;                                               // separate meta-file directory exists
+       RefPointer<DiskRep> mExecRep;                   // DiskRep for main executable file
 };
 
 
@@ -104,6 +108,7 @@ protected:
 protected:
        RefPointer<BundleDiskRep> rep;
        RefPointer<DiskRep::Writer> execWriter;
+       bool mMadeMetaDirectory;
 };
 
 
index f9ad55d9e0747ceaeed719f0cbf996b212cae5b1..5ce2ff6a9ae21c6a7ee357124568e0874326b05c 100644 (file)
@@ -53,6 +53,8 @@ const char *CodeDirectory::canonicalSlotName(SpecialSlot slot)
                return kSecCS_SIGNATUREFILE;
        case cdApplicationSlot:
                return kSecCS_APPLICATIONFILE;
+       case cdEntitlementSlot:
+               return kSecCS_ENTITLEMENTFILE;
        default:
                return NULL;
        }
@@ -71,6 +73,8 @@ unsigned CodeDirectory::slotAttributes(SpecialSlot slot)
                return cdComponentPerArchitecture | cdComponentIsBlob;
        case cdSignatureSlot:
                return cdComponentPerArchitecture; // raw
+       case cdEntitlementSlot:
+               return cdComponentIsBlob; // global
        default:
                return 0; // global, raw
        }
index 38cedc2caa1f0abeb6cfeef7fcfaaab2bf02094d..135f2aa81ec016e09b6c4c4723b5e573686c0b75 100644 (file)
@@ -64,6 +64,7 @@ enum {
 #define kSecCS_REQUIREMENTSFILE                "CodeRequirements"      // internal requirements
 #define kSecCS_RESOURCEDIRFILE         "CodeResources"         // resource directory
 #define kSecCS_APPLICATIONFILE         "CodeApplication"       // application-specific resource
+#define kSecCS_ENTITLEMENTFILE         "CodeEntitlements"      // entitlement configuration (just in case)
 
 
 //
@@ -83,6 +84,7 @@ enum {
        cdRequirementsSlot = 2,                         // internal requirements
        cdResourceDirSlot = 3,                          // resource directory
        cdApplicationSlot = 4,                          // Application specific slot
+       cdEntitlementSlot = 5,                          // embedded entitlement configuration
        // (add further primary slot numbers here)
 
        cdSlotCount,                                            // total number of special slots (+1 for slot 0)
index f818b06ad980420020aa8513d388039a201a7a58..8fe0921ef2511a5110da66a8641d6561f4996b09 100644 (file)
@@ -25,6 +25,7 @@
 // csutilities - miscellaneous utilities for the code signing implementation
 //
 #include "csutilities.h"
+#include <Security/SecCertificatePriv.h>
 #include <security_codesigning/requirement.h>
 #include <security_utilities/debugging.h>
 #include <security_utilities/errors.h>
@@ -56,5 +57,41 @@ void hashOfCertificate(SecCertificateRef cert, SHA1::Digest digest)
 }
 
 
+//
+// Check to see if a certificate contains a particular field, by OID. This works for extensions,
+// even ones not recognized by the local CL. It does not return any value, only presence.
+//
+bool certificateHasField(SecCertificateRef cert, const CssmOid &oid)
+{
+       assert(cert);
+       CSSM_DATA *value;
+       switch (OSStatus rc = SecCertificateCopyFirstFieldValue(cert, &oid, &value)) {
+       case noErr:
+               MacOSError::check(SecCertificateReleaseFirstFieldValue(cert, &oid, value));
+               return true;                                    // extension found by oid
+       case CSSMERR_CL_UNKNOWN_TAG:
+               break;                                                  // oid not recognized by CL - continue below
+       default:
+               MacOSError::throwMe(rc);                // error: fail
+       }
+       
+       // check the CL's bag of unrecognized extensions
+       CSSM_DATA **values;
+       bool found = false;
+       if (SecCertificateCopyFieldValues(cert, &CSSMOID_X509V3CertificateExtensionCStruct, &values))
+               return false;   // no unrecognized extensions - no match
+       if (values)
+               for (CSSM_DATA **p = values; *p; p++) {
+                       const CSSM_X509_EXTENSION *ext = (const CSSM_X509_EXTENSION *)(*p)->Data;
+                       if (oid == ext->extnId) {
+                               found = true;
+                               break;
+                       }
+               }
+       MacOSError::check(SecCertificateReleaseFieldValues(cert, &CSSMOID_X509V3CertificateExtensionCStruct, values));
+       return found;
+}
+
+
 } // end namespace CodeSigning
 } // end namespace Security
index ec3b7d9116cfb5d25088a41af3bb4014724db433..13c50742f91bcee0ca483a237b8cd12866daa9d1 100644 (file)
@@ -32,6 +32,7 @@
 
 #include <Security/Security.h>
 #include <security_utilities/hashing.h>
+#include <security_cdsa_utilities/cssmdata.h>
 
 
 namespace Security {
@@ -46,6 +47,13 @@ void hashOfCertificate(const void *certData, size_t certLength, SHA1::Digest dig
 void hashOfCertificate(SecCertificateRef cert, SHA1::Digest digest);
 
 
+//
+// Check to see if a certificate contains a particular field, by OID. This works for extensions,
+// even ones not recognized by the local CL. It does not return any value, only presence.
+//
+bool certificateHasField(SecCertificateRef cert, const CssmOid &oid);
+
+
 } // end namespace CodeSigning
 } // end namespace Security
 
index 0a284907473f64c6beea5a27a4bc9ac0f12d7ff2..5db86a395ffe0c2e7c7e99b576cf93d177be13a2 100644 (file)
@@ -81,6 +81,7 @@ DiskRep::Writer *DiskRep::writer()
 //
 DiskRep *DiskRep::bestGuess(const char *path)
 {
+       try {
     struct stat st;
     if (::stat(path, &st))
         UnixError::throwMe();
@@ -91,11 +92,19 @@ DiskRep *DiskRep::bestGuess(const char *path)
        
        // see if it's the main executable of a recognized bundle
        if (CFRef<CFURLRef> pathURL = makeCFURL(path))
-               if (CFRef<CFBundleRef> bundle = _CFBundleCreateWithExecutableURLIfLooksLikeBundle(NULL, pathURL))
+               if (CFRef<CFBundleRef> bundle = _CFBundleCreateWithExecutableURLIfMightBeBundle(NULL, pathURL))
                                return new BundleDiskRep(bundle);
        
        // follow the file choosing rules
        return bestFileGuess(path);
+       } catch (const CommonError &error) {
+               switch (error.unixError()) {
+               case ENOENT:
+                       MacOSError::throwMe(errSecCSStaticCodeNotFound);
+               default:
+                       throw;
+               }
+       }
 }
 
 
@@ -126,6 +135,11 @@ CFDictionaryRef DiskRep::defaultResourceRules()
        return NULL;    // none
 }
 
+void DiskRep::adjustResources(ResourceBuilder &builder)
+{
+       // do nothing
+}
+
 const Requirements *DiskRep::defaultRequirements(const Architecture *)
 {
        return NULL;    // none
index 001d1a559bab21ad26301e197ee2b35f86e603cb..4442320db2c382608d7d583519037dfa5e345e7f 100644 (file)
@@ -30,6 +30,7 @@
 #include "cs.h"
 #include "codedirectory.h"
 #include "requirement.h"
+#include "resources.h"
 #include "macho++.h"           // for class Architecture
 #include <security_utilities/refcount.h>
 #include <security_utilities/superblob.h>
@@ -56,6 +57,7 @@ public:
        virtual std::string recommendedIdentifier() = 0;                // default identifier
        virtual std::string resourcesRootPath();                                // resource directory if any
        virtual CFDictionaryRef defaultResourceRules();                 // default resource rules
+       virtual void adjustResources(ResourceBuilder &builder); // adjust resource rule set
        virtual const Requirements *defaultRequirements(const Architecture *arch); // default internal requirements
        virtual Universal *mainExecutableImage();                               // binary if Mach-O/Universal
        virtual size_t pageSize();                                                              // default main executable page size
index 6906c74842fae48e08d6bc1c0347724053f8d3a0..9fefe6a5575ac9d3b07577f5ddd5685096dd5a0b 100644 (file)
@@ -90,7 +90,7 @@ const Requirements *FileDiskRep::defaultRequirements(const Architecture *)
 {
        // read start of file
        char buffer[256];
-       size_t length = fd().read(buffer, sizeof(buffer));
+       size_t length = fd().read(buffer, sizeof(buffer), 0);
        if (length > 3 && buffer[0] == '#' && buffer[1] == '!' && buffer[2] == '/') {
                // isolate (full) path element in #!/full/path -some -other -stuff
                if (length == sizeof(buffer))
index 33867de03af8df2b235d800f9cc76b4f9328d02f..8fbd4446c424ad7d8b1bbe7a9345cc34848a3477 100644 (file)
@@ -25,6 +25,7 @@
 // reqdumper - Requirement un-parsing (disassembly)
 //
 #include "reqdumper.h"
+#include <security_cdsa_utilities/cssmdata.h>  // OID encoder
 #include <cstdarg>
 
 namespace Security {
@@ -142,6 +143,9 @@ void Dumper::expr(SyntaxLevel level)
        case opAppleAnchor:
                print("anchor apple");
                break;
+       case opAppleGenericAnchor:
+               print("anchor apple generic");
+               break;
        case opAnchorHash:
                print("anchor"); certSlot(); print(" = "); hashData();
                break;
@@ -179,9 +183,21 @@ void Dumper::expr(SyntaxLevel level)
        case opInfoKeyField:
                print("info["); dotString(); print("]"); match();
                break;
+       case opEntitlementField:
+               print("entitlement["); dotString(); print("]"); match();
+               break;
        case opCertField:
                print("certificate"); certSlot(); print("["); dotString(); print("]"); match();
                break;
+       case opCertGeneric:
+               print("certificate"); certSlot(); print("[");
+               {
+                       const unsigned char *data; size_t length;
+                       getData(data, length);
+                       print("field.%s", CssmOid((unsigned char *)data, length).toOid().c_str());
+               }
+               print("]"); match();
+               break;
        case opTrustedCert:
                print("certificate"); certSlot(); print("trusted");
                break;
@@ -221,7 +237,7 @@ void Dumper::match()
 {
        switch (MatchOperation op = MatchOperation(get<uint32_t>())) {
        case matchExists:
-               print(" /* exists */");
+               print(" exists");
                break;
        case matchEqual:
                print(" = "); data();
@@ -229,6 +245,24 @@ void Dumper::match()
        case matchContains:
                print(" ~ "); data();
                break;
+       case matchBeginsWith:
+               print(" = "); data(); print("*");
+               break;
+       case matchEndsWith:
+               print(" = *"); data();
+               break;
+       case matchLessThan:
+               print(" < "); data();
+               break;
+       case matchGreaterEqual:
+               print(" >= "); data();
+               break;
+       case matchLessEqual:
+               print(" <= "); data();
+               break;
+       case matchGreaterThan:
+               print(" > "); data();
+               break;
        default:
                print("MATCH OPCODE %d NOT UNDERSTOOD", op);
                break;
@@ -259,9 +293,10 @@ void Dumper::data(PrintMode bestMode /* = isSimple */, bool dotOkay /* = false *
                        bestMode = isBinary;
                        break;          // pessimal
                }
+       
        if (length == 0 && bestMode == isSimple)
-               bestMode = isPrintable;         // force quotes for empty string
-               
+               bestMode = isPrintable;     // force quotes for empty string
+
        switch (bestMode) {
        case isSimple:
                print("%.*s", length, data);
index b661b178ba9910af053f99be03cb020354236f10..089154bad8a5f855e719f854563c38e4fb457657 100644 (file)
@@ -28,7 +28,6 @@
 #include <Security/SecTrustSettingsPriv.h>
 #include <Security/SecCertificatePriv.h>
 #include <security_utilities/memutils.h>
-#include <security_cdsa_utilities/cssmdata.h>  // for hex encoding
 #include "csutilities.h"
 
 namespace Security {
@@ -66,6 +65,8 @@ bool Requirement::Interpreter::evaluate()
                return getString() == mContext->directory->identifier();
        case opAppleAnchor:
                return appleSigned();
+       case opAppleGenericAnchor:
+               return appleAnchored();
        case opAnchorHash:
                {
                        SecCertificateRef cert = mContext->cert(get<int32_t>());
@@ -77,9 +78,9 @@ bool Requirement::Interpreter::evaluate()
                        return infoKeyValue(key, Match(CFTempString(getString()), matchEqual));
                }
        case opAnd:
-               return evaluate() && evaluate();
+               return evaluate() & evaluate();
        case opOr:
-               return evaluate() || evaluate();
+               return evaluate() | evaluate();
        case opCDHash:
                {
                        SHA1 hash;
@@ -94,6 +95,12 @@ bool Requirement::Interpreter::evaluate()
                        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>());
@@ -101,6 +108,13 @@ bool Requirement::Interpreter::evaluate()
                        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 opTrustedCert:
                return trustedCert(get<int32_t>());
        case opTrustedCerts:
@@ -137,6 +151,18 @@ bool Requirement::Interpreter::infoKeyValue(const string &key, const Match &matc
 }
 
 
+//
+// 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
@@ -158,16 +184,6 @@ bool Requirement::Interpreter::certFieldValue(const string &key, const Match &ma
                { "subject.STREET", &CSSMOID_StreetAddress },
                { NULL, NULL }
        };
-
-       // email multi-valued match (any of...)
-       if (key == "email") {
-               CFRef<CFArrayRef> value;
-               if (IFDEBUG(OSStatus rc =) SecCertificateCopyEmailAddresses(cert, &value.aref())) {
-                       secdebug("csinterp", "cert %p lookup for email failed rc=%ld", cert, rc);
-                       return false;
-               }
-               return match(value);
-       }
        
        // DN-component single-value match
        for (const CertField *cf = certFields; cf->name; cf++)
@@ -180,16 +196,39 @@ bool Requirement::Interpreter::certFieldValue(const string &key, const Match &ma
                        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=%ld", cert, 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);
+}
+
+
 //
 // Check the Apple-signed condition
 //
-bool Requirement::Interpreter::appleSigned()
+bool Requirement::Interpreter::appleAnchored()
 {
        if (SecCertificateRef cert = mContext->cert(anchorCert))
                if (verifyAnchor(cert, appleAnchorHash())
@@ -197,6 +236,13 @@ bool Requirement::Interpreter::appleSigned()
                        || 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)
@@ -327,10 +373,18 @@ Requirement::Interpreter::Match::Match(Interpreter &interp)
                break;
        case matchEqual:
        case matchContains:
+       case matchBeginsWith:
+       case matchEndsWith:
+       case matchLessThan:
+       case matchGreaterThan:
+       case matchLessEqual:
+       case matchGreaterEqual:
                mValue = makeCFString(interp.getString());
                break;
        default:
-               assert(false);
+               // 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;
        }
 }
@@ -366,12 +420,49 @@ bool Requirement::Interpreter::Match::operator () (CFTypeRef candidate) const
                                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:
-               assert(false);
+               // 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;
+}
+
+
 }      // CodeSigning
 }      // Security
index ebeed8dcd985158d0ed0bb7ccd8e02e63cb76f32..fad9a478ee5bde5b0351e4c9918641acaa015157 100644 (file)
@@ -29,6 +29,7 @@
 
 #include <security_codesigning/reqreader.h>
 #include <Security/SecTrustSettings.h>
+#include <security_cdsa_utilities/cssmdata.h>  // CssmOid
 
 namespace Security {
 namespace CodeSigning {
@@ -49,8 +50,12 @@ protected:
        public:
                Match(Interpreter &interp);             // reads match postfix from interp
                Match(CFStringRef value, MatchOperation op) : mValue(value), mOp(op) { } // explicit
+               Match() : mValue(NULL), mOp(matchExists) { } // explict test for presence
                bool operator () (CFTypeRef candidate) const; // match to candidate
                
+       protected:
+               bool inequality(CFTypeRef candidate, CFStringCompareFlags flags, CFComparisonResult outcome, bool negate) const;
+               
        private:
                CFCopyRef<CFStringRef> mValue;  // match value
                MatchOperation mOp;                             // type of match
@@ -58,13 +63,17 @@ protected:
        
 protected:
        bool infoKeyValue(const std::string &key, const Match &match);
+       bool entitlementValue(const std::string &key, const Match &match);
        bool certFieldValue(const string &key, const Match &match, SecCertificateRef cert);
+       bool certFieldGeneric(const string &key, const Match &match, SecCertificateRef cert);
+       bool certFieldGeneric(const CssmOid &oid, const Match &match, SecCertificateRef cert);
        bool verifyAnchor(SecCertificateRef cert, const unsigned char *digest);
        bool appleSigned();
+       bool appleAnchored();
        bool trustedCerts();
        bool trustedCert(int slot);
        
-       SecTrustSettingsResult trustSetting(SecCertificateRef cert, bool isAnchor);
+       static SecTrustSettingsResult trustSetting(SecCertificateRef cert, bool isAnchor);
        
 private:
        const Context * const mContext;
index e4cd62835bbc7cc654140fc40220570eeb0da008..31642a9429c066c457c3fb27c0d6ad3113d22d0a 100644 (file)
@@ -84,6 +84,11 @@ void Requirement::Maker::anchor()
        put(opAppleAnchor);
 }
 
+void Requirement::Maker::anchorGeneric()
+{
+       put(opAppleGenericAnchor);
+}
+
 void Requirement::Maker::anchor(int slot, SHA1::Digest digest)
 {
        put(opAnchorHash);
@@ -131,6 +136,16 @@ void Requirement::Maker::cdhash(SHA1::Digest digest)
 }
 
 
+
+void Requirement::Maker::copy(const Requirement *req)
+{
+       assert(req);
+       if (req->kind() != exprForm)            // don't know how to embed this
+               MacOSError::throwMe(errSecCSReqUnsupported);
+       this->copy(req->at<const void>(sizeof(Requirement)), req->length() - sizeof(Requirement));
+}
+
+
 void *Requirement::Maker::insert(const Label &label, size_t length)
 {
        require(length);
index 9a72e8b41ffd71536d0b3161cb0d670a9b40ffe7..772adb1c1df7d6a468091be55be143c7e3ac67da 100644 (file)
@@ -55,10 +55,12 @@ public:
        void put(const std::string &s) { putData(s.data(), s.size()); }
        void put(const char *s) { putData(s, strlen(s)); }
        void putData(const void *data, size_t length);
+       void putData(CFStringRef s) { put(cfString(s)); }
        
        void anchor(int slot, SHA1::Digest digest);                     // given slot/digest
        void anchor(int slot, const void *cert, size_t length); // given slot/cert
-       void anchor();                                                                          // canonical Apple anchor
+       void anchor();                                                                          // made-by-Apple
+       void anchorGeneric();                                                           // anything drawn from the Apple anchor
        
        void trustedAnchor();
        void trustedAnchor(int slot);
@@ -67,6 +69,10 @@ public:
        void ident(const std::string &identHash);
        void cdhash(SHA1::Digest digest);
        
+       void copy(const void *data, size_t length)
+               { memcpy(this->alloc(length), data, length); }
+       void copy(const Requirement *req);                              // inline expand
+       
        //
        // Keep labels into exprOp code, and allow for "shifting in"
        // prefix code as needed (exprOp is a prefix-code language).
@@ -81,6 +87,31 @@ public:
        Endian<T> &insert(const Label &label, size_t length = sizeof(T))
        { return *reinterpret_cast<Endian<T>*>(insert(label, length)); }
        
+       //
+       // Help with making operator chains (foo AND bar AND baz...).
+       // Note that the empty case (no elements at all) must be resolved by the caller.
+       //
+       class Chain : public Label {
+       public:
+               Chain(Maker &myMaker, ExprOp op)
+                       : Label(myMaker), maker(myMaker), mJoiner(op), mCount(0) { }
+
+               void add()
+                       { if (mCount++) maker.insert<ExprOp>(*this) = mJoiner; }
+       
+               Maker &maker;
+               bool empty() const { return mCount == 0; }
+               unsigned count() const { return mCount; }
+
+       private:
+               ExprOp mJoiner;
+               unsigned mCount;
+       };
+       
+       
+       //
+       // Over-all construction management
+       //
        void kind(Kind k) { mBuffer->kind(k); }
        size_t length() const { return mPC; }
        Requirement *make();
index 41871e0a20b4e60e568cc3273548758d7165860b..d15fd1a57fb6adfe4971f44b431552ac2084c4d9 100644 (file)
@@ -60,7 +60,7 @@ public:
 
        // different forms of Requirements. Right now, we only support exprForm ("opExprs")
        enum Kind {
-               exprForm = 1                    // postfix expr form
+               exprForm = 1                    // prefix expr form
        };
        
        void kind(Kind k) { mKind = k; }
@@ -98,11 +98,12 @@ private:
 // An interpretation context
 //
 struct Requirement::Context {
-       Context(CFArrayRef certChain, CFDictionaryRef infoDict, const CodeDirectory *dir)
-               : certs(certChain), info(infoDict), directory(dir) { }
+       Context(CFArrayRef certChain, CFDictionaryRef infoDict, CFDictionaryRef entitlementDict, const CodeDirectory *dir)
+               : certs(certChain), info(infoDict), entitlements(entitlementDict), directory(dir) { }
        
        const CFArrayRef certs;
        const CFDictionaryRef info;
+       const CFDictionaryRef entitlements;
        const CodeDirectory * const directory;
 
        SecCertificateRef cert(int ix) const;           // get a cert from the cert chain
@@ -135,7 +136,7 @@ enum ExprOp {
        opFalse,                                                // unconditionally false
        opTrue,                                                 // unconditionally true
        opIdent,                                                // match canonical code [string]
-       opAppleAnchor,                                  // match apple anchor
+       opAppleAnchor,                                  // signed by Apple as Apple's product
        opAnchorHash,                                   // match anchor [cert hash]
        opInfoKeyValue,                                 // *legacy* match Info.plist field [key; value]
        opAnd,                                                  // binary prefix expr AND expr
@@ -146,6 +147,9 @@ enum ExprOp {
        opCertField,                                    // Certificate field [cert index; field name; match suffix]
        opTrustedCert,                                  // require trust settings to approve one particular cert [cert index]
        opTrustedCerts,                                 // require trust settings to approve the cert chain
+       opCertGeneric,                                  // Certificate component by OID [cert index; oid; match suffix]
+       opAppleGenericAnchor,                   // signed by Apple in any capacity
+       opEntitlementField,                             // entitlement dictionary field [string; match suffix]
        exprOpCount                                             // (total opcode count in use)
 };
 
@@ -154,6 +158,12 @@ enum MatchOperation {
        matchExists,                                    // anything but explicit "false" - no value stored
        matchEqual,                                             // equal (CFEqual)
        matchContains,                                  // partial match (substring)
+       matchBeginsWith,                                // partial match (initial substring)
+       matchEndsWith,                                  // partial match (terminal substring)
+       matchLessThan,                                  // less than (string with numeric comparison)
+       matchGreaterThan,                               // greater than (string with numeric comparison)
+       matchLessEqual,                                 // less or equal (string with numeric comparison)
+       matchGreaterEqual,                              // greater or equal (string with numeric comparison)
 };
 
 
index a98f92496e46788df51088bd46893772041b5640..c1f9bb2e10db2389fc54191cee1aca94c7f67aff 100644 (file)
@@ -25,7 +25,7 @@
 // resource directory construction and verification
 //
 #include "resources.h"
-#include "renum.h"
+#include <Security/CSCommon.h>
 #include <security_codesigning/cfmunge.h>
 
 namespace Security {
@@ -87,9 +87,14 @@ FTSENT *ResourceBuilder::next(string &path, Rule * &rule)
                Rule *bestRule = NULL;
                for (Rules::const_iterator it = mRules.begin(); it != mRules.end(); ++it) {
                        Rule *rule = *it;
-                       if (rule->match(path.c_str()))
+                       if (rule->match(path.c_str())) {
+                               if (rule->flags & exclusion) {
+                                       bestRule = NULL;
+                                       break;
+                               }
                                if (!bestRule || rule->weight > bestRule->weight)
                                        bestRule = rule;
+                       }
                }
                if (!bestRule || (bestRule->flags & omitted))
                        continue;
index 35ee00add6f1a89d4d06a290544f9d96d1093db6..834d8945740bcf07e284bd0ab5e9f598f85fcc2a 100644 (file)
 #ifndef _H_RSIGN
 #define _H_RSIGN
 
-#include "CodeSigner.h"
 #include "renum.h"
 #include <security_utilities/utilities.h>
+#include <security_utilities/cfutilities.h>
+#include <security_utilities/hashing.h>
 #include "regex.h"
+#include <CoreFoundation/CoreFoundation.h>
 #include <vector>
 
 namespace Security {
@@ -53,6 +55,7 @@ public:
        enum Action {
                optional = 0x01,                                // may be absent at runtime
                omitted = 0x02,                                 // do not seal even if present
+               exclusion = 0x04,                               // overriding exclusion (stop looking)
        };
        
        typedef unsigned int Weight;
@@ -69,6 +72,7 @@ public:
                const uint32_t flags;
        };
        void addRule(Rule *rule) { mRules.push_back(rule); }
+       void addExclusion(const std::string &pattern) { mRules.insert(mRules.begin(), new Rule(pattern, 0, exclusion)); }
        
        FTSENT *next(std::string &path, Rule * &rule);  // enumerate next file and match rule
 
index 6109eb579d23abe30e2757ea46bcfbb8e42024d4..e90c088d28cdff53b61e940fa058bd38d170f54e 100644 (file)
@@ -59,6 +59,7 @@ _SecHostSetHostingPort
 _kSecCodeSignerApplicationData
 _kSecCodeSignerDetached
 _kSecCodeSignerDryRun
+_kSecCodeSignerEntitlements
 _kSecCodeSignerFlags
 _kSecCodeSignerIdentifier
 _kSecCodeSignerIdentifierPrefix
@@ -72,12 +73,14 @@ _kSecCodeInfoChangedFiles
 _kSecCodeInfoCMS
 _kSecCodeInfoTime
 _kSecCodeInfoDesignatedRequirement
+_kSecCodeInfoEntitlements
 _kSecCodeInfoFormat
 _kSecCodeInfoIdentifier
 _kSecCodeInfoImplicitDesignatedRequirement
 _kSecCodeInfoMainExecutable
 _kSecCodeInfoPList
 _kSecCodeInfoRequirements
+_kSecCodeInfoRequirementData
 _kSecCodeInfoStatus
 _kSecCodeInfoTrust
 _kSecGuestAttributePid
index bdf6a0bf9dc4e5672b7c56a790402fcc7cc2b662..e2d65de1f7884d2567f4b5a63292b0b3c93c1474 100644 (file)
@@ -51,5 +51,12 @@ void EmbeddedSignatureBlob::Maker::component(CodeDirectory::SpecialSlot slot, CF
 }
 
 
+CFDictionaryRef EntitlementBlob::entitlements() const
+{
+       return makeCFDictionaryFrom(this->at<const UInt8 *>(sizeof(EntitlementBlob)),
+               this->length() - sizeof(EntitlementBlob));
+}
+
+
 } // end namespace CodeSigning
 } // end namespace Security
index 01f73d841c2d820c124c8c494b4f6bdb9963cac0..969b728cb8506fb8b92c6aa720bee00a3b15c123 100644 (file)
@@ -60,6 +60,15 @@ public:
 typedef SuperBlob<0xfade0cc1> DetachedSignatureBlob;   // indexed by main architecture
 
 
+//
+// An entitlement blob is used for embedding entitlement configuration data
+//
+class EntitlementBlob : public Blob<EntitlementBlob, 0xfade7171> {
+public:
+       CFDictionaryRef entitlements() const;
+};
+
+
 } // end namespace CodeSigning
 } // end namespace Security
 
index d3f964c12c27cfaf5622c0259a361a048296bdd2..15003b3da5e3221114f8f87d54a80b49b9925e3e 100644 (file)
@@ -31,6 +31,7 @@
 #include <Security/SecIdentity.h>
 #include <Security/CMSEncoder.h>
 #include <Security/CMSPrivate.h>
+#include <CoreFoundation/CFBundlePriv.h>
 #include "renum.h"
 #include <security_utilities/unix++.h>
 #include <security_utilities/unixchild.h>
@@ -86,10 +87,24 @@ void SecCodeSigner::Signer::sign(SecCSFlags flags)
        // prepare the resource directory, if any
        string rpath = rep->resourcesRootPath();
        if (!rpath.empty()) {
+               // explicitly given resource rules always win
                CFCopyRef<CFDictionaryRef> resourceRules(state.mResourceRules);
+               
+               // embedded resource rules come next
+               if (!resourceRules)
+                       if (CFTypeRef spec = CFDictionaryGetValue(infoDict, _kCFBundleResourceSpecificationKey))
+                               if (CFGetTypeID(spec) == CFStringGetTypeID())
+                                       if (CFRef<CFDataRef> data = cfLoadFile(rpath + "/" + cfString(CFStringRef(spec))))
+                                               if (CFRef<CFDictionaryRef> dict = makeCFDictionaryFrom(data))
+                                                       resourceRules = dict;
+
+               // finally, ask the DiskRep for its default
                if (!resourceRules)
                        resourceRules.take(rep->defaultResourceRules());
+               
+               // build the resource directory
                ResourceBuilder resources(rpath, cfget<CFDictionaryRef>(resourceRules, "rules"));
+               rep->adjustResources(resources);
                CFRef<CFDictionaryRef> rdir = resources.build();
                resourceDirectory.take(CFPropertyListCreateXMLData(NULL, rdir));
        }
@@ -242,6 +257,12 @@ void SecCodeSigner::Signer::populate(CodeDirectory::Builder &builder, DiskRep::W
                                builder.special(slot, state.mApplicationData);
 #endif
                        break;
+               case cdEntitlementSlot:
+                       if (state.mEntitlementData) {
+                               writer.component(cdEntitlementSlot, state.mEntitlementData);
+                               builder.special(slot, state.mEntitlementData);
+                       }
+                       break;
                default:
                        if (CFDataRef data = rep->component(slot))
                                builder.special(slot, data);
index 98e944db9879caaa0facd79059c16cb43ca611fc..90450ad1ce9ad47ff76088b06a8b4c37da09d11a 100644 (file)
@@ -57,6 +57,7 @@
                C259DFD70AD6D9BA00C9ACC6 /* sigblob.h in Headers */ = {isa = PBXBuildFile; fileRef = C259DFD50AD6D9BA00C9ACC6 /* sigblob.h */; };
                C26B45C10B8A9C0A003C0ACA /* ucspc in Frameworks */ = {isa = PBXBuildFile; fileRef = C26B45C00B8A9C00003C0ACA /* ucspc */; };
                C297DF250B014BB300E94EE0 /* SecCodeSigner.h in Headers */ = {isa = PBXBuildFile; fileRef = C21EA3DC0AD2F81300E6E31C /* SecCodeSigner.h */; settings = {ATTRIBUTES = (Private, ); }; };
+               C2A36B4B0D906C08003412BA /* resources.h in Headers */ = {isa = PBXBuildFile; fileRef = C2E911E10ADEBE3200275CB2 /* resources.h */; settings = {ATTRIBUTES = (Public, ); }; };
                C2A487530B7914F400849490 /* SecCodeHostLib.h in Headers */ = {isa = PBXBuildFile; fileRef = C2BC1F340B580DA7003EC9DC /* SecCodeHostLib.h */; settings = {ATTRIBUTES = (Private, ); }; };
                C2A487540B79150C00849490 /* SecIntegrity.h in Headers */ = {isa = PBXBuildFile; fileRef = C250F6C20B5EF1910076098F /* SecIntegrity.h */; settings = {ATTRIBUTES = (Private, ); }; };
                C2A487550B79152A00849490 /* SecIntegrity.h in Headers */ = {isa = PBXBuildFile; fileRef = C250F6C20B5EF1910076098F /* SecIntegrity.h */; settings = {ATTRIBUTES = (Private, ); }; };
                C2D8A0980AE7F74500F68F79 /* cfmunge.h in Headers */ = {isa = PBXBuildFile; fileRef = C2D8A07E0AE7F6E300F68F79 /* cfmunge.h */; settings = {ATTRIBUTES = (Public, ); }; };
                C2E2873D0B5D8D80009336A0 /* SecCodeHostLib.c in Sources */ = {isa = PBXBuildFile; fileRef = C2E2873C0B5D8D80009336A0 /* SecCodeHostLib.c */; };
                C2E911E20ADEBE3200275CB2 /* resources.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C2E911E00ADEBE3200275CB2 /* resources.cpp */; };
-               C2E911E30ADEBE3200275CB2 /* resources.h in Headers */ = {isa = PBXBuildFile; fileRef = C2E911E10ADEBE3200275CB2 /* resources.h */; };
+               C2E911E30ADEBE3200275CB2 /* resources.h in Headers */ = {isa = PBXBuildFile; fileRef = C2E911E10ADEBE3200275CB2 /* resources.h */; settings = {ATTRIBUTES = (Public, ); }; };
                C2EF10100A49BD89005A44BB /* renum.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C2EF100E0A49BD89005A44BB /* renum.cpp */; };
                C2EF10110A49BD89005A44BB /* renum.h in Headers */ = {isa = PBXBuildFile; fileRef = C2EF100F0A49BD89005A44BB /* renum.h */; settings = {ATTRIBUTES = (Public, ); }; };
                C2EF10130A49BD89005A44BB /* renum.h in Headers */ = {isa = PBXBuildFile; fileRef = C2EF100F0A49BD89005A44BB /* renum.h */; settings = {ATTRIBUTES = (Public, ); }; };
                        isa = PBXHeadersBuildPhase;
                        buildActionMask = 2147483647;
                        files = (
+                               C2A36B4B0D906C08003412BA /* resources.h in Headers */,
                                C2F656930BCBFFF40078779E /* cserror.h in Headers */,
                                C2A487540B79150C00849490 /* SecIntegrity.h in Headers */,
                                C2A487530B7914F400849490 /* SecCodeHostLib.h in Headers */,
                        isa = XCBuildConfiguration;
                        buildSettings = {
                                BUILD_VARIANTS = debug;
-                               CURRENT_PROJECT_VERSION = 32953;
+                               CURRENT_PROJECT_VERSION = 33803;
                                FRAMEWORK_SEARCH_PATHS = (
                                        /usr/local/SecurityPieces/Frameworks,
                                        /usr/local/SecurityPieces/Components/Security,
                                        normal,
                                        debug,
                                );
-                               CURRENT_PROJECT_VERSION = 32953;
+                               CURRENT_PROJECT_VERSION = 33803;
                                FRAMEWORK_SEARCH_PATHS = (
                                        /usr/local/SecurityPieces/Frameworks,
                                        /usr/local/SecurityPieces/Components/Security,
                                        normal,
                                        debug,
                                );
-                               CURRENT_PROJECT_VERSION = 32953;
+                               CURRENT_PROJECT_VERSION = 33803;
                                FRAMEWORK_SEARCH_PATHS = (
                                        /usr/local/SecurityPieces/Frameworks,
                                        /usr/local/SecurityPieces/Components/Security,
index 296847fd60822f340329265f473600fa13c7a9c1..45827a4c457ed1ef8541ba177e57c32b34f1ea9a 100644 (file)
 //     RequirementSet => set of labeled requirements (Requirements *)
 // The grammar can "autosense" - i.e. recognize which one it's fed and
 // return appropriate semantic data.
+//
 // The semantic data compiled is a malloc'ed BlobCore * - a Requirement
 // object or a SuperBlob containing multiple Requirements.
 //
+// Errors are indicated to the caller by accumulating error message strings
+// in the errors member variable. Any non-empty error value indicates failure.
+// Presence of semantic data is not a reliable indication of success.
+//
 header "post_include_hpp" {
 #include "requirement.h"
 using namespace CodeSigning;
@@ -44,6 +49,7 @@ header "post_include_cpp" {
 #include "csutilities.h"
 #include <security_utilities/cfutilities.h>
 #include <security_utilities/hashing.h>
+#include <security_cdsa_utilities/cssmdata.h>  // OID coding
 using namespace CodeSigning;
 typedef Requirement::Maker Maker;
 }
@@ -59,7 +65,9 @@ options {
 
 {
        //
-       // Collect error messages
+       // Collect error messages.
+       // Note that the immediate caller takes the absence of collected error messages
+       // to indicate compilation success.
        //
        void RequirementParser::reportError(const antlr::RecognitionException &ex)
        {
@@ -95,8 +103,25 @@ options {
                        throw antlr::SemanticException("invalid hash length");
                memcpy(hash, hexString(s).data(), SHA1::digestLength);
        }
+       
+       void RequirementParser::certMatchOperation(Maker &maker, int slot, string key)
+       {
+               if (!key.compare(0, 8, "subject.", 0, 8)) {
+                       maker.put(opCertField);
+                       maker.put(slot);
+                       maker.put(key);
+               } else if (!key.compare(0, 6, "field.", 0, 6)) {
+                       maker.put(opCertGeneric);
+                       maker.put(slot);
+                       CssmAutoData oid(Allocator::standard()); oid.fromOid(key.c_str() + 6);
+                       maker.putData(oid.data(), oid.length());
+               } else {
+                       throw antlr::SemanticException(key + ": unrecognized certificate field");
+               }
+       }
 }
 
+
 class RequirementParser extends Parser;
 
 options {
@@ -112,6 +137,7 @@ public:
 private:
        static string hexString(const string &s);
        static void hashString(const string &s, SHA1::Digest hash);
+       void certMatchOperation(Maker &maker, int slot, string key);
 }
 
 
@@ -190,9 +216,10 @@ primary[Maker &maker]
                        { maker.put(opFalse); }
        |       certspec[maker]
        |       infospec[maker]
+       |       entitlementspec[maker]
        |       "identifier" { string code; } eql code=identifierString
                        { maker.ident(code); }
-       |       "cdhash" { SHA1::Digest digest; } hash[digest]
+       |       "cdhash" { SHA1::Digest digest; } eql hash[digest]
                        { maker.cdhash(digest); }
        ;
 
@@ -201,8 +228,9 @@ primary[Maker &maker]
 // Certificate specifications restrict certificates in the signing chain
 //
 certspec[Maker &maker]
-       :       "anchor" "apple"
-                       { maker.put(opAppleAnchor); }
+       :       "anchor" "apple" appleanchor[maker]
+       |       "anchor" "generic" "apple"              // alternate form
+                       { maker.put(opAppleGenericAnchor); }
        |       ( "certificate" | "cert" | "anchor" ) "trusted"
                        { maker.trustedAnchor(); }
        |       ( "certificate" | "cert" ) { int slot; } slot=certSlot
@@ -210,11 +238,18 @@ certspec[Maker &maker]
        |       "anchor" certslotspec[maker, Requirement::anchorCert]
        ;
 
+appleanchor[Maker &maker]
+       :       empty
+                       { maker.put(opAppleAnchor); }
+       |       "generic"
+                       { maker.put(opAppleGenericAnchor); }
+       ;
+
 certslotspec[Maker &maker, int slot]   { string key; }
        :       eql { SHA1::Digest digest; } certificateDigest[digest]
             { maker.anchor(slot, digest); }
        |       key=bracketKey
-                       { maker.put(opCertField); maker.put(slot); maker.put(key); }
+                       { certMatchOperation(maker, slot, key); }
                match_suffix[maker]
        ;
 
@@ -228,13 +263,39 @@ infospec[Maker &maker]            { string key; }
                match_suffix[maker]
        ;
 
+
+//
+// Entitlement specifications place conditions on embedded entitlement entries
+//
+entitlementspec[Maker &maker]  { string key; }
+       :       "entitlement" key=bracketKey
+                       { maker.put(opEntitlementField); maker.put(key); }
+               match_suffix[maker]
+       ;
+
+
+//
+// Common match operations, written as a syntactic suffix (the operand precedes this)
+//
 match_suffix[Maker &maker]
-       :       empty
+       :       empty ( "exists" ) ?
                        { maker.put(matchExists); }
-       |       EQL { string value; } value=datavalue
-                       { maker.put(matchEqual); maker.put(value); }
+       |       ( EQL | EQQL )
+                       { MatchOperation mop = matchEqual; string value; }
+               ( STAR { mop = matchEndsWith; } ) ?
+               value=datavalue
+               ( STAR { mop = (mop == matchEndsWith) ? matchContains : matchBeginsWith; } ) ?
+                       { maker.put(mop); maker.put(value); }
        |       SUBS { string value; } value=datavalue
                        { maker.put(matchContains); maker.put(value); }
+       |       LESS { string value; } value=datavalue
+                       { maker.put(matchLessThan); maker.put(value); }
+       |       GT { string value; } value=datavalue
+                       { maker.put(matchGreaterThan); maker.put(value); }
+       |       LE { string value; } value=datavalue
+                       { maker.put(matchLessEqual); maker.put(value); }
+       |       GE { string value; } value=datavalue
+                       { maker.put(matchGreaterEqual); maker.put(value); }
        ;
 
 bracketKey returns [string key]
@@ -255,12 +316,6 @@ certSlot returns [int slot]
                        { slot = Requirement::anchorCert; }
        ;
 
-certSlotAnchor returns [int slot]
-       :       slot=certSlot
-       |       empty                   // defaults to anchor ( == 0)
-                       { slot = Requirement::anchorCert; }
-       ;
-
 // an arbitrary digest value
 hash[SHA1::Digest digest]
        :       hash:HASHCONSTANT
@@ -310,6 +365,7 @@ fluff
 
 eql
        :       EQL
+       |       EQQL
        |       empty
        ;
 
@@ -319,9 +375,11 @@ empty : ;
 //
 // The lexer for the Requirement language.
 // Really straightforward and conventional.
-// A subset of strings don't need to be quoted (DOTKEYs), though the disassembler
-//   will always quote them anyway.
-// There's a syntax H"abcd" to denote hash values (in hex).
+// A subset of strings don't need to be quoted (DOTKEYs). Neither do some simple
+//  pathnames starting with "/".
+// Hash values have a special syntax H"abcd" (abcd in straight hex).
+// Hex constants of the form 0xabcd can have any length; they are carried
+// around as strings (which are in turn stored as data in the language binary).
 //
 class RequirementLexer extends Lexer;
 
@@ -336,7 +394,7 @@ IDENT options { testLiterals=true; }
        ;
 
 DOTKEY options { testLiterals=true; }
-       :       IDENT ( "." IDENT )*
+       :       IDENT ( "." ( IDENT | INTEGER ) )*
        ;
 
 PATHNAME
@@ -369,11 +427,17 @@ LPAREN    : '(' ;
 RPAREN : ')' ;
 LBRACK : '[' ;
 RBRACK : ']' ;
+LESS   : '<' ;
+GT             : '>' ;
+LE             : "<=" ;
+GE             : ">=" ;
 COMMA  : ',' ;
 EQL            : '=' ;
+EQQL   : "==" ;
 SUBS   : '~' ;
 NEG            : '-' ;
 NOT            : '!' ;
+STAR   : '*' ;
 
 
 //