#openpgp-card #pin #user #storage #devices #state #config-file

bin+lib openpgp-card-state

Experimental storage mechanism for openpgp-card device state

6 releases

new 0.2.1 Apr 29, 2024
0.2.0 Apr 23, 2024
0.1.0 Mar 24, 2024
0.0.3 Mar 11, 2024
0.0.2 Feb 28, 2024

#4 in #openpgp-card

Download history 140/week @ 2024-02-18 213/week @ 2024-02-25 19/week @ 2024-03-03 238/week @ 2024-03-10 5/week @ 2024-03-17 314/week @ 2024-03-24 86/week @ 2024-03-31 123/week @ 2024-04-07 136/week @ 2024-04-14 261/week @ 2024-04-21

618 downloads per month
Used in 7 crates (3 directly)

MIT/Apache

30KB
302 lines

Shared state for applications that use OpenPGP cards

This crate facilitates use of OpenPGP card devices by applications.

In particular, it enables applications to perform operations on the hardware device without requiring any user interaction for PIN entry. Instead, applications obtain User PINs via this library.

graph TB
    Application --> CARD["OpenPGP card device <br/> (performs cryptographic operations <br/> after User PIN presentation)"]
    Application --> STATE["openpgp-card-state library <br/> (config and PIN storage backend access)"]
    STATE --> PINS["PIN storage backend <br/> (makes User PINs available to applications)"]

This crate uses a combination of two mechanisms:

  • A regular config file: Stores non-sensitive card metadata (e.g. an optional nickname for cards), and specifies which PIN storage backend is used by default, and per card.
  • PIN storage backend: Stores the (sensitive) User PIN of OpenPGP card devices on behalf of applications. Different types of PIN storage backend exist (see below). Users can pick the appropriate backend for their use case.

Use and architecture

This crate is used as a library by applications. The library facilitates read- and write-access to both the config file and the PIN storage backend. Generally speaking, the openpgp-card-state library doesn't require a long-running process (however, some PIN storage backends may consist of a long-running process).

From a user perspective, openpgp-card-state is usually an implementation detail of applications. However, it may be useful for users to understand how the mechanism works, to have a good mental model of the facility.

In openpgp-card-state, cards are addressed using the "ident"format, both for access to metadata in the config file, and for User PIN storage and retrieval.

Config file

The config file for openpgp-card-state is stored in a platform-specific default location. On Linux systems, this is typically $HOME/.config/openpgp-card-state/config.toml (the config file is handled using the directories crate, which uses platform specific standard locations, following the XDG specification on Linux systems).

A typical configuration entry for a card looks like this:

[[cards]]
ident = "0000:01234567"
pin_storage = "Keyring"
nickname = "my purple card"

This configuration entry specifies that the card with the ident 0000:01234567 is using the "Keyring" PIN storage backend for the User PIN. Additionally, the nickname my purple card is defined for the card.

Background, design tradeoffs and threat modeling

Historically, OpenPGP card devices were typically used via GnuPG.

This crate acts as shared infrastructure for non-GnuPG applications that use OpenPGP cards.

However, to understand the design space, we'll first look at some details of OpenPGP card use. In particular authorization of cryptographic operations. Then we outline how GnuPG interacts with OpenPGP cards. Finally, we discuss the concepts of this crate and related threat modeling.

OpenPGP card and User PINs

One topic of particular interest for this discussion is the handling of "User PIN"s. To authorize private key operations on a card (signing or decryption), the User PIN must be presented to the card.

The OpenPGP card specification is the canonical reference for PIN handling on OpenPGP card devices. The specification document refers to the User PIN as "PW1", and distinguishes two modes of using PW1: mode "81" for signing operations, and mode "82" for all other user operations (including decryption and authentication).

Typically, the User PIN only needs to be presented to the card once, and is then valid for the duration of a connection to the card. As an exception, cards can be configured so that User PIN presentation is valid only for a single signing operation, to require the user to enter the User PIN once per signing operation. We'll put this special case aside, for the following discussion.

PIN entry via the host computer vs. via a card reader with a pin pad

Historically, OpenPGP card devices were typically actual physical smart cards, which were used in separate card reader devices.

Some card readers feature a physical pin pad for entry of (numerical) PINs. This setup provides protection of the PIN from the host computer: Only the card reader itself sees the PIN that the card's owner enters on the card reader. In such setups, the host computer doesn't learn the PIN.

Modern OpenPGP card devices, on the other hand, have shifted almost entirely to different types of hardware: USB tokens that present to the host computer as a smart card reader with an inserted OpenPGP card (for example, the free software Gnuk running on an open hardware design, or various commercial devices by Nitrokey or Yubico). With such devices there is no way to present the User PIN to the "card" in a way that the host computer can't access.

Architecture of GnuPG

GnuPG is a venerable software suite. Its roots go back to 1997. It has pioneered the use of smart cards for private key operations for end users.

Its architecture consists of multiple processes which are linked via "assuan" (a GnuPG-specific IPC protocol). The following diagram shows GnuPG's multi-process architecture, as well as some application software and how it accesses GnuPG:

graph TB
    GPGME["GPGME <br/> (GnuPG access library)"] --> GPG
    GPG["GnuPG <br/> (CLI tool)"] --> GA["gpg-agent <br/> (long running: private key operations)"]
    GA --> SCD["scdaemon <br/> (long running: smart card access)"]
    GA --> pinentry["pinentry <br/> (prompts users for PINs and other secrets)"]

    GIT[Git] -.-> GPG
    SSH["SSH <br/> (can use OpenPGP cards via gpg-agent)"] -.-> GA
    TB[Thunderbird] -.-> GPGME

classDef application fill:#404040,stroke-dasharray: 5 5;
class TB,GIT,SSH application;

scdaemon is the GnuPG subsystem that handles access to OpenPGP card devices. It is designed to keep permanent and exclusive connections[^pcscshared] to any OpenPGP cards.

[^pcscshared]: More recent versions of scdaemon allow offer optional support for "shared" connections to OpenPGP cards.

This design has useful properties, especially when used with physical smart cards in readers with a physical pin pad: Without keeping a standing connection to such devices, the user would need to repeatedly re-enter their pin, on the physical pin pad of the reader. Possibly once for each operation, which would be prohibitive in many use cases.

In such scenarios, keeping a permanent connection to the card is a necessity for a good user experience.

However, the downside of this design is that no other applications (besides GnuPG's scdaemon) can reasonably use the cards, because GnuPG keeps them opened and assumes exclusive access (users have gone to some length to deal with the implications of this. Some have written shell scripts that strategically kill the scdaemon process as a workaround, to be able to access their card from other applications).

User PIN storage with openpgp-card-state

By contrast to GnuPG's approach (as outlined above), this crate makes different tradeoffs, and pursues different objectives.

Our main design goals are:

  • Enable multiple applications to directly use OpenPGP cards, without mediating access through some long-running process.
  • Provide a smooth user experience.
  • Simplicity.

Most users don't use an external pin pad, these days. This means that there is no strong reason to keep open a permanent connection to cards. Notice that modern OpenPGP card use necessitates disclosing the User PIN to the host computer, anyway. So the host computer can always send the User PIN to the card to authorize an operation.

For more discussion of threat modeling, see below.

Users should not be required to manually enter their User PIN for every single operation, so the User PIN needs to be available on the host computer, for applications, in some way. Acting as keeper of the User PIN is a central objective of this openpgp-card-state crate.

Threat modeling

As outlined above, this crate mainly deals with setups where the host computer has access to the User PIN (at least intermittently).

This implies a threat model where the User PIN for cards doesn't require immense protection against the host computer. Two possible classes of approach for handling the User PIN suggest themselves:

  • Persisting the User PIN on the host computer.
  • Keeping the User PIN available to applications (for some, possibly finite, duration) in a long-running process, but not persisting it on disk.

Persisting the User PIN

This crate allows users to choose between different approaches to handling User PINs. We refer to these as "PIN storage backends".

We propose that for most users it is reasonable and practical to persist the User PIN via a platform-specific, general purpose mechanisms for storage of secrets. Our default backend uses the "Keyring" User PIN storage backend (based on https://crates.io/crates/keyring) that implements this approach. It is backed by "secret-service" on Linux, "keychain" on Mac, and "credential manager" on Windows, respectively.

This approach is not appropriate for all cases. However, we think it is appropriate in a majority of cases. The User PIN mostly serves as protection in the case of theft of the physical OpenPGP card device, without simultaneous loss or breach of the host computer.

On the other hand, when protecting against remote attackers, "touch confirmation" for cryptographic operations is the most useful line of defense, with modern OpenPGP card devices. The User PIN is at best a weak defense in case of remote compromise of the host computer.

Ephemeral User PIN caching

For users whose threat model doesn't allow persisting the User PIN on disk, and who don't want to enter the User PIN for each operation, some kind of long-running process is required. In our architecture this will be a long-running process that serves as an ephemeral User PIN storage backend, shared between the applications of a user.

The ephemeral PIN storage backend for openpgp-card-storage is not yet ready, but it is on our roadmap.

PIN storage backends

One main purpose of this crate is to store and obtain the User PINs of OpenPGP card devices, in particular to make User PINs available to local applications.

Different users may have different requirements or priorities for their User PIN storage. So this crate supports different PIN storage mechanisms, at the user's choice.

"Keyring": Platform-specific protected persistent storage

By default, this crate uses the "Keyring" PIN storage backend. It is based on the https://crates.io/crates/keyring crate, and persists User PINs in platform-specific protected storage:

  • Linux: secret-service (this requires a service such as GNOME Keyring to provide the secret-service facility).
  • macOS: the "keychain" subsystem
  • Windows: the "credential manager" subsystem.

"Direct": Plaintext storage in the config file

Some users may not require their User PIN to be handled by a security-conscious subsystem, and may prefer to avoid the additional complexity that comes with using such a subsystem. For such use cases, the User PIN can alternatively be stored directly in the config file, as plain text.

This mode is especially convenient for use in contexts where protecting the User PIN is no concern, such as CI testing.

More PIN storage backends to come

A commonly requested feature is an ephemeral PIN storage backend that only keeps User PINs available in RAM, without persisting them to disk. An additional ephemeral PIN storage backend is forthcoming.

Default User PIN storage backend

The config file can explicitly define a default PIN storage backend. If this setting is present, the User PIN for any new cards will be stored using the specified default_pin_storage backend:

default_pin_storage = "Direct"

[[cards]]
ident = "0000:01234568"
nickname = "my yellow card"

[cards.pin_storage]
Direct = "123456"

Use of this library by application developers

This library is mainly aimed at application developers who want to implement OpenPGP card support. See here for a discussion of how to use this library.

NOTE: Please be aware that this library is young, and will likely go through some iterations before it stabilizes.

CLI tool

See here for a description of the accompanying CLI tool, which is mainly intended for debugging purposes.

Funding

This project has been funded in part through NGI Assure, a fund established by NLnet with financial support from the European Commission's Next Generation Internet program.

NGI Assure Logo

Dependencies

~2–12MB
~100K SLoC