hexapdf 0.28.0 → 0.29.0

Sign up to get free protection for your applications and to get access to all the features.
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