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| Source | Constructor |
|---|---|
| In-memory bytes | PdfReader::new(bytes) / PdfReader::from(vec) — or pass bytes straight to sign_pdf |
| A file path | PdfReader::open("in.pdf")? |
| Any reader / stream | PdfReader::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 pathEncrypted 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 state | What sign_pdf does |
|---|---|
| Fresh (unsigned) | Normal sign; cfg.encrypt optionally protects the output |
| Already signed | Appends an incremental revision; prior signatures stay valid |
| Signed and encrypted | Appends 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.)