Module fpdf.sign
Document signature generation
Expand source code
"Document signature generation"
import hashlib
from datetime import timezone
from unittest.mock import patch
from .syntax import build_obj_dict, Name
from .syntax import create_dictionary_string as pdf_dict
from .util import buffer_subst
class Signature:
def __init__(self, contact_info=None, location=None, m=None, reason=None):
self.type = Name("Sig")
self.filter = Name("Adobe.PPKLite")
self.sub_filter = Name("adbe.pkcs7.detached")
self.contact_info = contact_info
"Information provided by the signer to enable a recipient to contact the signer to verify the signature"
self.location = location
"The CPU host name or physical location of the signing"
self.m = m
"The time of signing"
self.reason = reason
"The reason for the signing"
self.byte_range = _SIGNATURE_BYTERANGE_PLACEHOLDER
self.contents = "<" + _SIGNATURE_CONTENTS_PLACEHOLDER + ">"
def serialize(self, _security_handler=None, _obj_id=None):
obj_dict = build_obj_dict(
{key: getattr(self, key) for key in dir(self)},
_security_handler=_security_handler,
_obj_id=_obj_id,
)
return pdf_dict(obj_dict)
def sign_content(signer, buffer, key, cert, extra_certs, hashalgo, sign_time):
"""
Perform PDF signing based on the content of the buffer, performing substitutions on it.
The signing operation does not alter the buffer size
"""
# We start by substituting the ByteRange,
# that defines which part of the document content the signature is based on.
# This is basically ALL the content EXCEPT the signature content itself.
sig_placeholder = _SIGNATURE_CONTENTS_PLACEHOLDER.encode("latin1")
start_index = buffer.find(sig_placeholder)
end_index = start_index + len(sig_placeholder)
content_range = (0, start_index - 1, end_index + 1, len(buffer) - end_index - 1)
# pylint: disable=consider-using-f-string
buffer = buffer_subst(
buffer,
_SIGNATURE_BYTERANGE_PLACEHOLDER,
"[%010d %010d %010d %010d]" % content_range,
)
# We compute the ByteRange hash, of everything before & after the placeholder:
content_hash = hashlib.new(hashalgo)
content_hash.update(buffer[: content_range[1]]) # before
content_hash.update(buffer[content_range[2] :]) # after
# This monkey-patching is needed, at the time of endesive v2.0.9,
# to get control over signed_time, initialized by endesive.signer.sign() to be datetime.now():
class mock_datetime:
@staticmethod
def now(tz): # pylint: disable=unused-argument
return sign_time.astimezone(timezone.utc)
sign = patch("endesive.signer.datetime", mock_datetime)(signer.sign)
contents = sign(
datau=None,
key=key,
cert=cert,
othercerts=extra_certs,
hashalgo=hashalgo,
attrs=True,
signed_value=content_hash.digest(),
)
contents = _pkcs11_aligned(contents).encode("latin1")
# Sanity check, otherwise we will break the xref table:
assert len(sig_placeholder) == len(contents)
return buffer.replace(sig_placeholder, contents, 1)
def _pkcs11_aligned(data):
data = "".join(f"{i:02x}" for i in data)
return data + "0" * (0x4000 - len(data))
_SIGNATURE_BYTERANGE_PLACEHOLDER = "[0000000000 0000000000 0000000000 0000000000]"
_SIGNATURE_CONTENTS_PLACEHOLDER = _pkcs11_aligned((0,))
Functions
def sign_content(signer, buffer, key, cert, extra_certs, hashalgo, sign_time)
-
Perform PDF signing based on the content of the buffer, performing substitutions on it. The signing operation does not alter the buffer size
Expand source code
def sign_content(signer, buffer, key, cert, extra_certs, hashalgo, sign_time): """ Perform PDF signing based on the content of the buffer, performing substitutions on it. The signing operation does not alter the buffer size """ # We start by substituting the ByteRange, # that defines which part of the document content the signature is based on. # This is basically ALL the content EXCEPT the signature content itself. sig_placeholder = _SIGNATURE_CONTENTS_PLACEHOLDER.encode("latin1") start_index = buffer.find(sig_placeholder) end_index = start_index + len(sig_placeholder) content_range = (0, start_index - 1, end_index + 1, len(buffer) - end_index - 1) # pylint: disable=consider-using-f-string buffer = buffer_subst( buffer, _SIGNATURE_BYTERANGE_PLACEHOLDER, "[%010d %010d %010d %010d]" % content_range, ) # We compute the ByteRange hash, of everything before & after the placeholder: content_hash = hashlib.new(hashalgo) content_hash.update(buffer[: content_range[1]]) # before content_hash.update(buffer[content_range[2] :]) # after # This monkey-patching is needed, at the time of endesive v2.0.9, # to get control over signed_time, initialized by endesive.signer.sign() to be datetime.now(): class mock_datetime: @staticmethod def now(tz): # pylint: disable=unused-argument return sign_time.astimezone(timezone.utc) sign = patch("endesive.signer.datetime", mock_datetime)(signer.sign) contents = sign( datau=None, key=key, cert=cert, othercerts=extra_certs, hashalgo=hashalgo, attrs=True, signed_value=content_hash.digest(), ) contents = _pkcs11_aligned(contents).encode("latin1") # Sanity check, otherwise we will break the xref table: assert len(sig_placeholder) == len(contents) return buffer.replace(sig_placeholder, contents, 1)
Classes
class Signature (contact_info=None, location=None, m=None, reason=None)
-
Expand source code
class Signature: def __init__(self, contact_info=None, location=None, m=None, reason=None): self.type = Name("Sig") self.filter = Name("Adobe.PPKLite") self.sub_filter = Name("adbe.pkcs7.detached") self.contact_info = contact_info "Information provided by the signer to enable a recipient to contact the signer to verify the signature" self.location = location "The CPU host name or physical location of the signing" self.m = m "The time of signing" self.reason = reason "The reason for the signing" self.byte_range = _SIGNATURE_BYTERANGE_PLACEHOLDER self.contents = "<" + _SIGNATURE_CONTENTS_PLACEHOLDER + ">" def serialize(self, _security_handler=None, _obj_id=None): obj_dict = build_obj_dict( {key: getattr(self, key) for key in dir(self)}, _security_handler=_security_handler, _obj_id=_obj_id, ) return pdf_dict(obj_dict)
Instance variables
var contact_info
-
Information provided by the signer to enable a recipient to contact the signer to verify the signature
var location
-
The CPU host name or physical location of the signing
var m
-
The time of signing
var reason
-
The reason for the signing
Methods
def serialize(self)
-
Expand source code
def serialize(self, _security_handler=None, _obj_id=None): obj_dict = build_obj_dict( {key: getattr(self, key) for key in dir(self)}, _security_handler=_security_handler, _obj_id=_obj_id, ) return pdf_dict(obj_dict)