Class: HexaPDF::Document::Signatures
- Inherits:
-
Object
- Object
- HexaPDF::Document::Signatures
- Includes:
- Enumerable
- Defined in:
- lib/hexapdf/document/signatures.rb
Overview
This class provides methods for interacting with digital signatures of a PDF file.
Defined Under Namespace
Classes: DefaultHandler
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.
-
#handler(name: :default, **options) ⇒ Object
Creates a signing handler with the given options and returns it.
-
#initialize(document) ⇒ Signatures
constructor
Creates a new Signatures object for the given PDF document.
Constructor Details
#initialize(document) ⇒ Signatures
Creates a new Signatures object for the given PDF document.
167 168 169 |
# File 'lib/hexapdf/document/signatures.rb', line 167 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; create a new document based on the 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 #handler and DefaultHandler.
write_options-
The key-value pairs of this hash will be passed on to the HexaPDF::Document#write command. Note that
incrementalwill be automatically set if signing an already existing file.
213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 |
# File 'lib/hexapdf/document/signatures.rb', line 213 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 # Prepare signature object signature[:Filter] = handler.filter_name signature[:SubFilter] = handler.sub_filter_name 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 signature[:M] = Time.now io = if file_or_io.kind_of?(String) File.open(file_or_io, 'w+') else file_or_io end # Save the current state so that we can determine the correct /ByteRange value and set the # values handler.finalize_objects(signature_field, signature) section = @document.write(io, incremental: true, **) data = section.map {|oid, _gen, entry| [entry.pos, oid] if entry.in_use? }.compact.sort index = data.index {|_pos, oid| oid == signature.oid } signature_offset = data[index][0] signature_length = data[index + 1][0] - data[index][0] io.pos = signature_offset signature_data = io.read(signature_length) io.rewind file_data = io.read # 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_data.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 file_data[signature_offset, signature_length] = signature_data signed_contents = file_data[0, contents_offset] << file_data[offset2, length2] signature[:Contents] = handler.sign(signed_contents) # Set the correct /Contents value as hexstring signature_data.sub!(/Contents\(0+\)/) do |match| length = match.size result = "Contents<#{signature[:Contents].unpack1('H*')}" "#{result.ljust(length - 1, '0')}>" end 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.
310 311 312 |
# File 'lib/hexapdf/document/signatures.rb', line 310 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.
299 300 301 302 303 304 305 306 |
# File 'lib/hexapdf/document/signatures.rb', line 299 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 |
#handler(name: :default, **options) ⇒ Object
Creates a signing handler with the given options and returns it.
A signing handler name is mapped to a class via the 'signature.signing_handler' configuration option.
175 176 177 178 179 180 |
# File 'lib/hexapdf/document/signatures.rb', line 175 def handler(name: :default, **) handler = @document.config.constantize('signature.signing_handler', name) do raise HexaPDF::Error, "No signing handler named '#{name}' is available" end handler.new(**) end |