### Configure Named PKCS#12 Setups Source: https://github.com/matthiasvalvekens/pyhanko/blob/master/docs/cli-guide/config.md Define named PKCS#12 setups for on-disk key material, specifying the PFX file path and any additional certificates. ```yaml pkcs12-setups: foo: pfx-file: path/to/signer.pfx other-certs: path/to/more/certs.chain.pem ``` -------------------------------- ### Install pyHanko with Optional Dependencies Source: https://github.com/matthiasvalvekens/pyhanko/blob/master/pkgs/pyhanko/README.md Use this command to install pyHanko along with common optional dependencies. Depending on your shell, quotes may be required. ```bash pip install 'pyHanko[pkcs11,image-support,opentype,qr]' ``` ```bash pip install pyHanko[pkcs11,image-support,opentype,qr] ``` -------------------------------- ### Install pyHanko CLI Source: https://github.com/matthiasvalvekens/pyhanko/blob/master/pkgs/pyhanko-cli/README.md Install the pyHanko CLI tool using pip. This is the basic installation command. ```bash pip install pyhanko-cli ``` -------------------------------- ### Configure PKCS#11 Setup with User PIN Source: https://github.com/matthiasvalvekens/pyhanko/blob/master/docs/cli-guide/config.md Include the user PIN directly in the PKCS#11 setup configuration for non-interactive use. Ensure file permissions are set correctly for security. ```yaml pkcs11-setups: test-setup: module-path: /usr/lib/libsofthsm2.so token-criteria: label: testrsa cert-label: signer user-pin: 1234 ``` -------------------------------- ### Install PyHanko with Optional Dependencies Source: https://github.com/matthiasvalvekens/pyhanko/blob/master/README.md Use this command to install PyHanko with common optional dependencies for PKCS#11, image support, OpenType fonts, QR codes, and the CLI. Quotes may be necessary depending on your shell. ```bash pip install 'pyHanko[pkcs11,image-support,opentype,qr]' pyhanko-cli ``` ```bash pip install pyHanko[pkcs11,image-support,opentype,qr] pyhanko-cli ``` -------------------------------- ### Install pyhanko-certvalidator Source: https://github.com/matthiasvalvekens/pyhanko/blob/master/pkgs/pyhanko-certvalidator/README.md Install the library using pip. Ensure you have Python 3.7 or higher. ```bash pip install pyhanko-certvalidator ``` -------------------------------- ### Configure Named PKCS#11 Setups Source: https://github.com/matthiasvalvekens/pyhanko/blob/master/docs/cli-guide/config.md Define named PKCS#11 setups in the configuration file to simplify command-line usage. This includes specifying the module path, token criteria, and certificate label. ```yaml pkcs11-setups: test-setup: module-path: /usr/lib/libsofthsm2.so token-criteria: label: testrsa cert-label: signer ``` -------------------------------- ### Install pyHanko CLI with Optional Features (Quoted) Source: https://github.com/matthiasvalvekens/pyhanko/blob/master/pkgs/pyhanko-cli/README.md Install pyHanko with optional dependencies for PKCS#11, image handling, OpenType/TrueType support, and QR code generation. Quotes may be necessary depending on your shell. ```bash pip install 'pyHanko[pkcs11,image-support,opentype,qr]' pyhanko-cli ``` -------------------------------- ### Configure Named PEM/DER Setups Source: https://github.com/matthiasvalvekens/pyhanko/blob/master/docs/cli-guide/config.md Define named setups for on-disk key material using individual PEM or DER-encoded files, including the key file, certificate file, and any additional certificates. ```yaml pemder-setups: bar: key-file: path/to/signer.key.pem cert-file: path/to/signer.cert.pem other-certs: path/to/more/certs.chain.pem ``` -------------------------------- ### Use Named PKCS#11 Setup from Command Line Source: https://github.com/matthiasvalvekens/pyhanko/blob/master/docs/cli-guide/config.md Invoke pyHanko using a named PKCS#11 configuration defined in the config file via the `--p11-setup` argument. ```bash pyhanko sign addsig pkcs11 --p11-setup test-setup input.pdf output.pdf ``` -------------------------------- ### Install pyHanko CLI with Optional Features (Unquoted) Source: https://github.com/matthiasvalvekens/pyhanko/blob/master/pkgs/pyhanko-cli/README.md Install pyHanko with optional dependencies for PKCS#11, image handling, OpenType/TrueType support, and QR code generation. This version omits quotes, which may work in some shells. ```bash pip install pyHanko[pkcs11,image-support,opentype,qr] pyhanko-cli ``` -------------------------------- ### Invoke pyHanko CLI Source: https://github.com/matthiasvalvekens/pyhanko/blob/master/docs/cli-guide/index.md Install pyHanko-cli separately. Invoke pyHanko using the 'pyhanko' command. ```bash pyhanko --help ``` -------------------------------- ### Create PAdES B-LTA Signature Source: https://github.com/matthiasvalvekens/pyhanko/blob/master/docs/lib-guide/signing.md This example demonstrates how to create a PAdES B-LTA signature by configuring `PdfSignatureMetadata` with PAdES-specific options and using `HTTPTimeStamper` for timestamping. It also includes embedding revocation information. ```python from pyhanko.pdf_utils.incremental_writer import IncrementalPdfFileWriter from pyhanko.sign import signers, timestamps from pyhanko.sign.fields import SigSeedSubFilter from pyhanko_certvalidator import ValidationContext # Load signer key material from PKCS#12 file # This assumes that any relevant intermediate certs are also included # in the PKCS#12 file. signer = signers.SimpleSigner.load_pkcs12( pfx_file='signer.pfx', passphrase=b'secret' ) # Set up a timestamping client to fetch timestamps tokens timestamper = timestamps.HTTPTimeStamper( url='http://tsa.example.com/timestampService' ) # Settings for PAdES-LTA signature_meta = signers.PdfSignatureMetadata( field_name='Signature', md_algorithm='sha256', # Mark the signature as a PAdES signature subfilter=SigSeedSubFilter.PADES, # We'll also need a validation context # to fetch & embed revocation info. validation_context=ValidationContext(allow_fetching=True), # Embed relevant OCSP responses / CRLs (PAdES-LT) embed_validation_info=True, # Tell pyHanko to put in an extra DocumentTimeStamp # to kick off the PAdES-LTA timestamp chain. use_pades_lta=True ) with open('input.pdf', 'rb') as inf: w = IncrementalPdfFileWriter(inf) with open('output.pdf', 'wb') as outf: signers.sign_pdf( w, signature_meta=signature_meta, signer=signer, timestamper=timestamper, output=outf ) ``` -------------------------------- ### Use Named Validation Context in CLI Source: https://github.com/matthiasvalvekens/pyhanko/blob/master/docs/cli-guide/config.md Example of signing a PDF using a specific named validation context from the configuration file. ```bash pyhanko sign addsig --field Sig1 --timestamp-url http://tsa.example.com \ --with-validation-info --validation-context special-setup \ --use-pades pemder --key key.pem --cert cert.pem input.pdf output.pdf ``` -------------------------------- ### Sign PDF using Google Cloud KMS Source: https://github.com/matthiasvalvekens/pyhanko/blob/master/docs/lib-guide/adv-examples.md Demonstrates how to use the GCP client library to produce signatures with Google Cloud KMS. Requires `google-cloud-kms` and `crcmod` to be installed. Assumes Application Default Credentials are configured. ```python from dataclasses import dataclass from google.cloud import kms from google.cloud.kms_v1 import CryptoKeyVersion from cryptography import x509 from cryptography.hazmat.primitives import hashes from pyhanko.pdf_utils.incremental_writer import IncrementalPdfFileWriter from pyhanko.sign import signers from pyhanko.sign.ades.settings import PdfSignatureMetadata from pyhanko.sign.validation.settings import SimpleCertificateStore from pyhanko.pdf_utils.reader import PdfReader from pyhanko.pdf_utils.writer import PdfWriter from pyhanko.sign.signers.pdf_cms_signer import sign_pdf, async_sign_pdf from pyhanko.keys import load_cert_from_pemder, load_certs_from_pemder from pyhanko.pdf_utils.errors import SigningError import crcmod import asyncio @dataclass(frozen=True) class GCPKeyRing: project_id: str location_id: str key_ring_id: str @dataclass(frozen=True) class GCPKMSKey: key_ring: GCPKeyRing key_id: str version_id: str @property def path(self) -> str: return kms.KeyManagementServiceAsyncClient.crypto_key_version_path( self.key_ring.project_id, self.key_ring.location_id, self.key_ring.key_ring_id, self.key_id, self.version_id, ) class GCPKMSSigner(signers.Signer): def __init__( self, *, signing_cert: x509.Certificate, kms_key: GCPKMSKey, **kwargs ): self.kms_key = kms_key self.client = kms.KeyManagementServiceAsyncClient() super().__init__(signing_cert=signing_cert, **kwargs) async def async_sign_raw( self, data: bytes, digest_algorithm: str, dry_run=False ) -> bytes: if dry_run: return bytes(256) # Note: this method makes no effort to check whether the digest # algorithm matches the expectation of the upstream API md_spec = get_pyca_cryptography_hash(digest_algorithm) md = hashes.Hash(md_spec) md.update(data) digest = md.finalize() name = self.kms_key.path crc32c = crcmod.predefined.mkPredefinedCrcFun("crc-32c") request = kms.AsymmetricSignRequest( { "name": name, "digest": {digest_algorithm: digest}, "digest_crc32c": crc32c(digest), } ) response = await self.client.asymmetric_sign(request=request) # From https://cloud.google.com/kms/docs/create-validate-signatures#kms-sign-asymmetric-python if ( not response.verified_digest_crc32c or response.name != name or response.signature_crc32c != crc32c(response.signature) ): raise SigningError( "The request sent to the server was corrupted in-transit." ) return response.signature KEYRING = GCPKeyRing("my-project-id", "europe-west1", "pyhanko-test") def run_test(input_file, output_file, key_name, signer_cert_file, ca_certs_file): cert_obj = load_cert_from_pemder(signer_cert_file) registry = SimpleCertificateStore.from_certs(load_certs_from_pemder(ca_certs_file)) signer = GCPKMSSigner( kms_key=GCPKMSKey(KEYRING, key_name, "1"), signing_cert=cert_obj, cert_registry=registry, ) with open(input_file, 'rb') as inf: w = IncrementalPdfFileWriter(inf) meta = PdfSignatureMetadata(field_name='Sig1') with open(output_file, 'wb') as outf: await async_sign_pdf(w, meta, signer, output=outf) asyncio.run( run_test( 'input.pdf', 'output.pdf', 'my-test-key', 'signer.cert.pem', 'ca-certs.cert.pem' ) ) ``` -------------------------------- ### Verify Sigstore Signatures for PyHanko Releases Source: https://github.com/matthiasvalvekens/pyhanko/blob/master/docs/artifact-authenticity.md Use this command to verify Sigstore signatures for standard PyHanko wheel and tar.gz archives. Ensure you have `sigstore` installed and download the `.sigstore` bundles and release artifacts. ```bash #!/bin/bash EXPECTED_VERSION= REPO=MatthiasValvekens/pyHanko sigstore verify github \ --cert-identity "https://github.com/$REPO/.github/workflows/release.yml@refs/tags/v$EXPECTED_VERSION" \ --ref "refs/tags/v$EXPECTED_VERSION" \ --repo "$REPO" \ pyhanko-$EXPECTED_VERSION-*.whl pyhanko-$EXPECTED_VERSION.tar.gz ``` -------------------------------- ### Check Formatting with Ruff Source: https://github.com/matthiasvalvekens/pyhanko/blob/master/CONTRIBUTING.md Use this command to check for code formatting issues according to Ruff's standards. Ensure your code adheres to the project's style guide. ```bash ruff format --check ``` -------------------------------- ### Advanced Stamp Style Configuration Source: https://github.com/matthiasvalvekens/pyhanko/blob/master/docs/cli-guide/config.md Configure a stamp style with advanced layout options for background image and text box positioning. This example demonstrates custom margins and alignment. ```yaml stamp-styles: more-complex-demo: type: text stamp-text: "Test Test Test\n%(ts)s" background: image.png background-opacity: 1 background-layout: x-align: left margins: left: 10 top: 10 bottom: 10 inner-content-layout: x-align: right margins: right: 10 ``` -------------------------------- ### Verify Sigstore Signatures for PyHanko Subprojects Source: https://github.com/matthiasvalvekens/pyhanko/blob/master/docs/artifact-authenticity.md Use this command to verify Sigstore signatures for subproject releases like `pyhanko-certvalidator`. The tag format differs, requiring a prefixed version number. Ensure `sigstore` is installed and necessary files are downloaded. ```bash #!/bin/bash EXPECTED_VERSION= REPO=MatthiasValvekens/pyHanko sigstore verify github \ --cert-identity "https://github.com/$REPO/.github/workflows/release.yml@refs/tags/pyhanko-certvalidator/v$EXPECTED_VERSION" \ --ref "refs/tags/pyhanko-certvalidator/v$EXPECTED_VERSION" \ --repo "$REPO" \ pyhanko_certvalidator-$EXPECTED_VERSION-*.whl pyhanko_certvalidator-$EXPECTED_VERSION.tar.gz ``` -------------------------------- ### Custom Signer with YubiHSM SDK Source: https://github.com/matthiasvalvekens/pyhanko/blob/master/docs/lib-guide/adv-examples.md Implement a custom pyHanko Signer using the YubiHSM Python SDK for signing PDFs with a YubiHSM 2. This example shows how to integrate with the YubiHSM's signing capabilities. ```python from pyhanko.keys.internal import translate_pyca_cryptography_cert_to_asn1 from pyhanko.sign.signers.pdf_cms import Signer from pyhanko_certvalidator.registry import SimpleCertificateStore from pyhanko_certvalidator.util import get_pyca_cryptography_hash from yubihsm.core import AuthSession from yubihsm.objects import AsymmetricKey, Opaque class YubiHsmECDSASigner(Signer): def __init__(self, session: AuthSession, key_obj_id: int, other_cert_object_ids: list[int]): self.key = AsymmetricKey(session, key_obj_id) signing_cert = translate_pyca_cryptography_cert_to_asn1( self.key.get_certificate() ) cert_registry = SimpleCertificateStore() for cert_id in other_cert_object_ids: cert = translate_pyca_cryptography_cert_to_asn1( Opaque(session, cert_id).get_certificate() ) cert_registry.register(cert) super().__init__( signing_cert=signing_cert, cert_registry=cert_registry ) async def async_sign_raw( self, data: bytes, digest_algorithm: str, dry_run=False ) -> bytes: return self.key.sign_ecdsa( data, get_pyca_cryptography_hash(digest_algorithm) ) ``` -------------------------------- ### Configure Signature Field with Seed Values Source: https://github.com/matthiasvalvekens/pyhanko/blob/master/docs/lib-guide/sig-fields.md Instantiate a signature field specification with specific seed value requirements. This example enforces certificate issuer, provides signing reasons, and requires a minimum digest method (SHA256). Use bitwise OR to combine flags. ```python from pyhanko.sign import fields from pyhanko.keys import load_cert_from_pemder franchising_ca = load_cert_from_pemder('path/to/certfile') sv = fields.SigSeedValueSpec( reasons=[ 'I vote in favour of the proposed measure', 'I vote against the proposed measure', 'I formally abstain from voting on the proposed measure' ], cert=fields.SigCertConstraints( issuers=[franchising_ca], flags=fields.SigCertConstraintFlags.ISSUER ), digest_methods=['sha256', 'sha384', 'sha512'], flags=fields.SigSeedValFlags.REASONS | fields.SigSeedValFlags.DIGEST_METHOD ) sp = fields.SigFieldSpec('BallotSignature', seed_value_dict=sv) ``` -------------------------------- ### Validate Signatures with EU Trusted Lists Source: https://github.com/matthiasvalvekens/pyhanko/blob/master/docs/cli-guide/validation.md Validate signatures against EU public trust lists using `--eutl-all` or `--eutl-territories`. Requires installing pyHanko with `[etsi,async-http]` optional dependencies. Caching of trusted lists can lead to long initial run times. ```bash pyhanko sign validate --eutl-all --pretty-print document.pdf ``` -------------------------------- ### Use Named On-Disk Key Material from Command Line Source: https://github.com/matthiasvalvekens/pyhanko/blob/master/docs/cli-guide/config.md Invoke pyHanko using named PKCS#12 or PEM/DER configurations from the command line using the `--p12-setup` or `--pemder-setup` arguments, respectively. ```bash pyhanko sign addsig pkcs12 --p12-setup foo input.pdf output.pdf pyhanko sign addsig pemder --pemder-setup bar input.pdf output.pdf ``` -------------------------------- ### Register Plugin via pyproject.toml Source: https://github.com/matthiasvalvekens/pyhanko/blob/master/docs/lib-guide/cli-plugins.md Configure your plugin's entry point in `pyproject.toml` under the `pyhanko.cli_plugin.signing` group to make it discoverable by pyHanko. ```toml [project.entry-points."pyhanko.cli_plugin.signing"] your_plugin = "some_package.path.to.module:SomePluginClass" ``` -------------------------------- ### Async Signing with aiohttp Source: https://github.com/matthiasvalvekens/pyhanko/blob/master/docs/lib-guide/signing.md Demonstrates how to use aiohttp for asynchronous network I/O in pyHanko for creating PAdES-B-LTA signatures. Requires setting up an aiohttp session and configuring ValidationContext and TimeStamper with aiohttp backends. ```python import aiohttp from pyhanko.pdf_utils.incremental_writer import IncrementalPdfFileWriter from pyhanko.sign import signers from pyhanko.sign.fields import SigSeedSubFilter from pyhanko.sign.timestamps.aiohttp_client import AIOHttpTimeStamper from pyhanko_certvalidator import ValidationContext from pyhanko_certvalidator.fetchers.aiohttp_fetchers import AIOHttpFetcherBackend # Load signer key material from PKCS#12 file # (see earlier examples) signer = signers.SimpleSigner.load_pkcs12( pfx_file='signer.pfx', passphrase=b'secret' ) # This demo async function takes an aiohttp session, an input # file name and an output file name. async def sign_doc_demo(session, input_file, output_file): # Use the aiohttp fetcher backend provided by pyhanko-certvalidator, # and tell it to use our client session. validation_context = ValidationContext( fetcher_backend=AIOHttpFetcherBackend(session), allow_fetching=True ) # Similarly, we choose an RFC 3161 client implementation # that uses AIOHttp under the hood timestamper = AIOHttpTimeStamper( 'http://tsa.example.com/timestampService', session=session ) # The signing config is otherwise the same settings = signers.PdfSignatureMetadata( field_name='AsyncSignatureExample', validation_context=validation_context, subfilter=SigSeedSubFilter.PADES, embed_validation_info=True ) with open(input_file, 'rb') as inf: w = IncrementalPdfFileWriter(inf) with open(output_file, 'wb') as outf: await signers.async_sign_pdf( w, settings, signer=signer, timestamper=timestamper, output=outf ) async def demo(): # Set up our aiohttp session async with aiohttp.ClientSession() as session: await sign_doc_demo(session, 'input.pdf', 'output.pdf') ``` -------------------------------- ### Configure Logging Options Source: https://github.com/matthiasvalvekens/pyhanko/blob/master/docs/cli-guide/config.md Set up logging levels and output streams for the root logger and specific modules. The `level` key is mandatory for per-module configurations. ```yaml logging: root-level: ERROR root-output: stderr by-module: pyhanko_certvalidator: level: DEBUG output: pyhanko_certvalidator.log pyhanko.sign: level: DEBUG ``` -------------------------------- ### Basic SigningCommandPlugin Skeleton Source: https://github.com/matthiasvalvekens/pyhanko/blob/master/docs/lib-guide/cli-plugins.md Implement this interface to create a pyHanko CLI plugin. Define the subcommand name, help text, CLI options, and the signer creation logic. ```python class MySigningCommand(SigningCommandPlugin): subcommand_name = 'mysigner' help_summary = 'a short line about the plugin' def click_options(self) -> List[click.Option]: ... def create_signer( self, context: CLIContext, **kwargs ) -> ContextManager[Signer]: ... ``` -------------------------------- ### Instantiate PdfFileReader from a file Source: https://github.com/matthiasvalvekens/pyhanko/blob/master/docs/lib-guide/reading-writing.md Open a PDF file for reading by instantiating PdfFileReader with a file object opened in binary read mode. ```python from pyhanko.pdf_utils.reader import PdfFileReader with open('document.pdf', 'rb') as doc: r = PdfFileReader(doc) # ... do stuff ... ``` -------------------------------- ### Generic Data Signing with CMS Source: https://github.com/matthiasvalvekens/pyhanko/blob/master/docs/lib-guide/signing.md Use this to produce CMS signatures for arbitrary data, not necessarily for PDF signatures. The signer can operate in detached or encapsulating mode. This example embeds the signed payload as an attachment in a PDF. ```python from pyhanko.sign.signers.pdf_cms import SimpleSigner from pyhanko.sign.signers.functions import embed_payload_with_cms from pyhanko.pdf_utils import embed, writer async def demo(): data = b'Hello world!' # instantiate a SimpleSigner sgn = SimpleSigner(...) # Sign some data signature = \ await sign.async_sign_general_data(data, 'sha256', detached=False) # Embed the payload into a PDF file, with the signature # object as a related file. w = writer.PdfFileWriter() # fresh writer, for demonstration's sake embed_payload_with_cms( w, file_spec_string='attachment.txt', file_name='attachment.txt', payload=embed.EmbeddedFileObject.from_file_data( w, data=data, mime_type='text/plain', ), cms_obj=signature, file_spec_kwargs={'description': "Signed attachment test"} ) ``` -------------------------------- ### Configure Time Drift Tolerance Source: https://github.com/matthiasvalvekens/pyhanko/blob/master/docs/cli-guide/config.md Set the global time drift tolerance and override it locally within validation contexts. This example shows a global setting of 30 seconds and a context-specific setting of 180 seconds. ```yaml time-tolerance: 30 validation-contexts: setup-a: time-tolerance: 180 trust: customca.pem.cert trust-replace: true other-certs: some-cert.pem.cert setup-b: trust: customca.pem.cert trust-replace: false ``` -------------------------------- ### Sign a PDF document using PdfSigner instance Source: https://github.com/matthiasvalvekens/pyhanko/blob/master/docs/lib-guide/signing.md This approach offers more granular control and reusability by instantiating PdfSigner directly. It's functionally equivalent to the sign_pdf convenience wrapper but allows for more customization. ```python from pyhanko.sign import signers from pyhanko.pdf_utils.incremental_writer import IncrementalPdfFileWriter cms_signer = signers.SimpleSigner.load( 'path/to/signer/key.pem', 'path/to/signer/cert.pem', ca_chain_files=('path/to/relevant/certs.pem',), key_passphrase=b'secret' ) with open('document.pdf', 'rb') as doc: w = IncrementalPdfFileWriter(doc) out = signers.PdfSigner( signers.PdfSignatureMetadata(field_name='Signature1'), signer=cms_signer, ).sign_pdf(w) # do stuff with 'out' # ... ``` -------------------------------- ### Register Plugin via Configuration File Source: https://github.com/matthiasvalvekens/pyhanko/blob/master/docs/lib-guide/cli-plugins.md Alternatively, reference your plugin class directly in pyHanko's configuration file under the `plugins` key. ```yaml plugins: - some_package.path.to.module:SomePluginClass ``` -------------------------------- ### Configure Signer Key Usage in Validation Context Source: https://github.com/matthiasvalvekens/pyhanko/blob/master/docs/cli-guide/config.md Define required signer key usage and extended key usage extensions for a named validation context. This example sets specific key usages and an OID for extended key usage. ```yaml validation-contexts: setup-a: trust: customca.pem.cert trust-replace: true other-certs: some-cert.pem.cert signer-key-usage: ["digital_signature", "non_repudiation"] signer-extd-key-usage: ["code_signing", "2.999"] ``` -------------------------------- ### Configure On-Disk Key Material with Passphrases Source: https://github.com/matthiasvalvekens/pyhanko/blob/master/docs/cli-guide/config.md For non-interactive use, specify passphrases for PKCS#12 or PEM/DER key material directly in the configuration. Ensure secure file access permissions. ```yaml pkcs12-setups: foo: pfx-file: path/to/signer.pfx other-certs: path/to/more/certs.chain.pem pfx-passphrase: secret pemder-setups: bar: key-file: path/to/signer.key.pem cert-file: path/to/signer.cert.pem other-certs: path/to/more/certs.chain.pem key-passphrase: secret ``` -------------------------------- ### Configure Signature Field with MDP Settings Source: https://github.com/matthiasvalvekens/pyhanko/blob/master/docs/lib-guide/sig-fields.md Use SigFieldSpec to define a signature field with specific MDP settings. This example locks a field named 'SomeTextField' and sets the DocMDP value to FORM_FILLING (level 2). Other software might not respect these pyHanko-specific settings. ```python from pyhanko.sign import fields fields.SigFieldSpec( 'Sig1', box=(10, 74, 140, 134), field_mdp_spec=fields.FieldMDPSpec( fields.FieldMDPAction.INCLUDE, fields=['SomeTextField'] ), doc_mdp_update_value=fields.MDPPerm.FORM_FILLING ) ``` -------------------------------- ### Asynchronous PDF Signing Source: https://github.com/matthiasvalvekens/pyhanko/blob/master/docs/lib-guide/signing.md Demonstrates how to sign a PDF document asynchronously. This is the preferred mode for lower-level functionality and can be more efficient for I/O-bound operations, such as network requests for remote signing services or revocation info. ```python import asyncio from pyhanko.sign import signers from pyhanko.pdf_utils.incremental_writer import IncrementalPdfFileWriter async def async_demo(signer, fname): with open(fname, 'rb') as doc: w = IncrementalPdfFileWriter(doc) out = await signers.async_sign_pdf( w, signers.PdfSignatureMetadata(field_name='Signature1'), signer=signer, ) return out cms_signer = signers.SimpleSigner.load( 'path/to/signer/key.pem', 'path/to/signer/cert.pem', ca_chain_files=('path/to/relevant/certs.pem',), key_passphrase=b'secret' ) asyncio.run(async_demo(cms_signer, 'document.pdf')) ``` -------------------------------- ### Use PdfCMSEmbedder API for PDF Signing Source: https://github.com/matthiasvalvekens/pyhanko/blob/master/docs/lib-guide/signing.md Demonstrates the low-level PdfCMSEmbedder API for managing PDF signing workflows. This includes setting up the form field, creating a placeholder signature, preparing the document for signing, and embedding the final CMS object. Use this when you need full control over the CMS object construction. ```python from datetime import datetime from pyhanko.sign import signers from pyhanko.sign.signers import cms_embedder from pyhanko.pdf_utils.incremental_writer import IncrementalPdfFileWriter from io import BytesIO input_buf = BytesIO(b'') w = IncrementalPdfFileWriter(input_buf) # Phase 1: coroutine sets up the form field, and returns a reference cms_writer = cms_embedder.PdfCMSEmbedder().write_cms( field_name='Signature', writer=w ) sig_field_ref = next(cms_writer) # just for kicks, let's check assert sig_field_ref.get_object()['/T'] == 'Signature' # Phase 2: make a placeholder signature object, # wrap it up together with the MDP config we want, and send that # on to cms_writer timestamp = datetime.now(tz=tzlocal.get_localzone()) sig_obj = signers.SignatureObject(timestamp=timestamp, bytes_reserved=8192) md_algorithm = 'sha256' # for demonstration purposes, let's do a certification signature instead # of a plain old approval signature here cms_writer.send( cms_embedder.SigObjSetup( sig_placeholder=sig_obj, mdp_setup=cms_embedder.SigMDPSetup( md_algorithm=md_algorithm, certify=True, docmdp_perms=fields.MDPPerm.NO_CHANGES ) ) ) # Phase 3: write & hash the document (with placeholder) prep_digest, output = cms_writer.send( cms_embedder.SigIOSetup(md_algorithm=md_algorithm, in_place=True) ) # The `output` variable is a handle to the stream that contains # the document to be signed, with a placeholder allocated to hold # the actual signature contents. # Phase 4: construct the CMS object, and pass it on to cms_writer # NOTE: I'm using a regular SimpleSigner here, but you can substitute # whatever CMS supplier you want. signer: signers.SimpleSigner = FROM_CA # let's supply the CMS object as a raw bytestring cms_bytes = signer.sign( data_digest=prep_digest.document_digest, digest_algorithm=md_algorithm, timestamp=timestamp ).dump() sig_contents = cms_writer.send(cms_bytes) # The (signed) output document is in `output` now. # `sig_contents` holds the content of the signature container # in the PDF file, including any padding. ``` -------------------------------- ### Sign PDF with PEM/DER key and certificate Source: https://github.com/matthiasvalvekens/pyhanko/blob/master/docs/cli-guide/signing.md Use this command to sign a PDF using a private key and certificate stored as separate PEM or DER files. You will be prompted for a passphrase if the key is protected. The `--passfile` option can be used to provide the passphrase from a file. ```bash pyhanko sign addsig --field Sig1 pemder \ --key key.pem --cert cert.pem input.pdf output.pdf ``` -------------------------------- ### Define Click Options for a Plugin Source: https://github.com/matthiasvalvekens/pyhanko/blob/master/docs/lib-guide/cli-plugins.md Provide a list of `click.Option` objects to define the command-line arguments for your plugin's subcommand. Refer to Click documentation for detailed option definitions. ```python def click_options(self) -> List[click.Option]: return [ click.Option( ('--lib',), help='path to PKCS#11 module', type=readable_file, required=False, ), click.Option( ('--token-label',), help='PKCS#11 token label', type=str, required=False, ), click.Option( ('--cert-label',), help='certificate label', type=str, required=False, ), click.Option( ('--key-label',), help='key label', type=str, required=False ), ] ``` -------------------------------- ### Sign PDF with Google Cloud KMS using PKCS#11 Source: https://github.com/matthiasvalvekens/pyhanko/blob/master/docs/lib-guide/adv-examples.md Demonstrates producing a signature with Google Cloud KMS using their PKCS#11 library. Assumes the PKCS#11 library is configured and the KMS_PKCS11_CONFIG environment variable is set. Requires ambient credentials for GCP API access. ```python from asn1crypto import algos from pyhanko.config.pkcs11 import PKCS11SignatureConfig from pyhanko.keys import load_cert_from_pemder, load_certs_from_pemder from pyhanko.pdf_utils.incremental_writer import IncrementalPdfFileWriter from pyhanko.sign import pkcs11 from pyhanko.sign.signers import sign_pdf, PdfSignatureMetadata MODULE="/path/to/libkmsp11.so" def run_test(input_file, output_file, key_name, signer_cert_file, ca_certs_file): cert_obj = load_cert_from_pemder(signer_cert_file) config = PKCS11SignatureConfig( module_path=MODULE, slot_no=0, key_label=key_name, signing_certificate=cert_obj, other_certs_to_pull=None, other_certs = list(load_certs_from_pemder(ca_certs_file)), ) with pkcs11.PKCS11SigningContext(config) as signer: with open(input_file, 'rb') as inf: w = IncrementalPdfFileWriter(inf) meta = PdfSignatureMetadata(field_name='Sig1') with open(output_file, 'wb') as outf: sign_pdf(w, meta, signer, output=outf) run_test( 'input.pdf', 'output.pdf', 'my-test-key', 'signer.cert.pem', 'ca-certs.cert.pem' ) ``` -------------------------------- ### Prepare EU Trusted List Registry Source: https://github.com/matthiasvalvekens/pyhanko/blob/master/docs/lib-guide/validation.md This asynchronous function prepares the EU trusted list registry. It uses `aiohttp` for network requests and `FileSystemTLCache` for caching trust lists. Consider limiting the number of territories using `only_territories` for faster initial downloads. ```python import asyncio import aiohttp from datetime import timedelta from pyhanko_certvalidator import ValidationContext from pyhanko.pdf_utils.reader import PdfFileReader from pyhanko.sign.validation import async_validate_pdf_signature from pyhanko.sign.validation.qualified.eutl_fetch import ( FileSystemTLCache, lotl_to_registry, ) from pyhanko.sign.validation.qualified.tsp import TSPTrustManager async def prepare_registry(): async with aiohttp.ClientSession() as client: tl_cache = FileSystemTLCache( '/var/cache/trust-lists', expire_after=timedelta(days=14) ) registry, errors = await lotl_to_registry( # 'None' => bootstrap from the list-of-the-lists # Note: downloading the full EUTL for all member states # on a cold cache can take a while # pass only_territories='be,fr,de' if you want to # limit the number of lists to take into account. lotl_xml=None, client=client, cache=tl_cache, ) # the 'errors' are recoverable errors, they generally # mean that the collected data may be incomplete return registry ``` -------------------------------- ### Prepare Document for Interrupted Signing (Python) Source: https://github.com/matthiasvalvekens/pyhanko/blob/master/docs/lib-guide/signing.md Prepares a PDF document for an interrupted signing process by calculating digests and extracting necessary signing instructions. This is useful for remote signing scenarios where the cryptographic operation is performed externally. Requires `pyhanko.sign`, `pyhanko_certvalidator`, and `pyhanko.pdf_utils`. ```python from pyhanko.sign import signers, fields, timestamps from pyhanko.sign.signers.pdf_signer import PdfTBSDocument from pyhanko_certvalidator import ValidationContext from pyhanko.pdf_utils.writer import BasePdfFileWriter # Skeleton code for an interrupted PAdES signature async def prep_document(w: BasePdfFileWriter): vc = ValidationContext(...) pdf_signer = signers.PdfSigner( signers.PdfSignatureMetadata( field_name='SigNew', embed_validation_info=True, use_pades_lta=True, subfilter=fields.SigSeedSubFilter.PADES, validation_context=vc, md_algorithm='sha256' ), # note: this signer will not perform any cryptographic operations, # it's just there to bundle certificates with the generated CMS # object and to provide size estimates signer=signers.ExternalSigner( signing_cert=..., ..., # placeholder value, appropriate for a 2048-bit RSA key # (for example's sake) signature_value=bytes(256), ), timestamper=timestamps.HTTPTimeStamper('http://tsa.example.com') ) prep_digest, tbs_document, output_handle = \ await pdf_signer.async_digest_doc_for_signing(w) md_algorithm = tbs_document.md_algorithm psi = tbs_document.post_sign_instructions signed_attrs = await ext_signer.signed_attrs( prep_digest.document_digest, 'sha256', use_pades=True ) return prep_digest, signed_attrs, psi, output_handle ``` -------------------------------- ### Configure PKCS#11 with Advanced Token Criteria Source: https://github.com/matthiasvalvekens/pyhanko/blob/master/docs/cli-guide/config.md Utilise advanced PKCS#11 configuration options, such as selecting tokens by serial number and using object IDs for certificates and keys. ```yaml pkcs11-setups: test-setup: module-path: /path/to/module.so token-criteria: serial: 17aa21784b9f cert-id: 1382391af78ac390 key-id: 1382391af78ac390 ``` -------------------------------- ### Sign a PDF document with SimpleSigner Source: https://github.com/matthiasvalvekens/pyhanko/blob/master/docs/lib-guide/signing.md Use this convenience wrapper for basic PDF signing. Ensure the signer key, certificate, and CA chain are correctly specified. The key passphrase is required if the private key is encrypted. ```python from pyhanko.sign import signers from pyhanko.pdf_utils.incremental_writer import IncrementalPdfFileWriter cms_signer = signers.SimpleSigner.load( 'path/to/signer/key.pem', 'path/to/signer/cert.pem', ca_chain_files=('path/to/relevant/certs.pem',), key_passphrase=b'secret' ) with open('document.pdf', 'rb') as doc: w = IncrementalPdfFileWriter(doc) out = signers.sign_pdf( w, signers.PdfSignatureMetadata(field_name='Signature1'), signer=cms_signer, ) # do stuff with 'out' # ... ``` -------------------------------- ### Verify pyHanko Release Artifacts with SLSA Source: https://github.com/matthiasvalvekens/pyhanko/blob/master/docs/artifact-authenticity.md Use this command to verify pyHanko release artifacts against SLSA provenance data. Ensure you have downloaded the provenance file and release artifacts, and replace `` with the actual version. ```bash EXPECTED_VERSION= REPO=MatthiasValvekens/pyHanko slsa-verifier verify-artifact \ --source-tag "v$EXPECTED_VERSION" \ --provenance-path ./multiple.intoto.jsonl \ --source-uri "github.com/$REPO" \ pyhanko-$EXPECTED_VERSION-*.whl pyhanko-$EXPECTED_VERSION.tar.gz ``` -------------------------------- ### Instantiate PdfFileReader from in-memory bytes Source: https://github.com/matthiasvalvekens/pyhanko/blob/master/docs/lib-guide/reading-writing.md Read PDF data from a bytes object by wrapping it in BytesIO and passing it to PdfFileReader. ```python from pyhanko.pdf_utils.reader import PdfFileReader from io import BytesIO buf = b'' doc = BytesIO(buf) r = PdfFileReader(doc) # ... do stuff ... ``` -------------------------------- ### Apply a Stamp to a PDF Page Source: https://github.com/matthiasvalvekens/pyhanko/blob/master/docs/cli-guide/stamping.md Use this command to render a stamp in a specified style at given coordinates on a particular page of a PDF. The output is written to a new file. Refer to the documentation for details on defining named styles. ```bash pyhanko stamp --style-name some-style --page 2 input.pdf output.pdf 50 100 ```