def test_pages_kids_tamper(bogus_kids, indirectify): w = IncrementalPdfFileWriter(BytesIO(MINIMAL)) # sign, then fill meta = signers.PdfSignatureMetadata(field_name='Sig1') out = signers.sign_pdf(w, meta, signer=FROM_CA) w = IncrementalPdfFileWriter(out) # add an empty sig field to trigger the annotation parsing logic # in the difference analysis tool fields.append_signature_field( w, sig_field_spec=fields.SigFieldSpec(sig_field_name="Extra")) page_root = w.root['/Pages'] if indirectify: bogus_kids = generic.ArrayObject(map(w.add_object, bogus_kids)) if bogus_kids is not None: page_root['/Kids'] = bogus_kids else: del page_root['/Kids'] w.update_container(page_root) out = BytesIO() w.write(out) r = PdfFileReader(out) s = r.embedded_signatures[0] assert s.field_name == 'Sig1' val_trusted_but_modified(s)
def test_double_sig_add_field_annots_indirect(): 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, ) # create a new signature field after signing w = IncrementalPdfFileWriter(out) # ... but first make the /Annots entry of the first page an indirect one first_page = w.root['/Pages']['/Kids'][0] annots_copy = generic.ArrayObject(first_page['/Annots']) first_page['/Annots'] = annots_ref = w.add_object(annots_copy) annots_copy.container_ref = annots_ref w.update_container(first_page) out = signers.sign_pdf(w, signers.PdfSignatureMetadata(field_name='SigNew'), signer=FROM_CA, new_field_spec=fields.SigFieldSpec( sig_field_name='SigNew', box=(10, 10, 10, 10))) r = PdfFileReader(out) s = r.embedded_signatures[0] assert s.field_name == 'Sig1' status = val_trusted(s, extd=True) assert status.modification_level == ModificationLevel.FORM_FILLING assert status.docmdp_ok s = r.embedded_signatures[1] assert s.field_name == 'SigNew' val_trusted(s)
def test_not_all_paths_cleared(): w = IncrementalPdfFileWriter(BytesIO(MINIMAL_ONE_FIELD)) # make /Fields indirect fields_arr = w.root['/AcroForm'].raw_get('/Fields') # just in case we ever end up declaring /Fields as indirect in the example assert isinstance(fields_arr, generic.ArrayObject) w.root['/AcroForm']['/Fields'] = w.root['/Blah'] = w.add_object(fields_arr) w.update_root() w.update_container(w.root['/AcroForm']) out = signers.sign_pdf( w, signature_meta=signers.PdfSignatureMetadata(field_name='Sig1'), signer=FROM_CA) # create a new signature field after signing w = IncrementalPdfFileWriter(out) out = signers.sign_pdf( w, signers.PdfSignatureMetadata(field_name='SigNew'), signer=FROM_CA, ) r = PdfFileReader(out) val_trusted_but_modified(embedded_sig=r.embedded_signatures[0])
def test_deep_modify(): w = IncrementalPdfFileWriter(BytesIO(MINIMAL)) obj3 = generic.Reference(3, 0, w) deep_obj = w.get_object(obj3)['/Resources']['/Font']['/F1']['/Subtype'] assert deep_obj.container_ref.idnum == obj3.idnum w.update_container(deep_obj) assert (0, 3) in w.objects
def test_historical_nonexistent_xref_access_nonstrict(): out = BytesIO() with open(NONEXISTENT_XREF_PATH, 'rb') as inf: w = IncrementalPdfFileWriter(inf) pg_dict = w.root['/Pages']['/Kids'][0] del pg_dict['/Bleh'] w.update_container(pg_dict) w.write(out) r = PdfFileReader(out, strict=False) hist_root = r.get_historical_root(0) bad_ref = hist_root['/Pages']['/Kids'][0].raw_get('/Bleh') assert isinstance(bad_ref.get_object(), generic.NullObject)
def test_trailer_refs(): # This is a corner case in the reference handler that shouldn't really # come up in real life. That said, it's easy to test for using a (somewhat # contrived) example, so let's do that. w = IncrementalPdfFileWriter(BytesIO(MINIMAL)) # Note: the container of the catalog is the thing root_ref points to, # but root_ref, when viewed as a PDF object by itself (i.e. the indirect # object embedded in the trailer) should have a TrailerReference as its # container. root_ref = w.trailer.raw_get('/Root') assert isinstance(root_ref.container_ref, generic.TrailerReference) w.update_container(root_ref)
def test_historical_nonexistent_xref_access(): out = BytesIO() with open(NONEXISTENT_XREF_PATH, 'rb') as inf: w = IncrementalPdfFileWriter(inf) pg_dict = w.root['/Pages']['/Kids'][0] del pg_dict['/Bleh'] w.update_container(pg_dict) w.write(out) r = PdfFileReader(out) current_state = r.root['/Pages']['/Kids'][0] assert '/Bleh' not in current_state hist_root = r.get_historical_root(0) bad_ref = hist_root['/Pages']['/Kids'][0].raw_get('/Bleh') with pytest.raises(misc.PdfReadError, match='not found.*error in strict mode'): bad_ref.get_object()
def test_form_field_ft_tamper(): w = IncrementalPdfFileWriter(BytesIO(SIMPLE_FORM)) # sign, then fill meta = signers.PdfSignatureMetadata(field_name='Sig1') out = signers.sign_pdf(w, meta, signer=FROM_CA) w = IncrementalPdfFileWriter(out) tf = w.root['/AcroForm']['/Fields'][1].get_object() tf['/FT'] = pdf_name('/Sig') w.update_container(tf) out = BytesIO() w.write(out) r = PdfFileReader(out) s = r.embedded_signatures[0] assert s.field_name == 'Sig1' val_trusted_but_modified(s)
def test_form_field_structure_modification(): w = IncrementalPdfFileWriter(BytesIO(SIMPLE_FORM)) meta = signers.PdfSignatureMetadata(field_name='Sig1') out = signers.sign_pdf(w, meta, signer=FROM_CA, timestamper=DUMMY_TS) w = IncrementalPdfFileWriter(out) field_arr = w.root['/AcroForm']['/Fields'] # shallow copy the text field tf = generic.DictionaryObject(field_arr[1].get_object()) tf['/T'] = generic.pdf_string('OtherField') field_arr.append(w.add_object(tf)) w.update_container(field_arr) out = BytesIO() w.write(out) r = PdfFileReader(out) s = r.embedded_signatures[0] assert s.field_name == 'Sig1' val_trusted_but_modified(s)
def test_form_field_kids_tamper(bogus_kids, indirectify): w = IncrementalPdfFileWriter(BytesIO(SIMPLE_FORM)) # sign, then fill meta = signers.PdfSignatureMetadata(field_name='Sig1') out = signers.sign_pdf(w, meta, signer=FROM_CA) w = IncrementalPdfFileWriter(out) tf = w.root['/AcroForm']['/Fields'][1].get_object() if indirectify: bogus_kids = generic.ArrayObject(map(w.add_object, bogus_kids)) tf['/Kids'] = bogus_kids w.update_container(tf) out = BytesIO() w.write(out) r = PdfFileReader(out) s = r.embedded_signatures[0] assert s.field_name == 'Sig1' val_trusted_but_modified(s)
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_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
def _prepare_sig_field(sig_field_name, root, update_writer: IncrementalPdfFileWriter, existing_fields_only=False, lock_sig_flags=True, **kwargs): """ Returns a tuple of a boolean and a reference to a signature field. The boolean is ``True`` if the field was created, and ``False`` otherwise. """ if sig_field_name is None: # pragma: nocover raise ValueError try: form = root['/AcroForm'] try: fields = form['/Fields'] except KeyError: raise ValueError('/AcroForm has no /Fields') candidates = enumerate_sig_fields_in(fields, with_name=sig_field_name) sig_field_ref = None try: field_name, value, sig_field_ref = next(candidates) if value is not None: raise SigningError( 'Signature field with name %s appears to be filled already.' % sig_field_name) except StopIteration: if existing_fields_only: raise SigningError( 'No empty signature field with name %s found.' % sig_field_name) form_created = False except KeyError: # we have to create the form if existing_fields_only: raise SigningError('This file does not contain a form.') # no AcroForm present, so create one form = generic.DictionaryObject() root[pdf_name('/AcroForm')] = update_writer.add_object(form) fields = generic.ArrayObject() form[pdf_name('/Fields')] = fields # now we need to mark the root as updated update_writer.update_root() form_created = True sig_field_ref = None if sig_field_ref is not None: return False, sig_field_ref if '.' in sig_field_name: raise NotImplementedError( "Creating fields deep in the form hierarchy is not supported" "right now.") # no signature field exists, so create one # default: grab a reference to the first page page_ref = update_writer.find_page_for_modification(0)[0] sig_form_kwargs = {'include_on_page': page_ref} sig_form_kwargs.update(**kwargs) sig_field = SignatureFormField(sig_field_name, writer=update_writer, **sig_form_kwargs) sig_field_ref = sig_field.reference fields.append(sig_field_ref) # make sure /SigFlags is present. If not, create it sig_flags = 3 if lock_sig_flags else 1 form.setdefault(pdf_name('/SigFlags'), generic.NumberObject(sig_flags)) # if a field was added to an existing form, register an extra update if not form_created: update_writer.update_container(fields) return True, sig_field_ref