Skip to content

Take the signing identity from the keychain, and classify it correctly - #45

Merged
frobware merged 4 commits into
masterfrom
check-install-identity-classification
Sep 19, 2026
Merged

frobware merged 4 commits into
masterfrom
check-install-identity-classification

Conversation

@frobware

@frobware frobware commented Sep 19, 2026

Copy link
Copy Markdown
Owner

Two related things about the signing identity: finding it, and judging it. Four commits, each self-contained.

Judging it

check-install exists to stop you installing a bundle that launchd will then refuse to start. It decided that on the wrong signal:

var isAdHoc: Bool { teamIdentifier == nil }

Ad-hoc signatures have no team identifier, but neither does any certificate that was not issued through an Apple developer account. So a bundle signed with one was called ad-hoc and refused over a registered agent, having just been built and signed successfully.

Ad-hoc has no certificate, so the designated requirement falls back to a cdhash that changes on every build -- which is exactly why it cannot satisfy a recorded launch requirement. A certificate produces a requirement naming its leaf hash, which does not move. The code directory carries an adhoc flag saying which is which, so read that rather than guessing from the team.

Finding it

local.mk existed to hold a string the keychain already knows. The build now asks for it, at about fifteen milliseconds, so with one codesigning certificate there is nothing to configure at all.

  • None still means ad-hoc, so a fresh checkout builds.
  • Several means refusing to choose, because signing with the wrong certificate costs you the Accessibility grant. The refusal prints each candidate as the line to paste.
  • local.mk still wins where it exists, and is still needed for a certificate the keychain does not report as valid -- a self-signed one is not listed, so it has to be named.

INSTALL.md follows the same shape, and local.mk.example puts the instructions next to the line they describe. Everything in the example is commented out, so copying it unedited leaves the build deriving the identity as it would have anyway.

The advice was wrong in the same place

Both the message printed with the refusal and INSTALL.md said make uninstall clears the launch requirement macOS records against the agent's label, and that unregistering does not. Both halves are backwards, and both were measured:

  • Uninstall first, install a differently signed bundle, and launchd still rejects the job with EX_CONFIG (78). The record is then rewritten so the identity you started with is rejected too.
  • make unregister followed by make register clears it, in seconds, without admin rights or sfltool.

Uninstalling does still matter, but not for the reason given: with no installed bundle there is nothing for check-install to compare against, so it is what gets you past the refusal. Both texts now give the whole sequence and say which step cannot be skipped.

INSTALL.md also stops calling the brackets in a certificate name a team id -- the team id is the OU, which is what codesign reports as TeamIdentifier -- corrects the Xcode path, which is Settings > Apple Accounts, and says a certificate is per machine so other Macs showing "Not in Keychain" is expected rather than broken.

Test plan

  • make ci green: 110 tests, 0 failures, plist lint, every target parses. Four tests added.
  • The new tests were written first and failed to compile against the old shape.
  • testAdHocBundleReportsItsIdentifierAndNoTeam builds a genuinely ad-hoc bundle with codesign and still classifies it correctly, so the flags path is exercised against a real signature.
  • The derivation was checked in all four states: one certificate, none, several, and local.mk present. It produces the identical string on two machines.
  • The EX_CONFIG claims come from doing it, on two machines, in both directions.

@coderabbitai

coderabbitai Bot commented Sep 19, 2026

Copy link
Copy Markdown

Review Change StackReview Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Advanced

Run ID: 473cba58-a9fa-46ea-b54d-3384259edf06

📥 Commits

Reviewing files that changed from the base of the PR and between c6494ea and fac5f92.

📒 Files selected for processing (1)
  • INSTALL.md

Included review availability: Your plan provides up to 2 included reviews per hour; 0 remain after this review.


📝 Walkthrough

Walkthrough

The change distinguishes ad-hoc signatures from self-signed certificates by using code-signature flags. It updates installation decisions, tests, documentation, and signing-identity mismatch guidance.

Changes

Signing identity and installation

Layer / File(s) Summary
Signing identity classification
Sources/signing.swift, Tests/cmd-key-happy-tests/SigningIdentityTests.swift
SigningIdentity stores explicit ad-hoc status. Bundle inspection reads SecCodeSignatureFlags.adhoc. Tests cover self-signed and ad-hoc identity equality and descriptions.
Self-signed installation decisions
Sources/InstallDecision.swift, Tests/cmd-key-happy-tests/InstallDecisionTests.swift
Documentation explains the registration check and self-signed requirements. Tests allow a self-signed build when no bundle is installed and when it replaces an identical registration.
Signing mismatch guidance
Sources/loginitem.swift, INSTALL.md
The mismatch guidance advises restoring CODESIGN_IDENTITY or running make unregister && make register after installation. It explains the retained macOS requirement and possible EX_CONFIG failure.

Priority: ⬇️ Low

Estimated code review effort: 2 (Simple) | ~15 minutes

Change: Bug fix

Merge Risk: ⚪ Minimal · up to fac5f

The documented installation recovery changes have no remaining actionable merge risk based on the supplied evidence.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 58.33% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 12 functions across 5 files. (1 skipped: … Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main changes: it reads the signing identity from the keychain and classifies ad-hoc and self-signed identities correctly.
Full details: Docstring Coverage

Explanation

Docstring coverage is 58.33% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 12 functions across 5 files. (1 skipped: 1 unsupported.)

  • Fix all pre-merge checks with AI
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@frobware
frobware force-pushed the check-install-identity-classification branch from dea826d to c6494ea Compare September 19, 2026 18:55
check-install exists to stop you installing a bundle launchd will
then refuse to start, and it decided that on the wrong signal:
isAdHoc was the absence of a team identifier. Any certificate not
issued through an Apple developer account has no team either, so a
bundle signed with one was called ad-hoc and refused over a
registered agent.

The two are not alike. Ad-hoc has no certificate, so the requirement
falls back to a code hash that changes on every build, which is
exactly why it cannot satisfy a recorded launch requirement. A
certificate produces a requirement naming its leaf hash, which does
not move. The code directory carries an adhoc flag saying which is
which, so read that instead of guessing from the team.

The advice printed with the refusal was wrong in the same place, and
so was INSTALL.md. make uninstall does not clear the launch
requirement macOS recorded against the label: uninstall first,
install a differently signed bundle, and launchd still rejects the
job with EX_CONFIG, and the record then rejects the identity you
started with too. An unregister followed by a register does clear it,
in seconds, with no admin rights. Uninstalling still matters, because
with no installed bundle there is nothing for this check to compare
against, but it is not the step that clears anything. Both now say
the whole sequence and which part cannot be skipped.

INSTALL.md also stops calling the brackets in a certificate name a
team id. The team id is the OU, and is what codesign reports as
TeamIdentifier.
@frobware
frobware force-pushed the check-install-identity-classification branch from c6494ea to fac5f92 Compare September 19, 2026 19:00
@frobware frobware changed the title Tell a self-signed certificate apart from an ad-hoc signature Stop calling a certificate without a team ad-hoc Sep 19, 2026
local.mk existed to hold a string the keychain already knows. With
one codesigning certificate there is now nothing to set up: ask for
it, at about fifteen milliseconds, and sign with it.

None still means ad-hoc, so a fresh checkout builds. Several means
refusing to choose, because picking the wrong certificate signs
against a requirement macOS did not record and the grant goes; the
refusal prints each candidate as the line to paste.

local.mk still wins where it exists, and is still what you want for a
certificate the keychain does not report as valid -- a self-signed
one is not listed, so it has to be named.
The install notes had you read an identity out of find-identity and
retype it into local.mk. The build asks the keychain itself now, so
with one certificate there is nothing to write, and the file is for
the two cases that need it: more than one certificate, or one the
keychain does not report as valid.

Also corrects the Xcode path, which is Settings > Apple Accounts, and
says the certificate is per machine so the other Macs showing "Not in
Keychain" is expected rather than something to fix.
The final form of the file, with how to get the value next to the
line it goes on: the Xcode path for a certificate, find-identity for
the exact string, and why you probably need neither.

Everything in it is commented out, so copying it unedited leaves the
build deriving the identity as it would have anyway, rather than
trying to sign with a placeholder.
@frobware frobware changed the title Stop calling a certificate without a team ad-hoc Take the signing identity from the keychain, and classify it correctly Sep 19, 2026
@frobware
frobware merged commit a1a2773 into master Sep 19, 2026
2 checks passed
@frobware
frobware deleted the check-install-identity-classification branch September 19, 2026 19:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant