|
|
|
@@ -31,8 +31,8 @@ make build
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
`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:
|
|
|
|
|
[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'
|
|
|
|
@@ -105,11 +105,12 @@ 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.
|
|
|
|
|
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.
|
|
|
|
|
|
|
|
|
@@ -119,8 +120,7 @@ refused with a message saying so.
|
|
|
|
|
|
|
|
|
|
`KEYFUNC_MNEMONIC` and `KEYFUNC_MNEMONIC_COMMAND` are removed from the
|
|
|
|
|
environment before the system `ssh` (`keyfunc ssh to`) and `sftp`
|
|
|
|
|
(`keyfunc ssh install`) are started, so the mnemonic is never handed on to
|
|
|
|
|
them.
|
|
|
|
|
(`keyfunc ssh install`) are started, so the mnemonic is never handed on to them.
|
|
|
|
|
|
|
|
|
|
Every command takes `--index` / `-n` and `--mnemonic-command`, and has `--help`.
|
|
|
|
|
`keyfunc --version` prints the version. `make build` stamps it; a binary
|
|
|
|
@@ -128,9 +128,8 @@ installed with `go install` reports the module version instead.
|
|
|
|
|
|
|
|
|
|
## 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.
|
|
|
|
|
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`:
|
|
|
|
@@ -160,24 +159,24 @@ same as for `pub`.
|
|
|
|
|
### `keyfunc ssh install <[user@]host> [-- sftp options...]`
|
|
|
|
|
|
|
|
|
|
Adds the `pub` line to `~/.ssh/authorized_keys` on the host. No command is run
|
|
|
|
|
on the host: the file is fetched, changed here, and written back with the
|
|
|
|
|
system `sftp` client in batch mode.
|
|
|
|
|
on the host: the file is fetched, changed here, and written back with the system
|
|
|
|
|
`sftp` client in batch mode.
|
|
|
|
|
|
|
|
|
|
The first connection lists `~/.ssh` and then fetches
|
|
|
|
|
`~/.ssh/authorized_keys` from it. The file reads as empty in two cases only:
|
|
|
|
|
`sftp` reported `~/.ssh` itself as not being there, or the listing came up and
|
|
|
|
|
the file was not in it. Any other outcome of that connection fails the run — a
|
|
|
|
|
`~/.ssh` that is there but cannot be entered, an `authorized_keys` that is there
|
|
|
|
|
but cannot be read, or a connection that did not come up — and the tool prints
|
|
|
|
|
what `sftp` said and exits with status 1 without writing anything, rather than
|
|
|
|
|
put a file back holding the new key alone. The listing is what tells a missing
|
|
|
|
|
directory from one shut to the user, which `sftp` reports on a fetch the same
|
|
|
|
|
way; the wording of a missing file elsewhere does not count either, since `ssh`
|
|
|
|
|
writes `No such file or directory` about an `-i` it cannot find on a session
|
|
|
|
|
that then authenticates through the agent. If an identical line is already in
|
|
|
|
|
the file, the tool prints `already present` and connects no further. Otherwise
|
|
|
|
|
the line is added (after a newline, if the file did not end with one) and a
|
|
|
|
|
second connection:
|
|
|
|
|
The first connection lists `~/.ssh` and then fetches `~/.ssh/authorized_keys`
|
|
|
|
|
from it. The file reads as empty in two cases only: `sftp` reported `~/.ssh`
|
|
|
|
|
itself as not being there, or the listing came up and the file was not in it.
|
|
|
|
|
Any other outcome of that connection fails the run — a `~/.ssh` that is there
|
|
|
|
|
but cannot be entered, an `authorized_keys` that is there but cannot be read, or
|
|
|
|
|
a connection that did not come up — and the tool prints what `sftp` said and
|
|
|
|
|
exits with status 1 without writing anything, rather than put a file back
|
|
|
|
|
holding the new key alone. The listing is what tells a missing directory from
|
|
|
|
|
one shut to the user, which `sftp` reports on a fetch the same way; the wording
|
|
|
|
|
of a missing file elsewhere does not count either, since `ssh` writes
|
|
|
|
|
`No such file or directory` about an `-i` it cannot find on a session that then
|
|
|
|
|
authenticates through the agent. If an identical line is already in the file,
|
|
|
|
|
the tool prints `already present` and connects no further. Otherwise the line is
|
|
|
|
|
added (after a newline, if the file did not end with one) and a second
|
|
|
|
|
connection:
|
|
|
|
|
|
|
|
|
|
- makes `~/.ssh` and sets it to mode `0700`, but only when the first connection
|
|
|
|
|
found none; a `~/.ssh` that was already there keeps the mode it had;
|
|
|
|
@@ -188,15 +187,14 @@ second connection:
|
|
|
|
|
The tool then prints `added`. So a run that adds a line connects twice. The
|
|
|
|
|
rename is the step that either happens or does not: the file on the host is
|
|
|
|
|
never half-written. `sftp` does it in one step against servers that offer
|
|
|
|
|
OpenSSH's POSIX rename extension, as OpenSSH's own server does; a server
|
|
|
|
|
without it may refuse to rename onto a file that is already there.
|
|
|
|
|
OpenSSH's POSIX rename extension, as OpenSSH's own server does; a server without
|
|
|
|
|
it may refuse to rename onto a file that is already there.
|
|
|
|
|
|
|
|
|
|
If a step fails, the tool prints what `sftp` said, removes nothing, and exits
|
|
|
|
|
with status 1. It names the uploaded file only when the step that failed was
|
|
|
|
|
the upload or one after it, which is where a file of that name can be on the
|
|
|
|
|
host; a failure before the upload names none. Everything `sftp`
|
|
|
|
|
writes goes to standard error, so the tool's own standard output is only
|
|
|
|
|
`added` or `already present`.
|
|
|
|
|
with status 1. It names the uploaded file only when the step that failed was the
|
|
|
|
|
upload or one after it, which is where a file of that name can be on the host; a
|
|
|
|
|
failure before the upload names none. Everything `sftp` writes goes to standard
|
|
|
|
|
error, so the tool's own standard output is only `added` or `already present`.
|
|
|
|
|
|
|
|
|
|
Anything after `--` is passed to `sftp` unchanged, which is where the port goes
|
|
|
|
|
(`-P 2222`, not `-p`). How the connection authenticates is up to the user's
|
|
|
|
@@ -216,10 +214,10 @@ and the tool then exits with status 1 unless `ssh` reported one of its own.
|
|
|
|
|
## age identities: `keyfunc age`
|
|
|
|
|
|
|
|
|
|
The application number is `657169`, path `m/83696968'/657169'/<n>'`. The 32
|
|
|
|
|
bytes from step 4 are clamped as X25519 requires and become an age 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.
|
|
|
|
|
bytes from step 4 are clamped as X25519 requires and become an age 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.
|
|
|
|
|
|
|
|
|
|
Test vectors, mnemonic
|
|
|
|
|
`abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about`:
|
|
|
|
@@ -256,11 +254,11 @@ says so and exits with status 1.
|
|
|
|
|
### `keyfunc mnemonic [-n N] [--words 12|18|24]`
|
|
|
|
|
|
|
|
|
|
Prints a child mnemonic derived from the main one, using BIP-85's own mnemonic
|
|
|
|
|
application (number `39`, English, path
|
|
|
|
|
`m/83696968'/39'/0'/<words>'/<n>'`, entropy taken as the specification says,
|
|
|
|
|
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.
|
|
|
|
|
application (number `39`, English, path `m/83696968'/39'/0'/<words>'/<n>'`,
|
|
|
|
|
entropy taken as the specification says, 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.
|
|
|
|
|
|
|
|
|
|
Test vector: the child-mnemonic step is checked against BIP-85's own published
|
|
|
|
|
vectors, which derive from the specification's master key
|
|
|
|
@@ -273,23 +271,33 @@ 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.
|
|
|
|
|
Errors go to standard error and the exit status is 1, except for `ssh to`, which
|
|
|
|
|
passes through `ssh`'s own exit status.
|
|
|
|
|
|
|
|
|
|
## Entrypoints
|
|
|
|
|
|
|
|
|
|
The repo adheres to the
|
|
|
|
|
[Scripts to Rule Them All](https://github.com/github/scripts-to-rule-them-all)
|
|
|
|
|
standard: most Makefile targets are thin shims over an executable in
|
|
|
|
|
`script/` (`build` and `clean` are the exceptions).
|
|
|
|
|
standard: most Makefile targets are thin shims over an executable in `script/`
|
|
|
|
|
(`build` and `clean` are the exceptions).
|
|
|
|
|
|
|
|
|
|
- `script/bootstrap` installs everything needed to build and develop,
|
|
|
|
|
idempotently: git and make from nix, apt, brew or apk, and Go, at the version
|
|
|
|
|
the `Dockerfile`'s Go image carries, from the official release archive
|
|
|
|
|
(checked against a sha256 in the script) into `~/.local/go`. `script/fmt`,
|
|
|
|
|
- `script/bootstrap` installs, idempotently, everything needed to build and
|
|
|
|
|
develop apart from Docker, which it only warns about when it is missing, and
|
|
|
|
|
the linter, which only runs inside Docker. In this order:
|
|
|
|
|
- git and make from nix, apt, brew or apk, and from there too curl and bash
|
|
|
|
|
when a later step needs them;
|
|
|
|
|
- Go at the version the `Dockerfile`'s Go image carries, from the official
|
|
|
|
|
release archive at go.dev (checked against a sha256 in the script) into
|
|
|
|
|
`~/.local/go`, unless the `go` first on the `PATH` already is that
|
|
|
|
|
version; then the Go modules. `script/bootstrap` itself, `script/fmt`,
|
|
|
|
|
`script/fmt-check`, `script/precommit` and the `Makefile` put
|
|
|
|
|
`~/.local/go/bin` first on their `PATH`, so they use that Go. It does not
|
|
|
|
|
install the linter, which only runs inside Docker.
|
|
|
|
|
`~/.local/go/bin` first on their `PATH`, so they use that Go;
|
|
|
|
|
- node: an installed one is used as it is, otherwise a pinned one is
|
|
|
|
|
installed through nvm, which, when it is missing, comes from its release
|
|
|
|
|
archive, checked against a sha256;
|
|
|
|
|
- yarn: an installed one is used as it is, otherwise the pinned version
|
|
|
|
|
through corepack, or through npm where there is no corepack;
|
|
|
|
|
- the pinned prettier, through yarn.
|
|
|
|
|
- `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
|
|
|
|
@@ -300,9 +308,11 @@ standard: most Makefile targets are thin shims over an executable in
|
|
|
|
|
- `script/lint` builds the `lint` phase of the `Dockerfile` alone, uncached: the
|
|
|
|
|
linter, pinned by hash, runs inside the build, so a complaint fails it 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/fmt` formats in place: the Go source with `go fmt`, then every
|
|
|
|
|
Markdown file with prettier (four-space indents, prose wrapped at 80 columns).
|
|
|
|
|
- `script/fmt-check` checks the same files the same way without writing, failing
|
|
|
|
|
if anything is unformatted. Both need the node, yarn and prettier that
|
|
|
|
|
`bootstrap` installs.
|
|
|
|
|
- `script/check` runs `test`, `lint` and `fmt-check` and changes no files.
|
|
|
|
|
- `script/docker` builds the Docker image, uncached, tagged with the project
|
|
|
|
|
name and stamped with the version `git describe` gives on the host. The image
|
|
|
|
@@ -319,7 +329,6 @@ standard: most Makefile targets are thin shims over an executable in
|
|
|
|
|
|
|
|
|
|
The open issues that stand between the tree and a 1.0 release:
|
|
|
|
|
|
|
|
|
|
- [#39 make fmt and make fmt-check cover Markdown with prettier](https://git.eeqj.de/sneak/keyfunc/issues/39)
|
|
|
|
|
- [#42 go-bip39 no longer exists upstream: keep it, or copy it into the repo?](https://git.eeqj.de/sneak/keyfunc/issues/42)
|
|
|
|
|
|
|
|
|
|
## License
|
|
|
|
|