hexapdf 0.27.0 → 0.29.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (93) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +100 -11
  3. data/examples/019-acro_form.rb +14 -3
  4. data/examples/023-images.rb +30 -0
  5. data/examples/024-digital-signatures.rb +23 -0
  6. data/lib/hexapdf/cli/info.rb +5 -1
  7. data/lib/hexapdf/cli/inspect.rb +2 -2
  8. data/lib/hexapdf/cli/split.rb +2 -2
  9. data/lib/hexapdf/configuration.rb +13 -14
  10. data/lib/hexapdf/content/canvas.rb +8 -3
  11. data/lib/hexapdf/dictionary.rb +1 -5
  12. data/lib/hexapdf/dictionary_fields.rb +6 -2
  13. data/lib/hexapdf/digital_signature/cms_handler.rb +137 -0
  14. data/lib/hexapdf/digital_signature/handler.rb +138 -0
  15. data/lib/hexapdf/digital_signature/pkcs1_handler.rb +96 -0
  16. data/lib/hexapdf/{type → digital_signature}/signature.rb +3 -8
  17. data/lib/hexapdf/digital_signature/signatures.rb +210 -0
  18. data/lib/hexapdf/digital_signature/signing/default_handler.rb +317 -0
  19. data/lib/hexapdf/digital_signature/signing/signed_data_creator.rb +308 -0
  20. data/lib/hexapdf/digital_signature/signing/timestamp_handler.rb +148 -0
  21. data/lib/hexapdf/digital_signature/signing.rb +101 -0
  22. data/lib/hexapdf/{type/signature → digital_signature}/verification_result.rb +37 -41
  23. data/lib/hexapdf/digital_signature.rb +56 -0
  24. data/lib/hexapdf/document.rb +27 -24
  25. data/lib/hexapdf/encryption/standard_security_handler.rb +2 -1
  26. data/lib/hexapdf/filter/ascii85_decode.rb +1 -1
  27. data/lib/hexapdf/importer.rb +32 -27
  28. data/lib/hexapdf/layout/list_box.rb +1 -5
  29. data/lib/hexapdf/object.rb +5 -0
  30. data/lib/hexapdf/parser.rb +13 -0
  31. data/lib/hexapdf/revision.rb +15 -12
  32. data/lib/hexapdf/revisions.rb +4 -0
  33. data/lib/hexapdf/tokenizer.rb +14 -8
  34. data/lib/hexapdf/type/acro_form/appearance_generator.rb +174 -128
  35. data/lib/hexapdf/type/acro_form/button_field.rb +5 -3
  36. data/lib/hexapdf/type/acro_form/choice_field.rb +2 -0
  37. data/lib/hexapdf/type/acro_form/field.rb +11 -5
  38. data/lib/hexapdf/type/acro_form/form.rb +33 -7
  39. data/lib/hexapdf/type/acro_form/signature_field.rb +2 -0
  40. data/lib/hexapdf/type/acro_form/text_field.rb +12 -2
  41. data/lib/hexapdf/type/annotations/widget.rb +3 -0
  42. data/lib/hexapdf/type/font_true_type.rb +14 -0
  43. data/lib/hexapdf/type/object_stream.rb +2 -2
  44. data/lib/hexapdf/type/outline.rb +1 -1
  45. data/lib/hexapdf/type/page.rb +56 -46
  46. data/lib/hexapdf/type.rb +0 -1
  47. data/lib/hexapdf/version.rb +1 -1
  48. data/lib/hexapdf/writer.rb +2 -3
  49. data/test/hexapdf/content/test_canvas.rb +5 -0
  50. data/test/hexapdf/{type/signature → digital_signature}/common.rb +34 -4
  51. data/test/hexapdf/digital_signature/signing/test_default_handler.rb +162 -0
  52. data/test/hexapdf/digital_signature/signing/test_signed_data_creator.rb +225 -0
  53. data/test/hexapdf/digital_signature/signing/test_timestamp_handler.rb +88 -0
  54. data/test/hexapdf/{type/signature/test_adbe_pkcs7_detached.rb → digital_signature/test_cms_handler.rb} +7 -7
  55. data/test/hexapdf/{type/signature → digital_signature}/test_handler.rb +4 -4
  56. data/test/hexapdf/{type/signature/test_adbe_x509_rsa_sha1.rb → digital_signature/test_pkcs1_handler.rb} +3 -3
  57. data/test/hexapdf/{type → digital_signature}/test_signature.rb +7 -7
  58. data/test/hexapdf/digital_signature/test_signatures.rb +137 -0
  59. data/test/hexapdf/digital_signature/test_signing.rb +53 -0
  60. data/test/hexapdf/{type/signature → digital_signature}/test_verification_result.rb +7 -7
  61. data/test/hexapdf/document/test_pages.rb +2 -2
  62. data/test/hexapdf/encryption/test_aes.rb +1 -1
  63. data/test/hexapdf/filter/test_predictor.rb +0 -1
  64. data/test/hexapdf/layout/test_box.rb +2 -1
  65. data/test/hexapdf/layout/test_column_box.rb +1 -1
  66. data/test/hexapdf/layout/test_list_box.rb +1 -1
  67. data/test/hexapdf/test_dictionary_fields.rb +2 -1
  68. data/test/hexapdf/test_document.rb +3 -9
  69. data/test/hexapdf/test_importer.rb +13 -6
  70. data/test/hexapdf/test_parser.rb +17 -0
  71. data/test/hexapdf/test_revision.rb +15 -14
  72. data/test/hexapdf/test_revisions.rb +43 -0
  73. data/test/hexapdf/test_stream.rb +1 -1
  74. data/test/hexapdf/test_tokenizer.rb +3 -4
  75. data/test/hexapdf/test_writer.rb +3 -3
  76. data/test/hexapdf/type/acro_form/test_appearance_generator.rb +135 -56
  77. data/test/hexapdf/type/acro_form/test_button_field.rb +6 -1
  78. data/test/hexapdf/type/acro_form/test_choice_field.rb +4 -0
  79. data/test/hexapdf/type/acro_form/test_field.rb +4 -4
  80. data/test/hexapdf/type/acro_form/test_form.rb +18 -0
  81. data/test/hexapdf/type/acro_form/test_signature_field.rb +4 -0
  82. data/test/hexapdf/type/acro_form/test_text_field.rb +13 -0
  83. data/test/hexapdf/type/test_font_true_type.rb +20 -0
  84. data/test/hexapdf/type/test_object_stream.rb +2 -1
  85. data/test/hexapdf/type/test_outline.rb +3 -0
  86. data/test/hexapdf/type/test_page.rb +67 -30
  87. data/test/hexapdf/type/test_page_tree_node.rb +4 -2
  88. metadata +69 -16
  89. data/lib/hexapdf/document/signatures.rb +0 -546
  90. data/lib/hexapdf/type/signature/adbe_pkcs7_detached.rb +0 -135
  91. data/lib/hexapdf/type/signature/adbe_x509_rsa_sha1.rb +0 -95
  92. data/lib/hexapdf/type/signature/handler.rb +0 -140
  93. data/test/hexapdf/document/test_signatures.rb +0 -352
@@ -0,0 +1,138 @@
1
+ # -*- encoding: utf-8; frozen_string_literal: true -*-
2
+ #
3
+ #--
4
+ # This file is part of HexaPDF.
5
+ #
6
+ # HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
7
+ # Copyright (C) 2014-2022 Thomas Leitner
8
+ #
9
+ # HexaPDF is free software: you can redistribute it and/or modify it
10
+ # under the terms of the GNU Affero General Public License version 3 as
11
+ # published by the Free Software Foundation with the addition of the
12
+ # following permission added to Section 15 as permitted in Section 7(a):
13
+ # FOR ANY PART OF THE COVERED WORK IN WHICH THE COPYRIGHT IS OWNED BY
14
+ # THOMAS LEITNER, THOMAS LEITNER DISCLAIMS THE WARRANTY OF NON
15
+ # INFRINGEMENT OF THIRD PARTY RIGHTS.
16
+ #
17
+ # HexaPDF is distributed in the hope that it will be useful, but WITHOUT
18
+ # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
19
+ # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
20
+ # License for more details.
21
+ #
22
+ # You should have received a copy of the GNU Affero General Public License
23
+ # along with HexaPDF. If not, see <http://www.gnu.org/licenses/>.
24
+ #
25
+ # The interactive user interfaces in modified source and object code
26
+ # versions of HexaPDF must display Appropriate Legal Notices, as required
27
+ # under Section 5 of the GNU Affero General Public License version 3.
28
+ #
29
+ # In accordance with Section 7(b) of the GNU Affero General Public
30
+ # License, a covered work must retain the producer line in every PDF that
31
+ # is created or manipulated using HexaPDF.
32
+ #
33
+ # If the GNU Affero General Public License doesn't fit your need,
34
+ # commercial licenses are available at <https://gettalong.at/hexapdf/>.
35
+ #++
36
+
37
+ require 'hexapdf/digital_signature/verification_result'
38
+
39
+ module HexaPDF
40
+ module DigitalSignature
41
+
42
+ # The base signature handler providing common functionality.
43
+ #
44
+ # Specific signature handler need to override methods if necessary and implement the needed
45
+ # ones that don't have a default implementation.
46
+ class Handler
47
+
48
+ # The signature dictionary used by the handler.
49
+ attr_reader :signature_dict
50
+
51
+ # Creates a new signature handler for the given signature dictionary.
52
+ def initialize(signature_dict)
53
+ @signature_dict = signature_dict
54
+ end
55
+
56
+ # Returns the common name of the signer (/Name field of the signature dictionary).
57
+ def signer_name
58
+ @signature_dict[:Name]
59
+ end
60
+
61
+ # Returns the time of signing (/M field of the signature dictionary).
62
+ def signing_time
63
+ @signature_dict[:M]
64
+ end
65
+
66
+ # Returns the certificate chain.
67
+ #
68
+ # Needs to be implemented by specific handlers.
69
+ def certificate_chain
70
+ raise "Needs to be implemented by specific handlers"
71
+ end
72
+
73
+ # Returns the certificate used for signing.
74
+ #
75
+ # Needs to be implemented by specific handlers.
76
+ def signer_certificate
77
+ raise "Needs to be implemented by specific handlers"
78
+ end
79
+
80
+ # Verifies general signature properties and prepares the provided OpenSSL::X509::Store
81
+ # object for use by concrete implementations.
82
+ #
83
+ # Needs to be called by specific handlers.
84
+ def verify(store, allow_self_signed: false)
85
+ result = VerificationResult.new
86
+ check_certified_signature(result)
87
+ verify_signing_time(result)
88
+ store.verify_callback =
89
+ store_verification_callback(result, allow_self_signed: allow_self_signed)
90
+ result
91
+ end
92
+
93
+ protected
94
+
95
+ # Verifies that the signing time was within the validity period of the signer certificate.
96
+ def verify_signing_time(result)
97
+ time = signing_time
98
+ cert = signer_certificate
99
+ if time && cert && (time < cert.not_before || time > cert.not_after)
100
+ result.log(:error, "Signer certificate not valid at signing time")
101
+ end
102
+ end
103
+
104
+ DOCMDP_PERMS_MESSAGE_MAP = { # :nodoc:
105
+ 1 => "No changes allowed",
106
+ 2 => "Form filling and signing allowed",
107
+ 3 => "Form filling, signing and annotation manipulation allowed",
108
+ }
109
+
110
+ # Sets an informational message on +result+ whether the signature is a certified signature.
111
+ def check_certified_signature(result)
112
+ sigref = signature_dict[:Reference]&.find {|ref| ref[:TransformMethod] == :DocMDP }
113
+ if sigref && signature_dict.document.catalog[:Perms]&.[](:DocMDP) == signature_dict
114
+ perms = sigref[:TransformParams]&.[](:P) || 2
115
+ result.log(:info, "Certified signature (#{DOCMDP_PERMS_MESSAGE_MAP[perms]})")
116
+ end
117
+ end
118
+
119
+ # Returns the block that should be used as the OpenSSL::X509::Store verification callback.
120
+ #
121
+ # +result+:: The VerificationResult object that should be updated if problems are found.
122
+ #
123
+ # +allow_self_signed+:: Specifies whether self-signed certificates are allowed.
124
+ def store_verification_callback(result, allow_self_signed: false)
125
+ lambda do |_success, context|
126
+ if context.error == OpenSSL::X509::V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT ||
127
+ context.error == OpenSSL::X509::V_ERR_SELF_SIGNED_CERT_IN_CHAIN
128
+ result.log(allow_self_signed ? :info : :error, "Self-signed certificate found")
129
+ end
130
+
131
+ true
132
+ end
133
+ end
134
+
135
+ end
136
+
137
+ end
138
+ end
@@ -0,0 +1,96 @@
1
+ # -*- encoding: utf-8; frozen_string_literal: true -*-
2
+ #
3
+ #--
4
+ # This file is part of HexaPDF.
5
+ #
6
+ # HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
7
+ # Copyright (C) 2014-2022 Thomas Leitner
8
+ #
9
+ # HexaPDF is free software: you can redistribute it and/or modify it
10
+ # under the terms of the GNU Affero General Public License version 3 as
11
+ # published by the Free Software Foundation with the addition of the
12
+ # following permission added to Section 15 as permitted in Section 7(a):
13
+ # FOR ANY PART OF THE COVERED WORK IN WHICH THE COPYRIGHT IS OWNED BY
14
+ # THOMAS LEITNER, THOMAS LEITNER DISCLAIMS THE WARRANTY OF NON
15
+ # INFRINGEMENT OF THIRD PARTY RIGHTS.
16
+ #
17
+ # HexaPDF is distributed in the hope that it will be useful, but WITHOUT
18
+ # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
19
+ # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
20
+ # License for more details.
21
+ #
22
+ # You should have received a copy of the GNU Affero General Public License
23
+ # along with HexaPDF. If not, see <http://www.gnu.org/licenses/>.
24
+ #
25
+ # The interactive user interfaces in modified source and object code
26
+ # versions of HexaPDF must display Appropriate Legal Notices, as required
27
+ # under Section 5 of the GNU Affero General Public License version 3.
28
+ #
29
+ # In accordance with Section 7(b) of the GNU Affero General Public
30
+ # License, a covered work must retain the producer line in every PDF that
31
+ # is created or manipulated using HexaPDF.
32
+ #
33
+ # If the GNU Affero General Public License doesn't fit your need,
34
+ # commercial licenses are available at <https://gettalong.at/hexapdf/>.
35
+ #++
36
+
37
+ require 'openssl'
38
+ require 'hexapdf/digital_signature/handler'
39
+
40
+ module HexaPDF
41
+ module DigitalSignature
42
+
43
+ # The signature handler for PKCS#1 based sub-filters, the only being the adbe.x509.rsa_sha1
44
+ # sub-filter.
45
+ #
46
+ # Since PKCS#1 signatures are deprecated with PDF 2.0, the handler only provides the
47
+ # implementation for reading and verifying signatures.
48
+ #
49
+ # See: PDF1.7/2.0 s12.8.3.2
50
+ class PKCS1Handler < Handler
51
+
52
+ # Returns the certificate chain.
53
+ def certificate_chain
54
+ return [] unless signature_dict.key?(:Cert)
55
+ [signature_dict[:Cert]].flatten.map {|str| OpenSSL::X509::Certificate.new(str) }
56
+ end
57
+
58
+ # Returns the signer certificate (an instance of OpenSSL::X509::Certificate).
59
+ def signer_certificate
60
+ certificate_chain.first
61
+ end
62
+
63
+ # Verifies the signature using the provided OpenSSL::X509::Store object.
64
+ def verify(store, allow_self_signed: false)
65
+ result = super
66
+
67
+ signer_certificate = self.signer_certificate
68
+ certificate_chain = self.certificate_chain
69
+
70
+ if certificate_chain.empty?
71
+ result.log(:error, "No certificates for verification found")
72
+ return result
73
+ end
74
+
75
+ signature = OpenSSL::ASN1.decode(signature_dict.contents)
76
+ if signature.tag != OpenSSL::ASN1::OCTET_STRING
77
+ result.log(:error, "PKCS1 signature object invalid, octet string expected")
78
+ return result
79
+ end
80
+
81
+ store.verify(signer_certificate, certificate_chain)
82
+
83
+ if signer_certificate.public_key.verify(OpenSSL::Digest.new('SHA1'),
84
+ signature.value, signature_dict.signed_data)
85
+ result.log(:info, "Signature valid")
86
+ else
87
+ result.log(:error, "Signature verification failed")
88
+ end
89
+
90
+ result
91
+ end
92
+
93
+ end
94
+
95
+ end
96
+ end
@@ -39,7 +39,7 @@ require 'hexapdf/dictionary'
39
39
  require 'hexapdf/error'
40
40
 
41
41
  module HexaPDF
42
- module Type
42
+ module DigitalSignature
43
43
 
44
44
  # Represents a digital signature that is used to authenticate a user and the contents of the
45
45
  # document.
@@ -53,14 +53,9 @@ module HexaPDF
53
53
  # By defining a custom signature handler one is able to also customize the signature
54
54
  # verification.
55
55
  #
56
- # See: PDF1.7 s12.8.1, PDF2.0 s12.8.1, HexaPDF::Type::AcroForm::SignatureField
56
+ # See: PDF1.7/2.0 s12.8.1, HexaPDF::Type::AcroForm::SignatureField
57
57
  class Signature < Dictionary
58
58
 
59
- autoload :Handler, 'hexapdf/type/signature/handler'
60
- autoload :AdbeX509RsaSha1, 'hexapdf/type/signature/adbe_x509_rsa_sha1'
61
- autoload :AdbePkcs7Detached, 'hexapdf/type/signature/adbe_pkcs7_detached'
62
- autoload :VerificationResult, 'hexapdf/type/signature/verification_result'
63
-
64
59
  # Represents a transform parameters dictionary.
65
60
  #
66
61
  # The allowed fields depend on the transform method, so not all fields are available all the
@@ -122,7 +117,7 @@ module HexaPDF
122
117
 
123
118
  # Represents a signature reference dictionary.
124
119
  #
125
- # See: PDF1.7 s12.8.1, PDF2.0 s12.8.1, HexaPDF::Type::Signature
120
+ # See: PDF1.7/2.0 s12.8.1, HexaPDF::DigitalSignature::Signature
126
121
  class SignatureReference < Dictionary
127
122
 
128
123
  define_type :SigRef
@@ -0,0 +1,210 @@
1
+ # -*- encoding: utf-8; frozen_string_literal: true -*-
2
+ #
3
+ #--
4
+ # This file is part of HexaPDF.
5
+ #
6
+ # HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
7
+ # Copyright (C) 2014-2022 Thomas Leitner
8
+ #
9
+ # HexaPDF is free software: you can redistribute it and/or modify it
10
+ # under the terms of the GNU Affero General Public License version 3 as
11
+ # published by the Free Software Foundation with the addition of the
12
+ # following permission added to Section 15 as permitted in Section 7(a):
13
+ # FOR ANY PART OF THE COVERED WORK IN WHICH THE COPYRIGHT IS OWNED BY
14
+ # THOMAS LEITNER, THOMAS LEITNER DISCLAIMS THE WARRANTY OF NON
15
+ # INFRINGEMENT OF THIRD PARTY RIGHTS.
16
+ #
17
+ # HexaPDF is distributed in the hope that it will be useful, but WITHOUT
18
+ # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
19
+ # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
20
+ # License for more details.
21
+ #
22
+ # You should have received a copy of the GNU Affero General Public License
23
+ # along with HexaPDF. If not, see <http://www.gnu.org/licenses/>.
24
+ #
25
+ # The interactive user interfaces in modified source and object code
26
+ # versions of HexaPDF must display Appropriate Legal Notices, as required
27
+ # under Section 5 of the GNU Affero General Public License version 3.
28
+ #
29
+ # In accordance with Section 7(b) of the GNU Affero General Public
30
+ # License, a covered work must retain the producer line in every PDF that
31
+ # is created or manipulated using HexaPDF.
32
+ #
33
+ # If the GNU Affero General Public License doesn't fit your need,
34
+ # commercial licenses are available at <https://gettalong.at/hexapdf/>.
35
+ #++
36
+
37
+ require 'openssl'
38
+ require 'stringio'
39
+ require 'hexapdf/digital_signature'
40
+ require 'hexapdf/error'
41
+
42
+ module HexaPDF
43
+ module DigitalSignature
44
+
45
+ # This class provides methods for interacting with digital signatures of a PDF file. Use it
46
+ # through HexaPDF::Document#signatures.
47
+ class Signatures
48
+
49
+ include Enumerable
50
+
51
+ # Creates a new Signatures object for the given PDF document.
52
+ def initialize(document)
53
+ @document = document
54
+ end
55
+
56
+ # Creates a signing handler with the given attributes and returns it.
57
+ #
58
+ # A signing handler name is mapped to a class via the 'signature.signing_handler'
59
+ # configuration option. The default signing handler is DefaultHandler.
60
+ def signing_handler(name: :default, **attributes)
61
+ handler = @document.config.constantize('signature.signing_handler', name) do
62
+ raise HexaPDF::Error, "No signing handler named '#{name}' is available"
63
+ end
64
+ handler.new(**attributes)
65
+ end
66
+
67
+ # Adds a signature to the document and returns the corresponding signature object.
68
+ #
69
+ # This method will add a new signature to the document and write the updated document to the
70
+ # given file or IO stream. Afterwards the document can't be modified anymore and still retain
71
+ # a correct digital signature. To modify the signed document (e.g. for adding another
72
+ # signature) create a new document based on the given file or IO stream instead.
73
+ #
74
+ # +signature+::
75
+ # Can either be a signature object (determined via the /Type key), a signature field or
76
+ # +nil+. Providing a signature object or signature field provides for more control, e.g.:
77
+ #
78
+ # * Setting values for optional signature object fields like /Reason and /Location.
79
+ # * (In)directly specifying which signature field should be used.
80
+ #
81
+ # If a signature object is provided and it is not associated with an AcroForm signature
82
+ # field, a new signature field is created and added to the main AcroForm object, creating
83
+ # that if necessary.
84
+ #
85
+ # If a signature field is provided and it already has a signature object as field value,
86
+ # that signature object is discarded.
87
+ #
88
+ # If the signature field doesn't have a widget, a non-visible one is created on the first
89
+ # page.
90
+ #
91
+ # +handler+::
92
+ # The signing handler that provides the necessary methods for signing and adjusting the
93
+ # signature and signature field objects to one's liking, see #handler and DefaultHandler.
94
+ #
95
+ # +write_options+::
96
+ # The key-value pairs of this hash will be passed on to the HexaPDF::Document#write
97
+ # method. Note that +incremental+ will be automatically set to ensure proper behaviour.
98
+ #
99
+ # The used signature object will have the following default values set:
100
+ #
101
+ # /Filter:: /Adobe.PPKLite
102
+ # /SubFilter:: /adbe.pkcs7.detached
103
+ # /M:: The current time.
104
+ #
105
+ # These values can be overridden in the #finalize_objects method of the signature handler.
106
+ def add(file_or_io, handler, signature: nil, write_options: {})
107
+ if signature && signature.type != :Sig
108
+ signature_field = signature
109
+ signature = signature_field.field_value
110
+ end
111
+ signature ||= @document.add({Type: :Sig})
112
+
113
+ # Prepare AcroForm
114
+ form = @document.acro_form(create: true)
115
+ form.signature_flag(:signatures_exist, :append_only)
116
+
117
+ # Prepare signature field
118
+ signature_field ||= form.each_field.find {|field| field.field_value == signature } ||
119
+ form.create_signature_field(generate_field_name)
120
+ signature_field.field_value = signature
121
+
122
+ if signature_field.each_widget.to_a.empty?
123
+ signature_field.create_widget(@document.pages[0], Rect: [0, 0, 0, 0])
124
+ end
125
+
126
+ # Prepare signature object
127
+ handler.finalize_objects(signature_field, signature)
128
+ signature[:ByteRange] = [0, 1_000_000_000_000, 1_000_000_000_000, 1_000_000_000_000]
129
+ signature[:Contents] = '00' * handler.signature_size # twice the size due to hex encoding
130
+
131
+ io = if file_or_io.kind_of?(String)
132
+ File.open(file_or_io, 'wb+')
133
+ else
134
+ file_or_io
135
+ end
136
+
137
+ # Save the current state so that we can determine the correct /ByteRange value and set the
138
+ # values
139
+ start_xref, section = @document.write(io, incremental: true, **write_options)
140
+ signature_offset, signature_length = Signing.locate_signature_dict(section, start_xref,
141
+ signature.oid)
142
+ io.pos = signature_offset
143
+ signature_data = io.read(signature_length)
144
+
145
+ io.seek(0, IO::SEEK_END)
146
+ file_size = io.pos
147
+
148
+ # Calculate the offsets for the /ByteRange
149
+ contents_offset = signature_offset + signature_data.index('Contents(') + 8
150
+ offset2 = contents_offset + signature[:Contents].size + 2 # +2 because of the needed < and >
151
+ length2 = file_size - offset2
152
+ signature[:ByteRange] = [0, contents_offset, offset2, length2]
153
+
154
+ # Set the correct /ByteRange value
155
+ signature_data.sub!(/ByteRange\[0 1000000000000 1000000000000 1000000000000\]/) do |match|
156
+ length = match.size
157
+ result = "ByteRange[0 #{contents_offset} #{offset2} #{length2}]"
158
+ result.ljust(length)
159
+ end
160
+
161
+ # Now everything besides the /Contents value is correct, so we can read the contents for
162
+ # signing
163
+ io.pos = signature_offset
164
+ io.write(signature_data)
165
+ signature[:Contents] = handler.sign(io, signature[:ByteRange].value)
166
+
167
+ # And now replace the /Contents value
168
+ Signing.replace_signature_contents(signature_data, signature[:Contents])
169
+ io.pos = signature_offset
170
+ io.write(signature_data)
171
+
172
+ signature
173
+ ensure
174
+ io.close if io && io != file_or_io
175
+ end
176
+
177
+ # :call-seq:
178
+ # signatures.each {|signature| block } -> signatures
179
+ # signatures.each -> Enumerator
180
+ #
181
+ # Iterates over all signatures in the order they are found.
182
+ def each
183
+ return to_enum(__method__) unless block_given?
184
+
185
+ return [] unless (form = @document.acro_form)
186
+ form.each_field do |field|
187
+ yield(field.field_value) if field.field_type == :Sig && field.field_value
188
+ end
189
+ end
190
+
191
+ # Returns the number of signatures in the PDF document. May be zero if the document has no
192
+ # signatures.
193
+ def count
194
+ each.to_a.size
195
+ end
196
+
197
+ private
198
+
199
+ # Generates a field name for a signature field.
200
+ def generate_field_name
201
+ index = (@document.acro_form.each_field.
202
+ map {|field| field.full_field_name.scan(/\ASignature(\d+)/).first&.first.to_i }.
203
+ max || 0) + 1
204
+ "Signature#{index}"
205
+ end
206
+
207
+ end
208
+
209
+ end
210
+ end