]> git.saurik.com Git - apple/libsecurity_codesigning.git/blobdiff - lib/bundlediskrep.cpp
libsecurity_codesigning-55037.15.tar.gz
[apple/libsecurity_codesigning.git] / lib / bundlediskrep.cpp
index b7d3315d03c7c134912cdfd0e69cc1d0b2dcdb8d..fa5a8574e58d47a8efe0ff32d93ff52d5d412c93 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2006-2007 Apple Inc. All Rights Reserved.
+ * Copyright (c) 2006-2011 Apple Inc. All Rights Reserved.
  * 
  * @APPLE_LICENSE_HEADER_START@
  * 
  * @APPLE_LICENSE_HEADER_END@
  */
 #include "bundlediskrep.h"
+#include "filediskrep.h"
 #include <CoreFoundation/CFURLAccess.h>
 #include <CoreFoundation/CFBundlePriv.h>
 #include <security_utilities/cfmunge.h>
 #include <copyfile.h>
+#include <fts.h>
 
 
 namespace Security {
@@ -33,14 +35,20 @@ namespace CodeSigning {
 using namespace UnixPlusPlus;
 
 
+//
+// Local helpers
+//
+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(_CFBundleCreateIfMightBeBundle(NULL, CFTempURL(path)))
+       : mBundle(CFBundleCreate(NULL, CFTempURL(path)))
 {
        if (!mBundle)
-               MacOSError::throwMe(errSecCSBadObjectFormat);
+               MacOSError::throwMe(errSecCSBadBundleFormat);
        setup(ctx);
        CODESIGN_DISKREP_CREATE_BUNDLE_PATH(this, (char*)path, (void*)ctx, mExecRep);
 }
@@ -52,8 +60,12 @@ BundleDiskRep::BundleDiskRep(CFBundleRef ref, const Context *ctx)
        CODESIGN_DISKREP_CREATE_BUNDLE_REF(this, ref, (void*)ctx, mExecRep);
 }
 
+// common construction code
 void BundleDiskRep::setup(const Context *ctx)
 {
+       mInstallerPackage = false;      // default
+       
+       // deal with versioned bundles (aka Frameworks)
        string version = resourcesRootPath()
                + "/Versions/"
                + ((ctx && ctx->version) ? ctx->version : "Current")
@@ -67,7 +79,96 @@ void BundleDiskRep::setup(const Context *ctx)
                if (ctx && ctx->version)        // explicitly specified
                        MacOSError::throwMe(errSecCSStaticCodeNotFound);
        }
-       mExecRep = DiskRep::bestFileGuess(this->mainExecutablePath(), ctx);
+       
+       CFDictionaryRef infoDict = CFBundleGetInfoDictionary(mBundle);
+       assert(infoDict);       // CFBundle will always make one up for us
+       CFTypeRef mainHTML = CFDictionaryGetValue(infoDict, CFSTR("MainHTML"));
+       CFTypeRef packageVersion = CFDictionaryGetValue(infoDict, CFSTR("IFMajorVersion"));
+
+       // conventional executable bundle: CFBundle identifies an executable for us
+       if (CFRef<CFURLRef> mainExec = CFBundleCopyExecutableURL(mBundle))              // if CFBundle claims an executable...
+               if (mainHTML == NULL) {                                                                                         // ... and it's not a widget
+                       mMainExecutableURL = mainExec;
+                       mExecRep = DiskRep::bestFileGuess(this->mainExecutablePath(), ctx);
+                       mFormat = "bundle with " + mExecRep->format();
+                       return;
+               }
+       
+       // widget
+       if (mainHTML) {
+               if (CFGetTypeID(mainHTML) != CFStringGetTypeID())
+                       MacOSError::throwMe(errSecCSBadBundleFormat);
+               mMainExecutableURL.take(makeCFURL(cfString(CFStringRef(mainHTML)), false,
+                       CFRef<CFURLRef>(CFBundleCopySupportFilesDirectoryURL(mBundle))));
+               if (!mMainExecutableURL)
+                       MacOSError::throwMe(errSecCSBadBundleFormat);
+               mExecRep = new FileDiskRep(this->mainExecutablePath().c_str());
+               mFormat = "widget bundle";
+               return;
+       }
+       
+       // do we have a real Info.plist here?
+       if (CFRef<CFURLRef> infoURL = _CFBundleCopyInfoPlistURL(mBundle)) {
+               // focus on the Info.plist (which we know exists) as the nominal "main executable" file
+               if ((mMainExecutableURL = _CFBundleCopyInfoPlistURL(mBundle))) {
+                       mExecRep = new FileDiskRep(this->mainExecutablePath().c_str());
+                       if (packageVersion) {
+                               mInstallerPackage = true;
+                               mFormat = "installer package bundle";
+                       } else {
+                               mFormat = "bundle";
+                       }
+                       return;
+               }
+       }
+
+       // we're getting desperate here. Perhaps an oldish-style installer package? Look for a *.dist file
+       std::string distFile = findDistFile(this->resourcesRootPath());
+       if (!distFile.empty()) {
+               mMainExecutableURL = makeCFURL(distFile);
+               mExecRep = new FileDiskRep(this->mainExecutablePath().c_str());
+               mInstallerPackage = true;
+               mFormat = "installer package bundle";
+               return;
+       }
+       
+       // this bundle cannot be signed
+       MacOSError::throwMe(errSecCSBadBundleFormat);
+}
+
+
+//
+// Return the full path to the one-and-only file named something.dist in a directory.
+// Return empty string if none; throw an exception if multiple. Do not descend into subdirectories.
+//
+static std::string findDistFile(const std::string &directory)
+{
+       std::string found;
+       char *paths[] = {(char *)directory.c_str(), NULL};
+       FTS *fts = fts_open(paths, FTS_PHYSICAL | FTS_NOCHDIR | FTS_NOSTAT, NULL);
+       bool root = true;
+       while (FTSENT *ent = fts_read(fts)) {
+               switch (ent->fts_info) {
+               case FTS_F:
+               case FTS_NSOK:
+                       if (!strcmp(ent->fts_path + ent->fts_pathlen - 5, ".dist")) {   // found plain file foo.dist
+                               if (found.empty())      // first found
+                                       found = ent->fts_path;
+                               else                            // multiple *.dist files (bad)
+                                       MacOSError::throwMe(errSecCSBadBundleFormat);
+                       }
+                       break;
+               case FTS_D:
+                       if (!root)
+                               fts_set(fts, ent, FTS_SKIP);    // don't descend
+                       root = false;
+                       break;
+               default:
+                       break;
+               }
+       }
+       fts_close(fts);
+       return found;
 }
 
 
@@ -79,7 +180,7 @@ void BundleDiskRep::setup(const Context *ctx)
 string BundleDiskRep::metaPath(const char *name)
 {
        if (mMetaPath.empty()) {
-               string support = cfString(CFBundleCopySupportFilesDirectoryURL(mBundle), true);
+               string support = cfStringRelease(CFBundleCopySupportFilesDirectoryURL(mBundle));
                mMetaPath = support + "/" BUNDLEDISKREP_DIRECTORY;
                if (::access(mMetaPath.c_str(), F_OK) == 0) {
                        mMetaExists = true;
@@ -161,21 +262,21 @@ CFURLRef BundleDiskRep::canonicalPath()
 
 string BundleDiskRep::mainExecutablePath()
 {
-       if (CFURLRef exec = CFBundleCopyExecutableURL(mBundle))
-               return cfString(exec, true);
-       else
-               MacOSError::throwMe(errSecCSNoMainExecutable);
+       return cfString(mMainExecutableURL);
 }
 
 string BundleDiskRep::resourcesRootPath()
 {
-       return cfString(CFBundleCopySupportFilesDirectoryURL(mBundle), true);
+       return cfStringRelease(CFBundleCopySupportFilesDirectoryURL(mBundle));
 }
 
 void BundleDiskRep::adjustResources(ResourceBuilder &builder)
 {
        // exclude entire contents of meta directory
        builder.addExclusion("^" BUNDLEDISKREP_DIRECTORY "/");
+
+       // exclude the store manifest directory
+       builder.addExclusion("^" STORE_RECEIPT_DIRECTORY "/");
        
        // exclude the main executable file
        string resources = resourcesRootPath();
@@ -204,7 +305,7 @@ size_t BundleDiskRep::signingLimit()
 
 string BundleDiskRep::format()
 {
-       return string("bundle with ") + mExecRep->format();
+       return mFormat;
 }
 
 CFArrayRef BundleDiskRep::modifiedFiles()
@@ -256,12 +357,39 @@ string BundleDiskRep::recommendedIdentifier(const SigningContext &)
 
 CFDictionaryRef BundleDiskRep::defaultResourceRules(const SigningContext &)
 {
+       // consider the bundle's structure
+       string rbase = this->resourcesRootPath();
+       if (rbase.substr(rbase.length()-2, 2) == "/.")  // produced by versioned bundle implicit "Current" case
+               rbase = rbase.substr(0, rbase.length()-2);      // ... so take it off for this
+       string resources = cfStringRelease(CFBundleCopyResourcesDirectoryURL(mBundle));
+       if (resources == rbase)
+               resources = "";
+       else if (resources.compare(0, rbase.length(), rbase, 0, rbase.length()) != 0)   // Resources not in resource root
+               MacOSError::throwMe(errSecCSBadBundleFormat);
+       else
+               resources = resources.substr(rbase.length() + 1) + "/"; // differential path segment
+
+       // installer package rules
+       if (mInstallerPackage)
+               return cfmake<CFDictionaryRef>("{rules={"
+                       "'^.*' = #T"                                                    // include everything, but...
+                       "%s = {optional=#T, weight=1000}"               // make localizations optional
+                       "'^.*/.*\\.pkg/' = {omit=#T, weight=10000}" // and exclude all nested packages (by name)
+                       "}}",
+                       (string("^") + resources + ".*\\.lproj/").c_str()
+               );
+       
+       // executable bundle rules
        return cfmake<CFDictionaryRef>("{rules={"
-               "'^version.plist$' = #T"
-               "'^Resources/' = #T"
-               "'^Resources/.*\\.lproj/' = {optional=#T, weight=1000}"
-               "'^Resources/.*\\.lproj/locversion.plist$' = {omit=#T, weight=1100}"
-               "}}");
+               "'^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
+               "}}",
+               (string("^") + resources).c_str(),
+               (string("^") + resources + ".*\\.lproj/").c_str(),
+               (string("^") + resources + ".*\\.lproj/locversion.plist$").c_str()
+       );
 }
 
 const Requirements *BundleDiskRep::defaultRequirements(const Architecture *arch, const SigningContext &ctx)
@@ -310,7 +438,7 @@ void BundleDiskRep::Writer::component(CodeDirectory::SpecialSlot slot, CFDataRef
                        AutoFileDesc fd(path, O_WRONLY | O_CREAT | O_TRUNC, 0644);
                        fd.writeAll(CFDataGetBytePtr(data), CFDataGetLength(data));
                } else
-                       MacOSError::throwMe(errSecCSBadObjectFormat);
+                       MacOSError::throwMe(errSecCSBadBundleFormat);
        }
 }