How-to guides/Digital Signatures

Add a PAdES-LTV digital signature to a PDF in Rust

Embed OCSP responses, CRLs, and certificate chains so your PDF signatures remain verifiable years after the signing certificate expires — required for eIDAS long-term validity.

rust
use pdfluent::{PdfDocument, Pkcs12Signer, SignOptions, PadesProfile};

fn main() -> pdfluent::Result<()> {
    let mut doc = PdfDocument::open("contract.pdf")?;
    let signer = Pkcs12Signer::from_pfx_file("cert.p12", "pfx-password")?;
    doc.sign(
        &signer,
        SignOptions::new().reason("Approved").profile(PadesProfile::LongTerm),
    )?;
    doc.save("signed-ltv.pdf")?;
    Ok(())
}

Step by step

1

Load your signing certificate

Read the PKCS#12 (.p12 or .pfx) file containing your signing certificate and private key. For production use, the certificate must be issued by a trusted CA — self-signed certificates will not satisfy eIDAS requirements.

rust
use pdfluent::{PdfDocument, Pkcs12Signer, SignOptions, PadesProfile};

let signer = Pkcs12Signer::from_pfx_file("signing_cert.p12", "cert_password")?;
2

Choose the right PAdES level

PAdES defines four levels of increasing archival strength. B-B is basic; B-T adds a timestamp; B-LT embeds OCSP responses and CRL data for long-term validation; B-LTA adds a second archival timestamp over all that data. For eIDAS compliance on documents that must remain valid for years, use B-LT at minimum.

rust
use pdfluent::PadesProfile;

// PadesProfile::BasicSignature  - basic, short-term
// PadesProfile::Timestamped     - adds a TSA timestamp
// PadesProfile::LongTerm        - embeds revocation data (LTV) [default]
// PadesProfile::LongTermArchive - archival timestamp over LT data

let profile = PadesProfile::LongTerm;
3

Sign the document — PDFluent embeds validation data automatically

At PAdES-B-LT, PDFluent contacts the OCSP responder and fetches current CRLs for every certificate in the chain, then embeds them in the PDF's Document Security Store (DSS). This happens automatically — you do not need to fetch OCSP responses manually.

rust
let opts = SignOptions::new()
    .reason("Contract approval")
    .location("Amsterdam, NL")
    .profile(PadesProfile::LongTerm);

let mut doc = PdfDocument::open("contract.pdf")?;
doc.sign(&signer, opts)?;
doc.save("contract_signed_ltv.pdf")?;
4

Optionally upgrade to PAdES-B-LTA for maximum archival longevity

PAdES-B-LTA adds a document timestamp over the entire DSS structure, including the embedded OCSP and CRL data. This seals the validation material against modification and extends the effective archival period as long as the timestamp algorithm remains trusted — typically several decades.

rust
let opts = SignOptions::new()
    .reason("Contract approval")
    .profile(PadesProfile::LongTermArchive);

doc.sign(&signer, opts)?;
doc.save("contract_signed_lta.pdf")?;

Notes and tips

  • PAdES-B-LT signing requires network access at signing time to fetch OCSP responses and CRLs. Ensure your signing environment can reach the CA's OCSP endpoint.
  • The Document Security Store (DSS) is written as part of the signed update. It does not invalidate or change the signature bytes.
  • For B-LTA, a Time Stamp Authority (TSA) URL is required. Use a publicly trusted TSA or your organization's internal TSA.
  • Redaction is irreversible — always test PAdES signing on a copy of the document before applying to originals.
  • EU eIDAS requires a Qualified Certificate for QES (Qualified Electronic Signature). PAdES-B-LT/LTA defines the format; the certificate class determines the legal weight.

Why PDFluent for this

Pure Rust

No JVM, no runtime, no DLL dependencies. Ships as a single native binary or WASM module.

Memory safe

Rust's ownership model prevents buffer overflows and use-after-free. No segfaults in PDF parsing.

Runs anywhere

Same code runs server-side, in Docker, on AWS Lambda, on Cloudflare Workers, or in the browser via WASM.

Frequently asked questions