Dec 6, 2020 Tags: devblog, kbs2, programming, rust
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.
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:
mmap
the object and read the unwrapped keyThis had a few nice properties (like persistence without running a separate process), but the downsides rapidly became obvious:
Despite being a POSIX API, shared memory is woefully underspecified. In particular, Linux’s implementation is significantly better behaved than macOS’s, leading to a collection of ugly hacks in the name of portability.
POSIX shared memory objects have unintuitive lifetimes: because they’re tied to the kernel,
they outlive the user’s login session and can be accessed in subsequent logins. kbs2 lock
was
supplied so that users could configure their systems to remove any kbs2
object(s) on logout
(or screensaver activation, or any other event), but I wasn’t a fan of this.
Using shared memory objects meant extensive use of native APIs in Rust, which in turn meant
extensive use of unsafe
.
For 0.2.x, kbs2
has switched to a more traditional daemon agent architecture:
kbs2 agent
(more on that below) runs as a daemon, listening on a Unix domain socketkbs2
invocations act as clients to the agent, querying for keys and requesting
that keys be unwrappedSwitching 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.
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:
1
2
3
4
$ kbs2 agent
# alternatively, in the foreground with debugging logs
$ RUST_LOG=debug kbs2 agent --foreground
To request that the agent unwrap a key:
1
2
3
4
5
# 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:
1
2
3
4
$ 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.
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:
1
$ 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.
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.
error-hook
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:
1
error-hook = "/path/to/error-hook-notify"
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:
1
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:
Ratcheting down of the agent trust model: the agent daemon currently only accepts connections from processes with the same effective UID; future versions may mandate that the connection is coming from a trusted process1.
An additional subcommand, kbs2 reroll
or similar, for changing the wrapped key entirely
(and re-encrypting all records with the new key).
Support for auto-starting the kbs2
agent daemon, so that users don’t have to configure
kbs2 agent
to run at login.
Additional configurable hooks.
Detailed CHANGELOG entries.
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. ↩