Signing/notarizing command line tools not build using Xcode

Hi,


I am in a bit of an double corner case here. I have a package which consists of ONLY command-line executables. This package is a number of Unix utilities AND it uses a traditional Unix style build system (autoconf/make). We used to distribute this as a gzipped tar file, but obviously this fails when it comes to Catalina and notarization. This package consists of a number of executables, some shared libraries those executables link against, and a few dylibs that those applications load at runtime using dlopen(). Our plan is to provide a traditional MacOS installer package going forward.


I have honestly been trying to do my homework, and I think I've got most of the mechanics down. I have an Apple Developer certificate, and I've been able to codesign executables and shared libraries, and ship them off to be checked by the notarization service, and that works. I have read all of the guides I can find about codesigning and notarization, including Signing a Mac Product For Distribution (thank you for that!). But I haven't QUITE seen something that covers my specific case, in that I am building a package OUTSIDE of Xcode AND it is not an application bundle (the build system is large and incorporating it into Xcode is just TOO much of a heavy lift right now). So I am trying to understand everything that I need to do.


Specifically, my questions are:


  • Do I need to compile an Info.plist into every bit of code? I understand HOW to do that, using the -sectcreate option to the linker, but it wasn't clear to me if that is required. Is it only required for executables, or both executables and libraries? The implication that I need to compile an Info.plist came from here https://eclecticlightdotcom.files.wordpress.com/2019/06/notarizecmdtool.pdf
  • There is a warning in the codesign man page under the --identifier option that says, "It is a very bad idea to sign different programs with the same identifier". Okay, fine. But it ALSO says that either gets the identifier from the Info.plist or the filename if that option isn't present. I am concerned that if I compile in the SAME Info.plist into every bit of code then all of the code gets the same identifier, and that would be "very bad". Obviously I can add the --identifier and --prefix options to codesign, but it wasn't clear if there were any implications in doing that.
  • I was originally under the impression that I had to bundle up the binaries separately to get notarized, but it SOUNDS like that all I need to do is once I create the installer package, with everything signed inside of it, AND I sign the installer package, I can just submit the installer for notarization and that will cover everything?
  • I know I can use spctl to check the status of executables, but it doesn't seem like that works for dylibs. Is that correct?


Thanks for any help you can provide.


--Ken

Replies

Do I need to compile an

Info.plist
into every bit of code?

No. The article you referenced was incorrect, and if you search that site you’ll find a follow-up article that acknowledges that.

Obviously I can add the

--identifier
and
--prefix
options to codesign, but it wasn't clear if there were any implications in doing that.

My Signing a Mac Product For Distribution post has specific advice on this front.

I can just submit the installer for notarization and that will cover everything?

Correct.

I know I can use

spctl
to check the status of executables, but it doesn't seem like that works for dylibs. Is that correct?

That is correct. You can use

spctl
for:
  • Apps:

    % spctl -a -t exec -vvv MyFancy.app

    .

  • Disk images:

    % spctl -a -t open -vvv --context context:primary-signature /path/to/disk.dmg

    .

  • Installer packages:

    % spctl -a -t install -vvv Test711909381.pkg

    .

For other code you can do this:

% codesign -vvvv -R="notarized" --check-notarization /path/to/your/code

Note This is new in 10.15.

I’m not sure if there are limits on what types of code this will work with, but it should be pretty broad.

Testing with

spctl
is fine for a rough check but you are much better off doing a full user-level test. Here’s how I do that:
  1. I restore a VM from a fresh snapshot that’s never seen my product.

  2. I download the product using a mechanism, like Safari, that quarantines the download.

  3. I install and run the product like a user would.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

It sounds like you are working way too hard. All you need to do is:

1) Sign your dylibs

2) Sign your executables

3) Build an installer package

4) Sign and notarize the installer package


You can incorporate all of that into your build process if you want. It is very easy. Back when Catalina disabled 32-bit apps, a few people freaked out over ghostscript. Apparently nobody had bothered to rebuild that since 32-bit days. I built and notarized a 64-bit version in a few minutes. The hardest part was fixing the bugs in ghostscript iteslf so it would build on a modern machine.


// Sign executables.

codesign --timestamp --options=runtime -s "Developer ID Application: ***" -v bin/gs

codesign --timestamp --options=runtime -s "Developer ID Application: ***" -v bin/pdftoraster

codesign --timestamp --options=runtime -s "Developer ID Application: ***" -v cups/pstoraster

codesign --timestamp --options=runtime -s "Developer ID Application: ***" -v cups/pstopxl


// I assume you will have to sign dylibs too. If your software is doing anything funky, you may need additional hardened runtime entitlements.


// I was building this from source. So I signed the working copy and then installed.

sudo make install


// Now, I needed to create a temp install for the installer package. Your files will, of couse, be different.

// This is also on a VM where there is nothing in /usr/local except for ghostscript.

sudo mkdir /tmp/ghostscript

ditto /usr/local/bin /tmp/ghostscript/usr/local/bin

ditto /usr/local/share /tmp/ghostscript/usr/local/share

ditto /usr/libexec/cups/filter/pdftoraster /tmp/ghostscript/usr/libexec/cups/filter/

ditto /usr/libexec/cups/filter/pstoraster /tmp/ghostscript/usr/libexec/cups/filter/

ditto /usr/libexec/cups/filter/pstopxl /tmp/ghostscript/usr/libexec/cups/filter/

ditto /private/etc/cups/pdftoraster.convs /tmp/ghostscript/private/etc/cups/

ditto /private/etc/cups/pstoraster.convs /tmp/ghostscript/private/etc/cups/


// Now create the installer.

productbuild --identifier "com.***.ghostscript64.pkg" --sign "Developer ID Installer: ***" --timestamp --root /tmp/ghostscript / ghostscript64.pkg


// Notarize the installer.

xcrun altool --notarize-app --primary-bundle-id "com.***.ghostscript64.pkg" --username “developer@***" --password "..." --file ghostscript64.pkg


// Wait for the e-mail or ping the server if you are impatient. An automated system would need to do something clever here. The

// altool can emit XML output that is easier to parse if you need to.

xcrun altool --notarization-history 0 -u "developer@***" -p "..."


// Once you are notarized, staple the ticket.

xcrun stapler staple ghostscript64.pkg


And that's all there is to it.

Ran into the same question today and found this post.

Although the above solutions probably work, I found a recent post with a much easier solution since Xcode 13 is out. (especially the archive script at the and of the article is very cool :) )

https://scriptingosx.com/2021/07/notarize-a-command-line-tool-with-notarytool/