hexapdf 0.19.1 → 0.19.2

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 496c87d6cffcfde7b155fbfcdd78673fb34931d4d0fd997bcee934decfba175d
4
- data.tar.gz: ce72598c9d33735c6ea4738c072b1f00202dc7bac24577c9c27a062add38ea8e
3
+ metadata.gz: cb1a2ebe9b89b1c97bcfe6a9227bbdc9b8627458900347ac0965784f4d95f83f
4
+ data.tar.gz: 294eeaa8e9d9926debf2d28d83c8cd419f5685a88b9d23c378fa783fce2d62cb
5
5
  SHA512:
6
- metadata.gz: cee5a6f86084490ba5602c40ec48945b9a3c0bdecb22fe03e2d7b8b6ad02c791900f9fd7354557a9ac3dfdebb4a8d95c7335fc329c0054156082853f33717144
7
- data.tar.gz: eaed67cce68e703eddbbb357e790a9f0bc62083ff8ba0b0856aa360d499f75e0e022b1ccd41ebcdd53e2fe269a93b5f587c47db41311c3f6a0beb9ed293e02cd
6
+ metadata.gz: e2284d3397a856d895b8ec2c83b3ead95ba11a571df90f9fdaed7cd83e4880004ea04150df9205a534d203ae5a1958977a7755ad9f06d622c4f38ae43c0823ab
7
+ data.tar.gz: 2d6b454f78f7b319240fc062429c32f87ed350ab64a596e7b5050abe65b4e8b4bd9c3551cb87a699219fbd484b5c7670c9662674d9aa5dc8ef800dbe70a273c2
data/CHANGELOG.md CHANGED
@@ -1,3 +1,13 @@
1
+ ## 0.19.2 - 2021-12-14
2
+
3
+ ### Fixed
4
+
5
+ * Set the trailer's ID field to an array of two empty strings when decrypting in
6
+ case it is missing
7
+ * Incremental writing when one of the existing revisions contains a
8
+ cross-reference stream
9
+
10
+
1
11
  ## 0.19.1 - 2021-12-12
2
12
 
3
13
  ### Added
@@ -328,8 +328,7 @@ module HexaPDF
328
328
  raise(HexaPDF::UnsupportedEncryptionError,
329
329
  "Invalid /R value for standard security handler")
330
330
  elsif dict[:R] <= 4 && !document.trailer[:ID].kind_of?(PDFArray)
331
- raise(HexaPDF::EncryptionError,
332
- "Document ID for needed for decryption")
331
+ document.trailer[:ID] = ['', '']
333
332
  end
334
333
  @trailer_id_hash = trailer_id_hash
335
334
 
@@ -62,9 +62,15 @@ module HexaPDF
62
62
  @object_stream_data = {}
63
63
  @reconstructed_revision = nil
64
64
  @in_reconstruct_revision = false
65
+ @contains_xref_streams = false
65
66
  retrieve_pdf_header_offset_and_version
66
67
  end
67
68
 
69
+ # Returns +true+ if the PDF file contains cross-reference streams.
70
+ def contains_xref_streams?
71
+ @contains_xref_streams
72
+ end
73
+
68
74
  # Loads the indirect (potentially compressed) object specified by the given cross-reference
69
75
  # entry.
70
76
  #
@@ -230,6 +236,7 @@ module HexaPDF
230
236
  maybe_raise("Cross-reference stream doesn't contain entry for itself", pos: pos)
231
237
  xref_section.add_in_use_entry(obj.oid, obj.gen, pos)
232
238
  end
239
+ @contains_xref_streams = true
233
240
  end
234
241
  xref_section.delete(0)
235
242
  [xref_section, trailer]
@@ -37,6 +37,6 @@
37
37
  module HexaPDF
38
38
 
39
39
  # The version of HexaPDF.
40
- VERSION = '0.19.1'
40
+ VERSION = '0.19.2'
41
41
 
42
42
  end
@@ -66,6 +66,8 @@ module HexaPDF
66
66
  @serializer = Serializer.new
67
67
  @serializer.encrypter = @document.encrypted? ? @document.security_handler : nil
68
68
  @rev_size = 0
69
+
70
+ @use_xref_streams = false
69
71
  end
70
72
 
71
73
  # Writes the document to the IO object.
@@ -87,6 +89,7 @@ module HexaPDF
87
89
  IO.copy_stream(@document.revisions.parser.io, @io)
88
90
 
89
91
  @rev_size = @document.revisions.current.next_free_oid
92
+ @use_xref_streams = @document.revisions.parser.contains_xref_streams?
90
93
 
91
94
  revision = Revision.new(@document.revisions.current.trailer)
92
95
  @document.revisions.each do |rev|
@@ -170,10 +173,13 @@ module HexaPDF
170
173
  end
171
174
  end
172
175
 
173
- if !object_streams.empty? && xref_stream.nil?
174
- raise HexaPDF::Error, "Cannot use object streams when there is no xref stream"
176
+ if (!object_streams.empty? || @use_xref_streams) && xref_stream.nil?
177
+ xref_stream = @document.wrap({Type: :XRef}, oid: rev.next_free_oid)
178
+ rev.add(xref_stream)
175
179
  end
176
180
 
181
+ @use_xref_streams = true if xref_stream
182
+
177
183
  [xref_stream, object_streams]
178
184
  end
179
185
 
@@ -229,19 +229,21 @@ describe HexaPDF::Encryption::StandardSecurityHandler do
229
229
  assert_match(/Invalid \/R/i, exp.message)
230
230
  end
231
231
 
232
- it "fails if the ID in the document's trailer is missing although it is needed" do
232
+ it "fails if the supplied password is invalid" do
233
233
  exp = assert_raises(HexaPDF::EncryptionError) do
234
- @handler.set_up_decryption({Filter: :Standard, V: 2, R: 2})
234
+ @handler.set_up_decryption({Filter: :Standard, V: 2, R: 6, U: 'a' * 48, O: 'a' * 48,
235
+ UE: 'a' * 32, OE: 'a' * 32})
235
236
  end
236
- assert_match(/Document ID/i, exp.message)
237
+ assert_match(/Invalid password/i, exp.message)
237
238
  end
238
239
 
239
- it "fails if the supplied password is invalid" do
240
+ it "assigns empty strings to the trailer's ID field if it is missing" do
241
+ refute(@document.trailer.key?(:ID))
240
242
  exp = assert_raises(HexaPDF::EncryptionError) do
241
- @handler.set_up_decryption({Filter: :Standard, V: 2, R: 6, U: 'a' * 48, O: 'a' * 48,
242
- UE: 'a' * 32, OE: 'a' * 32})
243
+ @handler.set_up_decryption({Filter: :Standard, V: 1, R: 2, U: 'a' * 48, O: 'a' * 48, P: 15})
243
244
  end
244
245
  assert_match(/Invalid password/i, exp.message)
246
+ assert_equal(['', ''], @document.trailer[:ID].value)
245
247
  end
246
248
 
247
249
  describe "/Perms field checking" do
@@ -531,12 +531,14 @@ describe HexaPDF::Parser do
531
531
  xref_section, trailer = @parser.load_revision(@parser.startxref_offset)
532
532
  assert_equal({Test: 'now'}, trailer)
533
533
  assert(xref_section[1].in_use?)
534
+ refute(@parser.contains_xref_streams?)
534
535
  end
535
536
 
536
537
  it "works for a cross-reference stream" do
537
538
  xref_section, trailer = @parser.load_revision(212)
538
539
  assert_equal({Size: 2}, trailer)
539
540
  assert(xref_section[1].in_use?)
541
+ assert(@parser.contains_xref_streams?)
540
542
  end
541
543
 
542
544
  it "fails if another object is found instead of a cross-reference stream" do
@@ -40,7 +40,7 @@ describe HexaPDF::Writer do
40
40
  219
41
41
  %%EOF
42
42
  3 0 obj
43
- <</Producer(HexaPDF version 0.19.1)>>
43
+ <</Producer(HexaPDF version 0.19.2)>>
44
44
  endobj
45
45
  xref
46
46
  3 1
@@ -72,7 +72,7 @@ describe HexaPDF::Writer do
72
72
  141
73
73
  %%EOF
74
74
  6 0 obj
75
- <</Producer(HexaPDF version 0.19.1)>>
75
+ <</Producer(HexaPDF version 0.19.2)>>
76
76
  endobj
77
77
  2 0 obj
78
78
  <</Length 10>>stream
@@ -103,21 +103,50 @@ describe HexaPDF::Writer do
103
103
  assert_document_conversion(@compressed_input_io)
104
104
  end
105
105
 
106
- it "writes a document in incremental mode" do
107
- doc = HexaPDF::Document.new(io: @std_input_io)
108
- doc.pages.add
109
- output_io = StringIO.new
110
- HexaPDF::Writer.write(doc, output_io, incremental: true)
111
- assert_equal(output_io.string[0, @std_input_io.string.length], @std_input_io.string)
112
- doc = HexaPDF::Document.new(io: output_io)
113
- assert_equal(4, doc.revisions.size)
114
- assert_equal(2, doc.revisions.current.each.to_a.size)
106
+ describe "write_incremental" do
107
+ it "writes a document in incremental mode" do
108
+ doc = HexaPDF::Document.new(io: @std_input_io)
109
+ doc.pages.add
110
+ output_io = StringIO.new
111
+ HexaPDF::Writer.write(doc, output_io, incremental: true)
112
+ assert_equal(output_io.string[0, @std_input_io.string.length], @std_input_io.string)
113
+ doc = HexaPDF::Document.new(io: output_io)
114
+ assert_equal(4, doc.revisions.size)
115
+ assert_equal(2, doc.revisions.current.each.to_a.size)
116
+ end
117
+
118
+ it "uses an xref stream if the document already contains at least one" do
119
+ doc = HexaPDF::Document.new(io: @compressed_input_io)
120
+ doc.pages.add
121
+ output_io = StringIO.new
122
+ HexaPDF::Writer.write(doc, output_io, incremental: true)
123
+ refute_match(/^trailer/, output_io.string)
124
+ end
115
125
  end
116
126
 
117
- it "raises an error if no xref stream is in a revision but object streams are" do
127
+ it "creates an xref stream if no xref stream is in a revision but object streams are" do
118
128
  document = HexaPDF::Document.new
119
129
  document.add({Type: :ObjStm})
120
- assert_raises(HexaPDF::Error) { HexaPDF::Writer.new(document, StringIO.new).write }
130
+ HexaPDF::Writer.new(document, StringIO.new).write
131
+ assert(:XRef, document.object(2).type)
132
+ end
133
+
134
+ it "creates an xref stream if a previous revision had one" do
135
+ document = HexaPDF::Document.new
136
+ document.pages.add
137
+ document.revisions.add
138
+ document.pages.add
139
+ document.add({Type: :ObjStm})
140
+ document.revisions.add
141
+ document.pages.add
142
+ io = StringIO.new
143
+ HexaPDF::Writer.new(document, io).write
144
+
145
+ document = HexaPDF::Document.new(io: io)
146
+ assert_equal(3, document.revisions.count)
147
+ assert(document.revisions[0].none? {|obj| obj.type == :XRef })
148
+ assert(document.revisions[1].one? {|obj| obj.type == :XRef })
149
+ assert(document.revisions[2].one? {|obj| obj.type == :XRef })
121
150
  end
122
151
 
123
152
  it "raises an error if the class is misused and an xref section contains invalid entries" do
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hexapdf
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.19.1
4
+ version: 0.19.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Thomas Leitner
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-12-12 00:00:00.000000000 Z
11
+ date: 2021-12-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: cmdparse
@@ -254,7 +254,6 @@ files:
254
254
  - lib/hexapdf/document/fonts.rb
255
255
  - lib/hexapdf/document/images.rb
256
256
  - lib/hexapdf/document/pages.rb
257
- - lib/hexapdf/document/signatures.rb
258
257
  - lib/hexapdf/encryption.rb
259
258
  - lib/hexapdf/encryption/aes.rb
260
259
  - lib/hexapdf/encryption/arc4.rb
@@ -397,7 +396,6 @@ files:
397
396
  - lib/hexapdf/type/page.rb
398
397
  - lib/hexapdf/type/page_tree_node.rb
399
398
  - lib/hexapdf/type/resources.rb
400
- - lib/hexapdf/type/signature/adbe_pkcs7_detached.rb
401
399
  - lib/hexapdf/type/trailer.rb
402
400
  - lib/hexapdf/type/viewer_preferences.rb
403
401
  - lib/hexapdf/type/xref_stream.rb
@@ -1,221 +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-2021 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
-
39
- module HexaPDF
40
- class Document
41
-
42
- # This class provides methods for interacting with digital signatures of a PDF file.
43
- class Signatures
44
-
45
- # This is the default signing handler which provides the ability to sign a document with a
46
- # provided certificate using the adb.pkcs7.detached algorithm.
47
- class DefaultHandler
48
-
49
- # Creates a new DefaultHandler with the given signing certificate, the associated signing
50
- # key and an optional array of certificates that should also be present in the signature.
51
- def initialize(certificate, key, certificate_chain = [])
52
- @certificate = certificate
53
- @key = key
54
- @certificate_chain = certificate_chain
55
- end
56
-
57
- # Returns the name to be set on the /Filter key when using this signing handler.
58
- def filter_name
59
- :"Adobe.PPKLite"
60
- end
61
-
62
- # Returns the name to be set on the /SubFilter key when using this signing handler.
63
- def sub_filter_name
64
- :"adbe.pkcs7.detached"
65
- end
66
-
67
- # Returns the size of the signature that would be created.
68
- def signature_size
69
- sign("").size
70
- end
71
-
72
- # Returns the DER serialized OpenSSL::PKCS7 structure containing the signature for the given
73
- # data.
74
- def sign(data)
75
- OpenSSL::PKCS7.sign(@certificate, @key, data, @certificate_chain,
76
- OpenSSL::PKCS7::DETACHED | OpenSSL::PKCS7::BINARY).to_der
77
- end
78
-
79
- end
80
-
81
- include Enumerable
82
-
83
- # Creates a new Signatures object for the given PDF document.
84
- def initialize(document)
85
- @document = document
86
- end
87
-
88
- # Adds a signature to the document and returns the corresponding signature object.
89
- #
90
- # This method will add a new signature to the document and write the updated document to the
91
- # given file or IO stream. Afterwards the document can't be modified anymore and still retain
92
- # a correct digital signature; create a new document based on the file or IO stream instead.
93
- #
94
- # +signature+::
95
- # Can either be a signature object or +nil+. Providing a signature object provides for
96
- # more control, e.g.:
97
- #
98
- # * Setting values for optional fields like /Reason and /Location.
99
- # * Indirectly specifying which signature field should be used.
100
- #
101
- # If the +signature+ is not associated with an AcroForm signature field, a new signature
102
- # field is created and added to the main AcroForm object, creating that if necessary.
103
- #
104
- # If the associated signature field doesn't have a widget, a non-visible one is created on
105
- # the first page.
106
- #
107
- # +handler+::
108
- # The signature handler that provides the necessary methods for signing, see
109
- # DefaultHandler.
110
- #
111
- # +write_options+::
112
- # These options will be passed on to the HexaPDF::Document#write command. Note that
113
- # +incremental+ will be automatically set if signing an already existing file.
114
- def add(file_or_io, handler, signature: nil, **write_options)
115
- signature ||= @document.add({Type: :Sig})
116
- signature[:Filter] = handler.filter_name
117
- signature[:SubFilter] = handler.sub_filter_name
118
- signature[:ByteRange] = [0, 1_000_000_000_000, 1_000_000_000_000, 1_000_000_000_000]
119
- signature[:Contents] = '00' * handler.signature_size # twice the size due to hex encoding
120
-
121
- # Prepare signature field
122
- form = @document.acro_form(create: true)
123
- form.signature_flag(:signatures_exist)
124
-
125
- signature_field = each.find {|value| value == signature }
126
- unless signature_field
127
- signature_field = form.create_signature_field(generate_field_name)
128
- signature_field.field_value = signature
129
- end
130
-
131
- if signature_field.each_widget.to_a.empty?
132
- signature_field.create_widget(@document.pages[0], Rect: [0, 0, 0, 0])
133
- end
134
-
135
- io = if file_or_io.kind_of?(String)
136
- File.open(file_or_io, 'w+')
137
- else
138
- file_or_io
139
- end
140
-
141
- # Save the current state so that we can determine the correct /ByteRange value and set the
142
- # values
143
- section = @document.write(io, incremental: true, **write_options)
144
- data = section.map {|oid, _gen, entry| [entry.pos.to_i, oid] }.sort
145
- index = data.index {|_pos, oid| oid == signature.oid }
146
- signature_offset = data[index][0]
147
- signature_length = data[index + 1][0] - data[index][0]
148
- io.pos = signature_offset
149
- signature_data = io.read(signature_length)
150
-
151
- io.rewind
152
- file_data = io.read
153
-
154
- # Calculate the offsets for the /ByteRange
155
- contents_offset = signature_offset + signature_data.index('Contents(') + 8
156
- offset2 = contents_offset + signature[:Contents].size + 2 # +2 because of the needed < and >
157
- length2 = file_data.size - offset2
158
- signature[:ByteRange] = [0, contents_offset, offset2, length2]
159
-
160
- # Set the correct /ByteRange value
161
- signature_data.sub!(/ByteRange\[0 1000000000000 1000000000000 1000000000000\]/) do |match|
162
- length = match.size
163
- result = "ByteRange[0 #{contents_offset} #{offset2} #{length2}]"
164
- result.ljust(length)
165
- end
166
-
167
- # Now everything besides the /Contents value is correct, so we can read the contents for
168
- # signing
169
- file_data[signature_offset, signature_length] = signature_data
170
- signed_contents = file_data[0, contents_offset] << file_data[offset2, length2]
171
- signature[:Contents] = handler.sign(signed_contents)
172
-
173
- # Set the correct /Contents value as hexstring
174
- signature_data.sub!(/Contents\(0+\)/) do |match|
175
- length = match.size
176
- result = "Contents<#{signature[:Contents].unpack1('H*')}"
177
- "#{result.ljust(length - 1, '0')}>"
178
- end
179
-
180
- io.pos = signature_offset
181
- io.write(signature_data)
182
-
183
- signature
184
- ensure
185
- io.close if io && io != file_or_io
186
- end
187
-
188
- # :call-seq:
189
- # signatures.each {|signature| block } -> signatures
190
- # signatures.each -> Enumerator
191
- #
192
- # Iterates over all signatures in the order they are found.
193
- def each
194
- return to_enum(__method__) unless block_given?
195
-
196
- return [] unless (form = @document.acro_form)
197
- form.each_field do |field|
198
- yield(field.field_value) if field.field_type == :Sig && field.field_value
199
- end
200
- end
201
-
202
- # Returns the number of signatures in the PDF document. May be zero if the document has no
203
- # signatures.
204
- def count
205
- each.to_a.size
206
- end
207
-
208
- private
209
-
210
- # Generates a field name for a signature field.
211
- def generate_field_name
212
- index = (@document.acro_form.each_field.
213
- map {|field| field.full_field_name.scan(/\ASignature(\d+)/).first&.first.to_i }.
214
- max || 0) + 1
215
- "Signature#{index}"
216
- end
217
-
218
- end
219
-
220
- end
221
- end
@@ -1,125 +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-2021 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 = VerificationResult.new
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
- verify_signing_time(result)
108
-
109
- store.verify_callback = store_verification_callback(result,
110
- allow_self_signed: allow_self_signed)
111
- if @pkcs7.verify(certificate_chain, store, signature_dict.signed_data,
112
- OpenSSL::PKCS7::DETACHED | OpenSSL::PKCS7::BINARY)
113
- result.log(:info, "Signature valid")
114
- else
115
- result.log(:error, "Signature verification failed")
116
- end
117
-
118
- result
119
- end
120
-
121
- end
122
-
123
- end
124
- end
125
- end