Table of Contents
KeyStore Architecture
rustpg uses a trait-based keystore system so that key material from different backends — the native RPG format, GnuPG's ~/.gnupg, and future stores — can be handled through a single interface.
Motivation
All key operations previously lived inside rpglib::key_material. Extracting a KeyStore trait makes it possible to:
- Point the CLI at an alternate RPG keystore file (
--storetype-rpg /path/to/alt.rpg) - Read GnuPG keys for listing and recipient selection (
--storetype-gnupg) - Build a standalone key management GUI (
keylib-librarian) that is independent of the mainrustpgwindow - Add future backends (OS certificate store, hardware token via PKCS#11, etc.) without touching crypto code
Crate dependency graph
keystore ← shared types + KeyStore trait (no crypto)
↑
rpglib ← crypto engine; KeyMaterial uses keystore types
↑
keystore-rpg ← RpgKeyStore wraps KeyMaterial, impl KeyStore
keystore-gnupg ← GnupgKeyStore, impl KeyStore (read-only stub)
keylib-tui-common ← shared TUI/GUI helpers (stub)
keylib-gui ← egui key-management dialogs (stub)
keylib-librarian ← standalone GUI binary (current: Hello World)
rpg, rustpg ← depend on rpglib + keystore-rpg + keystore-gnupg
rpglib does not depend on keystore-rpg — that would create a cycle. The CLI and GUI binaries pull both.
The KeyStore trait
Defined in crates/keystore/src/lib.rs:
pubtraitKeyStore: Send {fn store_type(&self)-> StoreType;fn is_writable(&self)-> bool;// default: false
fn identity_refs(&self)-> Vec<IdentityRef>;fn find(&self,slug: &str)-> Option<&Identity>;fn find_mut(&mutself,slug: &str)-> Option<&mutIdentity>;fn own_slug(&self)-> Option<&str>;fn own_identity(&self)-> Option<&Identity>;fn save(&mutself,password: Option<&str>)-> Result<()>;}save(None) means write unencrypted (legacy .txt path). save(Some(pw)) writes encrypted. Read-only stores return Err(Error::ReadOnly(...)) from save.
StoreType enum
pubenum StoreType{Rpg,Gnupg}// default: Rpg
Serialised as "rpg" / "gnupg" in rustpg.conf (store_type key) and printed by --list-keys output.
Shared types in keystore
The following types live in crates/keystore/src/lib.rs and are re-exported by rpglib::key_material for backward compatibility:
| Type | Purpose |
|---|---|
Identity |
A named identity with keypairs, pubkeys, role, email |
IdentityRef |
Owned, key-material-free summary; safe to hand to UI code |
KeyPair |
Public + private PEM, algo tag, optional revocation cert |
PubKey |
Public PEM only, algo tag, optional revocation cert |
Role |
Own / OwnArchived / Contact / ContactArchived |
Identity::to_ref() is defined here and uses has_revocation_cert() (cert-presence check) for the is_revoked field. For the security-critical crypto-verified revocation check, callers use rpglib::app::keys::is_revoked() directly.
PEM helpers (pem_encode, pem_decode) also live in keystore and are re-exported from rpglib::key_material and rpglib::app::keys.
keystore-rpg — RPG format backend
crates/keystore-rpg/src/lib.rs
pubstruct RpgKeyStore{pubinner: rpglib::key_material::KeyMaterial,}RpgKeyStore wraps KeyMaterial and delegates all trait methods to it. It is writable (is_writable() → true) and supports both encrypted and plaintext save paths.
Key constructors:
| Method | Description |
|---|---|
load_encrypted(password) |
Decrypt default key_material.rpg |
load_encrypted_from(path, password) |
Decrypt an alternate path |
load_or_default() |
Load plaintext .txt or return empty keyring |
save_encrypted_to(path, password) |
Write encrypted backup to arbitrary path |
keystore-rpg also re-exports the full RPG serialisation API (serialize, parse, merge_identities, serialize_identities) and the path helpers (key_material_path, key_material_rpg_path).
keystore-gnupg — GnuPG read-only stub
crates/keystore-gnupg/src/lib.rs
Reads GnuPG keys by shelling out to gpg — no binary key-file parsing, no S-expression decoding.
Load sequence:
gpg --list-secret-keys --with-colons→ collect fingerprints of secret keys (→Role::Own)gpg --list-keys --with-colons→ collect all public keys and UIDs- Parse colon-delimited
pub,fpr,uidrecord types - Build
Identitystructs: slug =gnupg-<first-16-chars-of-fpr>, role Own or Contact
GnuPG identities have no PEM key material — their keypairs and pubkeys vecs are empty. This means crypto operations (encrypt, sign, decrypt) cannot use them as key sources; they are useful for display and recipient selection only.
save() always returns Err(Error::ReadOnly("...")).
Slug example: fingerprint 9603D4AA9B71C78812B2C0AA1C4FE4A833CD3631 → slug gnupg-9603D4AA9B71C788.
keylib-tui-common (stub)
Planned: shared text-UI widgets and helpers used by both TUI and GUI keystore tools. Currently an empty library crate that establishes the dependency wiring.
keylib-gui (stub)
Planned: reusable egui dialogs for keystore operations (add identity, rotate key, import, export, revoke). keylib-librarian will depend on this. Currently empty.
keylib-librarian
A standalone GUI binary for managing key_material.rpg. Current state: single window with a Close button. See Key Librarian for the full page.
CLI integration
Two new flags on the rpg binary:
--storetype-gnupg
rpg --storetype-gnupg --list-keys
Loads GnupgKeyStore and runs gpg to list identities. Supported operations: --list-keys and --list-secret-keys. All write operations print:
Error: GnuPG keystore is read-only; use gpg(1) to modify keys.
--storetype-rpg [PATH]
rpg --storetype-rpg /mnt/usb/backup.rpg --list-keys
Opens an alternate RPG keystore file. When PATH is omitted, uses the default ~/.config/rustpg/key_material.rpg. The OS vault is not consulted for alternate paths — the password must come from --keyring-password or the interactive prompt.
GUI integration
View → Settings now shows a Store type row:
Store type: (*) RPG ( ) GnuPG (read-only, restart to apply)
The setting is saved to rustpg.conf as store_type = rpg or store_type = gnupg. The change takes effect on the next launch.
When GnuPG store is selected, GnupgKeyStore::load() is called at startup and the resulting identities are appended to the in-memory keyring. This lets them appear in key pickers and the identity list alongside the RPG identities. The RPG keyring itself is still loaded for own-key material (encrypt/sign/decrypt continue to use RPG keys).
Error types
keystore::Error:
| Variant | When |
|---|---|
Io(io::Error) |
File or subprocess I/O failure |
Format(String) |
Parse error in keyring text |
KeyNotFound(String) |
Slug lookup failed |
ReadOnly(String) |
Write attempted on a read-only store |
Other(String) |
Wraps any rpglib::Error (string-converted) |
rpglib::error::Error gains a From<keystore::Error> impl so that keystore::pem_decode()? works inside rpglib functions returning rpglib::Result.