Creating command tools for macOS used to be so simple. Although you could sign them, there seemed little point as so many were (and still are) unsigned. The messiest step in the process was putting them into an Installer package and signing it with a special developer certificate. Quite why you need a different certificate to make a package has remained a mystery to me. Thanks to Stéphane Sudre’s excellent Packages app, I got all that sorted out well over two years ago.
I was taken aback, then, when I heard from WWDC that Catalina was going to apply essentially the same security rules to command tools as it does to apps. This meant that my four command tools would have to be signed, hardened and notarized in very short order, as at that stage the Catalina beta was already available. When I write have to, I don’t mean that in a strict sense: of course I could just ship an unsigned tool, but if I wanted to continue providing a properly-signed package, I’d have to comply with the new rules.
Ever since Apple started its Notary Service, during WWDC 2018, I’d been looking for information about notarizing command tools, but nothing ever seemed to appear. Earlier this year, when preparing for the introduction of notarization requirements in 10.14.5, Apple revamped most of that documentation, but still didn’t explain how to get a command tool notarized, or even whether that was possible or necessary. The emphasis was almost exclusively on apps.
Apple has also been extremely vague over explaining what notarization really is and how it works. Like all things security, it feeds us small tidbits which it feels we should know, and doesn’t explain concepts, principles, or mechanisms. I knew that I could build a command tool, place it in a signed Installer package, and submit that for notarization, but did that constitute notarization of the command tool or just the package? Was it possible or necessary to harden a command tool, and if so, how?
Look today at the documentation, and it still doesn’t refer to command tools. The main page gives an incomplete list of four different types of “software deliverables” which can be notarized: apps, some other code bundles (not explained any further), disk images, and Installer packages. None of those covers command tools, unless they’re delivered in a package. And does that notarization cover just the package, or the installed tool too?
A little further down, Apple lays down the requirements for notarization:
- all executable code is to be signed using a Developer ID signature;
- the signature must include a secure timestamp;
- apps and “command line targets” are to have hardened runtimes;
- the com.apple.security.get-task-allow entitlement isn’t to be set;
- code is to be linked against the macOS 10.9 or later SDK.
I knew that I could meet the first of those by adding an Info.plist to the command tool and enabling signing. I presumed that using Xcode’s Archive feature, a secure timestamp would then be added with the signature, and that entitlement wouldn’t be set, and I was already using the macOS 10.14 SDK. That left only 3, the requirement for a hardened runtime.
So I followed the link there to see how I could enable a hardened runtime for a command tool. It takes you to Xcode Help, which refers only to the Capabilities library offered for an app. That doesn’t exist when your Xcode project is for a command tool.
I next scoured Apple’s article on customising the notarization workflow, which was of no help as it makes the opening assumption that you have already hardened your code. I then read advice on resolving common notarization issues, which refers back to the same Xcode Help page which I had already found of no help.
After a lot of Internet searching, I stumbled across a discussion which mentioned the Xcode option to Enable Hardened Runtime buried in the Signing options. Without that, I would have consistently failed in all attempts to follow Apple’s instructions, and notarization would have been doomed to failure.
The processes that I then had to go through to sign, harden, package and notarize each of my four command tools were a revelation. They’re detailed step by step in this article and in this PDF:
They demonstrate how badly this common task in development has been cobbled together without any design: it’s not a workflow but an obstacle course. To turn 260 lines of code into an installable and duly notarized 33 KB package took a total of 5 apps (including Xcode itself), 4 command tools which I had to invoke in 6 different commands, 2 different developer certificates (one for the tool, one for the package) and an app-specific password to be able to run
altool to submit the tool for notarization.
Of course no Apple engineer has to negotiate this tortuous labyrinth. So why should they bother to make it straightforward for third-party developers?
Apple: if you want to encourage developers to adopt your new security policies and stop delivering unsigned code, try providing tools which facilitate rather than obstruct, and document such common workflows as if someone there has actually bothered to test them out. Preparing software for distribution shouldn’t be harder than writing the code in the first place.