Class: HexaPDF::DigitalSignature::Signatures
- Inherits:
-
Object
- Object
- HexaPDF::DigitalSignature::Signatures
- Includes:
- Enumerable
- Defined in:
- lib/hexapdf/digital_signature/signatures.rb
Overview
This class provides methods for interacting with digital signatures of a PDF file. It is used through HexaPDF::Document#signatures.
Instance Method Summary collapse
-
#add(file_or_io, handler, signature: nil, write_options: {}) ⇒ Object
Adds a signature to the document and returns the corresponding signature object.
-
#count ⇒ Object
Returns the number of signatures in the PDF document.
-
#each ⇒ Object
:call-seq: signatures.each {|signature| block } -> signatures signatures.each -> Enumerator.
-
#initialize(document) ⇒ Signatures
constructor
Creates a new Signatures object for the given PDF document.
-
#signing_handler(name: :default, **attributes) ⇒ Object
Creates a signing handler with the given attributes and returns it.
Constructor Details
#initialize(document) ⇒ Signatures
Creates a new Signatures object for the given PDF document.
52 53 54 |
# File 'lib/hexapdf/digital_signature/signatures.rb', line 52 def initialize(document) @document = document end |
Instance Method Details
#add(file_or_io, handler, signature: nil, write_options: {}) ⇒ Object
Adds a signature to the document and returns the corresponding signature object.
This method will add a new signature to the document and write the updated document to the given file or IO stream. Afterwards the document can’t be modified anymore and still retain a correct digital signature. To modify the signed document (e.g. for adding another signature) create a new document based on the given file or IO stream instead.
signature
-
Can either be a signature object (determined via the /Type key), a signature field or
nil
. Providing a signature object or signature field provides for more control, e.g.:-
Setting values for optional signature object fields like /Reason and /Location.
-
(In)directly specifying which signature field should be used.
If a signature object is provided and it is not associated with an AcroForm signature field, a new signature field is created and added to the main AcroForm object, creating that if necessary.
If a signature field is provided and it already has a signature object as field value, that signature object is discarded.
If the signature field doesn’t have a widget, a non-visible one is created on the first page.
-
handler
-
The signing handler that provides the necessary methods for signing and adjusting the signature and signature field objects to one’s liking, see #signing_handler and Signing::DefaultHandler.
write_options
-
The key-value pairs of this hash will be passed on to the HexaPDF::Document#write method. Note that
incremental
will be automatically set to ensure proper behaviour.
99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 |
# File 'lib/hexapdf/digital_signature/signatures.rb', line 99 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..to_a.empty? signature_field.(@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. do || next unless (resources = .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, **) 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 |
#count ⇒ Object
Returns the number of signatures in the PDF document. May be zero if the document has no signatures.
194 195 196 |
# File 'lib/hexapdf/digital_signature/signatures.rb', line 194 def count each.to_a.size end |
#each ⇒ Object
:call-seq:
signatures.each {|signature| block } -> signatures
signatures.each -> Enumerator
Iterates over all signatures in the order they are found in the PDF.
183 184 185 186 187 188 189 190 |
# File 'lib/hexapdf/digital_signature/signatures.rb', line 183 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 |
#signing_handler(name: :default, **attributes) ⇒ Object
Creates a signing handler with the given attributes and returns it.
A signing handler name is mapped to a class via the ‘signature.signing_handler’ configuration option. The default signing handler is Signing::DefaultHandler.
60 61 62 63 64 65 |
# File 'lib/hexapdf/digital_signature/signatures.rb', line 60 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 |