App modification after installation and MacOS security, code signing, app notarization

Hi,

I have a PKG file which contains three packages: MyApp.pkg, MyOtherApp.pkg and MyLibs.pkg which contains all libraries and most of resources for these apps. Some libraries are common for both apps and some are specific for certain apps. After installation I have /Applications/MyApp.app, /Applications/MyOhterApp.app, /Libraries/MyAppLibs/Versions/1.2.3/. All these directories and PKG files are signed. 

  1. I want to make it possible to install or delete these apps separately. For example, if MyApp is already installed, then user may download and install only MyOtherApp.app and its specific libraries. When user deletes MyApp, deinstaller should delete MyApp.app and it's libraries not touching files used by MyOtherApp.

  2. I want to make MyApp to be able to install or delete some of its libraries. For example, when the user activates a feature, MyApp downloads libraries for this feature.

Both goals assume that at least library directory will be modified. So the question is, will everything be alright with apps signing and notarization? I'm new to MacOS and I'm not sure if I fully understand it's security policy yet.

I've done some experiments with manually deleting and changing files in both library and app directories. codesign and spctl utils show that directories are modified and signs are invalid, but the app launches and works without any problems even after I modified it's executable in MyApp.app. So it seems like  I can just don't care about signatures, but I think it is not a good solution, and I'm also not sure if it works for all users with different security settings.

Maybe I should pack each library separately and install them in separate directories in /Libraries/MyAppLibs/Versions/ ? I've not tried it yet. It sounds ok, but it changes file structure of MyLibs and I expect some difficulties in adapting MyApp to it.

So is there a way to do it right?

It would be easier to just bundle a copy of those libs inside each app. Then you won't need a .pkg to install them, or a deinstaller to remove them.

Anyway, if the libraries you replace in your /Libraries/MyAppLibs/ folder are properly signed, there shouldn't be any issue. Each .app bundle or library outside an .app bundle is a separate entity. Changing one entity won't affect the others, just make sure you keep using the same signing identity.

Changing something inside a .app bundle will break its signature, macOS doesn't check it every time, but the next time it will (and it will surely will) the app won't run anymore.

galad87 wrote:

It would be easier to just bundle a copy of those libs inside each app.

This! A thousand times this!

Unless your libraries are huge — and I’m talking GBs here — it’s not worth the effort trying to share them, especially if you’re new to the Mac and thus don’t understand all the fiddly details.

If you decide to go down this path anyway, follow the rest of galad87’s advice; it’s spot on IMO.

Share and Enjoy

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

I already have this mechanic on Windows

The Mac isn’t Windows. Techniques that are common on Windows may seem foreign on the Mac. For example, Mac apps are typically self contained. If a user wants to dispose of the app, they drag it to the Trash. Deinstallers, while they do exist, are pretty uncommon.

Given that culture, it’s hard to share libraries between products because your code doesn’t get invoked when the user drags the app to the Trash.

Libraries aren't that huge, but there are a lot of them and user may need only a few.

Right, but presumably a library that isn’t used has no impact, right? Other than to consume disk space of course (-:

Share and Enjoy

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

Thank you for your advice, but I'd like to give it a try anyway.

Why?

If you want folks to help you down a path that they’ve specifically told you not to go down, you need to provide some justification.

Share and Enjoy

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

I still think this is a really bad idea. All engineering is about trade-offs and here you’re trading a relatively small amount of disk space saving against a) user convenience, and b) a lot of engineering time. But hey, it’s your product and these decisions are ultimately yours to make.


The model you’re proposing was relatively common on macOS back in the day, and macOS continues to support it. The only significant caveat relates to code signing: This model works best if all the apps are from the same vendor [1]. If you can’t meet that requirement then things get somewhat more complex.

As to how you achieve this, the standard approach is to use an installer package. This will lay down both your app and the shared libraries it uses. If you separate your shared libraries into a sub-package, the installer will do the right thing if two apps use the same package.

On the de-installation front, macOS has no de-install mechanism. You will have to build that from scratch yourself. This will not be easy. Specifically:

  • It will need to escalate privileges, which is a whole world of un-fun on macOS.

  • macOS has no shared library reference counting mechanism, so the de-installer itself will need to determine whether it can safely remove a specific shared library.

On the versioning front, macOS generally encourages binary compatibility. That is, version N+1 of a library supports both version N+1 and version N clients. macOS and Xcode have good infrastructure for handling this (weak linking, availability macros, and so on). Thus, the standard practice here is for the installer to not replace an newer library with an older one, but for the newer library to support both old and new clients.

If binary compatibility isn’t your thing then my advice is that you embed the version number in the library name. From macOS’s perspective that makes each version of the library separate.

On the code signing front, each code item is signed separately and thus adding and removing libraries in /Libraries does not break anything. That is, when an app loads a library and the system checks the code signature of that library, that check only depends on the library itself, not on the context of that library.

This assumes you’re using standalone shared libraries (.dylib). If you’re using frameworks (.framework) then the code signature seals over everything within the .framework bundle. If you’re curious how this works, see my A Peek Behind the Code Signing Curtain post.

Finally, this:

I want to make MyApp to be able to install or delete some of its libraries.

is going to be a challenge on two fronts:

  • If you want the downloaded libraries to be installed in /Library then you need to deal with privilege escalation. That directory is only writable by root.

    A good way to handle this is to package that library in an installer package and then have your app download and open that. That way the Installer takes care of the privilege escalation (and other stuff).

  • If you write the libraries to disk yourself, make sure to follow the advice in Updating Mac Software.

This should be enough to get you started. I expect you’ll encounter other problems. If you do, or if I missed something, post back here with the details.

Share and Enjoy

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

[1] Specifically, all the code is signed with the same Team ID.

Thanks. A few more questions.

On the code signing front, each code item is signed separately and thus adding and removing libraries in /Libraries does not break anything.

What if I also have some executables in there? Does the same applies to executables as well? /Library/MyAppLibs/Versions/1.2.3/ contains all my libraries, a few frameworks, some executables and other files. Now, this directory is signed as a bundle. Do I understand correctly that don't need it to be signed? Won't it affect the ability to run executables from this directory?

  then have your app download and open that

Do you mean my app should run something like open /tmp/my_lib.pkg? Isn't this require root password anyway? Is there a way to run it without installation dialogue, like when I run it from terminal?

Is there a way to force MacOS to check library signature, so I can test if everything works fine after update?

/Library/MyAppLibs/Versions/1.2.3/ contains all my libraries, a few frameworks, some executables and other files. Now, this directory is signed as a bundle.

My general advice is that you only sign things as a bundle if they have a standard bundle structure. Signing an unstructured directory as a bundle might work but it takes you off the beaten path.

Do I understand correctly that don't need it to be signed?

Correct.

Won't it affect the ability to run executables from this directory?

It won’t. The executable carries its own signature and the system checks that signature regardless of where you load the executable from. Likewise for frameworks and shared libraries.

The alternative here is to create an actual bundle. It’s fine to nest executables, frameworks, and shared libraries within a bundle and then sign that as a unit. For advice on how to structure the bundle, see Placing Content in a Bundle. For advice on how to sign it, see Creating Distribution-Signed Code for Mac.

Keep in mind that, if you do this, you won’t be able to add or remove items from that bundle without breaking the seal on its code signature.


Do you mean my app should run something like open /tmp/my_lib.pkg?

Yes.

Well, no, because it’s better to just use NSWorkspace to open the installer package directly rather than going through open, but they’ll both end up at the same place.

Isn't this require root password anyway?

Right, but the Installer app will prompt the user for that.

Is there a way to run it without installation dialogue, like when I run it from terminal?

Yes, by running the installer tool. But you then become responsible for privilege escalation.

Is there a way to force MacOS to check library signature, so I can test if everything works fine after update?

Yes, using the code signing API.

Share and Enjoy

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

App modification after installation and MacOS security, code signing, app notarization
 
 
Q