Mach-O binary signing and notarization

Hello folks, I am practically at my wits end here and I think I have searched high and low and can't find any definitive steps or answers that can point me in the right direction. My team is working on a command line executable tool that is written in C. We have built the code on Mac OS 11.5 using our internal tools (no Xcode unfortunately) and are able to test and verify everything works. Now comes the hard part of signing and notarizing the tool so it can be distributed to our end users. I have figure out how to sign the executable and add some entitlements, the problem I have now is the notarization. Currently the tool is bundled in a dmg with the structure shown below. I ran the xcrun altool to notarize the dmg and after a couple hours it was successful. I was able to staple the ticket to the dmg but I don't see a way to staple the ticket to the tool itself (Stapling is not supported for mach-O binaries).

I was able to mount the dmg but when I click on the installer script or the executable, I get the dreaded gatekeeper warning that the developer cannot be confirmed. Anyone know any guides I can follow to get this application bundled and notarized and signed properly so my end users don't get hassled by gatekeeper? Any help is greatly appreciated.

Dmg structure:

DMG
 |----- Folder
 |        |----- dylib files
 |        |----- command line tool 
 |
 |----- installer script (copies dylibs to /usr/local/lib and copies command line tool  to /usr/local/ path and adds to PATH
 % xcrun stapler staple clitool.dmg
Processing: Downloads/Payload/clitool.dmg
Processing: Downloads/Payload/clitool.dmg
The staple and validate action worked!
Answered by DTS Engineer in 687044022

but Gatekeeper complains if I try to double click on the installer script or the command line tool.

That’s a separate issue, one that has no good solution AFAIK (r. 58097824). If a tool is quarantined and you double click it in the Finder, Terminal runs a document Gatekeeper check on it, and that always fails no matter how well signed or notarised the tool is. The only way around this is to avoid the problem. For example:

  • If you use an installer package to install the tool, it’s no longer quarantined and thus avoids this check.

  • You can wrap the tool in an app, for example, an AppleScript applet. This is useful for things like uninstall scripts, where you want it on the disk image but don’t want to install it.

  • You can educate your users to run the tool from a Terminal window. This runs through the standard Gatekeeper check, which passes because the tool is correctly signed and notarised.

I seem to have figured out a way to get this to work by signing files -> put in a .pkg -> sign .pkg -> put in .dmg -> sign and notarize .dmg.

That should work.

The post-install script keeps clitool in /usr/local/clitool and moves the dylib to /usr/local/lib.

I’m not an installer expert but that seems like a very roundabout way of achieving this goal. The installer is expecting a ‘root’, that is, a directory whose contents match the layout if you copied it to the root directory. In your case, that’d be:

root/
  usr/
    local/
      lib/
        libMine.dylb
        … and so on …
      bin/
        clitool

You then give it an install location of / and you’re done. No post-install script necessary.

Looks like installation fails now after packaging in .dmg.

Make sure you sign the installer correctly. You can use the code signing identity you use for code; you have to use an installer signing identity. For the specifics, see Signing a Mac Product For Distribution.

Share and Enjoy

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

I don't see a way to staple the ticket to the tool itself

That’s not necessary. Our recommendation here is that you only notarise and staple the outermost container, in this case your disk image. That ticket cover both the disk image and all of its contents. When the user mounts the disk image the system ingests the ticket and that’s how it knows that the tool and dynamic libraries are notarised.

ps You can find this and other tidbits in my Signing a Mac Product For Distribution post.

Share and Enjoy

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

Accepted Answer

but Gatekeeper complains if I try to double click on the installer script or the command line tool.

That’s a separate issue, one that has no good solution AFAIK (r. 58097824). If a tool is quarantined and you double click it in the Finder, Terminal runs a document Gatekeeper check on it, and that always fails no matter how well signed or notarised the tool is. The only way around this is to avoid the problem. For example:

  • If you use an installer package to install the tool, it’s no longer quarantined and thus avoids this check.

  • You can wrap the tool in an app, for example, an AppleScript applet. This is useful for things like uninstall scripts, where you want it on the disk image but don’t want to install it.

  • You can educate your users to run the tool from a Terminal window. This runs through the standard Gatekeeper check, which passes because the tool is correctly signed and notarised.

I seem to have figured out a way to get this to work by signing files -> put in a .pkg -> sign .pkg -> put in .dmg -> sign and notarize .dmg.

That should work.

The post-install script keeps clitool in /usr/local/clitool and moves the dylib to /usr/local/lib.

I’m not an installer expert but that seems like a very roundabout way of achieving this goal. The installer is expecting a ‘root’, that is, a directory whose contents match the layout if you copied it to the root directory. In your case, that’d be:

root/
  usr/
    local/
      lib/
        libMine.dylb
        … and so on …
      bin/
        clitool

You then give it an install location of / and you’re done. No post-install script necessary.

Looks like installation fails now after packaging in .dmg.

Make sure you sign the installer correctly. You can use the code signing identity you use for code; you have to use an installer signing identity. For the specifics, see Signing a Mac Product For Distribution.

Share and Enjoy

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

root/
  usr/
   local/
     lib/
       libMine.dylb
       … and so on …
     bin/
       clitool

@eskimo Wow, thanks so much! This is something I could not have figured out based on the documentation available out there. I created the expected folder path in my files folder and now am able to deploy to the correct folder path with no issues. I do however need to update the PATH variable to add the path to the clitool. Do I still need a postinstall script for this step or is there an easier way?

I do however need to update the PATH variable to add the path to the clitool. Do I still need a post-install script for this step or is there an easier way?

The best way to do this is to drop a file into /etc/paths.d.

Share and Enjoy

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

Mach-O binary signing and notarization
 
 
Q