11

MacOS now requires that all applications are hardened, signed and notarized. How does one sign and notarize an application created outside of XCode with a tool like PyInstaller?

I've sorted out the signing and notarization for .app files created outside of XTools. There's a really helpful thread here that shows how to add an entitlements.plist which fulfills the hardening of PyInstaller .app files. I believe this also works on command line utilities as well, but could be missing something. Submitting a .dmg containing a .app for notarization using altool will pass the tests and be notarized by Apple.

Submitting a single command line utility using the same process will also pass Notarization, but does not appear signed or notarized to the GateKeeper function on other machines. I assume this has something to do with the fact that a valid Info.plist file is not included in the PyInstaller binary as detailed in this blog post about building and delivering command line tools for Catalina.

Checking the signature of a signed file using codesign -dvv indicates that the Info.plist is "not bound".

$ codesign -dvv ./dist/helloworld
Executable=/Users/aaronciuffo/Documents/src/toy/codesign/dist/helloworld
Identifier=helloworld
Format=Mach-O thin (x86_64)
CodeDirectory v=20500 size=72086 flags=0x10000(runtime) hashes=2244+5 location=embedded
Signature size=9054
Authority=Developer ID Application: Aaron Ciuffo (4H9P6Q65AM)
Authority=Developer ID Certification Authority
Authority=Apple Root CA
Timestamp=Nov 2, 2020 at 9:03:30 PM
Info.plist=not bound
TeamIdentifier=4H9P6Q65AM
Runtime Version=10.11.0
Sealed Resources=none
Internal requirements count=1 size=172

One suggested solution is using the Go gon package but gon does not cover adding the required Info.plist as far as I can tell.

Is there a workflow or application that can assist in this? How does one create an CL application outside of XCode and successfully sign it?

Aaron Ciuffo
  • 804
  • 8
  • 22

2 Answers2

15

How to Sign and Notarize a Command Line Tool Manually

Apple requires that all distributed binaries are signed and notarized using a paid Apple Developer account. This can be done using commandline tools for binaries created with tools such as PyInstaller, or compiled using gcc.

Automated Python Script for this Process

The script linked below allows you to automate this process using project specific .ini files.

codesign.py

Setup

If you already have a developer account with Developer ID Application and Developer ID Installer certificates configured in XCode, skip this step

  • Create a developer account with Apple
  • Download and install X-Code from the Apple App Store
  • Open and run X-Code app and install whatever extras it requires
  • Open the preferences pane (cmd+,) and choose Accounts
    • click the + in the lower left corner
    • choose Apple ID
    • enter your apple ID and password
    • Previously created keys can be downloaded and installed from https://developer.apple.com
  • Select the developer account you wish to use
  • Choose Manage Certificates...
  • Click the + in the lower left corner and choose Developer ID Application
  • Click the + in the lower left corner and choose Developer ID Installer

Create an App-Specific password for altool to use

Instructions from Apple

  • Open KeyChain Access
  • Create a "New Password Item"
    • Keychain Item Name: Developer-altool
    • Account Name: your developer account email
    • Password: the application-specific password you just created

Create an executable binary with Pyinstaller or other tool

NB! Additional args such as --add-data may be needed to build a functional binary

  • Create a onefile binary
    • pyinstaller --onefile myapp.py

Sign the executable

  • Add the entitements.plist to the directory (see below)
  • List the available keys and locate a Developer ID Application certificate:
    • security find-identity -p basic -v
    1) ABC123 "Apple Development: aaronciuffonl@gmail.com ()"
    2) XYZ234 "Developer ID Installer: Aaron Ciuffo ()"
    3) QRS333 "Developer ID Application: Aaron Ciuffo ()"
    4) LMN343 "Developer ID Application: Aaron Ciuffo ()"
    5) ZPQ234 "Apple Development: aaron.ciuffo@gmail.com ()"
    6) ASD234 "Developer ID Application: Aaron Ciuffo ()"
    7) 01010A "Developer ID Application: Aaron Ciuffo ()"
       7 valid identities found
    
  • codesign --deep --force --options=runtime --entitlements ./entitlements.plist --sign "HASH_OF_DEVELOPER_ID APPLICATION" --timestamp ./dist/foo.app

Package as a pkg for installation

  • Create a temp directory to build the package:
    • mkdir /tmp/myapp
  • Use ditto to build the pkg installer structure
    • ditto /path/to/myapp /tmp/myapp/path/to/install/location
      • to install application "WhizBang" into /Applications/ on the target use: ditto ~/src/whiz_bang/dist/whizBang /tmp/whiz_bang/Applications/
    • repeat for all files that should be packaged
  • build the package
  • productbuild --identifier "com.your.pkgname.pkg" --sign "HASH_OF_INSTALLER_ID" --timestamp --root /tmp/myapp / myapp.pkg
  • NB! the format for the --root option is as follows: --root <ditto path> <relative path on target system to install from> <signed .pkg file>

Notarize

  • xcrun altool --notarize-app --primary-bundle-id "com.foobar.fooapp" --username="developer@foo.com" --password "@keychain:Developer-altool" --file ./myapp.pkg
  • Check email for successful notarization
    • Alternatively check status using:
      • xcrun altool --notarization-history 0 -u "developer@***" -p "@keychain:Developer-altool"
  • If notarization fails use the following to review a detailed log:
  xcrun altool --notarization-info "Your-Request-UUID" \
             --username "username@example.com" \                                    
             --password "@keychain:Developer-altool"   

Staple notarization to pkg

  • add the notariztaion to the pkg
    • xcrun stapler staple ghostscript64.pkg

Useful Resources

Aaron Ciuffo
  • 804
  • 8
  • 22
  • 1
    Really comprehensive, thanks! Works for notarizing DMG files too. – yodavid Apr 22 '21 at 03:33
  • 2
    Apple sure doesn't make it easy if any piece of your toolchain falls outside of XCode! – Aaron Ciuffo Jun 14 '21 at 09:16
  • 2
    Seriously? I have to pay to deploy my app to Mac? I guess no Mac support then... – Robert S. Jun 15 '21 at 19:05
  • @RobertS. Yup. And you get to pay every year if you want to offer updates. I assume this is to make it financially impractical to publish malware through this channel. Still it bugs me that I have to pay to distribute FREE software. I don't think Apple isn't so much interested in our coin as they are in controlling every aspect of publishing for their devices. – Aaron Ciuffo Jun 16 '21 at 10:58
  • Yeah it's a shame. I guess Apple doesn't like Open Source. I want to provide cross-platform open source software for all and then this. :/ – Robert S. Jun 17 '21 at 06:07
  • @RobertS. I don't think it has anything to do with liking FOSS, but more about tightening control over their platforms. They're all about building walled gardens around content to provide an illusion of security, safety and privacy. Once they have a nice tall wall around their ecosystem, they can set up toll booths at the borders and extract value from all the content that moves in and out. Our $99 dev-fee isn't even at the level of couch-change for them. They're more interested in extracting value from the the likes of Epic and Netflix. – Aaron Ciuffo Jun 18 '21 at 10:14
  • Thank you for this, Aaron. However, this concludes that there is no way to notarize just the cli tool bundled into a single file, we can only notarize the .pkg file and distribute the cli tool along with an installer, correct? – Maxim V. Pavlov Oct 16 '21 at 07:52
  • @maxim, This should work for most any single-file executable.This process does work and results in a .PKG file that can install to a target location. The script I link to above does the job really well if you set everything up as specified. I have not successfully notarized & signed .app folders that contain folders with multiple files, but this does work for single file frozen binaries. – Aaron Ciuffo Oct 17 '21 at 19:29
  • @AaronCiuffo I wish it was true. If one tries to notarize a simple executable file the notarization step will error out stating: ERROR ITMS-4064: "File extension of file 'myclitool' is invalid for this software; use 'zip,pkg,dmg' instead." – Maxim V. Pavlov Nov 29 '21 at 21:06
  • @RobertS. You know that signing a Windows application also required a paid certificate, don’t you? A lot of malware has what we might regard as open-source content, so, yes, neither Apple nor Microsoft like some open source. – Manngo Feb 11 '22 at 21:56
  • You just saved my life. For anyone struggling with notarising pure Mach-O binaries, you can just pack them in a zip file `zip my_bin.zip my_bin` and notarise `my_bin.zip`. – Klemen Tusar May 13 '22 at 22:04
2

DMG distribution

Using Aaron Ciuffo's post swap the PKG commands with this

If you want to distribute your binary in a DMG, in addition to calling codesign on the binary you must create the DMG correctly.

Start by creating your folder and clearing any junk or upload will fail.

mkdir DistributionFolder
xattr -cr ./DistributionFolder

Then move the binary into that folder and create an appropriately sized DMG

hdiutil create -fs HFS+ -volname MyApp -srcfolder ./DistributionFolder ./Distribution.dmg

Eject the drive. Sign the DMG and upload. Once notarised you can staple the same as in Aaron's answer.

Cameron Lowell Palmer
  • 21,528
  • 7
  • 125
  • 126