Mudrit
Concepts

Signing Flow

How sign_pdf turns any input — a path, bytes, a stream, an encrypted or already-signed PDF — into a signed document

sign_pdf is the one entry point for signing. It takes an input, a &dyn Signer, and a SignConfig, and returns the signed PDF bytes. Everything about where the input comes from and what state it's already in is handled for you — the same call signs a fresh PDF, a password-protected one, or one that already carries signatures.

use mudrit_pdfsign::prelude::*;

let signer = PfxSigner::from_file("samples/ABC12.pfx", "ABC12")?;
let pdf    = std::fs::read("in.pdf")?;
let cfg    = SignConfig::default();

let signed = sign_pdf(&pdf, &signer, &cfg)?;   // works with ANY Signer
std::fs::write("out.pdf", &signed)?;

The pipeline at a glance

Input sources — PdfReader

sign_pdf accepts anything convertible into a PdfReader, so the input side is never a constraint. Plain bytes convert automatically, and a PdfReader can wrap a file, an in-memory buffer, or any stream.

sign_pdf(bytes,                           &signer, &cfg)?;  // &[u8] / Vec<u8> / &Vec<u8> / [u8; N]
sign_pdf(PdfReader::open("in.pdf")?,      &signer, &cfg)?;  // a file path
sign_pdf(PdfReader::from_reader(stream)?, &signer, &cfg)?;  // any std::io::Read
PdfReader::open("in.pdf")?.sign(&signer, &cfg)?;            // fluent shorthand
SourceConstructor
In-memory bytesPdfReader::new(bytes) / PdfReader::from(vec) — or pass bytes straight to sign_pdf
A file pathPdfReader::open("in.pdf")?
Any reader / streamPdfReader::from_reader(src)? — a File, TcpStream, stdin, an HTTP body, a decompressor, …

A from_reader source is read fully into memory, because computing the signature's ByteRange needs the whole document. PdfReader also offers .bytes() / .len() / .is_empty() / .into_bytes(), and a password-safe Debug.

The sign_file helper

For the common read-sign-write-to-disk case, sign_file wraps the path handling:

sign_file("in.pdf", "out.pdf", &signer, &cfg)?;   // read path → sign → write path

Encrypted input: decrypt, then sign

A password belongs to the input document, not to how you sign it — so the open password rides on the input via PdfReader, and SignConfig stays purely about the signature.

let signed = sign_pdf(PdfReader::open("locked.pdf")?.password("secret"), &signer, &cfg)?;

The SDK decrypts the input in memory before signing. .maybe_password(Option<…>) is convenient when the password already lives in an Option. Signing an encrypted PDF without a password, or with the wrong one, fails with a clear error rather than producing a bad signature.

Input password vs. output encryption

By default the signed output is unencrypted. Keeping (or adding) protection on the output is a signing concern, so it lives on SignConfig (.encrypt(…) / .keep_password()) — see Encryption.

Auto-append: sign_pdf is smart about already-signed input

You don't choose between "sign" and "add a signature" — sign_pdf detects the document's state and routes accordingly.

If the input is already signed, sign_pdf appends the new signature as an incremental revision, so the existing signatures stay valid — instead of a fresh sign that would rebuild the document and break them.

let once  = sign_pdf(&pdf,  &signer, &cfg)?;   // first signature
let twice = sign_pdf(&once, &signer, &cfg)?;   // auto-appended second signature (both valid)

If that already-signed input is also encrypted, pass the open password and the appended revision is encrypted with the document's own key — so the output stays protected with the same password and cipher (any cipher, including AES-256).

let resigned = sign_pdf(PdfReader::from(signed_protected).password("asd"), &signer, &cfg)?;
Input stateWhat sign_pdf does
Fresh (unsigned)Normal sign; cfg.encrypt optionally protects the output
Already signedAppends an incremental revision; prior signatures stay valid
Signed and encryptedAppends a revision encrypted with the document's own key

Explicit field-lock control

For explicit control there is the lower-level add_locked_signature(input, page, rect, field, signer, tsa) — a FieldMDP field-lock plus LTV and timestamp. Most callers should let sign_pdf route automatically.

Reporting LTV: sign_pdf_reported

sign_pdf_reported behaves like sign_pdf but also returns an LtvReport describing the validation material that actually landed in the document's /DSS. This keeps a best-effort LTV sign from being silent about an incomplete result.

let (signed, report) = sign_pdf_reported(&pdf, &signer, &cfg)?;
if !report.complete {
    eprintln!("signed without revocation info ({} certs, no CRL/OCSP)", report.certs);
}

The report counts the certs, crls, and ocsps streams embedded, and complete is true when at least one revocation response (CRL or OCSP) was included. See LTV for the policy knobs (LtvPolicy::Require fails loudly instead of writing a half-LTV document).

Validation is external

Mudrit produces signed PDFs; it does not ship a signature-verification API. To confirm a signature validates, open the output in Adobe Acrobat / Reader or check it with pyHanko. (Hardware backends do a self-verify of their own signature against the certificate at sign time — that is an internal safety net, not a public verification API.)

Next

On this page