geth/docs/sigchain-keychain.md
Eric Wendland 538b52bdb6
Some checks failed
CI / fmt, clippy, docs (push) Failing after 5s
CI / test (ubuntu-latest) (push) Failing after 5s
CI / iroh integration smoke tests (push) Failing after 4s
CodeQL / Analyze Rust (push) Failing after 5s
Security / RustSec cargo-audit (push) Failing after 4s
CI / test (macos-latest) (push) Has been cancelled
CI / test (windows-latest) (push) Has been cancelled
simplify sigchain http distribution
2026-07-19 03:43:06 +02:00

155 lines
6.7 KiB
Markdown

# Geth Keychain And SSHSIGCHAIN
## Purpose
The geth keychain is the local identity-plane operation log for admin SSH keys,
users, devices, nodes, agents, and endpoint bindings. The daemon stores these
operations in SQLite and synchronizes authorized records over Iroh. Reducing
the operation log produces the current keychain view used by node management,
authorization, and OpenSSH `allowed_signers` projection.
[`sshsigchain.md`](sshsigchain.md) specifies SSHSIGCHAIN, the only portable
signed-chain format in this repository. It is deliberately separate from the
current local keychain operation log: SSHSIGCHAIN starts with an explicit
out-of-band trust tuple, uses exact parent hashes without a sequence counter,
and never uses a downloaded `allowed_signers` file as a trust root. The previous
test-only static JSONL publication format was removed.
## Local keychain model
The durable local log contains `KeychainOp` records and their
`KeychainOpSignature` records. Every operation has a deterministic canonical
binary signing payload under the `geth.keychain.v1@geth.local` namespace.
JSON is an API and storage representation, never the signed payload.
Important operation kinds are:
- `KeychainInit`
- `AdminKeyAdd`, `AdminKeyRevoke`
- `UserAdd`, `UserRename`, `UserRevoke`
- `DeviceAdd`, `DeviceRevoke`, `DeviceKeyAdd`, `DeviceKeyRevoke`
- `NodeAdd`, `NodeRename`, `NodeRevoke`
- `NodeEndpointAdd`, `NodeEndpointRevoke`
- `AgentBind`
`AdminKeyAdd` carries the OpenSSH public key material used to reconstruct the
active `allowed_signers` view. Local operation and signature identities are
append-only: an existing identity with different bytes is rejected rather than
replaced.
## Local signatures
An operation signature contains its operation ID, signer key fingerprint,
signer public key, SSHSIG namespace, OpenSSH signature bytes, and creation
time. geth signs through OpenSSH, for example:
```sh
ssh-keygen -Y sign -n geth.keychain.v1@geth.local -f <signing-key> <payload>
```
`<signing-key>` may be a local private key, an OpenSSH security-key stub, or a
public key whose private half is available through `ssh-agent`. The daemon does
not store private keys or passphrases.
Local verification accepts an operation only when a signature from an admin
key in the previously accepted local view verifies over the canonical payload.
This is useful for the local Iroh-synchronized operation log, but it is not a
portable SSHSIGCHAIN history and MUST NOT be presented as one.
## SSHSIGCHAIN authority and profiles
SSHSIGCHAIN uses the `sshsigchain.v1` SSHSIG namespace. Device/key management is
not a geth-specific profile: the mandatory Authority v1 reducer defines genesis,
device add/revoke, key add/revoke, proof-of-possession, scoped and delegable
permissions, device permission ceilings, anchor-policy changes, and no-op links.
Authority is always public and evaluated from the exact parent state. The root
key seeds authority only at genesis and can later be causally revoked.
Applications attach profile-ID-scoped commitments. A fresh 32-byte nonce salts
each payload commitment so a withheld small value is not directly vulnerable to
dictionary guessing. A disclosure can be added to or removed from transport
without changing the SSH signature or record hash. Hidden application data can
never alter devices, keys, permissions, or anchor policy. Verification without
all disclosures remains useful for authority, but affected application profile
state is reported as incomplete.
Keys receive direct permissions such as `DeviceAdd`, `KeyAddSelf`,
`ManagePermissions`, `AnchorAttest`, and `ProfileWrite(<profile>)`. An attached
key cannot exceed its device ceiling, and it cannot grant a permission outside
its delegable set. Newly added keys must sign the exact proposed key/device/
permission binding under `sshsigchain.key-proof.v1`.
Head claims are separately signed by an active `AnchorAttest` key. An authority
anchor policy names weighted/required attesting keys plus backend IDs, classes,
locators, weights, required backends/classes, and a separate backend threshold.
Adapters verify backend-specific receipts, while the core
rejects rollback behind a cached head and incomparable histories. Backends do
not decide which history is canonical. The head that changes anchor policy is
witnessed under the previous policy.
To inspect an SSHSIGCHAIN JSONL transport file, pin the chain ID and root public
key locally:
```sh
geth keychain verify-sigchain \
--in geth.sshsigchain.v1.jsonl \
--chain-id <64-hex-character-chain-id> \
--root-key ~/.ssh/geth-root.pub
```
This verifier is experimental while geth adds record creation, publication,
import, accepted-head persistence, concrete anchor adapters, and independently
generated wire vectors. Those later workflows must preserve the same explicit
trust tuple and Authority v1 semantics; they must not introduce a compatibility
route for the removed static format.
The verifier also accepts the canonical `.sscb` on-disk bundle. JSONL is a
human-facing record interchange and does not preserve standalone head claims or
anchor receipts. Convert between them with:
```sh
geth keychain bundle-create --in chain.jsonl --out chain.sscb
geth keychain bundle-extract --in chain.sscb --out chain.jsonl
```
The canonical bundle keeps disclosures separate from signed record objects and
sorts every object class deterministically. Adding a disclosure changes the
bundle hash but not the chain head. The same single file is the static HTTP
distribution artifact at any explicitly configured URL; there is no well-known
path, URL derivation, or trust-on-first-use. Distribution is untrusted: the
configured root, authority replay, anchor policy, locally accepted head, and
fork checks still decide acceptance.
## Local commands
Bootstrap an owner node:
```sh
geth init \
--admin-key ~/.ssh/id_ed25519_sk.pub \
--signing-key ~/.ssh/id_ed25519_sk \
--node-name laptop
```
Manage the current local keychain and its OpenSSH projection:
```sh
geth keychain admin-add \
--admin-key ~/.ssh/new_admin.pub \
--signing-key ~/.ssh/current_admin_sk \
--principal admin
geth keychain admin-revoke <key-fingerprint> \
--signing-key ~/.ssh/current_admin_sk
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 explain <op-id>
geth keychain explain-signer <key-id>
geth keychain verify
```
The keychain does not manage host `authorized_keys` policy. It can sign a
snapshot supplied by the operator, allowing another system to retain its own
SSH login policy while usefully checking approval against an active local admin
key.