Your cédula is a signing key. FirmaUY brings it to the terminal.
Sign and verify PDF, XML and any file with your Uruguayan national ID card over PKCS#11. Open-standard signatures (PAdES, XAdES, CAdES) designed to interoperate with compliant validators, with the whole trust chain checked locally against the Uruguayan national root. Signing and verifying keep your documents on your machine. Use it from the terminal, or from Python through its public API.
⚠️ Disclaimer: This tool performs local, technical signing and verification using open standards. It is experimental, community-maintained, not affiliated with AGESIC, not officially certified, and does not guarantee legal validity. For official validation, use the official AGESIC validator; see Legal and compliance for details.
✅ Always cross-check what you sign. Before sending or relying on a signed document, upload it to firma.gub.uy, the State validator, and keep its answer. Every time, for every document that matters. firmauy checking its own output is not independent evidence, and the arbiter your counterparty will use is the State's validator: agreement with it is tracked, not assumed. The cross-check is also the one recommended step that leaves your machine: uploading sends the document to a State-operated service, so for confidential material do it through whatever channel your situation allows. The portal validates PDF, so a detached
.p7scannot be checked there, which is worth weighing when you choose a format for something important.
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 firmauy # install
firmauy doctor # check the setup (pcscd, PKCS#11 module, card, CAs)
firmauy list-tokens # confirm the card is detected
firmauy sign-pdf input.pdf # sign -> input_firmado.pdf (prompts for the PIN)
firmauy verify input_firmado.pdf # verify (auto-detects format; offline chain check)Then upload input_firmado.pdf at firma.gub.uy. Always. See the
disclaimer above for why.
FirmaUY provides a local, developer-oriented workflow for signing and verifying documents and files with a Uruguayan national ID card (cédula) using PKCS#11 middleware: PDF (PAdES), XML (XAdES) and detached CAdES/.p7s signatures for arbitrary files.
Each format has a batch variant and optional RFC 3161 timestamping. Verification runs locally with certificate-chain validation to the Uruguayan national root, and needs no card. FirmaUY also reads the card's public data without a PIN (biographical data and photo), validates a cédula's check digit offline, and diagnoses the local setup.
Everything the CLI does is also a public Python API with typed results and typed errors,
firmauy.api, built on the same engine: see Use as a library.
See Commands for the full list, with every command's options behind firmauy <command> --help.
| Format | Command | Output | Verification | Timestamping |
|---|---|---|---|---|
| PDF / PAdES | sign-pdf |
signed .pdf |
verify-pdf / verify |
optional (external TSA) |
| XML / XAdES | sign-xml |
signed .xml |
verify-xml / verify |
optional (external TSA) |
| Any file / CAdES | sign-any |
detached .p7s |
verify-any / verify |
optional (external TSA) |
The full AdES triad (PAdES / XAdES / CAdES), signed locally with the cédula and verifiable with
standard validators; local verification anchors the chain to the Uruguayan national root. Each
command has a batch variant (sign-pdf-batch, sign-xml-batch, sign-any-batch). The default PDF
(PAdES) signature has a visible appearance modeled on real documents signed with the Uruguayan ID
card.
Timestamping is optional and bring-your-own: it works with any external RFC 3161 TSA via
--tsa-url, but it is not part of the standard cédula flow, and Uruguay has no free public TSA
(the accredited qualified ones are gated). See Timestamping.
- Smart card reader compatible with your OS
- Uruguayan ID card (cédula) with active certificate
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 currently supported or tested.
Python 3.10 or newer.
The tool expects the Uruguayan cédula PKCS#11 module (/usr/lib/pkcs11/libgclib.so), provided on
Arch Linux by the cedula-uruguay-pkcs11 AUR package. Install it as shown in
Setup on Arch Linux.
Native mode (optional): every signing command also accepts
--native, which talks to the cédula directly over PC/SC (pcscd and a reader) and needs no PKCS#11 middleware at all. It is experimental and not officially certified, though its output is accepted by the AGESIC validator. See Native signing and the card protocol reference.firmauy doctor --nativediagnoses this path (reader and card over PC/SC) instead of the PKCS#11 module.
OpenSC backend (merged upstream, pending release): @nicolasgutierrezdev and I added support for the Uruguayan cédula to OpenSC, the open-source smart-card framework, so the card can be used through a fully open-source PKCS#11 module (
opensc-pkcs11.so) instead of the proprietarylibgclib.so. The driver is merged upstream and signs end to end. It is not in a released OpenSC version yet, so for now you build from source. See Signing with the OpenSC backend.
sudo pacman -S pcsclite ccid pcsc-tools opensc
sudo systemctl enable --now pcscdInstall the PKCS#11 module from AUR:
yay -S cedula-uruguay-pkcs11
# or manually:
# https://aur.archlinux.org/packages/cedula-uruguay-pkcs11This 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
Use version 7.5.0-2 or later; older versions could crash the process when a wrong PIN was entered.
FirmaUY is published on PyPI. Install it as a standalone CLI with uv
(recommended) or into your current environment with pip:
uv tool install firmauy # recommended: isolated CLI on your PATH
pip install firmauy # or into the current Python environmentInstalling the package does not install the smart-card stack. You still need pcscd, a reader
and the PKCS#11 middleware (or --native, which needs only pcscd). See
Requirements and Setup on Arch Linux, then run
firmauy doctor to check everything is in place.
Run firmauy --help, or firmauy <command> --help for the full options of any command. Step-by-step
examples for every command are in the usage guide, and task-oriented recipes
(privacy, automation, jq pipelines) are in the cookbook.
Signing (prompts for the card's PIN, unless a non-interactive source is given). Add --native to any of these to sign over PC/SC without PKCS#11:
| Command | Description |
|---|---|
sign / sign-batch |
Sign a file (or a mixed folder in one session), auto-detecting the type: PDF -> PAdES, XML -> XAdES, anything else -> detached CAdES. --as forces the type |
sign-pdf / sign-pdf-batch |
Sign a PDF, or a whole folder in one session, with a visible signature (PAdES) |
sign-xml / sign-xml-batch |
Sign XML documents (XAdES-BES, or XAdES-T with --tsa-url) |
sign-any / sign-any-batch |
Produce a detached .p7s for any file (CAdES-BES) |
Verifying (offline chain validation to the national root, exit 0 VALID / 1 INVALID / 2 INDETERMINATE):
| Command | Description |
|---|---|
verify |
Verify any signed file, auto-detecting PDF / XML / .p7s |
verify-pdf / verify-xml / verify-any |
Format-specific verification |
Card and identity (no PIN required):
| Command | Description |
|---|---|
fetch-identity |
Read the cardholder's biographical data over PC/SC |
fetch-photo |
Save the cardholder's photo (to a file, a stream, or a JSON record) |
validate-ci |
Validate (or complete) a cédula's check digit, offline, with no card |
list-readers |
List the available PC/SC smart card readers |
list-tokens |
List the PKCS#11 tokens in the library |
list-certs |
List the certificates on the card (--pem / --json) |
Setup and maintenance:
| Command | Description |
|---|---|
doctor |
Diagnose the local environment (pcscd, PKCS#11 module, card, CAs). --native checks the PC/SC path native signing uses |
fetch-cas |
Optional: re-fetch the currently pinned national CA certificates into a per-user cache. A rotated CA needs a new release, or --ca-file |
Everything the CLI does is also a public Python API, firmauy.api, built on the same engine. It
returns frozen dataclasses and raises typed errors, never printed text or exit codes, so a GUI or
a script can consume it directly:
import getpass
from firmauy.api import sign, verify
report = sign("contract.pdf", pin_provider=lambda: getpass.getpass("PIN: "))
print(report.kind, "->", report.output_path) # pades -> contract_firmado.pdf
print(verify(report.output_path).indication) # VALIDThe pin_provider callback runs only after the reader, card and certificate check out, so a broken
setup never costs a PIN try. Domain conditions raise typed exceptions: a wrong PIN is
IncorrectPinError (carrying attempts_remaining on the native path), a locked card is
PinLockedError, an existing output is OutputExistsError, and so on.
The full surface (every signature format, whole batches in one card session, reading the cédula's identity and photo, listing readers, tokens and certificates, diagnostics, check-digit validation) is documented in docs/api.md.
- Usage guide: step-by-step examples for every command.
- Cookbook: task-oriented recipes for signing, verifying, privacy, automation and
jqpipelines. - Trust anchors: the bundled national CA certificates, how trust is pinned and refreshed, and the state of revocation.
- Card protocol reference: the cédula's data model and the APDU-level signing protocol behind
--native(native, PKCS#11-free signing). - OpenSC backend: build OpenSC from source and sign through the fully open-source
opensc-pkcs11.somodule instead of the proprietary middleware. - Library API: the full
firmauy.apireference: signing every format (single and batch), verification, reading the cédula, listing readers, tokens and certificates, diagnostics, and the typed reports and errors. - Architecture: the layer boundaries and invariants: the presentation-free engine, the adapters on top, the PIN path and the error design.
- Development: running from source with
uv, the test suite, and developing without the card (SoftHSM2).
- Never pass the PIN directly as a command-line argument.
- Prefer interactive PIN entry for manual use.
- From Python, prefer
pin_providerover holding the PIN in a variable: it is requested only once the reader, card and certificate check out. - 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.
- Found a vulnerability? Report it privately: see SECURITY.md.
This tool is designed to run entirely locally. The one recommended exception is deliberate and yours to take: cross-checking a signed document at firma.gub.uy uploads that document to a State-operated service.
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.
Note: the signing commands print a summary that includes identifying data (signer name, certificate issuer, certificate serial number and PKCS#11 key ID). This stays on your machine, but in batch or automated pipelines that output can end up in CI or centralized logs. Pass --quiet (-q) to the sign-pdf, sign-pdf-batch, sign-xml, sign-xml-batch, sign-any and sign-any-batch commands to suppress that block while still signing.
Note: fetch-identity reads and prints the cardholder's biographical data (names, birth date, birthplace, document number, MRZ), and fetch-photo outputs the cardholder's photo (to a file, a redirected stream, or a JSON record). This data is accessible from the card without PIN authentication, but it is still personal: pass --redact to fetch-identity to replace every field with [REDACTED] before sharing its output, use fetch-photo --json --redact for a metadata-only photo record, and treat any non-redacted output (file, redirected stream, or receiving application) as sensitive.
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.
In practice that is one habit: every signed document that matters gets uploaded to firma.gub.uy after signing, every time. The project keeps a public record of agreement between its verdicts and the State validator's, and redacted result rows are welcome: see the conformance protocol.
Local, developer-oriented signing and verification using a Uruguayan ID card through PKCS#11. It is especially aimed at users who want to:
- sign PDFs (PAdES), XML documents (XAdES), and arbitrary files (CAdES/.p7s) locally
- verify those signatures locally, including the certificate chain to the national root
- understand and reproduce a PKCS#11-based signing workflow
- experiment with smart card integration on Linux
- build automation around signing and verification under their own responsibility
It is not intended to replace official, certified, or legally guaranteed signing platforms.
This tool focuses on technical integration with PKCS#11: signing (PDF/PAdES, XML/XAdES, files/CAdES) and local, standards-based verification, including certificate-chain validation to the Uruguayan national root. It aims for practical interoperability rather than strict compliance with any specific implementation.
It is not an official validator: it does not consult the official trust-service status list (TSL) or evaluate accreditation / qualified status, provide legal guarantees, or replace certified signing platforms.
For authoritative or legal verification, use AGESIC's official validator at firma.gub.uy.
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
The project was later renamed to FirmaUY (PyPI package and CLI: firmauy). The registration above is under its original title, cedula-uy-pdf-sign.
This registration concerns only the authorship of the software as a copyrighted work. It does not imply certification or legal validity. See Legal and compliance.
The project uses uv. Clone, set up the environment and run the tests:
git clone https://github.com/carlosplanchon/firmauy.git
cd firmauy
uv sync
uv run pytestThe full workflow, the project layout, and developing without the card (SoftHSM2, since a wrong PIN can block the cédula) are in docs/development.md.
Bug reports, questions, and pull requests are welcome.
Feel free to open an issue on GitHub. The contribution mechanics (including the required
DCO sign-off, git commit -s) are in
CONTRIBUTING.md. For security problems, use the private channel described
in SECURITY.md instead of a public issue.
Cookbook recipes (see docs/cookbook.md) are welcome, and one of the best ways to help: they open the project to people who will not necessarily touch the PKCS#11, APDU or XAdES internals but can still share real, useful workflows. A good recipe shows a real workflow with minimal commands, the expected output, privacy notes, and the environment where it was tested.
Please do not include real names, document numbers, MRZ data, certificates, photos, or unredacted
verification output. Use --redact whenever sharing command output.
- @nicolasgutierrezdev: contributed the
fetch-identityandlist-readerscommands for reading the cardholder's biographical data over PC/SC (#1). Also 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.
This project is licensed under the Apache License 2.0.
