Architecture
How Mudrit splits into three independently usable crates joined by one small Signer trait
Mudrit is a Cargo workspace of three crates. The split is deliberate: key management is one concern, PDF signing is another, and a developer should be able to control each independently. The two layers meet at a single small trait, so the PDF engine never needs to know where a key lives.
The three-crate workspace
| Crate | Responsibility | Depends on |
|---|---|---|
mudrit-keystore | Private keys + certificates: the Signer trait, KeyStore discovery, and the three backends. Knows nothing about PDF, CMS, or timestamps. | — |
mudrit-pdfsign | The PDF signing engine: CMS/PKCS#7, RFC-3161 timestamps, LTV/DSS. Consumes any Signer. | mudrit-keystore |
mudrit | The all-in-one facade. Re-exports both halves, adds the optional Iced picker and pick_and_sign. | mudrit-pdfsign |
The dependency arrow points one way: the PDF signer depends on the key layer, never the reverse.
mudrit-keystore has no knowledge of PDFs at all — which is exactly what lets it be used on its own.
Every crate is independently usable
The workspace is not a monolith you take whole. Each crate compiles and ships on its own:
- Depend on
mudrit-keystorealone for pure key management (no PDF engine) — list a token's certificates, get aSigner, call.sign()on raw bytes. - Depend on
mudrit-pdfsignalone for the engine with your ownSigner— bring an HSM or cloud KMS and skip the bundled backends entirely. - Depend on
mudritfor the whole bundle.
Cargo features (pfx, pkcs11, winstore, pkcs11-picker) then decide what actually gets
compiled, so you never pay for a backend you don't use.
The facade is just convenience
mudrit re-exports mudrit-pdfsign (which in turn re-exports mudrit-keystore), so most users
depend on mudrit alone and reach everything through one use mudrit::prelude::*;. The sub-crates
stay fully usable for leaner builds.
Which crate do I need?
Prop
Type
The contract: the Signer trait
The whole boundary between the key layer and the PDF layer is one trait,
mudrit_keystore::Signer. The engine asks a Signer for its certificate, its chain, and a
signature over some bytes — nothing more.
pub trait Signer: Send {
fn certificate(&self) -> &[u8]; // leaf cert DER
fn chain(&self) -> &[Vec<u8>]; // intermediate certs
fn sign(&self, data: &[u8]) -> Result<Vec<u8>>; // CMS-ready signature bytes (RSA raw / PSS / ECDSA DER r,s)
fn algorithm(&self) -> SignatureAlgorithm { /* RSA-SHA256 by default */ }
fn subject(&self) -> (String, String) { /* CN, O from the cert */ }
}Because this is the only contact point, implementing Signer yourself — for an HSM, a cloud KMS,
a remote signing service — makes the entire PDF pipeline work unchanged. No backend feature is
required for a custom Signer.
Send, not Sync
Signer requires Send (so a signer can move across threads) but intentionally not Sync: a
live PKCS#11 session is Send but not safely shareable by reference. Parallel paths
(sign_batch, the async wrappers) add a + Sync bound at the use site instead of forcing it on
every backend.
The KeyStore discovery abstraction
Signer produces a signature; KeyStore sits above it and answers which key?. Modelled on
Java's KeyStore, it lists the KeyEntrys in a container — a .pfx, a PKCS#11 token, the Windows
store — and hands back a Signer for a chosen alias.
pub trait KeyStore {
fn aliases(&self) -> Result<Vec<KeyEntry>>; // enumerate keys
fn signer(&self, alias: &str) -> Result<Box<dyn Signer>>; // pick one
}Each backend is exposed both as a direct Signer and as a KeyStore (PfxKeyStore,
Pkcs11KeyStore, WinStoreKeyStore). A .pfx exposes one entry; a token or the Windows store may
expose several.
use mudrit_pdfsign::prelude::*;
let store = Pkcs11KeyStore::new(dll, pin);
for e in store.aliases()? {
println!("{} — {} ({})", e.alias, e.cn, e.serial);
}
let signer = store.signer(&alias)?; // Box<dyn Signer>The prelude
Every crate ships a prelude module that pulls the common API into scope in one import. Backend
types follow their Cargo feature, so a build without a backend simply omits its re-exports — the
core contract (Signer, KeyStore, SignatureAlgorithm, CertFilter, certificate parsing) is
always present.
use mudrit::prelude::*; // facade: Signer, SignConfig, sign_pdf, backends, …
use mudrit_pdfsign::prelude::*; // engine crate, when working one layer downPrefer use mudrit::prelude::*; in facade examples and use mudrit_pdfsign::prelude::*; when
illustrating the engine crate on its own.