Apple recently announced that new Mac developer accounts (and eventually all mac developers) who distribute Mac apps outside the app store will be required to submit app for notarization. The notarization was announced at WWDC 2018 and was optional, but Apple was clear that it was going to be required in a future version of macOS. That day is nearly upon us, as the new developers distributing apps on macOS on 10.14.5 or later will require the apps be notarized, and we can expect that 10.15 will require this for all macOS apps outside the App Store. But why is Apple doing this? How does it enhance security?
I have been looking into how notarization, stapling, and hardening work for the last week or so, and after some long discussions with other developers and some Apple folks, I have a good understanding of how the process works and the motivation is behind notarization.
Prior to notarization, macOS used Gatekeeper to prevent apps downloaded from the internet from being launched. macOS kept a list of known apps that were known to have issues and prevent then from being executed. However, after the app passed Gatekeeper and was approved by the user, it was difficult to detect if an existing binary got infected and there was no good way to revoke the approval of the application (the developer’s distribution certificate could be revoked but that would revoke all the developer’s applications). To combat malware, Apple introduced notarization, hardening,, and stapling.
In order to prevent malware, an app that contains dangerous or infected code should be stopped from running on the system. If existing binaries are infected, they need to be detected. Once detected, the malware must be prevented from spreading to other systems by preventing known apps from ever launching. To accomplish this, Apple has introduced 3 new technologies: notarization, app hardening, and ticket revocation.
To prevent apps from ever launching that contain dangerous code (such as malware), the app needs to be scanned prior to be installed. Apple now provides a notarization service where developers can upload their apps, Apple scans the apps to verify they do not contain any dangerous code, and notarize the app if it passes the tests. The signature of the app is then kept on Apple services and any time an app is launched for the first time, Gatekeeper can check against Apple’s servers to verify that app has been notarized. Apple also provides a digitally signed copy of a notarization to the developer as a “ticket” that can be attached (“stapled”) to the app. This is not required since Gatekeeper can look up the notarization, but it speeds up the launch process if the ticket is stapled to the app (or to the installer media like a disk image).
Once Gatekeeper approves the app, there needs to be a way to detect if an app has been modified (either in memory or on disk). To do this, Apple requires apps that are submitted to the notarization service to have hardening enabled. Hardening is a flag in the Mach-O header of the binary of that app that tells the macOS that the app has opted in to hardening. This enables the system to verify signature down to each page of code:
When you opt into this new runtime, the system will enforce that every single executable page within your address space must be backed by the original code signature that shipped with your app.WWDC 2018 session “Your Apps and the Future of macOS Security” :
Hardening can also prevent your app from doing something it was not intended, like send Apple Events, use the camera, or access the address book. The developer specifies what the app needs to access, and if some bad actor uses the app in an unattended way to access these resources, it will denied. The developer has control over what the app is allowed to access.
If a Developer ID signing key is exposed, Apple is now able to revoke tickets for unauthorized version of apps that were signed with the Developer ID signing key.
Notarization also protects your users if your Developer ID signing key is exposed. The notary service maintains an audit trail of the software distributed using your signing key. If you discover unauthorized versions of your software, you can work with Apple to revoke the tickets associated with those versions.https://developer.apple.com/documentation/security/notarizing_your_app_before_distribution
It may be possible that if an app passes the notarization process but is later found to contain malware, the ticket for this app can be revoked and the app will no longer be allowed to launched (either initially or subsequently). Launch Services on macOS contacts the server gs.apple.com on port 443 to verify if a app’s ticket has been revoked. It could be possible that if the ticket is revoked, the user is warned that the app is damaged and should be moved to the trash. I have not seen this behavior but it would be consistent and make sense.
Hardened Runtime Details
One of the interesting pieces to the new process is how the system determines if the app is using a ‘hardened runtime’. Runtime is an ambiguous term that can refer to a combination of libraries, frameworks, SDKs, and more on a system. In this specific case, I was curious what happens when the hardened runtime setting is selected in Xcode.
When the app is signed in Xcode, the Developer ID signing key is used with the codesign command:
codesign --verbose --force --deep -o runtime --sign 'Developer ID Application: Twocanoes Software, Inc. (UXP6YEHSPW)' CLIElvis
The important flag for Hardened Runtime is the “-o runtime” in codesign. This sets a flag inside the Mach-O header in a structure called “LC_CODE_SIGNATURE”. When an app is launched, Launch services uses spawn_via_launchd which uses posix_spawn, but both posix_spawn and execve find the LC_CODE_SIGNATURE for validation.
At first I assumed that stapling was similar to signing insofar as that any binary could be stapled or notarized. However, this is not the case. Only these types of objects can be stapled:
- macOS apps
- Non-app bundles, such as kernel extensions
- Disk images (UDIF format)
- Flat installer packages
The notarization is not for all the items a disk image or installer package. Any mach-o binary can be notarized. Disk Images and installer packages can contain Mach-O binaries, but are not Mach-O binaries themselves.
Once notarized, the ticket is for the macOS app or kernel extension (or more specifically, the Mach-O binary in the app or extension), regardless of what it is stapled to. If the ticket is stapled to a macOS app or kernel extension, it is added to the macOS app or kernel extension directly. If a disk image or flat installer package is sent for notarization, Mach-O binary inside the container used for notarization. After notarization, the ticket can be attached to the disk image or flat installer package. When the disk image or flat installer package is scanned by the system, the ticket is used to validate the enclosed macOS app or kernel extension.
I presume that the types of containers that a ticket can be stapled to is limited to disk images and flat packages because those containers have an appropriate location to store arbitrary data (including a notarization ticket), and the system will know how to extract it and validate the Mach-O binary.
When an app is first downloaded on the system, it is quarantined and scanned. However, depending on how the app is installed, Gatekeeper may or may not alert the user. It appears that the intent is downloaded code should not be run without explicit user consent which does not include the first opening of the downloaded resource. Safari has the ability to automatically open “Safe” files that include Disk Images and Zip files. It may be relatively easy for a user to open an app without understanding where it came from or if it is indeed an application (versus a document opened by an existing app). To prevent this confusion, Gatekeeper will prompt the user the first time the app is run if it is inside one of these “safe” containers. However, if the app is inside a macOS installer, the Gatekeeper prompts will not be shown since the user will understand that they are installing an application by walking through the prompts in the standard macOS installer. Even though the Gatekeeper prompt is not shown, a macOS package is still scanned by Gatekeeper prior to opening with the installer.
Hardening 3rd Party Helper Tools and Apps
When an app is submitted for notarization, only the main binary (located in Contents/MacOS folder in the application bundle) is used for notarization. However, any Mach-O binaries in the application bundle (including helper tools and frameworks) must be opted-in to a Hardened Runtime. Source code is not required to enable a Hardened Runtime. Any Mach-O binaries in the app bundle must be signed and the flag in the signature for Hardened Runtime must be set in the signature. This can be done with codesign command specified above (using the –deep and -o runtime arguments). Presumably, any entitlements used with codesiging these other binaries can be passed to the codesign command as well.