Skip to main content

fiverify

Offline validation of France Identité attestation PDFs — the PAdES-T qualified electronic seal issued by the French Ministry of the Interior.

Offline by default: nothing opens a socket unless you ask for it. Trust anchors, validation time and revocation data are inputs.

Install

pip install fiverify

Or from a clone, to work on it: pip install -e . (pip install --user -e . outside a virtualenv).

Python 3.11+. Pulls in asn1crypto and cryptography; nothing else.

Quick start

fi-verify attestation.pdf --fetch-revocation
fi-verify attestation.pdf --accept-unchecked-revocation
from fiverify import Status, verify_file

# Fetch the CRLs the certificates name — the only network access in the project.
report = verify_file("attestation.pdf", fetch_revocation=True, extract_attributes=True)

# Or stay fully offline, accepting that revocation status is unknown.
report = verify_file("attestation.pdf", accept_unchecked_revocation=True, extract_attributes=True)

if report.status is Status.PASSED:
    print(report.attributes)          # identity fields, only ever from verified bytes
else:
    print(report.status.value, [c.summary for c in report.failures()])

That is the complete check: signature, coverage, timestamp, chain to the eIDAS anchor, and revocation. examples/ holds a signed document and two forgeries of it, to try without a real attestation.

Revocation

The default (verify_pdf(...) with no revocation argument) returns INDETERMINATE, never PASSED — with no revocation data the tool cannot honestly say a certificate was good. Your options, least to most trusting:

CLI API
Fetch CRLs now --fetch-revocation fetch_revocation=True
Use CRLs you already have --crl ./crls/ revocation=RevocationStore.from_paths([...])
Accept that it is unknown --accept-unchecked-revocation accept_unchecked_revocation=True
Demand post-dated CRLs --strict-revocation strict_revocation=True

Revocation is evaluated at the signing timestamp, not now, so a certificate revoked afterward doesn't invalidate the seal — and only if that timestamp came from a TSA that chains to an anchor, since an untrusted one would be free to back-date the seal past the revocation. --strict-revocation requires a CRL issued after that timestamp rather than one merely in force over it — the stronger guarantee needed for archival validation.

--fetch-revocation downloads only what an anchored certificate names, and only over HTTP(S): a distribution point is a URL chosen by whoever wrote the document, so fetching one before its certificate reaches a trust anchor would make verification a fetch primitive. To pin the hosts as well, fetch separately with fetch_for_pdf(data, allowed_hosts=[...]) and pass the store in.

Identity attributes

Fields like familyName, givenName, birthdate are not extracted unless you ask (--attributes / extract_attributes=True), and are only ever returned from bytes covered by a signature that reaches a trust anchor — a document can be forged so the seal still verifies while content changed underneath it (see examples/forged-append.pdf), or simply signed by its author's own certificate, and report.attributes stays None rather than trusting that content.

Reading the report

report.status                            # PASSED | FAILED | INDETERMINATE
report.caveats                           # what was not checked, e.g. revocation
report.attributes                        # identity fields, if asked and verified
report.failures()                        # [Check(...), ...]
report.signatures[0].details             # signer, chain, gen_time, algorithms, …
report.to_dict()                         # JSON-safe

Results are three-valued, per ETSI EN 319 102-1. Every Check carries an id, a severity, an ETSI sub_indication, and the evidence behind it. strict=True makes profile expectations fatal rather than indeterminate.

Exit codes: 0 passed, 1 failed, 2 indeterminate, 3 usage or I/O error. Add --json for the full report, -v to include passing informational checks.

Profiles and trust anchors

Everything document-specific — policy OIDs, algorithm allow-lists, trust anchors and their SHA-256 pins — lives in src/fiverify/profiles/france-identite.toml, not in code. Chains terminate at AC SERVEUR CACHET EIDAS 2025, the eIDAS trust anchor itself (listed in the French Trusted List), pinned by fingerprint in the profile.

fi-verify doc.pdf --profile my.toml --anchor extra-ca.pem --at 2027-01-01

tools/tsl-anchors.py --verify reproduces the bundled anchor from the live EU/FR trusted lists rather than asking you to take it on trust; see the tool for rollover instructions.

What it does not do

  • OCSP. CRLs only. A responder URL is reported, not queried.
  • Embedded revocation data (PAdES-LT /DSS). Long-term validation of an old document depends on a CRL that still lists it.
  • PDF object-graph parsing. A revision appended after signing is reported as uncovered content, not classified — fine for single-signature documents, not yet for counter-signed ones.

Tests

pip install pytest && python3 -m pytest

88 tests, offline, no fixtures checked in — generated against a throwaway PKI, with every signature-arithmetic verdict cross-checked against openssl.

Download files

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

Source Distribution

fiverify-0.2.0.tar.gz (41.6 kB view details)

Uploaded Source

Built Distribution

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

fiverify-0.2.0-py3-none-any.whl (35.8 kB view details)

Uploaded Python 3

File details

Details for the file fiverify-0.2.0.tar.gz.

File metadata

  • Download URL: fiverify-0.2.0.tar.gz
  • Upload date:
  • Size: 41.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.14.6

File hashes

Hashes for fiverify-0.2.0.tar.gz
Algorithm Hash digest
SHA256 643f1e6dbafaec8dfba1dec6a47d06057341d59d61e2abddccfa145653711dba
MD5 e8f93040f10781f89158c89b9327901b
BLAKE2b-256 626aea0f149d22293fe767e48a77bcc92023f6d9669b3b2fe0a4569ea0efae28

See more details on using hashes here.

File details

Details for the file fiverify-0.2.0-py3-none-any.whl.

File metadata

  • Download URL: fiverify-0.2.0-py3-none-any.whl
  • Upload date:
  • Size: 35.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.14.6

File hashes

Hashes for fiverify-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 1d6e4c4ac5f5b97846e48201b8d47ece28b0c9cfe9f2826d67b55d404ddb81a9
MD5 583f37b91b89c8b69de9f836774d9e5c
BLAKE2b-256 f59f32213fd2004db134cb7cf38996e5957e704431ef9b3ea0b526334cec2c13

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

0.2.0 This release

2 files

0.1.1

2 files

0.1.0

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page