Ejemplo n.º 1
0
 def embed(self, writer: BasePdfFileWriter, obj_stream=None):
     fd = self._font_descriptor
     self[pdf_name('/FontDescriptor')] = fd_ref = writer.add_object(
         fd, obj_stream=obj_stream
     )
     font_stream_ref = self.set_font_file(writer)
     return fd_ref, font_stream_ref
Ejemplo n.º 2
0
 def register_widget_annotation(self, writer: BasePdfFileWriter,
                                sig_field_ref):
     annot_dict = self.annot_dict
     if annot_dict is not self:
         annot_ref = writer.add_object(annot_dict)
         self['/Kids'] = generic.ArrayObject([annot_ref])
     else:
         annot_ref = sig_field_ref
     writer.register_annotation(self.page_ref, annot_ref)
Ejemplo n.º 3
0
    def set_font_file(self, writer: BasePdfFileWriter):
        stream_buf = BytesIO()
        self.tt_font.save(stream_buf)
        stream_buf.seek(0)

        font_stream = generic.StreamObject(stream_data=stream_buf.read())
        font_stream.compress()
        font_stream_ref = writer.add_object(font_stream)
        self._font_descriptor[pdf_name('/FontFile2')] = font_stream_ref
        return font_stream_ref
Ejemplo n.º 4
0
 def set_font_file(self, writer: BasePdfFileWriter):
     stream_buf = BytesIO()
     # write the CFF table to the stream
     self.cff.compile(stream_buf, self.tt_font)
     stream_buf.seek(0)
     font_stream = generic.StreamObject({
         # this is a Type0 CFF font program (see Table 126 in ISO 32000)
         pdf_name('/Subtype'): pdf_name('/CIDFontType0C'),
     }, stream_data=stream_buf.read())
     font_stream.compress()
     font_stream_ref = writer.add_object(font_stream)
     self._font_descriptor[pdf_name('/FontFile3')] = font_stream_ref
     return font_stream_ref
Ejemplo n.º 5
0
    def set_font_file(self, writer: BasePdfFileWriter):
        stream_buf = BytesIO()
        self.tt_font.save(stream_buf)
        stream_buf.seek(0)

        font_stream = generic.StreamObject({
            # this is a Type2 TTF font program
            pdf_name('/Subtype'): pdf_name('/CIDFontType2'),
        }, stream_data=stream_buf.read())
        font_stream.compress()
        font_stream_ref = writer.add_object(font_stream)
        self._font_descriptor[pdf_name('/FontFile2')] = font_stream_ref
        return font_stream_ref
Ejemplo n.º 6
0
def append_signature_field(pdf_out: BasePdfFileWriter,
                           sig_field_spec: SigFieldSpec):
    """
    Append signature fields to a PDF file.

    :param pdf_out:
        Incremental writer to house the objects.
    :param sig_field_spec:
        A :class:`.SigFieldSpec` object describing the signature field
        to add.
    """
    root = pdf_out.root

    page_ref = pdf_out.find_page_for_modification(sig_field_spec.on_page)[0]
    # use default appearance
    field_created, sig_field_ref = _prepare_sig_field(
        sig_field_spec.sig_field_name, root, update_writer=pdf_out,
        existing_fields_only=False, lock_sig_flags=False,
        box=sig_field_spec.box, include_on_page=page_ref,
        combine_annotation=sig_field_spec.combine_annotation
    )
    if not field_created:
        raise PdfWriteError(
            'Signature field with name %s already exists.'
            % sig_field_spec.sig_field_name
        )

    sig_field = sig_field_ref.get_object()
    if sig_field_spec.seed_value_dict is not None:
        # /SV must be an indirect reference as per the spec
        sv_ref = pdf_out.add_object(
            sig_field_spec.seed_value_dict.as_pdf_object()
        )
        sig_field[pdf_name('/SV')] = sv_ref

    lock = sig_field_spec.format_lock_dictionary()
    if lock is not None:
        sig_field[pdf_name('/Lock')] = pdf_out.add_object(lock)
Ejemplo n.º 7
0
def _insert_or_get_field_at(writer: BasePdfFileWriter,
                            fields,
                            path,
                            parent_ref=None,
                            modified=False,
                            field_obj=None):

    current_partial, tail = path[0], path[1:]

    for field_ref in fields:
        assert isinstance(field_ref, generic.IndirectObject)
        field = field_ref.get_object()
        if field.get('/T', None) == current_partial:
            break
    else:
        # have to insert a new element into the fields array
        if field_obj is not None and not tail:
            field = field_obj
        else:
            # create a generic field
            field = generic.DictionaryObject()
        field['/T'] = pdf_string(current_partial)
        if parent_ref is not None:
            field['/Parent'] = parent_ref
        field_ref = writer.add_object(field)
        fields.append(field_ref)
        writer.update_container(fields)
        modified = True

    if not tail:
        return modified, field_ref
    # check for /Kids, and create it if necessary
    try:
        kids = field['/Kids']
    except KeyError:
        kids = field['/Kids'] = generic.ArrayObject()
        writer.update_container(field)
        modified = True

    # recurse in to /Kids array
    return _insert_or_get_field_at(writer,
                                   kids,
                                   tail,
                                   parent_ref=field_ref,
                                   modified=modified,
                                   field_obj=field_obj)
Ejemplo n.º 8
0
def _prepare_sig_field(sig_field_name,
                       root,
                       update_writer: BasePdfFileWriter,
                       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

    # 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, **sig_form_kwargs)
    created, sig_field_ref = _insert_or_get_field_at(
        update_writer,
        fields,
        path=sig_field_name.split('.'),
        field_obj=sig_field)
    update_writer.register_annotation(page_ref, 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
Ejemplo n.º 9
0
    def write_cms(self,
                  field_name: str,
                  writer: BasePdfFileWriter,
                  existing_fields_only=False):
        """
        .. versionadded:: 0.3.0

        .. versionchanged:: 0.7.0
            Digest wrapped in
            :class:`~pyhanko.sign.signers.pdf_byterange.PreparedByteRangeDigest`
            in step 3; ``output`` returned in step 3 instead of step 4.

        This method returns a generator coroutine that controls the process
        of embedding CMS data into a PDF signature field.
        Can be used for both timestamps and regular signatures.

        .. danger::
            This is a very low-level interface that performs virtually no
            error checking, and is intended to be used in situations
            where the construction of the CMS object to be embedded
            is not under the caller's control (e.g. a remote signer
            that produces full-fledged CMS objects).

            In almost every other case, you're better of using
            :class:`.PdfSigner` instead, with a custom :class:`.Signer`
            implementation to handle the cryptographic operations if necessary.

        The coroutine follows the following specific protocol.

        1. First, it retrieves or creates the signature field to embed the
           CMS object in, and yields a reference to said field.
        2. The caller should then send in a :class:`.SigObjSetup` object, which
           is subsequently processed by the coroutine. For convenience, the
           coroutine will then yield a reference to the signature dictionary
           (as embedded in the PDF writer).
        3. Next, the caller should send a :class:`.SigIOSetup` object,
           describing how the resulting document should be hashed and written
           to the output. The coroutine will write the entire document with a
           placeholder region reserved for the signature and compute the
           document's hash and yield it to the caller.
           It will then yield a ``prepared_digest, output`` tuple, where
           ``prepared_digest`` is a :class:`.PreparedByteRangeDigest` object
           containing the document digest and the relevant offsets, and
           ``output`` is the output stream to which the document to be
           signed was written.

           From this point onwards, **no objects may be changed or added** to
           the :class:`.IncrementalPdfFileWriter` currently in use.
        4. Finally, the caller should pass in a CMS object to place inside
           the signature dictionary. The CMS object can be supplied as a raw
           :class:`bytes` object, or an :mod:`asn1crypto`-style object.
           The coroutine's final yield is the value of the signature
           dictionary's ``/Contents`` entry, given as a hexadecimal string.

        .. caution::
            It is the caller's own responsibility to ensure that enough room
            is available in the placeholder signature object to contain
            the final CMS object.

        :param field_name:
            The name of the field to fill in. This should be a field of type
            ``/Sig``.
        :param writer:
            An :class:`.IncrementalPdfFileWriter` containing the
            document to sign.
        :param existing_fields_only:
            If ``True``, never create a new empty signature field to contain
            the signature.
            If ``False``, a new field may be created if no field matching
            ``field_name`` exists.
        :return:
            A generator coroutine implementing the protocol described above.
        """

        new_field_spec = self.new_field_spec \
            if not existing_fields_only else None
        # start by creating or fetching the appropriate signature field
        field_created, sig_field_ref = _get_or_create_sigfield(
            field_name,
            writer,
            existing_fields_only,
            new_field_spec=new_field_spec)

        # yield control to caller to further process the field dictionary
        # if necessary, request setup specs for sig object
        sig_obj_setup = yield sig_field_ref
        assert isinstance(sig_obj_setup, SigObjSetup)

        sig_field = sig_field_ref.get_object()

        # take care of the field's visual appearance (if applicable)
        appearance_setup = sig_obj_setup.appearance_setup
        if appearance_setup is not None:
            try:
                sig_annot, = sig_field['/Kids']
                sig_annot = sig_annot.get_object()
            except (ValueError, TypeError):
                raise SigningError(
                    "Failed to access signature field's annotation. "
                    "Signature field must have exactly one child annotation, "
                    "or it must be combined with its annotation.")
            except KeyError:
                sig_annot = sig_field

            appearance_setup.apply(sig_annot, writer)

        sig_obj = sig_obj_setup.sig_placeholder
        sig_obj_ref = writer.add_object(sig_obj)

        # fill in a reference to the (empty) signature object
        sig_field[pdf_name('/V')] = sig_obj_ref

        if not field_created:
            # still need to mark it for updating
            writer.mark_update(sig_field_ref)

        mdp_setup = sig_obj_setup.mdp_setup
        if mdp_setup is not None:
            mdp_setup.apply(sig_obj_ref, writer)

        # again, pass control to the caller
        # and request I/O parameters for putting the cryptographic signature
        # into the output.
        # We pass a reference to the embedded signature object as a convenience.

        sig_io = yield sig_obj_ref
        assert isinstance(sig_io, SigIOSetup)

        # pass control to the sig object's write_signature coroutine
        yield from sig_obj.fill(writer,
                                sig_io.md_algorithm,
                                in_place=sig_io.in_place,
                                output=sig_io.output,
                                chunk_size=sig_io.chunk_size)
Ejemplo n.º 10
0
    def supply_dss_in_writer(cls, pdf_out: BasePdfFileWriter,
                             sig_contents, *, certs=None,
                             ocsps=None, crls=None, paths=None,
                             validation_context=None,
                             embed_roots: bool = True) \
            -> 'DocumentSecurityStore':
        """
        Add or update a DSS, and optionally associate the new information with a
        VRI entry tied to a signature object.

        You can either specify the CMS objects to include directly, or
        pass them in as output from `pyhanko_certvalidator`.

        :param pdf_out:
            PDF writer 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 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.

        :return:
            a :class:`DocumentSecurityStore` object containing both the new
            and existing contents of the DSS (if any).
        """
        try:
            dss = cls.read_dss(pdf_out)
            created = False
        except ValidationInfoReadingError:
            created = True
            dss = cls(writer=pdf_out)

        if sig_contents is not None:
            identifier = \
                DocumentSecurityStore.sig_content_identifier(sig_contents)
        else:
            identifier = None

        def _certs():
            yield from certs or ()
            path: ValidationPath
            for path in (paths or ()):
                path_parts = iter(path)
                if not embed_roots:
                    # skip the first cert (i.e. the root)
                    next(path_parts)
                yield from path_parts

        def _ocsps():
            yield from ocsps or ()
            if validation_context is not None:
                yield from validation_context.ocsps

        def _crls():
            yield from crls or ()
            if validation_context is not None:
                yield from validation_context.crls

        dss.register_vri(
            identifier, certs=_certs(), ocsps=_ocsps(), crls=_crls()
        )
        dss_dict = dss.as_pdf_object()
        # if we're updating the DSS, this is all we need to do.
        # if we're adding a fresh DSS, we need to register it.

        if created:
            dss_ref = pdf_out.add_object(dss_dict)
            pdf_out.root[pdf_name('/DSS')] = dss_ref
            pdf_out.update_root()
        return dss
Ejemplo n.º 11
0
    def embed_subset(self, writer: BasePdfFileWriter, obj_stream=None):
        """
        Embed a subset of this glyph accumulator's font into the provided PDF
        writer. Said subset will include all glyphs necessary to render the
        strings provided to the accumulator via :meth:`feed_string`.

        .. danger::
            Due to the way ``fontTools`` handles subsetting, this is a
            destructive operation. The in-memory representation of the original
            font will be overwritten by the generated subset.

        :param writer:
            A PDF writer.
        :param obj_stream:
            If provided, write all relevant objects to the provided
            `obj_stream`. If ``None`` (the default), they will simply be written
            to the file as top-level objects.
        :return:
            A reference to the embedded ``/Font`` object.
        """
        if self._font_ref is not None:
            return self._font_ref
        self._extract_subset()
        cidfont_obj = CIDFontType0(self.tt)
        # TODO keep track of used subset prefixes in the writer!
        cff_topdict = self.tt['CFF '].cff[0]
        name = cidfont_obj.name
        cff_topdict.rawDict['FullName'] = '%s+%s' % (generate_subset_prefix(),
                                                     name)
        cidfont_obj.embed(writer, obj_stream=obj_stream)
        cidfont_ref = writer.add_object(cidfont_obj)
        to_unicode = self._format_tounicode_cmap(*cidfont_obj.ros)
        type0 = generic.DictionaryObject({
            pdf_name('/Type'):
            pdf_name('/Font'),
            pdf_name('/Subtype'):
            pdf_name('/Type0'),
            pdf_name('/DescendantFonts'):
            generic.ArrayObject([cidfont_ref]),
            # take the Identity-H encoding to inherit from the /Encoding
            # entry specified in our CIDSystemInfo dict
            pdf_name('/Encoding'):
            pdf_name('/Identity-H'),
            pdf_name('/BaseFont'):
            pdf_name('/%s-Identity-H' % cidfont_obj.name),
            pdf_name('/ToUnicode'):
            writer.add_object(to_unicode)
        })
        to_unicode.compress()
        # compute widths entry
        # (easiest to do here, since it seems we need the original CIDs)
        by_cid = iter(sorted(self._glyphs.values(), key=lambda t: t[0]))

        def _widths():
            current_chunk = []
            prev_cid = None
            (first_cid, _, _), itr = peek(by_cid)
            for cid, _, g in itr:
                if current_chunk and cid != prev_cid + 1:
                    yield generic.NumberObject(first_cid)
                    yield generic.ArrayObject(current_chunk)
                    current_chunk = []
                    first_cid = cid

                current_chunk.append(generic.NumberObject(g.width))
                prev_cid = cid
            if current_chunk:
                yield generic.NumberObject(first_cid)
                yield generic.ArrayObject(current_chunk)

        cidfont_obj[pdf_name('/W')] = generic.ArrayObject(list(_widths()))
        self._font_ref = ref = writer.add_object(type0, obj_stream=obj_stream)
        return ref