hexapdf 0.28.0 → 0.30.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +59 -10
  3. data/examples/024-digital-signatures.rb +23 -0
  4. data/lib/hexapdf/configuration.rb +12 -12
  5. data/lib/hexapdf/dictionary_fields.rb +13 -4
  6. data/lib/hexapdf/digital_signature/cms_handler.rb +137 -0
  7. data/lib/hexapdf/digital_signature/handler.rb +138 -0
  8. data/lib/hexapdf/digital_signature/pkcs1_handler.rb +96 -0
  9. data/lib/hexapdf/{type → digital_signature}/signature.rb +3 -8
  10. data/lib/hexapdf/digital_signature/signatures.rb +210 -0
  11. data/lib/hexapdf/digital_signature/signing/default_handler.rb +317 -0
  12. data/lib/hexapdf/digital_signature/signing/signed_data_creator.rb +308 -0
  13. data/lib/hexapdf/digital_signature/signing/timestamp_handler.rb +148 -0
  14. data/lib/hexapdf/digital_signature/signing.rb +101 -0
  15. data/lib/hexapdf/{type/signature → digital_signature}/verification_result.rb +37 -41
  16. data/lib/hexapdf/digital_signature.rb +56 -0
  17. data/lib/hexapdf/document/pages.rb +35 -18
  18. data/lib/hexapdf/document.rb +21 -14
  19. data/lib/hexapdf/encryption/standard_security_handler.rb +4 -3
  20. data/lib/hexapdf/type/font_simple.rb +14 -2
  21. data/lib/hexapdf/type.rb +0 -1
  22. data/lib/hexapdf/version.rb +1 -1
  23. data/test/hexapdf/{type/signature → digital_signature}/common.rb +31 -3
  24. data/test/hexapdf/digital_signature/signing/test_default_handler.rb +162 -0
  25. data/test/hexapdf/digital_signature/signing/test_signed_data_creator.rb +225 -0
  26. data/test/hexapdf/digital_signature/signing/test_timestamp_handler.rb +88 -0
  27. data/test/hexapdf/{type/signature/test_adbe_pkcs7_detached.rb → digital_signature/test_cms_handler.rb} +7 -7
  28. data/test/hexapdf/{type/signature → digital_signature}/test_handler.rb +4 -4
  29. data/test/hexapdf/{type/signature/test_adbe_x509_rsa_sha1.rb → digital_signature/test_pkcs1_handler.rb} +3 -3
  30. data/test/hexapdf/{type → digital_signature}/test_signature.rb +7 -7
  31. data/test/hexapdf/digital_signature/test_signatures.rb +137 -0
  32. data/test/hexapdf/digital_signature/test_signing.rb +53 -0
  33. data/test/hexapdf/{type/signature → digital_signature}/test_verification_result.rb +7 -7
  34. data/test/hexapdf/document/test_pages.rb +25 -0
  35. data/test/hexapdf/encryption/test_standard_security_handler.rb +2 -2
  36. data/test/hexapdf/test_dictionary_fields.rb +9 -3
  37. data/test/hexapdf/test_document.rb +1 -1
  38. data/test/hexapdf/test_writer.rb +6 -6
  39. data/test/hexapdf/type/test_font_simple.rb +18 -6
  40. metadata +25 -15
  41. data/lib/hexapdf/document/signatures.rb +0 -546
  42. data/lib/hexapdf/type/signature/adbe_pkcs7_detached.rb +0 -135
  43. data/lib/hexapdf/type/signature/adbe_x509_rsa_sha1.rb +0 -95
  44. data/lib/hexapdf/type/signature/handler.rb +0 -140
  45. data/test/hexapdf/document/test_signatures.rb +0 -352
@@ -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
@@ -0,0 +1,317 @@
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/error'
39
+ require 'hexapdf/version'
40
+ require 'stringio'
41
+
42
+ module HexaPDF
43
+ module DigitalSignature
44
+ module Signing
45
+
46
+ # This is the default signing handler which provides the ability to sign a document with the
47
+ # adbe.pkcs7.detached or ETSI.CAdES.detached algorithms. It is registered under the :default
48
+ # name.
49
+ #
50
+ # == Usage
51
+ #
52
+ # The signing handler is used by default by all methods that need a signing handler. Therefore
53
+ # it is usually only necessary to provide the actual attribute values.
54
+ #
55
+ # *Note*: Currently only RSA is supported, DSA and ECDSA are not. See the examples below for
56
+ # how to handle them using external signing.
57
+ #
58
+ #
59
+ # == CMS and PAdES Signatures
60
+ #
61
+ # The handler supports the older standard of CMS signatures as well as the newer PAdES
62
+ # signatures specified in PDF 2.0. By default, CMS signatures are created but this can be
63
+ # changed by setting #signature_type to :pades.
64
+ #
65
+ # When creating PAdES signatures the following two PAdES baseline signatures are supported:
66
+ # B-B and B-T. The difference between those two is that a timestamp handler was defined for
67
+ # B-T compatibility.
68
+ #
69
+ #
70
+ # == Signing Modes - Internal, External, External/Asynchronous
71
+ #
72
+ # This handler provides two ways to create the CMS signed-data structure required by
73
+ # Signatures#add:
74
+ #
75
+ # * By providing the signing certificate together with the signing key and the certificate
76
+ # chain, HexaPDF itself does the signing *internally*. It is the preferred way if all the
77
+ # needed information is available.
78
+ #
79
+ # Assign the respective data to the #certificate, #key and #certificate_chain attributes.
80
+ #
81
+ # * By using an *external signing mechanism*, a callable object assigned to #external_signing.
82
+ # Here the actual signing happens "outside" of HexaPDF, for example, in custom code or even
83
+ # asynchronously. This is needed in case the signing key is not directly available but only
84
+ # an interface to it (e.g. when dealing with a HSM).
85
+ #
86
+ # Depending on whether #certificate is set the signing happens differently:
87
+ #
88
+ # * If #certificate is not set, the callable object is used instead of #sign, so it needs to
89
+ # accept the same arguments as #sign and needs to return a complete, DER-serialized CMS
90
+ # signed data object.
91
+ #
92
+ # * If #certificate is set, the CMS signed data object is created by HexaPDF. The
93
+ # callable #external_signing object is called with the used digest algorithm and the
94
+ # already digested data which needs to be signed (but *not* digested) and the signature
95
+ # returned.
96
+ #
97
+ # If the signing process needs to be *asynchronous*, make sure to set the #signature_size
98
+ # appropriately, return an empty string during signing and later use
99
+ # Signatures.embed_signature to embed the actual signature.
100
+ #
101
+ #
102
+ # == Optional Data
103
+ #
104
+ # Besides the required data, some optional attributes can also be specified:
105
+ #
106
+ # * Reason, location and contact information
107
+ # * Making the signature a certification signature by applying the DocMDP transform method and
108
+ # a DoCMDP permission
109
+ #
110
+ #
111
+ # == Examples
112
+ #
113
+ # # Signing using certificate + key
114
+ # document.sign("output.pdf", certificate: my_cert, key: my_key,
115
+ # certificate_chain: my_chain)
116
+ #
117
+ # # Signing using an external mechanism without certificate set
118
+ # signing_proc = lambda do |io, byte_range|
119
+ # io.pos = byte_range[0]
120
+ # data = io.read(byte_range[1])
121
+ # io.pos = byte_range[2]
122
+ # data << io.read(byte_range[3])
123
+ # signing_service.pkcs7_sign(data).to_der
124
+ # end
125
+ # document.sign("output.pdf", signature_size: 10_000, external_signing: signing_proc)
126
+ #
127
+ # # Signing using external mechanism with certificate set
128
+ # signing_proc = lambda do |digest_method, hash|
129
+ # signing_service.sign_raw(digest_method, hash)
130
+ # end
131
+ # document.sign("output.pdf", certificate: my_cert, certificate_chain: my_chain,
132
+ # external_signing: signing_proc)
133
+ #
134
+ # # Signing with DSA or ECDSA certificate/keys
135
+ # signing_proc = lambda do |io, byte_range|
136
+ # io.pos = byte_range[0]
137
+ # data = io.read(byte_range[1])
138
+ # io.pos = byte_range[2]
139
+ # data << io.read(byte_range[3])
140
+ # OpenSSL::PKCS7.sign(certificate, key, data, certificate_chain,
141
+ # OpenSSL::PKCS7::DETACHED | OpenSSL::PKCS7::BINARY).to_der
142
+ # end
143
+ # document.sign("output.pdf", signature_size: 10_000, external_signing: signing_proc)
144
+ #
145
+ #
146
+ # == Implementing a Signing Handler
147
+ #
148
+ # This class also serves as an example on how to create a custom handler: The public methods
149
+ # #signature_size, #finalize_objects and #sign are used by the digital signature algorithm.
150
+ # See their descriptions for details.
151
+ #
152
+ # Once a custom signing handler has been created, it can be registered under the
153
+ # 'signature.signing_handler' configuration option for easy use. It has to take keyword
154
+ # arguments in its initialize method to be compatible with the Signatures#handler method.
155
+ class DefaultHandler
156
+
157
+ # The certificate with which to sign the PDF.
158
+ #
159
+ # If the certificate is provided, HexaPDF creates the signature object. Otherwise the
160
+ # #external_signing callable object has to create it.
161
+ attr_accessor :certificate
162
+
163
+ # The private key for the #certificate.
164
+ #
165
+ # If the key is provided, HexaPDF does the signing. Otherwise the #external_signing callable
166
+ # object has to sign the data.
167
+ attr_accessor :key
168
+
169
+ # The certificate chain that should be embedded in the PDF; usually contains all
170
+ # certificates up to the root certificate.
171
+ attr_accessor :certificate_chain
172
+
173
+ # The digest algorithm that should be used when creating the signature.
174
+ #
175
+ # See SignedDataCreator#digest_algorithm for the default value (if nothing is set) and for
176
+ # the allowed values.
177
+ attr_accessor :digest_algorithm
178
+
179
+ # The timestamp handler that should be used for timestamping the signature.
180
+ #
181
+ # If this attribute is set, a timestamp token is embedded into the CMS object.
182
+ attr_accessor :timestamp_handler
183
+
184
+ # A callable object for custom signing mechanisms.
185
+ #
186
+ # The callable object has two different uses depending on whether #certificate is set:
187
+ #
188
+ # * If #certificate is not set, it fulfills the same role as the #sign method and needs to
189
+ # conform to that interface.
190
+ #
191
+ # * If #certificate is set and #key is not, it is just used for signing. Here it needs to
192
+ # accept the used digest algorithm and the already digested data as arguments and return
193
+ # the signature.
194
+ attr_accessor :external_signing
195
+
196
+ # The reason for signing. If used, will be set on the signature object.
197
+ attr_accessor :reason
198
+
199
+ # The signing location. If used, will be set on the signature object.
200
+ attr_accessor :location
201
+
202
+ # The contact information. If used, will be set on the signature object.
203
+ attr_accessor :contact_info
204
+
205
+ # The size of the serialized signature that should be reserved.
206
+ #
207
+ # If this attribute has not been set, an empty string will be signed using #sign to
208
+ # determine the signature size.
209
+ #
210
+ # The size needs to be at least as big as the final signature, otherwise signing results in
211
+ # an error.
212
+ attr_writer :signature_size
213
+
214
+ # The type of signature to be written (i.e. the value of the /SubFilter key).
215
+ #
216
+ # The value can either be :cms (the default; uses a detached CMS signature) or :pades
217
+ # (uses an ETSI CAdES compatible signature).
218
+ attr_accessor :signature_type
219
+
220
+ # The DocMDP permissions that should be set on the document.
221
+ #
222
+ # See #doc_mdp_permissions=
223
+ attr_reader :doc_mdp_permissions
224
+
225
+ # Creates a new DefaultHandler instance with the given attributes.
226
+ def initialize(**arguments)
227
+ @signature_size = nil
228
+ @signature_type = :cms
229
+ arguments.each {|name, value| send("#{name}=", value) }
230
+ end
231
+
232
+ # Sets the DocMDP permissions that should be applied to the document.
233
+ #
234
+ # Valid values for +permissions+ are:
235
+ #
236
+ # +nil+::
237
+ # Don't set any DocMDP permissions (default).
238
+ #
239
+ # +:no_changes+ or 1::
240
+ # No changes whatsoever are allowed.
241
+ #
242
+ # +:form_filling+ or 2::
243
+ # Only filling in forms and signing are allowed.
244
+ #
245
+ # +:form_filling_and_annotations+ or 3::
246
+ # Only filling in forms, signing and annotation creation/deletion/modification are
247
+ # allowed.
248
+ def doc_mdp_permissions=(permissions)
249
+ case permissions
250
+ when :no_changes, 1 then @doc_mdp_permissions = 1
251
+ when :form_filling, 2 then @doc_mdp_permissions = 2
252
+ when :form_filling_and_annotations, 3 then @doc_mdp_permissions = 3
253
+ when nil then @doc_mdp_permissions = nil
254
+ else
255
+ raise ArgumentError, "Invalid permissions value '#{permissions.inspect}'"
256
+ end
257
+ end
258
+
259
+ # Returns the size of the serialized signature that should be reserved.
260
+ #
261
+ # If a custom size is set using #signature_size=, it used. Otherwise the size is determined
262
+ # by using #sign to sign an empty string.
263
+ def signature_size
264
+ @signature_size || sign(StringIO.new, [0, 0, 0, 0]).size
265
+ end
266
+
267
+ # Finalizes the signature field as well as the signature dictionary before writing.
268
+ def finalize_objects(_signature_field, signature)
269
+ signature[:Filter] = :'Adobe.PPKLite'
270
+ signature[:SubFilter] = (signature_type == :pades ? :'ETSI.CAdES.detached' : :'adbe.pkcs7.detached')
271
+ signature[:M] = Time.now
272
+ signature[:Reason] = reason if reason
273
+ signature[:Location] = location if location
274
+ signature[:ContactInfo] = contact_info if contact_info
275
+ signature[:Prop_Build] = {App: {Name: :HexaPDF, REx: HexaPDF::VERSION}}
276
+
277
+ if doc_mdp_permissions
278
+ doc = signature.document
279
+ if doc.signatures.count > 1
280
+ raise HexaPDF::Error, "Can set DocMDP access permissions only on first signature"
281
+ end
282
+ params = doc.add({Type: :TransformParams, V: :'1.2', P: doc_mdp_permissions})
283
+ sigref = doc.add({Type: :SigRef, TransformMethod: :DocMDP, DigestMethod: :SHA256,
284
+ TransformParams: params})
285
+ signature[:Reference] = [sigref]
286
+ (doc.catalog[:Perms] ||= {})[:DocMDP] = signature
287
+ end
288
+ end
289
+
290
+ # Returns the DER serialized CMS signed data object containing the signature for the given
291
+ # IO byte ranges.
292
+ #
293
+ # The +byte_range+ argument is an array containing four numbers [offset1, length1, offset2,
294
+ # length2]. The offset numbers are byte positions in the +io+ argument and the to-be-signed
295
+ # data can be determined by reading length bytes at the offsets.
296
+ def sign(io, byte_range)
297
+ if certificate
298
+ io.pos = byte_range[0]
299
+ data = io.read(byte_range[1])
300
+ io.pos = byte_range[2]
301
+ data << io.read(byte_range[3])
302
+ SignedDataCreator.create(data,
303
+ type: signature_type,
304
+ certificate: certificate, key: key,
305
+ digest_algorithm: digest_algorithm,
306
+ timestamp_handler: timestamp_handler,
307
+ certificates: certificate_chain, &external_signing).to_der
308
+ else
309
+ external_signing.call(io, byte_range)
310
+ end
311
+ end
312
+
313
+ end
314
+
315
+ end
316
+ end
317
+ end