README: policy sections and age and child mnemonic vectors (closes #21)
check / check (push) Failing after 1s
check / check (push) Failing after 1s
Add the sections REPO_POLICIES.md requires that the README lacked: a first line naming category, license and author; Getting Started; Entrypoints (one line per script/ file); Rationale; Design (the internal/ packages, with the existing "Adding a key type" note moved under it); TODO (the open issues between the tree and 1.0, as links); License; and Author. Add the age recipients and identity vectors for the eleven-abandon example mnemonic, copied from the agekey test, and the BIP-85 specification's own 12-word child mnemonic vector, copied from the childmnemonic test. Documentation only; no code changes. Model: opus-4-8
This commit is contained in:
@@ -1,16 +1,73 @@
|
||||
# keyfunc
|
||||
|
||||
`keyfunc` turns a BIP-39 mnemonic into key pairs that can be recreated from
|
||||
that mnemonic at any time. The same mnemonic, key type and index always give the
|
||||
same key.
|
||||
`keyfunc` is an MIT-licensed Go command-line tool by
|
||||
[@sneak](https://sneak.berlin) that turns a BIP-39 mnemonic into SSH keys, age
|
||||
identities and child mnemonics, each of which can be recreated from that
|
||||
mnemonic at any time. The same mnemonic, key type and index always give the same
|
||||
key.
|
||||
|
||||
It uses the BIP-85 entropy deriver from `git.eeqj.de/sneak/secret/pkg/bip85` and
|
||||
takes the same steps as that repository's `agehd` package.
|
||||
|
||||
Commands are grouped by what is derived: `keyfunc ssh ...` for ed25519 SSH
|
||||
keys, `keyfunc age ...` for age identities and for encrypting and decrypting
|
||||
with them, and `keyfunc mnemonic ...` for child mnemonics derived from the
|
||||
main one.
|
||||
Commands are grouped by what is derived: `keyfunc ssh ...` for ed25519 SSH keys,
|
||||
`keyfunc age ...` for age identities and for encrypting and decrypting with
|
||||
them, and `keyfunc mnemonic ...` for child mnemonics derived from the main one.
|
||||
|
||||
## Getting Started
|
||||
|
||||
Build from a clone and run the binary:
|
||||
|
||||
```
|
||||
git clone git@git.eeqj.de:sneak/keyfunc.git
|
||||
cd keyfunc
|
||||
make build
|
||||
./keyfunc --version
|
||||
```
|
||||
|
||||
`make build` produces `./keyfunc`. Every deriving command needs a mnemonic; see
|
||||
[Giving it the mnemonic](#giving-it-the-mnemonic) for where it is read from, then
|
||||
for example:
|
||||
|
||||
```
|
||||
./keyfunc ssh pub -n 0 --mnemonic-command 'secret get foo'
|
||||
```
|
||||
|
||||
## Rationale
|
||||
|
||||
A key you can derive again never has to be backed up. One mnemonic, kept safe
|
||||
once, stands behind every key this tool produces: lose a laptop and the SSH key,
|
||||
the age identity and any child mnemonic on it come back from the mnemonic alone,
|
||||
at the same index, byte for byte. Nothing else has to be written down, copied
|
||||
between machines, or stored in a secret manager, because it can always be
|
||||
derived again.
|
||||
|
||||
## Design
|
||||
|
||||
The entry point is a thin `cmd/keyfunc/main.go` (what `make build` builds) that
|
||||
calls into `internal/`. The packages there are:
|
||||
|
||||
- `internal/derive` turns a mnemonic into the 32 bytes a key is made from: it
|
||||
walks BIP-39 seed, BIP-32 master key and BIP-85 entropy, and holds the shared
|
||||
constants (the byte count and the largest key index).
|
||||
- `internal/mnemonic` finds the mnemonic to work from — a command, an
|
||||
environment variable, or a terminal prompt — and refuses one that fails the
|
||||
BIP-39 checksum.
|
||||
- `internal/sshkey` turns the derived bytes into an ed25519 SSH key
|
||||
(`sshkey.go`) and serves that key from an in-process SSH agent on a private
|
||||
unix socket, keeping it out of any file (`agent.go`).
|
||||
- `internal/agekey` turns the derived bytes into an age identity and encrypts
|
||||
and decrypts with it.
|
||||
- `internal/childmnemonic` derives a child mnemonic from the main one using
|
||||
BIP-85's own mnemonic application.
|
||||
- `internal/cli` builds the cobra command tree and runs it. Under it,
|
||||
`cli/options` holds the flags every command shares, and `cli/ssh`, `cli/age`
|
||||
and `cli/mnemonic` are the command groups.
|
||||
|
||||
### Adding a key type
|
||||
|
||||
Adding a key type is one package under `internal/` that turns the 32 derived
|
||||
bytes into that type's key, plus one cobra subcommand under `internal/cli/` that
|
||||
groups its commands.
|
||||
|
||||
## Derivation
|
||||
|
||||
@@ -151,6 +208,15 @@ the same steps `sneak/secret` takes in its `agehd` package. `secret` derives at
|
||||
a vendor-specific path today; for its keys to equal this tool's it moves to
|
||||
this path, which is a change in `secret`, not here.
|
||||
|
||||
Test vectors, mnemonic
|
||||
`abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about`:
|
||||
|
||||
```
|
||||
recipient index 0: age1xwdy9y6ckyfsgjc8k02e9uhsf3fmjy0ufysewlj68kmx5n67e3nsg2mftq
|
||||
recipient index 1: age1pmm92sxaf5mazjwvjph7dx2zq9r5p8l3rarfgqm7hmakqhvgyy4q5p3w7j
|
||||
identity index 0: AGE-SECRET-KEY-19QKK2P38598XLXMQFFU3P7J9PLDD7527T70JDHGDJ7AMNF3XT44S00JFU5
|
||||
```
|
||||
|
||||
### `keyfunc age pub`
|
||||
|
||||
Prints the recipient, the `age1...` public key, on one line.
|
||||
@@ -183,33 +249,67 @@ not through step 4). Default 12 words. A child mnemonic is a full mnemonic in
|
||||
its own right: it can seed another `keyfunc`, another wallet, or `secret`, and
|
||||
it never has to be written down, since it can be derived again.
|
||||
|
||||
## Adding a key type
|
||||
Test vector: the child-mnemonic step is checked against BIP-85's own published
|
||||
vectors, which derive from the specification's master key
|
||||
`xprv9s21ZrQH143K2LBWUUQRFXhucrQqBpKdRRxNVq2zBqsx8HVqFk2uYo8kmbaLLHRdqtQpUm98uKfu3vca1LqdGhUtyoFnCNkfmXRyPXLjbKb`.
|
||||
At key index 0 the 12-word English child mnemonic is:
|
||||
|
||||
Adding a key type is one package under `internal/` that turns the 32 derived
|
||||
bytes into that type's key, plus one cobra subcommand under `internal/cli/` that
|
||||
groups its commands.
|
||||
```
|
||||
girl mad pet galaxy egg matter matrix prison refuse sense ordinary nose
|
||||
```
|
||||
|
||||
## Errors
|
||||
|
||||
Errors go to standard error and the exit status is 1, except for `ssh to`,
|
||||
which passes through `ssh`'s own exit status.
|
||||
|
||||
## Building and running
|
||||
## Entrypoints
|
||||
|
||||
```
|
||||
make build # produces ./keyfunc
|
||||
make check # fmt-check, lint (golangci-lint) and tests
|
||||
```
|
||||
The repo adheres to the
|
||||
[Scripts to Rule Them All](https://github.com/github/scripts-to-rule-them-all)
|
||||
standard: each Makefile target is a thin shim over an executable in `script/`.
|
||||
|
||||
Examples:
|
||||
- `script/bootstrap` installs everything needed to build and develop (git, make,
|
||||
Go), idempotently, from nix, apt, brew or apk; it does not install the linter,
|
||||
which only runs inside Docker.
|
||||
- `script/setup` prepares a fresh clone: it runs `bootstrap`, then installs the
|
||||
git pre-commit hook.
|
||||
- `script/projectname` prints the project name; other scripts call it so they
|
||||
stay identical across repos.
|
||||
- `script/test` runs `go vet` and then the test suite, rerunning verbosely if a
|
||||
test fails.
|
||||
- `script/lint` runs the linter inside the image built from `Dockerfile.lint`
|
||||
(which pins the linter by hash), so a complaint fails the build and leaves no
|
||||
container behind.
|
||||
- `script/fmt` formats the Go source in place.
|
||||
- `script/fmt-check` checks that formatting without writing, failing if anything
|
||||
is unformatted.
|
||||
- `script/check` runs `test`, `lint` and `fmt-check` and changes no files.
|
||||
- `script/docker` builds the Docker image tagged with the project name.
|
||||
- `script/cibuild` is the CI build the Gitea workflow calls: it runs the linter,
|
||||
then `docker build`.
|
||||
- `script/precommit` is what the git pre-commit hook runs: `go mod tidy` and
|
||||
`go fmt`, failing if `go.mod` or `go.sum` changed, then `check`.
|
||||
- `script/install-precommit` installs the git pre-commit hook that runs
|
||||
`script/precommit`.
|
||||
|
||||
```
|
||||
keyfunc ssh pub -n 3 --mnemonic-command 'secret get foo'
|
||||
keyfunc ssh priv -n 3 > ~/.ssh/id_bip85_3
|
||||
keyfunc ssh install -n 3 user@example.com
|
||||
keyfunc ssh to -n 3 user@example.com uptime
|
||||
keyfunc age pub -n 0
|
||||
keyfunc age encrypt -n 0 --armor -o notes.age notes.txt
|
||||
keyfunc age decrypt -n 0 notes.age
|
||||
keyfunc mnemonic -n 1 --words 24
|
||||
```
|
||||
## TODO
|
||||
|
||||
The open issues that stand between the tree and a 1.0 release:
|
||||
|
||||
- [#14 Choose a license and add LICENSE](https://git.eeqj.de/sneak/keyfunc/issues/14)
|
||||
- [#15 Decide the Go module path before 1.0](https://git.eeqj.de/sneak/keyfunc/issues/15)
|
||||
- [#16 Do not pass the mnemonic environment variables on to ssh and sftp](https://git.eeqj.de/sneak/keyfunc/issues/16)
|
||||
- [#17 Clean up the agent socket and working files when a signal ends the tool](https://git.eeqj.de/sneak/keyfunc/issues/17)
|
||||
- [#22 1.0 release readiness](https://git.eeqj.de/sneak/keyfunc/issues/22)
|
||||
|
||||
## License
|
||||
|
||||
MIT. The license is not yet settled on the tracker
|
||||
([#14](https://git.eeqj.de/sneak/keyfunc/issues/14)); MIT is the recommended
|
||||
option there, so this README names it and the `LICENSE` file is added when that
|
||||
issue is answered.
|
||||
|
||||
## Author
|
||||
|
||||
[@sneak](https://sneak.berlin).
|
||||
|
||||
Reference in New Issue
Block a user