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_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_visible_field(): 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) sp = fields.SigFieldSpec('SigNew', box=(10, 74, 140, 134)) fields.append_signature_field(w, sp) out = signers.sign_pdf( w, signers.PdfSignatureMetadata(field_name='SigNew'), signer=FROM_CA, ) 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_double_sign_lock_second(): # test if the difference analysis correctly processes /Reference # on a newly added signature object w = IncrementalPdfFileWriter(BytesIO(MINIMAL)) fields.append_signature_field(w, field_with_lock_sp(True)) out = signers.sign_pdf( w, signers.PdfSignatureMetadata(field_name='SigFirst'), signer=FROM_CA, ) w = IncrementalPdfFileWriter(out) # now sign the locked field out = signers.sign_pdf( w, signers.PdfSignatureMetadata(field_name='SigNew'), signer=FROM_CA, ) r = PdfFileReader(out) s = r.embedded_signatures[0] val_trusted(s, extd=True) s = r.embedded_signatures[1] assert len(s.sig_object.get_object()['/Reference']) == 1 val_trusted(s)
def test_append_sig_field_acro_update(): # test different configurations of the AcroForm w = PdfFileWriter() w.root['/AcroForm'] = generic.DictionaryObject( {pdf_name('/Fields'): generic.ArrayObject()}) w.insert_page(simple_page(w, 'Hello world')) out = BytesIO() w.write(out) out.seek(0) sp = fields.SigFieldSpec('InvisibleSig') w = IncrementalPdfFileWriter(out) fields.append_signature_field(w, sp) assert len(w.root['/AcroForm']['/Fields']) == 1 w = PdfFileWriter() # Technically, this is not standards-compliant, but our routine # shouldn't care w.root['/AcroForm'] = generic.DictionaryObject() w.insert_page(simple_page(w, 'Hello world')) out = BytesIO() w.write(out) out.seek(0) sp = fields.SigFieldSpec('InvisibleSig') w = IncrementalPdfFileWriter(out) with pytest.raises(ValueError): fields.append_signature_field(w, sp)
def add_sig_field(infile, outfile, field): with pyhanko_exception_manager(): writer = IncrementalPdfFileWriter(infile) for s in field: name, spec = parse_field_location_spec(s) assert spec is not None fields.append_signature_field(writer, spec) writer.write(outfile) infile.close() outfile.close()
def test_sign_with_empty_kids(): w = IncrementalPdfFileWriter(BytesIO(MINIMAL)) fields.append_signature_field( w, fields.SigFieldSpec( sig_field_name='Sig1', combine_annotation=False, box=(20, 20, 80, 40) ) ) w.root['/AcroForm']['/Fields'][0]['/Kids'] = generic.ArrayObject() meta = signers.PdfSignatureMetadata(field_name='Sig1') with pytest.raises(SigningError, match="Failed to access.*annot.*"): signers.sign_pdf(w, meta, signer=FROM_CA)
def test_append_acroform_no_fields(): w = PdfFileWriter() # Technically, this is not standards-compliant, but our routine # shouldn't care w.root['/AcroForm'] = generic.DictionaryObject() w.insert_page(simple_page(w, 'Hello world')) out = BytesIO() w.write(out) out.seek(0) sp = fields.SigFieldSpec('InvisibleSig') w = IncrementalPdfFileWriter(out) with pytest.raises(PdfError, match="has no /Fields"): fields.append_signature_field(w, sp)
def test_append_sig_field_acro_update(): # test different configurations of the AcroForm w = PdfFileWriter() w.root['/AcroForm'] = generic.DictionaryObject({ pdf_name('/Fields'): generic.ArrayObject() }) w.insert_page(simple_page(w, 'Hello world')) out = BytesIO() w.write(out) out.seek(0) sp = fields.SigFieldSpec('InvisibleSig') w = IncrementalPdfFileWriter(out) fields.append_signature_field(w, sp) assert len(w.root['/AcroForm']['/Fields']) == 1
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 prepare_sv_field(sv_spec, add_field_lock=False): w = IncrementalPdfFileWriter(BytesIO(MINIMAL)) if add_field_lock: sp = fields.SigFieldSpec( 'Sig', seed_value_dict=sv_spec, field_mdp_spec=fields.FieldMDPSpec(fields.FieldMDPAction.INCLUDE, fields=['blah']), doc_mdp_update_value=fields.MDPPerm.NO_CHANGES) else: sp = fields.SigFieldSpec('Sig', seed_value_dict=sv_spec) fields.append_signature_field(w, sp) out = BytesIO() w.write(out) out.seek(0) return out
def test_append_sig_field_with_simple_sv(): w = IncrementalPdfFileWriter(BytesIO(MINIMAL)) sv = fields.SigSeedValueSpec( reasons=['a', 'b', 'c'], cert=fields.SigCertConstraints( subject_dn=FROM_CA.signing_cert.subject, issuers=[INTERM_CERT], subjects=[FROM_CA.signing_cert], key_usage=[ fields.SigCertKeyUsage.from_sets( {'digital_signature', 'non_repudiation'}, {'key_agreement'}) ]), digest_methods=['ssh256'], add_rev_info=True, subfilters=[fields.SigSeedSubFilter.ADOBE_PKCS7_DETACHED], timestamp_server_url='https://tsa.example.com', ) sp = fields.SigFieldSpec('InvisibleSig', seed_value_dict=sv) fields.append_signature_field(w, sp) out = BytesIO() w.write(out) out.seek(0) r = PdfFileReader(out) _, _, sig_field_ref = next(fields.enumerate_sig_fields(r)) sv_dict = sig_field_ref.get_object()['/SV'] assert sv_dict['/V'] == generic.NumberObject(2) del sv_dict['/V'] recovered_sv = fields.SigSeedValueSpec.from_pdf_object(sv_dict) # x509.Certificate doesn't have an __eq__ implementation apparently, # so for the purposes of the test, we replace them by byte dumps issuers1 = recovered_sv.cert.issuers issuers2 = sv.cert.issuers issuers1[0] = issuers1[0].dump() issuers2[0] = issuers2[0].dump() subjects1 = recovered_sv.cert.subjects subjects2 = sv.cert.subjects subjects1[0] = subjects1[0].dump() subjects2[0] = subjects2[0].dump() assert recovered_sv == sv
def test_add_sigfield_with_lock(include_docmdp): w = IncrementalPdfFileWriter(BytesIO(MINIMAL)) fields.append_signature_field(w, field_with_lock_sp(include_docmdp)) out = signers.sign_pdf( w, signers.PdfSignatureMetadata(field_name='SigNew'), signer=FROM_CA, ) r = PdfFileReader(out) s = r.embedded_signatures[0] assert s.field_name == 'SigNew' refs = s.sig_object.get_object()['/Reference'] assert len(refs) == 1 ref = refs[0] assert ref['/TransformMethod'] == '/FieldMDP' assert ref['/TransformParams']['/Fields'] == generic.ArrayObject(['blah']) assert ref.raw_get('/Data').reference == r.root_ref assert '/Perms' not in r.root if include_docmdp: # test if the Acrobat-compatibility hack was included assert ref['/TransformParams']['/P'] == 1 val_trusted(s)
async def test_add_revinfo_wrong_subfilter(): sv = fields.SigSeedValueSpec(flags=fields.SigSeedValFlags.ADD_REV_INFO, add_rev_info=True) sig_field_spec = fields.SigFieldSpec( sig_field_name='Sig', seed_value_dict=sv, ) w = IncrementalPdfFileWriter(BytesIO(MINIMAL)) fields.append_signature_field(w, sig_field_spec) cms_writer = cms_embedder.PdfCMSEmbedder().write_cms(field_name='Sig', writer=w) next(cms_writer) # wrong subfilter: PAdES # but we do embed an (empty) RevInfoArchival attribute sig_obj = signers.SignatureObject(bytes_reserved=8192, subfilter=PADES) cms_writer.send(cms_embedder.SigObjSetup(sig_placeholder=sig_obj)) prep_digest, output = cms_writer.send( cms_embedder.SigIOSetup(md_algorithm='sha256', in_place=True)) cms_obj = await FROM_CA.async_sign( data_digest=prep_digest.document_digest, digest_algorithm='sha256', signed_attr_settings=PdfCMSSignedAttributes( # empty adobe_revinfo_attr=asn1_pdf.RevocationInfoArchival({'ocsp': []}))) await PdfTBSDocument.async_finish_signing(output, prep_digest, cms_obj) r = PdfFileReader(output) s = r.embedded_signatures[0] status = await async_validate_pdf_signature(s, dummy_ocsp_vc()) summary = status.pretty_print_details() assert status.intact and status.valid assert 'not satisfy the SV constraints' in summary assert 'requires subfilter' in status.seed_value_constraint_error.args[0] assert not status.seed_value_ok
def test_form_field_in_group_locked_postsign_modify_success( field_filled, fieldmdp_spec): # the field that is filled in after signing is always the same, # but the initial one varies w = IncrementalPdfFileWriter(BytesIO(GROUP_VARIANTS[0])) sp = fields.SigFieldSpec('SigNew', box=(10, 74, 140, 134), field_mdp_spec=fieldmdp_spec, doc_mdp_update_value=fields.MDPPerm.FILL_FORMS) fields.append_signature_field(w, sp) set_text_field_in_group(w, field_filled, "Some text") meta = signers.PdfSignatureMetadata(field_name='SigNew') out = signers.sign_pdf(w, meta, signer=FROM_CA) w = IncrementalPdfFileWriter(out) set_text_field_in_group(w, 1, "Some other text") out = BytesIO() w.write(out) r = PdfFileReader(out) s = r.embedded_signatures[0] assert s.field_name == 'SigNew' val_trusted(s, extd=True)
def test_fieldmdp_all_pades_lta(requests_mock): w = IncrementalPdfFileWriter(BytesIO(MINIMAL)) vc = live_testing_vc(requests_mock) sp = fields.SigFieldSpec( 'SigNew', box=(10, 74, 140, 134), field_mdp_spec=fields.FieldMDPSpec(action=fields.FieldMDPAction.ALL), doc_mdp_update_value=fields.MDPPerm.FILL_FORMS) fields.append_signature_field(w, sp) meta = signers.PdfSignatureMetadata( field_name='SigNew', validation_context=vc, embed_validation_info=True, subfilter=fields.SigSeedSubFilter.PADES, use_pades_lta=True) out = signers.sign_pdf(w, meta, signer=FROM_CA, timestamper=DUMMY_TS) r = PdfFileReader(out) s = r.embedded_signatures[0] assert s.field_name == 'SigNew' status = val_trusted(s, extd=True) assert status.modification_level == ModificationLevel.LTA_UPDATES
def test_append_visible_sig_field(): w = IncrementalPdfFileWriter(BytesIO(MINIMAL)) sp = fields.SigFieldSpec('VisibleSig', box=(10, 0, 50, 8)) fields.append_signature_field(w, sp) assert len(w.root['/AcroForm']['/Fields']) == 1 out = BytesIO() w.write(out) out.seek(0) w = IncrementalPdfFileWriter(out) with pytest.raises(PdfWriteError): fields.append_signature_field(w, sp) w = IncrementalPdfFileWriter(BytesIO(MINIMAL_TWO_FIELDS)) fields.append_signature_field(w, sp) assert len(w.root['/AcroForm']['/Fields']) == 3