Why notarize apps?
Signing and notarization of apps and other executable code is a controversial topic. Over the last decade and more Apple has steadily introduced increasingly demanding standards, now requiring developers to notarize apps and other code they distribute outside the App Store. This article tries to explain why, and how this contributes to Mac security.
I would hope that what we all want is confidence that all executable code that our Mac runs, in particular apps, is exactly as was built by its developer. In addition to that, in the event that any code is found to be malicious, then macOS can promptly protect us by refusing to launch it. The first requirement is thus about verification of apps and code, and the second is about having a system that can block code from being launched in the first place.
CDHashes
The well-proven way to verify that files and bundles haven’t changed is using cryptographic hashes of their contents. Compute a hash, save it in a way that can’t be tampered with, and you can verify a bundle by recomputing its hash and confirming that it hasn’t changed. Apple has been using this for a long time, and its approach is a little more complex, as explained in detail in this excellent tech note.
When an app is signed, hashes are computed for different parts of its contents and assembled into a code directory, a data structure rather than a folder/directory. That data structure is then hashed to form the cdhash, or CDHash with mixed case to aid its reading. Because it’s a hash of hashes, it uniquely identifies that app, bundle or other executable code. CDHashes are thus part of the signing process, and the signature contains those CDHashes. They are also part of the notarization process, in which Apple’s Notary Service signs the CDHashes for code when it undergoes notarization, and that forms the notarization ticket that’s issued for that app, and normally attached or ‘stapled’ to it.
Between them, code signing and notarization thus provide two levels of verification, in a signature attached to the code itself, and in a record kept by Apple following successful notarization.
Unsigned apps
An unsigned app has no CDHashes, so its contents are uncontrolled and no verification is possible. It can change its own contents, morph itself from benign to malicious, forge its identity by posing as a completely different app, or be hijacked to run malicious code. While macOS could compute its CDHashes and Apple could try to track them, there’s no way to verify its identity, so external checks aren’t feasible, and there’s no way to block the code from being launched, as all it would need to do to evade that would be to change itself so its CDHashes changed.
Although macOS running on Intel Macs long tolerated this, from their release four years ago, Apple silicon Macs have refused to run such unsigned code.
Ad-hoc signed apps
Since Apple required code to be signed for Apple silicon Macs, all self-respecting build systems for macOS have automatically signed the code they generate. However, unless the developer has a certificate issued by Apple, by default they use ‘ad hoc’ certificates that are created locally and lack any chain of trust. That enables anyone to create CDHashes at any time, without any traceability to a trusted root certificate.
This is a slight improvement on completely unsigned code, and does enable an app to be identified by its CDHashes, but as they’re so easy to create, there’s no reliable way to verify that the app hasn’t changed since its original build. Although Apple could try to collect those CDHashes, there’s no useful way to block code from being launched, as all an adversary needs is to resign the code to change its CDHashes: they’re simply too labile to be trustworthy.
Certificate signed apps
For many years, before Apple introduced notarization over six years ago, this was the standard expected, but not required, of apps distributed by third-party developers. Although in theory developers could have used certificates provided by other authorities, not all Certificate Authorities are equal in their diligence, and Apple rightly wanted to be responsible for all revocations.
Certificates add control and verification, within limits determined by the certificate user. CDHashes gathered from code can be collected, but again their provenance relies on their user. At one time, they were commonly abused by those distributing malicious software. Although abused certificates were revoked by Apple, before that could happen, the malware had to be detected and identified, which could allow it to be run by many users for long before it could be blocked.
Certificate checks were another problem with this approach. It isn’t practical to check each certificate every time code is to be launched, so approvals have to be cached locally, adding to the delay before any revocation becomes effective.
Notarization
To address the limitations of signing code using developer certificates, Apple introduced the process of notarization. In this context, it adds:
CDHashes from notarization are known to Apple, and stored in its database, for quicker online checks, and more rapid revocation.
Apple screens apps being notarized to detect those that may be malicious.
Apple has a complete copy of every app that has been notarized, and already knows its CDHashes.
This finally checks the provenance of all code being run, through its CDHashes; if they’re not already known to Apple, then that build of the app can’t have been notarized, and can be blocked from launching, provided the user doesn’t disable notarization checks. Screening for malware forces those trying to get malicious code notarized to adopt techniques of obfuscation, but even if those are successful, Apple already has a copy of that app and its CDHashes. That eliminates much of the delay incurred by certificate-signed apps. Together these have proved sufficient disincentive to malware developers to try to abuse notarization.
Key features of notarization are thus:
Verification that the app or code hasn’t changed since it was built by its developer, up to the moment that it’s run.
Independent verification against Apple’s database.
Rapid blocking if the app or code is discovered to be malicious.
Apple is provided with a full copy of the app or code, to aid any further investigation.
All apps or code are checked independently for evidence that they’re malicious, before they can be released.
If you can come up with a system that achieves those and could replace notarization, I’m sure that Apple would love to hear of it.