Signing Mac Builds

For the past month or so I have been working with Ben to add functionality for the signing of mac builds to our current signing infrastructure. Along the way, we discovered a few things.

What is signing?

Code signing is the process of attaching a digital signature to a piece of software that allows the user’s OS to verify that the software does indeed come from its advertised source, and that it has not been altered since being signed.

Why do we need signing?

Apple’s recently announced Mountain Lion is going to be introducing a few new features. In particular the new Gatekeeper ( Learn more here ) is important for Release Engineering at Mozilla. The default setting for Gatekeeper will *only* allow Applications to be installed from The App Store or from Registered Developers. This means that any applications are not signed by a Registered Apple Developer will show Security warnings when users install them. To ensure that our users have a seamless experience, we need to make sure that our builds are signed correctly according to Apple’s standards.

Gatekeeper isn’t the only reason Code signing is important. Signing verifies that the contents of the Application a) come from the trusted developer and b) have not been altered. OSX also uses the code signatures to determine whether an Application is trustworthy enough to be allowed Keychain access.

Code Signing on OSX

Apple provides a tool for Signing Applications called ‘codesign’. The ‘codesign’ tool will apply a digital signature to an entire ‘.app’ directory.

There are two parts to the signature:

  1. A generated manifest containing hashes of each file in the directory. This file is located in ‘*.app/Contents/_CodeSigning/CodeResources’ and is generated according to a specified rules file (more on this later).
  2. A digital signature attached to the binary specified as ‘CFBundleExecutable’ in Info.plist . This signature also contains the hash of the generated CodeResources file.

‘Codesign’ will verify a given ‘.app’s signature with the -v option. (Be sure to add a second v to get ‘verbose’ output, otherwise the only indication of success is the return code)

$ codesign -vv Firefox.app
Firefox.app: valid on disk
Firefox.app: satisfies its Designated Requirement

Automating Signing

There are a few important details about code signing on OSX which were important to address.

OS Compatibility

Applications which are signed on OS 10.7 (Lion) do not verify on 10.5 (Leopard). Since we want to be running one set of servers to sign all of our builds, we need signed builds to verify on 10.5, 10.6, and 10.7. Luckily, applications signed on OS 10.6 (Snow Leopard) verify correctly on all three versions. We’ll be using 10.6 as the OS on our Mac signing servers at least until 10.8 (Mountain Lion) is released, at which point we’ll need to re-evaluate.

 

Keychain Popup

Apple’s ‘codesign’ command require an signing key, or ID, which is stored in a Keychain. The ID and Keychain are passed as arguments to ‘codesign’. Unfortunately, ‘codesign’ does not offer any way to enter the password at the command line. If it tries to access a locked Keychain, it will pop up a UI prompt, asking the user for the Keychain’s password.

The bane of release engineers worldwide

The bane of release engineers worldwide

Thankfully for those of us who like to automate things, the ‘security’ tool comes to our rescue here. ‘Security’ is another Apple tool that allows command-line access and manipulation of Keychains. And ‘security’ does allow the Keychain’s password to be entered either as an argument or in response to a terminal prompt.

$ security unlock-keychain ***.keychain
password to unlock ***.keychain:

One important thing to note about the ‘security unlock-keychain’ command is that it will only unlock the Keychain for the current security context. In particular this means that if the Keychain has been unlocked by the command being run in a terminal, it is not unlocked for any ssh connections into the machine.
To user ‘security unlock-keychain’ without user interaction, the password needs to be entered at the tty level. Pexpect is one option for python which will wait for the command-line prompt and enter the password.
Now we’ve got all the tools we need to automate signing with the following steps with no user interaction:

  1. Unlock the keychain with ‘security unlock-keychain’ and enter the passphrase using pexpect
  2. Run the ‘codesign’ command
  3. Lock the keychain again with ‘security lock-keychain’

Code Resources

The CodeResources file is used by ‘codesign’ to specify which files in the ‘.app’ directory need to be included in the signature and which files do not. Before signing, the CodeResources file contains a set of rules. After signing, the hashes of all files specified in the rules will be added to the CodeResources file.
This page by Apple provides some description and examples of the CodeResources file, but it fails to explicitly state an important fact: any path specified in the CodeResources rules will be interpreted recursively to include all of the files in that directory and below.
The CodeResources rules must specify all files that need to be included in the final signature. Any files not listed or not in any of the recursive includes will be ignored, and will not have their hashes added to the signed version of the CodeResources file. Any files that are included in a recursive listing of a directory can be explicitly omitted. The file specified as ‘CFBundleExecutable’ in Info.plist is never included in the signed CodeResources file. Instead it is signed directly by the ‘codesign’ command.

The following example will use this simple directory structure:

Test.app
    Contents
        Info.plist
        MacOS
            binary <-- the binary file listed in Info.plist
            anotherfile
        Resources
            omittedfile
            resourcesfile

Before signing, we specify a set of rules in the CodeResources file. These rules will be passed to the ‘codesign’ command when it is called. The following code specifies these rules:

  • Recursively include the ‘MacOS’ directory and the ‘Resources’ directory
  • Omit ‘Resources/omittedfile’ from signing.
  • Include ‘version.plist’ in the signature
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<dict>
        <!-- Recursively include 'MacOS' and 'Resources' dirs -->
		<key>^MacOS/</key>
		<true/>
		<key>^Resources/</key>
		<true/>
        <!-- Omit a file -->
		<key>^Resources/omittedfile$</key>
		<dict>
			<key>omit</key>
			<true/>
			<key>weight</key>
			<real>1100</real>
		</dict>
		<key>^version.plist$</key>
		<true/>
	</dict>
</dict>
</plist>

The after signing, the CodeResources file has a list of the hash of every file specified in the rules. If any of these files are changed, ‘codesign -vv’ will fail and will specify the changed file. Notice that the omitted file does not have its hash listed here, nor does the Info.plist file, which was not included explicitly, nor in any of the recursive includes. The binary file has been signed separately by ‘codesign’ and is not included in this file.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>files</key>
	<dict>
		<key>MacOS/anotherfile</key>
		<data>
		K2QRL8qPGNuexZXewkmnMlmcdKU=
		</data>
		<key>Resources/resourcesfile</key>
		<data>
		PhrNXR4JvoBSBHWVsGOxbosr2po=
		</data>
	</dict>
	<key>rules</key>
	<dict>
		<key>^MacOS/</key>
		<true/>
		<key>^Resources/</key>
		<true/>
		<key>^Resources/omittedfile$</key>
		<dict>
			<key>omit</key>
			<true/>
			<key>weight</key>
			<real>1100</real>
		</dict>
		<key>^version.plist$</key>
		<true/>
	</dict>
</dict>
</plist>

That’s it for now

Look for Mac signing servers in the next few weeks at Mozilla. I hope this helps us provide a more seamless user experience (maybe including Keychain access?), and readies us for the release of OS 10.8 (Mountain Lion).

This entry was posted in Mozilla, Releng and tagged , , , . Bookmark the permalink. Post a comment or leave a trackback: Trackback URL.

10 Comments

  1. Perry The Cynic
    Posted 2012/05/17 at 21:35 | Permalink

    Resource rule keys are not inherently recursive. They are simply unanchored regular expressions matching the whole file path starting after the Contents directory. That’s how “^Resources/” matches the content of Resources (note the ^ anchor but no $ at the end). Any regex can play; just get the priorities right when they overlap.
    The main executable and the Info.plist are very special and are *not* resources. Don’t specify them as such.
    Note that if you specify your own resource rules, you are completely replacing the default, so you may lose some default behavior as well. For example, the default declares all *.lproj resources (localizations) to be optional, so they can be stripped out by localization-removal tools without invalidating the signature. If you want to allow that, keep that rule. (I have no idea if anyone still cares about this. How big are your localizations?)
    Also note that the resource seal detects addition and removal as well as modifications of resources. Make sure you’ve got everything in there (including localizations) when you sign; later additions are not allowed.

    Oh, and as for your choice of signing identities: it’s a very good idea to sign dailies and betas differently from official releases.
    I don’t know if you realize it, but you have the option to sign your Nightly and Aurora builds with a non-Developer ID identity and have their user base explicitly allow them to run on their computers (by authorizing your signing identity there). The advantage is that those builds won’t “leak” onto general users’ Macs by accident. The drawback is that each user must explicitly (once) consent to running them. (Or is that a drawback? Your call.) On the other hand, if you want “runs anywhere out of the box,” then Developer ID is what you want.

    Good luck!
    — perry

    • Posted 2012/05/17 at 21:48 | Permalink

      Thank you for the pointers!

      We’ll be sure to double check our CodeResources rule set in light of your suggestions.

      Cheers!

    • Ben Hearsum
      Posted 2012/05/18 at 07:50 | Permalink

      Thank you so much for the detailed CodeResources overview, Perry – I wish you had been around a few months ago ;) . Firefox is built a fair bit differently than other OS X apps, so some of what you mention is not relevant to us. Eg, we ship completely separate binaries for different locales – there’s no .lproj files in our packages at all. We actually do want our Beta releases to get in the hands of as many people as possible, so we’ll likely continue using a Developer ID for those. You’re making me rethink whether or not we want to use a Developer ID certificate for Nightlies and Aurora, though…

  2. Daniel Glazman
    Posted 2012/05/18 at 04:17 | Permalink

    Wow, thanks for that article. That is going to impact all xulrunner-based apps too, including my BlueGriffon. Do you think the process outlined above is different for them?

    • Posted 2012/05/18 at 08:05 | Permalink

      You’ll likely need an Apple Developer ID, see Ben’s blog post.
      After that, if you’re signing by hand the process is fairly simple. You can either sign using Xcode or the codesign command.

      The reason we ran have a more complex solution is because we’re signing builds automatically and we need to avoid any required user input.

      The default CodeResources rules work well for many applications, so you may not have to worry about creating a CodeResources file.

  3. Carl
    Posted 2012/05/24 at 18:30 | Permalink

    You could also use security unlock-keychain -p $PASSWORD $KEYCHAIN_NAME

    • Posted 2012/05/24 at 18:40 | Permalink

      The reason we avoided using this solution is because we want to be absolutely sure that the password doesn’t show up in any command history.
      If we input the password via tty, it’s stored in memory and won’t risk being stored permanently.

  4. Nomis101
    Posted 2012/06/02 at 03:32 | Permalink

    How does this signing process work if you want to pack the application than into a DMG? E.g. if I build my own unofficial version of Thunderbird or Firefox, sign it with my own Developer ID and than pack it into the DMG, than the signature is broken “invalid signature (code or signature have been modified)”. How do I pack the App with a valid signature into the DMG? I need to sign my unofficial versions of Firefox/Thunderbird, otherwise it will not work (in default settings) on 10.8.
    For details what I’ve tried, see: http://groups.google.com/group/mozilla.dev.apps.thunderbird/browse_thread/thread/7578d96ba4fd294b#
    Thanks!

  5. Jim
    Posted 2012/06/18 at 13:46 | Permalink

    Tangentially related question for you… do you start your build servers from Terminal.app, ie attached to a GUI process? I’ve been having to do this for my own release engineering needs, and I’d really prefer not to have to do this, to just be able to do it from SSH. As you likely know though, running a tool like codesign or hdiutil from an unattached (to the GUI) ssh process does not work so well.

Post a Comment

Your email is never published nor shared. Required fields are marked *

*
*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>