Add static keychain publication workflow
This commit is contained in:
parent
b6ffcde54c
commit
cfd41522d1
11 changed files with 1852 additions and 46 deletions
|
|
@ -356,12 +356,25 @@ pattern of verifying key-registry changes from a prior trusted state. The
|
|||
transport-neutral replay rules, application-specific signature namespaces,
|
||||
allowed-signers projection, and JSONL sigchain helpers live in `geth-keychain`
|
||||
so other applications can reuse the same identity-log model without depending
|
||||
on the daemon, SQLite, Iroh, or local control. `geth keychain sync <node>` pulls
|
||||
keychain operations and signatures from an imported peer over Iroh and imports
|
||||
only operations with a valid OpenSSH signature from a currently trusted admin
|
||||
key over the canonical payload. See `docs/sigchain-keychain.md` for the
|
||||
detailed sigchain design. This is currently a pull-based signed operation log,
|
||||
not a CRDT or Keyhive-style convergent authority.
|
||||
on the daemon, SQLite, Iroh, or local control. The CLI can export the same
|
||||
reduced key registry as OpenSSH `allowed_signers` or as appendable JSONL
|
||||
sigchain data for website publication. It can also sign and verify arbitrary
|
||||
snapshots, such as externally managed `authorized_keys`, with an active
|
||||
keychain signer under an explicit OpenSSH namespace. `geth keychain
|
||||
publish-bundle` writes a website-ready bundle for
|
||||
`https://example.com/.well-known/sshsigchain/`, including `allowed_signers`,
|
||||
`geth.sigchain.jsonl`, a signed checkpoint, and optional signed snapshots.
|
||||
`geth keychain fetch --import` verifies the checkpoint and records the last
|
||||
accepted checkpoint per retrieval source URL to reject older bundles. The
|
||||
retrieval source may be a local mirror; the checkpoint still carries the signed
|
||||
advertised publication base URL, and explicit checkpoint verification can pin it. `geth keychain
|
||||
explain` and `explain-signer` provide basic auditability for why a keychain
|
||||
operation or signer is trusted. `geth keychain sync <node>` pulls keychain
|
||||
operations and signatures from an imported peer over Iroh and imports only
|
||||
operations with a valid OpenSSH signature from a currently trusted admin key
|
||||
over the canonical payload. See `docs/sigchain-keychain.md` for the detailed
|
||||
sigchain design. This is currently a pull-based signed operation log, not a
|
||||
CRDT or Keyhive-style convergent authority.
|
||||
|
||||
New devices can use the node enrollment flow instead of hand-editing keychain
|
||||
state. `geth node enroll request` creates a canonical, agent-key-signed request
|
||||
|
|
|
|||
|
|
@ -325,6 +325,37 @@ resource-scoped capability decisions.
|
|||
signed admin-key registry operations.
|
||||
- `[x]` `geth keychain allowed-signers` exports the active admin key view in
|
||||
OpenSSH `allowed_signers` format.
|
||||
- `[x]` `geth keychain allowed-signers --out <path>` writes the active
|
||||
OpenSSH `allowed_signers` projection directly to a file.
|
||||
- `[x]` `geth keychain sign-file --in <path> --out <sig>` signs arbitrary
|
||||
snapshots, such as externally managed `authorized_keys`, with an active
|
||||
admin key under an explicit namespace.
|
||||
- `[x]` `geth keychain verify-file --in <path> --signature <sig>` verifies a
|
||||
snapshot signature against the current keychain-derived `allowed_signers`
|
||||
projection or a supplied `--allowed-signers` file.
|
||||
- `[x]` `geth keychain sigchain --out <path>` writes the appendable JSONL
|
||||
sigchain suitable for static website publication.
|
||||
- `[x]` `geth keychain publish-bundle --out <dir>` writes a static website
|
||||
bundle rooted at `https://example.com/.well-known/sshsigchain/` by default.
|
||||
- `[x]` Publication bundles include `allowed_signers`, `geth.sigchain.jsonl`,
|
||||
`geth.sigchain.checkpoint.json`, and a detached checkpoint signature.
|
||||
- `[x]` Publication bundles can copy and sign external snapshots with
|
||||
`--snapshot <name>=<path>` without making the keychain own their contents.
|
||||
- `[x]` `geth keychain verify-sigchain --in <path>` verifies a JSONL sigchain
|
||||
file by replaying operations and signatures.
|
||||
- `[x]` `geth keychain import-sigchain --in <path>` imports a JSONL sigchain
|
||||
only if replay verification rejects no operations.
|
||||
- `[x]` `geth keychain verify-checkpoint` verifies checkpoint signatures,
|
||||
checkpoint hashes, base URL, and sigchain head consistency.
|
||||
- `[x]` `geth keychain fetch --url <base> --import` fetches static bundles
|
||||
with `curl` for HTTP(S) or filesystem reads for local/file URLs.
|
||||
- `[x]` Static fetch/import records the last accepted checkpoint per source
|
||||
URL and rejects older checkpoints for rollback resistance.
|
||||
- `[x]` `geth keychain explain <op-id>` and `explain-signer <key-id>` provide
|
||||
basic audit output for operations and admin signers.
|
||||
- `[x]` Agent/FIDO signing is supported through OpenSSH by passing a public
|
||||
key or security-key stub to `--signing-key`; PKCS#11 is documented as an
|
||||
ssh-agent-backed flow when loaded with `ssh-add -s <provider>`.
|
||||
- `[x]` `geth keychain verify` replays the keychain sigchain against the
|
||||
previously accepted admin-key view.
|
||||
- `[x]` Reusable sigchain mechanics live in `geth-keychain`, not in daemon
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in a new issue