Awesome
kbs2
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
- Quick start guide
- CLI documentation
- Configuration
- Customization
- Why another password manager?
- Technical details
- Hacking
- 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
.
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.
By way of example:
$ 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
using an AUR helper. For example,
$ yay -S kbs2
Other distributions will be supported sooner or later. Help us by looking at the open packaging issues!
Nix
This is a community-maintained package.
kbs2
can be installed through Nix:
$ nix-env -iA nixpkgs.kbs2
Cargo
If you're a Linux user, you'll need some X11 libraries. For Debian-based distributions:
$ sudo apt install -y libxcb-shape0-dev libxcb-xfixes0-dev
kbs2
itself is most easily installed via cargo
:
$ cargo install kbs2
After installation, kbs2
is completely ready for use. See the
Configuration section for some optional changes that you can
make.
Quick start guide
Initialize a new kbs2
configuration:
$ 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:
$ kbs2 new amazon
? Username? jonf-bonzo
? Password? [hidden]
List available records:
$ kbs2 list
amazon
facebook
Pull the password from a record:
$ kbs2 pass -c amazon
# alternatively, pipeline it
$ kbs2 pass facebook | pbcopy
Remove a record:
$ 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 <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:
$ kbs2 init
Create a new config and keypair without a master password:
$ kbs2 init --insecure-not-wrapped
Create a new config and keypair in a different location:
$ kbs2 -c /some/config/dir init
Create a new config keypair in a different location and specify a non-default store:
$ kbs2 -c /home/config/dir init --store-dir /some/store/dir
kbs2 new
Usage
create a new record
USAGE:
kbs2 new [FLAGS] [OPTIONS] <label>
ARGS:
<label> 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 <generator> use the given generator to generate sensitive fields
[default: default]
-k, --kind <kind> the kind of record to create [default: login]
[possible values: login, environment, unstructured]
Examples
Create a new login
record named foobar
:
$ kbs2 new foobar
? Username? hasdrubal
? Password? **********
Create a new environment
record named twitter-api
, overwriting it if it already exists:
$ 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:
$ 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:
$ kbs2 new -t email < <(echo -e "bill@microsoft.com\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 <kind> list only records of this kind
[possible values: login, environment, unstructured]
Examples
List all records, one per line:
$ 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}
.
$ kbs2 list -d
foobar login 1590277900
twitter-api environment 1590277907
pets.com login 1590277920
email login 1590277953
List only environment records:
$ kbs2 list -k environment
twitter-api
kbs2 rm
Usage
remove one or more records
USAGE:
kbs2 rm <label>...
ARGS:
<label>... the labels of the records to remove
FLAGS:
-h, --help Prints help information
Examples
Remove the foobar
record:
$ kbs2 rm foobar
kbs2 rename
Usage
rename a record
Usage: kbs2 rename [OPTIONS] <old-label> <new-label>
Arguments:
<old-label> the record's current label
<new-label> the new record label
Options:
-f, --force overwrite, if already present
-h, --help Print help
Examples
Rename the foo
record to bar
:
$ kbs2 rename foo bar
Rename foo
to bar
, even if bar
already exists:
$ kbs2 rename --force foo bar
kbs2 dump
Usage
dump one or more records
USAGE:
kbs2 dump [FLAGS] <label>...
ARGS:
<label>... 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:
$ kbs2 dump twitter-api
Label twitter-api
Kind environment
Variable TWITTER_API
Value 92h2890fn83fb2378fbf283bf73fbxkfnso90
Dump the pets.com
record in JSON format:
$ 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:
$ 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] <label>
ARGS:
<label> 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:
$ kbs2 pass pets.com
hunter2
Copy the password for the pets.com
record into the clipboard:
$ kbs2 pass -c pets.com
kbs2 env
Usage
get an environment record
USAGE:
kbs2 env [FLAGS] <label>
ARGS:
<label> 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:
$ kbs2 env twitter-api
export TWITTER_API=92h2890fn83fb2378fbf283bf73fbxkfnso90
Get just the value in an environment record:
$ kbs2 env -v twitter-api
92h2890fn83fb2378fbf283bf73fbxkfnso90
kbs2 edit
Usage
modify a record with a text editor
USAGE:
kbs2 edit [FLAGS] <label>
ARGS:
<label> 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:
$ kbs2 edit email
Open the email
record for editing with a custom $EDITOR
:
$ EDITOR=vim kbs2 edit email
kbs2 generate
Usage
generate secret values using a generator
USAGE:
kbs2 generate [generator]
ARGS:
<generator> the generator to use [default: default]
FLAGS:
-h, --help Prints help information
Examples
Generate a secret using the default generator:
$ kbs2 generate
rrayxfky-81x=h6i
Generate a secret using a generator named pwgen
:
$ 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:
$ kbs2 agent
Run the kbs2
agent in the foreground, for debugging purposes:
$ 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:
$ 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 key1
: query failed, agent is running but does not have the queried keypair2
: 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:
$ kbs2 agent query && echo "success" || echo "failure"
Query the agent for another config's keypair:
$ 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:
$ kbs2 agent unwrap
Add a custom config's key to the kbs2
agent:
$ 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:
$ kbs2 rewrap
Change the password on a wrapped key in another config:
$ kbs2 -c /path/to/config/dir rewrap
Change the password on a wrapped key without making a backup of the old wrapped key:
$ 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:
$ kbs2 rekey
Re-key without making backups of the original keyfile, config, and store (not recommended):
$ kbs2 rekey --no-backup
Re-key a different configuration and store:
$ kbs2 -c /some/other/kbs2/conf/dir rekey
kbs2 config
Usage
interact with kbs2's configuration file
USAGE:
kbs2 config <SUBCOMMAND>
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:
$ kbs2 config dump
# pretty-print the dumped JSON
$ kbs2 config dump --pretty
Configuration
kbs2
stores its configuration in <config dir>/kbs2/config.toml
, where <config dir>
is determined
by the the XDG basedir specification.
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
:
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
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 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
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 allkbs2 agent
subcommands)kbs2 init
All other subcommands, including custom subcommands, will cause the configured pre-hook
to run.
Read the 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 allkbs2 agent
subcommands)kbs2 init
All other subcommands, including custom subcommands, will cause the configured post-hook
to run.
Read the 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 allkbs2 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 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 of the 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:
[commands.new]
post-hook = "~/.config/kbs2/hooks/post-new.sh"
# ~/.config/kbs2/hooks/post-new.sh
>&2 echo "[+] created ${1}"
would produce:
$ 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:
[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.
[[generators]]
name = "hexonly"
alphabets = ["0123456789abcdef"]
length = 16
By default, kbs2
's configuration includes a default
generator that looks
something like this:
[[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.
$ 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.<name>]
hierarchy.
When run via kbs2
, custom commands receive the following environment variables:
KBS2_CONFIG_DIR
: The path to the configuration directory thatkbs2
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.
- NOTE: Subcommands are encouraged to use
KBS2_STORE
: The path to the secret store.KBS2_SUBCOMMAND
: Always set to1
. This can be used to determine whether a subcommand was run viakbs2
(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 ofkbs2
that executed this subcommand. Subcommands can use these numbers to enforce running under a minimum (or maximum) version ofkbs2
.
The 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
orstdout
from the parentkbs2
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 withKBS2_CONFIG_DIR
set to the configuration directory that the originalkbs2
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:
pre-hook = "~/.config/kbs2/hooks/pre.sh"
# ~/.config/kbs2/hooks/pre.sh
kbs2 some-other-command
and then:
$ 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
totrue
in the configuration. This will make all hooks reentrant — it's all or nothing, intentionally. - You can
unset
or otherwise delete theKBS2_HOOK
environment variable in your hook before runningkbs2
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
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
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 causeskbs2
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.
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 theroot
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 implementation. In particular,
kbs2
uses the rage implementation 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 thepublic-key
configuration setting, while the private key is stored in the file referenced by thekeyfile
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
tokbs2 init
.
Key unwrapping and persistence
As mentioned under Threat Model and 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
.
Hacking
Hacking on kbs2
is relatively straightforward. To build a fully functional development copy,
just use cargo build
in the repository root:
$ 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:
$ 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:
$ RUST_LOG=debug ./target/release/kbs2 list -k login
See the env_logger
documentation for more possible RUST_LOG
values.
History
TL;DR: kbs2
is short for "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, 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 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.