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, TimestampHandler
Class Method Summary collapse
-
.embed_signature(io, signature) ⇒ Object
Embeds the given
signatureinto the /Contents value of the newest signature dictionary of the PDF document given by theioargument. -
.locate_signature_dict(xref_section, start_xref_position, signature_oid) ⇒ Object
Uses the information in the given cross-reference section as well as the byte offset of the cross-reference section to calculate the offset and length of the signature dictionary with the given object id.
-
.replace_signature_contents(signature_data, contents) ⇒ Object
Replaces the value of the /Contents key in the serialized
signature_datawith the value ofcontents.
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, **attributes) ⇒ Object
Creates a signing handler with the given attributes 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.
385 386 387 |
# File 'lib/hexapdf/document/signatures.rb', line 385 def initialize(document) @document = document end |
Class Method Details
.embed_signature(io, signature) ⇒ Object
Embeds the given signature into the /Contents value of the newest signature dictionary of the PDF document given by the io argument.
This functionality can be used together with the support for external signing (see DefaultHandler and DefaultHandler#external_signing) to implement asynchronous signing.
Note: This will, most probably, only work on documents prepared for external signing by HexaPDF and not by other libraries.
342 343 344 345 346 347 348 349 350 351 352 353 354 355 |
# File 'lib/hexapdf/document/signatures.rb', line 342 def self.(io, signature) doc = HexaPDF::Document.new(io: io) signature_dict = doc.signatures.find {|sig| doc.revisions.current.object(sig) == sig } signature_dict_offset, signature_dict_length = locate_signature_dict( doc.revisions.current.xref_section, doc.revisions.parser.startxref_offset, signature_dict.oid ) io.pos = signature_dict_offset signature_data = io.read(signature_dict_length) replace_signature_contents(signature_data, signature) io.pos = signature_dict_offset io.write(signature_data) end |
.locate_signature_dict(xref_section, start_xref_position, signature_oid) ⇒ Object
Uses the information in the given cross-reference section as well as the byte offset of the cross-reference section to calculate the offset and length of the signature dictionary with the given object id.
360 361 362 363 364 365 |
# File 'lib/hexapdf/document/signatures.rb', line 360 def self.locate_signature_dict(xref_section, start_xref_position, signature_oid) data = xref_section.map {|oid, _gen, entry| [entry.pos, oid] if entry.in_use? }.compact.sort << [start_xref_position, nil] index = data.index {|_pos, oid| oid == signature_oid } [data[index][0], data[index + 1][0] - data[index][0]] end |
.replace_signature_contents(signature_data, contents) ⇒ Object
Replaces the value of the /Contents key in the serialized signature_data with the value of contents.
369 370 371 372 373 374 375 376 377 378 379 380 |
# File 'lib/hexapdf/document/signatures.rb', line 369 def self.replace_signature_contents(signature_data, contents) signature_data.sub!(/Contents(?:\(.*?\)|<.*?>)/) do |match| length = match.size result = "Contents<#{contents.unpack1('H*')}" if length < result.size raise HexaPDF::Error, "The reserved space for the signature was too small " \ "(#{(length - 10) / 2} vs #{(result.size - 10) / 2}) - use the handlers " \ "#signature_size method to increase the reserved space" end "#{result.ljust(length - 1, '0')}>" end 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 #handler and DefaultHandler.
write_options-
The key-value pairs of this hash will be passed on to the HexaPDF::Document#write method. Note that
incrementalwill be automatically set to ensure proper behaviour.
The used signature object will have the following default values set:
- /Filter
-
/Adobe.PPKLite
- /SubFilter
-
/adbe.pkcs7.detached
- /M
-
The current time.
These values can be overridden in the #finalize_objects method of the signature handler.
439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 |
# File 'lib/hexapdf/document/signatures.rb', line 439 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] = :'Adobe.PPKLite' signature[:SubFilter] = :'adbe.pkcs7.detached' signature[:M] = Time.now 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 = self.class.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 self.class.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.
529 530 531 |
# File 'lib/hexapdf/document/signatures.rb', line 529 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.
518 519 520 521 522 523 524 525 |
# File 'lib/hexapdf/document/signatures.rb', line 518 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, **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 DefaultHandler.
393 394 395 396 397 398 |
# File 'lib/hexapdf/document/signatures.rb', line 393 def 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 |