hexapdf 0.28.0 → 0.29.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 (40) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +41 -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 +6 -2
  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.rb +21 -14
  18. data/lib/hexapdf/encryption/standard_security_handler.rb +2 -1
  19. data/lib/hexapdf/type.rb +0 -1
  20. data/lib/hexapdf/version.rb +1 -1
  21. data/test/hexapdf/{type/signature → digital_signature}/common.rb +31 -3
  22. data/test/hexapdf/digital_signature/signing/test_default_handler.rb +162 -0
  23. data/test/hexapdf/digital_signature/signing/test_signed_data_creator.rb +225 -0
  24. data/test/hexapdf/digital_signature/signing/test_timestamp_handler.rb +88 -0
  25. data/test/hexapdf/{type/signature/test_adbe_pkcs7_detached.rb → digital_signature/test_cms_handler.rb} +7 -7
  26. data/test/hexapdf/{type/signature → digital_signature}/test_handler.rb +4 -4
  27. data/test/hexapdf/{type/signature/test_adbe_x509_rsa_sha1.rb → digital_signature/test_pkcs1_handler.rb} +3 -3
  28. data/test/hexapdf/{type → digital_signature}/test_signature.rb +7 -7
  29. data/test/hexapdf/digital_signature/test_signatures.rb +137 -0
  30. data/test/hexapdf/digital_signature/test_signing.rb +53 -0
  31. data/test/hexapdf/{type/signature → digital_signature}/test_verification_result.rb +7 -7
  32. data/test/hexapdf/test_dictionary_fields.rb +2 -1
  33. data/test/hexapdf/test_document.rb +1 -1
  34. data/test/hexapdf/test_writer.rb +3 -3
  35. metadata +25 -15
  36. data/lib/hexapdf/document/signatures.rb +0 -546
  37. data/lib/hexapdf/type/signature/adbe_pkcs7_detached.rb +0 -135
  38. data/lib/hexapdf/type/signature/adbe_x509_rsa_sha1.rb +0 -95
  39. data/lib/hexapdf/type/signature/handler.rb +0 -140
  40. 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