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_no_field_type(): 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, in_place=True) w = IncrementalPdfFileWriter(out) fields._insert_or_get_field_at( w, w.root['/AcroForm']['/Fields'], ('Blah', ), ) meta = signers.PdfSignatureMetadata(field_name='NewSig') out = signers.sign_pdf(w, meta, signer=FROM_CA) r = PdfFileReader(out) s = r.embedded_signatures[0] assert s.field_name == 'Sig1' status = validate_pdf_signature( s, signer_validation_context=SIMPLE_V_CONTEXT()) assert status.modification_level == ModificationLevel.OTHER
def test_double_sig_create_deep_field_post_sign(): w = IncrementalPdfFileWriter(BytesIO(MINIMAL_ONE_FIELD)) # create part of the structure already fields._insert_or_get_field_at( w, w.root['/AcroForm']['/Fields'], ('NewSigs', 'NewSig1'), field_obj=generic.DictionaryObject({pdf_name('/FT'): pdf_name('/Sig')})) out = signers.sign_pdf(w, signers.PdfSignatureMetadata( field_name='Sig1', certify=True, docmdp_permissions=fields.MDPPerm.FILL_FORMS), signer=FROM_CA, in_place=True) w = IncrementalPdfFileWriter(out) fqn = 'NewSigs.NewSig2' meta = signers.PdfSignatureMetadata(field_name=fqn) out = signers.sign_pdf(w, meta, signer=FROM_CA) r = PdfFileReader(out) s = r.embedded_signatures[0] assert s.field_name == 'Sig1' status = validate_pdf_signature( s, signer_validation_context=SIMPLE_V_CONTEXT()) # the /Kids array of NewSigs was modified, which we don't allow (right now) assert status.modification_level == ModificationLevel.OTHER
def test_allow_hybrid_sign_validate_allow(): fname = 'minimal-hybrid-xref.pdf' with open(os.path.join(PDF_DATA_DIR, fname), 'rb') as inf: w = IncrementalPdfFileWriter(inf, strict=False) meta = signers.PdfSignatureMetadata(field_name='Sig1') out = signers.PdfSigner(signature_meta=meta, signer=FROM_CA).sign_pdf(w) r = PdfFileReader(out, strict=False) s = r.embedded_signatures[0] vc = SIMPLE_V_CONTEXT() val_status = validate_pdf_signature(s, vc) assert val_status.bottom_line
def test_sign_reject_freed(forbid_freeing): w = IncrementalPdfFileWriter(BytesIO(MINIMAL_ONE_FIELD)) out = signers.sign_pdf( w, signature_meta=signers.PdfSignatureMetadata(field_name='Sig1'), signer=FROM_CA) # free the ref containing the /Info dictionary # since we don't have support for freeing objects in the writer (yet), # do it manually r = PdfFileReader(out) last_startxref = r.last_startxref # NOTE the linked list offsets are dummied out, but our Xref parser # doesn't care len_out = out.seek(0, os.SEEK_END) out.write(b'\n'.join([ b'xref', b'0 1', b'0000000000 65535 f ', b'2 1', b'0000000000 00001 f ', b'trailer<</Prev %d>>' % last_startxref, b'startxref', b'%d' % len_out, b'%%EOF' ])) r = PdfFileReader(out) last_rev = r.xrefs.xref_sections - 1 some_ref = generic.Reference(2, 0) assert some_ref in r.xrefs.refs_freed_in_revision(last_rev) sig = r.embedded_signatures[0] assert sig.signed_revision == 2 # make a dummy rule that whitelists our freed object ref class AdHocRule(QualifiedWhitelistRule): def apply_qualified(self, old: HistoricalResolver, new: HistoricalResolver): yield ModificationLevel.LTA_UPDATES, ReferenceUpdate( some_ref, paths_checked=RawPdfPath('/Root', '/Pages')) val_status = validate_pdf_signature( sig, SIMPLE_V_CONTEXT(), diff_policy=StandardDiffPolicy(DEFAULT_DIFF_POLICY.global_rules + [AdHocRule()], DEFAULT_DIFF_POLICY.form_rule, reject_object_freeing=forbid_freeing)) if forbid_freeing: assert val_status.modification_level == ModificationLevel.OTHER else: assert val_status.modification_level == ModificationLevel.LTA_UPDATES
def test_no_diff_summary(): w = IncrementalPdfFileWriter(BytesIO(MINIMAL)) meta = signers.PdfSignatureMetadata(field_name='Sig1') out = signers.sign_pdf(w, meta, signer=SELF_SIGN) # just do an incremental DSS update DocumentSecurityStore.add_dss( out, sig_contents=None, certs=(SELF_SIGN.signing_cert,) ) r = PdfFileReader(out) emb = r.embedded_signatures[0] status = validate_pdf_signature(emb, skip_diff=True) assert emb.diff_result is None assert status.modification_level is None assert not status.docmdp_ok assert status.coverage == SignatureCoverageLevel.ENTIRE_REVISION assert 'EXTENDED' in status.summary()
def test_diff_fallback_ok(policy, skip_diff): w = IncrementalPdfFileWriter(BytesIO(MINIMAL)) meta = signers.PdfSignatureMetadata(field_name='Sig1') out = signers.sign_pdf(w, meta, signer=SELF_SIGN) 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 # docmdp should still be OK without the diff check # because the signature covers the entire file assert status.docmdp_ok assert status.modification_level == ModificationLevel.NONE else: assert isinstance(emb.diff_result, DiffResult) assert status.modification_level == ModificationLevel.NONE assert status.docmdp_ok
def sign_with_sv(sv_spec, sig_meta, signer=FROM_CA, timestamper=DUMMY_TS, *, test_violation=False, add_field_lock=False): w = IncrementalPdfFileWriter( prepare_sv_field(sv_spec, add_field_lock=add_field_lock) ) pdf_signer = signers.PdfSigner(sig_meta, signer, timestamper=timestamper) pdf_signer._ignore_sv = test_violation out = pdf_signer.sign_pdf(w) r = PdfFileReader(out) s = r.embedded_signatures[0] status = validate_pdf_signature(s, dummy_ocsp_vc()) summary = status.pretty_print_details() if test_violation: assert 'not satisfy the SV constraints' in summary assert not status.seed_value_ok else: assert 'no SV issues' in summary assert status.seed_value_ok return EmbeddedPdfSignature(r, s.sig_field, s.fq_name)
def test_simple_sign_tamper(): w = IncrementalPdfFileWriter(BytesIO(MINIMAL)) meta = signers.PdfSignatureMetadata(field_name='Sig1') out = signers.sign_pdf(w, meta, signer=SELF_SIGN) r = PdfFileReader(out) emb = r.embedded_signatures[0] assert emb.field_name == 'Sig1' val_untrusted(emb) # try tampering with the file out.seek(0x9d) # this just changes the size of the media box, so the file should remain # a valid PDF. out.write(b'4') out.seek(0) r = PdfFileReader(out) emb = r.embedded_signatures[0] tampered = validate_pdf_signature(emb, SIMPLE_V_CONTEXT()) assert not tampered.intact assert not tampered.valid assert tampered.summary() == 'INVALID'
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 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