threadpanic/rustpg
1
0
Fork
You've already forked rustpg
0
2 Keyring Format
Bert van der Weerd edited this page 2026年05月12日 12:02:22 +02:00

Keyring Format

The serialisation and load/save logic described here is implemented in crates/keystore-rpg (backed by rpglib::key_material). For the trait-based multi-backend architecture see KeyStore Architecture.

rustpg stores all key material in a single file: ~/.config/rustpg/key_material.rpg. The file is AES-256-GCM encrypted using the same RPGS wire format as any other symmetric-encrypted file — open it with rpg --decrypt if you need to inspect it. The plaintext inside is a human-readable text format described below.


File locations

File Purpose
~/.config/rustpg/key_material.rpg Primary store — always AES-256-GCM encrypted
~/.config/rustpg/key_material.txt Legacy plaintext fallback — loaded on first run if .rpg is absent

Both files are written with permissions 0600 (owner read/write only) on Unix. Writes are atomic: the new content is written to a .tmp file and renamed over the target.


Outer encryption

key_material.rpg is a standard RPGS ciphertext. The master password is the Argon2id passphrase; the KDF parameters (memory cost, iterations, parallelism) and the 16-byte salt are stored in the RPGS header as with any other encrypted file. See Wire Formats for the byte layout.

The master password is optionally cached in the OS keyring (GNOME Keyring on Linux, Credential Manager on Windows) under the service name rustpg, key passwd_keyring. If the OS keyring is unavailable the app prompts on each launch.


Plaintext wire format (format-version 1)

The decrypted content of key_material.rpg — or the contents of key_material.txt — is UTF-8 text with the following structure:

rustpg-keyring
format-version 1
identity <slug>
role <role>
name <display name>
email <address> ← optional
keypair <algo>:<bits>:v<version>
-----BEGIN <ALGO> PUBLIC KEY-----
<base64>
-----END <ALGO> PUBLIC KEY-----
-----BEGIN <ALGO> PRIVATE KEY-----
<base64>
-----END <ALGO> PRIVATE KEY-----
-----BEGIN RUSTPG REVOCATION----- ← optional
<base64>
-----END RUSTPG REVOCATION-----
pubkey <algo>:<bits>:v<version>
-----BEGIN <ALGO> PUBLIC KEY-----
<base64>
-----END <ALGO> PUBLIC KEY-----
-----BEGIN RUSTPG REVOCATION----- ← optional
<base64>
-----END RUSTPG REVOCATION-----
identity <next-slug>
...

Rules:

  • Blank lines and lines beginning with # are ignored.
  • The file header (rustpg-keyring / format-version) ends at the first identity line.
  • Unknown header fields are skipped — forwards-compatible for new header additions.
  • Each identity block runs until the next identity line or EOF.
  • A keypair block is consumed until the first blank line after all PEM blocks are closed.
  • Field order within an identity block is fixed: identity → role → name → email → key blocks.

Identity slug

The slug is the lookup key for all operations — it is used in CLI flags (--my-id, --recipient), settings (last_recipient_id), and internal references. It must consist entirely of [a-zA-Z0-9_-] characters. The parser rejects any slug containing other characters.


Roles

Role string Rust variant Meaning
self Role::Own Active own identity — sign, decrypt, encrypt to self
self-archived Role::OwnArchived Rotated key — decrypt old files only
contact Role::Contact External party — encrypt-to, verify signatures
contact-archived Role::ContactArchived Retired contact — verify old signatures only

Only self and self-archived identities may hold a keypair block (private key material). A contact or contact-archived identity with a keypair block is a parse error.

When exporting a public identity (Export Public Identity... / --export), own-role identities are downgraded: selfcontact, self-archivedcontact-archived. The recipient sees a contact entry without private key material.


Key blocks

keypair — both halves

Used for own identities that can sign or decrypt. The algo tag has the form <algo>:<bits>:v<version>:

Currently used Algo Bits Version
Encryption x25519 256 v1
Signing ed25519 256 v1

PEM labels are custom (not OpenSSL-standard):

-----BEGIN X25519 PUBLIC KEY----- / -----BEGIN X25519 PRIVATE KEY-----
-----BEGIN ED25519 PUBLIC KEY----- / -----BEGIN ED25519 PRIVATE KEY-----

The PEM body is standard base64 of the raw 32-byte key. pem_encode() / pem_decode() are defined in crates/keystore/src/lib.rs and re-exported from rpglib::key_material and rpglib::app::keys — never read or write raw key bytes directly.

pubkey — public only

Used for contact identities, or when an own-role identity's private key is intentionally omitted (export path). Same algo tag format; only a public PEM block follows.


Revocation certificates

A revocation certificate is an Ed25519 signature over the literal bytes REVOKE, PEM-wrapped under the label RUSTPG REVOCATION:

-----BEGIN RUSTPG REVOCATION-----
<base64-encoded 64-byte Ed25519 signature over b"REVOKE">
-----END RUSTPG REVOCATION-----

The certificate is stored inline on the keypair or pubkey block of the Ed25519 key. Revocation is checked at runtime by verifying the cert against the stored Ed25519 public key — no timestamp, no expiry, no external trust anchor. An identity is revoked if and only if verify(pubkey, cert, "REVOKE") returns true.

Revocation is one-way: there is no un-revoke operation. Matching an incoming cert to a keyring identity is done by verifying the cert against every Ed25519 key in the keyring and returning the first match (find_revoke_match()).


Fingerprint

The fingerprint is SHA-256 of the raw Ed25519 public key bytes (32 bytes), displayed as 8 groups of 8 uppercase hex characters:

AABBCCDD EEFF0011 22334455 66778899 AABBCCDD EEFF0011 22334455 66778899

It is computed on demand from the stored PEM — not stored in the keyring file.


Import and export

Operation What is transferred
Export Public Identity rustpg-keyring header + one identity block, public keys only, role downgraded to contact
Export Full Identity Same but keypair blocks with private keys, role preserved
Import Identity Parsed as a full keyring file; all identities in the file are merged. Slug conflicts are refused — delete the existing identity first.
Import Revocation Cert PEM file verified against every identity; applied to the matching slug.
Backup Keyring Full encrypted key_material.rpg copy to an arbitrary path.
Restore Keyring Decrypts and replaces the live keyring entirely.

OS keyring integration

Two passwords are cached in the OS keyring under the service name rustpg:

Key Contents
passwd_keyring Master password protecting key_material.rpg
passwd_symmetric Last-used symmetric passphrase (pre-fills encrypt/decrypt dialogs)

Cache failures are silent — a missing daemon or permission error never prevents the app from starting. On Linux, GNOME Keyring requires the keyring crate compiled with features sync-secret-service and crypto-rust; without these it falls back to an in-memory mock that does not persist across sessions.