Mudrit
Guides

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.

Part of whole workspace

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 works

The 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 — call sign_pdf_reported to get an LtvReport of exactly what landed.
  • Or inject pre-fetched revocation material via ValidationMaterial and go offline, 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 headroom

Signing 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

Symptomprepare_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 needed

See 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.

Next

On this page