The Gatekeeper system has protected macOS users against malicious software since its introduction in OS X 10.7.3 (Lion). This system assures users that software comes from a trusted source and doesn’t contain malicious content. But how does it work?
The Mac software ecosystem has historically been fairly untroubled by malicious viruses and software. This was due in part to the comparatively small user base and partly because the system — which is based on Unix — is naturally partitioned into users and groups, making it difficult for malicious code to obtain administrative privileges.
But the platform is increasing in popularity. Apple’s desktop operating system accounted for approximately 10% of desktop market share in 2019. The same kernel — Apple XNU, a modified Mach microkernel — is also used by the company’s IOS devices: iPhone and Apple Watch. This increasing install base makes the platform an attractive target for malicious software.
Apple has two main strategies in place to protect its users. We’ll look at each stage of this protection regime in the next sections, but broadly they comprise:
Signing ensures the code belongs to us and can’t be changed after it’s signed. This is done by the
codesign tool, which:
Here’s how we test-sign the FusionReactor macOS Native Library, which is used by the FusionReactor Production Debugger:
xcrun codesign --verbose --strict --keychain /Users/jhawksley/Library/Keychains/login.keychain -s CERT_ID_HERE --timestamp target/libfrjvmti_x64.dylib
The second stage is to have Apple actually check our code.
The Notarization command looks like this:
xcrun altool --notarize-app --username "email@example.com" --password "our_password" --primary-bundle-id "com.intergral.bundleid" --file bundle.zip
Before we ship the library to Apple for Notarization, we have to sign it using
codesign, and we have to zip it up to minimize the transfer size. The username and password are those of a valid Apple Developer Account.
Notarization is an automated service, which — while not providing usability or design feedback, like App Review — does checks code is correctly signed, and doesn’t contain malicious content.
The result of this is a Notarization Ticket, which is a piece of secure data that Apple sends back to us and also publishes online in the Gatekeeper Code Directory Catalog.
Some types of software — for instance the native library we showed in Code Signing above — don’t have space in their structure for a ticket, so they can’t be stapled. Other types of software, like the FusionReactor macOS Installer, do have space, and the Notarization Ticket obtained above can be stapled to them.
When you run our software on your machine, Gatekeeper automatically checks to see if the software is valid. If there’s a network connection available, Gatekeeper uses the online Code Directory to look up the ticket and checks it against the software. Should no network is available, Gatekeeper uses the stapled ticket.
If a valid ticket is located, Gatekeeper knows that Apple has checked this software and that it meets their standards — and can run.
Apple has been gradually tightening up the conditions under which unsigned software can run. In macOS Catalina, this is still possible (you have to turn Gatekeeper off using Terminal commands) although in the future even that may no longer be possible.
When macOS tries to use or install unsigned content on macOS Catalina, you’ll see the following dialog (which can’t be bypassed) — and the content is not opened.
When content has been correctly signed, Gatekeeper tells you where it came from and lets you decide whether to open it. Here’s what we see when open our (signed, notarized, stapled) installer from our build server.
If you want to check a signature, this is easy to do. Open the Terminal app, and use the
codesign command to retrieve the certificate:
codesign -d --verbose=4 ~/Downloads/FusionReactor_macos_8_3_0-SNAPSHOT.dmg
This spits out the following (excerpted for clarity):
CodeDirectory v=20100 size=179 flags=0x0(none) hashes=1+2 location=embedded
Authority=Developer ID Application: Intergral Information Solutions GmbH (R3VQ6KXHEL)
Authority=Developer ID Certification Authority
Authority=Apple Root CA
Timestamp=Dec 5, 2019 at 14:52:15
Identifier is the file name (minus extension) we signed, and the
CandidateCDHashFull values tell Gatekeeper how to perform an online lookup of our Notarization Ticket.
Authority lines are the chain of trust: they show that Apple has trusted us by checking our details and issued a certificate, whose serial number is R3VQ6KXHEL.
Timestamp shows when we actually signed this software. If you try to use it in future, perhaps when our certificate has expired (hopefully there’ll be many new versions of FusionReactor before then!), the Timestamp assures Gatekeeper that the software was signed within the certificate validity period, and should be treated as if the certificate was still valid. Gatekeeper should then continue to open this software indefinitely.
It’s possible to automate the signing, notarization and stapling process, but it’s not exactly straightforward.
Apple’s development tool – the venerable Xcode – handles signing, notarization and stapling seamlessly as part of its user interface. Apple does provide command-line tools to perform these tasks (
stapler) but these all make some assumptions that the user running them is logged in.
The exact mechanics of automated signing of Apple binaries are rather beyond the scope of this article. However, we can give you some hints:
ssh. The Aqua session is required to use the code signing tools. The login can be done on the Mac itself, or using Apple Screen Sharing (which is a customized VNC) but the session should be ended by closing the window, not logging the user out.
Another wrinkle in the automation of this procedure is that the notarization command (
xcrun altool --notarize-app) is asynchronous. Once the code is submitted (which itself can take a couple of minutes), the Apple Notarization Service returns some XML containing a
RequestUUID. You have to then poll the Service using this UUID until it returns either success or failure. This can take up to 10 minutes, in our experience. If you don’t parallelize this part of your build, it will be impose a long delay.
Comments are closed.