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
| Recipe | What it shows | Needs / Feature |
|---|---|---|
sign_ltv_timestamp | Certify + RFC-3161 timestamp + LTV + multi-page/box | network · pfx |
pades_demo | PAdES B-B / B-T / B-LT / B-LTA at a chosen level | network · pfx |
pdfa_sign | Assert PDF/A-2B on the output (sRGB OutputIntent + pdfaid XMP + /ID); validate with veraPDF | pfx |
custom_timestamper | Implement the Timestamper trait (own HTTP client / auth) | network · pfx |
enterprise_full | multipage + multi-box + certified + LTV + timestamp + protected + metadata | network · pfx |
sign_winstore | Windows MY-store cert / token (native picker) | Windows + cert · winstore |
sign_pss_winstore | RSA-PSS via the Windows store (CNG); clean error if the device lacks PSS | Windows + cert · winstore |
sign_pss_pkcs11 | RSA-PSS via a PKCS#11 token (CKM_*_RSA_PKCS_PSS) | token + .dll · picker |
sign_pkcs11 | PKCS#11 token: discovery + sign by serial | token + .dll · pkcs11 |
verify_pkcs11 | PKCS#11 hardware path: discovery (no PIN) → login → token sign | token + .dll · pkcs11 |
pkcs11_manager | Multi-token API (Pkcs11Manager): tokens+info, certs (no PIN), unlock+sign | token(s) · pkcs11 |
pkcs11_unlock_modes | module_status · find_certificate · unlock_once (single-use) · builder auto_logout | token · pkcs11 |
pkcs11_hotplug | watch_tokens — live plug/remove stream for a UI | token · pkcs11 |
pkcs11_multi | Pkcs11MultiStore across several modules | token(s) · pkcs11 |
pkcs11_picker | The Iced picker → unlocked signer → sign | token + .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)?;| Level | Adds over the previous |
|---|---|
B_B | CAdES signed attrs + signing-certificate-v2 (the baseline) |
B_T | a signature timestamp (auto-uses the default TSA if none is set) |
B_LT | a DSS with the chain + CRLs + OCSP responses + a /VRI keyed to the signature |
B_LTA | a 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 veraPDFRepo 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 onTimestamp::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/| Driver | Keystore | Output folder(s) |
|---|---|---|
matrix_pfx | PFX (.pfx, offline + network) | temp/pfx/{rsa, ecdsa-p256, ecdsa-p384, ecdsa-p521} |
matrix_softhsm | PKCS#11 via SoftHSM (by serial) | temp/softhsm/{rsa, ecdsa-p256, [ecdsa-p384], [ecdsa-p521]} |
matrix_winstore | Windows store (by thumbprint, non-interactive) | temp/winstore/{rsa, ecdsa} |
sign_matrix_pkcs11 | PKCS#11 picker (real token, GUI PIN) | temp/pkcs11/picker |
sign_matrix_winstore | Windows 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.
| Example | Crate | Shows | Writes? |
|---|---|---|---|
pkcs11_ecdsa_probe | mudrit-keystore | Does the token advertise CKM_ECDSA + hold an EC key/cert? (no PIN) | read-only |
pkcs11_gen_ecdsa | mudrit-keystore | Generate an EC key on the token (p256/p384/p521) + store a self-signed cert | creates objects |
pkcs11_import_pfx | mudrit-keystore | Import an EC .pfx onto the token (DSC tokens often reject this) | creates objects |
sign_pkcs11_ecdsa | mudrit-pdfsign | Sign a PDF with the token's EC key + cryptographically verify | read-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> p521Write 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
Inspection & Key Selection
Parse certificate details, filter certificates headlessly, and branch on typed key-management errors — offline, no signing
Independently Usable Crates
mudrit-keystore and mudrit-pdfsign used entirely on their own — no facade, and in the engine's case, no bundled backend at all