How-to guides/Errors & Debugging
Signature Validation Error
PDF signature is invalid or certificate has expired

How to fix an invalid or expired PDF digital signature

PDF signature validation fails when the signing certificate has expired, the chain of trust is broken, the document was modified after signing, or the hash algorithm doesn't match. This guide explains each cause and how to diagnose and fix signature errors in Rust with PDFluent.

Why this happens

Signing certificate has expired

X.509 certificates used to sign PDFs have an explicit validity period (notBefore / notAfter). Once expired, validators reject the signature as untrusted. Many organisations sign PDFs with short-lived certificates without enabling long-term validation (LTV), so the signature becomes invalid over time.

Chain of trust is broken or root CA is not trusted

A PDF signature is only valid if the full certificate chain — from the signing certificate up through any intermediate CAs to a trusted root — can be verified. If an intermediate certificate is missing from the signature, or if the root CA is not in the verifier's trust store, validation fails.

PDF modified after signing

A digital signature covers a specific byte range of the PDF. If any byte outside the signed range changes after signing (incremental updates, appended pages, metadata changes), the hash no longer matches and the signature is reported as invalid. This is by design — it is the core security guarantee of PDF signatures.

How to fix it

1

Check certificate validity dates

Before trusting a signature, verify that the certificate was valid at the time of signing (not just today). PDFluent returns the signing time from the signature dictionary and checks it against the certificate validity window.

use pdfluent::Document;

let doc = Document::open("signed.pdf")?;

for sig in doc.signatures() {
    let sig = sig?;
    println!("Signer: {}", sig.subject_dn());
    println!("Signed at: {}", sig.signing_time());
    println!("Cert valid from: {}", sig.cert_not_before());
    println!("Cert valid until: {}", sig.cert_not_after());
    println!("Signature covers full document: {}", sig.covers_whole_file());
}
2

Verify the full certificate chain

To validate the chain of trust, PDFluent needs access to the root CAs and any intermediate certificates. You can provide a custom trust store or use the system trust store. If a certificate in the chain is missing, PDFluent reports which one is absent.

use pdfluent::{Document, TrustStore};

let doc = Document::open("signed.pdf")?;

// Use system trust store (includes common root CAs)
let trust = TrustStore::system();

for sig in doc.signatures() {
    let validation = sig?.validate(&trust)?;

    if validation.is_valid() {
        println!("Signature is valid and trusted");
    } else {
        println!("Validation failed: {:?}", validation.errors());
    }
}
3

Ensure PDF is not modified after signing

To confirm a signature covers the full document, check that the signed byte range includes all content up to the end of file. If the document has been modified post-signing, PDFluent reports the byte ranges that are outside the signature coverage.

use pdfluent::Document;

let doc = Document::open("signed.pdf")?;

for sig in doc.signatures() {
    let sig = sig?;
    if !sig.covers_whole_file() {
        eprintln!(
            "Warning: signature does not cover the full document.              {} bytes are outside the signed range.",
            sig.unsigned_byte_count()
        );
    }
}

Frequently asked questions