Sign and verify PDFs with Rust

A practical guide for Rust developers to add and validate digital signatures and read signature details from PDF documents.

Add PDFluent to your project

Add the pdfluent crate to Cargo.toml.

toml
[dependencies]
pdfluent = "1.0.0"

Add a digital signature to a PDF in Rust

Sign a PDF with a PKCS#12 certificate. PDFluent writes a conforming ISO 32000 signature that Adobe Acrobat, Preview, and other viewers can verify.

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

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"))?;
    doc.save("signed.pdf")?;
    Ok(())
}
  1. Load your PKCS#12 certificate

    Read the .p12 or .pfx certificate file and create a PdfSigner. The certificate must include the private key.

    rust
    use pdfluent::Pkcs12Signer;
    
    let signer = Pkcs12Signer::from_pfx_file("my_cert.p12", "your_p12_password")?;
  2. Configure signature metadata

    Set the reason, location, and contact info. These appear in the signature panel in PDF viewers.

    rust
    use pdfluent::SignOptions;
    
    let opts = SignOptions::new()
        .reason("I approve the content of this document")
        .location("Amsterdam, NL")
        .contact_info("legal@example.com")
        .field_name("Signature1");
  3. Position the visible signature appearance

    Add a visible signature box on a specific page and position. Skip this step for invisible signatures.

    rust
    use pdfluent::SignOptions;
    
    // Place a visible signature rectangle on page 0: [x1, y1, x2, y2] in points
    let opts = SignOptions::new()
        .reason("Approved")
        .visible_rect(0, [350.0, 50.0, 550.0, 110.0]);
  4. Sign the document and save

    Call sign() then save(). The output file contains the cryptographic signature bytes embedded in the PDF structure.

    rust
    doc.sign(&signer, opts)?;
    doc.save("contract_signed.pdf")?;
    
    println!("Signed.");
  • PDF signing is incremental: the original bytes are not modified, the signature is appended. This preserves prior signatures.
  • For LTV (Long-Term Validation), call opts.embed_ocsp(true) to include the OCSP response in the signature.
  • Self-signed certificates will produce a warning in Acrobat. Use a certificate from a trusted CA for production use.
  • The signature field name must be unique in the document. Signing a field that already exists replaces the signature.

Verify a PDF digital signature in Rust

Check that a PDF signature is cryptographically valid and that the document has not been modified since it was signed.

rust
use pdfluent::PdfDocument;

fn main() -> pdfluent::Result<()> {
    let doc = PdfDocument::open("signed.pdf")?;
    let report = doc.verify_signatures()?;
    println!("signed: {}, all valid: {}", report.is_signed(), report.all_valid());
    Ok(())
}
  1. Open the signed PDF

    Load the document. A read-only borrow is sufficient for verification.

    rust
    use pdfluent::PdfDocument;
    
    let doc = PdfDocument::open("signed_invoice.pdf")?;
  2. List signatures in the document

    Call doc.signatures() to get all signature fields. Each item includes the field name, signing time, and the raw certificate chain.

    rust
    let signatures = doc.signatures()?;
    println!("Found {} signature(s)", signatures.len());
    
    for sig in &signatures {
        println!("Field: {}", sig.field_name);
        println!("PdfSigner: {}", sig.signer_name);
        println!("Timestamp: {:?}", sig.timestamp);
    }
  3. Verify signature integrity

    SignatureVerifier checks that the signed byte range matches the current file contents. If any byte outside the signature field has changed, integrity_valid is false.

    rust
    let report = doc.verify_signatures()?;
    
    if report.all_valid() {
        println!("OK - all signatures valid, document not modified");
    } else {
        println!("FAIL - a signature is invalid or the document was modified");
    }
  4. Verify the certificate chain

    Check that the signing certificate chains to a trusted root. Supply your own trust store or use the system store.

    rust
    let report = doc.verify_signatures()?;
    println!("Signed: {}", report.is_signed());
    
    for v in report.validations() {
        println!("Field {}: {:?}", v.info.field_name, v.status);
    }
  • integrity_valid checks cryptographic hash only. certificate_trusted checks the CA chain separately.
  • PDF signatures cover a specific byte range. Content added after signing (incremental updates) falls outside that range.
  • For LTV-enabled signatures, call result.ltv_valid() to check embedded OCSP and CRL data.
  • A self-signed certificate will produce a `SignatureStatus::Unknown` result, as the SDK cannot establish trust with external anchors.

Read digital signature details from a PDF in Rust

Inspect the signer certificate, signing time, and signature coverage for each digital signature in a PDF.

rust
use pdfluent::PdfDocument;

fn main() -> pdfluent::Result<()> {
    let doc = PdfDocument::open("signed.pdf")?;
    for sig in doc.signatures()? {
        println!("{} by {}", sig.field_name, sig.signer_name);
    }
    Ok(())
}
  1. Open the signed PDF

    doc.signatures() returns an iterator over all digital signature fields in the AcroForm.

    rust
    let doc = PdfDocument::open("signed.pdf")?;
  2. List all signatures

    Each Signature object provides access to the signer name from the certificate, signing time, and field name.

    rust
    for sig in doc.signatures() {
        println!("Field: {}", sig.field_name());
        println!("PdfSigner: {}", sig.signer_name().unwrap_or("(unknown)"));
    }
  3. Read certificate details

    sig.certificate() returns the end-entity certificate. You can inspect the subject, issuer, serial number, and validity period.

    rust
    let cert = sig.certificate()?;
    println!("Subject:    {}", cert.subject());
    println!("Issuer:     {}", cert.issuer());
    println!("Serial:     {}", cert.serial_number_hex());
    println!("Valid from: {:?}", cert.not_before());
    println!("Valid to:   {:?}", cert.not_after());
  4. Check signing time and byte range

    The signing time may come from the certificate or from an embedded timestamp token. covers_whole_document checks whether the byte ranges cover the entire file.

    rust
    println!("Signing time: {:?}", sig.signing_time());
    println!("Has timestamp token: {}", sig.has_timestamp_token());
    println!("Covers whole document: {}", sig.covers_whole_document());
    
    let (ranges_bytes, total_bytes) = sig.byte_range_coverage(&doc)?;
    println!("Covered {}/{} bytes", ranges_bytes, total_bytes);
  5. Check if the document has been modified after signing

    If the byte ranges do not cover the entire file, content was appended after signing. This does not mean the signature is invalid, but it may indicate incremental updates.

    rust
    if !sig.covers_whole_document() {
        println!("Warning: document was modified after signing.");
    }
  • Reading signature info does not verify the cryptographic integrity. Call sig.verify() to perform a cryptographic check.
  • A PDF may contain multiple signatures, each covering different byte ranges. This is normal in multi-party signing workflows.
  • The signing time in the signature dictionary is set by the signer and can be spoofed. Use has_timestamp_token() and embedded TSA tokens for trusted time.
  • Certificate chain validation requires a trust store. Pass a custom trust store with sig.verify_with_trust_store(&store).