hexapdf 0.19.1 → 0.19.2

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