Skip to content

PAdES PDF Signer

By Romi Fadli, S.Kom. 10 min read Security & Secrets
On this page
A signed PDF with a QR stamp, wrapped in four nested frames for the signature, its timestamp, the embedded validation data, and the archive timestamp

PAdES PDF Signer is a Python service that puts a visible, legally meaningful signature on a PDF and makes sure it can still be verified long after every certificate behind it has expired. It signs to PAdES B-LTA — the long-term archival level of the European standard for PDF signatures — so the document carries its own timestamp, its own proof that no certificate in the chain was revoked, and an archive timestamp over all of it. It also verifies PDFs, reporting each signature in them.

I built it because a signature that only a live network can confirm is a signature with an expiry date. A reader opening a contract in ten years may find the certificate authority's responder gone and the signer's certificate long expired; the signature must still prove who signed, when, and that it was valid at that moment. Putting every piece of that proof inside the PDF is the whole job of this service.

At a glance

Language & frameworkPython, with FastAPI, pyHanko for PDF signatures, and aiohttp for transfers
ArchitectureTransaction Script Service
Version2.11.3
StatusStable, in production
FormA stateless HTTP service, shipped as a single executable, called by the signing application
Source codePrivate
What the module is and where it stands.

What it does

A. Signs a PDF with a visible stamp

The service takes a PDF and a stamp image — often a QR code — places the stamp in a box on a chosen page, and signs the document with the signer's own certificate. Each signature becomes a new field with a unique id, so a document can be signed by several people, one after another, without disturbing earlier signatures.

B. Embeds the proof that the signature was valid

At signing time the service collects every certificate in the chain and the revocation answers that show none of them was revoked, and stores them inside the PDF. A validator years later needs no network to know that the signature was good when it was made.

C. Seals the result with timestamps

The signature itself is timestamped by a timestamp authority, which proves when it was made; then a document timestamp is added over the whole file, including the validation data. That archive timestamp is what lets the signature outlive the algorithms and certificates it relies on.

D. Verifies signed PDFs

Given any PDF, the service reports every signature and document timestamp inside it: whether it is valid, who signed, when, why and where, how much the document changed after it, and whether long-term validation data is present.

E. Keeps the signer's secret out of every queue and log

Nothing upstream — queues, jobs, logs — ever holds the signer's passphrase in plain text, and it serves only the single signature it was given for.

Interface

CapabilityPurpose
Signsign the PDF behind one presigned link to PAdES B-LTA with a visible stamp, and upload the result to another; returns the signature id and the SHA-256 of the signed file
Verifyreport every signature and document timestamp in an uploaded PDF
Readinesschecks the certificate authorities, the timestamp authority, and storage in parallel, and names any that fail
The service's HTTP interface.

A signing request names the document, the stamp, the page and box where the stamp goes, the reason and location, and where the result should be written.

Standards

StandardWhat it guarantees here
PAdES B-LTA (ETSI EN 319 142)signatures that remain verifiable after their certificates expire, checked with an ETSI conformance checker
CMS signatures in PDF (ISO 32000)signatures added as incremental updates, so earlier signatures stay intact
RFC 3161, Time-Stamp Protocola signature timestamp and an archive timestamp from a trusted authority
RFC 5280 and RFC 6960revocation lists and OCSP answers collected at signing time and stored in the document
PKCS#12the signer's key and chain received as one protected bundle
W3C Trace Context and OpenTelemetrysigning, timestamping, and certificate retrieval appear as steps in the caller's trace
The standards the service follows, and what each one guarantees.

Inside

The service is a Transaction Script Service: each endpoint is one procedure that reads, in order, every step of a request — check the caller, unlock the signer's key, fetch the document and the stamp, build the validation context, sign, upload, record — and the work for each step sits in a module named after it: preparation, signing, trust, fetchers, verification, cryptography, telemetry. It is bundled into a single executable, so the container carries no source code.

Design decisions

The archival level was the goal, and I changed languages twice to reach it. The first version, in Python, reached the point where a widely used PDF reader accepted its long-term validation, and then stalled on the way to the full standard. Convinced the dead end was in the Python tooling, I moved the signer to Node.js, where a conformance checker's error count fell from three to one. Then the limit showed: the Node.js library could embed long-term validation only in the older Adobe style, not in the PAdES structure the standard requires. I moved back to Python with what the detour had taught me about how validation data has to be gathered, and the next version signed to full PAdES B-LTA with no errors in the ETSI conformance checker report.

Every piece of proof is collected before signing, not hoped for later. Twice, validation failed at signing time because a revocation answer was "too recent" — newer than the moment the validator was checking against. The first fix was to fetch the revocation lists of the root, the signing CA, and the timestamp authority in parallel before signing and hand them to the validation context directly; the second was a small tolerance for clock differences between services. Revocation checking is set to require an answer, so a signature is never embedded with a gap in its proof. The root certificate is left out of the document, because a validator must already trust it, not receive it from the file it is judging.

Signatures add to a document, never rewrite it. Each signature is written as an incremental update, so earlier signatures and their protection stay byte for byte as they were, and a document can collect signatures from several people. The writer also accepts PDFs with mixed cross-reference tables, so documents produced by a range of tools can be signed. Every signature gets a new field named with its own UUIDv7, and that name doubles as the signature id returned to the caller, so the record and the PDF always point at the same signature. The stamp is drawn without a border, so a QR code does not end up framed twice, and its box may start at zero, so it can sit flush against the edge of the page.

The signer's secrets are never stored anywhere on the way. The passphrase stays encrypted from the moment the application queues the job, so it never rests in job payloads or activity logs. The service holds no standing access of its own to the certificate authority: the authority decides, for each request and each caller, whether this key may be used, and nothing the service handles for a signature outlives it.

Files move by link, and only to storage the service knows. The document, the stamp, and the destination are all presigned links, so the application never streams a PDF through itself. Each link must point exactly to the expected storage origin and bucket, an allowlist kept in the source rather than in configuration, and anything else is rejected before a single byte is fetched. The response carries the SHA-256 of the signed file, so the application can find the document by its hash.

Verification is strict first, and judges each signature at its own moment. The verifier first checks that the upload is actually a PDF — its header and its end marker — and answers a fake file with a clear client error rather than a server failure. Each signature is then validated online and strictly; when that fails because the world has moved on since signing, it is evaluated again offline at its own signing time, from the validation data embedded in the document — exactly the proof the archival level exists to preserve. Trust roots are read from a mounted directory, so trusting a new root is a deployment change, not a code change.

Telemetry had to be made trustworthy before it could be trusted. Logging moved from writing straight to Elasticsearch to OpenTelemetry through a collector, continuing the trace of the application that called the service and passing it on to the timestamp authority and the certificate authority, so one signature appears as one trace across four services. Redaction inspects values, not only field names, so a presigned link's signature never reaches a log whatever the field is called. Three incidents shaped the rest. Failures were nearly invisible — 492 spans but only 14 log records in half an hour — so every rejected or failed request now goes through one handler that logs it, with a reason field kept as a string because the search index had already fixed that field's type. The readiness probe produced 99.8 per cent of all spans, so it is excluded from tracing. And inside the bundled executable two instrumentors silently switched themselves off, because the bundle lacked the package metadata they check; the build now copies that metadata, and revocation fetches appear in traces again. Metrics use delta temporality, which the collector's Elasticsearch exporter requires.

Readiness checks what signing actually needs. The service is ready only when the root CA, the signing CA, the timestamp authority, and storage all answer, checked in parallel, and an unready answer names which one failed. The probes go to each service directly rather than through the public proxy, so each dependency is measured where it actually runs.

Specifications

InputsPresigned links to the PDF, the stamp image, and the destination; the signer's certificate and protected passphrase; page, box, reason, and location
OutputsA PAdES B-LTA signed PDF uploaded to the destination; the signature id and SHA-256 to the caller; for verification, a report per signature
SignatureCMS in PDF, SHA-256, incremental update, visible stamp, signature and archive timestamps, embedded certificates and revocation data
Depends onThe signing CA for the signer's key and revocation data, the root CA, a timestamp authority, and S3-compatible storage
Verified withAn ETSI conformance checker and a widely used PDF reader
ObservabilityOpenTelemetry traces, logs, and metrics over OTLP
Runs asA single executable built in a multi-stage container image, with configuration and trust roots mounted
What the service takes, returns, and needs.

The certificate authority that holds each signer's key and answers the revocation questions embedded in every signature.

The timestamp authority that dates each signature and seals the archive.

The storage service that issues the links the document, the stamp, and the signed result travel through.

Where one signature's trace across four services ends up.

Terms

PAdES
PDF Advanced Electronic Signatures: the European standard for digital signatures in PDF, defined in levels from basic to long-term archival.
B-LTA
The long-term archival level of PAdES: a signature with a timestamp, embedded validation data, and an archive timestamp over the whole document.
Long-term validation
Embedding the certificates and revocation answers a validator will need, so a signature can be checked without contacting anyone.
Document timestamp
A timestamp applied to the entire PDF rather than to one signature, protecting everything added before it.
Incremental update
Appending changes to the end of a PDF instead of rewriting it, so earlier content and signatures remain exactly as they were.
PKCS#12
A file format that bundles a private key with its certificate and chain, protected by a password.

Technologies covered

  • Python
  • FastAPI
  • pyHanko
  • OpenTelemetry
  • OCSP
  • PAdES

About the author

Photo of Romi Fadli

Romi Fadli, S.Kom.

Solution Architect · Backend Engineer · Embedded Systems

In my day job I work as a solution architect. I design the system, lead the team that builds it — quality assurance, backend developers, a UI developer, DevOps and DevSecOps — and write the most critical modules myself, such as a certificate authority, a long-term PDF signer, and the core API of a personnel system.

I work across the whole stack that a running system actually needs. On the hardware side that means PCB design, microcontrollers, and embedded firmware — including RISC-V boards running FreeRTOS and Embassy, speaking MQTT over mutually authenticated TLS on a microcontroller with 64 KB of SRAM.

Most of my day-to-day work is software: Rust backends and API modules built on Clean Architecture, an OAuth2/OpenID Connect authorization server written from scratch, PAdES document signing, and certificate authorities — root CAs, timestamping authorities, and OCSP responders. I also build the frontends that sit on top of them.

Running all of that means infrastructure: Docker Compose stacks, networking, Mikrotik and RADIUS, observability with OpenTelemetry, and a steady habit of writing command-line tools and utilities for Linux when the existing ones do not fit.

Share

Enjoyed this?

Get an email when a new article is published.

Referenced by

A memory chip holding a key, circled by refresh arrows, beside a browser window where a signature stamp is being dragged into place on a PDF page

The Vue browser application of a document-signing platform: sessions held in memory and renewed by one queued refresh, PKCE sign-in, stamps placed precisely on PDF pages, identity checks with a face challenge, client consent, and role-based administration.

10 min read Read more →
A central core holding people, organisations, documents, and certificates, connected to eight surrounding services for storage, addresses, certificates, timestamps, the root, signing, thumbnails, and face challenges

The Laravel core of a document-signing platform: it owns users, identities, organisations, client systems, documents and their signed versions, stamps, and certificates, and orchestrates the services that sign, timestamp, render, and store — without ever holding a file.

9 min read Read more →
A locked offline host holding the root key, a signed revocation list crossing an air gap to a small publisher server, which serves the root certificate and the list to a validator

A tiny publish-only service that keeps a root CA reachable without making it touchable: it serves the root certificate and its revocation list, while the root key and every signature stay on an offline host — reusable by any project with its own CA.

7 min read Read more →
A purple root circle linked to a blue and an orange intermediate circle, which lead to a signed document with a green seal and to a clock.

Aurograf

DevOps

The digital signature platform of the City Government of South Tangerang: signers prove who they are, sign PDFs to PAdES B-LTA with certificates from the platform's own authorities, and anyone can verify the result — version 2, which I designed, built and operate.

10 min read Read more →

Related Articles

A memory chip holding a key, circled by refresh arrows, beside a browser window where a signature stamp is being dragged into place on a PDF page

The Vue browser application of a document-signing platform: sessions held in memory and renewed by one queued refresh, PKCE sign-in, stamps placed precisely on PDF pages, identity checks with a face challenge, client consent, and role-based administration.

10 min read Read more →
A central core holding people, organisations, documents, and certificates, connected to eight surrounding services for storage, addresses, certificates, timestamps, the root, signing, thumbnails, and face challenges

The Laravel core of a document-signing platform: it owns users, identities, organisations, client systems, documents and their signed versions, stamps, and certificates, and orchestrates the services that sign, timestamp, render, and store — without ever holding a file.

9 min read Read more →
A document's fingerprint passing through a clock, signed by a certificate below it, and returning as a time seal stamped on the same document

A Rust timestamp authority that stamps a document's hash with a signed, NTP-backed time per RFC 3161, so PDF signatures stay verifiable after their certificates expire — issuing and renewing its own signing certificate, with public OCSP and CRL.

9 min read Read more →