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.2.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.2-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.2.tar.gz.

File metadata

  • Download URL: pyunderskrift-0.1.2.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.2.tar.gz
Algorithm Hash digest
SHA256 48a30ee6c54d47057671c8f5fc3360031c6ff61831584a65097235e35ddeaf65
MD5 d9e54c730622c3de00d5ba0cbcbda210
BLAKE2b-256 0758813eda52911bd020d5ff26e5428c53d10a42102cfe630d1a578de69f20b4

See more details on using hashes here.

Provenance

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

File metadata

File hashes

Hashes for pyunderskrift-0.1.2-cp310-abi3-manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 b4fc16a932fd4caecd3dd0f0858ca23ea8a01097d9379ecaf0016982d05f31bc
MD5 1f43121cd50a90ea97858eaeb16963fb
BLAKE2b-256 be5733d6fc54fbaecab2435fa1c2ca98a159536beea99b63df2310c58ac05062

See more details on using hashes here.

Provenance

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