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()
Exemple #3
0
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)
Exemple #4
0
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()
Exemple #7
0
    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()
Exemple #8
0
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''
Exemple #9
0
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)
Exemple #11
0
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
Exemple #12
0
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