keyfunc: deterministic keys, age encryption and child mnemonics from one mnemonic (spec) #1
@@ -1,11 +1,14 @@
|
||||
# bip85ssh
|
||||
# bip85keys
|
||||
|
||||
`bip85ssh` turns a BIP-39 mnemonic (or a BIP-32 `xprv`) into SSH key pairs that
|
||||
can be recreated from that secret at any time. The same secret and the same
|
||||
index always give the same key. Only ed25519 keys are produced.
|
||||
`bip85keys` turns a BIP-39 mnemonic (or a BIP-32 `xprv`) into key pairs that can
|
||||
be recreated from that secret at any time. The same secret, 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
|
||||
follows the same steps as that repository's `agehd` package.
|
||||
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
|
||||
|
||||
@@ -15,20 +18,44 @@ The secret is turned into a key like this:
|
||||
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 are the ed25519 seed.
|
||||
5. those 32 bytes become the key in the way the key type needs.
|
||||
|
||||
The path is:
|
||||
|
||||
```
|
||||
m/83696968'/592366788'/1822331379'/<n>'
|
||||
m/83696968'/592366788'/<app>'/<n>'
|
||||
```
|
||||
|
||||
- `83696968` is the fixed BIP-85 purpose.
|
||||
- `592366788` is the vendor id, `sha256("berlin.sneak") & 0x7fffffff`, the same
|
||||
one `agehd` uses.
|
||||
- `1822331379` is the application id, `sha256("bip85ssh") & 0x7fffffff`.
|
||||
- `app` is the application id of the key type; each type has its own.
|
||||
- `n` is the key index: flag `--index` / `-n`, default `0`.
|
||||
|
||||
## Giving it the secret
|
||||
|
||||
The secret is never taken as a command-line argument. It is looked for in this
|
||||
order; the first one found wins:
|
||||
|
||||
1. `--mnemonic-file <path>` or `--xprv-file <path>`: the file holds the secret
|
||||
on one line.
|
||||
2. Environment variable `BIP85KEYS_MNEMONIC` or `BIP85KEYS_XPRV`.
|
||||
3. A prompt on the terminal with echo turned off. The prompt accepts either a
|
||||
mnemonic or an `xprv`; a value starting with `xprv` is treated as an `xprv`.
|
||||
|
||||
If none of these is available and standard input is not a terminal, the tool
|
||||
refuses and exits with status 1.
|
||||
|
||||
Every command takes `--index` / `-n` and the secret flags above, and has
|
||||
`--help`. `bip85keys --version` prints the version set at build time.
|
||||
|
||||
## SSH keys: `bip85keys ssh`
|
||||
|
||||
Only ed25519 keys are produced. The application id is `1822331379`,
|
||||
`sha256("bip85ssh") & 0x7fffffff`, so the path is
|
||||
`m/83696968'/592366788'/1822331379'/<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`:
|
||||
|
||||
@@ -37,26 +64,7 @@ index 0: ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIA1esgfi4OeaywgKh0o5r/8lMOUlUD/N+Yo
|
||||
index 1: ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIB26T7hdDuUF6wfTQ7NpIpyeTgGha4NlhQjaAhap5dqs
|
||||
```
|
||||
|
||||
## Giving it the secret
|
||||
|
||||
The secret is never taken as a command-line argument. It is looked for in this
|
||||
order; the first one found wins:
|
||||
|
||||
1. `--mnemonic-file <path>` or `--xprv-file <path>`: the file holds the secret
|
||||
on one line.
|
||||
2. Environment variable `BIP85SSH_MNEMONIC` or `BIP85SSH_XPRV`.
|
||||
3. A prompt on the terminal with echo turned off. The prompt accepts either a
|
||||
mnemonic or an `xprv`; a value starting with `xprv` is treated as an `xprv`.
|
||||
|
||||
If none of these is available and standard input is not a terminal, the tool
|
||||
refuses and exits with status 1.
|
||||
|
||||
## Commands
|
||||
|
||||
Every command takes `--index` / `-n` and the secret flags above, and has
|
||||
`--help`. `bip85ssh --version` prints the version set at build time.
|
||||
|
||||
### `bip85ssh pub`
|
||||
### `bip85keys ssh pub`
|
||||
|
||||
Prints one `authorized_keys` line to standard output:
|
||||
|
||||
@@ -64,16 +72,16 @@ Prints one `authorized_keys` line to standard output:
|
||||
ssh-ed25519 <key> <comment>
|
||||
```
|
||||
|
||||
The comment defaults to `bip85ssh/<n>`; change it with `--comment`.
|
||||
The comment defaults to `bip85keys/ssh/<n>`; change it with `--comment`.
|
||||
|
||||
### `bip85ssh priv`
|
||||
### `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`.
|
||||
|
||||
### `bip85ssh install <[user@]host> [-- ssh options...]`
|
||||
### `bip85keys ssh install <[user@]host> [-- ssh options...]`
|
||||
|
||||
Runs the system `ssh` to the host and, on the host:
|
||||
|
||||
@@ -85,7 +93,7 @@ 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.
|
||||
|
||||
### `bip85ssh ssh <host> [ssh arguments...]`
|
||||
### `bip85keys ssh ssh <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`
|
||||
@@ -93,23 +101,40 @@ 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 derive exactly what
|
||||
`sneak/secret` derives with its `agehd` package: application id `733482323`
|
||||
(`sha256("secret") & 0x7fffffff`), so the path is
|
||||
`m/83696968'/592366788'/733482323'/<n>'`; the 32 bytes from step 4 are clamped
|
||||
as X25519 requires and encoded as an `AGE-SECRET-KEY-1...` identity. An age key
|
||||
from this tool for a given mnemonic and index will equal the one `secret`
|
||||
derives.
|
||||
|
||||
## 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 the `ssh`
|
||||
command, which passes through `ssh`'s own exit status.
|
||||
Errors go to standard error and the exit status is 1, except for `ssh ssh`,
|
||||
which passes through `ssh`'s own exit status.
|
||||
|
||||
## Building and running
|
||||
|
||||
```
|
||||
make build # produces ./bip85ssh
|
||||
make build # produces ./bip85keys
|
||||
make check # fmt-check, lint (golangci-lint) and tests
|
||||
```
|
||||
|
||||
Examples:
|
||||
|
||||
```
|
||||
bip85ssh pub -n 3
|
||||
bip85ssh priv > ~/.ssh/id_bip85_3
|
||||
bip85ssh install -n 3 user@example.com
|
||||
bip85ssh ssh -n 3 user@example.com uptime
|
||||
bip85keys ssh pub -n 3
|
||||
bip85keys ssh priv -n 3 > ~/.ssh/id_bip85_3
|
||||
bip85keys ssh install -n 3 user@example.com
|
||||
bip85keys ssh ssh -n 3 user@example.com uptime
|
||||
```
|
||||
Reference in New Issue
Block a user