hexapdf 1.9.1 → 1.11.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 (46) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +55 -0
  3. data/lib/hexapdf/cli/command.rb +1 -2
  4. data/lib/hexapdf/configuration.rb +2 -2
  5. data/lib/hexapdf/content/canvas.rb +2 -2
  6. data/lib/hexapdf/content/smart_text_extractor.rb +3 -2
  7. data/lib/hexapdf/dictionary_fields.rb +7 -0
  8. data/lib/hexapdf/digital_signature/cms_handler.rb +27 -2
  9. data/lib/hexapdf/digital_signature/pkcs1_handler.rb +5 -0
  10. data/lib/hexapdf/digital_signature/signatures.rb +105 -0
  11. data/lib/hexapdf/digital_signature/signing/signed_data_creator.rb +0 -2
  12. data/lib/hexapdf/document.rb +2 -2
  13. data/lib/hexapdf/encryption/security_handler.rb +3 -1
  14. data/lib/hexapdf/layout/style.rb +2 -2
  15. data/lib/hexapdf/layout/text_fragment.rb +5 -1
  16. data/lib/hexapdf/layout/text_shaper.rb +1 -57
  17. data/lib/hexapdf/parser.rb +1 -2
  18. data/lib/hexapdf/serializer.rb +1 -1
  19. data/lib/hexapdf/task/import_pages.rb +41 -9
  20. data/lib/hexapdf/type/acro_form/appearance_generator.rb +1 -1
  21. data/lib/hexapdf/type/acro_form/choice_field.rb +3 -1
  22. data/lib/hexapdf/type/acro_form/field.rb +1 -1
  23. data/lib/hexapdf/type/catalog.rb +8 -1
  24. data/lib/hexapdf/type/document_security_store.rb +81 -13
  25. data/lib/hexapdf/type/font_type0.rb +1 -1
  26. data/lib/hexapdf/version.rb +1 -1
  27. data/test/hexapdf/content/test_canvas.rb +3 -3
  28. data/test/hexapdf/content/test_smart_text_extractor.rb +7 -0
  29. data/test/hexapdf/digital_signature/common.rb +209 -2
  30. data/test/hexapdf/digital_signature/test_cms_handler.rb +4 -2
  31. data/test/hexapdf/digital_signature/test_pkcs1_handler.rb +4 -0
  32. data/test/hexapdf/digital_signature/test_signatures.rb +131 -0
  33. data/test/hexapdf/encryption/test_security_handler.rb +12 -0
  34. data/test/hexapdf/layout/test_style.rb +3 -1
  35. data/test/hexapdf/layout/test_text_fragment.rb +11 -4
  36. data/test/hexapdf/task/test_import_pages.rb +42 -1
  37. data/test/hexapdf/test_dictionary_fields.rb +8 -0
  38. data/test/hexapdf/test_parser.rb +25 -9
  39. data/test/hexapdf/test_serializer.rb +7 -1
  40. data/test/hexapdf/type/acro_form/test_appearance_generator.rb +1 -1
  41. data/test/hexapdf/type/acro_form/test_choice_field.rb +5 -0
  42. data/test/hexapdf/type/acro_form/test_field.rb +3 -1
  43. data/test/hexapdf/type/test_catalog.rb +10 -0
  44. data/test/hexapdf/type/test_document_security_store.rb +120 -0
  45. data/test/test_helper.rb +7 -3
  46. metadata +939 -904
@@ -34,6 +34,7 @@
34
34
  # commercial licenses are available at <https://gettalong.at/hexapdf/>.
35
35
  #++
36
36
 
37
+ require 'openssl'
37
38
  require 'hexapdf/dictionary'
38
39
 
39
40
  module HexaPDF
@@ -54,25 +55,92 @@ module HexaPDF
54
55
  define_type :VRI
55
56
 
56
57
  define_field :Type, type: Symbol, default: type
57
- define_field :Cert, type: PDFArray
58
- define_field :CRL, type: PDFArray
59
- define_field :OCSP, type: PDFArray
60
- define_field :TU, type: PDFDate
61
- define_field :TS, type: Stream
58
+ define_field :Cert, type: PDFArray, version: '2.0'
59
+ define_field :CRL, type: PDFArray, version: '2.0'
60
+ define_field :OCSP, type: PDFArray, version: '2.0'
61
+ define_field :TU, type: PDFDate, version: '2.0'
62
+ define_field :TS, type: Stream, version: '2.0'
62
63
 
63
64
  end
64
65
 
65
66
  define_type :DSS
66
67
 
67
68
  define_field :Type, type: Symbol, default: type
68
- define_field :VRI, type: Dictionary
69
- define_field :Certs, type: PDFArray
70
- define_field :OCSPs, type: PDFArray
71
- define_field :CRLs, type: PDFArray
72
- define_field :SW, type: Symbol, default: :A, allowed_values: [:A, :B, :S, :N]
73
- define_field :S, type: Symbol, default: :P, allowed_values: [:A, :P]
74
- define_field :A, type: PDFArray, default: [0.5, 0.5]
75
- define_field :FB, type: Boolean, default: false, version: '1.5'
69
+ define_field :VRI, type: Dictionary, version: '2.0'
70
+ define_field :Certs, type: PDFArray, version: '2.0'
71
+ define_field :OCSPs, type: PDFArray, version: '2.0'
72
+ define_field :CRLs, type: PDFArray, version: '2.0'
73
+
74
+ # Adds validation data for a single signature to the /VRI dictionary and to the various
75
+ # arrays, and returns the new VRI entry.
76
+ #
77
+ # +signature+::
78
+ # The signature for which the validation data should be added.
79
+ #
80
+ # +certs+::
81
+ # Array of DER-encoded certificates.
82
+ #
83
+ # +ocsps+::
84
+ # Array of DER-encoded OCSP responses.
85
+ #
86
+ # +crls+::
87
+ # Array of DER-encoded CRLs.
88
+ def add_vri(signature, certs: [], ocsps: [], crls: [])
89
+ key = OpenSSL::Digest::SHA1.hexdigest(signature.contents).upcase.to_sym
90
+ vri = {Type: :VRI}
91
+ vri[:Cert] = certs.map {|der| add_cert(der) } unless certs.empty?
92
+ vri[:OCSP] = ocsps.map {|der| add_ocsp(der) } unless ocsps.empty?
93
+ vri[:CRL] = crls.map {|der| add_crl(der) } unless crls.empty?
94
+ (self[:VRI] ||= {})[key] = document.add(vri)
95
+ end
96
+
97
+ # Returns the VRI dictionary for the given signature if it exists or else +nil+.
98
+ def vri_for(signature)
99
+ self[:VRI] && self[:VRI][OpenSSL::Digest::SHA1.hexdigest(signature.contents).upcase.to_sym]
100
+ end
101
+
102
+ # Adds the DER-encoded certificate to the /Certs array if not already present and returns
103
+ # the stream object containing it.
104
+ def add_cert(cert_der)
105
+ add_data_as_stream_to_field(cert_der, :Certs)
106
+ end
107
+
108
+ # Adds the DER-encoded OCSP response to the /OCSPs array if not already present and returns
109
+ # the stream object containing it.
110
+ def add_ocsp(ocsp_der)
111
+ add_data_as_stream_to_field(ocsp_der, :OCSPs)
112
+ end
113
+
114
+ # Adds the DER-encoded CRL to the /CRLs array if not already present and returns the stream
115
+ # object containing it.
116
+ def add_crl(crl_der)
117
+ add_data_as_stream_to_field(crl_der, :CRLs)
118
+ end
119
+
120
+ # Returns all certificates in the /Certs array as OpenSSL::X509::Certificate objects.
121
+ def certificates
122
+ if key?(:Certs)
123
+ self[:Certs].length.times.map {|i| OpenSSL::X509::Certificate.new(self[:Certs][i].stream) }
124
+ else
125
+ []
126
+ end
127
+ end
128
+
129
+ private
130
+
131
+ # Adds the given string +data+ to the +field+ array as stream object and returns the resulting
132
+ # stream.
133
+ #
134
+ # If the field already contains a stream with the same data, this existing stream is returned.
135
+ def add_data_as_stream_to_field(data, field)
136
+ self[field] ||= []
137
+ existing_stream = self[field].find {|stream| stream.stream == data }
138
+ return existing_stream if existing_stream
139
+
140
+ stream = document.add({Filter: :FlateDecode}, stream: data)
141
+ self[field] << stream
142
+ stream
143
+ end
76
144
 
77
145
  end
78
146
 
@@ -146,7 +146,7 @@ module HexaPDF
146
146
  registry = system_info[:Registry]
147
147
  ordering = system_info[:Ordering]
148
148
  if (encoding.kind_of?(Symbol) && HexaPDF::Font::CMap.predefined?(encoding.to_s) &&
149
- encoding != :'Identity-H' && encoding != :'Identity-V') ||
149
+ encoding != :'Identity-H' && encoding != :'Identity-V') ||
150
150
  (registry == "Adobe" && ['GB1', 'CNS1', 'Japan1', 'Korea1'].include?(ordering))
151
151
  HexaPDF::Font::CMap.for_name("#{registry}-#{ordering}-UCS2")
152
152
  end
@@ -37,6 +37,6 @@
37
37
  module HexaPDF
38
38
 
39
39
  # The version of HexaPDF.
40
- VERSION = '1.9.1'
40
+ VERSION = '1.11.0'
41
41
 
42
42
  end
@@ -942,7 +942,7 @@ describe HexaPDF::Content::Canvas do
942
942
  it "correctly serializes the form with just the width given" do
943
943
  @canvas.image(@form, at: [1, 2], width: 50)
944
944
  assert_operators(@page.contents, [[:save_graphics_state],
945
- [:concatenate_matrix, [0.5, 0, 0, 0.5, -99, -48]],
945
+ [:concatenate_matrix, [0.5, 0, 0, 0.5, -49, -23]],
946
946
  [:paint_xobject, [:XO1]],
947
947
  [:restore_graphics_state]])
948
948
  end
@@ -950,7 +950,7 @@ describe HexaPDF::Content::Canvas do
950
950
  it "correctly serializes the form with just the height given" do
951
951
  @canvas.image(@form, at: [1, 2], height: 10)
952
952
  assert_operators(@page.contents, [[:save_graphics_state],
953
- [:concatenate_matrix, [0.2, 0, 0, 0.2, -99, -48]],
953
+ [:concatenate_matrix, [0.2, 0, 0, 0.2, -19, -8]],
954
954
  [:paint_xobject, [:XO1]],
955
955
  [:restore_graphics_state]])
956
956
  end
@@ -958,7 +958,7 @@ describe HexaPDF::Content::Canvas do
958
958
  it "correctly serializes the form with both width and height given" do
959
959
  @canvas.image(@form, at: [1, 2], width: 50, height: 10)
960
960
  assert_operators(@page.contents, [[:save_graphics_state],
961
- [:concatenate_matrix, [0.5, 0, 0, 0.2, -99, -48]],
961
+ [:concatenate_matrix, [0.5, 0, 0, 0.2, -49, -8]],
962
962
  [:paint_xobject, [:XO1]],
963
963
  [:restore_graphics_state]])
964
964
  end
@@ -101,6 +101,13 @@ describe HexaPDF::Content::SmartTextExtractor do
101
101
  ['Foot', 50, 10, 66, 20]]))
102
102
  end
103
103
 
104
+
105
+ it "works in case the majority of text runs have zero height" do
106
+ assert_equal(["Hello", "World News"].join("\n"*17), layout_runs([['World', 50, 80, 70, 80],
107
+ ['News', 75, 80, 100, 80],
108
+ ['Hello', 50, 100, 70, 100]]))
109
+ end
110
+
104
111
  it "ignores outliers when calculating the normal line spacing" do
105
112
  assert_equal("Hello\nWorld\n\n\n\nHere",
106
113
  layout_runs([['Hello', 50, 100, 70, 110],
@@ -5,6 +5,9 @@ module HexaPDF
5
5
 
6
6
  class Certificates
7
7
 
8
+ # Port for the combined OCSP responder / CRL distribution point test server.
9
+ OCSP_CRL_PORT = 34568
10
+
8
11
  def ca_key
9
12
  @ca_key ||= OpenSSL::PKey::RSA.new(2048)
10
13
  end
@@ -43,6 +46,106 @@ module HexaPDF
43
46
  end
44
47
  end
45
48
 
49
+ def signer_certificate_with_ocsp
50
+ @signer_certificate_with_ocsp ||=
51
+ begin
52
+ cert = create_cert(name: '/CN=RSA signer with OCSP', serial: 6,
53
+ public_key: signer_key, issuer: ca_certificate)
54
+ add_extensions(cert, ca_certificate, ca_key, key_usage: 'digitalSignature',
55
+ ocsp_url: "http://127.0.0.1:#{OCSP_CRL_PORT}/ocsp")
56
+ cert
57
+ end
58
+ end
59
+
60
+ def signer_certificate_with_crl
61
+ @signer_certificate_with_crl ||=
62
+ begin
63
+ cert = create_cert(name: '/CN=RSA signer with CRL', serial: 7,
64
+ public_key: signer_key, issuer: ca_certificate)
65
+ add_extensions(cert, ca_certificate, ca_key, key_usage: 'digitalSignature',
66
+ crl_url: "http://127.0.0.1:#{OCSP_CRL_PORT}/crl")
67
+ cert
68
+ end
69
+ end
70
+
71
+ def signer_certificate_with_unsuccessful_ocsp
72
+ @signer_certificate_with_unsuccessful_ocsp ||=
73
+ begin
74
+ cert = create_cert(name: '/CN=RSA signer with unsucessful OCSP', serial: 11,
75
+ public_key: signer_key, issuer: ca_certificate)
76
+ add_extensions(cert, ca_certificate, ca_key, key_usage: 'digitalSignature',
77
+ ocsp_url: "http://127.0.0.1:#{OCSP_CRL_PORT}/ocsp-unsuccessful")
78
+ cert
79
+ end
80
+ end
81
+
82
+ def signer_certificate_with_revoked_ocsp
83
+ @signer_certificate_with_revoked_ocsp ||=
84
+ begin
85
+ cert = create_cert(name: '/CN=RSA signer with revoked OCSP', serial: 12,
86
+ public_key: signer_key, issuer: ca_certificate)
87
+ add_extensions(cert, ca_certificate, ca_key, key_usage: 'digitalSignature',
88
+ ocsp_url: "http://127.0.0.1:#{OCSP_CRL_PORT}/ocsp-revoked")
89
+ cert
90
+ end
91
+ end
92
+
93
+ def signer_certificate_with_http_error_ocsp
94
+ @signer_certificate_with_http_error_ocsp ||=
95
+ begin
96
+ cert = create_cert(name: '/CN=RSA signer with HTTP error OCSP', serial: 13,
97
+ public_key: signer_key, issuer: ca_certificate)
98
+ add_extensions(cert, ca_certificate, ca_key, key_usage: 'digitalSignature',
99
+ ocsp_url: "http://127.0.0.1:#{OCSP_CRL_PORT}/ocsp-http-error")
100
+ cert
101
+ end
102
+ end
103
+
104
+ def signer_certificate_with_bad_ocsp
105
+ @signer_certificate_with_bad_ocsp ||=
106
+ begin
107
+ cert = create_cert(name: '/CN=RSA signer with bad OCSP', serial: 8,
108
+ public_key: signer_key, issuer: ca_certificate)
109
+ add_extensions(cert, ca_certificate, ca_key, key_usage: 'digitalSignature',
110
+ ocsp_url: "http://127.0.0.1:#{OCSP_CRL_PORT + 1}/ocsp")
111
+ cert
112
+ end
113
+ end
114
+
115
+ def signer_certificate_with_revoked_crl
116
+ @signer_certificate_with_revoked_crl ||=
117
+ begin
118
+ cert = create_cert(name: '/CN=RSA signer with revoked CRL', serial: 14,
119
+ public_key: signer_key, issuer: ca_certificate)
120
+ add_extensions(cert, ca_certificate, ca_key, key_usage: 'digitalSignature',
121
+ crl_url: "http://127.0.0.1:#{OCSP_CRL_PORT}/crl-revoked")
122
+ cert
123
+ end
124
+ end
125
+
126
+ def signer_certificate_with_http_error_crl
127
+ @signer_certificate_with_http_error_crl ||=
128
+ begin
129
+ cert = create_cert(name: '/CN=RSA signer with HTTP error CRL', serial: 15,
130
+ public_key: signer_key, issuer: ca_certificate)
131
+ add_extensions(cert, ca_certificate, ca_key, key_usage: 'digitalSignature',
132
+ crl_url: "http://127.0.0.1:#{OCSP_CRL_PORT}/crl-http-error")
133
+ cert
134
+ end
135
+ end
136
+
137
+ def signer_certificate_with_bad_crl
138
+ @signer_certificate_with_bad_crl ||=
139
+ begin
140
+ cert = create_cert(name: '/CN=RSA signer with bad CRL', serial: 9,
141
+ public_key: signer_key, issuer: ca_certificate)
142
+ add_extensions(cert, ca_certificate, ca_key, key_usage: 'digitalSignature',
143
+ crl_url: "http://127.0.0.1:#{OCSP_CRL_PORT + 1}/crl")
144
+ cert
145
+ end
146
+ end
147
+
148
+
46
149
  def dsa_signer_key
47
150
  @dsa_signer_key ||= OpenSSL::PKey::DSA.new(2048)
48
151
  end
@@ -77,11 +180,36 @@ module HexaPDF
77
180
  cert = create_cert(name: '/CN=timestamp', serial: 5,
78
181
  public_key: signer_key, issuer: ca_certificate)
79
182
  add_extensions(cert, ca_certificate, ca_key, key_usage: 'digitalSignature',
80
- extended_key_usage: 'timeStamping')
183
+ extended_key_usage: 'timeStamping',
184
+ crl_url: "http://127.0.0.1:#{OCSP_CRL_PORT}/crl")
185
+ cert
186
+ end
187
+ end
188
+
189
+ def ocsp_signing_certificate
190
+ @ocsp_signing_certificate ||=
191
+ begin
192
+ cert = create_cert(name: '/CN=OCSP', serial: 10,
193
+ public_key: signer_key, issuer: ca_certificate)
194
+ add_extensions(cert, ca_certificate, ca_key, key_usage: 'digitalSignature',
195
+ extended_key_usage: 'OCSPSigning')
81
196
  cert
82
197
  end
83
198
  end
84
199
 
200
+ def crl
201
+ @crl ||=
202
+ begin
203
+ crl = OpenSSL::X509::CRL.new
204
+ crl.version = 1
205
+ crl.issuer = ca_certificate.subject
206
+ crl.last_update = Time.now - 60
207
+ crl.next_update = Time.now + 86400
208
+ crl.sign(ca_key, OpenSSL::Digest.new('SHA256'))
209
+ crl
210
+ end
211
+ end
212
+
85
213
  def create_cert(name:, serial:, public_key:, issuer: nil)
86
214
  name = OpenSSL::X509::Name.parse(name)
87
215
  cert = OpenSSL::X509::Certificate.new
@@ -96,7 +224,7 @@ module HexaPDF
96
224
  end
97
225
 
98
226
  def add_extensions(subject_cert, issuer_cert, signing_key, is_ca: false, key_usage: nil,
99
- extended_key_usage: nil)
227
+ extended_key_usage: nil, ocsp_url: nil, crl_url: nil)
100
228
  extension_factory = OpenSSL::X509::ExtensionFactory.new
101
229
  extension_factory.subject_certificate = subject_cert
102
230
  extension_factory.issuer_certificate = issuer_cert
@@ -113,6 +241,16 @@ module HexaPDF
113
241
  subject_cert.add_extension(extension_factory.create_extension('extendedKeyUsage',
114
242
  extended_key_usage, true))
115
243
  end
244
+ if ocsp_url
245
+ subject_cert.add_extension(
246
+ extension_factory.create_extension('authorityInfoAccess', "OCSP;URI:#{ocsp_url}")
247
+ )
248
+ end
249
+ if crl_url
250
+ subject_cert.add_extension(
251
+ extension_factory.create_extension('crlDistributionPoints', "URI:#{crl_url}")
252
+ )
253
+ end
116
254
  subject_cert.sign(signing_key, OpenSSL::Digest.new('SHA1'))
117
255
  end
118
256
  private :add_extensions
@@ -148,6 +286,75 @@ module HexaPDF
148
286
  Thread.new { @tsa_server.start }
149
287
  end
150
288
 
289
+ def start_ocsp_crl_server
290
+ return if defined?(@ocsp_crl_server)
291
+ require 'webrick'
292
+ @ocsp_crl_server = WEBrick::HTTPServer.new(Port: OCSP_CRL_PORT, BindAddress: '127.0.0.1',
293
+ Logger: WEBrick::Log.new(StringIO.new), AccessLog: [])
294
+ @ocsp_crl_server.mount_proc('/ocsp') do |request, response|
295
+ ocsp_req = OpenSSL::OCSP::Request.new(request.body)
296
+ basic_resp = OpenSSL::OCSP::BasicResponse.new
297
+ ocsp_req.certid.each do |certid|
298
+ basic_resp.add_status(certid, OpenSSL::OCSP::V_CERTSTATUS_GOOD,
299
+ OpenSSL::OCSP::REVOKED_STATUS_UNSPECIFIED,
300
+ nil, Time.now - 60, Time.now + 86400, nil)
301
+ end
302
+ basic_resp.sign(CERTIFICATES.ocsp_signing_certificate, CERTIFICATES.signer_key,
303
+ [CERTIFICATES.ca_certificate])
304
+ response.body = OpenSSL::OCSP::Response.create(
305
+ OpenSSL::OCSP::RESPONSE_STATUS_SUCCESSFUL, basic_resp
306
+ ).to_der
307
+ response['Content-Type'] = 'application/ocsp-response'
308
+ end
309
+
310
+ @ocsp_crl_server.mount_proc('/ocsp-unsuccessful') do |request, response|
311
+ basic_resp = OpenSSL::OCSP::BasicResponse.new
312
+ basic_resp.sign(CERTIFICATES.ocsp_signing_certificate, CERTIFICATES.signer_key,
313
+ [CERTIFICATES.ca_certificate])
314
+ response.body = OpenSSL::OCSP::Response.create(
315
+ OpenSSL::OCSP::RESPONSE_STATUS_INTERNALERROR, basic_resp
316
+ ).to_der
317
+ response['Content-Type'] = 'application/ocsp-response'
318
+ end
319
+
320
+ @ocsp_crl_server.mount_proc('/ocsp-revoked') do |request, response|
321
+ ocsp_req = OpenSSL::OCSP::Request.new(request.body)
322
+ basic_resp = OpenSSL::OCSP::BasicResponse.new
323
+ ocsp_req.certid.each do |certid|
324
+ basic_resp.add_status(certid, OpenSSL::OCSP::V_CERTSTATUS_REVOKED,
325
+ OpenSSL::OCSP::REVOKED_STATUS_UNSPECIFIED,
326
+ Time.now - 100, Time.now - 60, Time.now + 86400, nil)
327
+ end
328
+ basic_resp.sign(CERTIFICATES.ocsp_signing_certificate, CERTIFICATES.signer_key,
329
+ [CERTIFICATES.ca_certificate])
330
+ response.body = OpenSSL::OCSP::Response.create(
331
+ OpenSSL::OCSP::RESPONSE_STATUS_SUCCESSFUL, basic_resp
332
+ ).to_der
333
+ response['Content-Type'] = 'application/ocsp-response'
334
+ end
335
+
336
+ @ocsp_crl_server.mount_proc('/crl') do |_request, response|
337
+ response.body = CERTIFICATES.crl.to_der
338
+ response['Content-Type'] = 'application/pkix-crl'
339
+ end
340
+
341
+ @ocsp_crl_server.mount_proc('/crl-revoked') do |_request, response|
342
+ crl = OpenSSL::X509::CRL.new
343
+ crl.version = 1
344
+ crl.issuer = ca_certificate.subject
345
+ crl.last_update = Time.now - 60
346
+ crl.next_update = Time.now + 86400
347
+ revoked = OpenSSL::X509::Revoked.new
348
+ revoked.serial = signer_certificate_with_revoked_crl.serial
349
+ revoked.time = Time.now - 100
350
+ crl.add_revoked(revoked)
351
+ crl.sign(ca_key, OpenSSL::Digest.new('SHA256'))
352
+ response.body = crl.to_der
353
+ response['Content-Type'] = 'application/pkix-crl'
354
+ end
355
+ Thread.new { @ocsp_crl_server.start }
356
+ end
357
+
151
358
  end
152
359
 
153
360
  end
@@ -125,7 +125,9 @@ describe HexaPDF::DigitalSignature::CMSHandler do
125
125
  fac.serial_number = 1
126
126
  fac.allowed_digests = ["sha256", "sha512"]
127
127
  res = fac.create_timestamp(CERTIFICATES.signer_key, CERTIFICATES.timestamp_certificate, req)
128
- @dict.contents = res.token.to_der
128
+ der_form = res.token.to_der
129
+ # Convert DER to BER with indefinite length encoding as variation
130
+ @dict.contents = der_form[0] << "\x80".b << der_form[4..-1] << "\x00\x00".b << "\x00\x00\x00\x00".b
129
131
  @dict.signature_type = 'ETSI.RFC3161'
130
132
  @handler = HexaPDF::DigitalSignature::CMSHandler.new(@dict)
131
133
 
@@ -151,7 +153,7 @@ describe HexaPDF::DigitalSignature::CMSHandler do
151
153
  key: CERTIFICATES.signer_key, timestamp_handler: tsh,
152
154
  certificates: [CERTIFICATES.ca_certificate]
153
155
  )
154
- @dict.contents = cms.to_der
156
+ @dict.contents = cms.to_der << "\x00\x00\x00\x00"
155
157
  @dict.signed_data = @data
156
158
  @handler = HexaPDF::DigitalSignature::CMSHandler.new(@dict)
157
159
  end
@@ -28,6 +28,10 @@ describe HexaPDF::DigitalSignature::PKCS1Handler do
28
28
  assert_equal(CERTIFICATES.signer_certificate, @handler.signer_certificate)
29
29
  end
30
30
 
31
+ it "returns nil for the embedded timestamp signature" do
32
+ assert_nil(@handler.embedded_tsa_signature)
33
+ end
34
+
31
35
  describe "verify" do
32
36
  before do
33
37
  @store = OpenSSL::X509::Store.new
@@ -161,4 +161,135 @@ describe HexaPDF::DigitalSignature::Signatures do
161
161
  assert(signed_doc.signatures.first.verify)
162
162
  end
163
163
  end
164
+
165
+ describe "add_ltv_information" do
166
+ before do
167
+ CERTIFICATES.start_ocsp_crl_server
168
+ @doc = HexaPDF::Document.new(io: StringIO.new(MINIMAL_PDF))
169
+ @io = StringIO.new(''.b)
170
+ end
171
+
172
+ # Signs @doc and returns a new HexaPDF::Document instance for the signed document.
173
+ def sign_with(certificate, key: CERTIFICATES.signer_key, certificate_chain: [CERTIFICATES.ca_certificate])
174
+ io = StringIO.new(''.b)
175
+ @doc.sign(io, certificate: certificate, key: key, certificate_chain: certificate_chain)
176
+ HexaPDF::Document.new(io: io)
177
+ end
178
+
179
+ it "adds all certificates to the DSS" do
180
+ doc = sign_with(CERTIFICATES.signer_certificate_with_ocsp)
181
+ doc.signatures.add_ltv_information
182
+ dss = doc.catalog.dss
183
+ assert_equal([CERTIFICATES.signer_certificate_with_ocsp, CERTIFICATES.ca_certificate],
184
+ dss.certificates)
185
+ end
186
+
187
+ it "fails if one of the certificates has no OCSP or CRL URL" do
188
+ doc = sign_with(CERTIFICATES.signer_certificate)
189
+ e = assert_raises(HexaPDF::Error) { doc.signatures.add_ltv_information }
190
+ assert_match(/No OCSP and CRL/, e.message)
191
+ end
192
+
193
+ it "adds a VRI entry for all non-root certificates" do
194
+ doc = sign_with(CERTIFICATES.signer_certificate_with_ocsp)
195
+ doc.signatures.add_ltv_information
196
+ dss = doc.catalog.dss
197
+ assert_equal(1, dss[:VRI].value.length)
198
+ assert(dss.vri_for(doc.signatures.first))
199
+ end
200
+
201
+ it "embeds OCSP validation data for a certificate that has an AIA OCSP URL" do
202
+ doc = sign_with(CERTIFICATES.signer_certificate_with_ocsp)
203
+ doc.signatures.add_ltv_information
204
+ dss = doc.catalog.dss
205
+ vri = dss.vri_for(doc.signatures.first)
206
+ assert_equal(dss[:OCSPs].value, vri[:OCSP].value)
207
+ assert_equal(dss[:Certs].value, vri[:Cert].value)
208
+ assert_nil(dss[:CRLs])
209
+ assert_nil(vri[:CRL])
210
+ end
211
+
212
+ it "handles a connection failure to the OCSP server" do
213
+ doc = sign_with(CERTIFICATES.signer_certificate_with_bad_ocsp)
214
+ e = assert_raises(HexaPDF::Error) { doc.signatures.add_ltv_information }
215
+ assert_match(/No OCSP and CRL/, e.message)
216
+ end
217
+
218
+ it "handles unsuccessful OCSP validation" do
219
+ doc = sign_with(CERTIFICATES.signer_certificate_with_unsuccessful_ocsp)
220
+ e = assert_raises(HexaPDF::Error) { doc.signatures.add_ltv_information }
221
+ assert_match(/No OCSP and CRL/, e.message)
222
+ end
223
+
224
+ it "handles successful OCSP validation but with a non-good certificate status" do
225
+ doc = sign_with(CERTIFICATES.signer_certificate_with_revoked_ocsp)
226
+ e = assert_raises(HexaPDF::Error) { doc.signatures.add_ltv_information }
227
+ assert_match(/OCSP response.*not valid/, e.message)
228
+ end
229
+
230
+ it "handles non-OK HTTP response codes when fetching the OCSP response" do
231
+ doc = sign_with(CERTIFICATES.signer_certificate_with_http_error_ocsp)
232
+ e = assert_raises(HexaPDF::Error) { doc.signatures.add_ltv_information }
233
+ assert_match(/No OCSP and CRL/, e.message)
234
+ end
235
+
236
+ it "doesn't use the OCSP information if the issuer certificate is not embedded" do
237
+ doc = sign_with(CERTIFICATES.signer_certificate_with_ocsp, certificate_chain: [])
238
+ e = assert_raises(HexaPDF::Error) { doc.signatures.add_ltv_information }
239
+ assert_match(/No OCSP and CRL/, e.message)
240
+ end
241
+
242
+ it "embeds CRL validation data when no OCSP URL is present in the certificate" do
243
+ doc = sign_with(CERTIFICATES.signer_certificate_with_crl)
244
+ doc.signatures.add_ltv_information
245
+ dss = doc.catalog.dss
246
+ vri = dss.vri_for(doc.signatures.first)
247
+ assert_equal(dss[:CRLs].value, vri[:CRL].value)
248
+ assert_equal(dss[:Certs].value, vri[:Cert].value)
249
+ assert_nil(dss[:OCSPs])
250
+ assert_nil(vri[:OCSP])
251
+ end
252
+
253
+ it "handles a connection failure to the CRL server" do
254
+ doc = sign_with(CERTIFICATES.signer_certificate_with_bad_crl)
255
+ # CRL URL available but bad endpoint
256
+ e = assert_raises(HexaPDF::Error) { doc.signatures.add_ltv_information }
257
+ assert_match(/No OCSP and CRL/, e.message)
258
+ end
259
+
260
+ it "handles a certificate that is revoked via CRL" do
261
+ doc = sign_with(CERTIFICATES.signer_certificate_with_revoked_crl)
262
+ e = assert_raises(HexaPDF::Error) { doc.signatures.add_ltv_information }
263
+ assert_match(/CRL.*certificate is revoked/, e.message)
264
+ end
265
+
266
+ it "handles non-OK HTTP response codes when fetching the CRL response" do
267
+ doc = sign_with(CERTIFICATES.signer_certificate_with_http_error_crl)
268
+ e = assert_raises(HexaPDF::Error) { doc.signatures.add_ltv_information }
269
+ assert_match(/No OCSP and CRL/, e.message)
270
+ end
271
+
272
+ it "embeds validation data for an embedded timestamp signature" do
273
+ CERTIFICATES.start_tsa_server
274
+ ts_handler = @doc.signatures.signing_handler(name: :timestamp, signature_size: 20_000,
275
+ tsa_url: "http://127.0.0.1:34567")
276
+ io = StringIO.new(''.b)
277
+ @doc.sign(io, certificate: CERTIFICATES.signer_certificate_with_ocsp,
278
+ key: CERTIFICATES.signer_key, certificate_chain: [CERTIFICATES.ca_certificate],
279
+ timestamp_handler: ts_handler)
280
+ doc = HexaPDF::Document.new(io: io)
281
+ doc.signatures.add_ltv_information
282
+ dss = doc.catalog.dss
283
+ vri = dss.vri_for(doc.signatures.first)
284
+ assert_equal(3, vri[:Cert].value.size)
285
+ end
286
+
287
+ it "creates VRI entries for all signatures" do
288
+ @doc = sign_with(CERTIFICATES.signer_certificate_with_ocsp)
289
+ doc = sign_with(CERTIFICATES.signer_certificate_with_crl)
290
+ doc.signatures.add_ltv_information
291
+ dss = doc.catalog.dss
292
+ assert_equal(2, dss[:VRI].value.size)
293
+ end
294
+ end
164
295
  end
@@ -302,6 +302,18 @@ describe HexaPDF::Encryption::SecurityHandler do
302
302
  assert_equal('string', obj.stream)
303
303
  end
304
304
 
305
+ it "handles decryption of should-be stream objects without actual streams" do
306
+ doc = HexaPDF::Document.new
307
+ obj = doc.add({}) # Create XObject as Dictionary and not as Stream
308
+ obj[:Type] = :XObject
309
+ obj[:Subtype] = :Form
310
+ doc.catalog[:Test] = obj
311
+ doc.encrypt
312
+ doc = HexaPDF::Document.new(io: StringIO.new(doc.write_to_string))
313
+ assert_kind_of(HexaPDF::Stream, doc.catalog[:Test])
314
+ assert_equal('', doc.catalog[:Test].raw_stream)
315
+ end
316
+
305
317
  it "doesn't decrypt a document's Encrypt dictionaries" do
306
318
  @document = HexaPDF::Document.new
307
319
  @document.trailer[:Encrypt] = @document.add({Key: "Something"})
@@ -616,7 +616,7 @@ describe HexaPDF::Layout::Style::LinkLayer do
616
616
  annot = call_link(dest: true)
617
617
  assert_equal(:Link, annot[:Subtype])
618
618
  assert_equal([10, 10, 25, 20], annot[:Rect].value)
619
- assert_equal([10, 10, 25, 10, 25, 20, 10, 20], annot[:QuadPoints].value)
619
+ assert_equal([10, 20, 25, 20, 10, 10, 25, 10], annot[:QuadPoints].value)
620
620
  end
621
621
 
622
622
  it "removes the border by default" do
@@ -852,8 +852,10 @@ describe HexaPDF::Layout::Style do
852
852
  end
853
853
 
854
854
  it "has several dynamically generated properties with default values that take blocks" do
855
+ refute(@style.text_segmentation_algorithm?)
855
856
  assert_equal(HexaPDF::Layout::TextLayouter::SimpleTextSegmentation,
856
857
  @style.text_segmentation_algorithm)
858
+ refute(@style.text_line_wrapping_algorithm?)
857
859
  assert_equal(HexaPDF::Layout::TextLayouter::SimpleLineWrapping,
858
860
  @style.text_line_wrapping_algorithm)
859
861