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 */
};
state.mResourceRules = get<CFDictionaryRef>(kSecCodeSignerResourceRules);
state.mApplicationData = get<CFDataRef>(kSecCodeSignerApplicationData);
+ state.mEntitlementData = get<CFDataRef>(kSecCodeSignerEntitlements);
}
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
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");
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");
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).
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 */
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");
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;
#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>
for (unsigned n = 0; n < cdSlotCount; n++)
mCache[n] = NULL;
mInfoDict = NULL;
+ mEntitlements = NULL;
mResourceDict = NULL;
mDesignatedReq = NULL;
mTrust = NULL;
// 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;
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
#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.
{
assert(req);
validateDirectory();
- req->validate(Requirement::Context(mCertChain, infoDictionary(), codeDirectory()), failure);
+ req->validate(Requirement::Context(mCertChain, infoDictionary(), entitlements(), codeDirectory()), failure);
}
{
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;
}
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);
CFRef<SecRequirementRef>((new SecRequirement(dreq))->handle()));
CFDictionaryAddValue(dict, kSecCodeInfoImplicitDesignatedRequirement, ddreqRef);
}
+
+ if (CFDataRef ent = component(cdEntitlementSlot))
+ CFDictionaryAddValue(dict, kSecCodeInfoEntitlements, ent);
}
//
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);
}
std::string format() const { return mRep->format(); }
CFDataRef component(CodeDirectory::SpecialSlot slot);
CFDictionaryRef infoDictionary();
+ CFDictionaryRef entitlements();
CFDictionaryRef resourceDictionary();
CFURLRef resourceBase();
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:
// 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
#include <CoreFoundation/CFURLAccess.h>
#include <CoreFoundation/CFBundlePriv.h>
#include <security_codesigning/cfmunge.h>
+#include <copyfile.h>
namespace Security {
// 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);
//
// 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();
}
}
// 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
// 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;
}
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('/');
"}}");
}
+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);
checkModifiedFile(files, cdCodeDirectorySlot);
checkModifiedFile(files, cdSignatureSlot);
checkModifiedFile(files, cdResourceDirSlot);
+ checkModifiedFile(files, cdEntitlementSlot);
return files;
}
{
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()
}
BundleDiskRep::Writer::Writer(BundleDiskRep *r)
- : rep(r)
+ : rep(r), mMadeMetaDirectory(false)
{
execWriter = rep->mExecRep->writer();
}
//
// 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)
{
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);
}
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"
std::string recommendedIdentifier();
std::string resourcesRootPath();
CFDictionaryRef defaultResourceRules();
+ void adjustResources(ResourceBuilder &builder);
const Requirements *defaultRequirements(const Architecture *arch);
Universal *mainExecutableImage();
size_t pageSize();
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
};
protected:
RefPointer<BundleDiskRep> rep;
RefPointer<DiskRep::Writer> execWriter;
+ bool mMadeMetaDirectory;
};
return kSecCS_SIGNATUREFILE;
case cdApplicationSlot:
return kSecCS_APPLICATIONFILE;
+ case cdEntitlementSlot:
+ return kSecCS_ENTITLEMENTFILE;
default:
return NULL;
}
return cdComponentPerArchitecture | cdComponentIsBlob;
case cdSignatureSlot:
return cdComponentPerArchitecture; // raw
+ case cdEntitlementSlot:
+ return cdComponentIsBlob; // global
default:
return 0; // global, raw
}
#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)
//
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)
// 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>
}
+//
+// 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
#include <Security/Security.h>
#include <security_utilities/hashing.h>
+#include <security_cdsa_utilities/cssmdata.h>
namespace Security {
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
//
DiskRep *DiskRep::bestGuess(const char *path)
{
+ try {
struct stat st;
if (::stat(path, &st))
UnixError::throwMe();
// 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;
+ }
+ }
}
return NULL; // none
}
+void DiskRep::adjustResources(ResourceBuilder &builder)
+{
+ // do nothing
+}
+
const Requirements *DiskRep::defaultRequirements(const Architecture *)
{
return NULL; // none
#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>
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
{
// 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))
// reqdumper - Requirement un-parsing (disassembly)
//
#include "reqdumper.h"
+#include <security_cdsa_utilities/cssmdata.h> // OID encoder
#include <cstdarg>
namespace Security {
case opAppleAnchor:
print("anchor apple");
break;
+ case opAppleGenericAnchor:
+ print("anchor apple generic");
+ break;
case opAnchorHash:
print("anchor"); certSlot(); print(" = "); hashData();
break;
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;
{
switch (MatchOperation op = MatchOperation(get<uint32_t>())) {
case matchExists:
- print(" /* exists */");
+ print(" exists");
break;
case matchEqual:
print(" = "); data();
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;
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);
#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 {
return getString() == mContext->directory->identifier();
case opAppleAnchor:
return appleSigned();
+ case opAppleGenericAnchor:
+ return appleAnchored();
case opAnchorHash:
{
SecCertificateRef cert = mContext->cert(get<int32_t>());
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;
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>());
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:
}
+//
+// 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
{ "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++)
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())
|| 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)
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;
}
}
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
#include <security_codesigning/reqreader.h>
#include <Security/SecTrustSettings.h>
+#include <security_cdsa_utilities/cssmdata.h> // CssmOid
namespace Security {
namespace CodeSigning {
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
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;
put(opAppleAnchor);
}
+void Requirement::Maker::anchorGeneric()
+{
+ put(opAppleGenericAnchor);
+}
+
void Requirement::Maker::anchor(int slot, SHA1::Digest digest)
{
put(opAnchorHash);
}
+
+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);
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);
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).
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();
// 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; }
// 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
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
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)
};
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)
};
// resource directory construction and verification
//
#include "resources.h"
-#include "renum.h"
+#include <Security/CSCommon.h>
#include <security_codesigning/cfmunge.h>
namespace Security {
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;
#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 {
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;
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
_kSecCodeSignerApplicationData
_kSecCodeSignerDetached
_kSecCodeSignerDryRun
+_kSecCodeSignerEntitlements
_kSecCodeSignerFlags
_kSecCodeSignerIdentifier
_kSecCodeSignerIdentifierPrefix
_kSecCodeInfoCMS
_kSecCodeInfoTime
_kSecCodeInfoDesignatedRequirement
+_kSecCodeInfoEntitlements
_kSecCodeInfoFormat
_kSecCodeInfoIdentifier
_kSecCodeInfoImplicitDesignatedRequirement
_kSecCodeInfoMainExecutable
_kSecCodeInfoPList
_kSecCodeInfoRequirements
+_kSecCodeInfoRequirementData
_kSecCodeInfoStatus
_kSecCodeInfoTrust
_kSecGuestAttributePid
}
+CFDictionaryRef EntitlementBlob::entitlements() const
+{
+ return makeCFDictionaryFrom(this->at<const UInt8 *>(sizeof(EntitlementBlob)),
+ this->length() - sizeof(EntitlementBlob));
+}
+
+
} // end namespace CodeSigning
} // end namespace Security
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
#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>
// 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));
}
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);
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,
// 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;
#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;
}
{
//
- // 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)
{
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 {
private:
static string hexString(const string &s);
static void hashString(const string &s, SHA1::Digest hash);
+ void certMatchOperation(Maker &maker, int slot, string key);
}
{ 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); }
;
// 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
| "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]
;
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]
{ 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
eql
: EQL
+ | EQQL
| 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;
;
DOTKEY options { testLiterals=true; }
- : IDENT ( "." IDENT )*
+ : IDENT ( "." ( IDENT | INTEGER ) )*
;
PATHNAME
RPAREN : ')' ;
LBRACK : '[' ;
RBRACK : ']' ;
+LESS : '<' ;
+GT : '>' ;
+LE : "<=" ;
+GE : ">=" ;
COMMA : ',' ;
EQL : '=' ;
+EQQL : "==" ;
SUBS : '~' ;
NEG : '-' ;
NOT : '!' ;
+STAR : '*' ;
//