Skip to main content

Python bindings for the underskrift PDF signing and verification library

Project description

pyunderskrift

Python bindings for the underskrift PDF signing and verification library.

Built with PyO3 and maturin.

Features

  • PDF signing with PKCS#12 software signers (PAdES and PKCS#7 sub-filters)
  • Signature verification with configurable trust stores and policies
  • Three-phase remote signing for HSM / cloud signing workflows
  • Signature extraction without verification
  • Trust store management for signature, timestamp, and SVT certificate authorities
  • Algorithm registry to control permitted cryptographic algorithms
  • Policy evaluation with basic and PKIX-based signature validation policies
  • GIL released during all signing, verification, and extraction operations

Installation

python3 -m pip install pyunderskrift

Requires Python >= 3.10.

Quick start

Sign a PDF

from pyunderskrift import PdfSigner, SoftwareSigner, SigningOptions, SubFilter

signer = SoftwareSigner.from_pkcs12_file("signer.p12", "password")
options = SigningOptions(
    sub_filter=SubFilter.Pades,
    field_name="Signature1",
    reason="Approved",
    location="Stockholm",
)

pdf_data = open("document.pdf", "rb").read()
pdf_signer = PdfSigner(options)
signed_pdf = pdf_signer.sign(pdf_data, signer)

with open("signed.pdf", "wb") as f:
    f.write(signed_pdf)

Verify signatures

from pyunderskrift import (
    SignatureVerifier,
    TrustStore,
    TrustStoreSet,
    SignatureStatus,
)

# Build trust stores
sig_store = TrustStore.from_pem_directory("./trust/sig")
tsa_store = TrustStore.from_pem_directory("./trust/tsa")

stores = TrustStoreSet()
stores.set_sig_store(sig_store)
stores.set_tsa_store(tsa_store)

# Verify
verifier = SignatureVerifier(stores)
pdf_data = open("signed.pdf", "rb").read()
report = verifier.verify_pdf(pdf_data)

for sig in report.signatures:
    print(f"{sig.field_name}: {sig.status} - {sig.summary}")

print(f"All valid: {report.all_valid()}")
print(f"Document modified: {report.document_modified}")

Extract signatures (no verification)

from pyunderskrift import extract_signatures

pdf_data = open("signed.pdf", "rb").read()
for sig in extract_signatures(pdf_data):
    print(f"  Field: {sig.field_name}")
    print(f"  Type: {sig.signature_type.kind}")
    print(f"  Signer: {sig.signer_name}")
    print(f"  Time: {sig.signing_time}")

Three-phase remote signing

from pyunderskrift import (
    RemoteSignerInfo,
    RemoteSigningOptions,
    DigestAlgorithm,
    SignatureAlgorithm,
    SubFilter,
    prepare_signature,
    finalize_signature,
)

# Phase 1: Prepare (caller provides signer certificate info)
signer_info = RemoteSignerInfo(
    certificate_der=cert_der_bytes,
    chain_der=[intermediate_der],
    digest_algorithm=DigestAlgorithm.Sha256,
    signature_algorithm=SignatureAlgorithm.RsaPkcs1v15,
)
options = RemoteSigningOptions(sub_filter=SubFilter.Pades)

prepared = prepare_signature(pdf_data, signer_info, options)

# Phase 2: Sign the hash remotely (e.g. via HSM API)
signature_bytes = remote_hsm_sign(prepared.attrs_hash)

# Phase 3: Finalize
signed_pdf = finalize_signature(prepared, signature_bytes)

Validation policies

from pyunderskrift import (
    BasicPdfSignaturePolicy,
    PkixPdfSignaturePolicy,
    SignatureVerifier,
    TrustStoreSet,
    PolicyConclusion,
)

# Basic policy: integrity + crypto + trust + no modifications
basic = BasicPdfSignaturePolicy(require_no_modifications=True)

# PKIX policy: adds revocation checks, grace periods, timestamp validation
pkix = PkixPdfSignaturePolicy(
    grace_period_secs=86400,
    require_revocation_check=True,
    require_no_modifications=True,
    use_timestamp_time=True,
)

stores = TrustStoreSet()
# ... configure stores ...

verifier = SignatureVerifier(stores)
verifier.set_pkix_policy(pkix)

report = verifier.verify_pdf(pdf_data)
for sig in report.signatures:
    if sig.policy_result is not None:
        if sig.policy_result.conclusion == PolicyConclusion.Passed:
            print(f"{sig.field_name}: policy PASSED")
        else:
            print(f"{sig.field_name}: {sig.policy_result.message}")
            for check in sig.policy_result.checks:
                if not check.passed:
                    print(f"  FAIL: {check.check_name} - {check.message}")

API overview

The full API is documented in the type stub. Key types:

Enums

SignatureStatus, DetectedPadesLevel, PadesLevel, SubFilter, StoreKind, PolicyConclusion, DigestAlgorithm, SignatureAlgorithm, SigningTimePlacement, RevocationSource, RevocationReason

Struct-based enums

CryptoValidity, CertValidity, SignatureType, ValidationStatus -- each has a .kind string property and optional variant-specific properties.

Trust

TrustStore, TrustStoreSet

Verification

SignatureVerifier, VerificationReport, SignatureVerificationResult

Policy

BasicPdfSignaturePolicy, PkixPdfSignaturePolicy, PolicyResult, PolicyCheckResult

Signing

PdfSigner, SigningOptions, SoftwareSigner, AlgorithmRegistry

Extraction

ExtractedSignature, extract_signatures()

Remote signing

RemoteSignerInfo, RemoteSigningOptions, PreparedSignature, prepare_signature(), finalize_signature()

Building from source

Requires Rust (stable) and Python >= 3.10.

git clone https://github.com/kushaldas/pyunderskrift.git
cd pyunderskrift
uv venv
source .venv/bin/activate
uv pip install maturin pytest
maturin develop
pytest tests/ -vvv

To generate test fixtures (requires OpenSSL):

cd tests/fixtures
bash ../../gen-test-fixtures.sh

License

BSD-2-Clause

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

pyunderskrift-0.1.3.tar.gz (49.7 kB view details)

Uploaded Source

Built Distribution

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

pyunderskrift-0.1.3-cp310-abi3-manylinux_2_28_x86_64.whl (4.0 MB view details)

Uploaded CPython 3.10+manylinux: glibc 2.28+ x86-64

File details

Details for the file pyunderskrift-0.1.3.tar.gz.

File metadata

  • Download URL: pyunderskrift-0.1.3.tar.gz
  • Upload date:
  • Size: 49.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for pyunderskrift-0.1.3.tar.gz
Algorithm Hash digest
SHA256 231cdf10883e2be0fb2cb95c09c94980d42743bee93359c58188c3ab670a8d4d
MD5 ebc10b8bf069578fa8c0f867bf318813
BLAKE2b-256 1d8c147f04830eb84f4d0998ff24f74d43cca5679b294b00777707d3473324ca

See more details on using hashes here.

Provenance

The following attestation bundles were made for pyunderskrift-0.1.3.tar.gz:

Publisher: release.yml on kushaldas/pyunderskrift

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file pyunderskrift-0.1.3-cp310-abi3-manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for pyunderskrift-0.1.3-cp310-abi3-manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 31f9a9e4dc389e2d064898c726ec342bea2b8638968376b049e9444a4257bb5e
MD5 841214478e0942c82efb8aa3acb36fc8
BLAKE2b-256 41ceb3a5df393f30e9a58ba80e3113ad3f1865ade753d1c49e20d924f9f832f8

See more details on using hashes here.

Provenance

The following attestation bundles were made for pyunderskrift-0.1.3-cp310-abi3-manylinux_2_28_x86_64.whl:

Publisher: release.yml on kushaldas/pyunderskrift

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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