code signing

Framework Signing Update

I recently wrote about problems using Leopard code signing with Mac OS X frameworks. I've since gotten feedback on my bug report. It looks like the problem isn't so much that frameworks can't be signed but that the correct signing procedure isn't documented.

The code signing documentation indicates that bundles should be signed. Frameworks are bundles, so if you're looking to sign your code you'll likely be tempted to sign a framework like this:

$ codesign -s "authority info" Sparkle.framework

But if you do that you run into the confusing situation I encountered, with your framework structure modified and difficulty knowing if the bundle is valid or not.

The feedback I got on my bug report explains that code signing should be done differently for versioned bundles like frameworks and... whatever other versioned bundles there might be. Although frameworks commonly contain only one version, they're designed so that multiple versions can be present. When signing a framework then, you sign the specific version, not the entire framework bundle. So instead of the above, you instead do something like this:

$ codesign -s "authority info" Sparkle.framework/Versions/A

If there are other versions, sign them separately.

This leaves the framework structure unmodified:

$ find Sparkle.framework/ -name Sparkle -exec ls -l {} \;
lrwxr-xr-x  1 tph  wheel  24 Nov 29 11:44 Sparkle.framework/
    /Sparkle -> Versions/Current/Sparkle
-rwxr-xr-x  1 tph  wheel  242928 Nov 29 11:44 Sparkle.framework/
    /Versions/A/Sparkle

Of course when verifying the signature, you also need to verify the versions independently of the framework bundle. The framework itself won't have a valid signature, but that's not what you should be looking at anyway:

$ codesign -vvv Sparkle.framework/Versions/A
Sparkle.framework/Versions/A: valid on disk
$ codesign -vvv Sparkle.framework/
Sparkle.framework/: code or signature modified

This seems to make sense. A framework is designed to allow multiple independent bundles, with symbolic links to indicate which is current. So, sign each bundle on its own.


Don't Sign that Framework

Yesterday I was working on a forthcoming update to Chimey and I noticed something odd. Chimey of course makes use of SparklePlus for automatic updates, and after a test run of my build-for-release script, Sparkle was looking a little odd.

Normally a framework has one or more binaries, with a symbolic link pointing to the current version. From the command line you'd expect something like this:

$ find Sparkle.framework/ -name Sparkle -exec ls -l {} \;
lrwxr-xr-x  1 tph  wheel  24 Nov 21 11:20 Sparkle.framework/
    /Sparkle -> Versions/Current/Sparkle
-rwxr-xr-x  1 tph  wheel  233088 Nov 21 11:20 Sparkle.framework/
    /Versions/A/Sparkle

Instead I was seeing this:

find Sparkle.framework/ -name Sparkle -exec ls -l {} \;
-rwxr-xr-x  1 tph  wheel  242928 Nov 21 11:20 Sparkle.framework/
    /Sparkle
-rwxr-xr-x  1 tph  wheel  233088 Nov 21 11:20 Sparkle.framework/
    /Versions/A/Sparkle

Yow, how the hell did that happen? I thought it might have something to do with copying the framework, either when compiling or when building the disk image, but that wouldn't account for the different file sizes. A quick check on the current version of Chimey showed that this was something new, not something I'd been doing all along without realizing it.

So what was different? My build-for-release script now signs my code using Leopard code signing.

Apple's documentation on code signing indicates that "You should sign every program in your product, including applications, tools, hidden helper tools, utilities and so forth." Chimey's main bundle includes a preference pane and two helper tools, so I was making sure to sign all of them. The main documentation doesn't mention frameworks specifically, but the Code Signing Release notes indicate that "You may also sign any libraries, frameworks, plugins, and scripts you ship, whether they are delivered with an application or separately." Framework signatures don't get checked yet but might be in the future. I had made my script as future-proof as possible by signing the Sparkle framework now.

But if you sign a framework, the codesign tool modifies the structure of the framework, as I found with Sparkle. Just for sanity's sake I made sure that this affects any Mac OS X framework and is not some kind of Sparkle-specific behavior.

And the file sizes? I can guess what's going on, but I made sure:

$ codesign -vvv Sparkle.framework/Sparkle 
Sparkle.framework/Sparkle: valid on disk
$ codesign -vvv Sparkle.framework/Versions/A/Sparkle
Sparkle.framework/Versions/A/Sparkle: code object is not signed

Not only is codesign changing the framework structure, it's also leaving the framework in an inconsistent state with regard to whether it's signed. Instead of one binary I've got two, and even though I signed the framework, one of those two is still unsigned.

At the same time, checking on the framework bundle still returns a valid signature, despite the presence of unsigned code in there:

$ codesign -vvv Sparkle.framework
Sparkle.framework: valid on disk

It's a good thing that Leopard doesn't currently check on framework signatures. For now it seems it's probably best not to bother signing a framework. Although codesign leaves you with something that should work, it's not clear that it's actually doing anything useful, and it's bloating the framework size in the process.

This has been filed as bug #5609522 with Apple, in case anyone from Apple reads this.


Leopard Code Signing, Questions and Answers

One of the new features in Leopard that people may not notice at first is code signing. It's not the sort of thing that makes for a flashy demo, so you may not have noticed.

When a software developer creates an application they can now optionally add a cryptographically-secure signature to it which can later be used to find out if the application was modified. It can also be used to identify a new version of an application in relation to an older version of the same application. If both are signed in the same way, Mac OS X won't notify you about the update and ask if it's OK for the new version to use the same keychain information as the old one.

Beginning with Mac OS X 10.5 Apple signs all of their applications.

What does modified mean in this situation? It's not as clear as one might think, but the basics are:

  • Modifying the executable, the part that actually runs the program, will invalidate the signature.
  • Removing platform-specific code (just the Intel or PowerPC part) can be an exception to this (more detail below).
  • Removing language-specific localizations-- such as the parts needed to present menus and windows in a different language-- can be safely removed without invalidating the signature.

Macaroni and Code Signing

Which brings me to my main point. The authors of 1Password have run into some trouble with applications whose signatures get invalidated, in their case Safari's signature. As they describe in their Switcher's Blog, some of their customers found that 1Password no longer worked with Safari, and some detective work revealed that the problem was an invalid code signature.

Unfortunately while doing this they made the mistake of including Macaroni in the list of applications that could cause code signatures to become invalid. Almost immediately I started getting email from concerned users thinking that they needed to shut down Macaroni to protect their Macs. In fact Macaroni does not and has never removed platform-specific code from applications.

Many users have requested this feature since Intel Macs first appeared, and I did investigate it, but it seemed to be a technical and support mine field and so I left it out. It's not necessarily a bad idea to remove PowerPC code if you have an Intel Mac, or vice versa, but it seems like doing so is more of an expert move, to be done if you feel you have a good technical understanding of what you're doing and why. And if you do, you probably don't need Macaroni to help you with it. You could create a custom Macaroni job to automate the cleanup for you, but I still maintain that it has no place in the standard set of Macaroni jobs.

And now, a demo.

For demonstration purposes I'm going to look at Safari and Mail, both included with Leopard, and see what does and doesn't invalidate their signatures. I will of course be working on copies of the applications, because I'm going to be messing with their internals for your entertainment.

Demo: Localization cleanup

First I'll select Safari and open up the "Get Info" window. As you can see all of the languages Apple ships with it are present. There are 18 in all, though they don't all show up without some scrolling. Mail produces the same result.

safari-bundle-languages.jpg

I'll also verify that I'm starting with valid code signatures on both:

$ codesign -vvv Safari.app
Safari.app: valid on disk
$ codesign -vvv Mail.app
Mail.app: valid on disk

Now I'll run Macaroni and have it remove localized files. After doing this, I bring up the "Get Info" window again and verify that everything except English has been removed:

safari-bundle-delocalized.jpg

And how about those code signatures?

$ codesign -vvv Safari.app
Safari.app: valid on disk
$ codesign -vvv Mail.app
Mail.app: valid on disk

Looking good.

Demo: Platform code cleanup

Now I'm going to strip out the PowerPC code, leaving me with copies of the applications that work only on Intel Macs. I don't have any of the fancy utilities that does this for you, so instead I'm going with the command line and Apple's standard tools. There's more than one way to do this, but the easiest is probably to use "ditto". Besides copying files, ditto will optionally strip out platform-specific code while copying.

$ ditto --rsrc --arch i386 Safari.app Safari-i386.app
$ ditto --rsrc --arch i386 Mail.app Mail-i386.app

And the signatures?

$ codesign -vvv Mail-i386.app/
Mail-i386.app/: valid on disk
$ codesign -vvv Safari-i386.app/
Safari-i386.app/: a sealed resource is missing or invalid
/tmp/Safari-i386.app/Contents/Resources/SafariSyncClient.app/
Contents/MacOS/SafariSyncClient: resource modified

Hmm, not so good for Safari there. Mail looks OK though.

Should you care?

If you're using both 1Password and you strip platform-specific code from your applications, you should be aware of this. As the 1Password developers have discovered, an invalid code signature can prevent their tool from working. This is exactly the kind of thing that stopped me from adding this kind of application stripping to Macaroni.

If you don't use 1Password, it's probably OK to strip platform-specific code if you want. Invalid code signatures might mean you'll have to enter your password more often after updating applications, but aside from that it doesn't look like you'll notice it. It may not be worth the effort though. Leopard's Mail.app starts out at 286.5MB. After removing localizations that drops by more than 90%, to 24.7MB. Removing PowerPC code on an Intel Mac reduces it further to 21.6MB, only another 1% or so off of the initial size.

Either way though, Macaroni's not going to hurt your code signatures.



Atomic Bird, LLC