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_pades_dss_object_clobber(requests_mock): w = IncrementalPdfFileWriter(BytesIO(MINIMAL_TWO_FIELDS)) meta1 = signers.PdfSignatureMetadata( field_name='Sig1', validation_context=live_testing_vc(requests_mock), subfilter=PADES, embed_validation_info=True, ) dummy_ref = w.add_object(generic.pdf_string("Hi there")).reference out = signers.sign_pdf(w, meta1, signer=FROM_CA, timestamper=DUMMY_TS) w = IncrementalPdfFileWriter(out) # We're going to reassign the DSS object to another object ID, namely # one that clobbers the dummy_ref object. This should be ample cause # for suspicion. dss = w.root['/DSS'] w.objects[(dummy_ref.generation, dummy_ref.idnum)] = dss w.root['/DSS'] = generic.IndirectObject(idnum=dummy_ref.idnum, generation=dummy_ref.generation, pdf=w) w.update_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_pades_double_sign_delete_dss(requests_mock): w = IncrementalPdfFileWriter(BytesIO(MINIMAL_TWO_FIELDS)) meta1 = signers.PdfSignatureMetadata( field_name='Sig1', validation_context=live_testing_vc(requests_mock), subfilter=PADES, embed_validation_info=True, ) meta2 = signers.PdfSignatureMetadata( field_name='Sig2', validation_context=live_testing_vc(requests_mock), subfilter=PADES, embed_validation_info=True, ) out = signers.sign_pdf(w, meta1, signer=FROM_CA, timestamper=DUMMY_TS) w = IncrementalPdfFileWriter(out) out = signers.sign_pdf(w, meta2, signer=FROM_CA, timestamper=DUMMY_TS) w = IncrementalPdfFileWriter(out) # DSS is now covered by the second signature, so this is illegal del w.root['/DSS'] w.update_root() out = BytesIO() w.write(out) r = PdfFileReader(out) assert '/DSS' not in r.root s = r.embedded_signatures[0] assert s.field_name == 'Sig1' val_trusted(s, extd=True) # however, the second signature is violated by the deletion of the /DSS key s = r.embedded_signatures[1] assert s.field_name == 'Sig2' val_trusted_but_modified(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_pades_dss_object_typing_tamper(requests_mock, bogus_dss): w = IncrementalPdfFileWriter(BytesIO(MINIMAL_TWO_FIELDS)) meta1 = signers.PdfSignatureMetadata( field_name='Sig1', validation_context=live_testing_vc(requests_mock), subfilter=PADES, embed_validation_info=True, ) out = signers.sign_pdf(w, meta1, signer=FROM_CA, timestamper=DUMMY_TS) out.seek(0) original_out = out.read() # write some bogus reference into the DSS w = IncrementalPdfFileWriter(BytesIO(original_out)) w.root['/DSS'] = w.add_object(bogus_dss) w.update_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_bogus_metadata_manipulation(): # test using a double signature created using Adobe Reader # (uses object streams, XMP metadata updates and all the fun stuff) infile = BytesIO( read_all(PDF_DATA_DIR + '/minimal-two-fields-signed-twice.pdf')) bogus = b'This is bogus data, yay!' def do_check(): r = PdfFileReader(out) print(r.get_object(generic.Reference(2, 0, r), revision=3).data) s = r.embedded_signatures[0] status = validate_pdf_signature(s) assert status.modification_level == ModificationLevel.OTHER w = IncrementalPdfFileWriter(infile) w.root['/Metadata'] = w.add_object(generic.StreamObject(stream_data=bogus)) w.update_root() out = BytesIO() w.write(out) do_check() w = IncrementalPdfFileWriter(infile) metadata_ref = w.root.raw_get('/Metadata') metadata_stream: generic.StreamObject = metadata_ref.get_object() metadata_stream.strip_filters() metadata_stream._data = bogus metadata_stream._encoded_data = None w.mark_update(metadata_ref) out = BytesIO() w.write(out) do_check() w = IncrementalPdfFileWriter(infile) w.root['/Metadata'] = generic.NullObject() w.update_root() out = BytesIO() w.write(out) do_check() w = IncrementalPdfFileWriter(infile) w.root['/Metadata'] = w.add_object(generic.NullObject()) w.update_root() out = BytesIO() w.write(out) do_check()
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