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
cdInfoSlot
(at index -1) holds the digest of the Info.plist file (for a bundle) or possibly an embedded Info.plist (for a standalone Mach-O executable that has one). - The
cdRequirementsSlot
(at index -2) holds the digest of the CodeRequirements component, which defines the designated requirement. - The
cdResourceDirSlot
(at index -3) holds the digest of the CodeResources component, which protects resources in the associated bundle (such as strings files or nib files), as well as any nested code signatures. - The
cdEntitlementSlot
(at index -5) andcdEntitlementDERSlot
(at index -7) hold the digests of the CodeEntitlements and CodeEntitlementsDER components, which define the entitlements in XML and DER serializations. - The
cdLaunchConstraintSelf
(at index -8),cdLaunchConstraintParent
(at index -9),cdLaunchConstraintResponsible
(at index -10) andcdLibraryConstraint
(at index -11) slots hold the digests of the various LaunchConstraint components, which restrict the environments in which the code is allowed to run. - The non-negative slot indexes, starting from zero, hold the digests of each (4096-byte) page of the Mach-O binary. Since there is a separate code signature for each architecture, page zero always starts with a Mach-O header (even if that's contained inside a Universal Mach-O file). These per-page digests protect the entire executable, up to the point where the code signature itself is embedded.
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:
- For a normal resource — such as a strings file or a compiled nib file — the SHA-256 digest
of the file is given by the
hash2
key. Verification of the code signature will generally pass only if all such files are found and have the specified digest (and if no unspecified files have been added). - For nested code — such as another bundle, a framework or a standalone Mach-O executable that is
also code-signed — the designated requirement for that code is given by the
requirement
key, and the cdhash by thecdhash
key. Verification of the code signature (on, say, an app bundle) will generally involve recursive verification on all nested code, so this reference form avoids redundant verification from the parent context: the named code simply must match the specified requirement (having passed its own signature verification). In theory, nested code could be swapped out for a different version, provided it still meets the requirement (which usually specifies an identifier and a team ID).To our knowledge, even though the cdhash is recorded, signature verification doesn't use it. In fact, nested code is likely to have multiple cdhash values, one per architecture, but this is not represented in the CodeResources. The cdhash here seems to be the one for the platform on which the code was signed.
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 itsrequirement
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
andrules
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:
- For LaunchConstraintParent, the constraint
must be met by the process that directly launches the code, i.e. using
posix_spawn(2)
orexecve(2)
. For a launchd daemon or agent, or an XPC service, this ought to belaunchd(8)
. - For LaunchConstraintResponsible, the constraint must be met by the process that triggered the code to launch, which might be different from the process doing the spawn. For an XPC service, the responsible process is the app that uses that service. Otherwise, it tends to be the same as the code itself; in particular, a launchd daemon or agent does not get a distinct responsible process, perhaps because a single launchd job can be shared by multiple clients.
- For LaunchConstraintSelf, the constraint
must be met by the code itself.
It's not clear that this has much meaning for third-party code, since there's no point in
constraining aspects of the code signature via a mechanism that is part of that same signature.
We'd guess that self constraints are designed for system-level concerns — such as
preventing older versions of macOS executables (which might have known vulnerabilities) from
being launched on the current (newer) version of macOS, using requirements like
on-system-volume
. - For LibraryConstraints, the constraint
must be met by each library that is dynamically loaded by the code. This applies only if the
com.apple.security.cs.disable-library-validation
entitlement is used to disable the library validation feature of the hardened runtime, and is a way of relaxing such validation selectively instead of entirely.
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:
vers
appears to be the version of the constraint dictionary mechanism being used, which is currently 1. Note that this is distinct from the serialization version atEntitlementRoot.version
, even though that is also currently 1.comp
appears to be a compatibility version, i.e. the minimumvers
that must be supported by the constraint-reading code in order to properly interpret the constraint; this is unsurprisingly also 1 at present.ccat
appears to be a “constraint category,” which always seems to be zero at present. Based on this and this, we'd guess that there is a default implicit constraint inferred from the category, although it isn't clear how that interacts with the given explicit constraint; in any case, category zero seems to mean no implicit constraint.reqs
is the actual constraint dictionary, as originally expressed in property list form, per the spec noted above.
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:
Magic Value | Kind 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 theLC_CODE_SIGNATURE
block is presumably why a Mach-O binary can't be stapled with a notarization ticket: theLC_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 isCodeEntitlements
(plural) butCodeEntitlementDER
(singular). Also, if there is an alternate code directory, it is namedCodeRequirements-1
rather thanCodeDirectory-1
as you might expect; we're assuming this is an ages-old copy/paste error inCodeDirectory::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
Date | Changes |
---|---|
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. |