Skip to content

AlchemicStudio/Cachet

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

30 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Cachet

Batch PDF signing for Belgian administrative workflows, usable from both a command line and a CustomTkinter GUI, with three modes:

  • beid — qualified (QES-grade) cryptographic signatures with the Belgian eID card via PKCS#11, stamping a visible vignette (cardholder photo + name + date) on each document. One PIN entry per document.
  • azure — personal advanced (AES) signatures with the user's own certificate held in Azure Key Vault, after a single Microsoft Entra ID login per batch; only the document digest ever leaves the machine.
  • image — a simple image stamp (no cryptographic value).

Signatures are PAdES up to B-LTA by default: trusted RFC 3161 timestamp, embedded revocation info (LTV), and an archival timestamp chain — with trust anchors drawn from the EU Trusted List (eID) or the organisation's internal CA (Azure). Every signed file is re-validated on the spot and the achieved level is reported; levels are never silently downgraded.

Batches are validated against a template PDF (exact page count + dimensions) before signing, outputs never overwrite existing files, and the whole thing ships as two standalone PyInstaller binaries (windowed GUI + headless CLI) for Linux and Windows.

⚖️ The eID mode uses the card's non-repudiation certificate, legally equivalent to a handwritten signature. The national register number is embedded in every signature produced — mind the distribution of the signed PDFs.


The three modes

Mode Requires Nature Output
beid reader + eID card + PIN per document cryptographic eID signature (pyHanko via the PKCS#11 middleware) — qualified-grade (QES) visible vignette: cardholder photo + "Signed by:" / name / date
azure Microsoft (Entra ID) login, one per batch + a personal key/cert in Azure Key Vault cryptographic personal signature — advanced (AES), not qualified visible vignette: "Signed by:" / name / date (no photo)
image nothing image stamp (this is not a cryptographic signature) the supplied image, placed at a chosen position

In all modes, the same vignette/image + page + position is applied to all the documents in the batch — template validation guarantees the files are geometrically identical.

eID vs Azure — which one?

  • beid (eID) carries the strongest legal weight (qualified certificate, legally equivalent to a handwritten signature) but needs the physical card, a reader, and one PIN entry per document.
  • azure signs with the user's personal certificate held in Azure Key Vault after one interactive Microsoft login per batch — far better ergonomics for large batches, but the result is an advanced electronic signature (AES): fine for internal documents; for documents relied upon by external third parties, a publicly recognised (qualified) issuer would be needed.
  • image is a visual stamp only — no cryptographic value.

Template validation (--template)

If a template PDF is supplied, each input is accepted only if it has the same page count AND exactly identical per-page dimensions (strict equality, no tolerance). Rejected files are never signed; the rejection reason is displayed (CLI summary / GUI table).

Output

Files are written as {name}_signe.pdf in the output folder and are never overwritten: on collision, - 1, - 2, … are appended.


Requirements (runtime)

  • beid mode: the Belgian eID middleware installed (https://eid.belgium.be), which provides the PKCS#11 library (libbeidpkcs11.so / beidpkcs11.dll / …dylib), a reader + inserted eID card, and the PC/SC service (pcscd) running. The PIN is requested for each document. Levels ≥ b-t additionally need network access (TSA, EU trusted list, OCSP/CRL — see Signature levels below); b-b is offline.
  • azure mode: no hardware — outbound network to login.microsoftonline.com, the Key Vault URL, the TSA (≥ b-t) and the internal CA's CRL/OCSP (≥ b-lt); per-user keys provisioned in Key Vault (see the azure section above).
  • image mode: nothing special — pure PDF stamping.
  • Graphical interface: customtkinter + a Python with tkinter and a display. The page preview (step 6) is rendered by pypdfium2 (the bundled PDFium engine) — no external dependency to install. (poppler / pdftoppm is now only an optional fallback if already present.)

Installation (from source)

python3 -m venv venv
./venv/bin/pip install -r requirements.txt
# GUI on Ubuntu/Debian, if tkinter is missing:  sudo apt install python3-tk

Usage — command line

# eID signing (vignette bottom-right of the last page), PAdES B-LTA by default:
./venv/bin/python sign_pdfs_beid.py --input ../pdfs --output ../signes --mode beid

# eID signing at a lighter level (basic signature, fully offline):
./venv/bin/python sign_pdfs_beid.py --input ../pdfs --output ../signes --pades-level b-b

# Image stamp (no card), validated against a template:
./venv/bin/python sign_pdfs_beid.py --mode image \
  --template ../pdfs/MODELE.pdf --input ../pdfs --output ../signes \
  --image-path signature.png --page 1 --x 360 --y 150

# Azure mode (personal Key Vault certificate, one Microsoft login per batch):
./venv/bin/python sign_pdfs_beid.py --mode azure \
  --azure-vault-url https://myorg-sign.vault.azure.net \
  --azure-trust-anchors ./internal-ca-chain.pem \
  --input ../pdfs --output ../signes

# Graphical interface:
./venv/bin/python sign_pdfs_beid.py --gui

Options

Flag Meaning
--gui launch the graphical interface; otherwise run in console mode.
--input <paths…> files and/or folders to process (folders are globbed for *.pdf).
--output <folder> output folder ({name}_signe.pdf, never overwritten).
--template <pdf> template PDF; if supplied, inputs are validated against it.
--mode beid|image signature mode (default beid).
--image-path <img> image to stamp (required in --mode image).
--page <N> target page, 1-based. Image: insertion page. beID: vignette page.
--x <pt> --y <pt> lower-left corner, in points from the page's bottom-left. beID: omit both ⇒ bottom-right of the last page.
--pades-level <lvl> PAdES baseline level: b-b, b-t, b-lt, b-lta (default b-lta). See Signature levels below.
--timestamp-url <url> RFC 3161 TSA for levels ≥ b-t. Precedence: flag > CACHET_TSA_URL env > http://timestamp.digicert.com.
--trust-list-url <url> EU LOTL URL seeding the LTV trust anchors. Precedence: flag > CACHET_LOTL_URL env > the official EU URL.
--refresh-trust-list force re-download of the EU trusted list (bypass the 24 h cache).
--digest <alg> signature digest: sha256 (default), sha384, sha512.
--no-verify skip the post-signing self-verification (levels ≥ b-t).
--pades deprecated no-op (PAdES is now the default); use --pades-level.
--legacy-cms deprecated: legacy non-PAdES adbe.pkcs7.detached signature (no timestamp, no LTV). Incompatible with levels above b-b.
--lib <path> path to the eID PKCS#11 library (otherwise OS-default value).
--field <name> base name of the signature field (beid/azure modes).
--azure-vault-url <url> Key Vault URL (required in azure mode; env CACHET_AZURE_VAULT_URL).
--azure-key-name <name> explicit key override — bypasses the per-user derivation and is flagged in the output (env CACHET_AZURE_KEY_NAME).
--azure-key-name-template <tpl> per-user key derivation, default sig-{upn}; placeholders {upn}, {upn_local}, {oid}, sanitised to the Key Vault charset (env CACHET_AZURE_KEY_NAME_TEMPLATE).
--azure-cert-name <name> certificate name if it differs from the key name (env CACHET_AZURE_CERT_NAME).
--azure-auth <m> interactive (browser; GUI default), device-code (CLI default), default (DefaultAzureCredential — testing/CI only, breaks the per-user model). Env CACHET_AZURE_AUTH.
--azure-trust-anchors <path> PEM/DER file or directory with the internal CA chain used as LTV trust anchors in azure mode (required for b-lt/b-lta and for self-verification at b-t). Env CACHET_AZURE_TRUST_ANCHORS.
--azure-graph opt-in: use the Microsoft Graph /me displayName for the vignette.

Signature levels (PAdES baseline, ETSI EN 319 142-1)

Level Adds Network needed
b-b basic eID signature none (offline)
b-t + trusted RFC 3161 timestamp TSA (+ EU trusted list unless --no-verify)
b-lt + revocation info (OCSP/CRL) embedded in the document (LTV) TSA, EU trusted list, OCSP/CRL endpoints
b-lta + archival document-timestamp chain (default) same as b-lt
  • Long-term validation (LTV): from b-lt upward, everything needed to validate the signature later (CA certificates from the EU trusted list, OCSP/CRL responses) is embedded at signing time, so the PDF stays verifiable after certificates expire. b-lta adds a document timestamp so the evidence chain itself stays provable — for genuine archival, that chain must be renewed periodically (see BUILD.md).
  • ⚠ Free vs qualified timestamps: the default TSA (http://timestamp.digicert.com) is free and yields technically valid B-T/B-LTA signatures, but not qualified timestamps in the eIDAS sense. For genuine qualified long-term preservation, point --timestamp-url at a qualified TSA (see the EU trusted list for QTSPs offering QTST services).
  • Self-verification: after writing each signed PDF (levels ≥ b-t), the file is re-opened and validated, and the achieved level is reported in the summary (e.g. PAdES-B-LTA, LTV ok). A mismatch marks the document failed — the level is never silently downgraded; --no-verify skips this check.
  • Offline behaviour: b-b and image mode work fully offline. Levels ≥ b-t fail with an actionable error naming the unreachable endpoint (TSA, EU trusted list, OCSP/CRL).
  • 🔒 Privacy: the signer's national register number (RRN) is embedded in every eID signature (it is part of the certificate). The CLI warns at startup and in the summary — mind how signed PDFs are distributed.

Azure mode (--mode azure) — personal AES from Key Vault

  • What it is: each user signs in their own name with their personal certificate + non-exportable key held in Azure Key Vault, after an interactive Microsoft Entra ID login (one per batch — not per document). Only the document digest is sent to Azure; the document never leaves the machine. The result is an advanced electronic signature (AES) — appropriate for internal documents; it is not a qualified signature (QES).
  • Prerequisite (Azure admin): provision, per user, a Key Vault key + certificate issued by the organisation's internal CA (ADCS / Entra-issued), named after the key template (default sig-{upn}, e.g. sig-jane-doe-example-org), and grant each user get on certificates and sign on their own key (access policy / RBAC). Provisioning itself is out of scope for this tool.
  • Per-user rule: the key name is derived from the signed-in user's UPN, so a user can only sign with their own key; an explicit --azure-key-name bypass is visibly flagged, and Key Vault access policy remains the hard authorization gate.
  • Trust/LTV: azure mode builds its validation context from the internal CA chain (--azure-trust-anchors) — the EU trusted list is not used here (it is eID-specific). For b-lt/b-lta the internal CA must publish reachable CRL/OCSP endpoints, otherwise signing fails with a clear error (never a silent downgrade).
  • Network: login.microsoftonline.com, the vault URL, the TSA (≥ b-t) and the internal CA's CRL/OCSP endpoints (≥ b-lt) must be reachable.

Backward compatibility: the legacy positional form inputs… output_folder is still accepted if --input/--output are absent.

Usage — graphical interface

A vertical wizard walks through the flow: 1. template → 2. files → 3. output folder → 4. validation (pass/fail table) → 5. mode (eID/image/Azure + PAdES level selector, default b-lta; in Azure mode a panel offers the vault settings and a "Sign in with Microsoft" action) → 6. page + position (actual page preview, click to place) → 7. launch → 8. per-document summary.


Standalone executables (Linux & Windows)

The project compiles into two standalone binaries per OS (a windowed GUI cachet, a console CLI cachet-cli) — no Python required on the target machine. Official builds are published as GitHub Releases: merging develop into main automatically tags v{version} and attaches the Linux/Windows packages (see Release process in BUILD.md). See BUILD.md for all the routes (native Linux, native Windows, Wine, and GitHub Actions CI).

./build_linux.sh        # Linux  -> dist/cachet , dist/cachet-cli
build_windows.bat       # Windows -> dist\cachet.exe , dist\cachet-cli.exe

The eID middleware remains a runtime dependency (beid mode) and is never bundled; the page preview, for its part, works without installing anything (PDFium bundled via pypdfium2).

Tests

Headless unittest suite (no card, no tkinter):

./venv/bin/python -m unittest -v

Project structure

File Role
sign_pdfs_beid.py core + CLI entry point (business logic, importable without tkinter).
trust.py EU trusted-list (LOTL) trust provider: anchors for LTV in beid mode, with local cache.
azure_signer.py azure mode: Entra ID login, per-user Key Vault key/cert resolution, pyHanko signer (digest-only signing).
gui.py CustomTkinter interface (façade over the core).
gui_main.py entry point of the windowed binary (opens the GUI).
test_sign_pdfs_beid.py, test_trust.py, test_azure.py unittest test suites.
cachet.spec PyInstaller recipe (two binaries).
build_*.sh / build_windows.bat build scripts.
.github/workflows/build.yml CI: Windows + Linux binaries as artifacts.
BUILD.md detailed packaging guide.

About

Batch PDF signing with the Belgian eID card (QES) or a personal Azure Key Vault certificate (AES), producing PAdES B-LTA signatures with timestamping, embedded revocation info, and post-signing verification. Template-validated batches, CLI + CustomTkinter GUI, shipped as standalone Linux/Windows binaries.

Topics

Resources

License

Stars

Watchers

Forks

Contributors