Mudrit
Cookbook

Advanced (Network / Hardware)

PAdES levels, PDF/A, custom timestamp authorities, Windows-store and PKCS#11 token signing, and SoftHSM2-backed ECDSA diagnostics

Everything here either reaches the network (a timestamp authority, a CRL/OCSP fetch) or needs hardware (a Windows certificate store, a PKCS#11 token / smart card). Every recipe is a self-contained snippet for your own project — see Cookbook for how they're written. The final Internal section is different: it documents the SDK's own validation harness, which ships only with the source bundle and is not something integrators run.

Recipes

RecipeWhat it showsNeeds / Feature
sign_ltv_timestampCertify + RFC-3161 timestamp + LTV + multi-page/boxnetwork · pfx
pades_demoPAdES B-B / B-T / B-LT / B-LTA at a chosen levelnetwork · pfx
pdfa_signAssert PDF/A-2B on the output (sRGB OutputIntent + pdfaid XMP + /ID); validate with veraPDFpfx
custom_timestamperImplement the Timestamper trait (own HTTP client / auth)network · pfx
enterprise_fullmultipage + multi-box + certified + LTV + timestamp + protected + metadatanetwork · pfx
sign_winstoreWindows MY-store cert / token (native picker)Windows + cert · winstore
sign_pss_winstoreRSA-PSS via the Windows store (CNG); clean error if the device lacks PSSWindows + cert · winstore
sign_pss_pkcs11RSA-PSS via a PKCS#11 token (CKM_*_RSA_PKCS_PSS)token + .dll · picker
sign_pkcs11PKCS#11 token: discovery + sign by serialtoken + .dll · pkcs11
verify_pkcs11PKCS#11 hardware path: discovery (no PIN) → login → token signtoken + .dll · pkcs11
pkcs11_managerMulti-token API (Pkcs11Manager): tokens+info, certs (no PIN), unlock+signtoken(s) · pkcs11
pkcs11_unlock_modesmodule_status · find_certificate · unlock_once (single-use) · builder auto_logouttoken · pkcs11
pkcs11_hotplugwatch_tokens — live plug/remove stream for a UItoken · pkcs11
pkcs11_multiPkcs11MultiStore across several modulestoken(s) · pkcs11
pkcs11_pickerThe Iced picker → unlocked signer → signtoken + .dll · picker

PAdES baseline levels

.pades(PadesLevel::…) switches the signature to ETSI.CAdES.detached, adds the ESS signing-certificate-v2 attribute, and pulls in each level's requirements automatically.

use mudrit::prelude::*;

let signer = PfxSigner::from_file("signer.pfx", "password")?;

let cfg = SignConfig::builder()
    .place("F", [350, 60, 560, 160])?
    .pades(PadesLevel::B_LTA) // signing-cert-v2 + TSA + DSS(OCSP+CRL+VRI) + document timestamp
    .build();

let signed = sign_pdf(&pdf, &signer, &cfg)?;
LevelAdds over the previous
B_BCAdES signed attrs + signing-certificate-v2 (the baseline)
B_Ta signature timestamp (auto-uses the default TSA if none is set)
B_LTa DSS with the chain + CRLs + OCSP responses + a /VRI keyed to the signature
B_LTAa trailing document timestamp (/DocTimeStamp), the archive stamp

B_T and above need network access. Renew a B_LTA archive later with add_document_timestamp.

Repo examples: pades_demo, sign_ltv_timestamp, enterprise_full — ship with the licensed source bundle. See the PAdES guide.

Assert PDF/A on the output

.pdfa(PdfaLevel::A2b) injects an sRGB /OutputIntents + pdfaid XMP + /ID, preserving an already-conformant input. It cannot be combined with .encrypt(...).

use mudrit::prelude::*;

let cfg = SignConfig::builder()
    .place("F", [350, 60, 560, 160])?
    .pdfa(PdfaLevel::A2b)
    .build();

let signed = sign_pdf(&pdf, &signer, &cfg)?;   // validate with veraPDF

Repo example: pdfa_sign (source bundle). See the PDF/A guide.

Custom / protected timestamp authority

For anything the built-in Timestamp::url(...) doesn't cover — mutual-TLS, a proxy, a non-standard flow — implement Timestamper yourself. The SDK hands you the RFC-3161 request (DER); you POST it and return the raw TimeStampResp (DER).

use mudrit::prelude::*;

struct MyTsa { /* client, certs, … */ }

impl Timestamper for MyTsa {
    fn timestamp(&self, request: &[u8]) -> Result<Vec<u8>> {
        // your own client (ureq/reqwest/…) with mTLS / proxy / etc.
        todo!("POST `request` to your TSA, return the raw TimeStampResp DER")
    }
}

let cfg = SignConfig::builder()
    .place("F", [350, 60, 560, 160])?
    .timestamp(Timestamp::custom(MyTsa { /* … */ }))
    .build();

let signed = sign_pdf(&pdf, &signer, &cfg)?;

Repo example: custom_timestamper (source bundle). Built-in auth (Basic / Bearer / header) is on Timestamp::url(...) — see Timestamps.

Windows-store signing

The Windows MY store handles the PIN prompt, wrong-PIN count, and lockout natively at sign time — often the simplest path for a USB token that installs a CSP/KSP minidriver.

use mudrit::prelude::*;

let signer = WinStoreSigner::from_thumbprint("AB12…")?;   // headless: an exact cert
let signer = WinStoreSigner::select()?;                   // native selection dialog
let signer = WinStoreSigner::select_with(&CertFilter::default().signing_only(true))?; // filtered dialog

let cfg    = SignConfig::builder().place("F", [350, 60, 560, 160])?.build();
let signed = sign_pdf(&pdf, &signer, &cfg)?;

.with_algorithm(SignatureAlgorithm::RsaPssSha256) selects PSS where the provider (CNG) supports it — a device that lacks PSS fails with a clear error rather than a silent downgrade.

Repo examples: sign_winstore, sign_pss_winstore (source bundle). See Signing backends.

PKCS#11 token signing

Cross-platform token access through any PKCS#11 module (.dll / .so / .dylib). Open a signer by the token's certificate serial; the SDK owns C_Login and surfaces PIN state as typed errors.

use mudrit::prelude::*;

let signer = Pkcs11Signer::open(dll_path, pin, serial_hex)?;   // dll path, user PIN, hex serial
let cfg    = SignConfig::builder().place("F", [350, 60, 560, 160])?.build();
let signed = sign_pdf(&pdf, &signer, &cfg)?;

RSA-PSS is available where the token advertises CKM_*_RSA_PKCS_PSS; ECDSA P-256/384/521 is supported where the token can do the curve. For both, the SDK re-verifies the produced signature against the certificate before use.

Repo examples: sign_pkcs11, verify_pkcs11, sign_pss_pkcs11 (source bundle).

Multi-token: Pkcs11Manager

Pkcs11Manager is the headless, thread-safe façade a real UI wraps: one serialised worker (so thread-unsafe vendor drivers can't crash concurrently), multiple vendor modules, and three layers — tokens (no PIN), certs on a token (no PIN, filtered), then unlock by serial.

use mudrit::prelude::*;

let mgr    = Pkcs11Manager::new(["/usr/lib/eps2003csp11.so", "/opt/vendor/SignatureP11.so"]);
let tokens = mgr.list_tokens()?;                                                          // no PIN
let certs  = mgr.list_certificates(&tokens[0].token, &CertFilter::any().signing_only(true))?; // no PIN
let signer = mgr.unlock(&tokens[0].token, &certs[0].serial, Some("PIN"))?;                // -> Box<dyn Signer>
let signed = sign_pdf(&pdf, signer.as_ref(), &cfg)?;

Wrong PIN returns Error::WrongPin { final_try, count_low } / Error::PinLocked (the token's real flags) for retry UX; a protected-auth-path (PIN-pad) token takes unlock(.., None).

Single-token concurrency

sign_batch parallelizes across a thread pool, but a single hardware token can't sign concurrently — use BatchOptions::default().threads(1) when batching over one token. Pkcs11Manager's worker already serializes access to each module for the same reason.

Repo examples: pkcs11_manager, pkcs11_unlock_modes, pkcs11_hotplug, pkcs11_multi (source bundle). See the PKCS#11 guide.

PKCS#11 picker

The bundled cross-platform (Iced) picker runs a Windows-style select → PIN flow and returns an unlocked Box<dyn Signer> (or Error::Cancelled). Needs the picker feature.

use mudrit::prelude::*;

let modules = ["/usr/lib/eps2003csp11.so"];
let signer  = Pkcs11Picker::new(modules).select()?;   // -> Box<dyn Signer>
let signed  = sign_pdf(&pdf, signer.as_ref(), &cfg)?;

// or the one-call convenience that bridges picker → engine:
if let Some(signed) = mudrit::pick_and_sign(modules, &cfg, &pdf)? {
    std::fs::write("signed.pdf", signed)?;
}

Repo example: pkcs11_picker (source bundle). See Certificate pickers.

Does your token actually do ECDSA / PSS?

Token ECDSA support varies — some devices advertise CKM_ECDSA but ship a broken EC implementation. The raw-r‖s→DER conversion, the ECDSA self-verify, and an end-to-end ECDSA PDF signature (P-256 + P-521) are unit-tested offline, and the token ECDSA/PSS device path is validated against SoftHSM2. The Windows-store CNG (NCryptSignHash) path is compile-checked only — always smoke-test against your actual device before relying on it. The read-only pkcs11_ecdsa_probe repo example checks whether a token advertises CKM_ECDSA and holds an EC key/cert without needing a PIN.

Internal: SDK validation (source bundle only)

For SDK developers, not integrators

Everything in this section is part of Mudrit's own verification harness — how the SDK team exercises the signing engine across every keystore and mechanism. It ships only with the licensed source bundle and is not a workflow integrators run. The tools here write objects to a token and are only ever pointed at a dedicated SoftHSM or isolated test token — never a production DSC. If you are integrating Mudrit, use the recipes above; you can skip this section entirely.

The harness runs a single 65-scenario matrix (crates/mudrit/examples/shared/scenario_matrix.rs — single / multi / certify / encrypt / PAdES B-B…B-LTA / doc-timestamp / re-sign / field-lock / existing-field / …) against every signing backend and every algorithm, writing one numbered PDF per scenario into its own folder — never a flat or shared path:

temp/<keystore>/<mechanism>/   e.g.  temp/pfx/ecdsa-p256/, temp/softhsm/rsa/, temp/winstore/ecdsa/
DriverKeystoreOutput folder(s)
matrix_pfxPFX (.pfx, offline + network)temp/pfx/{rsa, ecdsa-p256, ecdsa-p384, ecdsa-p521}
matrix_softhsmPKCS#11 via SoftHSM (by serial)temp/softhsm/{rsa, ecdsa-p256, [ecdsa-p384], [ecdsa-p521]}
matrix_winstoreWindows store (by thumbprint, non-interactive)temp/winstore/{rsa, ecdsa}
sign_matrix_pkcs11PKCS#11 picker (real token, GUI PIN)temp/pkcs11/picker
sign_matrix_winstoreWindows picker (native dialog)temp/winstore/picker

A new capability is added as a scenario in the shared matrix — so every keystore picks it up — not as a one-off program with its own output path.

cargo run -p mudrit --example matrix_pfx
cargo run -p mudrit --example matrix_winstore -- <thumb-rsa> <thumb-ecdsa>

Provisioning a SoftHSM token for matrix_softhsm

matrix_softhsm needs one RSA and one EC identity provisioned on SoftHSM tokens first. pkcs11_provision generates a key on the token and stores a self-signed test cert (it forces issuer = subject so the cert validates standalone).

softhsm2-util --init-token --free --label MudritRSA --so-pin 4321 --pin 1234
softhsm2-util --init-token --free --label MudritEC  --so-pin 4321 --pin 1234
export PKCS11_PIN=1234
cargo run -p mudrit-keystore --example pkcs11_provision -- <softhsm2.so> MudritRSA rsa2048 samples/ABC12.pfx ABC12
cargo run -p mudrit-keystore --example pkcs11_provision -- <softhsm2.so> MudritEC  ecp256  samples/ecdsa-p256.pfx ecdsa
cargo run -p mudrit --example matrix_softhsm -- <softhsm2.so> 1234 <serial-rsa> <serial-ec>

ECDSA device-path validation against SoftHSM2

The EC token path is validated end-to-end against SoftHSM2 — the same tools work against real hardware, but the write tools generate keys and must only touch a test token.

ExampleCrateShowsWrites?
pkcs11_ecdsa_probemudrit-keystoreDoes the token advertise CKM_ECDSA + hold an EC key/cert? (no PIN)read-only
pkcs11_gen_ecdsamudrit-keystoreGenerate an EC key on the token (p256/p384/p521) + store a self-signed certcreates objects
pkcs11_import_pfxmudrit-keystoreImport an EC .pfx onto the token (DSC tokens often reject this)creates objects
sign_pkcs11_ecdsamudrit-pdfsignSign a PDF with the token's EC key + cryptographically verifyread-only
export SOFTHSM2_CONF=path/to/softhsm2.conf
softhsm2-util --init-token --free --label ecdsa-test --so-pin 87654321 --pin 12345678
export PKCS11_PIN=12345678
cargo run -p mudrit-keystore --example pkcs11_gen_ecdsa   -- <softhsm2.so> samples/ecdsa-test-signer.pfx asd p521
cargo run -p mudrit-keystore --example pkcs11_ecdsa_probe -- <softhsm2.so>
cargo run -p mudrit-pdfsign  --example sign_pkcs11_ecdsa  -- <softhsm2.so> <serial-from-gen> p521

Write tools read PKCS11_PIN, never the command line

pkcs11_provision, pkcs11_gen_ecdsa, and pkcs11_import_pfx create objects on the token and read the PIN from PKCS11_PIN only — never pass it as a CLI argument. They never bulk-delete. Provision one identity per token for a clean single-cert CMS, and always use a dedicated SoftHSM or isolated test token — never run these against a production DSC.

Next

On this page