Code Signing and Mavericks

The Change

Very simply put, you can no longer sign a bundle (like your .app) if any nested bundle in that package is unsigned. These nested bundles are things like helper executables, embedded frameworks, plug-ins and XPC services.

The result is that you’ll need to update your Xcode projects as soon as you start building on 10.9. It’s taken me several days to understand what these changes are, and with the help of Perry Kiehtreiber on the developer forums, I’d like to share what I’ve learned.

(Yes, I realize this essay is going to break the NDA, but since Apple is asking us to submit apps for Mavericks, I want as many developers as possible to avoid the utter confusion I faced earlier this week.)

The Effect

So what happens when you do your first app build on 10.9 using Xcode 5.0.1? If you embed a framework that’s unsigned, like the very popular Sparkle.framework, you’ll see a message during the final CodeSign build phase:

CodeSign build/Release/xScope.app
    cd /Users/craig/Projects/Mac/xScope
    setenv CODESIGN_ALLOCATE /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/codesign_allocate
    Using code signing identity "Developer ID Application: The Iconfactory"
    /usr/bin/codesign --force --sign D2A3FE1814B0BA31B1924F1C3C3B5C89643FBED5 --requirements =designated\ =>\ anchor\ apple\ generic\ \ and\ identifier\ \"xScope\"\ and\ ((cert\ leaf[field.1.2.840.113635.100.6.1.9]\ exists)\ or\ (\ certificate\ 1[field.1.2.840.113635.100.6.2.6]\ exists\ and\ certificate\ leaf[field.1.2.840.113635.100.6.1.13]\ exists\ \ and\ certificate\ leaf[subject.OU]\ =\ \"RYQWBTQRPT\"\ )) /Users/craig/Projects/Mac/xScope/build/Release/xScope.app
/Users/craig/Projects/Mac/xScope/build/Release/xScope.app: code object is not signed at all
In subcomponent: /Users/craig/Projects/Mac/xScope/build/Release/xScope.app/Contents/Frameworks/Sparkle.framework
Command /usr/bin/codesign failed with exit code 1

** BUILD FAILED **

The codesign command is reporting that “code object is not signed at all” and Xcode is adding the “In subcomponent” to tell you which framework is at fault (it could just have easily been HockeyApp or any other third-party framework you use.)

So how do you go about fixing this?

The Wrong Way

In the past, many developers have relied on codesign‘s --deep option to make sure the entire bundle is signed. Specifying this option in “Other Code Signing Flags” will get rid of the error during the build, but all you’re doing is just postponing the pain.

The reason is that --deep recursively signs the nested bundles. As it does this, it applies the parameters for the top-level bundle to all the nested bundles. Things like your app’s entitlements will cause the resulting bundles to not be valid.

In fact, if you try to download and install the resulting app, Gatekeeper will notify your customers that your app is damaged and can’t be opened, with a default button to move it to the Trash:

You’ll see the same thing if you check the binary using the command line:

$ spctl --verbose=4 --assess --type execute build/Release/xScope.app
build/Release/xScope.app: a sealed resource is missing or invalid

The Right Way

What’s the right way to make sure the embedded framework is correctly signed? The answer is to add another Build Phase to your target.

If you’re embedding frameworks, you’ll have a “Copy Files” phase that moves things like Sparkle.framework into the Frameworks destination. Just after this Build Phase, add a Run Script with the following shell commands:

LOCATION="${BUILT_PRODUCTS_DIR}"/"${FRAMEWORKS_FOLDER_PATH}"
IDENTITY="Developer ID Application: The Iconfactory"
codesign --verbose --force --sign "$IDENTITY" "$LOCATION/Sparkle.framework/Versions/A"

This short script tells Xcode to sign the framework that’s just been copied into the build product. In this case, it’s using the Developer ID for Gatekeeper. If you were doing a build for the Mac App Store you’d use your “3rd Party Mac Developer Application” identity. Add a codesign command for every framework you use.

If you have other embedded code, such as helper executables, plug-ins or XPC services, you’ll need to sign them appropriately after copying them into your app bundle.

Updated October 18th, 2013: Another alternative is to set the code signing identity in the Build Settings of the frameworks you’re building from source. The trick here is that the identity of the framework needs to match the identity of the app itself. You can’t have have an App Store distribution identity for the framework and a Developer ID for the app. I found it much easier to explicitly re-sign the frameworks than to pass configuration settings from MyApp.xcodeproj to MyFramework.xcodeproj. It’s also easier to manage because the project changes are the same for binary-only frameworks (like Sparkle) and frameworks we build from source (like Chameleon).

The Checks

You’ll want to do a quick check of the build product before uploading it to either your website or iTunes Connect. The first thing you’ll want to do is check the signed bundle meets its designated requirement:

$ codesign --verify --verbose=4 build/Release/xScope.app
--validated:/Users/craig/Projects/Mac/xScope/build/Release/xScope.app/Contents/Frameworks/Sparkle.framework/Versions/Current/.
build/Release/xScope.app: valid on disk
build/Release/xScope.app: satisfies its Designated Requirement

If there’s a problem, you’ll see a message that the app “does not satisfy its designated Requirement”. To view information about the signed code or the designated requirements, you can use these commands:

$ codesign --display --verbose=4 build/Release/xScope.app
$ codesign --display --verbose=4 build/Release/xScope.app/Contents/Frameworks/Sparkle.framework
$ codesign --display --requirements - --verbose=4 build/Release/xScope.app
$ codesign --display --requirements - --verbose=4 build/Release/xScope.app/Contents/Frameworks/Sparkle.framework

If this is a build you’ll be uploading to your website, you’ll want to make sure it will be accepted by Gatekeeper (and not display the “damaged” dialog.) Use spctl to do this:

$ spctl --verbose=4 --assess --type execute build/Release/xScope.app
build/Release/xScope.app: accepted
source=Developer ID

If this is an App Store build, you MUST check the .pkg file that gets uploaded to iTunes Connect (see the next section and you’ll see why I say MUST.) If you use productbuild to create the package manually, you’ll already have a .pkg file to test.

For those of you who submit archives directly from Xcode, you can generate the .pkg file using the command line:

$ xcodebuild -exportArchive -exportFormat PKG -archivePath /path/to/your.xcarchive -exportPath /tmp/CHOCKS -exportSigningIdentity "3rd Party Mac Developer Application: CHOCK LOCK INK” -exportInstallerIdentity "3rd Party Mac Developer Installer:  CHOCK LOCK INK"

You can find the path to your .xcarchive by selecting it in the Organizer and then using the Editor > Show in Finder menu item. The command above will create a /tmp/CHOCKS.pkg. Yes, you now have CHOCKS PACKAGE IF YOU KNOW WHAT I MEAN

(A quick side note, if you use xcodebuild, it got a lot of love in Mavericks. Make sure to check out the man page.)

To check out CHOCKS.pkg, run the installer with the -store option:

$ sudo installer -store -pkg /tmp/CHOCKS.pkg -target /
installer: Note: running installer as an admin user (instead of root) gives better Mac App Store fidelity
installer: CHOCKS.pkg has valid signature for submission: 3rd Party Mac Developer Installer: The Iconfactory
installer: Installation Check: Passed
installer: Volume Check: Passed
installer: Bundle com.artissoftware.mac.xScope will be installed to /Applications/xScope.app
installer: Starting install
installer: Install 0.0% complete
installer: Install 13.8% complete
installer: Install 22.2% complete
installer: Install 47.6% complete
installer: Install 88.3% complete
installer: Install 100.0% complete
installer: Finished install

Now, sign out of the App Store and launch the app that was just installed in your Applications folder. If everything is OK, you’ll see the prompt for your Apple ID and a receipt will be written in the app’s _MASReceipt folder.

But not always.

(For more information on testing installer packages, check out the Testing section in my Mac App Store Guide.)

The Suckage

After being installed for the first time, some apps never get a receipt when they are launched on Mavericks. The app starts up, sees that there’s no receipt in /Contents/_MASReceipt and signals that it’s missing by exiting with a 173 code. Normally, storeagent will recognize this and prompt for an Apple ID. After valid credentials are provided, the receipt is written and the app is launched again.

Several developers, myself included, have noticed that after exiting with a 173, only the following is logged in the console:

Oct 17 11:59:03 Myrtle.local storeagent[72031]: Unsigned app (/Applications/Twitterrific.app).

If this happens to you, it seems your only course of action is to not validate the receipt. Your code will launch fine if you never return a 173. Which, of course, sucks because it’s then trivial to pirate your the app.

For any Apple folks that might be reading this, check out the Radar: rdar://problem/15254213

Updated October 18th, 2013: Developers that are doing their builds on 10.8 also need to watch out for this problem. As a workaround, I tried building the product on 10.8.4 using Xcode 5.0. The resulting .pkg exhibited the same behavior at launch time as the one created with the Xcode GM on Mavericks.

The Workarounds

Xcode sometimes has problems generating a valid designated requirement. When you check the designated requirement, you might see this:

$ codesign --verify --verbose=1 build/Release/Twitterrific.app
build/Release/Twitterrific.app: valid on disk
build/Release/Twitterrific.app: does not satisfy its designated Requirement

$ codesign --display --requirements - build/Release/Twitterrific.app
Executable=/Users/craig/Desktop/BugReport/Ostrich/build/Release/Twitterrific.app/Contents/MacOS/Twitterrific
designated => anchor apple generic and identifier Twitterrific and (certificate leaf[field.1.2.840.113635.100.6.1.9] /* exists */ or certificate 1[field.1.2.840.113635.100.6.2.6] /* exists */ and certificate leaf[field.1.2.840.113635.100.6.1.13] /* exists */ and certificate leaf[subject.OU] = RYQWBTQRPT)

If you look closely, you’ll see that the identifier used for the requirement is incorrect: it should be com.iconfactory.Twitterrific, not just Twitterrific. The workaround for this bug is simply to set the identifier explicitly in your Build Settings. In our case, we added --identifier com.iconfactory.Twitterrific to Other Code Signing Flags.

Updated October 18th, 2013: It looks like this bug happens when you precompile your Info.plist. Thanks to Chris Liscio for verifying that problem. Make sure to dupe it, if you’re affected!

As to why you’d want to precompile your Info.plist, there are two good reasons.

The End

There you have it: a short summary of my last three days of confusion caused by new Gatekeeper requirements, issues with Xcode and bugs in Mavericks. Hopefully, this essay will save you some of that same agony.