Explicit Requirement Satisfied

If you do any fine-tuned customization to your app’s code signing “designated requirement,” you may come across situations where the code appears to be signed correctly, but codesign verification nonetheless yields a cryptic, terse failure message:

./Your.app: valid on disk
./Your.app: does not satisfy its designated Requirement

This problem is pretty insidious, because the failure of an app to meet its designated requirement does not actually prevent you distributing it and having users run it. The code is still signed by you, it just doesn’t identify itself to the code signing system in such a way that it will be considered trusted by the system as being “itself.” This is bad.

How bad? The most likely to affect your users if the app uses the system keychain at all to store secure data. Even data placed in the keychain by the app itself will be denied to the app unless the user approves of the access. Worse, even if the user approves the access with “Always Allow,” the system will continue to prompt the user each and every time it tries to access the data.

The reason for the failure is that the “designated requirement” serves as a test of identity for your app. If your app doesn’t meet that test, the system assumes you no longer have any business accessing the data that you previously placed in the keychain. So an app that starts out not even meeting its own designated requirement is invalid from the start. We need our apps to meet their own designated requirements.

For most apps this is a no-brainer: just let the code signing system provide a default designated requirement. Typically, the default asserts that the bundle identifier and certificate signing chain used when signing the app are later the same. In short: unless an app is signed with the same certificate and has the same bundle ID as it had when it stored the data, it will not be allowed to access the data now.

Why customize the designated requirement? The reasons are probably too varied for me to imagine, but one common scenario that happens to affect me is one in which two different bundle IDs may effectively refer to the same “app.” For example instances of MarsEdit may possess either the “com.red-sweater.marsedit” or “com.red-sweater.marsedit.macappstore” identifiers, depending upon whether they were built for the Mac App Store or for direct sale. Another scenario where a custom designated requirement would be appropriate is if an application changed developers and either of the old developer’s and new developer’s certificate is meant to be considered legitimate. If you’re curious, poke around at the designated requirements of some of the apps on your Mac:

codesign -d -r- /Applications/Google\ Chrome.app
designated => (identifier "com.google.Chrome" or identifier "com.google.Chrome.canary") and certificate leaf = H"85cee8254216185620ddc8851c7a9fc4dfe120ef"

In this case it appears that Google wants to confirm that a very specific certificate was used to sign Chrome, but wants the pre-release “Canary” builds to be treated as authentic copies of the app, even though they have a different bundle identifier.

Customizing the designated requirement can lead to a pretty lengthy specification string that can be hard to process mentally. When such a string produces an app that doesn’t meet its own requirement, it can be hard to guess at which specific clause is failing. The codesign tool’s error message is of no help, but you can take advantage of a useful option related to the tool’s verification functionality. Pass “-R” along with a specific requirement string, and it will be evaluated separately from the designated requirement. The long and short of this is you can feed the codesign tool pieces of the larger requirement string, and see which ones pass and fail. To provide a string right on the command line, start it with an “=”:

codesign -vv -R '=identifier "com.apple.mail"' /Applications/iTunes.app 
/Applications/iTunes.app: valid on disk
/Applications/iTunes.app: satisfies its Designated Requirement
test-requirement: code failed to satisfy specified code requirement(s)

In this contrived case, we ask codesign to confirm that iTunes has Mail’s bundle identifier, and of course it fails. When we instead provide a requirement string that makes sense…

codesign -vv -R '=identifier "com.apple.iTunes"' /Applications/iTunes.app
/Applications/iTunes.app: valid on disk
/Applications/iTunes.app: satisfies its Designated Requirement
/Applications/iTunes.app: explicit requirement satisfied

…the codesign tool confirms that it passes.

In the past I have been at a loss for how to more easily debug designated requirements issues. I only discovered the “-R” option today, but I’m sure it will be a great help in the months and years to come.