keyfunc: the tool's name, chosen by sneak
The name says what the tool is: a key is a function of the mnemonic and an index, computed when asked for and stored nowhere. Directory, module, binary and environment variables take it. Model: fable-5-1
This commit is contained in:
144
keyfunc/README.md
Normal file
144
keyfunc/README.md
Normal file
@@ -0,0 +1,144 @@
|
||||
# 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.
|
||||
|
||||
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 key type: `keyfunc ssh ...` for ed25519 SSH keys (in
|
||||
this PR) and `keyfunc age ...` for age identities (planned, see below).
|
||||
|
||||
## Derivation
|
||||
|
||||
The mnemonic is turned into a key like this:
|
||||
|
||||
1. mnemonic -> BIP-39 seed (empty passphrase);
|
||||
2. seed -> BIP-32 master key;
|
||||
3. master key -> 64 bytes of BIP-85 entropy at the path below;
|
||||
4. entropy -> BIP-85 DRNG (SHAKE256); read 32 bytes;
|
||||
5. those 32 bytes become the key in the way the key type needs.
|
||||
|
||||
The path is:
|
||||
|
||||
```
|
||||
m/83696968'/<app>'/<n>'
|
||||
```
|
||||
|
||||
- `83696968` is the fixed BIP-85 purpose.
|
||||
- `app` is the application number of the key type. There is no vendor id: the
|
||||
path is meant as a standard any implementation can follow, not something tied
|
||||
to one tool. Each key type's number is spelled the way BIP-85 spells its own
|
||||
RSA application (`828365` is the ASCII codes of `R`, `S`, `A` written out):
|
||||
SSH is `838372` (`S` `S` `H`), age is `657169` (`A` `G` `E`).
|
||||
- `n` is the key index: flag `--index` / `-n`, default `0`.
|
||||
|
||||
## Giving it the mnemonic
|
||||
|
||||
The mnemonic itself is never a command-line argument. It is looked for in this
|
||||
order; the first one found wins:
|
||||
|
||||
1. `--mnemonic-command <command>`: a shell command, run with `sh -c`, whose
|
||||
standard output is the mnemonic. Example: `--mnemonic-command 'secret get
|
||||
foo'`. Whitespace around the output is dropped. If the command exits with a
|
||||
non-zero status, the tool prints its standard error and exits with status 1.
|
||||
2. Environment variable `KEYFUNC_MNEMONIC_COMMAND`: the same, as a shell
|
||||
command held in the environment.
|
||||
3. Environment variable `KEYFUNC_MNEMONIC`: the mnemonic itself.
|
||||
4. A prompt on the terminal with echo turned off.
|
||||
|
||||
If none of these is available and standard input is not a terminal, the tool
|
||||
refuses and exits with status 1. A mnemonic that fails the BIP-39 checksum is
|
||||
refused with a message saying so.
|
||||
|
||||
Every command takes `--index` / `-n` and `--mnemonic-command`, and has `--help`.
|
||||
`keyfunc --version` prints the version set at build time.
|
||||
|
||||
## SSH keys: `keyfunc ssh`
|
||||
|
||||
Only ed25519 keys are produced. The application number is `838372`, so the
|
||||
path is `m/83696968'/838372'/<n>'`. The 32 bytes from step 4 are the ed25519
|
||||
seed.
|
||||
|
||||
Test vector, mnemonic
|
||||
`abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about`:
|
||||
|
||||
```
|
||||
index 0: ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJZOtOczrc/7CQytcuFwt7s4r8KjkZWkwjLZWBaFKD+7
|
||||
index 1: ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOEWY8+/gmHYVC4u0Y0I4FKs+eVUulTPHfk9VtXw1tMF
|
||||
```
|
||||
|
||||
### `keyfunc ssh pub`
|
||||
|
||||
Prints one `authorized_keys` line to standard output:
|
||||
|
||||
```
|
||||
ssh-ed25519 <key> <comment>
|
||||
```
|
||||
|
||||
The comment defaults to `keyfunc/ssh/<n>`; change it with `--comment`.
|
||||
|
||||
### `keyfunc ssh priv`
|
||||
|
||||
Prints the unencrypted private key in OpenSSH format (the
|
||||
`-----BEGIN OPENSSH PRIVATE KEY-----` block that `ssh` reads) to standard output
|
||||
and nothing else, so it can be redirected into a file. The key's comment is the
|
||||
same as for `pub`.
|
||||
|
||||
### `keyfunc ssh install <[user@]host> [-- ssh options...]`
|
||||
|
||||
Runs the system `ssh` to the host and, on the host:
|
||||
|
||||
- creates `~/.ssh` with mode `0700` if it is missing;
|
||||
- creates `~/.ssh/authorized_keys` with mode `0600` if it is missing;
|
||||
- appends the `pub` line only if an identical line is not already there.
|
||||
|
||||
It then prints `added` or `already present`. How this `ssh` connection
|
||||
authenticates is up to the user's normal `ssh` setup (existing keys, agent,
|
||||
password). Anything after `--` is passed to `ssh` unchanged.
|
||||
|
||||
### `keyfunc ssh to <host> [ssh arguments...]`
|
||||
|
||||
Derives the key, serves it from an SSH agent that runs inside the tool on a unix
|
||||
socket in a new private `0700` temporary directory, then runs the system `ssh`
|
||||
with `-o IdentityAgent=<that socket>` followed by the host and all remaining
|
||||
arguments unchanged. The tool exits with `ssh`'s exit status and removes the
|
||||
socket and directory on the way out. The private key is never written to disk.
|
||||
|
||||
## age identities: `keyfunc age` (planned)
|
||||
|
||||
Not in this PR, which delivers the SSH type first; it is the next type to add.
|
||||
`keyfunc age pub` and `keyfunc age priv` will use application number
|
||||
`657169`, path `m/83696968'/657169'/<n>'`; the 32 bytes from step 4 are clamped
|
||||
as X25519 requires and encoded as an `AGE-SECRET-KEY-1...` identity, 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.
|
||||
|
||||
## 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.
|
||||
|
||||
## 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
|
||||
|
||||
```
|
||||
make build # produces ./keyfunc
|
||||
make check # fmt-check, lint (golangci-lint) and tests
|
||||
```
|
||||
|
||||
Examples:
|
||||
|
||||
```
|
||||
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
|
||||
```
|
||||
Reference in New Issue
Block a user