# Mac Notarization (and GHC)

**URL:** <https://discourse.haskell.org/t/mac-notarization-and-ghc/5722>\
**Category:** Uncategorized\
**Created:** [February 2, 2023, 2:21pm UTC](https://discourse.haskell.org/t/mac-notarization-and-ghc/5722 "2023-02-02T14:21:04Z")\
**Posts on this page:** 8\
**Page:** 1

<div class="post-metadata">

**Author:** ![chreekat](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/chreekat/32/2669_2.png) [@chreekat](https://discourse.haskell.org/u/chreekat)\
**Post date:** [February 2, 2023, 2:21pm UTC](https://discourse.haskell.org/t/mac-notarization-and-ghc/5722/1 "2023-02-02T14:21:04Z")

</div>

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](https://developer.apple.com/documentation/security/notarizing_macos_software_before_distribution), which is fairly complete.

## What is Mac notarization?

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!

### Getting 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](https://gitlab.haskell.org/-/snippets/5709), where you can see the actual commands used to get an app notarized.

### Checking notarization

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.

## How does GHC handle notarization?

Currently, GHC deals with notarization in the following ways:

1. 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.
2. 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.

3. I have not checked, but I assume Stack also does not opt in to a notarization check.

## What does the future hold for GHC and Mac notarization?

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.

### Wasn’t this scheduled for 9.6?

Yeah, that’s my bad. I messed up in communicating how things were going.

### So how did it go?

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!

* * *

> **GLOSSARY**
>
> #### App:
> 
> - 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.
> 
> #### App bundle:
> 
> - 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.
> 
> #### Bindist:
> 
> - 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).
> 
> #### Code signing:
> 
> - 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.
> 
> #### Hardened Runtime:
> 
> “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.”
> 
> - [Hardened Runtime | Apple Developer Documentation](https://developer.apple.com/documentation/security/hardened_runtime)
> 
> #### Installer package:
> 
> - 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](https://matthew-brett.github.io/docosx/flat_packages.html)

---

<div class="post-metadata">

**Author:** ![hasufell](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/hasufell/32/1250_2.png) [@hasufell](https://discourse.haskell.org/u/hasufell)\
**Post date:** [February 2, 2023, 3:06pm UTC](https://discourse.haskell.org/t/mac-notarization-and-ghc/5722/2 "2023-02-02T15:06:09Z")

</div>

So I guess this means for distributors (like GHCup) the best thing is to just keep using `xattr -r -d com.apple.quarantine <path>` no matter if it’s notarized or not?

---

<div class="post-metadata">

**Author:** ![chreekat](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/chreekat/32/2669_2.png) [@chreekat](https://discourse.haskell.org/u/chreekat)\
**Post date:** [February 2, 2023, 3:28pm UTC](https://discourse.haskell.org/t/mac-notarization-and-ghc/5722/3 "2023-02-02T15:28:28Z")

</div>

On the contrary, since GHCUp does not opt in to quarantining bindists, I would be curious to learn if it ever needed to use `xattr` in the first place. It’s not something I tried yet.

In the long term, I speculate we may want to opt the whole Haskell ecosystem into notarization. If we go that path, rather than circumventing the notarization check, GHCUp would _enforce_ it.

In the short term, it’s ain’t broke so let’s not fix it.

Edit: this was supposed to be a reply to @hasufell but I clicked the wrong button.

---

<div class="post-metadata">

**Author:** ![hasufell](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/hasufell/32/1250_2.png) [@hasufell](https://discourse.haskell.org/u/hasufell)\
**Post date:** [February 3, 2023, 3:08am UTC](https://discourse.haskell.org/t/mac-notarization-and-ghc/5722/4 "2023-02-03T03:08:30Z")

</div>

> [@chreekat](#):
>
> On the contrary, since GHCUp does not opt in to quarantining bindists, I would be curious to learn if it ever needed to use `xattr` in the first place. It’s not something I tried yet.

There are multiple tickets where users needed to use `xattr` to make GHC bindists work:

- [#17353: ghc 8.8.1 fails on macOS 10.15 Catalina · Issues · Glasgow Haskell Compiler / GHC · GitLab](https://gitlab.haskell.org/ghc/ghc/-/issues/17353)
- [#21202: Install problem of GHC 9.2.2 on macOS 12.2.1 · Issues · Glasgow Haskell Compiler / GHC · GitLab](https://gitlab.haskell.org/ghc/ghc/-/issues/21202)

There seems to be an issue with stripping binaries as well:

- [#318: ghcup mac binaries should be signed · Issues · Haskell / ghcup-hs · GitLab](https://gitlab.haskell.org/haskell/ghcup-hs/-/issues/318)

So I don’t have clarity over the issue, still.

---

<div class="post-metadata">

**Author:** ![chreekat](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/chreekat/32/2669_2.png) [@chreekat](https://discourse.haskell.org/u/chreekat)\
**Post date:** [February 3, 2023, 6:53am UTC](https://discourse.haskell.org/t/mac-notarization-and-ghc/5722/5 "2023-02-03T06:53:25Z")

</div>

> [@hasufell](#):
>
> There are multiple tickets where users needed to use `xattr` to make GHC bindists work:

Those GHC issues are both closed. The first is only tangential to code signing and notarization, and the second was dealt with in the way I’ve described above: the install script in the GHC bindist removes the quarantine attribute (assuming it exists) with `xattr`.

The GHCUp issue is more interesting and it would be good to try to reproduce it at some point. I found it difficult to trigger any warnings unless I was downloading something with Safari.

---

<div class="post-metadata">

**Author:** ![hasufell](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/hasufell/32/1250_2.png) [@hasufell](https://discourse.haskell.org/u/hasufell)\
**Post date:** [February 3, 2023, 8:28am UTC](https://discourse.haskell.org/t/mac-notarization-and-ghc/5722/6 "2023-02-03T08:28:04Z")

</div>

> [@chreekat](#):
>
> the install script in the GHC bindist removes the quarantine attribute (assuming it exists) with `xattr`

Which GHC versions? Is this backported to 8.0.2?

GHCup cannot easily assume bindist fixes exist. We don’t just support installing the latest GHC version, which I think is sometimes easily forgotten.

Additionally we support users installing bindists from source and feeding them into GHCup.

I’ve yet to see a clear outline when it’s needed and when not and how tools would be able to discover when that’s the case.

---

<div class="post-metadata">

**Author:** ![ocramz](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/ocramz/32/3713_2.png) [@ocramz](https://discourse.haskell.org/u/ocramz)\
**Post date:** [February 4, 2023, 6:33am UTC](https://discourse.haskell.org/t/mac-notarization-and-ghc/5722/7 "2023-02-04T06:33:52Z")

</div>

As a Mac user, Thank you for digging into this @chreekat ^^

Apple’s “walled garden” strategy is distinctly hostile to open source.

---

<div class="post-metadata">

**Author:** ![chreekat](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/chreekat/32/2669_2.png) [@chreekat](https://discourse.haskell.org/u/chreekat)\
**Post date:** [February 7, 2023, 1:42pm UTC](https://discourse.haskell.org/t/mac-notarization-and-ghc/5722/8 "2023-02-07T13:42:37Z")

</div>

> [@hasufell](#):
>
> I’ve yet to see a clear outline when it’s needed and when not and how tools would be able to discover when that’s the case.

I think nothing should be changed in GHCUp right now, precisely because I know that GHCUp supports many versions of GHC besides HEAD! 🙂 This would be true even if GHC _did_ start notarizing bindists.

In fact, one of my outstanding concerns is that there wasn’t enough up-front coordination across tools. Adding notarization **must** be done in a backward-compatible way so that there is no forced upgrade required for GHCUp or anything else. It is not just a GHC issue.

In other words, I pushed back on doing this now because how tools should discover and react to notarization _has not been designed yet_. So I guess we have some of the same concerns.
