I spent a month looking into notarizing GHC for Macs. Although GHC still won’t be notarized for 9.6—more on that later—I did learn a lot about it, and I’d like to share that knowledge for posterity. If you are looking to distribute (Haskell) apps on Macs, this may be useful to you! I will also explain how GHC currently deals with the notarization requirement and hint at next steps.
My main source for notarization knowledge was Apple Developer Documentation, which is fairly complete.
Notarization is a security mechanism on macOS. Developers submit their apps to an Apple service that checks for malware and certain other issues. If the checks pass, Apple will provide a certificate for the app. This certificate is checked by macOS when the app is run. Although it’s possible to run un-notarized apps, Apple recommends against it, and recommends that developers get their apps notarized!
Apple provides a notarization service to developers. The service notarizes app bundles, installer packages, or zip files that contain executables. (See glossary below.) To be notarized, the executables within must be code signed with the Hardened Runtime enabled.
I have a prototype script that notarizes a GHC bindist, where you can see the actual commands used to get an app notarized.
Notarization is checked in two ways. The first is the App Store. An app must be notarized before it can be uploaded for distribution.
If you distribute your apps to users directly over the internet, however—as GHC does—there is another mechanism. (Some details in this section may still be wrong, but I have the gist of it correct. Corrections welcome!) Safari and other apps automatically make a notarization check when downloading files. Downloads that fail the check will have a ‘quarantine’ attribute applied to them, which applies recursively to the contents of archive files. Quarantined files cannot be executed unless the user makes manual security changes to their system.
Notarization certificates are stored on Apple servers, and macOS will make a network call to verify an app if necessary. To avoid this, developers can directly attach the certificate to their apps.
The notarization check is not very robust to user intervention. Notably, curl does not make a notarization check: Files downloaded with curl will not be quarantined, bypassing the whole scheme. You can also manually clear the quarantine attribute after a failed notarization check, or just
cat <evil.zip >saintly.zip to conjure a new file with no quarantine attribute.
Currently, GHC deals with notarization in the following ways:
- For users who download a bindist with Safari, the install script automatically clears the quarantine attribute on all files. Thus, although the original download fails the notarization check, the quarantine is removed and GHC works as usual.
- Unlike Safari, GHCUp does not trigger a notarization check.
- GHCUp further attempts to remove the quarantine attribute. I have not checked, but I think this has no effect.
- The underlying GHC install script still also clears the quarantine attribute, as just mentioned.
- I have not checked, but I assume Stack also does not opt in to a notarization check.
GHCHQ wants to notarize GHC bindists. Once this is done, bindists downloaded with Safari will pass the notarization check and not be quarantined. The primary motivation is the concern that Apple may suddenly lock down execution even further in unexpected ways. By notarizing bindists, GHC is somewhat closer to the standard workflow for Mac apps. This would also allow removing the attribute-clearing steps, and even pave the way for GHCUp to opt in to triggering a notarization check.
Yeah, that’s my bad. I messed up in communicating how things were going.
For one thing, it took me a long time to dig into the Apple development experience. I even bought my first Mac to get hands-on experience both as a developer and user. I also struggled to understand the problem being solved and how to reach the goal in a way that satisfied my principles. There are a lot of outstanding concerns:
- The cost of automation, particularly testing the notarization for efficacy
- The limited scope of use cases that need notarization
- Notarization requires enabling the Hardened Runtime, which needs to be tested to see how it interacts with GHC plugins and similar use cases
- Opportunity cost versus redesigning how GHC is packaged for Mac in the first place
- The ease in bypassing notarization checks
- The need for cross-project collaboration. This is not just a GHC issue (see discussion below).
By the time I finally reached my recommendation against notarizing the existing bindist, the schedule was too tight to have a full discussion. The concern about further security requirements from Apple is valid, however, and notarization will be revisited for 9.8.
Hopefully that was useful information about Mac notarization!
- In Apple documentation, this term usually means a product built for end users and distributed as an app bundle, ideally through the App Store.
- Other methods of distribution are officially blessed, however, and the term is obviously highly overloaded.
- A distribution method for Mac applications.
- A directory with a particular file structure and a name that ends in “.app”.
- Treated as a single file by Finder (but not the shell).
- Double-clicking on it will execute the application within.
- Contains metadata and resources for the app.
- Appears to have been replaced by installer packages for a while, but are once again the primary way to distribute applications.
- A term used in GHC (not related to Apple).
- An archive file containing the compiler, a few other utilities like haddock, core libraries, and some scripts for installing the files.
- Similar in structure and purpose to an Apple-specific “installer package” (see above).
- A mechanism for validating that files have not been tampered with.
- Uses public key cryptography and a certificate chain rooted at Apple.
- Acquiring a signing key requires a (free) account and some personal information.
- In certain circumstances, the signature for a binary file is checked by macOS before the file is executed.
“The Hardened Runtime… protects the runtime integrity of your software by
preventing certain classes of exploits, like code injection, dynamically linked
library (DLL) hijacking, and process memory space tampering.”
- An archive file designed for use with the macOS Installer.
- May contain files to be installed, metadata, other resources, and scripts that are run by the Installer automatically.
- Also known as a “flat package”.
- Replaced app bundles at some point, but only for a short while.
- Further reading OSX flat packages — docosx 0.2 documentation