def test_update_hybrid_twice(fname): with open(os.path.join(PDF_DATA_DIR, fname), 'rb') as inf: w = IncrementalPdfFileWriter(inf) t_obj = w.trailer['/Info'].raw_get('/Title') assert isinstance(t_obj, generic.IndirectObject) w.objects[(t_obj.generation, t_obj.idnum)] \ = generic.pdf_string('Updated') out = BytesIO() w.write(out) r = PdfFileReader(out) assert r.trailer['/Info']['/Title'] == 'Updated' container_info = r.xrefs.get_xref_container_info(1) assert container_info.xref_section_type == XRefSectionType.HYBRID_MAIN container_info = r.xrefs.get_xref_container_info(2) assert container_info.xref_section_type == XRefSectionType.STANDARD w = IncrementalPdfFileWriter(out) w.add_object(generic.pdf_string('This is an object')) w.write_in_place() r = PdfFileReader(out) assert '/XRefStm' not in r.trailer assert '/XRefStm' not in r.trailer_view assert r.trailer['/Info']['/Title'] == 'Updated' container_info = r.xrefs.get_xref_container_info(1) assert container_info.xref_section_type == XRefSectionType.HYBRID_MAIN container_info = r.xrefs.get_xref_container_info(2) assert container_info.xref_section_type == XRefSectionType.STANDARD container_info = r.xrefs.get_xref_container_info(3) assert container_info.xref_section_type == XRefSectionType.STANDARD
def test_xref_null_update(): buf = BytesIO(MINIMAL) w = IncrementalPdfFileWriter(buf) w.write_in_place() r = PdfFileReader(buf) assert r.xrefs.total_revisions == 2 assert r.xrefs.explicit_refs_in_revision(1) == set()
def test_rogue_backreferences(): w = IncrementalPdfFileWriter(BytesIO(MINIMAL)) # intentionally refer back to the contents of the first page w.root['/DSS'] = w.root['/Pages']['/Kids'][0].get_object().raw_get( '/Contents') w.update_root() meta = signers.PdfSignatureMetadata(field_name='Sig1', ) out = signers.sign_pdf(w, meta, signer=FROM_CA) # pretend to add a new form field, but actually secretly do a page # tree modification. sp = fields.SigFieldSpec('SigNew', box=(10, 74, 140, 134), doc_mdp_update_value=fields.MDPPerm.FILL_FORMS) w = IncrementalPdfFileWriter(out) fields.append_signature_field(w, sp) w.write_in_place() w = IncrementalPdfFileWriter(out) contents_ref = w.root['/Pages']['/Kids'][0].get_object().raw_get( '/Contents') content_stream: generic.StreamObject = contents_ref.get_object() content_stream._data = content_stream.data + b"q Q" content_stream._encoded_data = None w.mark_update(contents_ref) w.write_in_place() r = PdfFileReader(out) emb = r.embedded_signatures[0] emb.compute_integrity_info() assert isinstance(emb.diff_result, SuspiciousModification)
def test_no_changes_policy(): w = IncrementalPdfFileWriter(BytesIO(MINIMAL_ONE_FIELD)) out = signers.sign_pdf( w, signers.PdfSignatureMetadata( field_name='Sig1', certify=True, docmdp_permissions=fields.MDPPerm.FILL_FORMS), signer=FROM_CA, ) w = IncrementalPdfFileWriter(out) # do an /Info update dt = generic.pdf_date(datetime(2020, 10, 10, tzinfo=pytz.utc)) info = generic.DictionaryObject({pdf_name('/CreationDate'): dt}) w.set_info(info) w.write_in_place() # check with normal diff policy r = PdfFileReader(out) s = r.embedded_signatures[0] assert s.field_name == 'Sig1' status = val_trusted(s, extd=True) assert status.modification_level == ModificationLevel.LTA_UPDATES assert status.docmdp_ok # now check with the ultra-strict no-op policy r = PdfFileReader(out) s = r.embedded_signatures[0] status = validate_pdf_signature(s, diff_policy=NO_CHANGES_DIFF_POLICY) assert isinstance(s.diff_result, SuspiciousModification) assert not status.docmdp_ok
def test_xref_stream_null_update(): buf = BytesIO(MINIMAL_XREF) w = IncrementalPdfFileWriter(buf) w.write_in_place() r = PdfFileReader(buf) assert r.xrefs.total_revisions == 2 # The xref stream itself got added assert len(r.xrefs.explicit_refs_in_revision(1)) == 1
def test_image_content_embed(infile): path = os.path.join(IMG_DIR, infile) w = IncrementalPdfFileWriter(BytesIO(MINIMAL)) img_content = images.PdfImage( image=path, writer=w, opacity=0.6, ) w.add_content_to_page(0, img_content, prepend=True) w.write_in_place()
def add_dss(cls, output_stream, sig_contents, *, certs=None, ocsps=None, crls=None, paths=None, validation_context=None, force_write: bool = False, embed_roots: bool = True): """ Wrapper around :meth:`supply_dss_in_writer`. The result is applied to the output stream as an incremental update. :param output_stream: Output stream to write to. :param sig_contents: Contents of the new signature (used to compute the VRI hash), as as a hexadecimal string, including any padding. If ``None``, the information will not be added to any VRI dictionary. :param certs: Certificates to include in the VRI entry. :param ocsps: OCSP responses to include in the VRI entry. :param crls: CRLs to include in the VRI entry. :param paths: Validation paths that have been established, and need to be added to the DSS. :param force_write: Force a write even if the DSS doesn't have any new content. :param validation_context: Validation context from which to draw OCSP responses and CRLs. :param embed_roots: .. versionadded:: 0.9.0 Option that controls whether the root certificate of each validation path should be embedded into the DSS. The default is ``True``. .. note:: Trust roots are configured by the validator, so embedding them typically does nothing in a typical validation process. Therefore they can be safely omitted in most cases. Nonetheless, embedding the roots can be useful for documentation purposes. .. warning:: This only applies to paths, not the ``certs`` parameter. """ pdf_out = IncrementalPdfFileWriter(output_stream) dss = cls.supply_dss_in_writer( pdf_out, sig_contents, certs=certs, ocsps=ocsps, crls=crls, paths=paths, validation_context=validation_context, embed_roots=embed_roots ) if force_write or dss.modified: pdf_out.write_in_place()
def test_append_sigfield_trivial_ap(): buf = BytesIO(MINIMAL) w = IncrementalPdfFileWriter(buf) spec = fields.SigFieldSpec(sig_field_name='Sig1', box=(20, 20, 80, 40)) fields.append_signature_field(w, sig_field_spec=spec) w.write_in_place() r = PdfFileReader(buf) pg1 = r.root['/Pages']['/Kids'][0] assert '/Annots' in pg1 assert len(pg1['/Annots']) == 1 annot = pg1['/Annots'][0] assert '/AP' in annot assert annot['/AP']['/N'].data == b''
def test_append_sigfield_second_page(): buf = BytesIO(MINIMAL_TWO_PAGES) w = IncrementalPdfFileWriter(buf) fields.append_signature_field(w, fields.SigFieldSpec('Sig1', on_page=1)) w.write_in_place() r = PdfFileReader(buf) pg1 = r.root['/Pages']['/Kids'][0] assert '/Annots' not in pg1 pg2 = r.root['/Pages']['/Kids'][1] assert '/Annots' in pg2 assert len(pg2['/Annots']) == 1 assert pg2['/Annots'][0]['/T'] == 'Sig1'
def test_no_clobbering_xref_streams(): # Test witnessing the limitation on our reader implementation # that disallows references to the xref stream of a previous revision # from being overridden. # (this behaviour may change in the future, but for now, the test is in # place to deal with it) buf = BytesIO(MINIMAL_XREF) w = IncrementalPdfFileWriter(buf) # update the xref stream in the previous revision stream_ref = w.prev.xrefs.get_xref_container_info(0).stream_ref w.mark_update(stream_ref) w.write_in_place() with pytest.raises(misc.PdfReadError, match="XRef.*must not be clobbered"): PdfFileReader(buf)
def test_tamper_sig_obj(policy, skip_diff): w = IncrementalPdfFileWriter(BytesIO(MINIMAL)) meta = signers.PdfSignatureMetadata(field_name='Sig1') out = signers.sign_pdf(w, meta, signer=FROM_CA) w = IncrementalPdfFileWriter(out) sig_obj = w.prev.embedded_signatures[0].sig_object sig_obj['/Bleh'] = generic.BooleanObject(False) w.update_container(sig_obj) w.write_in_place() r = PdfFileReader(out) emb = r.embedded_signatures[0] status = validate_pdf_signature(emb, diff_policy=policy, skip_diff=skip_diff) if skip_diff: assert emb.diff_result is None else: assert isinstance(emb.diff_result, SuspiciousModification) assert status.coverage == SignatureCoverageLevel.CONTIGUOUS_BLOCK_FROM_START assert status.modification_level == ModificationLevel.OTHER
def test_embed_subset(): w = IncrementalPdfFileWriter(BytesIO(MINIMAL)) with open(NOTO_SERIF_JP, 'rb') as ffile: ga = GlyphAccumulator(w, ffile, font_size=10) res = ga.shape('版') assert b'[<66eb>] TJ' in res.graphics_ops res = ga.shape('テスト') assert b'[<0637> 40 <062a0639>] TJ' in res.graphics_ops # check the 'ffi' ligature res = ga.shape('difficult') assert b'[<0045004ae9e200440056004d0055>] TJ' in res.graphics_ops w.write_in_place() font_ref = ga.as_resource() font = font_ref.get_object() df = font['/DescendantFonts'][0].get_object() font_file = df['/FontDescriptor']['/FontFile3'] # assert no ToUnicode assignment for 'f' assert b'\n<0066>' not in font['/ToUnicode'].data # assert a ToUnicode assignment for the 'ffi' ligature assert b'\n<e9e2> <006600660069>' in font['/ToUnicode'].data assert len(font_file.data) < 4000
def test_image_direct_embed(infile): img = Image.open(os.path.join(IMG_DIR, infile)) w = IncrementalPdfFileWriter(BytesIO(MINIMAL)) image_ref = images.pil_image(img, w) page_ref, resources = w.find_page_for_modification(0) resources[pdf_name('/XObject')] = generic.DictionaryObject( {pdf_name('/Img'): image_ref}) w.update_container(resources) content_stream: generic.StreamObject = page_ref.get_object()['/Contents'] content_stream._data = \ content_stream.data + b' q 50 0 0 50 5 5 cm /Img Do Q' content_stream._encoded_data = None w.update_container(content_stream) w.write_in_place() # TODO flatten and do a visual comparison image_obj = image_ref.get_object() if 'indexed' in infile: assert '/SMask' not in image_obj else: assert '/SMask' in image_obj