Mudrit
Concepts

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

CrateResponsibilityDepends on
mudrit-keystorePrivate keys + certificates: the Signer trait, KeyStore discovery, and the three backends. Knows nothing about PDF, CMS, or timestamps.
mudrit-pdfsignThe PDF signing engine: CMS/PKCS#7, RFC-3161 timestamps, LTV/DSS. Consumes any Signer.mudrit-keystore
mudritThe 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-keystore alone for pure key management (no PDF engine) — list a token's certificates, get a Signer, call .sign() on raw bytes.
  • Depend on mudrit-pdfsign alone for the engine with your own Signer — bring an HSM or cloud KMS and skip the bundled backends entirely.
  • Depend on mudrit for 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 down

Prefer use mudrit::prelude::*; in facade examples and use mudrit_pdfsign::prelude::*; when illustrating the engine crate on its own.

Next

On this page