rsop-oct 0.1.9

SOP CLI tool for OpenPGP card devices based on rPGP
Documentation

rsoct - a SOP tool for OpenPGP card

rsoct (rsop-oct) is a special purpose Stateless OpenPGP (SOP) CLI tool, for use with OpenPGP card devices. SOP exposes a simple, standardized CLI interface for a set of common OpenPGP operations.

rsoct is a special-purpose alternative to the rsop CLI tool. It features native support for private key operations on OpenPGP card devices, and is based on a stack of rPGP, openpgp-card-rpgp and rpgpie.

The Stateless OpenPGP (SOP) CLI interface

The stateless OpenPGP command line interface (SOP) is an implementation-agnostic standard for handling OpenPGP messages and key material. rsoct is one (partial) implementation of this standard.

Stateless OpenPGP tools (like rsoct) are well suited for scripting use cases.

For more information about SOP, see https://datatracker.ietf.org/doc/draft-dkg-openpgp-stateless-cli/.

For the basic rsop tool see https://crates.io/crates/rsop. It supports private key operations based on software-based private key material (where key material is handled on the host computer, as opposed to in a hardware security device).

Installing

rsoct can be built and installed from its Rust sources with cargo:

$ cargo install rsop-oct

Alternatively, you can check for rsop-oct in your system's package manager.

OpenPGP card support

rsoct natively supports use of secret key material on OpenPGP card devices.

rsoct uses certificates in place of TSKs

In all places where SOP normally requires "KEYS" objects (aka OpenPGP "transferable secret keys"), rsoct instead requires certificates (aka OpenPGP "(transferable) public keys").

These certificate(s) are used by rsoct to search for an OpenPGP card slot that contains the associated private key material.

(Note that OpenSSH also uses this convention of using public keys as a lookup hint to find hardware-backed private key material. See for example here.)

PIN input

OpenPGP card devices require a User PIN to perform cryptographic operations.

rsoct supports two methods for providing the User PIN to cards:

  • The openpgp-card-state library for User PIN handling. rsoct can obtain User PINs via any of the backends supported by openpgp-card-state.
  • Explicitly provided User PINs via the SOP --with-key-password parameter (in rsoct, this parameter is interpreted as the User PIN for the OpenPGP card.)

With openpgp-card-state

By default, rsop-oct includes support for the openpgp-card-state library for User PIN handling.

In this mode, rsop-oct can automatically obtain User PINs from the openpgp-card-state backend, and doesn't require explicitly provided PINs.

With directly provided PINs

Alternatively, rsop-oct can be used completely standalone, without relying on the openpgp-card-state crate.

To build a version of rsop-oct without support for openpgp-card-state, the features settings can be adjusted like this:

$ cargo install --path . --no-default-features --features="cli"

In this mode, the User PIN must be provided for all private key operations, via the usual SOP parameter --with-key-password (analogous to a password that is used to lock a software key).

Example usage

To demonstrate the use of rsoct with an OpenPGP card, we'll do a walkthrough of a complete demonstration run here. We'll start with a spare card that we can overwrite and provision.

If you already have a provisioned OpenPGP card with key material on it, you can skip down to the "decryption" and "signing" sections, and use your existing key material.

In addition to rsoct, this example run uses:

  • the rsop tool to generate a new OpenPGP private key, and
  • the oct tool (from the openpgp-card-tools crate) to initialize our OpenPGP card.

Generating a test key on the host computer

First, we generate a new private key for our test user, Alice. We use the file extension .tsk to signify that the file contains a Transferable Secret Key (or "OpenPGP private/secret key").

$ rsop generate-key "<alice@example.org>" > alice.tsk

Then, we extract a certificate from the TSK file. That is, we extract the equivalent "public key" representation of Alice's TSK, which omits the private key material. We store the resulting certificate with the file extension .cert:

$ rsop extract-cert < alice.tsk > alice.cert

Initializing a test card

Importing private key material into our OpenPGP card

Now, we plug in our test OpenPGP card and check its identity:

$ oct list
Available OpenPGP cards:
FFFE:57011137

The card we're using in this example has the identity FFFE:57011137 (this card contains no keys that we care about, so we are happy to use it. The following step entirely overwrites the card's contents!)

So first, we'll factory-reset the card, to start from a blank slate. This command removes any key material from the card, and resets the User and Admin PIN to their default values:

$ oct system factory-reset --card FFFE:57011137
Resetting Card FFFE:57011137

Now we import Alice's key material onto our test-card:

$ oct admin --card FFFE:57011137 import alice.tsk
Enter Admin PIN:

The default Admin PIN on most OpenPGP card devices is 12345678. We entered this PIN at the prompt above.

We can have a look at the newly imported key material on the card, now:

$ oct status
OpenPGP card FFFE:57011137 (card version 2.0)

Signature key:
  Fingerprint: 26FD 6C05 D8AB 6D9A 7A27  A5CA DB2E 1E31 FB8E 9EA7
  Creation Time: 2024-04-08 16:26:54 UTC
  Algorithm: Ed25519 (EdDSA)
  Signatures made: 0

Decryption key:
  Fingerprint: 4B8D 7AE1 D4DE 65CE F0A8  4D2E A60A B338 5999 2476
  Creation Time: 2024-04-08 16:26:54 UTC
  Algorithm: Cv25519 (ECDH)

Authentication key:
  Fingerprint: [unset]
  Algorithm: RSA 2048 [e 32]

Remaining PIN attempts: User: 3, Admin: 3, Reset Code: 3

Configuring the User PIN for the test card with openpgp-card-state

Now we need to store the User PIN for our test card in a mechanism that openpgp-card-state can access. For this test, we'll just store the User PIN in a plain text config file. The easiest way to store the User PIN is to add the following content to the openpgp-card-state config file (you need to adjust the value for ident to reflect your own card's identity, if you're playing along at home):

[[cards]]
ident = "FFFE:57011137"

[cards.pin_storage]
Direct = "123456"

Note that after the factory-reset above, the User PIN of cards is typically 123456. For this test run, we don't change the User PIN (this is obviously not good practice for production cards), and just store this User PIN value in the openpgp-card-state configuration file (using the "Direct" PIN storage backend, which means the PIN is stored directly in the config file, as plain text).

On Linux systems the openpgp-card-state config file is typically located in ~/.config/openpgp-card-state/config.toml. It can be changed with any editor.

When using an OpenPGP card in production, with valuable key material on it, you might want to consider using a different PIN storage backend. See the documentation for openpgp-card-state for more details about this.

Using an initialized OpenPGP card for decryption or signing

Decryption on the card

We have now completed provisioning our card. In this example we will use the key material on the card to decrypt a message that was encrypted to Alice's certificate.

So first, we encrypt a message to Alice. To do this, we use Alice's public key material, from Alice's certificate alice.cert:

$ echo "hello alice" | rsop encrypt alice.cert > alice.msg

Now, we can decrypt the message based on just the public key material for Alice. Notice that we're giving rsoct the certificate file alice.cert:

$ cat alice.msg | rsoct decrypt alice.cert
hello alice

Note that without using an OpenPGP card, this would not work! For software key-based operation, decryption needs the private key material from alice.tsk.

When using rsoct to perform private key operations on an OpenPGP card (that is: decryption or signing), a number of things happen in the background:

  • All OpenPGP cards that are plugged into your system are enumerated.
  • rsoct checks if any of the cards contains the private key material that matches the subkey in alice.cert that is relevant to the operation.
  • Once a card with suitable key material is found, rsoct uses that card's identifier to look up the card's User PIN via the openpgp-card-state configuration (note that the PIN can be stored in different backends, which use different storage mechanisms and provide different security guarantees).
  • If the user PIN is stored via openpgp-card-state, rsoct obtains it.
  • The User PIN is presented to the card for verification. This authorizes the requested cryptographic operation.
  • The cryptographic operation is performed on the card.

Signing on the card

Analogously, it's possible to produce a cryptographic signature with an OpenPGP card, using rsoct:

$ echo "hello world" | rsoct inline-sign alice.cert  > sig.alice

Note that, as above, the public key data in alice.cert is not by itself sufficient to issue a signature. As above for decryption, rsoct searches for an OpenPGP card device that contains the private signing key material which corresponds to the signing subkey in alice.cert, and asks the card to issue a signature with that key.

Once produced, anyone can verify this signature as usual, by checking its validity against Alice's public key material in the certificate alice.cert:

$ cat sig.alice | rsop inline-verify alice.cert
hello world

Overview of building blocks

flowchart TD
    RSOP-OCT["rsop-oct <br/> (SOP CLI tool)"] --> RPGPIE
    RPGPIE["rpgpie <br/> (OpenPGP semantics library)"] --> RPGP
    RPGP["rPGP <br/> (OpenPGP implementation)"]
    RSOP-OCT --> OCR["openpgp-card-rpgp"] --> RPGP
    RSOP-OCT --> OCS["openpgp-card-state <br/> (OpenPGP card User PIN handling)"]
    OCR --> OC["openpgp-card"]
    RSOP-OCT --> SOP["sop <br/> (generic SOP CLI implementation)"]

Rust SOP interface

The rsoct CLI tool is built using the excellent https://crates.io/crates/sop framework. The rsoct binary itself is trivially derived from implementations of its traits.

Warning, early-stage project!

rsoct (and its foundational library rpgpie) is in an early development stage. Use with caution!

In particular, error handling is not sufficiently elaborated yet. Failures are often not explained in sufficient detail.

Public-key operations are not currently handled in rsoct. Ideally, this support will be added in the future, but there is currently no timeline for it.