Troubleshooting & FAQ
Symptom, cause, and fix for the errors you are most likely to hit — wrong passwords and PINs, untrusted certificates, PSS on tokens, LTV, sizing, and appearance fonts.
Something not signing, or the signature not showing up the way you expected? Find your symptom below, then follow the cause and fix. Every case here is grounded in a real Mudrit error or behaviour. For the full list of error variants, see the Errors reference.
Both error enums are #[non_exhaustive]
mudrit_pdfsign::Error and mudrit_keystore::Error can gain variants in a minor release, so any
match you write needs a _ => arm. Match the cases you handle, and fall through to a generic
message for the rest.
Wrong PFX / P12 password
Symptom — opening a .pfx / .p12 returns mudrit_keystore::Error::WrongPassword.
Cause — the password did not decrypt the file, or the file failed its integrity (MAC) check. Mudrit cannot tell a wrong password from a corrupt file apart — both surface as the same variant.
Fix — double-check the password (the bundled test file samples/ABC12.pfx uses ABC12). If you
are sure it is right, the file itself may be damaged — try re-exporting the PFX.
match PfxSigner::from_file("cert.pfx", pw) {
Ok(signer) => { /* … */ }
Err(mudrit_keystore::Error::WrongPassword) => eprintln!("wrong PFX password (or corrupt file)"),
Err(e) => eprintln!("could not load PFX: {e}"),
}Wrong or locked token PIN
Symptom — unlocking a PKCS#11 token returns Error::WrongPin { final_try, count_low } or
Error::PinLocked.
Cause — the PIN was rejected. PKCS#11 reports PIN state as flags, not an exact remaining
count: final_try means the token says this was the last attempt before it locks, and count_low
means only a few attempts remain. PinLocked means the token has already locked the user PIN.
Fix — drive a careful retry UX: warn hard on final_try before the user tries again, and
stop retrying once you see PinLocked (further attempts are pointless — the token must be unlocked
on the device with the PUK / admin PIN).
use mudrit_keystore::Error;
match mgr.unlock(&token, &serial, Some(pin)) {
Ok(signer) => { /* … */ }
Err(Error::WrongPin { final_try: true, .. }) => show_red("Wrong PIN — LAST attempt before lock!"),
Err(Error::WrongPin { count_low: true, .. }) => show_amber("Wrong PIN — only a few attempts remain"),
Err(Error::WrongPin { .. }) => show_amber("Wrong PIN — try again"),
Err(Error::PinLocked) => show_locked("PIN locked — unlock it on the device"),
Err(e) => show_red(&e.to_string()),
}Never brute-force a token
Because WrongPin can be the final attempt, do not loop and retry automatically. Show the state,
let the human decide, and back off on final_try. A locked token needs the issuer's unlock procedure.
See PKCS#11 tokens and Error handling for the full retry flow.
Signature shows "validity unknown" or untrusted
Symptom — the PDF is signed and opens fine, but Adobe shows a yellow "?" / "validity unknown" (or "at least one signature has problems") instead of a green check.
Cause — this is almost never a Mudrit bug. It means the signing certificate does not chain to a
root the viewer trusts. The bundled samples/ABC12.pfx is a test certificate, so it will always
show "validity unknown". Trust lives in the viewer, not in the signature.
Fix — sign with a real certificate whose root the viewer trusts:
- Adobe trusts roots on the AATL (Adobe Approved Trust List).
- The Windows store trusts roots installed in the OS.
- In India, viewers configured with the CCA root trust CCA-issued DSCs.
Then add LTV and a timestamp so validity survives long-term (see LTV and Timestamps).
The green tick is the viewer's call
Mudrit builds the layered appearance so the viewer can draw its dynamic validity icon
(.trust_icon(true)), but Mudrit cannot make a certificate trusted — only a trust anchor the viewer
recognises does that.
RSA-PSS fails or is silently downgraded on a token / Windows store
Symptom — requesting an RSA-PSS algorithm on a PKCS#11 token or the Windows store fails with
Error::UnsupportedKey, with a message that the device returned a signature that does not verify as
PSS.
Cause — some token KSPs / CSPs ignore the PSS padding flag and quietly sign with PKCS#1 v1.5 instead. If Mudrit trusted that blindly, it would label PKCS#1 bytes as PSS in the CMS and produce an Adobe-invalid file. So Mudrit self-verifies every hardware signature against the leaf certificate and fails loud when the maths doesn't match the requested scheme.
Fix — use a scheme the device actually supports: RSA PKCS#1 v1.5 (always available on every backend), or move that identity to the PFX backend, which does PSS in pure Rust for guaranteed support.
// If the token can't really do PSS, sign with deterministic PKCS#1 v1.5 instead:
let signer = Pkcs11Signer::open(dll, pin, serial)?; // RSA PKCS#1 v1.5 — always worksThe same self-verify guards ECDSA and catches a token that binds the wrong (encryption) key. See Algorithms.
Error::LtvIncomplete with LtvPolicy::Require
Symptom — signing with LtvPolicy::Require fails with Error::LtvIncomplete.
Cause — LTV is fail-loud on request: you asked Mudrit to guarantee revocation material in the DSS, but no CRL or OCSP could be obtained (no network, or the responder was unreachable). Rather than write a silently half-LTV document, Mudrit errors.
Fix — pick one of:
- Drop to
LtvPolicy::BestEffort(the default for.ltv(true)), which embeds what it can and never lies — callsign_pdf_reportedto get anLtvReportof exactly what landed. - Or inject pre-fetched revocation material via
ValidationMaterialand gooffline, so no live fetch is needed:
let cfg = SignConfig::builder()
.place("L", [350, 60, 560, 160])?
.ltv(true)
.validation_material(material) // pre-fetched CRL / OCSP / certs
.offline(true) // don't hit the network
.ltv_policy(LtvPolicy::Require) // now satisfiable from injected material
.build();See LTV.
"CMS too big" for the reservation
Symptom — signing fails with Error::InvalidInput complaining the /Contents payload is too big
for the reservation (the "CMS too big" case).
Cause — you used SigSize::Fixed(n) and the actual CMS blob — cert chain plus signature plus (if
timestamping) the TSA token — didn't fit in the n bytes you reserved. The /Contents placeholder
only needs to be large enough; too small fails hard rather than truncating.
Fix — use SigSize::Auto (the default) so Mudrit sizes the placeholder from the chain,
signature, and TSA-token length automatically, or bump SigSize::Fixed(n) with headroom.
.sig_size(SigSize::Auto) // measured automatically — recommended
// or
.sig_size(SigSize::Fixed(24_000)) // bigger reservation with headroomSigning a password-protected input
Symptom — signing an encrypted PDF fails with an encryption / password error, or a "document is encrypted" message.
Cause — the input document is password-protected, and the open password rides on the input
(via PdfReader), not on the SignConfig. Without it, Mudrit can't decrypt the input to sign it.
Fix — attach the open password to the PdfReader:
let signed = sign_pdf(PdfReader::open("locked.pdf")?.password("secret"), &signer, &cfg)?;Use .maybe_password(pw) when the password is already an Option<String>. By default the signed
output is unencrypted; to keep it protected, add .keep_password() or .encrypt(…) on the config
— see Encryption.
Deferred (two-step) signing rejects the input
Symptom — prepare_signature returns Error::InvalidInput about an unsupported feature.
Cause — deferred "hash-then-sign" is v1 and deliberately narrow. It supports a fresh
(unsigned), unencrypted input, Method::Single / MultiShared, certification, LTV, and PAdES
B-B / B-T. It rejects encrypted output, Method::MultiChained, appending to an already-signed
PDF, an encrypted input, and PAdES B-LT / B-LTA — each with a clear message.
Fix — for any of those, use the in-process sign_pdf, which handles them all. Reserve deferred
signing for the case it exists for: the private key lives elsewhere (a remote / HSM service) and only
a hash can be sent out. See Deferred signing.
PDF/A plus encryption
Symptom — combining .pdfa(PdfaLevel::A2b) with .encrypt(…) (or .keep_password()) fails.
Cause — the PDF/A standard forbids encryption, so the two options are mutually exclusive by design.
Fix — choose one. Assert PDF/A for an archival, unencrypted file, or encrypt for a password-protected file — not both. See PDF/A.
Complex-script or right-to-left appearance text renders wrong
Symptom — a visible signature with Devanagari, Arabic, Hebrew, Thai, or other complex-script text
either errors (Error::InvalidInput about a script Helvetica cannot render) or shows boxes / broken
shaping.
Cause — the default appearance font is a standard-14 font (Helvetica, WinAnsi / Latin only). It cannot map or shape complex scripts, and it cannot do right-to-left joining.
Fix — embed a Unicode TrueType/OpenType font with Appearance::font (bytes) or
Appearance::font_path (a path), which Mudrit renders with full shaping. For right-to-left base
direction pair it with .rtl():
let ap = Appearance::new()
.font_path("NotoSansDevanagari.ttf") // embed a Unicode font for the script
.rtl(); // right-to-left base direction, when neededSee Appearance.
A token advertises ECDSA but produces broken signatures
Symptom — an ECDSA token is detected and reports CKM_ECDSA, but signing fails the sign-time
self-verify, or the output is invalid.
Cause — some hardware tokens advertise CKM_ECDSA yet ship a broken EC implementation (for
example, generating keys that aren't usable P-256). Mudrit re-verifies the token's r‖s signature
against the certificate and refuses to emit a file it can't verify — which is why the failure surfaces
at sign time rather than in Adobe later.
Fix — probe the device before relying on it, with the read-only ECDSA probe, and fall back to RSA (or the PFX backend) if the token's EC path is broken:
cargo run -p mudrit-keystore --example pkcs11_ecdsa_probe -- <module.dll>Verify EC tokens against your real device
Mudrit's ECDSA device path is validated end-to-end against SoftHSM2, but physical-token EC support
varies by vendor. Always confirm with pkcs11_ecdsa_probe (and a real test signature) on the exact
device you will use in production. See PKCS#11 tokens.