pdf-signature

Crystal library for PAdES PDF digital signatures (B-B, B-T, B-LT, B-LTA). Companion to aloli-crystal/pdf.

= aloli-crystal/pdf-signature :toc: left :toc-title: Contents

Crystal library for digitally signing PDFs in PAdES format (PDF Advanced Electronic Signatures, ETSI EN 319 142).

Companion to aloli-crystal/pdf, which already covers encryption (RC4 / AES-128 / AES-256) and PDF parsing/writing. This shard adds digital signing, the only cryptographic protection that detects any modification of a PDF.

[NOTE]

This shard is in its infancy (v0.1). See doc/RATIONALE.adoc for the in-depth analysis (PAdES, TSA, trust infrastructure, ALOLI roadmap, architectural decisions). The substantive document is in French — translation to come.

== Why a dedicated shard?

Three distinct protections coexist on a PDF:

[cols="1,1,1,1",options="header"] |=== | Protection | Prevents reading | Detects modification | Cryptographic | User password | ✅ | ❌ | ✅ | Permissions (/P) | ❌ | ⚠️ honor-system | ❌ | Digital signature | ❌ | ✅ | ✅ |===

The first two are handled by aloli-crystal/pdf. Signing deserves its own shard because:

  • Distinct API surface (signature ≠ encryption).
  • Optional dependencies (not every pdf consumer will sign).
  • Different release cycle (PAdES B-B → B-T → B-LT → B-LTA, post-quantum algorithms…).

== Roadmap

[cols="1,3,1",options="header"] |=== | Version | Target | Status

| v0.1 | PAdES B-B (basic detached PKCS#7 signature) | ✅ done

| v0.2 | PAdES B-T (RFC 3161 timestamping) | ✅ done

| v0.3 | PAdES B-LT (long-term validation material via /DSS) | ✅ done

| v0.4 | PAdES B-LTA (archive timestamping — indefinite extension) | ✅ done

| v0.5 | Verification (reading + validation) | ✅ done

| v0.6 | HSM / PKCS#11 backend | ✅ done |===

== Quickstart

[source,crystal]

require "pdf-signature"

B-B — sign an existing PDF (incremental update)

PDF::Signature::Signer.sign( input: "report.pdf", output: "signed-report.pdf", certificate: "./signer.p12", passphrase: "...", # or via env / Keychain level: :b_b, # :b_b | :b_t (done) | :b_lt | :b_lta (future) reason: "ISO 27001 audit validation", location: "Laguiole, FR", signing_time: Time.utc, )

B-T — add an RFC 3161 signature timestamp from a TSA

PDF::Signature::Signer.sign( input: "report.pdf", output: "signed-report.pdf", certificate: "./signer.p12", passphrase: "...", level: :b_t, tsa_url: "https://freetsa.org/tsr", # any RFC 3161 service reason: "ISO 27001 audit validation", )

B-LT — B-T + embedded long-term validation material (/DSS).

Signer and TSA certs are harvested automatically; supply the issuing

CA chain plus the revocation proofs (OCSP responses / CRLs).

PDF::Signature::Signer.sign( input: "report.pdf", output: "signed-report.pdf", certificate: "./signer.p12", passphrase: "...", level: :b_lt, tsa_url: "https://freetsa.org/tsr", ltv_certs: ["./issuing-ca.pem"], ltv_ocsps: ["./signer-ocsp.der"], ltv_crls: ["./issuing-ca.crl"], )

Verify a signed PDF (one report per signature / document timestamp)

PDF::Signature::Verifier.verify("signed-report.pdf", ca_bundle: "./trust.pem").each do |r| puts "#{r.field}: #{r.level} valid=#{r.valid?} whole=#{r.covers_whole_document?}" end

NOTE: B-T requires the openssl CLI (≥ 3.0, for cms -cades) on the PATH. The signature is a CAdES-BES detached envelope (ESS signing-certificate-v2) with the timestamp embedded as the id-aa-timeStampToken unsigned attribute, validated against openssl cms, openssl ts and poppler's pdf-sign.

== PAdES levels — summary

[cols="1,3,3",options="header"] |=== | Level | Main guarantee | Typical use case

| B-B | Modification detection + signer identity (self-declared date) | Internal signatures, immediate validation

| B-T | B-B + date proof via third-party TSA | ISO 27001 audit reports, mid-term contracts — minimum eIDAS advanced

| B-LT | B-T + embedded validation material (CRL/OCSP/certs) | Security policies, regulatory dossiers (5-10 years)

| B-LTA | B-LT + renewable archive timestamps | Legal archives, qualified eIDAS signatures, notarial |===

== Install

As a library :

[source,yaml]

dependencies: pdf-signature: github: aloli-crystal/pdf-signature version: "~> 0.7"

As the pdf-sign command-line tool — build and symlink (no sudo, no copy into /usr/local/bin) :

[source,sh]

shards build --release ln -s "$PWD/bin/pdf-sign" ~/bin/pdf-sign

== CLI : pdf-sign

[source,sh]

Sign (passphrase via the environment, never on the command line)

export PDFSIG_PASSPHRASE=… pdf-sign sign -i report.pdf -o signed.pdf -c signer.p12
-l b-t -t https://freetsa.org/tsr -r "ISO 27001 audit"

Long-term (B-LT) : supply the CA chain + revocation proofs

pdf-sign sign -i report.pdf -o signed.pdf -c signer.p12 -l b-lt
-t https://freetsa.org/tsr
-C issuing-ca.pem -O signer-ocsp.der -R issuing-ca.crl

Hardware key (PKCS#11 / HSM / SoftHSM) : PIN via the environment

export PDFSIG_PIN=… pdf-sign sign -i report.pdf -o signed.pdf -c signer.crt -l b-t
-t https://freetsa.org/tsr
-k "pkcs11:token=…;object=…;type=private" -m /path/to/module.so

Strict PAdES (native CMS, no signing-time signed attribute ; B-T+)

pdf-sign sign -i report.pdf -o signed.pdf -c signer.p12 -l b-t -s
-t https://freetsa.org/tsr

Verify (one report per signature ; exit 0 if all valid, 2 otherwise)

pdf-sign verify signed.pdf -a trust.pem

pdf-sign help [sign|verify]

Neither the PKCS#12 passphrase nor the PKCS#11 PIN ever appears on the command line : you pass the name of an environment variable (-p/--passphrase-env, -P/--pkcs11-pin-env) that holds it, so it stays out of ps.

== Dependencies

  • https://github.com/aloli-crystal/pdf[`aloli-crystal/pdf`] ≥ 0.5 — PDF object model, parser, writer
  • OpenSSL CLI ≥ 1.1 (already on every modern Unix) — used for PKCS#7 and RFC 3161 (the shard shells out to openssl cms and openssl ts, see doc/RATIONALE.adoc § Technical choices)

== Documentation

  • doc/RATIONALE.adoc — substantive analysis: signature principles, the 4 PAdES levels, TSA construction, Sigstore model, ALOLI trust infrastructure trajectory, architectural decisions (in French)
  • CHANGELOG.adoc — version history

== License

MIT — see LICENSE.

Repository

pdf-signature

Owner
Statistic
  • 0
  • 0
  • 0
  • 1
  • 2
  • 17 days ago
  • May 7, 2026
License

MIT License

Links
Synced at

Fri, 05 Jun 2026 15:47:04 GMT

Languages