import base64 import io from datetime import datetime, timezone from pyhanko import stamp from pyhanko.sign import fields, signers from pyhanko.sign import signers from pyhanko.pdf_utils import text from pyhanko.pdf_utils.incremental_writer import IncrementalPdfFileWriter from signing_service.domain.ports.pdf_signer import PDFSignerPort class PyHankoPDFSigner(PDFSignerPort): async def sign(self, pdf_bytes: bytes, certificate: str, password: str,) -> bytes: """ pdf_bytes: original PDF byte certificate: base64-encoded PKCS#12 (PFX) """ # 1️⃣ Decodificar certificado pfx_bytes = base64.b64decode(certificate) # 3️⃣ Preparar PDF en modo incremental writer = IncrementalPdfFileWriter(io.BytesIO(pdf_bytes)) # 2️⃣ Cargar clave y certificado signer = signers.SimpleSigner.load_pkcs12_data( pkcs12_bytes=pfx_bytes, passphrase=password.encode(), other_certs=[], ) # 3️⃣ Extract certificate info (from signer) cert = signer.signing_cert subject = cert.subject.native.get('common_name', 'Desconocido') # 5️⃣ Define signature field position fields.append_signature_field( writer, sig_field_spec=fields.SigFieldSpec( sig_field_name="Signature1", on_page=-1, box=(380, 100, 560, 160), # x, y, x+width, y+height ) ) # 4️⃣ Signature metadata (panel de firmas) meta = signers.PdfSignatureMetadata( field_name="Signature1", name=subject, ) pdf_signer = signers.PdfSigner( meta, signer=signer, stamp_style=stamp.TextStampStyle( stamp_text='Firmado digitalmente por: %(signer)s\nFecha: %(ts)s', text_box_style=text.TextBoxStyle( border_width=0, font_size=12 #font=opentype.GlyphAccumulatorFactory('path to font police.ttf'), ), #background=images.PdfImage('path to img.jpg') ) ) # 4️⃣ Firmar signed_pdf: io.BytesIO = await pdf_signer.async_sign_pdf( writer, ) # 5️⃣ Obtener PDF firmado return signed_pdf.getbuffer().tobytes()