ho-04.3 — Sign the dev binary; verify Keychain biometry end-to-end
- Ho-04.3-AT-01.md
Add codesign to scripts/install.sh using the practitioner's existing Developer ID Application certificate. Sign with hardened runtime, empty entitlements (biometry-gated Keychain access doesn't require specific entitlements — only a stable signing identity). Verify end-to-end that sharibako key generate on the signed binary prompts Touch ID, writes to Keychain, and sharibako key export --private --i-know-this-is-plaintext re-prompts and retrieves.
Ri- . Small, surgical, well-understood. The practitioner has signed CLIs before via the M4Bmaker pipeline; this ho reuses that discipline for sharibako with product-specific entitlements.
Out of scope:
- Notarization (distribution concern; Phase 6)
.dmgpackaging or Homebrew tap (also Phase 6)- Any change to how the CLI USES Keychain — that code is already written in ho-04's -01
- Multi-binary signing (sharibako CLI is the only binary in the release build)
Resolves:
- ho-04's Followup entry naming the errSecMissingEntitlement (-34018) blocker surfaced during AT-01 dogfooding
Problem
sharibako key generate on macOS hits errSecMissingEntitlement (-34018) when writing a Keychain item with SecAccessControl .userPresence. This is Apple's security model: unsigned binaries have no stable signing identity, and Keychain refuses biometry-gated access to items whose owner can't be established. The Swift code path in AT-01 is correct; the binary just lacks a signing identity.
All AT-01 and AT-02 dogfooding uses the --age-key <path> bypass instead. That's fine for the file-based verbs but leaves the Keychain path unverified in production shape.
Solution
Sign the release binary during install.sh with the practitioner's Developer ID Application cert (Developer ID Application: ANDREW TODD MARCUS (3N8F759K8D)). Empty entitlements plist (biometry doesn't need special entitlements — only a stable identity). Hardened runtime (--options runtime) both for security posture and to keep the door open to Phase 6 notarization without rework.
On non-macOS platforms, install.sh skips signing with a warning and installs the raw binary (the --age-key file path remains the primary Linux flow).
Changes
- New:
sharibako.entitlementsat repo root. Minimal — empty<dict/>plist. Hardened-runtime-capable. - Modify:
scripts/install.sh— afterswift build -c release, add a codesign step gated onuname -s = Darwin. On non-Darwin, print "Signing skipped (not macOS)." Print the signing identity used on success. Fail the script if signing was attempted and returned non-zero. - Modify:
/hos/ho-04-cli.md— update the section to record that Keychain biometry was verified end-to-end on<date>after ho-04.3 landed. Small addition.
Signing invocation:
codesign \
--sign "Developer ID Application: ANDREW TODD MARCUS (3N8F759K8D)" \
--entitlements sharibako.entitlements \
--options runtime \
--timestamp \
--force \
.build/release/sharibako
Then a pass immediately after:
codesign --verify --verbose .build/release/sharibako
spctl --assess --type execute --verbose .build/release/sharibako
Both should exit 0. spctl may print "rejected" if the binary isn't notarized — that's expected for local signing and doesn't block Keychain access. The codesign --verify check is what matters.
Results
Done means:
- Fresh
./scripts/install.shon macOS produces a/usr/local/bin/sharibakothat is codesigned with Developer ID codesign --verifyon the installed binary exits 0sharibako key generateon the signed binary prompts Touch ID (no-34018error)- The Keychain item is written and
security find-generic-password -s sharibakofinds it sharibako key export --private --i-know-this-is-plaintextre-prompts Touch ID and retrieves the raw keysharibako key export --publicretrieves the recipient without a Touch ID prompt (it reads metadata only, no private-key access)- On a Linux VM or container,
install.shprints "Signing skipped (not macOS)" and still produces a workingsharibakobinary ho-04-cli.md's section names the verification date
Not required:
- Automated tests for the signing step. This is
install.shand one plist file. Manual verification suffices. - Notarization or stapling. Distribution concerns; Phase 6.
- Any Swift code changes. The Keychain code in AT-01 is already correct.
Deferred to execution:
- Whether the entitlements file needs any additions beyond empty. Start empty; add only what
codesign --options runtimerefuses to sign without. - Whether
install.shshould verify the cert exists in the practitioner's Keychain before attempting signing (probably yes, with a clear error message pointing at how to install it).
What actually shipped (reconcile, 2026-07-03)
The drafted Solution — sign the raw binary with an empty entitlements plist, on the premise that "biometry doesn't need special entitlements, only a stable identity" — did not work. It resolved the "Deferred to execution" question ("whether the entitlements file needs additions beyond empty") in the direction the ho left open: it needs more than empty, and more than a bare signature.
What scripts/install.sh actually does:
sharibako.entitlementscarrieskeychain-access-groups(3N8F759K8D.net.sageframe.sharibako), not an empty dict. Biometry-gated Keychain access with a stable owner requires the item to live in a declared access group, and the binary must claim that group via entitlement.keychain-access-groupsis a restricted entitlement — macOS only honours it for a codesigned.appbundle with an embedded provisioning profile (scripts/Sharibako_Developer_ID.provisionprofile→Contents/embedded.provisionprofile). A signed bare binary is rejected. Soinstall.shbuilds a thin.apparound the CLI, signs the bundle (--options runtime --timestamp, Developer ID3N8F759K8D), installs it toSHARIBAKO_BUNDLE_DIR, and symlinks the inner binary intoSHARIBAKO_INSTALL_DIR.- The cert-presence guard and provisioning-profile guard that the ho left "deferred to execution" both shipped as clear pre-flight errors.
This is a materially different mechanism from the drafted one, but it lands the same deliverable — a signed install whose Keychain path works with Touch ID. The Problem section (the -34018 / errSecMissingEntitlement blocker) and the Out-of-scope boundaries (no notarization, no DMG, no Swift changes) all held.
Reflect
Signing shipped and is verified end-to-end. The verification came for free while closing ho-04.5: on 2026-07-03, sharibako run --scope diary against the signed install prompted Touch ID, read the age key from the Keychain, and decrypted every secret — the exact biometry-gated Keychain path this ho exists to prove. errSecMissingEntitlement (-34018) is gone; the raw-binary debug build instead fails with errSecItemNotFound (-25300) because it isn't in the access group, which is the expected and correct behaviour (captured in project memory as the gate).
- The empty-entitlements hypothesis was wrong, and the ho anticipated it. The "Deferred to execution" note is what a well-formed / ho does — it names the black box it hasn't opened. Opening it revealed the bundle requirement. No time lost chasing the wrong premise blindly.
- The drafted "update ho-04-cli.md's Reflect with the verification date" step was NOT done — and correctly so. ho-04-cli is closed; forbids editing its content. The verification record lives here, in the ho that owns the signing work, instead. (The drafted step predated ho-04's close; it's void now.)
- Not verified in this pass:
key generate/key import/key exporton the signed binary specifically (the Results checklist items). The retrieval path is proven by therundogfood; the storage path (writing a fresh Keychain item viakey generate) was exercised earlier when thediaryvault's key was first stored, but not re-run under this reconcile. Low risk — sameSecItemAddpath — but named honestly as unproven-this-session. - Linux fallback (
install.shprints "Signing skipped" and installs the raw binary) is unexercised — no Linux run this cycle. Deferred to whenever a Linux install actually happens.
Authored: 2026-07-02. Executed, reconciled, and closed: 2026-07-03.
Rendered from the corpus, verbatim · source on GitHub →