Per sneak: no xprv input. The mnemonic comes from a shell command given as a flag or an environment variable (for example `secret get foo`), from an environment variable holding it, or from a no-echo prompt. The file flag is dropped since the command covers it. Model: fable-5-1
5.2 KiB
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:
- mnemonic -> BIP-39 seed (empty passphrase);
- seed -> BIP-32 master key;
- master key -> 64 bytes of BIP-85 entropy at the path below;
- entropy -> BIP-85 DRNG (SHAKE256); read 32 bytes;
- those 32 bytes become the key in the way the key type needs.
The path is:
m/83696968'/592366788'/<app>'/<n>'
83696968is the fixed BIP-85 purpose.592366788is the vendor id,sha256("berlin.sneak") & 0x7fffffff, the same oneagehduses.appis the application id of the key type; each type has its own.nis the key index: flag--index/-n, default0.
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:
--mnemonic-command <command>: a shell command, run withsh -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.- Environment variable
BIP85KEYS_MNEMONIC_COMMAND: the same, as a shell command held in the environment. - Environment variable
BIP85KEYS_MNEMONIC: the mnemonic itself. - 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 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:
index 0: ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIA1esgfi4OeaywgKh0o5r/8lMOUlUD/N+YoAiC8SNEML
index 1: ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIB26T7hdDuUF6wfTQ7NpIpyeTgGha4NlhQjaAhap5dqs
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
~/.sshwith mode0700if it is missing; - creates
~/.ssh/authorized_keyswith mode0600if it is missing; - appends the
publine 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 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
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 ssh ssh,
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 ssh -n 3 user@example.com uptime