Notarizing installers for macOS Catalina

[See newest updates at the bottom of the article]

According to Apple (read here the whole article):

The Apple notary service is an automated system that scans your software for malicious content, checks for code-signing issues, and returns the results to you quickly. Notarization gives users more confidence that the Developer ID-signed software you distribute has been checked by Apple for malicious components. Notarization is not App Review.

Basically, it is some kind of whitelisting. From macOS Catalina on (late September 2019), notarization is mandatory: your installers won’t work if you don’t do your homeworks.

Gatekeeper

When your customers run your product’s installer.pkg, macOS Gatekeeper connects with Apple, checking whether the file you’re executing has been vetted and thus can be run safely. If this is the case, Gatekeeper pops up the usual warning anyway:

Book Design

Because users can also be offline, you can staple the notary service’s response ticket directly to the installer – an optional but recommended step.

What you need

  1. An active subscription to the Apple Developer Program ($99 per year).
  2. Xcode 10 or newer (free download via the App Store).
  3. An app-specific password defined for your AppleID (see here for instruction).

Notarization walkthrough

I assume that you are at least vaguely familiar with the topic of code signing. I’m going to show you how to notarize a .pkg installer; caveats for slightly more complex scenarios (e.g. a .dmg that wraps a .pkg and/or .app plus other assets) will follow in a later section.

1. Create a DeveloperID Installer certificate

Log in the Apple Developer portal portal and follow the “Certificates, Identifiers & Profiles” section. Create a new Certificate: when asked, select DeveloperID Installer:

Book Design

Follow the instruction to complete the process – you’ll be required to open the Certificate Assistant from the Keychain Access application to create a Certificate request, then upload it to the Developer Portal. When the process is complete, you’ll be presented with a download link for your certificate, to be imported via double click into the Keychain Access app.

You can verify that the certificate has been successfully installed opening the Terminal and pasting the following command:

security find-identity -v

1) 6A7C3D1A28FA5483E5E8857F2A12EDC71731CAED "Developer ID Installer: Davide Barranca (I2WD7PWGD9)"
  1 valid identities found

Keep note of the string between quotes that says "Developer ID Installer: ...".

2. Signing the Installer

You don’t just need to notarize an installer, it must be signed first. Assuming that the installer.pkg file sits in the folder you have cd to in the Terminal window, you sign the installer with the following command (the output I’ve got comes right below the first line):

productsign --sign "Developer ID Installer: Davide Barranca" ./installer.pkg ./installer_signed.pkg

  productsign: using timestamp authority for signature
  productsign: signing product with identity "Developer ID Installer: Davide Barranca (I2WD7PWGD9)" from keychain /Library/Keychains/System.keychain
  productsign: adding certificate "Developer ID Certification Authority"
  productsign: adding certificate "Apple Root CA"
  productsign: Wrote signed product archive to ./installer_signed.pkg

Let’s break down that:

  • productsign is the utility in charge of the signing.
  • --sign is the argument that defines the Identity associated to the Apple issued certificated, which follows as a string.
  • ./installer.pkg is the relative path (can be absolute as well) that points to the file you want to be signed.
  • ./installer_signed.pkg is the path of the new, signed file that is going to be created by productsign.

Of course you should substitute the "Developer ID Installer:" string with the one you’ve noted in the previous section, as well as the files paths.

You can verify that the installer_signed.pkg has been really signed with:

pkgutil --check-signature ./installer_signed.pkg

   Status: signed by a certificate trusted by Mac OS X
   Certificate Chain:
    1. Developer ID Installer: Davide Barranca (Y4PA7PZLM9)
       SHA1 fingerprint: 3A 7C 3D 1E 28 AA 54 83 E5 E8 11 7F 2A 12 ED C7 17
       --------------------------------------------------------------------    2. Developer ID Certification Authority
       SHA1 fingerprint: 3B 16 6C 3B CC C4 B7 11 C9 FE 22 FA B9 13 56 41 E3       --------------------------------------------------------------------    3. Apple Root CA
       SHA1 fingerprint: 61 1E 5B A2 2C 59 3A 08 FF 58 D1 61 E2 24 52 D1 98       

3. Sending the notarization request

Keep handy the app-specific password you’ve requested from your AppleID account (if you’ve not done it yet, follow the instruction here). Let’s say that it is cvbs-epfg-sizx-olwd. The command I’ve used to request the notarization is:

xcrun altool --notarize-app --primary-bundle-id "com.ccextensions.alce3" --username "YourAppleID@mail.com" --password "cvbs-epfg-sizx-olwd" --file "/full/path/to/the/installer_signed.pkg"

Let’s break down that.

  • xcrun is used to find and execute XCode commands.
  • altool is the executable that performs the notarization request.
  • --notarize-app is the flag that tells altool to request notarization.
  • --primary-bundle-id links to the installer I’m submitting its unique bundle id (in my case "com.ccextensions.alce3", substitute it with yours)
  • --username wants your AppleID.
  • --password wants the app-specific password you’ve requested (here, "cvbs-epfg-sizx-olwd")
  • --file is the flag that tells altool which file to upload. Please note that I had to specify the absolute full path (not the relative that starts with ./) between quotes

The command takes a while to upload the file, and gives you zero feedback; after a while, if the process has been successful, you get back something like:

2019-09-18 16:30:35.659 altool[8788:92462] No errors uploading '/full/path/to/the/installer_signed.pkg'.
RequestUUID = 181638fb-a618-2298-bff0-47fa79f01326

Keep note of the RequestUUID string.

At this point your request for notarizationhas been sent to Apple, and you have to wait for them to process the file.

Mind you: if you get “Error: You must first sign the relevant contracts online. (1048)” (as it has happened to me several times) don’t panic.

You can launch XCode to check whether it asks you to accept its Terms and Conditions, or run the following command to agree from the Terminal:

sudo xcodebuild -license

Then try checking at iTunesConnect, in the section “Agreements, Tax and Banking”. The “Paid Apps” list item may have a Status of “Pending User Information”1, but there shouldn’t be any Agreement pending (usually their warnings are listed separately).

If, still, nothing works it may be a glitch on Apple’s side, so wait a couple of hours and try again.

4. Checking the notarization status

In the Terminal, enter:

xcrun altool --notarization-info 181638fb-a618-2298-bff0-47fa79f01326 --username "YourAppleID@mail.com" --password "cvbs-epfg-sizx-olwd"

The command uses now --notarization-info to ping the current status, and expects the same RequestUUID string that has been sent you in response of the original request. The result is something like that:

2019-09-18 16:31:48.449 altool[9133:95654] No errors getting notarization info.

   RequestUUID: 181638fb-a618-2298-bff0-47fa79f01326
          Date: 2019-09-18 14:30:36 +0000
        Status: in progress
    LogFileURL: (null)

Please note the in progress status. At some point the result will be different, and hopefully similar to this one:

   RequestUUID: 287628eb-e628-4998-b4b0-47fa79f07386
          Date: 2019-09-18 14:30:36 +0000
        Status: success
    LogFileURL: https://osxapps-ssl.itunes.apple.com/awfully/long/URL
    Status Code: 0
Status Message: Package Approved

The installer has been successfully notarized!

If, for some reason, you have to perform several requests and want to check the notarization requests history, run the following command:

xcrun altool --notarization-history 0 --username "YourAppleID@mail.com" --password "cvbs-epfg-sizx-olwd"

Notarization History - page 0

Date                      RequestUUID                          Status  Status Code Status Message   
------------------------- ------------------------------------ ------- ----------- ----------------
2019-09-18 14:30:36 +0000 181638fb-a618-2298-bff0-47fa79f01326 success 0           Package Approved

Next page value: 1768817026000

5. Stapling the ticket to the file

As I wrote, this step is optional but highly recommended: Gatekeeper will be able to find the whitelist info in the file itself, without the need to perform an online check.

xcrun stapler staple "/full/path/to/the/installer_signed.pkg"

Processing: /full/path/to/the/installer_signed.pkg
The staple and validate action worked!

The command here is much simpler: you don’t need to pass any RequestUUID string yourself, for stapler will do the call home at Apple’s on its own.

You can check stapling details with:

stapler validate --verbose "/full/path/to/the/installer_signed.pkg"

The result is too long to paste here, and frankly I’ve no idea what it means: as long as it ends with "The validate action worked!" you should be fine.

And… you’re done 🍾

In my experience the Notarization can take from 60 seconds up to many hours (one time I’ve had the Terminal waiting and checking the status from 3PM to midnight). In theory it should be fast, in practice you can occasionally run into long delays.

Caveats for different scenarios

The example I’ve shown is for one installer.pkg file. Let’s say that you (as I do) deliver to your customers a product.dmg file, that wraps the installer.pkg, an additional uninstaller.app (say, an app-ified AppleScript) and some documentation as well.

In this case, you need to:

  • Notarize only the outmost container (here the .dmg).
  • Sign all the executable children elements (here the .app and .pkg) and the .dmg as well.

Please note that in order to sign a .dmg you need a “Developer ID Application” certificate, instead of the “Developer ID Installer” I’ve used for the .pkg

Lastly, things may get a bit convoluted when it comes to extra libraries/bundles that may be called by your panel – if you feel like it’s your case, please read this thread.

UPDATE: DMG Canvas

The software that I use, and recommend, to build .dmg files is Araelium DMG Canvas, which has recently bumped to version 3.x (a paid upgrade, around $10). One of the new features is the possibility to automate the notarization process while building the .dmg itself.

DMG Canvas

To tell you the truth, the first product I’ve used that for went flawlessly, and in a snap; the second product failed due to a timeout, which sounds very much an Apple issue rather than Araelium’s. Please note that DMG Canvas does the stapling too, which is quite handy, and can be automatized to fit your build system.

UPDATE: Uninstaller app

As I’ve finally upgraded my machine to Catalina, I’m able to experience the entire, happy spectrum of Catalina-related issues. I’ve discovered that with the Uninstall.app the signature I’ve been doing is not enough: the notarization of the outer .dmg fails with the error: "The executable does not have the hardened runtime enabled.".

If this is the case, be aware that you should not export the Uninstall.scpt to Uninstall.app while signing it with the Script Editor (it’s an option in the File > Export dialog). Instead, export it without signature and then use the codesign utility with the -o runtime flag:

codesign -s "Developer ID Application: Davide Barranca" -o runtime /path/to/Uninstall.app

Also, it might happen that the signing process of the .app fails because of the error: "resource fork, Finder information, or similar detritus not allowed". You can spot such digital detritus running:

xattr -lr /path/to/Uninstall.app

/path/to/Uninstall.app: com.apple.FinderInfo:
00000000  50 4C 41 50 6C 74 61 70 00 00 00 00 00 00 00 00  |PLAPltap........|
00000010  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  |................|
00000020
/path/to/Uninstall.app: com.apple.metadata:_kMDItemUserTags:
00000000  62 70 6C 69 73 74 30 30 A0 08 00 00 00 00 00 00  |bplist00........|
00000010  01 01 00 00 00 00 00 00 00 01 00 00 00 00 00 00  |................|
00000020  00 00 00 00 00 00 00 00 00 09                    |..........|
0000002a
/path/to/Uninstall.app: com.apple.metadata:kMDLabel_vtos3pl2xookcnywmtbwlcbnry:
00000000  F2 20 2F 2C 8B D7 B3 8E B9 A9 39 DD 12 79 01 05  |. /,......9..y..|
00000010  BB 76 D8 72 90 73 F2 E9 B2 A3 2A 79 4F 5C 76 F0  |.v.r.s....*yO.v.|
00000020  5B 09 C3 8C 53 5D 90 FA 6D 27 2C 00 86 5E 57 7C  |[...S]..m',..^W||
00000030  DF F9 0B 78 83 19 F9 CA 48 6D 66 21 E9 D9 A8 D4  |...x....Hmf!....|
00000040  5E D2 52 53 A1 94 97 94 C4 3C ED A1 89 FF D4 5B  |^.RS.....<.....[|
00000050  AB 17 0F 53 4F 8A 75 85 FD                       |...SO.u..|
00000059

To clean that mess (whatever it is), run:

xattr -cr /path/to/Uninstall.app

…and the subsequent codesign should complete with no problems.

Support this site!

Please consider supporting my work with the purchase of these books and courses. You can find them all here, bundles available. Thanks! 🙏🏻

Courses

PS. I will update the Ultimate Guide to Native Installers and Automated Build Systems as well, I just wanted you to get informed asap.

  1. If you don’t plan to sell through Apple, there’s no point in filling the uber-annoying Tax forms, etc.Â