#include "bundlediskrep.h"
#include "filediskrep.h"
#include "dirscanner.h"
+#include "notarization.h"
#include <CoreFoundation/CFBundlePriv.h>
#include <CoreFoundation/CFURLAccess.h>
#include <CoreFoundation/CFBundlePriv.h>
// We make a CFBundleRef immediately, but everything else is lazy
//
BundleDiskRep::BundleDiskRep(const char *path, const Context *ctx)
- : mBundle(_CFBundleCreateUnique(NULL, CFTempURL(path)))
+ : mBundle(_CFBundleCreateUnique(NULL, CFTempURL(path))), forcePlatform(false)
{
if (!mBundle)
MacOSError::throwMe(errSecCSBadBundleFormat);
setup(ctx);
+ forcePlatform = mExecRep->appleInternalForcePlatform();
CODESIGN_DISKREP_CREATE_BUNDLE_PATH(this, (char*)path, (void*)ctx, mExecRep);
}
{
mBundle = ref; // retains
setup(ctx);
+ forcePlatform = mExecRep->appleInternalForcePlatform();
CODESIGN_DISKREP_CREATE_BUNDLE_REF(this, ref, (void*)ctx, mExecRep);
}
mInstallerPackage = false; // default
mAppLike = false; // pessimism first
bool appDisqualified = false; // found reason to disqualify as app
-
+
// capture the path of the main executable before descending into a specific version
CFRef<CFURLRef> mainExecBefore = CFBundleCopyExecutableURL(mBundle);
CFRef<CFURLRef> infoPlistBefore = _CFBundleCopyInfoPlistURL(mBundle);
CFDataRef BundleDiskRep::metaData(const char *name)
{
- return cfLoadFile(CFTempURL(metaPath(name)));
+ if (CFRef<CFURLRef> url = makeCFURL(metaPath(name))) {
+ return cfLoadFile(url);
+ } else {
+ secnotice("bundlediskrep", "no metapath for %s", name);
+ return NULL;
+ }
}
CFDataRef BundleDiskRep::metaData(CodeDirectory::SpecialSlot slot)
}
}
+BundleDiskRep::RawComponentMap BundleDiskRep::createRawComponents()
+{
+ RawComponentMap map;
+
+ /* Those are the slots known to BundleDiskReps.
+ * Unlike e.g. MachOReps, we cannot handle unknown slots,
+ * as we won't know their slot <-> filename mapping.
+ */
+ int const slots[] = {
+ cdCodeDirectorySlot, cdSignatureSlot, cdResourceDirSlot,
+ cdTopDirectorySlot, cdEntitlementSlot, cdEntitlementDERSlot,
+ cdRepSpecificSlot};
+
+ for (int slot = 0; slot < (int)(sizeof(slots)/sizeof(slots[0])); ++slot) {
+ /* Here, we only handle metaData slots, i.e. slots that
+ * are explicit files in the _CodeSignature directory.
+ * Main executable slots (if the main executable is a
+ * EditableDiskRep) are handled when editing the
+ * main executable's rep explicitly.
+ * There is also an Info.plist slot, which is not a
+ * real part of the code signature.
+ */
+ CFRef<CFDataRef> data = metaData(slot);
+
+ if (data) {
+ map[slot] = data;
+ }
+ }
+
+ for (CodeDirectory::Slot slot = cdAlternateCodeDirectorySlots; slot < cdAlternateCodeDirectoryLimit; ++slot) {
+ CFRef<CFDataRef> data = metaData(slot);
+
+ if (data) {
+ map[slot] = data;
+ }
+ }
+
+ return map;
+}
// Check that all components of this BundleDiskRep come from either the main
// executable or the _CodeSignature directory (not mix-and-match).
return mExecRep->signingLimit();
}
+size_t BundleDiskRep::execSegBase(const Architecture *arch)
+{
+ return mExecRep->execSegBase(arch);
+}
+
+size_t BundleDiskRep::execSegLimit(const Architecture *arch)
+{
+ return mExecRep->execSegLimit(arch);
+}
+
string BundleDiskRep::format()
{
return mFormat;
CFArrayRef BundleDiskRep::modifiedFiles()
{
- CFMutableArrayRef files = CFArrayCreateMutableCopy(NULL, 0, mExecRep->modifiedFiles());
+ CFRef<CFArrayRef> execFiles = mExecRep->modifiedFiles();
+ CFRef<CFMutableArrayRef> files = CFArrayCreateMutableCopy(NULL, 0, execFiles);
checkModifiedFile(files, cdCodeDirectorySlot);
checkModifiedFile(files, cdSignatureSlot);
checkModifiedFile(files, cdResourceDirSlot);
checkModifiedFile(files, cdTopDirectorySlot);
checkModifiedFile(files, cdEntitlementSlot);
+ checkModifiedFile(files, cdEntitlementDERSlot);
checkModifiedFile(files, cdRepSpecificSlot);
for (CodeDirectory::Slot slot = cdAlternateCodeDirectorySlots; slot < cdAlternateCodeDirectoryLimit; ++slot)
checkModifiedFile(files, slot);
- return files;
+ return files.yield();
}
void BundleDiskRep::checkModifiedFile(CFMutableArrayRef files, CodeDirectory::SpecialSlot slot)
"'^.*' = #T" // everything is a resource
"'^Info\\.plist$' = {omit=#T,weight=10}" // explicitly exclude this for backward compatibility
"}}");
-
- // new (V2) executable bundle rules
- return cfmake<CFDictionaryRef>("{" // *** the new (V2) world ***
- "rules={" // old (V1; legacy) version
- "'^version.plist$' = #T" // include version.plist
- "%s = #T" // include Resources
- "%s = {optional=#T, weight=1000}" // make localizations optional
- "%s = {weight=1010}" // ... except for Base.lproj which really isn't optional at all
- "%s = {omit=#T, weight=1100}" // exclude all locversion.plist files
- "},rules2={"
- "'^.*' = #T" // include everything as a resource, with the following exceptions
- "'^[^/]+$' = {nested=#T, weight=10}" // files directly in Contents
- "'^(Frameworks|SharedFrameworks|PlugIns|Plug-ins|XPCServices|Helpers|MacOS|Library/(Automator|Spotlight|LoginItems))/' = {nested=#T, weight=10}" // dynamic repositories
- "'.*\\.dSYM($|/)' = {weight=11}" // but allow dSYM directories in code locations (parallel to their code)
- "'^(.*/)?\\.DS_Store$' = {omit=#T,weight=2000}" // ignore .DS_Store files
- "'^Info\\.plist$' = {omit=#T, weight=20}" // excluded automatically now, but old systems need to be told
- "'^version\\.plist$' = {weight=20}" // include version.plist as resource
- "'^embedded\\.provisionprofile$' = {weight=20}" // include embedded.provisionprofile as resource
- "'^PkgInfo$' = {omit=#T, weight=20}" // traditionally not included
- "%s = {weight=20}" // Resources override default nested (widgets)
- "%s = {optional=#T, weight=1000}" // make localizations optional
- "%s = {weight=1010}" // ... except for Base.lproj which really isn't optional at all
- "%s = {omit=#T, weight=1100}" // exclude all locversion.plist files
- "}}",
-
- (string("^") + resources).c_str(),
- (string("^") + resources + ".*\\.lproj/").c_str(),
- (string("^") + resources + "Base\\.lproj/").c_str(),
- (string("^") + resources + ".*\\.lproj/locversion.plist$").c_str(),
-
- (string("^") + resources).c_str(),
- (string("^") + resources + ".*\\.lproj/").c_str(),
- (string("^") + resources + "Base\\.lproj/").c_str(),
- (string("^") + resources + ".*\\.lproj/locversion.plist$").c_str()
- );
+
+ // new (V2) executable bundle rules
+ if (!resources.empty()) {
+ return cfmake<CFDictionaryRef>("{" // *** the new (V2) world ***
+ "rules={" // old (V1; legacy) version
+ "'^version.plist$' = #T" // include version.plist
+ "%s = #T" // include Resources
+ "%s = {optional=#T, weight=1000}" // make localizations optional
+ "%s = {weight=1010}" // ... except for Base.lproj which really isn't optional at all
+ "%s = {omit=#T, weight=1100}" // exclude all locversion.plist files
+ "},rules2={"
+ "'^.*' = #T" // include everything as a resource, with the following exceptions
+ "'^[^/]+$' = {nested=#T, weight=10}" // files directly in Contents
+ "'^(Frameworks|SharedFrameworks|PlugIns|Plug-ins|XPCServices|Helpers|MacOS|Library/(Automator|Spotlight|LoginItems))/' = {nested=#T, weight=10}" // dynamic repositories
+ "'.*\\.dSYM($|/)' = {weight=11}" // but allow dSYM directories in code locations (parallel to their code)
+ "'^(.*/)?\\.DS_Store$' = {omit=#T,weight=2000}" // ignore .DS_Store files
+ "'^Info\\.plist$' = {omit=#T, weight=20}" // excluded automatically now, but old systems need to be told
+ "'^version\\.plist$' = {weight=20}" // include version.plist as resource
+ "'^embedded\\.provisionprofile$' = {weight=20}" // include embedded.provisionprofile as resource
+ "'^PkgInfo$' = {omit=#T, weight=20}" // traditionally not included
+ "%s = {weight=20}" // Resources override default nested (widgets)
+ "%s = {optional=#T, weight=1000}" // make localizations optional
+ "%s = {weight=1010}" // ... except for Base.lproj which really isn't optional at all
+ "%s = {omit=#T, weight=1100}" // exclude all locversion.plist files
+ "}}",
+
+ (string("^") + resources).c_str(),
+ (string("^") + resources + ".*\\.lproj/").c_str(),
+ (string("^") + resources + "Base\\.lproj/").c_str(),
+ (string("^") + resources + ".*\\.lproj/locversion.plist$").c_str(),
+
+ (string("^") + resources).c_str(),
+ (string("^") + resources + ".*\\.lproj/").c_str(),
+ (string("^") + resources + "Base\\.lproj/").c_str(),
+ (string("^") + resources + ".*\\.lproj/locversion.plist$").c_str()
+ );
+ } else {
+ /* This is a bundle format without a Resources directory, which means we need to omit
+ * Resources-directory specific rules that would create conflicts. */
+
+ /* We also declare that flat bundles do not use any nested code rules.
+ * Embedded, where flat bundles are used, currently does not support them,
+ * and we have no plans for allowing the replacement of nested code there.
+ * Should anyone actually intend to use nested code rules in a flat
+ * bundle, they are free to create their own rules. */
+
+ return cfmake<CFDictionaryRef>("{" // *** the new (V2) world ***
+ "rules={" // old (V1; legacy) version
+ "'^version.plist$' = #T" // include version.plist
+ "'^.*' = #T" // include Resources
+ "'^.*\\.lproj/' = {optional=#T, weight=1000}" // make localizations optional
+ "'^Base\\.lproj/' = {weight=1010}" // ... except for Base.lproj which really isn't optional at all
+ "'^.*\\.lproj/locversion.plist$' = {omit=#T, weight=1100}" // exclude all locversion.plist files
+ "},rules2={"
+ "'^.*' = #T" // include everything as a resource, with the following exceptions
+ "'.*\\.dSYM($|/)' = {weight=11}" // but allow dSYM directories in code locations (parallel to their code)
+ "'^(.*/)?\\.DS_Store$' = {omit=#T,weight=2000}" // ignore .DS_Store files
+ "'^Info\\.plist$' = {omit=#T, weight=20}" // excluded automatically now, but old systems need to be told
+ "'^version\\.plist$' = {weight=20}" // include version.plist as resource
+ "'^embedded\\.provisionprofile$' = {weight=20}" // include embedded.provisionprofile as resource
+ "'^PkgInfo$' = {omit=#T, weight=20}" // traditionally not included
+ "'^.*\\.lproj/' = {optional=#T, weight=1000}" // make localizations optional
+ "'^Base\\.lproj/' = {weight=1010}" // ... except for Base.lproj which really isn't optional at all
+ "'^.*\\.lproj/locversion.plist$' = {omit=#T, weight=1100}" // exclude all locversion.plist files
+ "}}"
+ );
+ }
}
// Takes an array of CFNumbers of errors to tolerate.
//
void BundleDiskRep::strictValidate(const CodeDirectory* cd, const ToleratedErrors& tolerated, SecCSFlags flags)
+{
+ strictValidateStructure(cd, tolerated, flags);
+
+ // now strict-check the main executable (which won't be an app-like object)
+ mExecRep->strictValidate(cd, tolerated, flags & ~kSecCSRestrictToAppLike);
+}
+
+void BundleDiskRep::strictValidateStructure(const CodeDirectory* cd, const ToleratedErrors& tolerated, SecCSFlags flags)
{
// scan our metadirectory (_CodeSignature) for unwanted guests
if (!(flags & kSecCSQuickCheck))
if (!mAppLike)
if (tolerated.find(kSecCSRestrictToAppLike) == tolerated.end())
MacOSError::throwMe(errSecCSNotAppLike);
-
- // now strict-check the main executable (which won't be an app-like object)
- mExecRep->strictValidate(cd, tolerated, flags & ~kSecCSRestrictToAppLike);
}
void BundleDiskRep::recordStrictError(OSStatus error)
if (const char *name = CodeDirectory::canonicalSlotName(slot)) {
rep->createMeta();
string path = rep->metaPath(name);
+
+#if TARGET_OS_OSX
+ // determine AFSC status if we are told to preserve compression
+ bool conductCompression = false;
+ cmpInfo cInfo;
+ if (this->getPreserveAFSC()) {
+ struct stat statBuffer;
+ if (stat(path.c_str(), &statBuffer) == 0) {
+ if (queryCompressionInfo(path.c_str(), &cInfo) == 0) {
+ if (cInfo.compressionType != 0 && cInfo.compressedSize > 0) {
+ conductCompression = true;
+ }
+ }
+ }
+ }
+#endif
+
AutoFileDesc fd(path, O_WRONLY | O_CREAT | O_TRUNC, 0644);
fd.writeAll(CFDataGetBytePtr(data), CFDataGetLength(data));
+ fd.close();
+
+#if TARGET_OS_OSX
+ // if the original file was compressed, compress the new file after move
+ if (conductCompression) {
+ CFMutableDictionaryRef options = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
+ CFStringRef val = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%d"), cInfo.compressionType);
+ CFDictionarySetValue(options, kAFSCCompressionTypes, val);
+ CFRelease(val);
+
+ CompressionQueueContext compressionQueue = CreateCompressionQueue(NULL, NULL, NULL, NULL, options);
+
+ if (!CompressFile(compressionQueue, path.c_str(), NULL)) {
+ secinfo("bundlediskrep", "%p Failed to queue compression of file %s", this, path.c_str());
+ MacOSError::throwMe(errSecCSInternalError);
+ }
+
+ FinishCompressionAndCleanUp(compressionQueue);
+ compressionQueue = NULL;
+ CFRelease(options);
+ }
+#endif
+
mWrittenFiles.insert(name);
} else
MacOSError::throwMe(errSecCSBadBundleFormat);
execWriter->flush();
purgeMetaDirectory();
}
-
-
+
// purge _CodeSignature of all left-over files from any previous signature
void BundleDiskRep::Writer::purgeMetaDirectory()
{
}
+void BundleDiskRep::registerStapledTicket()
+{
+ string root = cfStringRelease(copyCanonicalPath());
+ registerStapledTicketInBundle(root);
+}
} // end namespace CodeSigning
} // end namespace Security