Breaking the resource rules

August 5, 2014

Read it and weep: Changes in OS X 10.9.5 and Yosemite Developer Preview 5. Needless to say, this is the biggest change to Gatekeeper since the introduction of Gatekeeper. It's essentially Gatekeeper 2.

My company's apps contain embedded frameworks. You could say that the majority of the code in the apps is framework code. This is how we're able to create a suite of apps, by building on existing code and sharing it among the apps. In our repository, each framework has its own Xcode project, and each app has its own Xcode project too. In an app, we use an embedded framework in the standard way that you would use a framework: include the framework bundle in the build setting FRAMEWORK_SEARCH_PATHS, include the framework headers with #import <FrameworkName/HeaderName.h>, and include the framework in the link build phase.

Since these are embedded, private frameworks, not intended for public use, we don't want to ship the framework headers along with the apps. There are things in the headers that we don't want anyone to see. (For various reasons, including embarrassment.) However, we still need the app to be able to find the headers in order for the #import statements to compile. So, we had a tool that deleted the headers from the app bundle after it built. Before code signing, Developer ID, and Gatekeeper, this was not a problem. Indeed, it was not a problem for a long time even after Gatekeeper, because frameworks were not initially required to be code signed. Once Apple added that requirement, however, we had a problem, because if you delete the headers from the framework bundle, you modify the bundle and invalidate its code signature.

This is where resource rules come in. The codesign tool has a --resource-rules flag that allows you to override the default resource rules. You can use custom resource rules to exclude certain files from the code signature. We used custom resource rules to exclude the header files from a framework's code signature, which enabled us to delete the headers from the bundle without invalidating the signature. The --resource-rules flag could be set in Xcode via the build setting CODE_SIGN_RESOURCE_RULES_PATH.

As TN2206 indicates, custom resource rules no longer work on 10.9.5 or Yosemite Developer Preview 5. The effect, for us, is that none of our shipping apps will currently be accepted by Gatekeeper on those versions of OS X. This was quite a shock. Fortunately, there is a "happy" ending to the story. We needed a solution to the problem quickly, and ideally a solution that didn't require us to completely rearrange our source code repository. The potent combination of desperation and brilliance, if I may be so modest, led me to the solution.

Our saving grace was that all of our Xcode projects use shared xcconfig files, which allows us to make repository-wide changes to build settings without having to edit each of the countless individual Xcode project files. The fix itself was pretty simple. First, we needed to move the header files out of the framework bundles, so that the headers no longer needed to be stripped. This was accomplished with PRIVATE_HEADERS_FOLDER_PATH = $(PRODUCT_NAME). Instead of putting the Foo.framework headers in the default PrivateHeaders/ folder in the bundle, Xcode now puts them in a folder named Foo/ in the build folder. Second, we needed the apps to be able to find the headers in their new location. This was accomplished with HEADER_SEARCH_PATHS = $(FRAMEWORK_SEARCH_PATHS). The trick here is that the apps already have their $(FRAMEWORK_SEARCH_PATHS) set to the correct value to find the framework bundles, so the apps already know where the framework build folders are located. The use of $(PRODUCT_NAME) was crucial to make sure that <FrameworkName/HeaderName.h> still worked, so that we didn't have to rewrite every #import statement in the repository.

There you have it. I'm offering this solution to everyone for free, although if you feel inspired to send me 4-5 figure checks, I won't be offended. I'm not going to make any editorial comments here on the change to Gatekeeper in 10.9.5, because this post is intended for developers, to aid them in their work. And my comments would be NSFW.