Notarization and hot-update feature of an app

Hello, we have an app (a service) that contains an auto-update feature. This auto-update feature allows to update certain components, packaged as dylib files: 1 component = 1 dylib file. Those dylib files are written in a way that the app can unload the old versions and load new versions without restarting itself. So, when the user clicks "update", the app will download new dylibs from the company server, it will unload old dylibs, load new ones, and will start using new dylibs immediately. The dylibs are digitally signed with the same digital sign that was used to sign all the executables in the app.

I'm wondering how to solve the notarization problem for this case. Since notarization is a process that results in acquiring a "staple id" that should be placed inside the Contents/CodeResources file, does that mean that it's impossible to distribute "*****" MachO files that are not a part of any bundle? Because those "*****" MachO files do not have any place that allows to embed a "staple id". Do I understand it correctly?

Is there a method of embedding a "staple id" any other way than inside the Contents/CodeResources file?

What would be a suggested solution to the "auto-update feature that downloads signed dylib files" problem? Should we wrap dylibs into bundles, and download bundles instead of the "*****" dylibs? (since bundles can be stapled).

Sorry, I didn't realize that I'm using a disallowed word. Instead of it, I would suggest to use "raw MachO files", that is a MachO file that do not belong to any bundle.

I can't edit the post, so I'm ussing this comment as an errata: I've already written one comment about this, but it got removed by the Forums. Those stars do not represent any offensive word. It got censored by the Forums, but a synonim would be e.g. "raw", or "direct", or "bundle-less".

I’d like to clarify one thing here. You wrote:

Those dylib files are written in a way that the app can unload the old versions and load new versions without restarting itself.

This means that your app is loading and unloading these libraries dynamically, right? Using APIs like dlopen and dlclose?

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Yes, the app uses dlopen(3) and dlclose(3).

OK, cool.

You seem to be stuck on two things:

  • How to notarise code that’s not in a bundle structure?

  • How to staple that code?

First things first, you definitely should be notarising your code here. While you might be able to get away with not doing that right now [1], that’s not a good option for the long term.

However, stapling is optional. For more background on this, see The Pros and Cons of Stapling.

In your specific case, it’s very likely that the Mac will be online at the time that the code is first run — because your software update mechanism downloads the code and then runs it immediately — and thus stapling is likely to be a non-issue.

In terms of how you notarise your code, that depends on your final packaging. If your software update mechanism uses one of the packaging types supported by the notary service — zip archive (.zip), disk image (.dmg), installer package (.pkg) — you can notarise that directly. If not, the easiest option is to package your code into a zip archive and notarise that.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

[1] Notarisation checks typically only kick in if your code is quarantined and the software update system within your app doesn’t have to quarantine anything.

Thanks for the answer @eskimo!

It did clear some things up, but not everything. I'm still trying to figure out how stapling should work with an app that auto-updates itself.

Let's say I have an app bundle "A.app" with 2 components: an executable "E", and a dylib file "D". I notarize the whole "A.app" bundle, and I staple it -- this part is clear. Then, I create a new app bundle "B.app", that has its own executable "E1" and the component "D" (its file has the same SHA1 as used in "A.app"). I notarize "E1" executable. I don't staple "B.app". So the questions are:

  1. Should I assume that "D" component inside "B.app" is already notarized (because it was notarized in the process of notarizing the whole A.app bundle)? Or should it be notarized once again, since it's exactly the same component as in "A.app", but now is included inside a different app bundle: "B.app"?

  2. Since I don't staple "B.app", I now depend on the system to automatically download missing staples from Apple's servers. But I never notarized the whole "B.app", I've just notarized "E1" separately, and notarized "D" as part of notarization of the whole "A.app" bundle. Is the system able to even download any notarization proofs in this case?

Notarisation checks typically only kick in if your code is quarantined and the software update system within your app doesn’t have to quarantine anything.

Do you know if it will stay like this in the future as well?

I notarize "E1" executable.

This seems wonky. Is E1 the main executable for app B?

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Yes, "E1" would be the main executable for "B.app".

Does the notarization system assume only bundles will be notarized? Will there be a problem (now or in the future) with notarization of individual executable/dylib files?

(I understand that since "E1" would be a main executable for "B.app", then the way it should be done is to notarize the whole "B.app" bundle. But I'm interested in knowing how it looks like from Apple's perspective if I notarize "E1" individually, notarize "D" when it was a part of a different app bundle, and I don't notarize "B.app" at all).

Accepted Answer

I understand that since "E1" would be a main executable for "B.app", then the way it should be done is to notarize the whole "B.app" bundle.

Yes, this.

You can’t notarise E1 outside of the context of B because:

  • When you sign E1, its code directory should contain information about the resources from B.

  • If you sign it outside of that context, it won’t contain that info.

  • That’ll result in a different code directory hash (cdhash).

  • The notarised ticket contains that cdhash value.

If you want to do, the trick is to place E1 within B, sign and notarise that, then extract E1 and make a software update out of that. On the user’s machine, integrate E1 into B and you’re all set. macOS doesn’t care how you got to a correctly signed and notarised B, it just cares that it’s correctly signed and notarised.

See TN3126 Inside Code Signing: Hashes for more info about this. Oh, and my Notarisation Fundamentals post.

Does the notarization system assume only bundles will be notarized?

No. It’s happy to notarise non-bundled code. However, any given piece of code must either be bundled or not bundled.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Notarization and hot-update feature of an app
 
 
Q