From f4ac1d50fe1e1f7c53271faa4f9281e6f5fb81c5 Mon Sep 17 00:00:00 2001 From: Matthias Valvekens Date: Thu, 2 Nov 2023 07:45:01 +0100 Subject: [PATCH] Change ByteRange placeholder to allow bigger input Fixes #336 --- pyhanko/sign/signers/pdf_byterange.py | 29 +++++++++++++++++++-------- pyhanko_tests/test_signing.py | 19 ++++++++++++++++++ 2 files changed, 40 insertions(+), 8 deletions(-) diff --git a/pyhanko/sign/signers/pdf_byterange.py b/pyhanko/sign/signers/pdf_byterange.py index eb362037..b8f75a7a 100644 --- a/pyhanko/sign/signers/pdf_byterange.py +++ b/pyhanko/sign/signers/pdf_byterange.py @@ -34,6 +34,9 @@ ] +BYTE_RANGE_ARR_PLACE_HOLDER_LENGTH = 60 + + class SigByteRangeObject(generic.PdfObject): """ Internal class to handle the ``/ByteRange`` arrays themselves. @@ -62,21 +65,31 @@ def fill_offsets(self, stream, sig_start, sig_end, eof): # so we can just write over it stream.seek(self._range_object_offset) + self._filled = True self.write_to_stream(stream, None) stream.seek(old_seek) - self._filled = True def write_to_stream(self, stream, handler=None, container_ref=None): if self._range_object_offset is None: self._range_object_offset = stream.tell() - string_repr = "[ %08d %08d %08d %08d ]" % ( - 0, - self.first_region_len, - self.second_region_offset, - self.second_region_len, - ) - stream.write(string_repr.encode('ascii')) + if self._filled: + string_repr = "[%d %d %d %d]" % ( + 0, + self.first_region_len, + self.second_region_offset, + self.second_region_len, + ) + buffer_remaining = ( + BYTE_RANGE_ARR_PLACE_HOLDER_LENGTH + 2 - len(string_repr) + ) + assert buffer_remaining >= 0 + stream.write(string_repr.encode('ascii')) + stream.write(b" " * buffer_remaining) + else: + stream.write(b"[") + stream.write(b" " * BYTE_RANGE_ARR_PLACE_HOLDER_LENGTH) + stream.write(b"]") class DERPlaceholder(generic.PdfObject): diff --git a/pyhanko_tests/test_signing.py b/pyhanko_tests/test_signing.py index dfc4d5da..584cace9 100644 --- a/pyhanko_tests/test_signing.py +++ b/pyhanko_tests/test_signing.py @@ -1628,3 +1628,22 @@ def test_sign_with_build_props_versioned_app_name(): build_prop_dict = s.sig_object['/Prop_Build']['/App'] assert build_prop_dict['/Name'] == '/Test Application' assert build_prop_dict['/REx'] == '1.2.3' + + +def test_sign_120mb_file(): + # put this in a function to avoid putting multiple copies + # of a huge buffer in locals + def _gen_signed(): + w = IncrementalPdfFileWriter(BytesIO(MINIMAL)) + w.root['/YugeObject'] = w.add_object( + generic.StreamObject(stream_data=bytes(120 * 1024 * 1024)) + ) + w.update_root() + + meta = signers.PdfSignatureMetadata(field_name='Sig1') + return signers.sign_pdf(w, meta, signer=SELF_SIGN) + + r = PdfFileReader(_gen_signed()) + emb = r.embedded_signatures[0] + assert emb.field_name == 'Sig1' + val_untrusted(emb)