]> git.saurik.com Git - apple/security.git/blobdiff - OSX/libsecurity_codesigning/lib/bundlediskrep.cpp
Security-59306.61.1.tar.gz
[apple/security.git] / OSX / libsecurity_codesigning / lib / bundlediskrep.cpp
index 571c33e12a592fef51d6823c81e065a78840ed9d..7c4909702644b1685db09b6dac96f61e0f53336b 100644 (file)
@@ -23,6 +23,7 @@
 #include "bundlediskrep.h"
 #include "filediskrep.h"
 #include "dirscanner.h"
+#include "notarization.h"
 #include <CoreFoundation/CFBundlePriv.h>
 #include <CoreFoundation/CFURLAccess.h>
 #include <CoreFoundation/CFBundlePriv.h>
@@ -47,11 +48,12 @@ static std::string findDistFile(const std::string &directory);
 // We make a CFBundleRef immediately, but everything else is lazy
 //
 BundleDiskRep::BundleDiskRep(const char *path, const Context *ctx)
-       : mBundle(CFBundleCreate(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);
 }
 
@@ -59,6 +61,7 @@ BundleDiskRep::BundleDiskRep(CFBundleRef ref, const Context *ctx)
 {
        mBundle = ref;          // retains
        setup(ctx);
+       forcePlatform = mExecRep->appleInternalForcePlatform();
        CODESIGN_DISKREP_CREATE_BUNDLE_REF(this, ref, (void*)ctx, mExecRep);
 }
 
@@ -74,7 +77,7 @@ void BundleDiskRep::checkMoved(CFURLRef oldPath, CFURLRef newPath)
        // to their "Current" version binary in the main bundle
        if (realpath(cfString(oldPath).c_str(), cOld) == NULL ||
                realpath(cfString(newPath).c_str(), cNew) == NULL)
-               MacOSError::throwMe(errSecCSInternalError);
+               MacOSError::throwMe(errSecCSAmbiguousBundleFormat);
        
        if (strcmp(cOld, cNew) != 0)
                recordStrictError(errSecCSAmbiguousBundleFormat);
@@ -83,6 +86,7 @@ void BundleDiskRep::checkMoved(CFURLRef oldPath, CFURLRef newPath)
 // common construction code
 void BundleDiskRep::setup(const Context *ctx)
 {
+       mComponentsFromExecValid = false; // not yet known
        mInstallerPackage = false;      // default
        mAppLike = false;                       // pessimism first
        bool appDisqualified = false; // found reason to disqualify as app
@@ -93,6 +97,8 @@ void BundleDiskRep::setup(const Context *ctx)
 
        // validate the bundle root; fish around for the desired framework version
        string root = cfStringRelease(copyCanonicalPath());
+       if (filehasExtendedAttribute(root, XATTR_FINDERINFO_NAME))
+               recordStrictError(errSecCSInvalidAssociatedFileData);
        string contents = root + "/Contents";
        string supportFiles = root + "/Support Files";
        string version = root + "/Versions/"
@@ -111,7 +117,7 @@ void BundleDiskRep::setup(const Context *ctx)
                // treat like a shallow bundle; do not allow Versions arbitration
                appDisqualified = true;
        } else if (::access(version.c_str(), F_OK) == 0) {      // versioned bundle
-               if (CFBundleRef versionBundle = CFBundleCreate(NULL, CFTempURL(version)))
+               if (CFBundleRef versionBundle = _CFBundleCreateUnique(NULL, CFTempURL(version)))
                        mBundle.take(versionBundle);    // replace top bundle ref
                else
                        MacOSError::throwMe(errSecCSStaticCodeNotFound);
@@ -145,8 +151,7 @@ void BundleDiskRep::setup(const Context *ctx)
 
                        mMainExecutableURL = mainExec;
                        mExecRep = DiskRep::bestFileGuess(this->mainExecutablePath(), ctx);
-                       if (!mExecRep->fd().isPlainFile(this->mainExecutablePath()))
-                               recordStrictError(errSecCSRegularFile);
+                       checkPlainFile(mExecRep->fd(), this->mainExecutablePath());
                        CFDictionaryRef infoDict = CFBundleGetInfoDictionary(mBundle);
                        bool isAppBundle = false;
                        if (infoDict)
@@ -169,8 +174,7 @@ void BundleDiskRep::setup(const Context *ctx)
                if (!mMainExecutableURL)
                        MacOSError::throwMe(errSecCSBadBundleFormat);
                mExecRep = new FileDiskRep(this->mainExecutablePath().c_str());
-               if (!mExecRep->fd().isPlainFile(this->mainExecutablePath()))
-                       recordStrictError(errSecCSRegularFile);
+               checkPlainFile(mExecRep->fd(), this->mainExecutablePath());
                mFormat = "widget bundle";
                mAppLike = true;
                return;
@@ -181,8 +185,7 @@ void BundleDiskRep::setup(const Context *ctx)
                // focus on the Info.plist (which we know exists) as the nominal "main executable" file
                mMainExecutableURL = infoURL;
                mExecRep = new FileDiskRep(this->mainExecutablePath().c_str());
-               if (!mExecRep->fd().isPlainFile(this->mainExecutablePath()))
-                       recordStrictError(errSecCSRegularFile);
+               checkPlainFile(mExecRep->fd(), this->mainExecutablePath());
                if (packageVersion) {
                        mInstallerPackage = true;
                        mFormat = "installer package bundle";
@@ -197,8 +200,7 @@ void BundleDiskRep::setup(const Context *ctx)
        if (!distFile.empty()) {
                mMainExecutableURL = makeCFURL(distFile);
                mExecRep = new FileDiskRep(this->mainExecutablePath().c_str());
-               if (!mExecRep->fd().isPlainFile(this->mainExecutablePath()))
-                       recordStrictError(errSecCSRegularFile);
+               checkPlainFile(mExecRep->fd(), this->mainExecutablePath());
                mInstallerPackage = true;
                mFormat = "installer package bundle";
                return;
@@ -244,27 +246,6 @@ static std::string findDistFile(const std::string &directory)
 }
 
 
-//
-// Create a path to a bundle signing resource, by name.
-// 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::metaPath(const char *name)
-{
-       if (mMetaPath.empty()) {
-               string support = cfStringRelease(CFBundleCopySupportFilesDirectoryURL(mBundle));
-               mMetaPath = support + "/" BUNDLEDISKREP_DIRECTORY;
-               if (::access(mMetaPath.c_str(), F_OK) == 0) {
-                       mMetaExists = true;
-               } else {
-                       mMetaPath = support;
-                       mMetaExists = false;
-               }
-       }
-       return mMetaPath + "/" + name;
-}
-
-
 //
 // Try to create the meta-file directory in our bundle.
 // Does nothing if the directory already exists.
@@ -272,7 +253,7 @@ string BundleDiskRep::metaPath(const char *name)
 //
 void BundleDiskRep::createMeta()
 {
-       string meta = metaPath(BUNDLEDISKREP_DIRECTORY);
+       string meta = metaPath(NULL);
        if (!mMetaExists) {
                if (::mkdir(meta.c_str(), 0755) == 0) {
                        copyfile(cfStringRelease(copyCanonicalPath()).c_str(), meta.c_str(), NULL, COPYFILE_SECURITY);
@@ -282,7 +263,45 @@ void BundleDiskRep::createMeta()
                        UnixError::throwMe();
        }
 }
+       
 
+//
+// Create a path to a bundle signing resource, by name.
+// This is in the BUNDLEDISKREP_DIRECTORY directory in the bundle's support directory.
+//
+string BundleDiskRep::metaPath(const char *name)
+{
+       if (mMetaPath.empty()) {
+               string support = cfStringRelease(CFBundleCopySupportFilesDirectoryURL(mBundle));
+               mMetaPath = support + "/" BUNDLEDISKREP_DIRECTORY;
+               mMetaExists = ::access(mMetaPath.c_str(), F_OK) == 0;
+       }
+       if (name)
+               return mMetaPath + "/" + name;
+       else
+               return mMetaPath;
+}
+       
+CFDataRef BundleDiskRep::metaData(const char *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)
+{
+       if (const char *name = CodeDirectory::canonicalSlotName(slot))
+               return metaData(name);
+       else
+               return NULL;
+}
+
+
+       
 //
 // Load's a CFURL and makes sure that it is a regular file and not a symlink (or fifo, etc.)
 //
@@ -296,14 +315,13 @@ CFDataRef BundleDiskRep::loadRegularFile(CFURLRef url)
 
        AutoFileDesc fd(path);
 
-       if (!fd.isPlainFile(path))
-               recordStrictError(errSecCSRegularFile);
+       checkPlainFile(fd, path);
 
        data = cfLoadFile(fd, fd.fileSize());
 
        if (!data) {
-               secdebug(__PRETTY_FUNCTION__, "failed to load %s", cfString(url).c_str());
-               MacOSError::throwMe(errSecCSInternalError);
+               secinfo("bundlediskrep", "failed to load %s", cfString(url).c_str());
+               MacOSError::throwMe(errSecCSInvalidSymlink);
        }
 
        return data;
@@ -313,8 +331,10 @@ CFDataRef BundleDiskRep::loadRegularFile(CFURLRef url)
 // Load and return a component, by slot number.
 // Info.plist components come from the bundle, always (we don't look
 // for Mach-O embedded versions).
+// ResourceDirectory always comes from bundle files.
 // Everything else comes from the embedded blobs of a Mach-O image, or from
-// files located in the Contents directory of the bundle.
+// files located in the Contents directory of the bundle; but we must be consistent
+// (no half-and-half situations).
 //
 CFDataRef BundleDiskRep::component(CodeDirectory::SpecialSlot slot)
 {
@@ -325,17 +345,75 @@ CFDataRef BundleDiskRep::component(CodeDirectory::SpecialSlot slot)
                        return loadRegularFile(info);
                else
                        return NULL;
-       // by default, we take components from the executable image or files
-       default:
-               if (CFDataRef data = mExecRep->component(slot))
-                       return data;
-               // falling through
-       // but the following always come from files
        case cdResourceDirSlot:
-               if (const char *name = CodeDirectory::canonicalSlotName(slot))
-                       return metaData(name);
-               else
-                       return NULL;
+               mUsedComponents.insert(slot);
+               return metaData(slot);
+       // by default, we take components from the executable image or files (but not both)
+       default:
+               if (CFRef<CFDataRef> data = mExecRep->component(slot)) {
+                       componentFromExec(true);
+                       return data.yield();
+               }
+               if (CFRef<CFDataRef> data = metaData(slot)) {
+                       componentFromExec(false);
+                       mUsedComponents.insert(slot);
+                       return data.yield();
+               }
+               return NULL;
+       }
+}
+
+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).
+void BundleDiskRep::componentFromExec(bool fromExec)
+{
+       if (!mComponentsFromExecValid) {
+               // first use; set latch
+               mComponentsFromExecValid = true;
+               mComponentsFromExec = fromExec;
+       } else if (mComponentsFromExec != fromExec) {
+               // subsequent use: check latch
+               MacOSError::throwMe(errSecCSSignatureFailed);
        }
 }
 
@@ -411,6 +489,16 @@ size_t BundleDiskRep::signingLimit()
        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;
@@ -418,16 +506,18 @@ string BundleDiskRep::format()
 
 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)
@@ -451,6 +541,10 @@ void BundleDiskRep::flush()
        mExecRep->flush();
 }
 
+CFDictionaryRef BundleDiskRep::diskRepInformation()
+{
+    return mExecRep->diskRepInformation();
+}
 
 //
 // Defaults for signing operations
@@ -524,37 +618,73 @@ CFDictionaryRef BundleDiskRep::defaultResourceRules(const SigningContext &ctx)
                        "'^.*' = #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 = {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 = {omit=#T, weight=1100}"                           // exclude all locversion.plist files
-               "}}",
-                       
-               (string("^") + resources).c_str(),
-               (string("^") + resources + ".*\\.lproj/").c_str(),
-               (string("^") + resources + ".*\\.lproj/locversion.plist$").c_str(),
-                       
-               (string("^") + resources).c_str(),
-               (string("^") + resources + ".*\\.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
+            "}}"
+        );
+   }
 }
 
 
@@ -588,6 +718,22 @@ size_t BundleDiskRep::pageSize(const SigningContext &ctx)
 //
 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))
+               validateMetaDirectory(cd);
+       
+       // check accumulated strict errors and report them
+       if (!(flags & kSecCSRestrictSidebandData))      // tolerate resource forks etc.
+               mStrictErrors.erase(errSecCSInvalidAssociatedFileData);
+       
        std::vector<OSStatus> fatalErrors;
        set_difference(mStrictErrors.begin(), mStrictErrors.end(), tolerated.begin(), tolerated.end(), back_inserter(fatalErrors));
        if (!fatalErrors.empty())
@@ -598,9 +744,6 @@ void BundleDiskRep::strictValidate(const CodeDirectory* cd, const ToleratedError
                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)
@@ -609,6 +752,45 @@ void BundleDiskRep::recordStrictError(OSStatus error)
 }
 
 
+void BundleDiskRep::validateMetaDirectory(const CodeDirectory* cd)
+{
+       // we know the resource directory will be checked after this call, so we'll give it a pass here
+       if (cd->slotIsPresent(-cdResourceDirSlot))
+               mUsedComponents.insert(cdResourceDirSlot);
+       
+       // make a set of allowed (regular) filenames in this directory
+       std::set<std::string> allowedFiles;
+       for (auto it = mUsedComponents.begin(); it != mUsedComponents.end(); ++it) {
+               switch (*it) {
+               case cdInfoSlot:
+                       break;          // always from Info.plist, not from here
+               default:
+                       if (const char *name = CodeDirectory::canonicalSlotName(*it)) {
+                               allowedFiles.insert(name);
+                       }
+                       break;
+               }
+       }
+       DirScanner scan(mMetaPath);
+       if (scan.initialized()) {
+               while (struct dirent* ent = scan.getNext()) {
+                       if (!scan.isRegularFile(ent))
+                               MacOSError::throwMe(errSecCSUnsealedAppRoot);   // only regular files allowed
+                       if (allowedFiles.find(ent->d_name) == allowedFiles.end()) {     // not in expected set of files
+                               if (strcmp(ent->d_name, kSecCS_SIGNATUREFILE) == 0) {
+                                       // special case - might be empty and unused (adhoc signature)
+                                       AutoFileDesc fd(metaPath(kSecCS_SIGNATUREFILE));
+                                       if (fd.fileSize() == 0)
+                                               continue;       // that's okay, then
+                               }
+                               // not on list of needed files; it's a freeloading rogue!
+                               recordStrictError(errSecCSUnsealedAppRoot);     // funnel through strict set so GKOpaque can override it
+                       }
+               }
+       }
+}
+
+
 //
 // Check framework root for unsafe symlinks and unsealed content.
 //
@@ -644,6 +826,23 @@ void BundleDiskRep::validateFrameworkRoot(string root)
        }
 }
 
+       
+//
+// Check a file descriptor for harmlessness. This is a strict check (only).
+//
+void BundleDiskRep::checkPlainFile(FileDesc fd, const std::string& path)
+{
+       if (!fd.isPlainFile(path))
+               recordStrictError(errSecCSRegularFile);
+       checkForks(fd);
+}
+       
+void BundleDiskRep::checkForks(FileDesc fd)
+{
+       if (fd.hasExtendedAttribute(XATTR_RESOURCEFORK_NAME) || fd.hasExtendedAttribute(XATTR_FINDERINFO_NAME))
+               recordStrictError(errSecCSInvalidAssociatedFileData);
+}
+
 
 //
 // Writers
@@ -677,8 +876,49 @@ void BundleDiskRep::Writer::component(CodeDirectory::SpecialSlot slot, CFDataRef
                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);
        }
@@ -715,8 +955,30 @@ void BundleDiskRep::Writer::remove(CodeDirectory::SpecialSlot slot)
 void BundleDiskRep::Writer::flush()
 {
        execWriter->flush();
+       purgeMetaDirectory();
 }
 
+// purge _CodeSignature of all left-over files from any previous signature
+void BundleDiskRep::Writer::purgeMetaDirectory()
+{
+       DirScanner scan(rep->mMetaPath);
+       if (scan.initialized()) {
+               while (struct dirent* ent = scan.getNext()) {
+                       if (!scan.isRegularFile(ent))
+                               MacOSError::throwMe(errSecCSUnsealedAppRoot);   // only regular files allowed
+                       if (mWrittenFiles.find(ent->d_name) == mWrittenFiles.end()) {   // we didn't write this!
+                               scan.unlink(ent, 0);
+                       }
+               }
+       }
+       
+}
+
+void BundleDiskRep::registerStapledTicket()
+{
+       string root = cfStringRelease(copyCanonicalPath());
+       registerStapledTicketInBundle(root);
+}
 
 } // end namespace CodeSigning
 } // end namespace Security