Skip to main content

Sign PDFs with Uruguayan national ID (cédula) via PKCS#11, generating cryptographic PDF signatures visually compatible with signatures produced by the official Uruguayan cédula software. Not affiliated with AGESIC.

Project description

cedula-uy-pdf-sign

cedula-uy-pdf-sign banner

Sign PDF documents locally using a Uruguayan national ID card (cédula) through PKCS#11, producing PDF digital signatures compatible with standard PDF signature validators.

PyPI version Python versions License: Apache 2.0 DeepWiki

⚠️ Disclaimer: This is an experimental, community-maintained project. It is not affiliated with or endorsed by AGESIC, is not officially certified, and does not guarantee the legal validity of the signatures it produces. Use at your own risk. See Legal and compliance for details.

Quick start

Requires Linux with the Uruguayan cédula PKCS#11 middleware installed. The full smart-card setup is in Requirements and Setup on Arch Linux.

uv tool install cedula-uy-pdf-sign     # install
firmauy list-tokens                    # verify the card is detected
firmauy sign input.pdf                 # sign -> input_firmado.pdf (prompts for the PIN)

Overview

cedula-uy-pdf-sign provides a local, developer-oriented workflow for signing PDF documents with a Uruguayan national ID card using PKCS#11 middleware.

The CLI tool is invoked as firmauy and supports:

  • signing individual PDF documents
  • batch-signing multiple PDFs with a single PKCS#11 session
  • signing XML documents, individually or in batch (XAdES-BES, enveloped)
  • configuring the visible signature position
  • selecting the signature page
  • discovering available PKCS#11 tokens and certificates
  • non-interactive PIN sources for controlled automation workflows

Requirements

Hardware

  • Smart card reader compatible with your OS
  • Uruguayan ID card (cédula) with active certificate

Operating system

This tool targets Linux and is primarily developed and tested on Arch Linux.

Other Linux distributions may work if the required smart card stack, PKCS#11 middleware, and Python environment are correctly configured.

Windows and macOS are not supported.

Python

Python 3.10 or newer.

PKCS#11 middleware

The default PKCS#11 module expected by this tool is:

/usr/lib/pkcs11/libgclib.so

On Arch Linux, this is provided by the cedula-uruguay-pkcs11 AUR package.

Setup on Arch Linux

1. Install smart card stack

sudo pacman -S pcsclite ccid pcsc-tools opensc
sudo systemctl enable --now pcscd

2. Install PKCS#11 library for the Uruguayan ID card

Install the PKCS#11 module from AUR:

yay -S cedula-uruguay-pkcs11
# or manually:
# https://aur.archlinux.org/packages/cedula-uruguay-pkcs11

This is a community-maintained AUR package that repackages the official cédula drivers distributed by the Uruguayan government. It is not an official government package.

It provides the default PKCS#11 module used by this tool:

/usr/lib/pkcs11/libgclib.so

Installation

Installation with uv

uv tool install cedula-uy-pdf-sign

Usage

The CLI tool is invoked as firmauy.

CLI help

Use --help on any command to see all available options:

firmauy --help
firmauy sign --help
firmauy sign-batch --help

Sign a single PDF

firmauy sign input.pdf output_signed.pdf

The tool will prompt for the PKCS#11 PIN interactively.

If the output path is omitted, the signed file is saved as:

<input>_firmado.pdf

Custom signature position

firmauy sign input.pdf output_signed.pdf --x1 20 --y1 20 --x2 225 --y2 90

Specify page

Pages are 0-indexed. Use -1 to sign the last page.

firmauy sign input.pdf output_signed.pdf --page 0

Non-interactive PIN

PIN can be supplied without an interactive prompt via --pin-source:

# From an environment variable
firmauy sign input.pdf output_signed.pdf --pin-source env --pin-env-var MY_PIN

# From stdin
echo "1234" | firmauy sign input.pdf output_signed.pdf --pin-source stdin

# From a file descriptor
firmauy sign input.pdf output_signed.pdf --pin-source fd --pin-fd 3

⚠️ Avoid exposing the PIN in shell history or process lists.

Timestamping (TSA, optional)

Embed a trusted timestamp from a Time Stamping Authority:

firmauy sign input.pdf output_signed.pdf --tsa-url https://your-tsa/endpoint

TSA timestamping is optional and is not required for the standard Uruguayan cédula signing flow. It adds independent, trusted-time evidence to the signature and may involve an external network request.

Sign batch

Sign multiple PDFs with a single PKCS#11 session. The card PIN is entered only once.

# Explicit file list
firmauy sign-batch file1.pdf file2.pdf file3.pdf --output-dir ~/signed

# Whole directory
firmauy sign-batch --input-dir ~/docs --output-dir ~/signed

# Whole directory, recursively
firmauy sign-batch --input-dir ~/docs --recursive --output-dir ~/signed

# Both can be combined
firmauy sign-batch extra.pdf --input-dir ~/docs --output-dir ~/signed

Output files are named <original-name>_firmado.pdf by default.

Change the suffix with --suffix:

firmauy sign-batch --input-dir ~/docs --output-dir ~/signed --suffix _signed

The output directory is created automatically if it does not exist.

All options available for sign (position, PIN source, reason, TSA, etc.) are also available for sign-batch.

⚠️ This tool produces cryptographic signatures. Legal validity depends on applicable regulations and use context.

Make sure you have reviewed all documents before signing them in batch.

Sign an XML document (XAdES)

Sign an XML document with the cédula, producing a standards-based XAdES-BES enveloped signature following the XAdES specification (ETSI EN 319 132). It verifies with standard XAdES validators and is suitable for signing structured documents such as electronic fiscal documents (CFE / facturación electrónica).

firmauy sign-xml input.xml output_signed.xml

If the output path is omitted, the signed file is saved as <input>_firmado.xml.

Token discovery, certificate selection and PIN handling work exactly like the PDF commands, so the same options apply: --token-label, --cert-id, --pin-source (with --pin-env-var / --pin-fd), --timezone and --overwrite.

# Non-interactive PIN, same as the PDF commands
echo "1234" | firmauy sign-xml input.xml output_signed.xml --pin-source stdin

Signature profile produced:

  • Format: XAdES-BES, enveloped; the <ds:Signature> is appended as the last child of the document root, with a single reference over the whole document (URI="").
  • Canonicalization: inclusive C14N 1.0 (REC-xml-c14n-20010315).
  • Algorithms: RSA-SHA256 signature, SHA-256 digests.
  • Signed properties: signing time, signing-certificate digest and data-object format.

⚠️ This is the XAdES-BES level (no trusted timestamp). The produced signature is cryptographically valid and conforms to the XAdES standard; legal and regulatory validity depends on your use case and applicable rules.

Sign multiple XML documents (batch)

Sign many XML files with a single PKCS#11 session (the card PIN is entered only once). This mirrors sign-batch for PDFs and is convenient for bulk workflows such as electronic invoicing.

# Explicit file list
firmauy sign-xml-batch file1.xml file2.xml --output-dir ~/signed

# Whole directory (add --recursive to descend into subfolders)
firmauy sign-xml-batch --input-dir ~/docs --output-dir ~/signed

For unattended bulk signing, supply the PIN non-interactively (entered once for the whole batch), exactly as with the other commands:

# PIN from an environment variable
firmauy sign-xml-batch --input-dir ~/docs --output-dir ~/signed \
  --pin-source env --pin-env-var MY_PIN

# PIN from stdin
echo "1234" | firmauy sign-xml-batch --input-dir ~/docs --output-dir ~/signed --pin-source stdin

Output files are named <original-name>_firmado.xml by default; change it with --suffix. The output directory is created automatically. All the sign-xml options (token, certificate and PIN selection, --timezone, --overwrite) also apply.

Make sure you have reviewed all documents before signing them in batch.

Discover tokens and certificates

List all visible PKCS#11 tokens:

firmauy list-tokens

List certificates available on a token:

firmauy list-certs

Security considerations

  • Never pass the PIN directly as a command-line argument.
  • Prefer interactive PIN entry for manual use.
  • For automation, prefer protected file descriptors or controlled environments.
  • Review every document before signing it.
  • Use batch signing only in trusted workflows.
  • Keep your smart card, reader, PIN, and PKCS#11 middleware under your own control.

Privacy

This tool is designed to run entirely locally.

It does not collect, transmit, or store any user data externally.

All cryptographic operations are performed on the user's machine and/or the connected smart card.

Note: Optional features such as timestamping (TSA) may involve external network requests, depending on user configuration.

Signature verification

Signed documents can be independently verified using external tools, such as the official validator provided by AGESIC (no affiliation implied):

https://firma.gub.uy/

Note that a successful technical verification does not by itself imply legal validity for every use case. See Legal and compliance.

Known issues

Incorrect PIN may crash the process (middleware bug)

On Arch Linux, entering an incorrect PIN may cause the process to terminate abruptly with:

*** stack smashing detected ***: terminated

This is a bug in the PKCS#11 middleware (libgclib.so), not in cedula-uy-pdf-sign.

The middleware correctly returns CKR_PIN_INCORRECT to the caller, but then appears to corrupt its own memory during error handling.

This is independently reproducible with pkcs11-tool:

pkcs11-tool --module /usr/lib/pkcs11/libgclib.so --login --test
# With wrong PIN -> process crashes with stack smash

Because the crash occurs inside native code, it cannot be caught or recovered from at the Python level.

Practical advice: double-check your PIN before invoking firmauy.

This behavior is outside the control of this application.

Additional notes

  • The default visual signature appearance was derived by analyzing documents signed with official software.
  • This project focuses on practical interoperability rather than strict compliance with any specific implementation.

Legal and compliance

This project is copyright-registered, experimental, community-maintained, and not officially certified.

It is intended for developers and technically proficient users who understand the implications of using smart cards, PKCS#11 middleware, and digital signatures.

This project:

  • is not affiliated with or endorsed by AGESIC
  • does not claim official certification or compliance
  • does not guarantee the legal validity of generated signatures
  • is provided for technical and educational purposes

While it uses standard cryptographic mechanisms and aims to align with Uruguayan digital signature practices, the generated signatures should not be assumed valid for legal or regulatory use without independent verification. Users are solely responsible for ensuring that generated signatures meet any legal or regulatory requirements applicable to their use case.

Intended use

Local, developer-oriented PDF signing using a Uruguayan ID card through PKCS#11. It is especially aimed at users who want to:

  • sign PDF documents locally
  • understand and reproduce a PKCS#11-based signing workflow
  • experiment with smart card integration on Linux
  • build automation around PDF signing under their own responsibility

It is not intended to replace official, certified, or legally guaranteed signing platforms.

Scope

This tool focuses on technical integration with PKCS#11, PDF signing workflows, and reproducibility of signature appearance.

It does not validate certificates against official trust lists, provide legal guarantees, or replace certified signing platforms.

Copyright / software registration

This software has been registered as a computer program with the Uruguayan Dirección Nacional de la Propiedad Industrial y Registro de Software.

The registration was published in the official Boletín de la Propiedad Industrial Nº 357:

  • Entry: Software (w/000235)
  • Filing date: 2026-04-15
  • Applicant: Carlos Andrés Planchón Prestes [UY]
  • Title: cedula-uy-pdf-sign
  • Classification: Programa de ordenador
  • Official publication: Boletín de la Propiedad Industrial Nº 357

This registration concerns the authorship of the software as a copyrighted work. It does not imply official certification, endorsement, legal validity of generated signatures, or regulatory compliance of any specific use case. See Legal and compliance.

Development

The project uses uv for environment and dependency management.

# Clone the repository
git clone https://github.com/carlosplanchon/cedula-uy-pdf-sign.git
cd cedula-uy-pdf-sign

# Create the environment and install dependencies (runtime + dev)
uv sync

# Run the test suite (PKCS#11 integration tests need SoftHSM2, see below)
uv run pytest

# Run the CLI from the working tree
uv run firmauy --help

The package source lives under src/cedula_uy_pdf_sign/; tests under tests/.

Developing without the real card (SoftHSM2)

Entering the wrong PIN too many times blocks the cédula, so you should not develop against the real card. Instead, you can run the full signing pipeline against a software PKCS#11 token (SoftHSM2) that mimics a cédula closely enough to exercise token discovery, certificate selection, PIN handling and signing.

# Arch Linux: install the software token + tooling
sudo pacman -S softhsm opensc openssl

# Provision a throwaway "fake cédula" token under ./.softhsm
bash scripts/dev-softhsm-setup.sh

The script prints ready-to-run firmauy list-certs / firmauy sign commands pointing at the SoftHSM module. The resulting PDF is a cryptographically valid signature, but it will not validate as a cédula signature on firma.gub.uy (the issuing CA is a local fake, by design). Reset everything with rm -rf .softhsm.

The token persists under .softhsm, so this doubles as a normal development loop: run firmauy by hand as often as you like (signing test PDFs, trying signature positions with --x1/--y1/..., exercising sign-batch, reproducing a reported bug) while iterating on the code, without the card and without risking PIN lockout. The real card is then only needed for a final validation run and for middleware-specific behaviour.

The same setup powers a set of end-to-end integration tests (tests/test_integration_pkcs11.py) that exercise the real PKCS#11 path: signing plus cryptographic verification of the resulting PDF, and the selection/error branches that are unsafe to reproduce on the real card (expired certificate, certificate without a private key, multiple tokens, certificate scoring and --cert-id override). They are skipped automatically when SoftHSM2 / OpenSC / OpenSSL are not installed, so uv run pytest works either way.

Contributing & reporting issues

Bug reports, questions, and pull requests are welcome.

Feel free to open an issue on GitHub.

Acknowledgements

  • @nicolasgutierrezdev: provided reference for the signature appearance inspired by signatures generated using the Uruguayan ID card (cédula), and helped test the XAdES (XML) signing feature.

License

This project is licensed under the Apache License 2.0.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

cedula_uy_pdf_sign-0.7.0.tar.gz (37.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

cedula_uy_pdf_sign-0.7.0-py3-none-any.whl (27.6 kB view details)

Uploaded Python 3

File details

Details for the file cedula_uy_pdf_sign-0.7.0.tar.gz.

File metadata

  • Download URL: cedula_uy_pdf_sign-0.7.0.tar.gz
  • Upload date:
  • Size: 37.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.3

File hashes

Hashes for cedula_uy_pdf_sign-0.7.0.tar.gz
Algorithm Hash digest
SHA256 e2935b6271dfd402f71a0e9439c5ead6f9b560cd19b4bcc45ee5d59267ed547c
MD5 d199cfa4b9e5b2d17e5d4448c6a640ba
BLAKE2b-256 33f7989797403b95d06f02fd93fbfd056bf42087057a134e5036c5cb09a16589

See more details on using hashes here.

File details

Details for the file cedula_uy_pdf_sign-0.7.0-py3-none-any.whl.

File metadata

File hashes

Hashes for cedula_uy_pdf_sign-0.7.0-py3-none-any.whl
Algorithm Hash digest
SHA256 2a7c8477cc942b6571bd5e382e4b8a4b53a91d8b0f2aa90c88cc2873779c5291
MD5 043556627d1e0d2c51bd70cb08aeb022
BLAKE2b-256 7aa0af63b63818dbad434d6155d23fabd166c9822ce0e50ae3de15fdc1c5c967

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page