What is inside a code signature: the code directory, hashes, the designated requirement and the team ID, and how do you read and test them with codesign?

25.6.2 · L5 · Interview

Predict first

A binary is ad hoc signed and then inspected. Which identity facts and requirement does codesign report?

// === main.swift ===
print("hello")
// === run.sh ===
swiftc main.swift -o app 2>/dev/null
codesign -s - -f -i com.example.tool app 2>/dev/null
codesign -dvvv app 2>&1 | grep -E "^Identifier|flags=|^Hash type|^TeamIdentifier|^Signature="
codesign -d -r- app 2>&1 | grep -o "designated => cdhash"
codesign --verify --test-requirement='=identifier "com.example.tool"' app && echo "requirement identifier com.example.tool: satisfied"
codesign --verify --test-requirement='=identifier "other"' app 2>&1 | tail -1
codesign -d -r- /System/Applications/Calculator.app 2>&1 | grep designated
  • Identifier com.example.tool (set with -i), flags adhoc, sha256, TeamIdentifier not set, a cdhash designated requirement; the wrong identifier test fails; Calculator is anchor apple
  • Identifier app and a team ID taken from the machine
  • The designated requirement is always anchor apple generic

Mental model. The code directory is the tamper-evident seal listing every page of a contract; the designated requirement is the clause saying whose signature counts as “the same company” next time.

A signature is a code directory plus optional blobs. The code directory lists a hash of every page of the executable and of each special slot (the Info.plist, the resources seal, the entitlements, the requirements), the identifier, the team ID when there is one and some flags. The hash of the code directory itself is the CDHash: the single value that identifies exactly this build, and what systems store when they must refer to one binary (notarisation tickets, trust caches). The designated requirement is a rule, written in the requirements language, saying how to recognise “the same app” across updates. For an ad hoc signature there is no identity, so the rule is cdhash H"…" and any rebuild is a different app; the check shows that, shows TeamIdentifier=not set, and shows that --test-requirement accepts the right identifier and rejects another. A system app is identifier "com.apple.calculator" and anchor apple. A store or Developer ID app reads anchor apple generic and certificate leaf[…] or leaf[subject.OU] = "TEAMID" (Mac App Store apps use an Apple-specific certificate extension, Developer ID uses another), meaning “signed by Apple-issued certificates for this team”, so updates signed by the same team still match.

Why it matters in practice: Keychain access groups, app groups and Gatekeeper decisions are tied to the identifier and team in the signature, not to the file name; codesign -dvvv shows the identifier, flags (adhoc, runtime for the hardened runtime), hash type and authorities, codesign -d --entitlements :- prints the embedded entitlements, and codesign --verify --strict --deep checks the seal. When “the app cannot be opened” or a keychain item is inaccessible after a re-sign, compare the designated requirement and team ID of the old and new builds first. Only the ad hoc flow and Apple’s own binaries were inspected here; a Developer ID or distribution certificate was not used.

Ad hoc has no identity, so the requirement is the build’s own hash.

Common traps

  • Re-signing ad hoc and expecting the Keychain or app group to follow. The designated requirement is a hash, so the new build is a different app.
  • Reading “Authority=” lines as the whole story. The requirement and team ID, not the display name, decide what the system trusts.

They might follow up with

  • What does the hardened runtime flag change? It turns on extra protections (Q419); it appears as runtime in the flags.
  • Is the CDHash the same as the file hash? No, it hashes the code directory, which itself contains the page hashes.

Related questions

  • 25.6.1 How do you decode and audit a provisioning profile: which fields matter, how do you tell development, ad hoc…
  • 25.6.3 Why must nested code be signed before the code that contains it, what does “nested code is modified or…
  • 20.3.2 What does a Mac app need that an iOS app does not: the menu bar, windows, the App Sandbox, Hardened Runtime…
  • 19.1.2 How do app groups, shared containers and keychain access groups let an app and its extensions share data, and…

← How do you decode and audit a provisioning profile: which fields…   Why must nested code be signed before the code that contains it, what… →