6 releases (breaking)

0.6.0 Aug 27, 2024
0.5.0 Jun 4, 2024
0.4.0 May 19, 2024
0.3.0 Mar 24, 2024
0.1.0 Jan 21, 2024

#2412 in Cryptography

Download history 205/week @ 2024-07-20 130/week @ 2024-07-27 139/week @ 2024-08-03 526/week @ 2024-08-10 249/week @ 2024-08-17 411/week @ 2024-08-24 320/week @ 2024-08-31 180/week @ 2024-09-07 334/week @ 2024-09-14 298/week @ 2024-09-21 343/week @ 2024-09-28 390/week @ 2024-10-05 530/week @ 2024-10-12 363/week @ 2024-10-19 381/week @ 2024-10-26 294/week @ 2024-11-02

1,618 downloads per month
Used in 8 crates (5 directly)

LGPL-2.0-or-later

93KB
1K SLoC

The sequoia-keystore crate implements a server that manages secret key material. Secret key material can be stored in files, on hardware devices like smartcards, or accessed via the network. sequoia-keystore doesn't implement these access methods. This is taken care of by various backends. The backends implement a common interface, which is defined by this crate.


lib.rs:

Defines the traits that keystore backends need to implement.

Sequoia's keystore is a service, which manages and multiplexes access to secret key material. Conceptually, keys live on devices, and devices are managed by backends. A device may be as simple as an on-disk file, it may be a smartcard, or it could be another keystore server that is accessed over the network.

A backend implements the traits defined in this crate. The traits abstract away the details of the various devices. They are mostly concerned with enumerating keys, and executing the low-level decrypt and sign operations. The backend interfaces are different from, and more low level than the general-purpose interface exposed to applications.

The following figure illustrates the architecture. The squares represent different address spaces.

   +---------------+        +---------------+
   |  Application  |        |  Application  |
   +---------------+        +---------------+
                 \            /
+----------------------------------------------+
|                   Keystore                   |
|                /            \                |
|        soft key              openpgp card    |
|        backend                 backend       |
+----------------------------------------------+

The keystore does not have to run as a server; it is also possible to co-locate the keystore in an application, as shown here:

+----------------------------------------------+
|                 Application                  |
|                      |                       |
|                   Keystore                   |
|                /            \                |
|        soft key              openpgp card    |
+----------------------------------------------+

Using a daemon instead of a library or a sub-process, which is spawned once per application and is terminated when the application terminates, offers several advantages.

The main user-visible advantage is that the daemon is able to hold state. In the case of soft keys, the daemon can cache an unencrypted key in memory so that the user doesn't have to unlock the key as frequently. This is particularly helpful when a command-line tool like sq is executed multiple times in a row and each time accesses the same password-protected key. Likewise, a daemon can cache a PIN needed to access an HSM. It can also keep the HSM open thereby avoiding the initialization overhead. This also applies to remote keys: an ssh tunnel, for instance, can be held open, and reused as required.

A separate daemon also simplifies an important non-functional security property: process separation. Since soft keys aren't managed by the application, but by the daemon, an attacker is not able to use a heartbleed-style attack to exfiltrate secret key material.

The traits model backends as collections of devices each of which contains zero or more keys. The following figure illustrates a possible configuration. The keystore uses two backends, the softkey backend, and the openpgp card backend, and each backend has two devices. The softkey backend models certificates as devices; the openpgp card backend has one device for each physical device. Each device contains between 1 and 3 keys. The interface does not impose a limit on the number of devices per backend, or the number of keys per device. As such, a TPM managing thousands of keys is conceivable, and in scope.

+----------------------------------------------------+
|                   Keystore                         |
|                /            \                      |
|        soft key              openpgp card          |
|       /        \            /            \         |
|   0x1234      0xABCE     Gnuk         Nitro Key    |
|   /    \        |      #123456         #234567     |
| 0x10  0x23     0x34   /   |   \       /   |   \    |
|                     0x31 0x49 0x5A  0x64 0x71 0x88 |
+----------------------------------------------------+

The different devices may or may not be connected at any given time. For instance, the user may remove a smartcard, but if the backend has recorded the configuration, the keystore still knows about the

When the keystore starts, it eagerly initializes the various backends that it knows about. At this time, backends are statically linked to the keystore, and have to be explicitly listed in the keystore initialization function.

When a backend is initialized, the initialization function is passed a directory. The backend should read any required state from a subdirectory, which is named after the backend. For instance, the soft keys backend uses the "softkeys" subdirectory.

A backend must be extremely careful when using state stored somewhere else. If a user selects a different home directory, then they usually want a different configuration, which is isolated from the main configuration. This is not entirely possible in the case where a backend uses a physical resource, for example.

Keys and Devices

At its simplest, a device contains zero or more OpenPGP keys. A device may also be locked or unlocked, registered or not registered, and available or unavailable.

If a device is locked, it first has to be unlocked before it can be used. Sometimes a device can be unlocked by supplying a password via the DeviceHandle::unlock interface. Other times, the device has to be manually unlocked by the user. If a device is locked, it may or may not be possible to enumerate the keys stored on the device.

If a device is registered, then the device's configuration has been cached locally. In this case, the keys on the device can be enumerated even if the device is not connected to the host. For instance, when an OpenPGP card is registered, the OpenPGP card backend records the serial number of device, the list of keys that are stored on the smartcard, and their attributes. When the user enumerates the keys managed by the key store, these keys are returned, even if the smartcard is not attached. The user cannot, of course, use the keys.

If a device is registered, but is not attached to the system, then it is considered unavailable. If the user attempts to use a key on an unavailable device, then an error is returned. In this case, the application could normally prompt the user to make the corresponding device available.

These states are documented in more detail in the documentation for DeviceHandle.

Whether a key is registered or available is purely a function of the device. If a device contains multiple keys, and they can be registered, or available independent of the other keys, then the backend must model the keys as separate devices.

Dependencies

~16–29MB
~412K SLoC