From 516ae4771041b50b4461674fd622f46f053aa4bc Mon Sep 17 00:00:00 2001 From: Apple Date: Fri, 25 Apr 2008 21:49:35 +0000 Subject: [PATCH] libsecurity_codesigning-33803.tar.gz --- lib/CSCommon.h | 2 + lib/CodeSigner.cpp | 1 + lib/CodeSigner.h | 1 + lib/SecCode.cpp | 2 + lib/SecCode.h | 14 +- lib/SecCodeSigner.cpp | 1 + lib/SecCodeSigner.h | 1 + lib/StaticCode.cpp | 122 +++++++++++++++-- lib/StaticCode.h | 6 + lib/bundlediskrep.cpp | 90 ++++++++----- lib/bundlediskrep.h | 17 ++- lib/codedirectory.cpp | 4 + lib/codedirectory.h | 2 + lib/csutilities.cpp | 37 ++++++ lib/csutilities.h | 8 ++ lib/diskrep.cpp | 16 ++- lib/diskrep.h | 2 + lib/filediskrep.cpp | 2 +- lib/reqdumper.cpp | 41 +++++- lib/reqinterp.cpp | 123 +++++++++++++++--- lib/reqinterp.h | 11 +- lib/reqmaker.cpp | 15 +++ lib/reqmaker.h | 33 ++++- lib/requirement.h | 18 ++- lib/resources.cpp | 9 +- lib/resources.h | 6 +- lib/security_codesigning.exp | 3 + lib/sigblob.cpp | 7 + lib/sigblob.h | 9 ++ lib/signer.cpp | 21 +++ .../project.pbxproj | 10 +- requirements.grammar | 100 +++++++++++--- 32 files changed, 628 insertions(+), 106 deletions(-) diff --git a/lib/CSCommon.h b/lib/CSCommon.h index b1fd186..5b24b60 100644 --- a/lib/CSCommon.h +++ b/lib/CSCommon.h @@ -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 */ }; diff --git a/lib/CodeSigner.cpp b/lib/CodeSigner.cpp index da36284..1503ea1 100644 --- a/lib/CodeSigner.cpp +++ b/lib/CodeSigner.cpp @@ -193,6 +193,7 @@ SecCodeSigner::Parser::Parser(SecCodeSigner &state, CFDictionaryRef parameters) state.mResourceRules = get(kSecCodeSignerResourceRules); state.mApplicationData = get(kSecCodeSignerApplicationData); + state.mEntitlementData = get(kSecCodeSignerEntitlements); } diff --git a/lib/CodeSigner.h b/lib/CodeSigner.h index 150d528..f94feb3 100644 --- a/lib/CodeSigner.h +++ b/lib/CodeSigner.h @@ -67,6 +67,7 @@ private: CFRef mResourceRules; // explicit resource collection rules (override) CFRef mSigningTime; // signing time desired (kCFNull for none) CFRef mApplicationData; // contents of application slot + CFRef mEntitlementData; // entitlement configuration data const Requirements *mRequirements; // internal code requirements size_t mCMSSize; // size estimate for CMS blob uint32_t mCdFlags; // CodeDirectory flags diff --git a/lib/SecCode.cpp b/lib/SecCode.cpp index 9eaa69f..eccd396 100644 --- a/lib/SecCode.cpp +++ b/lib/SecCode.cpp @@ -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"); diff --git a/lib/SecCode.h b/lib/SecCode.h index 471de30..697c89d 100644 --- a/lib/SecCode.h +++ b/lib/SecCode.h @@ -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 */ diff --git a/lib/SecCodeSigner.cpp b/lib/SecCodeSigner.cpp index b6d3b45..6e752a4 100644 --- a/lib/SecCodeSigner.cpp +++ b/lib/SecCodeSigner.cpp @@ -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"); diff --git a/lib/SecCodeSigner.h b/lib/SecCodeSigner.h index 5d72ce4..32c0abb 100644 --- a/lib/SecCodeSigner.h +++ b/lib/SecCodeSigner.h @@ -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; diff --git a/lib/StaticCode.cpp b/lib/StaticCode.cpp index 78d7208..9984fac 100644 --- a/lib/StaticCode.cpp +++ b/lib/StaticCode.cpp @@ -35,6 +35,7 @@ #include #include #include +#include #include #include #include @@ -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(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 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] = and ... + maker.put(opAnd); + maker.put(opCertField); // certificate + maker.put(0); // leaf + maker.put("subject.CN"); // [subject.CN] + maker.put(matchEqual); // = + maker.putData(leafCN); // + // ... cert 1[field.] exists + maker.put(opCertGeneric); // certificate + maker.put(1); // 1 + maker.putData(adcSdkMarkerOID.Data, adcSdkMarkerOID.Length); // [field.] + 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 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 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 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((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); } diff --git a/lib/StaticCode.h b/lib/StaticCode.h index 49d7d51..7aed254 100644 --- a/lib/StaticCode.h +++ b/lib/StaticCode.h @@ -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 mInfoDict; // derived from mCache slot + CFRef mEntitlements; // derived from mCache slot CFRef mResourceDict; // derived from mCache slot const Requirement *mDesignatedReq; // cached designated req if we made one up diff --git a/lib/bundlediskrep.cpp b/lib/bundlediskrep.cpp index ad4d819..ffaa478 100644 --- a/lib/bundlediskrep.cpp +++ b/lib/bundlediskrep.cpp @@ -24,6 +24,7 @@ #include #include #include +#include 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 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); } diff --git a/lib/bundlediskrep.h b/lib/bundlediskrep.h index 4aae953..a6ffe2b 100644 --- a/lib/bundlediskrep.h +++ b/lib/bundlediskrep.h @@ -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 mBundle; - std::string mResourcePath; - RefPointer mExecRep; + std::string mMetaPath; // path to directory containing signing files + bool mMetaExists; // separate meta-file directory exists + RefPointer mExecRep; // DiskRep for main executable file }; @@ -104,6 +108,7 @@ protected: protected: RefPointer rep; RefPointer execWriter; + bool mMadeMetaDirectory; }; diff --git a/lib/codedirectory.cpp b/lib/codedirectory.cpp index f9ad55d..5ce2ff6 100644 --- a/lib/codedirectory.cpp +++ b/lib/codedirectory.cpp @@ -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 } diff --git a/lib/codedirectory.h b/lib/codedirectory.h index 38cedc2..135f2aa 100644 --- a/lib/codedirectory.h +++ b/lib/codedirectory.h @@ -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) diff --git a/lib/csutilities.cpp b/lib/csutilities.cpp index f818b06..8fe0921 100644 --- a/lib/csutilities.cpp +++ b/lib/csutilities.cpp @@ -25,6 +25,7 @@ // csutilities - miscellaneous utilities for the code signing implementation // #include "csutilities.h" +#include #include #include #include @@ -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 diff --git a/lib/csutilities.h b/lib/csutilities.h index ec3b7d9..13c5074 100644 --- a/lib/csutilities.h +++ b/lib/csutilities.h @@ -32,6 +32,7 @@ #include #include +#include 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 diff --git a/lib/diskrep.cpp b/lib/diskrep.cpp index 0a28490..5db86a3 100644 --- a/lib/diskrep.cpp +++ b/lib/diskrep.cpp @@ -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 pathURL = makeCFURL(path)) - if (CFRef bundle = _CFBundleCreateWithExecutableURLIfLooksLikeBundle(NULL, pathURL)) + if (CFRef 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 diff --git a/lib/diskrep.h b/lib/diskrep.h index 001d1a5..4442320 100644 --- a/lib/diskrep.h +++ b/lib/diskrep.h @@ -30,6 +30,7 @@ #include "cs.h" #include "codedirectory.h" #include "requirement.h" +#include "resources.h" #include "macho++.h" // for class Architecture #include #include @@ -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 diff --git a/lib/filediskrep.cpp b/lib/filediskrep.cpp index 6906c74..9fefe6a 100644 --- a/lib/filediskrep.cpp +++ b/lib/filediskrep.cpp @@ -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)) diff --git a/lib/reqdumper.cpp b/lib/reqdumper.cpp index 33867de..8fbd444 100644 --- a/lib/reqdumper.cpp +++ b/lib/reqdumper.cpp @@ -25,6 +25,7 @@ // reqdumper - Requirement un-parsing (disassembly) // #include "reqdumper.h" +#include // OID encoder #include 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())) { 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); diff --git a/lib/reqinterp.cpp b/lib/reqinterp.cpp index b661b17..089154b 100644 --- a/lib/reqinterp.cpp +++ b/lib/reqinterp.cpp @@ -28,7 +28,6 @@ #include #include #include -#include // 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()); @@ -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()); @@ -101,6 +108,13 @@ bool Requirement::Interpreter::evaluate() Match match(*this); return certFieldValue(key, match, cert); } + case opCertGeneric: + { + SecCertificateRef cert = mContext->cert(get()); + string key = getString(); + Match match(*this); + return certFieldGeneric(key, match, cert); + } case opTrustedCert: return trustedCert(get()); 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 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 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 diff --git a/lib/reqinterp.h b/lib/reqinterp.h index ebeed8d..fad9a47 100644 --- a/lib/reqinterp.h +++ b/lib/reqinterp.h @@ -29,6 +29,7 @@ #include #include +#include // 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 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; diff --git a/lib/reqmaker.cpp b/lib/reqmaker.cpp index e4cd628..31642a9 100644 --- a/lib/reqmaker.cpp +++ b/lib/reqmaker.cpp @@ -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(sizeof(Requirement)), req->length() - sizeof(Requirement)); +} + + void *Requirement::Maker::insert(const Label &label, size_t length) { require(length); diff --git a/lib/reqmaker.h b/lib/reqmaker.h index 9a72e8b..772adb1 100644 --- a/lib/reqmaker.h +++ b/lib/reqmaker.h @@ -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 &insert(const Label &label, size_t length = sizeof(T)) { return *reinterpret_cast*>(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(*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(); diff --git a/lib/requirement.h b/lib/requirement.h index 41871e0..d15fd1a 100644 --- a/lib/requirement.h +++ b/lib/requirement.h @@ -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) }; diff --git a/lib/resources.cpp b/lib/resources.cpp index a98f924..c1f9bb2 100644 --- a/lib/resources.cpp +++ b/lib/resources.cpp @@ -25,7 +25,7 @@ // resource directory construction and verification // #include "resources.h" -#include "renum.h" +#include #include 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; diff --git a/lib/resources.h b/lib/resources.h index 35ee00a..834d894 100644 --- a/lib/resources.h +++ b/lib/resources.h @@ -27,10 +27,12 @@ #ifndef _H_RSIGN #define _H_RSIGN -#include "CodeSigner.h" #include "renum.h" #include +#include +#include #include "regex.h" +#include #include 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 diff --git a/lib/security_codesigning.exp b/lib/security_codesigning.exp index 6109eb5..e90c088 100644 --- a/lib/security_codesigning.exp +++ b/lib/security_codesigning.exp @@ -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 diff --git a/lib/sigblob.cpp b/lib/sigblob.cpp index bdf6a0b..e2d65de 100644 --- a/lib/sigblob.cpp +++ b/lib/sigblob.cpp @@ -51,5 +51,12 @@ void EmbeddedSignatureBlob::Maker::component(CodeDirectory::SpecialSlot slot, CF } +CFDictionaryRef EntitlementBlob::entitlements() const +{ + return makeCFDictionaryFrom(this->at(sizeof(EntitlementBlob)), + this->length() - sizeof(EntitlementBlob)); +} + + } // end namespace CodeSigning } // end namespace Security diff --git a/lib/sigblob.h b/lib/sigblob.h index 01f73d8..969b728 100644 --- a/lib/sigblob.h +++ b/lib/sigblob.h @@ -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 { +public: + CFDictionaryRef entitlements() const; +}; + + } // end namespace CodeSigning } // end namespace Security diff --git a/lib/signer.cpp b/lib/signer.cpp index d3f964c..15003b3 100644 --- a/lib/signer.cpp +++ b/lib/signer.cpp @@ -31,6 +31,7 @@ #include #include #include +#include #include "renum.h" #include #include @@ -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 resourceRules(state.mResourceRules); + + // embedded resource rules come next + if (!resourceRules) + if (CFTypeRef spec = CFDictionaryGetValue(infoDict, _kCFBundleResourceSpecificationKey)) + if (CFGetTypeID(spec) == CFStringGetTypeID()) + if (CFRef data = cfLoadFile(rpath + "/" + cfString(CFStringRef(spec)))) + if (CFRef 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(resourceRules, "rules")); + rep->adjustResources(resources); CFRef 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); diff --git a/libsecurity_codesigning.xcodeproj/project.pbxproj b/libsecurity_codesigning.xcodeproj/project.pbxproj index 98e944d..90450ad 100644 --- a/libsecurity_codesigning.xcodeproj/project.pbxproj +++ b/libsecurity_codesigning.xcodeproj/project.pbxproj @@ -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, ); }; }; @@ -144,7 +145,7 @@ 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, ); }; }; @@ -632,6 +633,7 @@ 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 */, @@ -997,7 +999,7 @@ 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, @@ -1047,7 +1049,7 @@ normal, debug, ); - CURRENT_PROJECT_VERSION = 32953; + CURRENT_PROJECT_VERSION = 33803; FRAMEWORK_SEARCH_PATHS = ( /usr/local/SecurityPieces/Frameworks, /usr/local/SecurityPieces/Components/Security, @@ -1095,7 +1097,7 @@ normal, debug, ); - CURRENT_PROJECT_VERSION = 32953; + CURRENT_PROJECT_VERSION = 33803; FRAMEWORK_SEARCH_PATHS = ( /usr/local/SecurityPieces/Frameworks, /usr/local/SecurityPieces/Components/Security, diff --git a/requirements.grammar b/requirements.grammar index 296847f..45827a4 100644 --- a/requirements.grammar +++ b/requirements.grammar @@ -29,9 +29,14 @@ // 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 #include +#include // 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 : '*' ; // -- 2.45.2