hexapdf 0.28.0 → 0.31.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +86 -10
  3. data/examples/024-digital-signatures.rb +23 -0
  4. data/lib/hexapdf/cli/command.rb +16 -1
  5. data/lib/hexapdf/cli/info.rb +9 -1
  6. data/lib/hexapdf/cli/inspect.rb +2 -2
  7. data/lib/hexapdf/composer.rb +76 -28
  8. data/lib/hexapdf/configuration.rb +29 -16
  9. data/lib/hexapdf/dictionary_fields.rb +13 -4
  10. data/lib/hexapdf/digital_signature/cms_handler.rb +137 -0
  11. data/lib/hexapdf/digital_signature/handler.rb +138 -0
  12. data/lib/hexapdf/digital_signature/pkcs1_handler.rb +96 -0
  13. data/lib/hexapdf/{type → digital_signature}/signature.rb +3 -8
  14. data/lib/hexapdf/digital_signature/signatures.rb +210 -0
  15. data/lib/hexapdf/digital_signature/signing/default_handler.rb +317 -0
  16. data/lib/hexapdf/digital_signature/signing/signed_data_creator.rb +308 -0
  17. data/lib/hexapdf/digital_signature/signing/timestamp_handler.rb +148 -0
  18. data/lib/hexapdf/digital_signature/signing.rb +101 -0
  19. data/lib/hexapdf/{type/signature → digital_signature}/verification_result.rb +37 -41
  20. data/lib/hexapdf/digital_signature.rb +56 -0
  21. data/lib/hexapdf/document/pages.rb +31 -18
  22. data/lib/hexapdf/document.rb +29 -15
  23. data/lib/hexapdf/encryption/standard_security_handler.rb +4 -3
  24. data/lib/hexapdf/filter/flate_decode.rb +20 -8
  25. data/lib/hexapdf/layout/page_style.rb +144 -0
  26. data/lib/hexapdf/layout.rb +1 -0
  27. data/lib/hexapdf/task/optimize.rb +8 -6
  28. data/lib/hexapdf/type/font_simple.rb +14 -2
  29. data/lib/hexapdf/type/object_stream.rb +7 -2
  30. data/lib/hexapdf/type/outline.rb +1 -1
  31. data/lib/hexapdf/type/outline_item.rb +1 -1
  32. data/lib/hexapdf/type/page.rb +29 -8
  33. data/lib/hexapdf/type/xref_stream.rb +11 -4
  34. data/lib/hexapdf/type.rb +0 -1
  35. data/lib/hexapdf/version.rb +1 -1
  36. data/lib/hexapdf/writer.rb +1 -1
  37. data/test/hexapdf/{type/signature → digital_signature}/common.rb +31 -3
  38. data/test/hexapdf/digital_signature/signing/test_default_handler.rb +162 -0
  39. data/test/hexapdf/digital_signature/signing/test_signed_data_creator.rb +225 -0
  40. data/test/hexapdf/digital_signature/signing/test_timestamp_handler.rb +88 -0
  41. data/test/hexapdf/{type/signature/test_adbe_pkcs7_detached.rb → digital_signature/test_cms_handler.rb} +7 -7
  42. data/test/hexapdf/{type/signature → digital_signature}/test_handler.rb +4 -4
  43. data/test/hexapdf/{type/signature/test_adbe_x509_rsa_sha1.rb → digital_signature/test_pkcs1_handler.rb} +3 -3
  44. data/test/hexapdf/{type → digital_signature}/test_signature.rb +7 -7
  45. data/test/hexapdf/digital_signature/test_signatures.rb +137 -0
  46. data/test/hexapdf/digital_signature/test_signing.rb +53 -0
  47. data/test/hexapdf/{type/signature → digital_signature}/test_verification_result.rb +7 -7
  48. data/test/hexapdf/document/test_pages.rb +25 -0
  49. data/test/hexapdf/encryption/test_standard_security_handler.rb +2 -2
  50. data/test/hexapdf/filter/test_flate_decode.rb +19 -5
  51. data/test/hexapdf/layout/test_page_style.rb +70 -0
  52. data/test/hexapdf/task/test_optimize.rb +11 -9
  53. data/test/hexapdf/test_composer.rb +35 -10
  54. data/test/hexapdf/test_dictionary_fields.rb +9 -3
  55. data/test/hexapdf/test_document.rb +1 -1
  56. data/test/hexapdf/test_writer.rb +8 -8
  57. data/test/hexapdf/type/test_font_simple.rb +18 -6
  58. data/test/hexapdf/type/test_object_stream.rb +16 -7
  59. data/test/hexapdf/type/test_outline.rb +3 -1
  60. data/test/hexapdf/type/test_outline_item.rb +3 -1
  61. data/test/hexapdf/type/test_page.rb +42 -11
  62. data/test/hexapdf/type/test_xref_stream.rb +6 -1
  63. metadata +27 -15
  64. data/lib/hexapdf/document/signatures.rb +0 -546
  65. data/lib/hexapdf/type/signature/adbe_pkcs7_detached.rb +0 -135
  66. data/lib/hexapdf/type/signature/adbe_x509_rsa_sha1.rb +0 -95
  67. data/lib/hexapdf/type/signature/handler.rb +0 -140
  68. 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