Programming, philosophy, pedaling.

kbs2 0.2.x

Dec 6, 2020

Tags: programming, devblog, rust, kbs2

As of yesterday and beginning with 0.2.1, kbs2 is officially in its second beta series.

Like with the 0.1.x series, I’ll use this post to explain the features that made it into the 0.2.x tree, as well as important changes and breakages. I’ll also use this post to revise the 1.0.0 roadmap.

If you’re looking for a brief explanation of what kbs2 is, read this first.



Improvements to wrapped key management

As documented in the “cryptography” section, kbs2 protects the private key used for record encryption by having the user encrypt it (i.e., the keys itself) with a master password. This encrypted key is called the “wrapped key.”

The additional protection conferred by a wrapped key needs to be balanced against usability: without a caching or persistence mechanism for the unwrapped key, users would be forced to provide their master password on each kbs2 operation.

Before the 0.2.x series, kbs2 established persistence of the unwrapped key using a POSIX shared memory object:

This had a few nice properties (like persistence without running a separate process), but the downsides rapidly became obvious:

For 0.2.x, kbs2 has switched to a more traditional daemon agent architecture:

Switching to a separate daemon requires users to do slightly more setup (in that they now must run kbs2 agent on login or at some point before they intend to use kbs2), but this may be mitigated in a future patch release by having kbs2 auto-spawn the agent.


Added: kbs2 agent

As mentioned above, the kbs2 CLI has grown a new subcommand for running and interacting with the agent daemon: kbs2 agent.

To run the agent itself:

$ kbs2 agent

# alternatively, in the foreground with debugging logs
$ RUST_LOG=debug kbs2 agent --foreground

To request that the agent unwrap a key:

# unwrap the default config's wrapped key
$ kbs2 agent unwrap

# unwrap specified config's wrapped key
$ kbs2 -c /path/to/config/dir agent unwrap

Note: In normal use, you should never have to call kbs2 agent unwrap — all kbs2 commands will perform the equivalent operations internally and will prompt you for the master password as necessary.

To request that the agent flush all keys from memory:

$ kbs2 agent flush

# optionally, ask the agent to additionally quit
$ kbs2 agent flush --quit

Like the (now removed) kbs2 lock, this is primarily useful for integration with screensavers or other system events where the user would prefer that the unwrapped keys not be present in memory.

Added: kbs2 rewrap

kbs2 rewrap is a new utility command for handling a maintenance task: changing the master password on a wrapped key. Instead of running the manual age or rage commands themselves, users can now simply run:

$ kbs2 rewrap

…and they’ll be prompted to enter both their old master password (for unwrapping) and a new master password (for rewrapping). By default, kbs2 rewrap also makes a backup of the old wrapped key, making it harder to accidentally flush your encrypted passwords down the drain.

Removed: kbs2 lock and kbs2 unlock

kbs2 lock and kbs2 unlock were the primary subcommands for interacting with the POSIX shared memory mechanism that older versions of kbs2 used for key persistence. That mechanism has been removed, so the subcommands were also removed.



kbs2 0.2.x introduces error-hook, a new top-level execution hook (alongside pre-hook and post-hook).

error-hook can be used to run a program whenever a kbs2 subcommand fails, including external subcommands. It passes a message describing the error to the hook, allowing the hook to log or display it. An example of this has been added to the contrib section of the kbs2 repository as error-hook-notify.

Using it is as simple as setting error-hook in your kbs2 config:

error-hook = "/path/to/error-hook-notify"

Configurable Pinentry support

kbs2 has and continues to use Pinentry (via the excellent pinentry-rs) for its default password prompts. Before 0.2.x, kbs2 would always select the “default” Pinentry binary, hardcoded as pinentry.

This is almost always a reasonable choice on Linux systems (where it tends to be a symlink to the user’s preferred Pinentry), but not on macOS (where pinentry is usually the curses-based version and not a graphical version like pinentry-mac).

To remedy this, the 0.2.x series supports the pinentry config setting. In your config:

pinentry = "pinentry-mac"

…will switch all kbs2 prompts to pinentry-mac, if available. On failure to locate a Pinentry binary, kbs2 will continue to fall back to the (TTY-based) rpassword.


In terms of core functionality, kbs2 is roughly where it was with the 0.1.x series — I’d like to add a couple more convenient features, but there’s nothing fundamentally blocking a stable 1.0.0 release.

With that being said, here’s what you’re likely to see before a 1.0.0 release:

  1. This is a little finnicky, but essentially: once we have the client’s PID, we can get the executable that it was spawned from. This should always match (in contents) the path that kbs2 agent was spawned from, so any mismatch indicates an untrustworthy client. This can be circumvented by an attacker (at least on macOS), but it’s a little more difficult than just running a malicious process as the same user. 

Reddit discussion