MacOS – What are the restrictions of ad-hoc code signing

code-signingmacos

It is possible to sign code or apps "ad-hoc" using codesign. The man page tells us the following about ad-hoc code signing:

If identity is the single letter "-" (dash), ad-hoc signing is performed. Ad-hoc signing does not use an identity at all, and identifies exactly one instance of code. Significant restrictions apply to the use of ad-hoc signed code; consult documentation before using this.

(Emphasis added by me)

I wanted to know more and tried to find said documentation, but I was unable to find any details. I have found a technical note called "macOS Code Signing In Depth", but it does not mention ad-hoc signing at all.

What are these "significant restrictions" and where are they documented?

Best Answer

Basically ad-hoc signing in this context means that the binary is signed without any cryptographic proof at all.

In essence normally binaries are signed by adding a so-called CMS (a cryptographic message) where the hash of the CodeDirectory is the message that is signed by the signing identity. This means that an outsider can verify that the code was indeed signed by someone holding the private key for that identity.

When running programs, the macOS system can verify that these signatures are valid, and that it trusts the signing identity - and if it does, run the program. This is the basics of the GateKeeper functionality.

Ad-hoc signed binaries are vastly different as they contain no such CMS. Instead it simply holds the SHA-1 hash value of the CodeDirectory without any cryptographic proof of its validity, and no path of certificates/identities to verify against.

The CodeDirectory is an object that describes a particular instance of static code by having hash values for various pieces of code that the application is made from. By ensuring that the CodeDirectory is untampered by verifying the cryptographic signature, and that the various code bits of the application match the hash values stored in the directory, you can ascertain that the code wasn't tampered with.

Without the cryptographic proof this "untampered" check cannot be performed in the normal way.

Instead ad-hoc signed binaries are checked by comparing the SHA-1 hash value against a list of "known good" hash values stored in the static trust cache inside the kernel.

In essence this means that the "significant restrictions" placed upon any application you ad-hoc sign yourself is that it won't pass any kind of verification anywhere. It basically be the same as a non-signed binary.

However, if you're Apple, you can create applications that aren't codesigned in the ordinary way, and are instead explicitly trusted by the kernel. I.e. if for example Apple wants to ensure that an application is untampered when run at an early stage in system startup where the full signing identity verification is not up and running (or is unavailable) they can use ad-hoc signing. These applications can always be verified by the static trust cache, no matter if your certificate repository is hosed or anything like that.

In practice creating ad-hoc signed binaries is only of practical value for Apple developers.

You can find minor documentation on ad-hoc signing in Apple's developer section. For example:

https://developer.apple.com/documentation/security/seccodesignatureflags/kseccodesignatureadhoc

But you can also find snippets of documents in the source code for the codesign utility itself, and in the source code for libsecurity.