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.4.tar.gz (50.1 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.4-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.4.tar.gz.

File metadata

  • Download URL: pyunderskrift-0.1.4.tar.gz
  • Upload date:
  • Size: 50.1 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.4.tar.gz
Algorithm Hash digest
SHA256 8ef7d04dfcdbe9342c176208411b840e7ac433e50ac8e04d378689bbe27e88ae
MD5 f996c3d77d2a1b0630c81f2dc390a005
BLAKE2b-256 49e5d23a5d3c1f9e7c161127fa67a2ad01e24802e79a7ce6a8e35c2c17c28159

See more details on using hashes here.

Provenance

The following attestation bundles were made for pyunderskrift-0.1.4.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.4-cp310-abi3-manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for pyunderskrift-0.1.4-cp310-abi3-manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 5ee188989d1be38784c62b00d5f928629a6257510012217092511c403ede1927
MD5 caeb81a63d049f92bc3338bbdee6ef7b
BLAKE2b-256 76cbe140b47040626313bdd493eee153a3cfb482038789d1b4dc3ef47ebd428b

See more details on using hashes here.

Provenance

The following attestation bundles were made for pyunderskrift-0.1.4-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