Add static keychain publication workflow

This commit is contained in:
Eric Wendland 2026-05-27 00:59:52 +02:00
commit cfd41522d1
11 changed files with 1852 additions and 46 deletions

View file

@ -75,6 +75,20 @@ Signatures are produced with:
ssh-keygen -Y sign -n geth.keychain.v1@geth.local -f <signing-key> <payload>
```
`<signing-key>` can be:
- a local private OpenSSH key file,
- a FIDO/YubiKey OpenSSH security-key stub,
- a public key whose private half is already loaded in `ssh-agent`, or
- a public key backed by a PKCS#11 token loaded into `ssh-agent` with
`ssh-add -s <provider>`.
Encrypted private key files should normally be unlocked into `ssh-agent` before
running geth control commands. The daemon never stores private keys or
passphrases. Direct PKCS#11 signing is intentionally not a first-class geth
backend because portable `ssh-keygen -Y sign` flows do not expose the same
provider flag as OpenSSH certificate signing.
Verification uses:
```sh
@ -168,10 +182,17 @@ and cacheable:
The crate provides helpers to encode/decode JSONL and flatten entries back into
`KeychainOp[]` plus `KeychainOpSignature[]`.
Future work should add explicit checkpoint records containing the accepted head
ID, byte offset, reduced view hash, and log hash. That would let static clients
resume verification without replaying the entire file while still detecting
rollback or truncation.
geth writes checkpoint records with `geth keychain publish-bundle`. The
checkpoint contains the accepted head, operation/signature counts, byte length,
BLAKE3 hashes for the sigchain and allowed signers projection, a reduced-view
hash, generation time, and the discovery base URL. The checkpoint is signed as
`geth.sigchain.checkpoint.json.sig` so static clients can detect rollback or
truncation before importing updates. `geth keychain fetch --import` stores the
last accepted checkpoint for a source URL and rejects older checkpoints from the
same source. The fetch URL is a retrieval location and may be a `file://` mirror
for testing; the checkpoint's signed `base_url` remains the advertised static
publication location. Use `geth keychain verify-checkpoint --base-url <url>` when
a consumer needs to pin that advertised value explicitly.
## Commands
@ -205,6 +226,47 @@ format:
```sh
geth keychain allowed-signers > allowed_signers
geth keychain allowed-signers --out allowed_signers
geth keychain sign-file \
--in authorized_keys \
--out authorized_keys.sig \
--signing-key ~/.ssh/id_ed25519_sk
geth keychain verify-file \
--in authorized_keys \
--signature authorized_keys.sig
geth keychain sigchain --out geth.sigchain.jsonl
geth keychain publish-bundle \
--out public/.well-known/sshsigchain \
--signing-key ~/.ssh/id_ed25519_sk \
--snapshot authorized_keys=authorized_keys
geth keychain verify-checkpoint \
--checkpoint geth.sigchain.checkpoint.json \
--signature geth.sigchain.checkpoint.json.sig \
--sigchain geth.sigchain.jsonl \
--allowed-signers allowed_signers
geth keychain fetch --url https://example.com/.well-known/sshsigchain/ --import
geth keychain verify-sigchain --in geth.sigchain.jsonl
geth keychain import-sigchain --in geth.sigchain.jsonl
geth keychain explain <op-id>
geth keychain explain-signer <key-id>
```
The default static discovery base URL used by `publish-bundle` is
`https://example.com/.well-known/sshsigchain/`. The keychain does not manage
`authorized_keys` policy. It signs an arbitrary
snapshot you provide, which lets other projects keep their own SSH login policy
while rooting snapshot approval in the keychain. By default `sign-file` uses the
personal identity namespace `geth.authorized-keys.v1@eric.wendland.dev`; pass
`--namespace` for project-specific snapshots. The signer must be an active key
in the current `allowed_signers` projection, so website consumers can fetch the
snapshot, signature, and allowed signers and verify:
```sh
ssh-keygen -Y verify \
-f allowed_signers \
-I admin \
-n geth.authorized-keys.v1@eric.wendland.dev \
-s authorized_keys.sig < authorized_keys
```
Replay and verify the local sigchain:
@ -221,7 +283,6 @@ locally initialized owner/admin key as the bootstrap trust anchor.
The current geth prototype does not yet provide:
- signed checkpoint objects equivalent to `skm.last-verified-commit`
- transparency-log style append proofs
- anti-rollback protection beyond local state, HTTP cache validators, and sync
conflict checks