Big news from Apple this week that forthcoming releases of Mac OS X 10.9.5 and 10.10 will enforce new policies that will render some 3rd party apps “untrusted” unless they are updated. The long and short of it is that apps using an older version of code signing will not be trusted, and even those using the newer version must forego the use of custom “resource rules,” which allowed developers to exclude certain files in a bundle from consideration as part of the code signature. A file that was thus excluded could be removed or modified without “breaking the seal” and triggering warnings about the code signature.
Jeff Johnson points out a far-reaching implication of this new limitation, which is that developers who bundle frameworks in their apps will need to see to it that the header files are either removed before signing the framework, or left in-tact. Removing the headers after signing will now necessarily lead to a broken seal and thus an untrusted app that Gatekeeper refuses to run.
Jeff’s novel solution involves overriding the default location both for where frameworks install their headers while being built, and for where host apps look for the headers of frameworks they depend upon. This seems like a reasonable solution that many people will benefit from.
Me? I’ve been burned too many times by default code signing behaviors, so I long ago switched to an approach which many will consider too complicated, but which has nonetheless saved my bacon on repeated occasions. The “resource rules” change from Apple wouldn’t have even registered on my radar, because the final code signing of all my bundled frameworks, plugins, XPC services … every darned executable, is controlled by a custom build phase which is run very late in my apps’ build process, right before the final code signing of the overall app that Xcode handles at the very end.
How does this work in practice? Here’s what MarsEdit’s build process looks like from Xcode’s build phase editor:
You can see that the process of assembling a typical Red Sweater app involves a lot of building up (copying things in), followed by a lot of tearing down, in which I remove framework headers, localizations, etc. These processes are basically fast enough that I don’t mind doing it all programmatically at build time, because it lets me worry less about the pristineness of individual components, and leaves me with with confidence that everything in the final product has been cleaned up. For the Mac App Store build I even have an additional build phase at the end called “Verify Mac App Store Requirements” in which I double check things that have bit me before in App Store submissions, but which Apple doesn’t flag themselves as part of the submission process.
That item at the very bottom is where all the good code signing happens. It’s a somewhat complicated python script that in turn relies upon a library of utility script modules I’ve written for these kinds of things, but the gist of what the “Sign Bundled Frameworks, Apps, Bundles & Tools” build phase does is:
1. Identify the target app being built. In python, you can do this using something like this in a build phase script:
appBundlePath = "%s/%s/" % (os.environ["BUILT_PRODUCTS_DIR"], os.environ["WRAPPER_NAME"])
2. Iterate through the target app bundle, looking for “executable” code. I could hard-code this for every project, but I like the relative safety of knowing that if I add a framework, tool, whatever, it will get caught in this phase. There is also a little gotcha here in that you want to find and sign the deepest items first e.g. so that you sign a helper tool in a framework before the framework that contains it. I have a python module that handles iterating for these items and returns the path to each one in appropriate order.
3. Now the moment of truth: for each item you find, you want to sign it in such a way that you give it your final blessing with all the nuanced code signing flags you care about. My actual code is a little more complicated than this in that I have special cases to avoid using Apple’s timestamp server for debug builds, and some other little tweaks. A common kind of customization you might want to make is to e.g. add arguments to codesign that will cause the preservation of entitlements, so that e.g. an XPC service with its own entitlements will not have those blown away by your re-signing of the app. See “man codesign” for more information about the “-preserve-metadata” option and other flags. But keeping things simple, the gist of signing the code manually from python in a build phase looks like this:
signingIdentity = os.environ["CODE_SIGN_IDENTITY"]
signArgs = ["/usr/bin/codesign",]
signArgs += ["-f", "-s", signingIdentity, thePath]
myCall = subprocess.Popen(signArgs, stdout=subprocess.PIPE)
myResults = myCall.communicate()
return (myCall.returncode == 0)
Pass the path of every executable tool, plugin, bundle, library, framework, and helper app to this function right before your app is finished building, and all of your custom tweaks, removed header files, adjusted content will be sealed into the final code signature, and you’ll never (ha!) have to think about it again. “Simple,” right?