When we added support for Gatekeeper in Fetch 5.7.1 last month, we caused Fetch to break on Mac OS X 10.5. Others ran into related compatibility problems; Daniel Jalkut and Ken Case proposed a solution. We chose a different approach from theirs.
Background
A Mac OS X application can be digitally signed by its developer. The digital signature functions as a wax seal: the developer's identity is securely inscribed in the application, and it becomes possible to detect if the application has been tampered in any way.
This is a good idea for a variety of reasons; for example, detecting that an application has been tampered with is a measure of protection against malware.
With this mechanism in place, it becomes possible for users to give special rights to an application (for example, the right to access the address book) and be sure that this right will not be hijacked by an impostor. This, too, is a measure of protection against malware.
Central to this mechanism, therefore, is the question of app identity: if I have two apps that claim to be different versions of the same app, should I trust them? If they really are different versions of the same app, then they should be extended equal rights. If, however, one of them is an impostor, then the two should not be given equal rights.
The solution Apple took to this question is that every signed app, in addition to its developer's identity, embeds a statement that describes precisely what conditions have to be met by another app in order for the other app to be considered merely a different version of the same app. This statement is known as the app's designated requirement.
Problem
Where we run into problems is that the designated requirement embedded inside an application signed by Xcode 4.3 using an Apple Developer ID (which is required to support Gatekeeper in Mac OS X 10.8 Mountain Lion) is too complex for Mac OS X 10.5.x to understand. As a result, a Gatekeeper-enabled app running on Mac OS X 10.5.x always seems to be an impostor, which results in a variety of problems (such as the user always being prompted to allow the app to use passwords stored in the keychain, instead of being prompted only the first time — exactly the problem we ran into with Fetch 5.7.1).
Obviously, then, the question is whether it is possible to create a designated requirement that is compatible with Gatekeeper but not too complex for Mac OS X 10.5.x to understand, thus retaining Gatekeeper support, but keeping Mac OS X 10.5.x happy.
The answer is: yes, if you are willing to require Mac OS X 10.5.8.
In Detail
Gatekeeper-enabled apps fail verification on Mac OS X 10.5.x for two different reasons:
- Understanding and verifying a Gatekeeper-compatible designated requirement requires support for some code signing features that were simply absent in Mac OS 10.5. There is nothing you can do about this, but since these features are present in Mac OS X 10.5.8, you can require your users to install the (free) update to Mac OS X 10.5.8. You are still left with a support burden, but it's not as bad as requiring a major upgrade. (We did not take the time to figure out exactly where in the 10.5.x release series this started working.)
- A designated requirement needs to be compiled before being built into an application. A Gatekeeper-compatible designated requirement compiled by Xcode 4.3.x on Mac OS X 10.7 is incompatible with Mac OS X 10.5.x and Mac OS X 10.6.x. However, the same designated requirement compiled on Mac OS X 10.5.8 is compatible with all later versions of Mac OS X. Therefore, if you compile your designated requirement on Mac OS X 10.5.8 and then embed it into your app using Xcode 4.3, you will produce a Gatekeeper-enabled app that is compatible with Mac OS X 10.5.8 and later.
Which all brings us to: why do you even care? Can't you just do what Daniel and Ken proposed and be done with it?
Well, you could. But if you do, you will expose yourself to the following two problems:
- When your Apple-issued developer certificate is renewed (its expiration time is no more than 5 years into the future), you will find that the app signed by the renewed certificate will not meet the designated requirement of the app signed by the current certificate. This is because Daniel and Ken used a designated requirement that is tied to a specific certificate, whereas our solution uses a designated requirement that is tied to our company identity.
- The Mac App Store version of your app and the Gatekeeper version of your app will not satisfy each other's designated requirement — again, because the designated requirement is tied to a specific certificate.
To understand the details of this, you need to look at what the designated requirement created looks like for an app distributed in the Mac App Store by a developer who has also been issued a Developer ID for Gatekeeper distribution:
[ 1] designated => ( [ 2] anchor apple generic [ 3] and certificate leaf[field.1.2.840.113635.100.6.1.9] /* exists */ [ 4] or [ 5] anchor apple generic [ 6] and certificate 1[field.1.2.840.113635.100.6.2.6] /* exists */ [ 7] and certificate leaf[field.1.2.840.113635.100.6.1.13] /* exists */ [ 8] and certificate leaf[subject.OU] = some-developer-id [ 9] ) [10] and identifier "some-bundle-id"
What this says is “The designated requirement (1) of this app is to have the bundle ID some-bundle-id (10) and either a) be trusted by Apple (2) and signed by Apple for App Store distribution (3) or b) be trusted by Apple (5) and signed by developer with ID some-developer-id (8) using their Gatekeeper certificate (6,7)”.
So, in short: you probably shouldn't use a trivial designated requirement tied to a specific certificate. Instead, you should:
- Extract the full Gatekeeper designated requirement. If your app is on the App Store, download it from the App Store and extract its designated requirement; otherwise, build for Gatekeeper distribution using Xcode 4.3 and extract that designated requirement. To extract the designated requirement from your app, run “codesign -d -r-” on it, and save the “designated =>…” part of it to a file.
- Compile the requirement on Mac OS X 10.5.8 (or the oldest version of Mac OS X that your app supports), using “csreq -r requirement.txt -b requirement.bin”.
- Embed that compiled requirement inside your Xcode 4.3 build, using “codesign -r requirement.bin”.
- Drop support for older minor versions of Mac OS X 10.5.
Much thanks to Ken and Daniel for giving me a starting point for figuring this out!
“A designated requirement needs to be compiled before being built into an application.”
Why?
The human-readable text form of a code-signing requirement has to be converted to a data structure at some point prior to verification. Performing that conversion when the app is compiled makes every subsequent signature verification faster.
Thanks SO MUCH for this!
You said: “A Gatekeeper-compatible designated requirement compiled by Xcode 4.3.x on Mac OS X 10.7 is incompatible with Mac OS X 10.5.x and Mac OS X 10.6.x.”
From other things I’m reading, I thought it was still compatible with 10.6 – just not 10.5. We only want to support 10.6 and above, which is why I ask. I don’t want to binary-compile my reqs on a 10.6 box (since I don’t have easy access to one at the moment), so I’m hoping that compiling my reqs from text as part of my codesign command line (on a 10.7 or 10.8 machine) will suffice.
I am not 100% sure, but I believe that you will discover that using Mac OS X 10.7 (or later) to build for Gatekeeper will result in an application that does not work correctly on Mac OS X 10.6.
I know for sure that we had some users report problems under 10.6, but I don’t didn’t research in-depth what the nature of those problems was. Since we support Mac OS X 10.5, my work focused on making the app work on Mac OS X 10.5, and once I got that working, Mac OS X 10.6 was also covered.
If all you want is Mac OS X 10.6 compatibility, I am not sure if you can get away without doing any of the extra work described in this post.
The safest thing for you to do would be to build a version of your app on Mac OS X 10.7 and then test it on Mac OS 10.6.
Fortunately, you can run Mac OS X 10.6 Server in a virtual machine (definitely via VMware Fusion, possibly also using Parallels), which means that you don’t need a separate 10.6 box to test on 10.6. Just install 10.6 Server in a virtual machine for testing purposes.
Finally, it’s worth noting that all of the problems with app identity when using a Gatekeeper certificate surface only when the OS tries to verify the identity of your app. On Mac OS X 10.6, there aren’t a lot of circumstances when this happens; the main ones I know about are use of Keychain and use of Firewall. If you don’t use Keychain or Firewall, you might not even care if your app’s Gatekeeper signature is invalid on 10.6, because the OS and your users will never notice.
When I run the command, codesign -r I get an error: “-r: no identity found”
That’s “-r-” in there, not just “-r”.
When I run either:
codesign -r-
or
codesign -r
I get the following output:
Usage: codesign -s identity [-fv*] [-o flags] [-r reqs] [-i ident] path … # sign
codesign -v [-v*] [-R testreq] path|pid … # verify
codesign -d [options] path … # display contents
codesign -h pid … # display hosting paths
Am I missing something?
Ah yeah, I see what’s going on. My instructions assume that you already know how to sign an executable using codesign, and just needed to know how to do so using a specific designated requirement.
The full codesign invocation you need to sign your app is going to be:
codesign --sign "Name of your developer identity" -r "Path to your requirement.bin" "Path to your app"
Sorry for not being clearer in the first place, and I hope this helps!