forked from sneak/hacks
Per sneak: the path carries no vendor id, since this is meant as a standard others can follow. Application numbers follow BIP-85's own spelling for RSA: SSH is 838372, age is 657169. The ssh subcommand that runs the system ssh is now "to". Test vectors recomputed for the new path. secret's age keys move to this path in a change there. Model: fable-5-1
145 lines
5.4 KiB
Markdown
145 lines
5.4 KiB
Markdown
# bip85keys
|
|
|
|
`bip85keys` 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: `bip85keys ssh ...` for ed25519 SSH keys (in
|
|
this PR) and `bip85keys 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 `BIP85KEYS_MNEMONIC_COMMAND`: the same, as a shell
|
|
command held in the environment.
|
|
3. Environment variable `BIP85KEYS_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`.
|
|
`bip85keys --version` prints the version set at build time.
|
|
|
|
## SSH keys: `bip85keys 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
|
|
```
|
|
|
|
### `bip85keys ssh pub`
|
|
|
|
Prints one `authorized_keys` line to standard output:
|
|
|
|
```
|
|
ssh-ed25519 <key> <comment>
|
|
```
|
|
|
|
The comment defaults to `bip85keys/ssh/<n>`; change it with `--comment`.
|
|
|
|
### `bip85keys 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`.
|
|
|
|
### `bip85keys 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.
|
|
|
|
### `bip85keys 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: `bip85keys age` (planned)
|
|
|
|
Not in this PR, which delivers the SSH type first; it is the next type to add.
|
|
`bip85keys age pub` and `bip85keys 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 ./bip85keys
|
|
make check # fmt-check, lint (golangci-lint) and tests
|
|
```
|
|
|
|
Examples:
|
|
|
|
```
|
|
bip85keys ssh pub -n 3 --mnemonic-command 'secret get foo'
|
|
bip85keys ssh priv -n 3 > ~/.ssh/id_bip85_3
|
|
bip85keys ssh install -n 3 user@example.com
|
|
bip85keys ssh to -n 3 user@example.com uptime
|
|
```
|