class HexaPDF::DigitalSignature::Signatures
through HexaPDF::Document#signatures.
This class provides methods for interacting with digital signatures of a PDF file. It is used
def add(file_or_io, handler, signature: nil, write_options: {})
The key-value pairs of this hash will be passed on to the HexaPDF::Document#write
+write_options+::
Signing::DefaultHandler.
signature and signature field objects to one's liking, see #signing_handler and
The signing handler that provides the necessary methods for signing and adjusting the
+handler+::
page.
If the signature field doesn't have a widget, a non-visible one is created on the first
that signature object is discarded.
If a signature field is provided and it already has a signature object as field value,
that if necessary.
field, a new signature field is created and added to the main AcroForm object, creating
If a signature object is provided and it is not associated with an AcroForm signature
* (In)directly specifying which signature field should be used.
* Setting values for optional signature object fields like /Reason and /Location.
+nil+. Providing a signature object or signature field provides for more control, e.g.:
Can either be a signature object (determined via the /Type key), a signature field or
+signature+::
signature) create a new document based on the given file or IO stream instead.
a correct digital signature. To modify the signed document (e.g. for adding another
given file or IO stream. Afterwards the document can't be modified anymore and still retain
This method will add a new signature to the document and write the updated document to the
Adds a signature to the document and returns the corresponding signature object.
def add(file_or_io, handler, signature: nil, write_options: {}) if signature && signature.type != :Sig signature_field = signature signature = signature_field.field_value end signature ||= @document.add({Type: :Sig}) # Prepare AcroForm form = @document.acro_form(create: true) form.signature_flag(:signatures_exist, :append_only) # Prepare signature field signature_field ||= form.each_field.find {|field| field.field_value == signature } || form.create_signature_field(generate_field_name) signature_field.field_value = signature if signature_field.each_widget.to_a.empty? signature_field.create_widget(@document.pages[0], Rect: [0, 0, 0, 0]) end # Work-around for Adobe Acrobat to recognize images (https://stackoverflow.com/a/73011571/8203541) signature_field.each_widget do |widget| next unless (resources = widget.appearance&.resources) resources[:XObject]&.each do |_name, entry| entry[:Resources] ||= {} end end # Prepare signature object handler.finalize_objects(signature_field, signature) signature[:ByteRange] = [0, 1_000_000_000_000, 1_000_000_000_000, 1_000_000_000_000] signature[:Contents] = '00' * handler.signature_size # twice the size due to hex encoding io = if file_or_io.kind_of?(String) File.open(file_or_io, 'wb+') else file_or_io end # Save the current state so that we can determine the correct /ByteRange value and set the # values start_xref, section = @document.write(io, incremental: true, **write_options) signature_offset, signature_length = Signing.locate_signature_dict(section, start_xref, signature.oid) io.pos = signature_offset signature_data = io.read(signature_length) io.seek(0, IO::SEEK_END) file_size = io.pos # Calculate the offsets for the /ByteRange contents_offset = signature_offset + signature_data.index('Contents(') + 8 offset2 = contents_offset + signature[:Contents].size + 2 # +2 because of the needed < and > length2 = file_size - offset2 signature[:ByteRange] = [0, contents_offset, offset2, length2] # Set the correct /ByteRange value signature_data.sub!(/ByteRange\[0 1000000000000 1000000000000 1000000000000\]/) do |match| length = match.size result = "ByteRange[0 #{contents_offset} #{offset2} #{length2}]" result.ljust(length) end # Now everything besides the /Contents value is correct, so we can read the contents for # signing io.pos = signature_offset io.write(signature_data) signature[:Contents] = handler.sign(io, signature[:ByteRange].value) # And now replace the /Contents value Signing.replace_signature_contents(signature_data, signature[:Contents]) io.pos = signature_offset io.write(signature_data) signature ensure io.close if io && io != file_or_io end
def count
Returns the number of signatures in the PDF document. May be zero if the document has no
def count each.to_a.size end
def each
signatures.each -> Enumerator
signatures.each {|signature| block } -> signatures
:call-seq:
def each return to_enum(__method__) unless block_given? return [] unless (form = @document.acro_form) form.each_field do |field| yield(field.field_value) if field.field_type == :Sig && field.field_value end end
def generate_field_name
def generate_field_name index = (@document.acro_form.each_field. map {|field| field.full_field_name.scan(/\ASignature(\d+)/).first&.first.to_i }. max || 0) + 1 "Signature#{index}" end
def initialize(document)
def initialize(document) @document = document end
def signing_handler(name: :default, **attributes)
A signing handler name is mapped to a class via the 'signature.signing_handler'
Creates a signing handler with the given attributes and returns it.
def signing_handler(name: :default, **attributes) handler = @document.config.constantize('signature.signing_handler', name) do raise HexaPDF::Error, "No signing handler named '#{name}' is available" end handler.new(**attributes) end