Signing and Notarising a Mac binary written in Go

This post is heavily ‘borrowed’ from this post on Stack Overflow-

https://stackoverflow.com/questions/64652704/how-to-notarize-an-macos-command-line-tool-created-outside-of-xcode

And I’ve also liberally borrowed from this script, with many thanks to Armin Briegel. In fact, the way things turned out I could have just used that script, because what I needed was to sign and notarise a command line tool, and that’s exactly the contents of that post. I went further and tried to make the CLI tool into an app, but that failed for a lot of reasons. Nevertheless, I’ll post the code in case it helps someone…

If your tool is a command line utility, use this code

If your tool needs to be in an app bundle, use this code

I’ve been looking for a mesh VPN to provide to clients for several years before we needed it during lockdown- and one by one they’ve proven too immature, impossible to use, impossible to deploy at scale or just impossible. So after another brutal realisation that my intended hero product was not going to work, I went back to a previous one-

And found that things had really improved at Netmaker. It’s a controller for Wireguard, and the rapid pace of development means there’s always new features around the corner. But we don’t care about that, we care about stability! And documentation!

The documentation is the best I’ve seen on an open source project for ages. And another revelation came when I got on to their Discord channel- open, honest and knowledgeable input from the developers.

But there’s just one issue- the Mac client is supplied as a plain binary file. It needs to be signed and notarised and have security entitlements just to run. So it won’t.

We need to fix this even to do a ‘Proof of Concept’, so let’s do it…

Requirements

You’ll need a bunch of things, let’s just assume you are a desperate as me and already have an Apple developer account, some signing certificates, and a willingness to spend so much time in front of a computer that your spouse forgets your name…

Log in to your developer AppleID and create an ‘App specific Password’
Now copy that password into your Keychain with the name set to ‘Developer-altool’

I got to cheat a bit with the next bit- I’d played with this in Xcode to the point where I had a file for the entitlements and an info.plist file to include. These aren’t hard to get or make. Here’s the entire text of the ‘netclientRelease.entitlements’ file-

<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">

<plist version="1.0">

<dict>

<key>com.apple.security.cs.allow-unsigned-executable-memory</key>

<true/>

<key>com.apple.security.cs.disable-library-validation</key>

<true/>

</dict>

</plist>

Also note that our app has no GUI, so we add a key called ‘LSUIElement’ to stop it from appearing in the Dock
download the binary, ours is called ‘netclient-darwin’
Change the name to simply ‘netclient’

Codesign the binary

You’ll need both netclient binary and netclientRelease.entitlements in your build folder

codesign --deep --force --options=runtime --entitlements ./netclientRelease.entitlements --sign "Developer ID Application: Buymax Pty Ltd (XXXXXXXXX)" --timestamp ./netclient

(Optional) Build the binary into an app bundle

My build didn’t require this, and so the code posted above isn’t 100% working. But it does look like it will work with minimal tweaking, so have at it.
For this step I used Macapp Go to create the bundle files and put everything together. This (obviously) requires Golang to be installed but that’s why you’re signing a go binary, right? What do we need for this?
You’ll need to create the required folder structure and add the required parts into the correct spots –

MacApp_Go_Folder
|-macapp.go (script)
|-icon.png
|-build_folder
|-signed binary
|-entitlements
|-info.plist

Note the icon.png goes one level up for some reason. Execute the script- All explained here

go run macapp.go -assets build_folder -bin netclient -icon ./netmaker.png -identifier com.gravitl.netclient -name "netclient" -o ~/Desktop


At this point you should have a ‘netclient.app’ in your MacApp_Go_Folder

(Optional) Sign the .app bundle

Again, do this only if you are making an app. Make a temp directory to hold the app parts

mkdir /tmp/netclient

Then use ‘ditto’ to create the install path in your temp folder and copy the bits into the correct structure

ditto /path/to/netclient /tmp/netclient/usr/local/netclient

You can add anything else to this folder structure that needs to be present, like a launchdaemon, postinstall script etc. Then create the .pkg

productbuild --identifier "com.gravitl.netclient.pkg" --sign "Developer ID Installer: Buymax Pty Ltd (XXXXXXXXX)" --timestamp --root /tmp/netclient/etc/netclient / netclient.pkg

Notarisation

xcrun altool --notarize-app --primary-bundle-id "com.gravitl.netclient” --username="developeremail@company.com.au" --password "@keychain:Developer-altool" --file ./netclient.pkg

you’ll be asked to supply an admin password to access the App Specific Password and your .pkg will be uploaded to Apple for checking. Once you get an email back saying it’s ok, you can staple the .pkg with

xcrun stapler staple netclient.pkg

You can check that everything is correct with Apparency, Packages, or the command line 

*Before you say something like gon could do this- no it can’t, it doesn’t yet support creating .app bundles. I also dearly wanted to use Armin Briegels script here, but it assumes you will build the binary with Xcode, but no, Xcode looked at me funny when I gave it the go binary…

Putting it all together

Use the code in the links above! Please suggest improvements and corrections, and fork at will…

Recent posts