Archaeology

All About Code Signing

After a long time allowing detailed documentation about code signing to stagnate, Apple have recently published the start of an Inside Code Signing series of Tech Notes, including TN3125: Provisioning Profiles, TN3126: Hashes and TN3127: Requirements.

The discussion below is based mostly on the open-source version of the Security.framework, along with some reverse-engineering. So it's a bit of a different take.

What Is A Code Signature?

At its most basic, the code signature for an executable is a Cryptographic Message Syntax (CMS)-based signature, applied to a tree of SHA-256 digests, which are calculated from the pages of the executable, the contents of associated resources (if the executable is the main executable of a bundle), and a set of entitlements, among other things.

Components of a Code Signature

A code signature is constructed of several different components, which are mostly related by SHA-256 digests. These are shown in the figure below, and each one is explained in more detail after.

                                                                                ┌───────────────────────────────────┐
                                                               ┌────────────────▶            Info.plist             │
                                                               │                ├───────────────────────────────────┤
                                                               │                │                                   │
           ┌───────────────────────────────────┐               │                │                                   │
      ┌────▶           CodeDirectory           │               │                └───────────────────────────────────┘
      │    ├───────────────────────────────┬───┤               │
      │    │              version          │   ├───────────────┘                ┌───────────────────────────────────┐
      │    │            identifier         │ S ├────────────────────────────────▶         CodeRequirements          │
      │    │              team ID          │ L ├─────────────────────┐          ├───────────────────────────────────┤
      │    │               flags           │ O ├───────────────┐     │          │    designated => anchor apple     │
      │    │                               │ T │               │     │          │          generic and ...          │
      │    │                               │ S ├───────┐       │     │          │                                   │
      │    │                               │   ├─────┐ │       │     │          │                                   │
      │    │                               │   ├───┐ │ │       │     │          │                                   │
      │    └───────────────────────────────┴───┘   │ │ │       │     │          └───────────────────────────────────┘
      │                                            │ │ │       │     │
      └─────────────────────┐                      │ │ │       │     │          ┌───────────────────────────────────┐
                            │                      │ │ │       │     └──────────▶           CodeResources           │
                            │                      │ │ │       │                ├───────────────────────────────────┤
                            │                      │ │ │       │                │           files (hash)            │
                       signedData                  │ │ │       │                │   code (designated requirement)   │
                        (digest)                   │ │ │       │                │                                   │
                            │                      │ │ │       │                │                                   │
                            │                      │ │ │       │                │                                   │
                            │                      │ │ │       │                └───────────────────────────────────┘
                            │                      │ │ │       │
                            │                      │ │ │       │                ┌───────────────────────────────────┐
                            │                      │ │ │       └────────────────▶       CodeEntitlements(DER)       │
          ┌───────────────────────────────────┐    │ │ │                        ├───────────────────────────────────┤
          │           CodeSignature           │    │ │ │                        │     com.apple.sandbox = true      │
          ├───────────────────────────────────┤    │ │ │                        │                                   │
          │   Cryptographic Message Syntax    │    │ │ │                        │                                   │
          │             RFC 5652              │    │ │ │                        │                                   │
          │                                   │    │ │ │                        │                                   │
          │                                   │    │ │ │                        └───────────────────────────────────┘
          │                                   │    │ │ │
          └───────────────────────────────────┘    │ │ │
                                                   │ │ │                                               Mach-O Binary
                                                   │ │ │                        ┌───────────────────────────────────┐
                                                   │ │ │                        │         struct fat_header         │
                                                   │ │ │                        ├───────────────────────────────────┤
                                                   │ │ └────────────────────────▶   struct mach_header_64 (arm64)   │
                                                   │ │                          ├───────────────────────────────────┤
                                                   │ └──────────────────────────▶                                   │
                                                   │                            ├────────────────•••────────────────┤
                                                   └────────────────────────────▶                                   │
                                                                                ├───────────────────────────────────┤
                                                                                │  struct mach_header_64 (x86_64)   │
                                                                                ├───────────────────────────────────┤
                                                                                │                                   │
                                                                                ├────────────────•••────────────────┤
                                                                                │                                   │
                                                                                └───────────────────────────────────┘

CodeDirectory

The CodeDirectory is the root of a tree of digests that make up the code signature. It has some basic metadata, such as the code signing identifier (typically the same as the bundle identifier), the Team ID associated with the Apple Developer Program account, and option flags (such as the hardened runtime), among other things.

The CodeDirectory also contains a table of digests (usually SHA-256 for modern code signatures), each of which is computed from some part of the signed “code” or from some other component of the code signature. This table of digests describes all of the protected parts of the bundle, so that any change — to a bundled resource, for example — invalidates the signature.

The CodeDirectory organizes these digests by slot numbers, which are esssentially just indexes into an array (see e.g. codedirectory.h). The most commonly used slots are as follows:

The digest of the CodeDirectory itself is called the code directory hash or simply the cdhash. You'll see the cdhash used in various ways, since it effectively identifies a specific piece of signed code (and will change when any aspect of that code changes). For example, notarization status is tracked by cdhash, as are Gatekeeper exceptions.

For modern code signatures, all of these digests will be SHA-256 (although Alternate Code Directories may be present to allow for backward compatibility with older versions of macOS). This includes the top-level cdhash, but this will often appear truncated to the first 20 bytes (or 40 hex digits) of the full SHA-256 digest.

CodeRequirements

The CodeRequirements contains any internal code-signing requirements that the code declares to macOS.

Code-signing requirements make an assertion about the code signature of a bundle or executable, and are expressed in a domain-specific language. You can use requirements to test a particular bit of signed code. For example, the simple requirement anchor trusted means that the code was signed with some certificate that was (indirectly) issued by some system-trusted certificate authority (i.e. found in the System Roots keychain):

$ /usr/bin/codesign --verify --verbose=4 --test-requirement="=anchor trusted" /Applications/Some.app
/Applications/Some.app: valid on disk
/Applications/Some.app: satisfies its Designated Requirement
/Applications/Some.app: explicit requirement satisfied

More interesting for this discussion, the code signature itself can — and pretty much always does — embed a special requirement called the designated requirement. The purpose of the designated requirement is to declare an effective “code signing identity” to macOS: any code that meets this requirement is considered to be the “same” for various purposes.

Typically, the designated requirement is a default chosen by codesign(1), although it is stored in the code signature, and can be modified by the signer. As an example, here is the (default) designated requirement for Archaeology, annotated and formatted a bit:

anchor apple generic /* signing certificate was issued by Apple, trusted by Apple Root CA */
   and identifier "com.mothersruin.Archaeology" /* and app has this bundle identifier */
   and ( certificate leaf[field.1.2.840.113635.100.6.1.9] /* and signing certificate is the Mac App Store one */
         or ( certificate 1[field.1.2.840.113635.100.6.2.6] /* ... OR was issued by the Developer ID intermediate CA */
              and certificate leaf[field.1.2.840.113635.100.6.1.13] /* ... for Developer ID Application use */
              and certificate leaf[subject.OU] = "936EB786NH" ) ) /* ... for the developer with this team ID */

This designated requirement is constructed such that any version of the app meets the requirement, but different apps (from the same or a different developer) do not. The default requirement has a bit of complexity that allows the Mac App Store and Developer ID versions of the same app to be considered equivalent (this complexity is there whether or not both versions really exist).

The first time that you use an app with this designated requirement, macOS will store the requirement wherever it needs to limit access to a specific resource. For example, if the app saves a password in your keychain, the designated requirement is stored with it. Later, that app is allowed to access that keychain item, as long as it (still) meets the requirement — even if the app has been updated in the interim. (This is how the “access control” feature of the keychain works.)

Likewise, if the app is sandboxed, macOS makes a container when you first open it (this is found under your home folder at Library/Containers/com.example.bundle-identifier). This app — and only this app — should have access to this container. But of course, an updated version of the same app should maintain that access. Again, macOS stores the designated requirement of the app when it first makes the container, and thereafter, any app that meets that requirement is allowed access.

CodeResources

If the code signature is on a bundle, the CodeResources references all of the individual resource files that are protected by that code signature. It also references nested bundles and/or executables that have their own code signatures.

The CodeResources is a standard macOS property list (usually in XML format), and is the component you've most likely already encountered, since it is pretty much always stored as a file in the bundle, at Contents/_CodeSignature/CodeResources.

In the most common case, the bundle has a main Mach-O executable, which might be Universal and support multiple processor architectures. As noted above, code signatures are always per-architecture, meaning that there will be a CodeDirectory, CodeSignature, CodeRequirements and so on for every architecture. (Some of these are [usually] identical across architectures, like the CodeRequirements. Others are equivalent but not identical: the CodeDirectory references different code pages and so obviously has different digests, while the CodeSignature is signing a different CodeDirectory and so must differ, although it will [usually] be signed with the same certificate.)

The CodeResources is the one component that is always shared across architectures, as it always lives in the bundle, and there is no concept of architecture-specific resources.

In the CodeResources property list, the most interesting key is files2, which contains two types of entries, both keyed by their path within the bundle:

There's a big proviso about this handling of nested code: it applies only if that code is found at a standard “code place.” For example, Contents/PlugIns is a standard code place: a code-signed Share.appex bundle there will be referenced by its requirement as described above. However, Contents/Library/QuickLook is not a standard code place: a code-signed Preview.qlgenerator there will be treated as a resource, such that every file it contains will be listed in the CodeResources and have its digest verified. (This may or may not be redundant work, depending on whether or not the code signature on Preview.qlgenerator ever gets independently verified.)

The problem is that the standard code places are not well-documented and haven't really kept up with the evolution of macOS bundles. The last time they were documented was in macOS Code Signing in Depth, in 2016. A couple places have been added since then; the most authoritative list of code places is under the (otherwise useless) rules2 key:

(Frameworks|SharedFrameworks|PlugIns|Plug-ins|XPCServices|Helpers|MacOS|Library/(Automator|Spotlight|LoginItems))

Note that this omits newer, standard bundle locations, like Contents/Library/SystemExtensions. Also, probably the most commonly-used location for auxiliary tools — even by Apple — is the Resources directory, which is also not a standard code place and never has been. ¯\_(ツ)_/¯

The other keys in CodeResources are all pretty much obsolete: files and rules are for backward-compatibility with older macOS versions. rules2 is the remnant of a feature allowing the signer to declare that certain pieces of the bundle are to be excluded from the code signature, or that certain files are allowed to be missing. This has been forbidden since OS X 10.9 (Mavericks), and will produce a “resource envelope is obsolete (custom omit rules)” error. (Apple grants itself some exceptions to this rule, as explained further here.)

CodeEntitlements and CodeEntitlementsDER

The CodeEntitlements contains the entitlements that the executable requests, as a standard macOS property list, in XML format. Entitlements can be associated with any code signature, but whether or not they have any effect depends on the type of code-signed object; for example, entitlements are meaningful on apps and app extensions, but not on frameworks. Moreover, some entitlements must be “provisioned” with a provisioning profile to be effective, and other (private-to-Apple) entitlements are permitted only for code signed by Apple itself.

Starting in macOS 11 (Big Sur), codesign(1) also records entitlements in an alternate, DER-encoded form, stored in a parallel slot named CodeEntitlementsDER. Presumably, this version is preferred on newer versions of macOS, and is meant to resolve the various vulnerabilities related to XML property list deserialization — such as CVE-2020-9842 (a.k.a. “psychic paper”). The XML-serialized CodeEntitlements is still created, presumably for backward compatibility with macOS 10.15 (Catalina) and earlier.

In ASN.1 notation, the structure of the DER entitlements appears to be as follows:

Entitlements ::= [APPLICATION 16] IMPLICIT EntitlementRoot

EntitlementRoot ::= SEQUENCE
{
    version                     INTEGER,
    dictionary          [16]    IMPLICIT EntitlementDictionary
}

EntitlementDictionary  ::=  SEQUENCE SIZE (1..MAX) OF EntitlementKeyValuePair

EntitlementKeyValuePair ::= SEQUENCE
{
    key                         UTF8String,
    value                       EntitlementValue
}

EntitlementValue ::= CHOICE {
    noValue                     NULL,
    trueOrFalse                 BOOLEAN,
    dataBlob                    OCTET STRING,
    dateTime                    GeneralizedTime,
    valueArray                  EntitlementArray,
    string                      UTF8String,
    numeric                     INTEGER,
    dictionary          [16]    IMPLICIT EntitlementDictionary
}

EntitlementArray ::= SEQUENCE (1..MAX) OF EntitlementValue

The above structure is what we deduced on macOS 12 (Monterey), and appears to still be in use as of macOS 13 (Ventura). The representation is explicitly versioned; to date, only version 1 seems to exist.

Note that the Big Sur representation was somewhat different, lacking the top-level structure with explicit versioning, and using a different representation for dictionaries:

V0Entitlements ::= SET SIZE (1..MAX) OF V0EntitlementKeyValuePair

V0EntitlementKeyValuePair ::= SEQUENCE
{
    key                         UTF8String,
    value                       V0EntitlementValue
}

V0EntitlementValue ::= CHOICE {
    noValue                     NULL,
    trueOrFalse                 BOOLEAN,
    dataBlob                    OCTET STRING,
    dateTime                    GeneralizedTime,
    valueArray                  V0EntitlementArray,
    string                      UTF8String,
    numeric                     INTEGER,
    dictionary                  V0EntitlementDictionary,
    set           [PRIVATE 17]  V0EntitlementArray
}

V0EntitlementArray ::= SEQUENCE (1..MAX) OF V0EntitlementValue

V0EntitlementDictionary ::= SET (1..MAX) OF V0EntitlementKeyValuePair

We'd presume (but have not verified) that Monterey and later can read these version zero entitlements (created by Big Sur), but it seems likely that given a version 1 structure, Big Sur would fall back on the XML serialization in CodeEntitlements instead.

Most recent code signatures use the modern version 1 format, but some Apple-signed code still uses version 0 — such as Safari even on the macOS 14 (Sonoma) beta.
At least as of macOS 12, libCoreEntitlements.dylib (which is private and lives only in the DYLD Shared Cache) is responsible for serializing and deserializing the DER-format entitlements. This library is used by Security.framework, as well as by the bits of AppleMobileFileIntegrity.

LaunchConstraint

There are several different types of LaunchConstraint component: each contains a a constraint that must be met for the code to be launched. Parts of this scheme originated in macOS 13 (Ventura), but the complete mechanism was first publicly documented in macOS 14 (Sonoma).

Apple documents a “constraint dictionary” format for defining constraints. The specific type of component determines which code must meet the constraint:

Note that it is always the launching of the code that contains the LaunchConstraint that will be gated, even though 3 of the 4 types are requirements that other code must meet.

In each case, the constraint dictionary is compiled into a DER-encoded form that is based on the CodeEntitlementsDER format. That is, it shares the same ASN.1 definition as above:

ConstraintDictionary ::= [APPLICATION 16] IMPLICIT EntitlementRoot

Interpreted as a dictionary, there are 4 top-level keys:

As of macOS 13, libTLE.dylib (which is private and lives only in the DYLD Shared Cache) is responsible for managing and evaluating launch constraints, and depends on libCoreEntitlements.dylib to handle the DER-encoded dictionary format. TLE — which we assume stands for “trusted launch environment” — is used by codesign(1) for embedding constraints in the code signature, and by AppleMobileFileIntegrity.kext for checking the constraints at launch time.

CodeSignature

The CodeSignature is the actual signature part. As noted above, this is a Cryptographic Message Syntax (CMS) signature of the CodeDirectory. This effectively signs the (usually) SHA-256 cdhash. If you examine the ASN.1, you'll notice that the EncapsulatedContentInfo omits the actual eContent item. As RFC 5652 says:

The optional omission of the eContent within the EncapsulatedContentInfo field makes it possible to construct “external signatures”. In the case of external signatures, the content being signed is absent from the EncapsulatedContentInfo value included in the signed-data content type.

In this case, the “content being signed” is the CodeDirectory, which is supplied when the CMS signature is verified.

Unless codesign(1) was run with the ‑‑timestamp=none option, the CMS should include a trusted timestamp under the attribute named “id-aa-timeStampToken”. This is generally missing from Apple and Mac App Store signatures. ¯\_(ツ)_/¯

Alternate Code Directories

As noted above, CodeDirectory typically uses SHA-256 digests. Support for SHA-256 digests was added in macOS 10.12 (Sierra), but there is a backward-compatibility scheme for apps that still deploy to older macOS versions.

If you create a code signature for pre-Sierra deployment — the deployment target is generally extracted from the Mach-O deployment target — the CodeDirectory will revert to using SHA-1 digests. In addition, there will be an Alternate Code Directory which uses SHA-256 digests. Older versions of macOS may only “see” the standard CodeDirectory with SHA-1 digests, but will know what to do with that. On newer versions of macOS, the SecCode APIs will choose the “best” code directory, which means the strongest digest algorithm that it knows how to handle. That may not be the primary CodeDirectory, but rather one of the alternates.

Even when there are alternate code directories, the CodeSignature is always signing the primary CodeDirectory. However, that doesn't mean that the (stronger) alternate code directory is unprotected: the CMS signature includes a (signed) attribute called “Apple Code Signing Hash Agility V2” that enumerates the digests of all code directories. Presumably, this is used by Security.framework to verify that the chosen “best” code directory has not been tampered with.
Some versions of codesign(1) would incorrectly omit alternate code directories when run on Apple Silicon, because it was looking at only the deployment target of the native architecture (and Apple Silicon never has a target less than macOS 11). This appears to have been fixed in macOS 13 (Ventura), however.

Storage of a Code Signature

Having discussed the components of a code signature, we turn to the question of where those components can be found, which differs with the type of “object” being signed.

But first, we have to examine the fundamental abstraction of a data “blob” used by Security.framework.

About Blobs and SuperBlobs

The storage of a code signature is based on some C++ templates defined by Security.framework.

With some simplification, and focusing mostly on the in-memory layout — which becomes the on-disk layout — the base class Security::Blob is structured like this:

class Security::Blob
{
   uint32_t _magic;
   uint32_t _length;
};

with additional payload data of ( _length - 2 * sizeof( uint32_t ) ) bytes following. (In other words, the _length is calculated from the start of the blob.)

Security.framework defines magic values for the various kinds of code signature components, including:

Commonly-encountered magic values for a Security::Blob
Magic ValueKind of Component
0xfade0c02 CodeDirectory (including alternate code directories)
0xfade0c01 CodeRequirements
0xfade7171 CodeEntitlements
0xfade7172 CodeEntitlementsDER
0xfade0cc0 embedded signature superblob (see below)
0xfade0b01 a BlobWrapper around other binary data, such as a CMS signature or a notarization ticket

Both _magic and _length — and most values stored in code signature components — are in Network Byte Order (NBO, i.e. Big Endian).

Building on that, Security::SuperBlob is a container for other blobs, and adds an “index” of sub-blobs, each with a type and offset:

class Security::SuperBlob : public Security::Blob
{
   struct Index
   {
      uint32_t  _type;            // type of sub-blob
      uint32_t  _offset;          // offset of sub-blob (from start of superblob)
   };
   uint32_t     _count;           // number of sub-blobs
   struct Index _index[ _count ]; // _count index entries
};

The _type is usually the same as one of the slot numbers defined by codedirectory.h. Of course, each of the sub-blobs also has a magic value, which is different than the type, but there is definitely some redundancy here. (We presume that the magic is meant to designate a kind of data that the blob contains, whereas the type might be more specific. For example, all code directories — primary and alternate — use the magic 0xfade0c02, but they have different types in the superblob index.)

As with the blob, the additional members of the superblob are always in NBO.

Embedding in Mach-O Binaries

For a bundle with a Mach-O main executable — or for a standalone Mach-O executable — all of the code signature components (except CodeResources) are stored in the Mach-O file itself.

The Mach-O format defines a load command for the code signature:

#define LC_CODE_SIGNATURE 0x1d	/* loca[tion] of code signature */

/*
 * The linkedit_data_command contains the offsets and sizes of a blob
 * of data in the __LINKEDIT segment.
 */
struct linkedit_data_command {
    uint32_t	cmd;		/* LC_CODE_SIGNATURE, ... */
    uint32_t	cmdsize;	/* sizeof(struct linkedit_data_command) */
    uint32_t	dataoff;	/* file offset of data in __LINKEDIT segment */
    uint32_t	datasize;	/* file size of data in __LINKEDIT segment  */
};

which points to a block of data in the __LINKEDIT segment. (For a Universal executable, there is one of these for each architecture.)

All of the code signature components are packaged into a superblob with magic value 0xfade0cc0, and this is placed in the range given by LC_CODE_SIGNATURE.

Note that the code page digests in the CodeDirectory stop where the code signature data starts, so the code signature is always at the very end of the architecture. Further, the code page digests start at the Mach-O header and include all of the load commands, so the LC_CODE_SIGNATURE block must be inserted (with zeroed data) before the CodeDirectory is calculated and the CodeSignature is created. This preparation step is the job of codesign_allocate(1), which is used by codesign(1).

This fixing of the LC_CODE_SIGNATURE block is presumably why a Mach-O binary can't be stapled with a notarization ticket: the LC_CODE_SIGNATURE block can't be expanded without invalidating the code signature, and reserving such space in every binary would be quite wasteful, especially since notarization tickets can have essentially any number of digests.

Storage in Bundles

A bundle might not have a Mach-O main executable, but it can still be code-signed. (Resource-only bundles are not uncommon, and a main executable that is a script is possible.) In this case, each of the code signature components go into individual files in the Contents/_CodeSignature directory, e.g. _CodeSignature/CodeDirectory, _CodeSignature/CodeSignature and so on.

Of course, CodeResources is the one component that always uses this form of storage, so _CodeSignature/CodeResources exists even when everything else is in the Mach-O main executable.

There are some inconsistencies in the naming of these separate files. For example, it is CodeEntitlements (plural) but CodeEntitlementDER (singular). Also, if there is an alternate code directory, it is named CodeRequirements-1 rather than CodeDirectory-1 as you might expect; we're assuming this is an ages-old copy/paste error in CodeDirectory::canonicalSlotName().

Storage in Extended Attributes

Going even further afield, it is quite possible to code sign a standalone, non-Mach-O executable file, such as an executable script with a #! directive. In this case, each of the code signature components go into extended attributes with the prefix com.apple.cs., e.g. com.apple.cs.CodeDirectory, com.apple.cs.CodeSignature and so on.

Of course, this is not terribly common, and extended attributes often get lost when files are transferred between systems. But it's at least a theoretical possibility to make this work.

The naming inconsistencies described above for bundle storage also apply to extended attribute storage.

Embedding in Disk Images

In OS X 10.11 (El Capitan), it became possible to code-sign a disk image. A disk image stores its code signature in a way that is quite similiar to the Mach-O binary case.

The UDIF disk image format has a fixed-length trailer structure, which since El Capitan has included an offset and length for a code signature block (in a part of the trailer that was previously unused). When a disk image is signed, the UDIF trailer is updated with a code signature offset and length, and all of the code signature components are packaged into a superblob with magic value 0xfade0cc0, which is then inserted where indicated by the trailer.

Note that the trailer itself is protected by the CodeDirectory — with a digest in the cdRepSpecificSlot (index -6) — so the code signature offset must be updated before the CodeDirectory is calculated. However, there's one trick here, which is that the length of the code signature block is zeroed out to calculate the digest. This allows the code signature block to grow without invalidating the code signature.

This trick is why a disk image can be stapled with a notarization ticket. An additional sub-blob is added to the existing superblob to hold the notarization ticket, and the length of the code signature block is adjusted accordingly. Nothing else in the disk image moves, because the code signature is the last thing before the trailer.

Revision History

Revision history for “All About Code Signing” page
DateChanges
July 26, 2023 Added section on LaunchConstraint mechanism.
June 13, 2023 Corrected the ASN.1 definitions for DER-encoded entitlements, both the modern versioned format, and the implict version 0 one. The previous definitions were wrong around entitlements with dictionary values in the version 1 format; dictionary-valued entitlements are rare (only Apple-private ones that we've seen).
March 9, 2023 Original version published.