Skip to content

pitboc/pdf-qes-signer

Repository files navigation

PDF QES Signer

A GUI tool for visually placing signature fields in PDF documents and applying qualified electronic signatures (QES) via PKCS#11 / smartcard.

Repository: Primary: codeberg.org/pitbo/pdf-qes-signer Mirror: github.com/pitboc/pdf-qes-signer (read-only)

Issues and contributions: Please use the Codeberg repository. The GitHub repository is a read-only mirror and does not accept issues or pull requests.

Background and motivation

Qualified electronic signatures (QES) in Germany require a signature card issued by an accredited trust service provider. Deutsche Telekom Security GmbH issues TCOS-based QES cards (model TCOS 3.0 SigG) under the Telesec brand.

On Linux, these cards are not properly supported by OpenSC – the standard open-source PKCS#11 middleware. The proprietary PKCS#11 library provided by Deutsche Telekom (libpkcs11tcos_SigG_PCSC.so) is required instead.

At the time this project was started, no free signing software for Linux was available that worked reliably with these cards and allowed visually placing signature fields in a PDF. PDF QES Signer was created to fill this gap.

Tested hardware

Property Value
Token manufacturer DEUTSCHE TELEKOM SECURITY GMBH
Token model TCOS 3.0 SigG
Hardware version 4.32
Firmware version 3.0
PKCS#11 library libpkcs11tcos_SigG_PCSC.so

The library is distributed by Deutsche Telekom together with the card and is not included in this repository. You can verify your card is recognized with:

pkcs11-tool --module ./libpkcs11tcos_SigG_PCSC.so --list-slots

Features

Signing

  • Open PDF files and navigate multi-page documents (single-page and continuous view)
  • Zoom with Ctrl+scroll (cursor-centred), pan with middle-button drag
  • Draw signature fields by left-click and drag on the PDF canvas
  • Click an existing field to select it; right-click to delete it
  • Selected field is highlighted with a bold border and shows the visual appearance preview
  • Configure visual appearance: optional PNG image (with transparency), signer name, location, reason, and date
  • Apply a QES signature via any PKCS#11-compatible smartcard or USB token — specifically tested and developed for Telesec TCOS 3.0 SigG cards
  • Sign using a PFX/PKCS#12 file certificate as an alternative to a hardware token
  • PIN-pad support: leave the PIN field empty to use the hardware PIN pad; the PKCS#11 session is kept open so the PIN is requested only once
  • Optional RFC 3161 timestamp (TSA) embedded in the signature
  • Optional long-term archival (PAdES-LTA): embeds OCSP revocation status and an archival timestamp; requires a TSA URL and a CA-issued certificate with OCSP service. Trust roots are sourced from the Mozilla CA bundle (certifi) plus any CA certificates present on the token — no system CA store required. If the OCSP fetch fails, signing proceeds normally with a warning.
  • Text annotations: place free-text labels on PDF pages before signing — activate the text tool in the toolbar, click on the page, and type. Font (Helvetica / Times / Courier), size, colour, and character spacing are configurable. Annotations are saved as recoverable PDF FreeText objects and burned into the page content on signing so they appear in every viewer.
  • Chain multiple signatures: after signing, the signed PDF is reloaded automatically so further fields can be signed in sequence
  • Existing unsigned fields in already-signed PDFs are shown as locked (orange) and protected from modification to preserve the existing signature hash
  • docMDP restriction (Document Modification Detection and Prevention): when applying the first signature, a dialog lets you choose whether subsequent changes are allowed — no restriction, form fields & further signatures only (P=2, recommended), or no changes at all (P=1). The last choice is remembered per profile as the default for the next document.

Signature validation

  • Check signatures (Sign → Check signatures): opens a non-modal revision tree showing all PDF revisions newest-first; the main window remains interactive while the dialog is open
  • Each signed revision displays: signer name, signing time (self-reported or TSA-confirmed), crypto integrity status, and PAdES conformance level (B / T / LT / LTA) with a plain-language explanation
  • Certificate chain inspection: each signature and embedded TSA token shows a chain status row (valid / self-signed / unknown root / revocation unknown / incomplete / expired / revoked) with a colour-coded label and tooltip; clicking Details → opens a floating inspector window listing every certificate in the chain with role, validity period, source, and OCSP status. Trust is evaluated offline against the Mozilla CA bundle (certifi); the window position and size are persisted across sessions. Certificate chains for TSA tokens are completed from the document DSS so that root certificates added by a later LTA revision are visible for earlier signatures as well.
  • EU LOTL / TSL trust validation: when the automatic fetch mode is active, the validator confirms QES certificate chains against the EU List of Trusted Lists and the relevant national Trust Service List (TSL). The country is inferred from the C= attribute in the certificate's Subject DN. Trust anchors are accepted only when their SHA-256 fingerprint appears in a nationally-published TSL — not merely because they are embedded in the PDF. Downloaded TSL data is cached under ~/.config/pdf-signer/tsl_cache/ and reused until the TSL's own NextUpdate date.
  • Trust Store Cache (Sign → Trust Store Cache…): shows the status of the locally cached LOTL URL list and national TSL files (validity date, size) and lets you delete stale or unwanted cache data.
  • Unsigned revisions (form field fills, DSS updates, XMP metadata) can be revealed with Show all revisions
  • Clicking a revision switches the PDF viewer to show the document as it looked at that point in time (historical view)
  • Post-signature modification warning: if the document has been modified after the last signature in a way that affects visible content (e.g. form fields changed by an external tool), a prominent warning banner appears immediately when the file is opened — without requiring the user to open the validation dialog. DSS and XMP revisions produced by the LTA process are explicitly excluded from the warning to avoid false alarms.
  • When a warning is active, Show all revisions is automatically enabled in the validation dialog so the modified revision is immediately visible
  • docMDP lock detection: if an open PDF carries a P=1 certification signature (no changes allowed), a warning banner is shown immediately and all editing and signing actions are disabled; drawing new signature fields is also blocked at the canvas level

Configuration and profiles

  • Multiple named profiles (Profiles menu): each profile stores a complete set of signing parameters (PKCS#11 library, key label, TSA URL, appearance settings). Profiles can be created, renamed, deleted, and switched at runtime.
  • Multilingual UI: German, English, French, Spanish, Italian, Dutch, Polish, and Portuguese (switchable at runtime without restart)
  • Persistent configuration in ~/.config/pdf-signer/settings.ini (global) and ~/.config/pdf-signer/profiles/<name>.ini (per profile)

Requirements

Windows: additional prerequisites

The Windows installer (setup_pdf_signer.bat) handles these automatically. If you prefer to install manually:

  • Python 3.11+ – install from python.org; check "Add python.exe to PATH" during installation
  • Microsoft Visual C++ Redistributable 2015–2022 (x64) – required by PyMuPDF; download from Microsoft: vc_redist.x64.exe

Installation

Note: The current development version (master branch) contains the latest features and fixes and is generally preferred over the tagged releases.

Quick install (one-liner)

Linux:

bash <(curl -fsSL https://codeberg.org/pitbo/pdf-qes-signer/raw/branch/master/setup_pdf_signer.sh)

Note: bash <(curl ...) is required instead of curl ... | bash because the installer prompts for input interactively.

Windows (CMD):

powershell -Command "irm 'https://codeberg.org/pitbo/pdf-qes-signer/raw/branch/master/setup_pdf_signer.bat' -OutFile '%TEMP%\setup_pdf_signer.bat'; & '%TEMP%\setup_pdf_signer.bat'"

Windows (PowerShell):

irm 'https://codeberg.org/pitbo/pdf-qes-signer/raw/branch/master/setup_pdf_signer.bat' -OutFile "$env:TEMP\setup_pdf_signer.bat"; & "$env:TEMP\setup_pdf_signer.bat"

Both installers ask for an update channel during setup:

Channel Description
stable Official releases — recommended (default)
develop Pre-releases and test builds

The selected channel is remembered for future upgrades.

Clone with Git

# Primary (Codeberg)
git clone https://codeberg.org/pitbo/pdf-qes-signer.git

# Mirror (GitHub)
git clone https://github.com/pitboc/pdf-qes-signer.git

cd pdf-qes-signer

Linux / macOS:

./setup_pdf_signer.sh

Windows:

setup_pdf_signer.bat

The setup script creates a .venv/ virtual environment, installs all dependencies, and generates a launcher script.

Windows installer

setup_pdf_signer.bat is a self-contained GUI installer that requires no admin rights and no pre-installed tools beyond a standard Windows 10/11 installation.

What the installer does:

  • Checks whether Python ≥ 3.11 and the Microsoft Visual C++ Redistributable (≥ 14.30) are present; if not, it downloads and installs them silently (user-mode installation, no elevation required).
  • Creates a Python virtual environment in the chosen install directory and installs PDF QES Signer plus all dependencies via pip.
  • Optionally creates a Desktop shortcut and/or a Start Menu entry.
  • Optionally adds a "Sign with PDF QES Signer" entry to the Windows Explorer context menu for .pdf files (available via Shift+Right-click → Show more options on Windows 11; appears in the top-level context menu on Windows 10).
  • Registers the application in Apps & Features (Settings → Apps) with a proper uninstaller entry.
  • Writes an install log to %TEMP%\pdf_signer_install.log.

Update channel: the installer always asks whether to use the stable (recommended, default) or develop (pre-releases) channel. The choice is saved and pre-selected on the next run.

Upgrade: running the installer again on an existing installation shows an Update dialog — the GPL acceptance step is skipped and the previous install directory is pre-filled.

Uninstall: use Settings → Apps → PDF QES Signer → Uninstall, or run uninstall.bat in the install directory directly.

Downgrade / installing a specific version

Use the --installversion parameter to install any released version. This is also the recommended way to switch from the develop channel back to a specific stable release.

Linux – installer:

bash setup_pdf_signer.sh --installversion v0.3.3

Linux – one-liner:

bash <(curl -fsSL https://codeberg.org/pitbo/pdf-qes-signer/raw/branch/master/setup_pdf_signer.sh) --installversion v0.3.3

Windows – installer:

setup_pdf_signer.bat --installversion v0.3.3

Windows – one-liner (CMD):

powershell -Command "irm 'https://codeberg.org/pitbo/pdf-qes-signer/raw/branch/master/setup_pdf_signer.bat' -OutFile '%TEMP%\setup_pdf_signer.bat'; & '%TEMP%\setup_pdf_signer.bat' --installversion v0.3.3"

Windows – one-liner (PowerShell):

irm 'https://codeberg.org/pitbo/pdf-qes-signer/raw/branch/master/setup_pdf_signer.bat' -OutFile "$env:TEMP\setup_pdf_signer.bat"; & "$env:TEMP\setup_pdf_signer.bat" --installversion v0.3.3

Alternatively, use pip directly:

~/.local/share/pdf-signer/.venv/bin/pip install --force-reinstall \
  https://codeberg.org/pitbo/pdf-qes-signer/releases/download/v0.3.3/pdf_qes_signer-0.3.3-py3-none-any.whl

Usage

Linux / macOS:

./start_signer.sh [PDF_FILE]

Windows:

start_signer.bat [PDF_FILE]

Or manually:

source .venv/bin/activate        # Linux/macOS
.venv\Scripts\activate.bat       # Windows
python -m pdf_signer [PDF_FILE]

Workflow

  1. Open a PDF via File → Open PDF or the toolbar button.
  2. Draw one or more signature fields by left-click + drag on the page.
  3. Configure the visual appearance in the right panel (Text / Image tabs).
  4. Configure the PKCS#11 token via Settings → Configure PKCS#11 / Token:
    • Enter the path to your PKCS#11 library.
      • Telesec TCOS card: /path/to/libpkcs11tcos_SigG_PCSC.so
      • Other cards: e.g. /usr/lib/x86_64-linux-gnu/opensc-pkcs11.so
    • Click Test Token to read key and certificate labels.
    • Select the correct key label.
  5. Enter your PIN in the Token / PIN panel (or leave empty for PIN-pad).
  6. Select the target signature field in the list (or "invisible" for a non-visual signature) – either by clicking the field in the PDF view or by selecting it in the field list on the right.
  7. Sign via Sign → Sign document (QES) or the toolbar button and choose the output file location.

Save fields without signing

Use File → Save with fields (copy) to embed the signature field annotations into a PDF copy without applying a signature. This is useful for preparing documents that others will sign later.

Check signatures

Open a signed PDF and click Sign → Check signatures (or the toolbar button). The revision tree opens alongside the main window — both remain usable at the same time. Click any revision row to preview the document as it looked at that point. If the document was modified after the last signature in a way that affects visible content, a warning banner appears automatically in the main window when the file is opened.

Configuration

The application stores its settings in two files:

  • ~/.config/pdf-signer/settings.ini – global settings (language, active profile, validation fetch mode)
  • ~/.config/pdf-signer/profiles/<name>.ini – per-profile settings (PKCS#11 library, key, TSA, docMDP, appearance)
  • ~/.config/pdf-signer/tsl_cache/lotl_urls.json – cached EU LOTL URL list (created on demand during signature validation)
  • ~/.config/pdf-signer/tsl_cache/tsl_<CC>.xml – cached national TSL for country CC, e.g. tsl_DE.xml; refreshed automatically when expired

Both files are created automatically on first run. See pdf_signer.ini.example for all available options with default values.

If an old single-file pdf_signer.ini exists, it is migrated automatically on first start and renamed to pdf_signer.ini.migrated.

Project structure

pdf_signer/
├── __init__.py             # package marker, version
├── __main__.py             # enables python -m pdf_signer
├── main.py                 # entry point: argument parsing, QApplication
├── config.py               # AppConfig (INI persistence), PDF_STANDARD_FONTS
├── appearance.py           # SigAppearance, Qt and Pillow renderers
├── signer.py               # SaveFieldsWorker, SignWorker, PKCS#11 logic
├── pdf_view.py             # PDFViewWidget, SignatureFieldDef
├── continuous_view.py      # ContinuousView (continuous and single-page mode)
├── dialogs.py              # Pkcs11ConfigDialog, AppearanceConfigDialog, TokenInfoDialog, DocMDPDialog, CertChainDetailWindow
├── main_window.py          # PDFSignerApp main window
├── validation_result.py    # DocumentValidation data model (revisions, signatures, PAdES profiles)
├── validation_extractor.py # Phase 1 offline extraction (crypto integrity, certificate chain, DSS)
├── validation_dialog.py    # ValidationDialog: revision tree, PAdES profile, warning banner
├── validation_worker.py    # Phase 2 background worker (trust chain validation, AIA/OCSP, EU LOTL)
├── lotl_trust.py           # EU LOTL/TSL trust store (cached XML)
├── i18n/
│   └── __init__.py         # I18n class, t() function (gettext-backed)
└── locale/
    └── <lang>/LC_MESSAGES/ # .po source + compiled .mo for de, en, fr, es, it, nl, pl, pt
tests/
├── test_cert_chain_security.py  # security tests: forged/expired/tampered cert chains (no network)
└── test_tsl_loading.py          # TSL loading tests: country hint, fetch trigger, EU_TSL classification
tools/
└── create_test_pdfs.py     # generates test PDFs with forged/expired/tampered/TSL-trigger chains

Testing

The test suite covers security-critical validation logic and LOTL/TSL loading behaviour. No hardware token is required.

Install the development dependencies once:

source .venv/bin/activate
pip install -e ".[dev]"

Run all tests:

.venv/bin/python -m pytest

test_cert_chain_security.py — no network required

Generates synthetic PDFs with forged, expired, and tampered certificate chains and asserts that the validator never classifies any of them as VALID. The most critical test (test_spoofed_root_dn_not_trusted) verifies that a certificate with the same Distinguished Name as a trusted Mozilla CA root but a different key is not accepted — this guards against trust-bypass attacks where only the Subject DN is compared instead of the full certificate fingerprint.

test_tsl_loading.py — partly requires network

Tests that the TSL loading mechanism fires correctly when a certificate's Subject DN contains a country attribute (C=), and that it is silent when no country attribute is present.

Test Network What is verified
test_tsl_confirmed_via_mock no Fake TSL containing the test cert's fingerprint → cert classified as EU_TSL
test_tsl_fetched_real_network yes Real Finnish TSL downloaded via EU LOTL → tsl_FI.xml appears in cache
test_no_tsl_fetch_no_country no No C= in Subject → TSL fetch never triggered, cache unchanged

test_tsl_fetched_real_network fetches the Finnish TSL (~130 KB) and writes it to ~/.config/pdf-signer/tsl_cache/tsl_FI.xml. On subsequent runs the cached file is reused and no network request is made.

Translations

The UI is available in German, English, French, Spanish, Italian, Dutch, Polish, and Portuguese.

Translations for French, Spanish, Italian, Dutch, Polish, and Portuguese were generated with AI assistance and may be rough or contain inaccuracies — especially for legal and technical terms related to qualified electronic signatures.

Native speakers are warmly invited to review and improve the translations. If you find an error or would like to contribute a corrected version, please open an issue or pull request on Codeberg.

Known limitations

TSL XML signature not verified

The EU LOTL and all national Trust Service Lists (TSLs) carry an XML Digital Signature (XMLDSig) as required by ETSI TS 119 612. This application does not verify those signatures. XMLDSig requires XML canonicalisation (C14N) and XMLDSig envelope parsing — a format fundamentally different from the CMS/PKCS#7 signatures used in PDFs. Full verification would require xmlsec + lxml (native C library libxmlsec1), which are not current dependencies.

In practice the risk is low: integrity during download is guaranteed by TLS (trust anchor: Mozilla CA bundle via certifi). A local attacker who could modify the cached TSL files under ~/.config/pdf-signer/tsl_cache/ would already have write access to the user's home directory.

API documentation

The source modules contain detailed docstrings explaining both the public API and key architectural decisions (field categories, incremental write strategy, image-padding trick, coordinate systems). To browse them as HTML:

source .venv/bin/activate
pip install pdoc          # once, as a development tool
pdoc pdf_signer           # opens browser – no files written to disk
pdoc -n pdf_signer        # same, but don't open a browser (-n / --no-browser)

License

GNU General Public License v3.0 or later – see LICENSE.

About

GUI tool for qualified electronic signatures (QES) via PKCS#11 smartcard – TCOS 3.0 SigG

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors