Open-source digital signatures for PDFs. Sign with a USB token (DSC) or smartcard from any website, or with a key held on your own server. Python backend, plain JS everywhere else.
One rule shapes the design: the PDF never leaves the server. The browser carries a 32-byte hash out and a ~256-byte signature back, so a 200 MB file signs as fast as a 200 KB one.
Status: working end to end. Signed output validates as PAdES. Not yet published to the extension stores.
Web page ──(cert, 32-byte hash, signature)── Your server (pyHanko, PDF stays here)
│
Extension ──(native messaging)── docsigner-host ──(PKCS#11)── USB token
- The page asks the extension for the user's certificates. The extension asks
docsigner-host, a small native app that reads them from the token. - The page sends the chosen certificate to your server. The server prepares the signature inside the PDF and returns the hash to sign.
- The token signs the hash. The PIN prompt happens in the native app, so the PIN never touches the browser or the network.
- The server embeds the signature and hands back a download link.
Server-held keys skip steps 1 and 3: one call to /api/sign-server-side.
The longer version, with the design decisions and why:
docs/architecture.md.
Linux, macOS, and Windows. Needs Python 3.10 or newer. Use python3 on
Linux/macOS, python on Windows.
python3 -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install -e ./core -e ./server
cp .env.example .env # Windows: copy .env.example .env
python -m docsigner_server # http://127.0.0.1:8001Run it from the repo root, since .env is read from the directory you launch in.
Then serve the repo root and open the demo (browsers block file:// fetch, and
the demo imports js/docsigner.js from its sibling folder):
python3 -m http.server 8080 # then open http://localhost:8080/demo/Server-side signing works right there. To sign with a token you also need the
extension (extension/README.md) and the native host
(host/README.md) installed.
Prefer a desktop app to a browser? desktop/README.md
batch-signs a folder with no server and no extension.
| Folder | What it is | Read first |
|---|---|---|
core/ |
docsigner-core, the Python signing library (pyHanko underneath) |
docs/core.md |
server/ |
docsigner-server, the HTTP API + server/openapi.json |
docs/server.md |
host/ |
docsigner-host, the native binary that talks to tokens (Rust, ~1 MB) |
docs/host.md |
desktop/ |
docsigner-desktop, batch-sign a folder locally |
docs/desktop.md |
extension/ |
WebExtension (MV3) for Chrome, Edge, Brave, Firefox | extension/README.md |
js/ |
docsigner.js, the page-side library (one file, no deps) |
js/README.md |
demo/ |
A working demo page, and the integration example | demo/README.md |
trust/ |
Trust anchors for the LTV profiles | server/README.md |
Two things sit above all of it:
CONTRACTS.md— every wire format, frozen. HTTP routes, native messaging commands, the page bridge, error codes.docs/— the index, and how the docs are organised.
The Frappe/ERPNext app lives in its own repo, docsigner_integration: sign print
formats from the desk, bulk one-PIN signing, auto-sign on submit, QR verification
e-copies. It embeds core/ as a pip dependency. Its plan is in
docs/frappe-app.md until that repo takes a copy.
PAdES B-B through B-LTA per ETSI EN 319 142-1, plus CCA-LTV and CCA-LTA for
India. Detached CAdES-BES over any file, and enveloped XAdES-B over XML. RSA and
ECDSA, SHA-256/384/512. Full coverage and the country story:
docs/architecture.md.
pip install -e ./core -e ./server
pip install -r requirements-dev.txt
pytest core/tests server/tests
PYTHONPATH=desktop/backend pytest desktop/backend/tests
cargo test --manifest-path host/Cargo.toml
cd js && node --testEverything runs without hardware: the HTTP flow is tested with in-memory keys,
and the host's tests assert invariants that hold whether or not a token is
plugged in. The end-to-end suite has a gated real-token path,
DOCSIGNER_E2E_REAL_TOKEN=1 pytest e2e/test_host_e2e.py.
Testing with a real token before a release is a manual step, and the list is
docs/release-checklist.md.
Extension store listings, signed host binaries (Authenticode on Windows,
notarization on macOS), and real-token runs on all 3 operating systems. Details
in docs/roadmap.md.
Apache-2.0. Copyright 2026 Resilient Software Services LLP. Full text in
LICENSE; the attributions Apache-2.0 asks you to carry forward are
in NOTICE.
The bundled handwriting fonts are under the SIL Open Font License 1.1, and the
logo comes from Lucide under ISC. Both are covered in NOTICE.