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
@@ -1,546 +0,0 @@
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 'net/http'
39
- require 'hexapdf/error'
40
- require 'stringio'
41
-
42
- module HexaPDF
43
- class Document
44
-
45
- # This class provides methods for interacting with digital signatures of a PDF file.
46
- class Signatures
47
-
48
- # This is the default signing handler which provides the ability to sign a document with the
49
- # adbe.pkcs7.detached or ETSI.CAdES.detached algorithms. It is registered under the :default
50
- # name.
51
- #
52
- # == Usage
53
- #
54
- # The signing handler is used by default by all methods that need a signing handler. Therefore
55
- # it is usually only necessary to provide the actual attribute values.
56
- #
57
- # This handler provides two ways to create the PKCS#7 signed-data structure required by
58
- # Signatures#add:
59
- #
60
- # * By providing the signing certificate together with the signing key and the certificate
61
- # chain. This way HexaPDF itself does the signing. It is the preferred way if all the needed
62
- # information is available.
63
- #
64
- # Assign the respective data to the #certificate, #key and #certificate_chain attributes.
65
- #
66
- # * By using an external signing mechanism. Here the actual signing happens "outside" of
67
- # HexaPDF, for example, in custom code or even asynchronously. This is needed in case the
68
- # signing certificate plus key are not directly available but only an interface to them
69
- # (e.g. when dealing with a HSM).
70
- #
71
- # Assign a callable object to #external_signing. If the signing process needs to be
72
- # asynchronous, make sure to set the #signature_size appropriately, return an empty string
73
- # during signing and later use Signatures.embed_signature to embed the actual signature.
74
- #
75
- # Additional functionality:
76
- #
77
- # * Optionally setting the reason, location and contact information.
78
- # * Making the signature a certification signature by applying the DocMDP transform method.
79
- #
80
- # Example:
81
- #
82
- # # Signing using certificate + key
83
- # document.sign("output.pdf", certificate: my_cert, key: my_key,
84
- # certificate_chain: my_chain)
85
- #
86
- # # Signing using an external mechanism:
87
- # signing_proc = lambda do |io, byte_range|
88
- # io.pos = byte_range[0]
89
- # data = io.read(byte_range[1])
90
- # io.pos = byte_range[2]
91
- # data << io.read(byte_range[3])
92
- # signing_service.pkcs7_sign(data)
93
- # end
94
- # document.sign("output.pdf", signature_size: 10_000, external_signing: signing_proc)
95
- #
96
- # == Implementing a Signing Handler
97
- #
98
- # This class also serves as an example on how to create a custom handler: The public methods
99
- # #signature_size, #finalize_objects and #sign are used by the digital signature algorithm.
100
- # See their descriptions for details.
101
- #
102
- # Once a custom signing handler has been created, it can be registered under the
103
- # 'signature.signing_handler' configuration option for easy use. It has to take keyword
104
- # arguments in its initialize method to be compatible with the Signatures#handler method.
105
- class DefaultHandler
106
-
107
- # The certificate with which to sign the PDF.
108
- attr_accessor :certificate
109
-
110
- # The private key for the #certificate.
111
- attr_accessor :key
112
-
113
- # The certificate chain that should be embedded in the PDF; normally contains all
114
- # certificates up to the root certificate.
115
- attr_accessor :certificate_chain
116
-
117
- # A callable object fulfilling the same role as the #sign method that is used instead of the
118
- # default mechanism for signing.
119
- #
120
- # If this attribute is set, the attributes #certificate, #key and #certificate_chain are not
121
- # used.
122
- attr_accessor :external_signing
123
-
124
- # The reason for signing. If used, will be set on the signature object.
125
- attr_accessor :reason
126
-
127
- # The signing location. If used, will be set on the signature object.
128
- attr_accessor :location
129
-
130
- # The contact information. If used, will be set on the signature object.
131
- attr_accessor :contact_info
132
-
133
- # The size of the serialized signature that should be reserved.
134
- #
135
- # If this attribute has not been set, an empty string will be signed using #sign to
136
- # determine the signature size.
137
- #
138
- # The size needs to be at least as big as the final signature, otherwise signing results in
139
- # an error.
140
- attr_writer :signature_size
141
-
142
- # The type of signature to be written (i.e. the value of the /SubFilter key).
143
- #
144
- # The value can either be :adobe (the default; uses a detached PKCS7 signature) or :etsi
145
- # (uses an ETSI CAdES compatible signature).
146
- attr_accessor :signature_type
147
-
148
- # The DocMDP permissions that should be set on the document.
149
- #
150
- # See #doc_mdp_permissions=
151
- attr_reader :doc_mdp_permissions
152
-
153
- # Creates a new DefaultHandler with the given attributes.
154
- def initialize(**arguments)
155
- @signature_size = nil
156
- arguments.each {|name, value| send("#{name}=", value) }
157
- end
158
-
159
- # Sets the DocMDP permissions that should be applied to the document.
160
- #
161
- # Valid values for +permissions+ are:
162
- #
163
- # +nil+::
164
- # Don't set any DocMDP permissions (default).
165
- #
166
- # +:no_changes+ or 1::
167
- # No changes whatsoever are allowed.
168
- #
169
- # +:form_filling+ or 2::
170
- # Only filling in forms and signing are allowed.
171
- #
172
- # +:form_filling_and_annotations+ or 3::
173
- # Only filling in forms, signing and annotation creation/deletion/modification are
174
- # allowed.
175
- def doc_mdp_permissions=(permissions)
176
- case permissions
177
- when :no_changes, 1 then @doc_mdp_permissions = 1
178
- when :form_filling, 2 then @doc_mdp_permissions = 2
179
- when :form_filling_and_annotations, 3 then @doc_mdp_permissions = 3
180
- when nil then @doc_mdp_permissions = nil
181
- else
182
- raise ArgumentError, "Invalid permissions value '#{permissions.inspect}'"
183
- end
184
- end
185
-
186
- # Returns the size of the serialized signature that should be reserved.
187
- #
188
- # If a custom size is set using #signature_size=, it used. Otherwise the size is determined
189
- # by using #sign to sign an empty string.
190
- def signature_size
191
- @signature_size || sign(StringIO.new, [0, 0, 0, 0]).size
192
- end
193
-
194
- # Finalizes the signature field as well as the signature dictionary before writing.
195
- def finalize_objects(_signature_field, signature)
196
- signature[:SubFilter] = :'ETSI.CAdES.detached' if signature_type == :etsi
197
- signature[:Reason] = reason if reason
198
- signature[:Location] = location if location
199
- signature[:ContactInfo] = contact_info if contact_info
200
-
201
- if doc_mdp_permissions
202
- doc = signature.document
203
- if doc.signatures.count > 1
204
- raise HexaPDF::Error, "Can set DocMDP access permissions only on first signature"
205
- end
206
- params = doc.add({Type: :TransformParams, V: :'1.2', P: doc_mdp_permissions})
207
- sigref = doc.add({Type: :SigRef, TransformMethod: :DocMDP, DigestMethod: :SHA1,
208
- TransformParams: params})
209
- signature[:Reference] = [sigref]
210
- (doc.catalog[:Perms] ||= {})[:DocMDP] = signature
211
- end
212
- end
213
-
214
- # Returns the DER serialized OpenSSL::PKCS7 structure containing the signature for the given
215
- # IO byte ranges.
216
- #
217
- # The +byte_range+ argument is an array containing four numbers [offset1, length1, offset2,
218
- # length2]. The offset numbers are byte positions in the +io+ argument and the to-be-signed
219
- # data can be determined by reading length bytes at the offsets.
220
- def sign(io, byte_range)
221
- if external_signing
222
- external_signing.call(io, byte_range)
223
- else
224
- io.pos = byte_range[0]
225
- data = io.read(byte_range[1])
226
- io.pos = byte_range[2]
227
- data << io.read(byte_range[3])
228
- OpenSSL::PKCS7.sign(@certificate, @key, data, @certificate_chain,
229
- OpenSSL::PKCS7::DETACHED | OpenSSL::PKCS7::BINARY).to_der
230
- end
231
- end
232
-
233
- end
234
-
235
- # This is a signing handler for adding a timestamp signature (a PDF2.0 feature) to a PDF
236
- # document. It is registered under the :timestamp name.
237
- #
238
- # The timestamp is provided by a timestamp authority and establishes the document contents at
239
- # the time indicated in the timestamp. Timestamping a PDF document is usually done in context
240
- # of long term validation but can also be done standalone.
241
- #
242
- # == Usage
243
- #
244
- # It is necessary to provide at least the URL of the timestamp authority server (TSA) via
245
- # #tsa_url, everything else is optional and uses default values. The TSA server must not use
246
- # authentication to be usable.
247
- #
248
- # Example:
249
- #
250
- # document.sign("output.pdf", handler: :timestamp, tsa_url: 'https://freetsa.org/tsr')
251
- class TimestampHandler
252
-
253
- # The URL of the timestamp authority server.
254
- #
255
- # This value is required.
256
- attr_accessor :tsa_url
257
-
258
- # The hash algorithm to use for timestamping. Defaults to SHA512.
259
- attr_accessor :tsa_hash_algorithm
260
-
261
- # The policy OID to use for timestamping. Defaults to +nil+.
262
- attr_accessor :tsa_policy_id
263
-
264
- # The size of the serialized signature that should be reserved.
265
- #
266
- # If this attribute has not been set, an empty string will be signed using #sign to
267
- # determine the signature size which will contact the TSA server
268
- #
269
- # The size needs to be at least as big as the final signature, otherwise signing results in
270
- # an error.
271
- attr_writer :signature_size
272
-
273
- # The reason for timestamping. If used, will be set on the signature object.
274
- attr_accessor :reason
275
-
276
- # The timestamping location. If used, will be set on the signature object.
277
- attr_accessor :location
278
-
279
- # The contact information. If used, will be set on the signature object.
280
- attr_accessor :contact_info
281
-
282
- # Creates a new TimestampHandler with the given attributes.
283
- def initialize(**arguments)
284
- @signature_size = nil
285
- arguments.each {|name, value| send("#{name}=", value) }
286
- end
287
-
288
- # Returns the size of the serialized signature that should be reserved.
289
- def signature_size
290
- @signature_size || (sign(StringIO.new, [0, 0, 0, 0]).size * 1.5).to_i
291
- end
292
-
293
- # Finalizes the signature field as well as the signature dictionary before writing.
294
- def finalize_objects(_signature_field, signature)
295
- signature.document.version = '2.0'
296
- signature[:Type] = :DocTimeStamp
297
- signature[:SubFilter] = :'ETSI.RFC3161'
298
- signature[:Reason] = reason if reason
299
- signature[:Location] = location if location
300
- signature[:ContactInfo] = contact_info if contact_info
301
- end
302
-
303
- # Returns the DER serialized OpenSSL::PKCS7 structure containing the timestamp token for the
304
- # given IO byte ranges.
305
- def sign(io, byte_range)
306
- hash_algorithm = tsa_hash_algorithm || 'SHA512'
307
- digest = OpenSSL::Digest.new(hash_algorithm)
308
- io.pos = byte_range[0]
309
- digest << io.read(byte_range[1])
310
- io.pos = byte_range[2]
311
- digest << io.read(byte_range[3])
312
-
313
- req = OpenSSL::Timestamp::Request.new
314
- req.algorithm = hash_algorithm
315
- req.message_imprint = digest.digest
316
- req.policy_id = tsa_policy_id if tsa_policy_id
317
-
318
- http_response = Net::HTTP.post(URI(tsa_url), req.to_der,
319
- 'content-type' => 'application/timestamp-query')
320
- if http_response.kind_of?(Net::HTTPOK)
321
- response = OpenSSL::Timestamp::Response.new(http_response.body)
322
- if response.status == 0
323
- response.token.to_der
324
- else
325
- raise HexaPDF::Error, "Timestamp token could not be created: #{response.failure_info}"
326
- end
327
- else
328
- raise HexaPDF::Error, "Invalid TSA server response: #{http_response.body}"
329
- end
330
- end
331
-
332
- end
333
-
334
- # Embeds the given +signature+ into the /Contents value of the newest signature dictionary of
335
- # the PDF document given by the +io+ argument.
336
- #
337
- # This functionality can be used together with the support for external signing (see
338
- # DefaultHandler and DefaultHandler#external_signing) to implement asynchronous signing.
339
- #
340
- # Note: This will, most probably, only work on documents prepared for external signing by
341
- # HexaPDF and not by other libraries.
342
- def self.embed_signature(io, signature)
343
- doc = HexaPDF::Document.new(io: io)
344
- signature_dict = doc.signatures.find {|sig| doc.revisions.current.object(sig) == sig }
345
- signature_dict_offset, signature_dict_length = locate_signature_dict(
346
- doc.revisions.current.xref_section,
347
- doc.revisions.parser.startxref_offset,
348
- signature_dict.oid
349
- )
350
- io.pos = signature_dict_offset
351
- signature_data = io.read(signature_dict_length)
352
- replace_signature_contents(signature_data, signature)
353
- io.pos = signature_dict_offset
354
- io.write(signature_data)
355
- end
356
-
357
- # Uses the information in the given cross-reference section as well as the byte offset of the
358
- # cross-reference section to calculate the offset and length of the signature dictionary with
359
- # the given object id.
360
- def self.locate_signature_dict(xref_section, start_xref_position, signature_oid)
361
- data = xref_section.map {|oid, _gen, entry| [entry.pos, oid] if entry.in_use? }.compact.sort <<
362
- [start_xref_position, nil]
363
- index = data.index {|_pos, oid| oid == signature_oid }
364
- [data[index][0], data[index + 1][0] - data[index][0]]
365
- end
366
-
367
- # Replaces the value of the /Contents key in the serialized +signature_data+ with the value of
368
- # +contents+.
369
- def self.replace_signature_contents(signature_data, contents)
370
- signature_data.sub!(/Contents(?:\(.*?\)|<.*?>)/) do |match|
371
- length = match.size
372
- result = "Contents<#{contents.unpack1('H*')}"
373
- if length < result.size
374
- raise HexaPDF::Error, "The reserved space for the signature was too small " \
375
- "(#{(length - 10) / 2} vs #{(result.size - 10) / 2}) - use the handlers " \
376
- "#signature_size method to increase the reserved space"
377
- end
378
- "#{result.ljust(length - 1, '0')}>"
379
- end
380
- end
381
-
382
- include Enumerable
383
-
384
- # Creates a new Signatures object for the given PDF document.
385
- def initialize(document)
386
- @document = document
387
- end
388
-
389
- # Creates a signing handler with the given attributes and returns it.
390
- #
391
- # A signing handler name is mapped to a class via the 'signature.signing_handler'
392
- # configuration option. The default signing handler is DefaultHandler.
393
- def handler(name: :default, **attributes)
394
- handler = @document.config.constantize('signature.signing_handler', name) do
395
- raise HexaPDF::Error, "No signing handler named '#{name}' is available"
396
- end
397
- handler.new(**attributes)
398
- end
399
-
400
- # Adds a signature to the document and returns the corresponding signature object.
401
- #
402
- # This method will add a new signature to the document and write the updated document to the
403
- # given file or IO stream. Afterwards the document can't be modified anymore and still retain
404
- # a correct digital signature. To modify the signed document (e.g. for adding another
405
- # signature) create a new document based on the given file or IO stream instead.
406
- #
407
- # +signature+::
408
- # Can either be a signature object (determined via the /Type key), a signature field or
409
- # +nil+. Providing a signature object or signature field provides for more control, e.g.:
410
- #
411
- # * Setting values for optional signature object fields like /Reason and /Location.
412
- # * (In)directly specifying which signature field should be used.
413
- #
414
- # If a signature object is provided and it is not associated with an AcroForm signature
415
- # field, a new signature field is created and added to the main AcroForm object, creating
416
- # that if necessary.
417
- #
418
- # If a signature field is provided and it already has a signature object as field value,
419
- # that signature object is discarded.
420
- #
421
- # If the signature field doesn't have a widget, a non-visible one is created on the first
422
- # page.
423
- #
424
- # +handler+::
425
- # The signing handler that provides the necessary methods for signing and adjusting the
426
- # signature and signature field objects to one's liking, see #handler and DefaultHandler.
427
- #
428
- # +write_options+::
429
- # The key-value pairs of this hash will be passed on to the HexaPDF::Document#write
430
- # method. Note that +incremental+ will be automatically set to ensure proper behaviour.
431
- #
432
- # The used signature object will have the following default values set:
433
- #
434
- # /Filter:: /Adobe.PPKLite
435
- # /SubFilter:: /adbe.pkcs7.detached
436
- # /M:: The current time.
437
- #
438
- # These values can be overridden in the #finalize_objects method of the signature handler.
439
- def add(file_or_io, handler, signature: nil, write_options: {})
440
- if signature && signature.type != :Sig
441
- signature_field = signature
442
- signature = signature_field.field_value
443
- end
444
- signature ||= @document.add({Type: :Sig})
445
-
446
- # Prepare AcroForm
447
- form = @document.acro_form(create: true)
448
- form.signature_flag(:signatures_exist, :append_only)
449
-
450
- # Prepare signature field
451
- signature_field ||= form.each_field.find {|field| field.field_value == signature } ||
452
- form.create_signature_field(generate_field_name)
453
- signature_field.field_value = signature
454
-
455
- if signature_field.each_widget.to_a.empty?
456
- signature_field.create_widget(@document.pages[0], Rect: [0, 0, 0, 0])
457
- end
458
-
459
- # Prepare signature object
460
- signature[:Filter] = :'Adobe.PPKLite'
461
- signature[:SubFilter] = :'adbe.pkcs7.detached'
462
- signature[:M] = Time.now
463
- handler.finalize_objects(signature_field, signature)
464
- signature[:ByteRange] = [0, 1_000_000_000_000, 1_000_000_000_000, 1_000_000_000_000]
465
- signature[:Contents] = '00' * handler.signature_size # twice the size due to hex encoding
466
-
467
- io = if file_or_io.kind_of?(String)
468
- File.open(file_or_io, 'wb+')
469
- else
470
- file_or_io
471
- end
472
-
473
- # Save the current state so that we can determine the correct /ByteRange value and set the
474
- # values
475
- start_xref, section = @document.write(io, incremental: true, **write_options)
476
- signature_offset, signature_length = self.class.locate_signature_dict(section, start_xref,
477
- signature.oid)
478
- io.pos = signature_offset
479
- signature_data = io.read(signature_length)
480
-
481
- io.seek(0, IO::SEEK_END)
482
- file_size = io.pos
483
-
484
- # Calculate the offsets for the /ByteRange
485
- contents_offset = signature_offset + signature_data.index('Contents(') + 8
486
- offset2 = contents_offset + signature[:Contents].size + 2 # +2 because of the needed < and >
487
- length2 = file_size - offset2
488
- signature[:ByteRange] = [0, contents_offset, offset2, length2]
489
-
490
- # Set the correct /ByteRange value
491
- signature_data.sub!(/ByteRange\[0 1000000000000 1000000000000 1000000000000\]/) do |match|
492
- length = match.size
493
- result = "ByteRange[0 #{contents_offset} #{offset2} #{length2}]"
494
- result.ljust(length)
495
- end
496
-
497
- # Now everything besides the /Contents value is correct, so we can read the contents for
498
- # signing
499
- io.pos = signature_offset
500
- io.write(signature_data)
501
- signature[:Contents] = handler.sign(io, signature[:ByteRange].value)
502
-
503
- # And now replace the /Contents value
504
- self.class.replace_signature_contents(signature_data, signature[:Contents])
505
- io.pos = signature_offset
506
- io.write(signature_data)
507
-
508
- signature
509
- ensure
510
- io.close if io && io != file_or_io
511
- end
512
-
513
- # :call-seq:
514
- # signatures.each {|signature| block } -> signatures
515
- # signatures.each -> Enumerator
516
- #
517
- # Iterates over all signatures in the order they are found.
518
- def each
519
- return to_enum(__method__) unless block_given?
520
-
521
- return [] unless (form = @document.acro_form)
522
- form.each_field do |field|
523
- yield(field.field_value) if field.field_type == :Sig && field.field_value
524
- end
525
- end
526
-
527
- # Returns the number of signatures in the PDF document. May be zero if the document has no
528
- # signatures.
529
- def count
530
- each.to_a.size
531
- end
532
-
533
- private
534
-
535
- # Generates a field name for a signature field.
536
- def generate_field_name
537
- index = (@document.acro_form.each_field.
538
- map {|field| field.full_field_name.scan(/\ASignature(\d+)/).first&.first.to_i }.
539
- max || 0) + 1
540
- "Signature#{index}"
541
- end
542
-
543
- end
544
-
545
- end
546
- end
@@ -1,135 +0,0 @@
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/type/signature'
39
-
40
- module HexaPDF
41
- module Type
42
- class Signature
43
-
44
- # The signature handler for the adbe.pkcs7.detached sub-filter.
45
- class AdbePkcs7Detached < Handler
46
-
47
- # Creates a new signature handler for the given signature dictionary.
48
- def initialize(signature_dict)
49
- super
50
- @pkcs7 = OpenSSL::PKCS7.new(signature_dict.contents)
51
- end
52
-
53
- # Returns the common name of the signer.
54
- def signer_name
55
- signer_certificate.subject.to_a.assoc("CN")&.[](1) || super
56
- end
57
-
58
- # Returns the time of signing.
59
- def signing_time
60
- signer_info.signed_time rescue super
61
- end
62
-
63
- # Returns the certificate chain.
64
- def certificate_chain
65
- @pkcs7.certificates
66
- end
67
-
68
- # Returns the signer certificate (an instance of OpenSSL::X509::Certificate).
69
- def signer_certificate
70
- info = signer_info
71
- certificate_chain.find {|cert| cert.issuer == info.issuer && cert.serial == info.serial }
72
- end
73
-
74
- # Returns the signer information object (an instance of OpenSSL::PKCS7::SignerInfo).
75
- def signer_info
76
- @pkcs7.signers.first
77
- end
78
-
79
- # Verifies the signature using the provided OpenSSL::X509::Store object.
80
- def verify(store, allow_self_signed: false)
81
- result = super
82
-
83
- signer_info = self.signer_info
84
- signer_certificate = self.signer_certificate
85
- certificate_chain = self.certificate_chain
86
-
87
- if certificate_chain.empty?
88
- result.log(:error, "No certificates found in signature")
89
- return result
90
- end
91
-
92
- if @pkcs7.signers.size != 1
93
- result.log(:error, "Exactly one signer needed, found #{@pkcs7.signers.size}")
94
- end
95
-
96
- unless signer_certificate
97
- result.log(:error, "Signer serial=#{signer_info.serial} issuer=#{signer_info.issuer} " \
98
- "not found in certificates stored in PKCS7 object")
99
- return result
100
- end
101
-
102
- key_usage = signer_certificate.extensions.find {|ext| ext.oid == 'keyUsage' }
103
- unless key_usage && key_usage.value.split(', ').include?("Digital Signature")
104
- result.log(:error, "Certificate key usage is missing 'Digital Signature'")
105
- end
106
-
107
- if signature_dict.signature_type == 'ETSI.RFC3161'
108
- # Getting the needed values is not directly supported by Ruby OpenSSL
109
- p7 = OpenSSL::ASN1.decode(signature_dict.contents.sub(/\x00*\z/, ''))
110
- signed_data = p7.value[1].value[0]
111
- content_info = signed_data.value[2]
112
- content = OpenSSL::ASN1.decode(content_info.value[1].value[0].value)
113
- digest_algorithm = content.value[2].value[0].value[0].value
114
- original_hash = content.value[2].value[1].value
115
- recomputed_hash = OpenSSL::Digest.digest(digest_algorithm, signature_dict.signed_data)
116
- hash_valid = (original_hash == recomputed_hash)
117
- else
118
- data = signature_dict.signed_data
119
- hash_valid = true # hash will be checked by @pkcs7.verify
120
- end
121
- if hash_valid && @pkcs7.verify(certificate_chain, store, data,
122
- OpenSSL::PKCS7::DETACHED | OpenSSL::PKCS7::BINARY)
123
- result.log(:info, "Signature valid")
124
- else
125
- result.log(:error, "Signature verification failed")
126
- end
127
-
128
- result
129
- end
130
-
131
- end
132
-
133
- end
134
- end
135
- end