Signing macOS packages

As part of my ongoing project, the generated packages should be signed to allow easy installation. Codesigning a macOS bundle is easy in theory, but I got some weird errors.

Arbitrary restrictions with paths

% codesign -s KEYID --deep -v ImageDir/MyApp.app
ImageDir/MyApp.app: bundle format unrecognized, invalid, or unsuitable
In subcomponent: /Users/sjl/obj/ImageDir/MyApp.app/Contents/MacOS/path/to/a/libary/7.7.0.1

(paths changed to protect the innocent)

If I rename "7.7.0.1" to "foo", it works. If I move the dynamic libraries one directory up, it works.

The reason why a path component with a version number is problematic is because of how the code signing machinery works. I stumbled on this piece of documentation from Apple, macOS Code Signing In Depth:

Note that a location where code is expected to reside cannot generally contain directories full of nested code, because those directories tend to be interpreted as bundles. So this occasional practice is not recommended and not officially supported. If you do do this, do not use periods in the directory names. The code signing machinery interprets directories with periods in their names as code bundles and will reject them if they don't conform to the expected code bundle layout.

Do not copy Qt frameworks by hand

I had just used

% cp -a QtGui.framework MyApp.app/Contents/Frameworks/

but codesign complained about that. Instead, I got good results by building the bundle otherwise, and making it standalone by using the macdeployqt utility.

This checks the package, or at least the main binary in the Info.plist, for what Qt frameworks are needed, and deploys the necessary libraries and plugins. After that, there was less clutter in the framework directories of my app and signing worked correctly.

Qt binaries seem to need to be at the top-level, i.e., MyApp.app/Contents, to load the Qt plugins correctly, the problem mentioned on the previous section. To solve this, I moved the Qt main application to the top-level Contents/MacOS. This required a bit of tweaking to the dll loading of the app, as the directory layout changed, but no biggie.

All's well that ends well

After going over the hurdles described above, I've got nice signed packages.

% codesign -s SIGNING --deep -v ImageDir/MyApp.app
ImageDir/MyApp.app: signed app bundle with Mach-O thin (x86_64) [com.semeai.app]
% codesign -s SIGNING -v MyApp.dmg
MyApp.dmg: signed  []

And verification:

% codesign -v --deep -v MyApp.dmg
MyApp.dmg: valid on disk
MyApp.dmg: satisfies its Designated Requirement
% codesign -v --deep -v ImageDir/MyApp.app/
ImageDir/MyApp.app/: valid on disk
ImageDir/MyApp.app/: satisfies its Designated Requirement

Easy as pie :)

Only thing remaining really is verifying that these packages work on 10.10. On 10.9 I get “the sealed resource directory is invalid.", which indicates a difference in the bundle layout between the versions.

links

social