2024-06-27 07:51:32 +00:00
|
|
|
import io
|
2024-10-15 05:47:58 +00:00
|
|
|
from random import randint
|
2024-06-27 07:51:32 +00:00
|
|
|
from typing import Optional
|
|
|
|
|
|
|
|
from pyhanko import stamp
|
|
|
|
from pyhanko.pdf_utils.incremental_writer import IncrementalPdfFileWriter
|
2024-10-21 16:28:20 +00:00
|
|
|
from pyhanko.sign import signers, fields
|
2024-06-27 07:51:32 +00:00
|
|
|
from typing_extensions import Buffer
|
|
|
|
|
2024-10-21 16:28:20 +00:00
|
|
|
from timestamp import LocalOpensslTimestamp
|
2024-10-11 13:07:21 +00:00
|
|
|
|
2024-06-27 07:51:32 +00:00
|
|
|
|
|
|
|
class SignOrchestrator:
|
|
|
|
"""Orchestrate the signature on document"""
|
|
|
|
|
2024-10-11 13:07:21 +00:00
|
|
|
def __init__(self, pkcs12_path: str,
|
|
|
|
tsa_config_path: str, tsa_password: str, tsa_cert_chain: str,
|
|
|
|
pkcs12_password: Optional[bytes] = None):
|
2024-06-27 07:51:32 +00:00
|
|
|
# Load signer key material from PKCS#12 file
|
|
|
|
# This assumes that any relevant intermediate certs are also included
|
|
|
|
# in the PKCS#12 file.
|
|
|
|
self.signer = signers.SimpleSigner.load_pkcs12(
|
|
|
|
pfx_file=pkcs12_path, passphrase=pkcs12_password
|
|
|
|
)
|
|
|
|
|
|
|
|
# Set up a timestamping client to fetch timestamps tokens
|
2024-10-11 13:07:21 +00:00
|
|
|
self.timestamper = LocalOpensslTimestamp(tsa_config_path, tsa_password, tsa_cert_chain)
|
2024-06-27 07:51:32 +00:00
|
|
|
|
|
|
|
self.stamp_style = stamp.TextStampStyle(
|
|
|
|
stamp_text="Signé par:\n%(signer_text)s\nLe %(ts)s",
|
|
|
|
border_width=1,
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
def _make_signature_metadata(self, reason: str, field_name: str):
|
|
|
|
# Settings for PAdES-LTA
|
|
|
|
return signers.PdfSignatureMetadata(
|
|
|
|
field_name=field_name, md_algorithm='sha256',
|
|
|
|
# Mark the signature as a PAdES signature
|
|
|
|
subfilter=fields.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,
|
|
|
|
reason=reason,
|
|
|
|
)
|
|
|
|
|
2024-10-15 05:47:58 +00:00
|
|
|
def sign(self, reason: str, signature_index: int|None, input_content: Buffer, on_page: int, box_place: (int, int, int, int), signer_text: str) -> io.BytesIO:
|
|
|
|
field_name = 'Signature' + str(signature_index) if signature_index is not None else 'Signature'+ str(randint(1000, 99999999999))
|
2024-06-27 07:51:32 +00:00
|
|
|
signature_meta = self._make_signature_metadata(reason, field_name)
|
|
|
|
|
|
|
|
pdf_signer = signers.PdfSigner(
|
|
|
|
signature_meta, signer=self.signer, timestamper=self.timestamper,
|
|
|
|
stamp_style=self.stamp_style
|
|
|
|
)
|
|
|
|
|
|
|
|
inf = io.BytesIO(input_content)
|
|
|
|
w = IncrementalPdfFileWriter(inf)
|
|
|
|
fields.append_signature_field(w, sig_field_spec=fields.SigFieldSpec(
|
|
|
|
field_name, on_page=on_page, box=box_place
|
|
|
|
))
|
|
|
|
outf = io.BytesIO()
|
|
|
|
pdf_signer.sign_pdf(
|
|
|
|
w, output=outf, appearance_text_params={'signer_text': signer_text}
|
|
|
|
)
|
|
|
|
|
|
|
|
return outf
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|