A practical guide for Rust developers to add and validate digital signatures and read signature details from PDF documents.
Add the pdfluent crate to Cargo.toml.
[dependencies]
pdfluent = "1.0.0"Sign a PDF with a PKCS#12 certificate. PDFluent writes a conforming ISO 32000 signature that Adobe Acrobat, Preview, and other viewers can verify.
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(())
}Read the .p12 or .pfx certificate file and create a PdfSigner. The certificate must include the private key.
use pdfluent::Pkcs12Signer;
let signer = Pkcs12Signer::from_pfx_file("my_cert.p12", "your_p12_password")?;Set the reason, location, and contact info. These appear in the signature panel in PDF viewers.
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");Add a visible signature box on a specific page and position. Skip this step for invisible signatures.
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]);Call sign() then save(). The output file contains the cryptographic signature bytes embedded in the PDF structure.
doc.sign(&signer, opts)?;
doc.save("contract_signed.pdf")?;
println!("Signed.");Check that a PDF signature is cryptographically valid and that the document has not been modified since it was signed.
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(())
}Load the document. A read-only borrow is sufficient for verification.
use pdfluent::PdfDocument;
let doc = PdfDocument::open("signed_invoice.pdf")?;Call doc.signatures() to get all signature fields. Each item includes the field name, signing time, and the raw certificate chain.
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);
}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.
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");
}Check that the signing certificate chains to a trusted root. Supply your own trust store or use the system store.
let report = doc.verify_signatures()?;
println!("Signed: {}", report.is_signed());
for v in report.validations() {
println!("Field {}: {:?}", v.info.field_name, v.status);
}Inspect the signer certificate, signing time, and signature coverage for each digital signature in a PDF.
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(())
}doc.signatures() returns an iterator over all digital signature fields in the AcroForm.
let doc = PdfDocument::open("signed.pdf")?;Each Signature object provides access to the signer name from the certificate, signing time, and field name.
for sig in doc.signatures() {
println!("Field: {}", sig.field_name());
println!("PdfSigner: {}", sig.signer_name().unwrap_or("(unknown)"));
}sig.certificate() returns the end-entity certificate. You can inspect the subject, issuer, serial number, and validity period.
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());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.
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);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.
if !sig.covers_whole_document() {
println!("Warning: document was modified after signing.");
}