Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/woodruffw/kbs2

A secret manager backed by age
https://github.com/woodruffw/kbs2

age hacktoberfest password-manager rust secret-managers

Last synced: 4 days ago
JSON representation

A secret manager backed by age

Awesome Lists containing this project

README

        

kbs2
====

[![CI](https://github.com/woodruffw/kbs2/actions/workflows/ci.yml/badge.svg)](https://github.com/woodruffw/kbs2/actions/workflows/ci.yml)
[![Crates.io](https://img.shields.io/crates/v/kbs2)](https://crates.io/crates/kbs2)
[![Packaging status](https://repology.org/badge/tiny-repos/kbs2.svg)](https://repology.org/project/kbs2/versions)

**Warning! `kbs2` is beta-quality software! Using `kbs2` means accepting that your secrets may be
lost or compromised at any time!**

`kbs2` is a command line utility for managing *secrets*.

Quick links:

* [Installation](#installation)
* [Quick start guide](#quick-start-guide)
* [CLI documentation](#cli-documentation)
* [`kbs2 init`](#kbs2-init)
* [`kbs2 new`](#kbs2-new)
* [`kbs2 list`](#kbs2-list)
* [`kbs2 rm`](#kbs2-rm)
* [`kbs2 rename`](#kbs2-rename)
* [`kbs2 dump`](#kbs2-dump)
* [`kbs2 pass`](#kbs2-pass)
* [`kbs2 env`](#kbs2-env)
* [`kbs2 edit`](#kbs2-edit)
* [`kbs2 generate`](#kbs2-generate)
* [`kbs2 agent`](#kbs2-agent)
* [`kbs2 agent flush`](#kbs2-agent-flush)
* [`kbs2 agent unwrap`](#kbs2-agent-unwrap)
* [`kbs2 rewrap`](#kbs2-rewrap)
* [`kbs2 rekey`](#kbs2-rekey)
* [`kbs2 config`](#kbs2-config)
* [`kbs2 config dump`](#kbs2-config-dump)
* [Configuration](#configuration)
* [Generators](#generators)
* [Customization](#customization)
* [Custom commands](#custom-commands)
* [Hooks](#hooks)
* [Managing your key and master password](#managing-your-key-and-master-password)
* [Why another password manager?](#why-another-password-manager)
* [Technical details](#technical-details)
* [Hacking](#hacking)
* [History](#history)

## Installation

### Packages

`kbs2` is available via a variety of official and community-supplied packages.

See the matrix below for a list of repositories containing `kbs2`.

[![Packaging status](https://repology.org/badge/vertical-allrepos/kbs2.svg)](https://repology.org/project/kbs2/versions)

**These packages are the recommended way to install `kbs2` if you are not developing it.**

#### Debian/Ubuntu

*This is an official package.*

If you're running a Debian or Ubuntu distribution on AMD64, you can use the `.deb` packages
attached to the [latest release](https://github.com/woodruffw/kbs2/releases/latest).

By way of example:

```console
$ wget https://github.com/woodruffw/kbs2/releases/download/v0.7.2/kbs2_0.7.2_amd64.deb
$ sudo dpkg -i kbs2_0.7.2_amd64.deb
# don't forget to request kbs2's dependencies
$ sudo apt-get -f install
```

#### Arch Linux

*This is a community-maintained package.*

`kbs2` can be installed from available
[AUR packages](https://aur.archlinux.org/packages/?O=0&SeB=b&K=kbs2&outdated=&SB=n&SO=a&PP=50&do_Search=Go)
using an [AUR helper](https://wiki.archlinux.org/index.php/AUR_helpers). For example,

```console
$ yay -S kbs2
```

Other distributions will be supported sooner or later. Help us by looking at the
[open packaging issues](https://github.com/woodruffw/kbs2/labels/C%3Apackaging)!

#### Nix

*This is a community-maintained package.*

`kbs2` can be installed through Nix:

```console
$ nix-env -iA nixpkgs.kbs2
```

#### Cargo

If you're a Linux user, you'll need some X11 libraries. For Debian-based distributions:

```console
$ sudo apt install -y libxcb-shape0-dev libxcb-xfixes0-dev
```

`kbs2` itself is most easily installed via `cargo`:

```console
$ cargo install kbs2
```

After installation, `kbs2` is completely ready for use. See the
[Configuration](#configuration) section for some *optional* changes that you can
make.

## Quick start guide

Initialize a new `kbs2` configuration:

```console
$ kbs2 init
```

By default, a fresh `kbs2` configuration will store records in `$HOME/.local/share/kbs2`. Users
can override this by passing `--store-dir DIR` to `kbs2 init`, or at any point by modifying
`store` in the config itself.

`kbs2 init` will automatically generate a configuration file and keypair, prompting you for
a "master" password.

**Note**: By default, most `kbs2` commands will start the authentication agent (`kbs2 agent`)
in the background if it isn't already running.

Create a new (login) record:

```console
$ kbs2 new amazon
? Username? jonf-bonzo
? Password? [hidden]
```

List available records:

```console
$ kbs2 list
amazon
facebook
```

Pull the password from a record:

```console
$ kbs2 pass -c amazon
# alternatively, pipeline it
$ kbs2 pass facebook | pbcopy
```

Remove a record:

```console
$ kbs2 rm facebook
```

`kbs2`'s subcommands are substantially more featured than the above examples demonstrate;
run each with `--help` to see a full set of supported options.

## CLI documentation

### `kbs2 init`

#### Usage

```
initialize kbs2 with a new config and keypair

USAGE:
kbs2 init [FLAGS] [OPTIONS]

FLAGS:
-f, --force overwrite the config and keyfile, if already present
-h, --help Prints help information
--insecure-not-wrapped don't wrap the keypair with a master password

OPTIONS:
-s, --store-dir the directory to store encrypted kbs2 records in
[default: $HOME/.local/share/kbs2]
```

#### Examples

Create a new config and keypair, prompting the user for a master password:

```console
$ kbs2 init
```

Create a new config and keypair **without** a master password:

```console
$ kbs2 init --insecure-not-wrapped
```

Create a new config and keypair in a different location:

```console
$ kbs2 -c /some/config/dir init
```

Create a new config keypair in a different location and specify a non-default store:

```console
$ kbs2 -c /home/config/dir init --store-dir /some/store/dir
```

### `kbs2 new`

#### Usage

```
create a new record

USAGE:
kbs2 new [FLAGS] [OPTIONS]

ARGS:
the record's label

FLAGS:
-f, --force overwrite, if already present
-h, --help Prints help information
-t, --terse read fields in a terse format, even when connected to a tty

OPTIONS:
-G, --generator use the given generator to generate sensitive fields
[default: default]
-k, --kind the kind of record to create [default: login]
[possible values: login, environment, unstructured]
```

#### Examples

Create a new `login` record named `foobar`:

```console
$ kbs2 new foobar
? Username? hasdrubal
? Password? **********
```

Create a new `environment` record named `twitter-api`, overwriting it if it already exists:

```console
$ kbs2 new -f -k environment twitter-api
? Variable? TWITTER_API
? Value? [hidden]
[Press [enter] to auto-generate]
```

Create a new `login` record named `pets.com`, generating the password with the default generator:

```console
$ kbs2 new pets.com
? Username? catlover1312
? Password?
[Press [enter] to auto-generate]
```

Entering nothing in the password prompt will cause `kbs2` to generate a password
using the `"default"` generator. You can use the `--generator` option to specify
a different generator, if you have another one configured.

Create a new `login` record named `email`, getting the fields in a terse format:

```console
$ kbs2 new -t email < <(echo -e "[email protected]\x01hunter2")
```

When in "terse" mode, `kbs2` expects fields to be separated by `\x01` (ASCII SOH)
characters.

### `kbs2 list`

#### Usage

```
list records

USAGE:
kbs2 list [FLAGS] [OPTIONS]

FLAGS:
-d, --details print (non-field) details for each record
-h, --help Prints help information

OPTIONS:
-k, --kind list only records of this kind
[possible values: login, environment, unstructured]
```

#### Examples

List all records, one per line:

```console
$ kbs2 list
foobar
twitter-api
pets.com
email
```

List (non-sensitive) details for each record. The format of the detailed listing is
`{record} {kind} {timestamp}`.

```console
$ kbs2 list -d
foobar login 1590277900
twitter-api environment 1590277907
pets.com login 1590277920
email login 1590277953
```

List only environment records:

```console
$ kbs2 list -k environment
twitter-api
```

### `kbs2 rm`

#### Usage

```
remove one or more records

USAGE:
kbs2 rm ...

ARGS:
... the labels of the records to remove

FLAGS:
-h, --help Prints help information
```

#### Examples

Remove the `foobar` record:

```console
$ kbs2 rm foobar
```

### `kbs2 rename`

#### Usage

```
rename a record

Usage: kbs2 rename [OPTIONS]

Arguments:
the record's current label
the new record label

Options:
-f, --force overwrite, if already present
-h, --help Print help
```

#### Examples

Rename the `foo` record to `bar`:

```console
$ kbs2 rename foo bar
```

Rename `foo` to `bar`, even if `bar` already exists:

```console
$ kbs2 rename --force foo bar
```

### `kbs2 dump`

#### Usage

```
dump one or more records

USAGE:
kbs2 dump [FLAGS] ...

ARGS:
... the labels of the records to dump

FLAGS:
-h, --help Prints help information
-j, --json dump in JSON format (JSONL when multiple)
```

#### Examples

Dump the `twitter-api` record:

```console
$ kbs2 dump twitter-api
Label twitter-api
Kind environment
Variable TWITTER_API
Value 92h2890fn83fb2378fbf283bf73fbxkfnso90
```

Dump the `pets.com` record in JSON format:

```console
$ kbs2 dump -j pets.com | json_pp
{
"timestamp" : 1590363392,
"label" : "pets.com",
"body" : {
"fields" : {
"username" : "hasdrubal",
"password" : "hunter2"
},
"kind" : "Login"
}
}
```

Dump multiple records, demonstrating JSONL:

```console
$ kbs2 dump -j carthage roma
{"timestamp":1590363392,"label":"bepis","body":{"kind":"Login","fields":{"username":"hamilcar","password":"ihatecato"}}}
{"timestamp":1590363392,"label":"conk","body":{"kind":"Login","fields":{"username":"cato","password":"carthagodelendaest"}}}
```

### `kbs2 pass`

#### Usage

```
get the password in a login record

USAGE:
kbs2 pass [FLAGS]

ARGS:
the record's label

FLAGS:
-c, --clipboard copy the password to the clipboard
-h, --help Prints help information
```

#### Examples

Get the password for the `pets.com` record:

```console
$ kbs2 pass pets.com
hunter2
```

Copy the password for the `pets.com` record into the clipboard:

```console
$ kbs2 pass -c pets.com
```

### `kbs2 env`

#### Usage

```
get an environment record

USAGE:
kbs2 env [FLAGS]

ARGS:
the record's label

FLAGS:
-h, --help Prints help information
-n, --no-export print only VAR=val without `export`
-v, --value-only print only the environment variable value, not the variable name
```

#### Examples

Get an environment record in `export`-able form:

```console
$ kbs2 env twitter-api
export TWITTER_API=92h2890fn83fb2378fbf283bf73fbxkfnso90
```

Get just the value in an environment record:

```console
$ kbs2 env -v twitter-api
92h2890fn83fb2378fbf283bf73fbxkfnso90
```

### `kbs2 edit`

#### Usage

```
modify a record with a text editor

USAGE:
kbs2 edit [FLAGS]

ARGS:
the record's label

FLAGS:
-h, --help Prints help information
-p, --preserve-timestamp don't update the record's timestamp
```

#### Examples

Open the `email` record for editing:

```console
$ kbs2 edit email
```

Open the `email` record for editing with a custom `$EDITOR`:

```console
$ EDITOR=vim kbs2 edit email
```

### `kbs2 generate`

#### Usage

```
generate secret values using a generator

USAGE:
kbs2 generate [generator]

ARGS:
the generator to use [default: default]

FLAGS:
-h, --help Prints help information
```

#### Examples

Generate a secret using the default generator:

```console
$ kbs2 generate
rrayxfky-81x=h6i
```

Generate a secret using a generator named `pwgen`:

```console
$ kbs2 generate pwgen
iit4wie6faeL4aiyupheec5Xochosero
```

### `kbs2 agent`

#### Usage

```
run the kbs2 authentication agent

USAGE:
kbs2 agent [FLAGS] [SUBCOMMAND]

FLAGS:
-F, --foreground run the agent in the foreground
-h, --help Prints help information

SUBCOMMANDS:
flush remove all unwrapped keys from the running agent
help Prints this message or the help of the given subcommand(s)
unwrap unwrap the current config's key in the running agent
```

#### Examples

Run the `kbs2` agent in the background, prompting the user to unwrap the current config's key:

```console
$ kbs2 agent
```

Run the `kbs2` agent in the foreground, for debugging purposes:

```console
$ RUST_LOG=debug kbs2 agent --foreground
```

### `kbs2 agent flush`

#### Usage

```
remove all unwrapped keys from the running agent

USAGE:
kbs2 agent flush [FLAGS]

FLAGS:
-h, --help Prints help information
-q, --quit quit the agent after flushing
```

#### Examples

Remove all keys from the current `kbs2` agent:

```console
$ kbs2 agent flush
```

### `kbs2 agent query`

#### Usage

```
ask the current agent whether it has the current config's key

USAGE:
kbs2 agent query

FLAGS:
-h, --help Prints help information
-V, --version Prints version information
```

`kbs2 agent query` exits with a few discrete codes to signal the query status:

* `0`: query succeeded, agent is running and has a keypair for the config's public key
* `1`: query failed, agent is running but does not have the queried keypair
* `2`: query failed, agent is running but the keypair isn't managed by the agent
(i.e., it's an unwrapped keypair)
* `3`: query failed, agent is not running

All other error codes should be treated as an unspecified error that prevented a query.

#### Examples

Query the agent for the current config:

```console
$ kbs2 agent query && echo "success" || echo "failure"
```

Query the agent for another config's keypair:

```console
$ kbs2 -c /some/other/config agent query
```

### `kbs2 agent unwrap`

#### Usage

```
unwrap the current config's key in the running agent

USAGE:
kbs2 agent unwrap

FLAGS:
-h, --help Prints help information
```

#### Examples

Add the current config's key to the `kbs2` agent:

```console
$ kbs2 agent unwrap
```

Add a custom config's key to the `kbs2` agent:

```console
$ kbs2 -c /path/to/config/dir agent unwrap
```

### `kbs2 rewrap`

#### Usage

```
change the master password on a wrapped key

USAGE:
kbs2 rewrap [FLAGS]

FLAGS:
-f, --force overwrite a previous backup, if one exists
-h, --help Prints help information
-n, --no-backup don't make a backup of the old wrapped key
```

#### Examples

Change the password on the wrapped key in the default config:

```console
$ kbs2 rewrap
```

Change the password on a wrapped key in another config:

```console
$ kbs2 -c /path/to/config/dir rewrap
```

Change the password on a wrapped key without making a backup of the old wrapped key:

```console
$ kbs2 rewrap -n
```

### `kbs2 rekey`

#### Usage

```
re-encrypt the entire store with a new keypair and master password

USAGE:
kbs2 rekey [FLAGS]

FLAGS:
-h, --help Prints help information
-n, --no-backup don't make a backup of the old wrapped key, config, or store
```

#### Examples

Re-key the default config and its store:

```console
$ kbs2 rekey
```

Re-key without making backups of the original keyfile, config, and store (**not** recommended):

```console
$ kbs2 rekey --no-backup
```

Re-key a different configuration and store:

```console
$ kbs2 -c /some/other/kbs2/conf/dir rekey
```

### `kbs2 config`

#### Usage

```
interact with kbs2's configuration file

USAGE:
kbs2 config

OPTIONS:
-h, --help Print help information

SUBCOMMANDS:
dump dump the active configuration file as JSON
help Print this message or the help of the given subcommand(s)
```

### `kbs2 config dump`

#### Usage

```
dump the active configuration file as JSON

USAGE:
kbs2 config dump [OPTIONS]

OPTIONS:
-h, --help Print help information
-p, --pretty pretty-print the JSON
```

#### Examples

Dump the current configuration as JSON:

```console
$ kbs2 config dump

# pretty-print the dumped JSON
$ kbs2 config dump --pretty
```

## Configuration

`kbs2` stores its configuration in `/kbs2/config.toml`, where `` is determined
by the the [XDG basedir specification](https://wiki.freedesktop.org/www/Specifications/basedir-spec/).
On Linux, it's probably `~/.config/kbs2`.

**NOTE**: If `config.toml` isn't found in a configuration directory, `kbs2` attempts to use
`kbs2.conf` in the same directory. This is for backwards compatibility, and will be removed
once `kbs2` has its first stable release.

`config.toml` is TOML-formatted, and might look something like this after a clean start with `kbs2 init`:

```toml
public-key = "age1elujxyndwy0n9j2e2elmk9ns8vtltg69q620dr0sz4nu5fgj95xsl2peea"
keyfile = "/home/william/.config/kbs2/key"
store = "/home/william/.local/share/kbs2"

[commands.pass]
clipboard-duration = 10
clear-after = true
```

### `public-key` (default: generated by `kbs2 init`)

The `public-key` setting records the public half of the age keypair used by `kbs2`.

`kbs2 init` pre-populates this setting; users should **not** modify it **unless** also modifying
the `keyfile` setting (e.g., to point to a pre-existing age keypair).

### `keyfile` (default: generated by `kbs2 init`)

The `keyfile` setting records the path to the private half of the age keypair used by `kbs2`.

`kbs2 init` pre-populates this setting; users should **not** modify it **unless** also modifying
the `public-key` setting (e.g., to point to a pre-existing age keypair).

### `agent-autostart` (default: `true`)

The `agent-autostart` setting controls whether or not `kbs2` attempts to auto-start the
authentication agent (`kbs2 agent`) whenever encryption or decryption operations are requested.
By default, `kbs2 agent` will be started (unless it's already running).

When set to `false`, `kbs2` will report an error if `kbs2 agent` is not running. In this case,
users should configure their system to launch `kbs2 agent` at login (or some other convenient time).

### `wrapped` (default: `true`)

The `wrapped` settings records whether `keyfile` is a "wrapped" private key, i.e. whether
the private key itself is encrypted with a master password.

By default, `kbs2 init` asks the user for a master password and creates a wrapped key.
See the [`kbs2 init`](#kbs2-init) documentation for more information.

### `store` (default: `$HOME/.local/share/kbs2`)

The `store` setting records the path to the secret store, i.e. where records are kept.

Users may modify this setting to store their records in a custom directory.

### `pinentry` (default: `"pinentry"`)

The `pinentry` setting specifies the
[Pinentry](https://gnupg.org/related_software/pinentry/index.html) binary to use for passphrase
operations (i.e., prompting the user for their master password).

`pinentry` is a reasonable default for most systems; macOS users may wish to use
[`pinentry-mac`](https://github.com/GPGTools/pinentry-mac) instead.

### `pre-hook` (default: `None`)

The `pre-hook` setting can be used to run a command before (almost) every `kbs2` invocation.

There are currently three cases where the configured `pre-hook` will *not* run:

* `kbs2` (i.e., no subcommand)
* `kbs2 agent` (and all `kbs2 agent` subcommands)
* `kbs2 init`

All other subcommands, including custom subcommands, will cause the configured `pre-hook` to run.

Read the [Hooks](#hooks) documentation for more details.

### `post-hook` (default: `None`)

The `post-hook` setting can be used to run a command after (almost) every `kbs2` invocation,
*on success*.

There are currently three cases where the configured `post-hook` will *not* run:

* `kbs2` (i.e., no subcommand)
* `kbs2 agent` (and all `kbs2 agent` subcommands)
* `kbs2 init`

All other subcommands, including custom subcommands, will cause the configured `post-hook` to run.

Read the [Hooks](#hooks) documentation for more details.

### `error-hook` (default: `None`)

The `error-hook` setting can be used to run a command after (almost) every `kbs2` invocation,
*on failure*.

There are currently three cases where the configured `error-hook` will *not* run:

* `kbs2` (i.e., no subcommand)
* `kbs2 agent` (and all `kbs2 agent` subcommands)
* `kbs2 init`

All other subcommands, including custom subcommands, will cause the configured `error-hook` to run.

The `error-hook` setting passes a single argument to its hook, which is a string representation
of the error that occurred.

Read the [Hooks](#hooks) documentation for more details.

### `reentrant-hooks` (default: `false`)

The `reentrant-hooks` setting controls whether hooks are run multiple times when a hook itself
runs `kbs2`. By default, hooks are run only for the initial `kbs2` invocation.

Read the [Reentrancy section](#reentrancy) of the [Hooks](#hooks) documentation for more details.

### `commands.new.default-username` (default: `None`)

The `commands.new.default-username` setting allows the user to specify a default
username for logins created with `kbs2 new`.

When specified, `kbs2 new`'s username prompt will fill in the default when the user presses
only `[enter]`.

### `commands.new.pre-hook` (default: `None`)

The `commands.new.pre-hook` setting is like the global `pre-hook` setting, except that it runs
immediately before record creation during `kbs2 new` (and **only** `kbs2 new`).

### `commands.new.post-hook` (default: `None`)

The `commands.new.post-hook` setting is like the global `post-hook` setting, except that it runs
immediately after record creation during `kbs2 new` (and **only** `kbs2 new`).

The `commands.new.post-hook` setting passes a single argument to its hook, which is the label
of the record that was just created. For example, the following:

```toml
[commands.new]
post-hook = "~/.config/kbs2/hooks/post-new.sh"
```

```bash
# ~/.config/kbs2/hooks/post-new.sh

>&2 echo "[+] created ${1}"
```

would produce:

```console
$ kbs2 new foo
? Username? bar
? Password? **********
[+] created foo
```

### `commands.pass.clipboard-duration` (default: `10`)

The `commands.pass.clipboard-duration` setting determines the duration, in seconds, for persisting
a password stored in the clipboard via `kbs2 pass -c`.

### `commands.pass.clear-after` (default: `true`)

The `commands.pass.clear-after` setting determines whether or not the clipboard is cleared at
all after `kbs2 pass -c`.

Setting this to `false` overrides any duration configured in `commands.pass.clipboard-duration`.

### `commands.pass.pre-hook` (default: `None`)

The `command.pass.pre-hook` setting is like the global `pre-hook` setting, except that it runs
immediately before record access during `kbs2 pass` (and **only** `kbs2 pass`).

### `command.pass.post-hook` (default: `None`)

The `command.pass.post-hook` setting is like the global `post-hook` setting, except that it runs
immediately after record access during `kbs2 pass` (and **only** `kbs2 pass`).

### `command.pass.clear-hook` (default: `None`)

The `command.pass.clear-hook` is like the other `command.pass` hooks, except that it only runs
after the password has been cleared from the clipboard.

### `commands.edit.editor` (default: `None`)

The `commands.edit.editor` setting controls which editor is used when opening a file with
`kbs2 edit`. This setting takes precedence over the `$EDITOR` environment variable, which is
used as a fallback.

This setting is allowed to contain flags. For example, the following would be split correctly:

```toml
[commands.edit]
editor = "subl -w"
```

### `commands.edit.post-hook` (default: `None`)

The `command.edit.post-hook` setting is like the global `post-hook` setting, except that it runs
immediately after record editing during `kbs2 edit` (and **only** `kbs2 edit`).

### `commands.rm.post-hook` (default: `None`)

The `command.rm.post-hook` setting is like the global `post-hook` setting, except that it runs
immediately after record removal during `kbs2 rm` (and **only** `kbs2 rm`).

The label of each record removed by `kbs2 rm` is passed as a separate argument to
the `post-hook`.

### `commands.rename.post-hook` (default: `None`)

The `command.rename.post-hook` setting is like the global `post-hook` setting, except that it runs
immediately after record removal during `kbs2 rename` (and **only** `kbs2 rename`).

The record's old and new names are passed as separate arguments to the `post-hook`,
in that order.

### Generators

`kbs2` supports *generators* for producing sensitive values, allowing users to automatically
generate passwords and environment variables.

Generators are configured as entries in `[[generators]]`.

The following configures an generator named "hexonly" that generates a secret from the
configured alphabet and length.

```toml
[[generators]]
name = "hexonly"
alphabets = ["0123456789abcdef"]
length = 16
```

By default, `kbs2`'s configuration includes a `default` generator that looks
something like this:

```toml
[[generators]]
name = "default"
# kbs2 samples from each alphabet, to ensure a good distribution of symbols
alphabets = [
"abcdefghijklmnopqrstuvwxyz",
"ABCDEFGHIJKLMNOPQRSTUVWXYZ",
"0123456789",
"(){}[]-_+=",
]
length = 16
```

These generators can be used with `kbs2 new`. For example, the following will
use the `hexonly` generator when the user presses `[enter]` instead of manually
entering a password.

```console
$ kbs2 new -G hexonly pets.com
? Username? catlover2000
? Password?
[Press [enter] to auto-generate]
```

## Customization

Beyond the configuration above, `kbs2` offers several avenues for customization.

### Custom commands

`kbs2` supports `git`-style subcommands, allowing you to easily write your own.

For example, running the following:

```
$ kbs2 frobulate --xyz
```

will cause `kbs2` to run `kbs2-frobulate --xyz`. Custom commands are allowed to read from and
write to the config file under the `[commands.ext.]` hierarchy.

When run via `kbs2`, custom commands receive the following environment variables:

* `KBS2_CONFIG_DIR`: The path to the configuration directory that `kbs2` itself was loaded with.
Subcommands can use this path to read the current configuration file or any other content stored
in the configuration directory.
* **NOTE**: Subcommands are encouraged to use `kbs2 config dump` to read the configuration
state instead of attempting to find the correct file manually.
* `KBS2_STORE`: The path to the secret store.
* `KBS2_SUBCOMMAND`: Always set to `1`. This can be used to determine whether a subcommand was run
via `kbs2` (e.g. `kbs2 foo`) versus directly (e.g. `kbs2-foo`).
* `KBS2_MAJOR_VERSION`, `KBS2_MINOR_VERSION`, `KBS2_PATCH_VERSION`: The major, minor, and patch
numbers for the version of `kbs2` that executed this subcommand. Subcommands can use these numbers
to enforce running under a minimum (or maximum) version of `kbs2`.

The [contrib/ext-cmds](contrib/ext-cmds/) directory contains several useful external commands.

### Hooks

`kbs2` exposes hook-points during the lifecycle of an invocation, allowing users to
inject additional functionality or perform their own bookkeeping.

#### The hook API

All hooks, whether pre- or post-, have the following behavior:

* Hooks **do not** inherit `stdin` or `stdout` from the parent `kbs2` process
* Hooks **do** inherit `stderr` from the parent process, and *may* use it to print anything
they please
* Hooks **always** run from the `store` directory
* Hooks are run with `KBS2_HOOK=1` in their environment and with `KBS2_CONFIG_DIR` set to the
configuration directory that the original `kbs2` command was loaded with
* An error exit from a hook (or failure to execute) causes the entire `kbs2` command to fail

Hooks *may* introduce additional behavior, so long as it does not conflict with the above.
Any additional hook behavior is documented under that hook's configuration setting.

#### Reentrancy

`kbs2`'s hooks are non-reentrant by default.

To understand what that means, imagine the following hook setup:

```toml
pre-hook = "~/.config/kbs2/hooks/pre.sh"
```

```bash
# ~/.config/kbs2/hooks/pre.sh

kbs2 some-other-command
```

and then:

```console
$ kbs2 list
```

In this setting, most users would expect `pre.sh` to be run exactly once: on `kbs2 list`.

However, naively, it *ought* to execute twice: once for `kbs2 list`, and again for
`kbs2 some-other-command`. In other words, naively, hooks would *reenter* themselves whenever
they use `kbs2` internally.

Most users find this confusing and would consider it an impediment to hook writing, so `kbs2`
does **not** do this by default. However, **should** you wish for reentrant hooks, you have two
options:

* You can set `reentrant-hooks` to `true` in the configuration. This will make *all* hooks
reentrant — it's all or nothing, intentionally.
* You can `unset` or otherwise delete the `KBS2_HOOK` environment variable in your hook
before running `kbs2` internally. This allows you to control which hooks cause reentrancy.
**Beware**: `KBS2_HOOK` is an implementation detail! Unset it at your own risk!

### Managing your key and master password

#### Rewrapping and rekeying

`kbs2` supports two basic options for managing the (wrapped) key that encrypts all records
in the secret store: *rewrapping* and *rekeying*.

*Rewrapping* means changing the password on your wrapped key. Rewrapping **does not**
modify the underlying key itself, which means that your individual records in the store
**do not** change. Rewrapping is done with the [`kbs2 rewrap`](#kbs2-rewrap) command.

You **should** rewrap under the following (non-exhaustive) conditions:

* You're doing a routine update of your master password
* You believe that your master password has been disclosed, but **not** the underlying wrapped key

*Rekeying* means changing the wrapped key itself, and consequently re-encrypting every record
with the new wrapped key. When rekeying you *can* choose the same master password as the old key.
However, you *should* choose a new password. **Unlike** rewrapping, rekeying **does** change
the individual records in your store, and makes them no longer decryptable with your previous
key. Rekeying is done with the [`kbs2 rekey`](#kbs2-rekey) command.

You **should** rekey under the following (non-exhaustive) conditions:

* You believe that your underlying wrapped key has been disclosed
* You're sharing a `kbs2` to a new device, and you'd like that device to have its own wrapped key

Rekeying is a more drastic operation than rewrapping: it involves rewriting the keypair,
the `kbs2` config, and every record in the store. This means it comes with some technical caveats:

* `kbs2 rekey` does not preserve the layout of your config file. Users should be mindful of this
when rekeying.

* `kbs2 rekey` makes a backup of the secret store by copying each record in the store to a
backup folder. Anything in the secret store that is not a record
(like a metadata or revision control directory, or a hidden file) is **not** copied during backup.
Rekeying causes `kbs2` to write the newly encrypted records into the same store, so any non-record
members of the store will remain unmodified.

## Why another password manager?

No good reason. See the [history section](#history).

## Technical details

### Threat model

`kbs2`'s threat model is similar to that of most password and secret managers. In particular:

* `kbs2` does *not* attempt to defend against the `root` user *or* arbitrary code executed by the
current user.
* `kbs2` tries to avoid operations that would result in secret material (i.e. the private key
and the decrypted contents of records) being saved or cached on disk, but does *not* attempt to
present the consumers of secret material from doing so.
* `kbs2`, by default, attempts to prevent offline private key extraction by encrypting the private
key at rest with a master password. `kbs2` does *not* attempt to prevent the user from mishandling
their master password.

### Cryptography

`kbs2` does **not** implement any cryptography on its own — it uses *only* the cryptographic
primitives supplied by an [age](https://github.com/FiloSottile/age) implementation. In particular,
`kbs2` uses the [rage implementation](https://github.com/str4d/rage) of age.

The particulars of `kbs2`'s cryptographic usage are as follows:

* Every `kbs2` configuration file specifies a symmetric keypair. The public key is
stored in the `public-key` configuration setting, while the private key is stored in the file
referenced by the `keyfile` setting.
* By default, `kbs2` "wraps" (i.e. encrypts) the private key with a master password. This makes
offline key extraction attacks more difficult (although not impossible) and makes the consequences
of wrapped private key disclosure less severe. Users *may* choose to use a non-wrapped key by
passing `--insecure-not-wrapped` to `kbs2 init`.

### Key unwrapping and persistence

As mentioned under [Threat Model](#threat-model) and [Cryptography](#cryptography), `kbs2` uses
a wrapped private key by default.

Without any persistence, wrapped key usage would be tedious: the user would have to re-enter
their master password on each `kbs2` action, defeating the point of having a secret manager.

To avoid this, `kbs2` establishes persistence of the unwrapped key with an authentication agent:
running `kbs2 agent` will start a daemon in the background, which subsequent `kbs2` invocations
can connect to (as needed) via a Unix domain socket. By default, running `kbs2 agent`
will prompt the user for the currently configured key's master password. Users can add additional
unwrapped keys to their running agent by invoking [`kbs2 agent unwrap`](#kbs2-agent-unwrap).

## Hacking

Hacking on `kbs2` is relatively straightforward. To build a fully functional development copy,
just use `cargo build` in the repository root:

```console
$ cargo build
$ ./target/debug/kbs2 --help
```

Of note: some functionality in the age crate has pathological performance in debug builds.
In particular, decryption and key unwrapping are known to be particularly slow.

To avoid this, use a release build:

```console
$ cargo build --release
$ ./target/release/kbs2 --help
```

### Logging

`kbs2` uses `log` and `env_logger` for logging. You can past `RUST_LOG=debug` in your environment
to enable debug logging:

```console
$ RUST_LOG=debug ./target/release/kbs2 list -k login
```

See the [`env_logger` documentation](https://docs.rs/env_logger/) for more possible `RUST_LOG` values.

## History

TL;DR: `kbs2` is short for "[KBSecret](https://github.com/kbsecret/kbsecret) 2".

In 2017, I wrote KBSecret as a general purpose secret manager for the Keybase ecosystem.

KBSecret was written in Ruby and piggybacked off of Keybase + KBFS for encryption, storage,
and synchronization. It was also *extremely* flexible, allowing user-defined record types, secret
sharing between users and teams, and a variety of convenient and well-behaved CLI tools for
integration into my development ecosystem.

Unfortunately, KBSecret was also *extremely* slow: it was written in
[obnoxiously metaprogrammed Ruby](https://github.com/kbsecret/kbsecret/blob/20ac2bf/lib/kbsecret/config.rb#L175),
relied heavily on re-entrant CLIs, and was further capped by the latency and raw performance of KBFS
itself.

Having a slow secret manager was fine for my purposes, but I
[no longer trust](https://keybase.io/blog/keybase-joins-zoom) that Keybase (and KBFS) will continue
to receive the work they require. I also no longer have the time to maintain KBSecret's (slowly)
deteriorating codebase.

`kbs2` is my attempt to reproduce the best parts of KBSecret in a faster language. Apart from the
name and some high-level design decisions, it shares nothing in common with the original KBSecret.
It's only named `kbs2` because I'm used to typing "kbs" in my terminal.