8000 Prove non-exportable key custody on a real keychain (BEP, Gatehouse §6.10) · Issue #2436 · gominimal/minimal · GitHub
[go: up one dir, main page]

Skip to content

Prove non-exportable key custody on a real keychain (BEP, Gatehouse §6.10) #2436

Open

Description

@norrietaylor

Residual of #2363 (T2, epic #1501). Named by minimal-arch condition C2 on 909b8a7.

Every test in crates/bep/src/keys.rs runs against keys::memory::MemoryStore. The macOS lane now compiles keys::keychain (justfile scope carries -p bep) but nothing exercises it. Gatehouse §6.10 lines 837 and 839 require hardware-backed, non-exportable custody for the sealing and root keys.

Done when a proof runs on a real keychain on the macOS runner and shows: a generated key is not exportable (kSecAttrIsExtractable false), a key the store lost while another role's key is held gives the replacement diagnostic and never regenerates silently, and the Linux backend that OQ4 leaves out (crates/bep/src/main.rs line 49) has an equivalent proof or a recorded decision.


Generated by Claude Code

Activity

  1. norrietaylor commented on Oct 31, 2026

    @norrietaylor
    MemberAuthor

    Ran a probe on macOS 26.7 (arm64). It uses the same key attributes as crates/bep/src/keys.rs generate(): P-256, permanent, kSecAttrIsExtractable=false. The probe was an unsigned swift script.

    keychain result
    legacy login keychain (what keys.rs uses today) the key is created, and SecKeyCopyExternalRepresentation returns its bytes, so it is exportable
    data-protection keychain (kSecUseDataProtectionKeychain) generation fails with -34018 (errSecMissingEntitlement)

    What this means:

    • The legacy keychain does not enforce non-exportable for the process that made the key. keys.rs:653 refuses such a key, so every bep start on this host fails with "the keychain made the key exportable". That refusal is correct. No change to the code inside generate() fixes it.
    • The data-protection keychain, or the Secure Enclave, needs a keychain-access-groups entitlement. On macOS that is a restricted entitlement: it needs a Developer ID signature and an embedded provisioning profile. An ad-hoc build cannot get it.
    • Not probed: whether a Developer ID-signed binary changes the legacy-keychain result.

    Consequence for the proof this issue asks for: a real-keychain key-custody proof needs a signed bep with that entitlement. That ties this issue to the release-signing task #2770.

    Separate defect: bep --replace-key <role> deletes the old key before it generates the new one. When generation fails, the host has no key at all, and the message tells the user to run the same command again, which cannot succeed.

  2. norrietaylor commented on Oct 31, 2026

    @norrietaylor
    MemberAuthor

    Order changed (owner decision, 2026-10-31): this issue now waits on #2770 (T101, build, sign and notarize bep in the macOS release), not the other way round. The plan comment on #1501 and #2770's body are updated.

    Why: the probe on this issue needs code signed with the Developer ID. None of the nightly binaries that are signed today creates a SecKey. min's client key is a 32-byte secret stored as a keychain item. Only bep generates a key with kSecAttrIsExtractable=false, and the release does not ship bep yet. So the first Developer-ID-signed bep that #2770 ships is itself the probe.

    Proof for this issue: on a stock Mac, install the release or nightly that first carries the signed bep-macos-arm64, and start it (bep serve, or a GitHub grant box through min). One of two outcomes:

    • It starts with no "the keychain made the key exportable" refusal. Then the login keychain keeps a signed program's keys non-exportable, and this issue closes on that run.
    • It refuses. Then signing alone is not enough, and the custody fix (the data-protection keychain with its keychain-access-groups entitlement and a provisioning profile) lands before T102 #2772.

    Until then, debug builds keep an exportable key with a warning (T111 #2785), so dev Macs can run the live legs.

  3. norrietaylor commented on Oct 31, 2026

    @norrietaylor
    MemberAuthor

    Probe result: the signed bep is refused. Signing alone does not keep the key non-exportable.

    • Binary: bep-macos-arm64 from release run 41181733187 (branch feat/t101-release-bep, feat(release): ship a signed, notarized bep beside minvmd on macOS #2792). Developer ID Application, TeamIdentifier 3G47C5HY64, hardened runtime (flags=0x10000(runtime)), notarized (spctl: "accepted source=Notarized Developer ID"). No entitlements.
    • Host: macOS 26.7, arm64, the login keychain.
    • Command: bep-macos-arm64 --replace-key sealing --identity <temp>/identity.json
    • Exit 1. Output:
    WARN bep::keys: removed a proxy key and generated no new key role=sealing error=the key store failed for the sealing key: the keychain made the key exportable
    The old sealing key was deleted, but no new key was made: the key store failed for the sealing key: the keychain made the key exportable. Run `bep --replace-key sealing` again.
    

    So the custody check (KeyError::Exportable) fired on a key that a Developer-ID-signed, notarized bep generated. Per the decision recorded above, the custody fix lands before T102 (#2772): the data-protection keychain with a keychain-access-groups entitlement and a provisioning profile.

    This binary predates #2801, so the old --replace-key deleted the old key before it generated the new one. That is the ordering #2801 fixes.

  4. norrietaylor commented on Oct 31, 2026

    @norrietaylor
    MemberAuthor

    Custody fix filed as #2812: Secure Enclave keys, with bep bundled under a Developer ID provisioning profile. Its last step re-runs this probe on the bundled release bep, which closes this issue for macOS. The Linux backend (OQ4) stays here.

  5. norrietaylor commented on Nov 5, 2026

    @norrietaylor
    MemberAuthor

    macOS part done. The probe passes on the bundled release bep (0.8.0-dev.381.g3c2b2a73): no exportable refusal, and --replace-key exits 0 for all three roles. The result is on #2812.

    This issue stays open for the Linux backend (OQ4): it still needs an equivalent proof or a recorded decision.

    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions

      0