hexapdf 0.19.0 → 0.20.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 (92) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +69 -0
  3. data/data/hexapdf/cert/demo_cert.rb +22 -0
  4. data/data/hexapdf/cert/root-ca.crt +119 -0
  5. data/data/hexapdf/cert/signing.crt +125 -0
  6. data/data/hexapdf/cert/signing.key +52 -0
  7. data/data/hexapdf/cert/sub-ca.crt +125 -0
  8. data/lib/hexapdf/cli/info.rb +21 -1
  9. data/lib/hexapdf/configuration.rb +26 -0
  10. data/lib/hexapdf/content/graphics_state.rb +24 -5
  11. data/lib/hexapdf/content/processor.rb +1 -1
  12. data/lib/hexapdf/document/signatures.rb +327 -0
  13. data/lib/hexapdf/document.rb +26 -0
  14. data/lib/hexapdf/encryption/standard_security_handler.rb +1 -2
  15. data/lib/hexapdf/importer.rb +1 -1
  16. data/lib/hexapdf/layout/style.rb +2 -1
  17. data/lib/hexapdf/object.rb +5 -3
  18. data/lib/hexapdf/parser.rb +21 -9
  19. data/lib/hexapdf/rectangle.rb +0 -6
  20. data/lib/hexapdf/revision.rb +13 -6
  21. data/lib/hexapdf/type/acro_form/appearance_generator.rb +2 -4
  22. data/lib/hexapdf/type/acro_form/field.rb +2 -0
  23. data/lib/hexapdf/type/acro_form/form.rb +9 -1
  24. data/lib/hexapdf/type/annotation.rb +36 -3
  25. data/lib/hexapdf/type/font.rb +5 -0
  26. data/lib/hexapdf/type/font_simple.rb +1 -1
  27. data/lib/hexapdf/type/font_type3.rb +20 -0
  28. data/lib/hexapdf/type/object_stream.rb +3 -1
  29. data/lib/hexapdf/type/signature/adbe_pkcs7_detached.rb +125 -0
  30. data/lib/hexapdf/type/signature/adbe_x509_rsa_sha1.rb +99 -0
  31. data/lib/hexapdf/type/signature/handler.rb +112 -0
  32. data/lib/hexapdf/type/signature/verification_result.rb +92 -0
  33. data/lib/hexapdf/type/signature.rb +236 -0
  34. data/lib/hexapdf/type.rb +1 -0
  35. data/lib/hexapdf/version.rb +1 -1
  36. data/lib/hexapdf/writer.rb +24 -10
  37. data/test/hexapdf/content/test_graphics_state.rb +9 -1
  38. data/test/hexapdf/content/test_operator.rb +8 -3
  39. data/test/hexapdf/content/test_processor.rb +1 -1
  40. data/test/hexapdf/document/test_signatures.rb +225 -0
  41. data/test/hexapdf/encryption/test_standard_security_handler.rb +8 -6
  42. data/test/hexapdf/layout/test_style.rb +11 -0
  43. data/test/hexapdf/test_document.rb +28 -0
  44. data/test/hexapdf/test_object.rb +7 -2
  45. data/test/hexapdf/test_parser.rb +14 -0
  46. data/test/hexapdf/test_rectangle.rb +0 -7
  47. data/test/hexapdf/test_revision.rb +44 -14
  48. data/test/hexapdf/test_writer.rb +44 -14
  49. data/test/hexapdf/type/acro_form/test_field.rb +11 -1
  50. data/test/hexapdf/type/acro_form/test_form.rb +5 -0
  51. data/test/hexapdf/type/signature/common.rb +71 -0
  52. data/test/hexapdf/type/signature/test_adbe_pkcs7_detached.rb +99 -0
  53. data/test/hexapdf/type/signature/test_adbe_x509_rsa_sha1.rb +66 -0
  54. data/test/hexapdf/type/signature/test_handler.rb +76 -0
  55. data/test/hexapdf/type/signature/test_verification_result.rb +47 -0
  56. data/test/hexapdf/type/test_annotation.rb +40 -2
  57. data/test/hexapdf/type/test_font.rb +4 -0
  58. data/test/hexapdf/type/test_font_simple.rb +5 -5
  59. data/test/hexapdf/type/test_font_type3.rb +16 -1
  60. data/test/hexapdf/type/test_object_stream.rb +9 -0
  61. data/test/hexapdf/type/test_signature.rb +131 -0
  62. metadata +21 -33
  63. data/test/data/cert/create.sh +0 -171
  64. data/test/data/cert/root-ca/certs/84E66B6F4C359E741C0AFA014790DF39.pem +0 -119
  65. data/test/data/cert/root-ca/certs/84E66B6F4C359E741C0AFA014790DF3A.pem +0 -125
  66. data/test/data/cert/root-ca/db/crlnumber +0 -1
  67. data/test/data/cert/root-ca/db/index +0 -2
  68. data/test/data/cert/root-ca/db/index.attr +0 -1
  69. data/test/data/cert/root-ca/db/index.attr.old +0 -1
  70. data/test/data/cert/root-ca/db/index.old +0 -1
  71. data/test/data/cert/root-ca/db/serial +0 -1
  72. data/test/data/cert/root-ca/db/serial.old +0 -1
  73. data/test/data/cert/root-ca/private/root-ca.key +0 -52
  74. data/test/data/cert/root-ca/root-ca.conf +0 -65
  75. data/test/data/cert/root-ca/root-ca.crt +0 -119
  76. data/test/data/cert/root-ca/root-ca.csr +0 -28
  77. data/test/data/cert/signature-1-pkcs7-detached.pdf +0 -182
  78. data/test/data/cert/sub-ca/certs/453FF080E3EDCD6A388D5368DFC320D9.pem +0 -125
  79. data/test/data/cert/sub-ca/db/crlnumber +0 -1
  80. data/test/data/cert/sub-ca/db/index +0 -1
  81. data/test/data/cert/sub-ca/db/index.attr +0 -1
  82. data/test/data/cert/sub-ca/db/index.old +0 -0
  83. data/test/data/cert/sub-ca/db/serial +0 -1
  84. data/test/data/cert/sub-ca/db/serial.old +0 -1
  85. data/test/data/cert/sub-ca/private/signing.key +0 -52
  86. data/test/data/cert/sub-ca/private/sub-ca.key +0 -52
  87. data/test/data/cert/sub-ca/signing.crt +0 -125
  88. data/test/data/cert/sub-ca/signing.csr +0 -28
  89. data/test/data/cert/sub-ca/signing.p12 +0 -0
  90. data/test/data/cert/sub-ca/sub-ca.conf +0 -65
  91. data/test/data/cert/sub-ca/sub-ca.crt +0 -125
  92. data/test/data/cert/sub-ca/sub-ca.csr +0 -28
@@ -597,6 +597,11 @@ end
597
597
  describe HexaPDF::Layout::Style do
598
598
  before do
599
599
  @style = HexaPDF::Layout::Style.new
600
+ @style.font = Object.new.tap do |obj|
601
+ obj.define_singleton_method(:pdf_object) do
602
+ Object.new.tap {|pdf| pdf.define_singleton_method(:glyph_scaling_factor) { 0.001 } }
603
+ end
604
+ end
600
605
  end
601
606
 
602
607
  it "can assign values on initialization" do
@@ -644,6 +649,7 @@ describe HexaPDF::Layout::Style do
644
649
  end
645
650
 
646
651
  it "has several simple and dynamically generated properties with default values" do
652
+ @style = HexaPDF::Layout::Style.new
647
653
  assert_raises(HexaPDF::Error) { @style.font }
648
654
  assert_equal(10, @style.font_size)
649
655
  assert_equal(0, @style.character_spacing)
@@ -725,6 +731,11 @@ describe HexaPDF::Layout::Style do
725
731
  font = Object.new
726
732
  font.define_singleton_method(:scaling_factor) { 1 }
727
733
  font.define_singleton_method(:wrapped_font) { wrapped_font }
734
+ font.define_singleton_method(:pdf_object) do
735
+ obj = Object.new
736
+ obj.define_singleton_method(:glyph_scaling_factor) { 0.001 }
737
+ obj
738
+ end
728
739
  @style.font = font
729
740
  end
730
741
 
@@ -596,6 +596,34 @@ describe HexaPDF::Document do
596
596
  end
597
597
  end
598
598
 
599
+ describe "signature interface" do
600
+ it "returns whether the document is signed or not" do
601
+ refute(@doc.signed?)
602
+
603
+ form = @doc.acro_form(create: true)
604
+ form.signature_flag(:signatures_exist)
605
+ assert(@doc.signed?)
606
+ end
607
+
608
+ it "returns all signature fields of the document" do
609
+ form = @doc.acro_form(create: true)
610
+ sig1 = @doc.add({FT: :Sig, T: 'sig1', V: :sig1})
611
+ sig2 = @doc.add({FT: :Sig, T: 'sig2', V: :sig2})
612
+ form.root_fields << sig1 << sig2
613
+ assert_equal([:sig1, :sig2], @doc.signatures.to_a)
614
+ end
615
+
616
+ it "allows to conveniently sign a document" do
617
+ mock = Minitest::Mock.new
618
+ mock.expect(:handler, :handler, [{name: :handler, opt: :key}])
619
+ mock.expect(:add, :added, [:io, :handler, {signature: :sig, write_options: :write_options}])
620
+ @doc.instance_variable_set(:@signatures, mock)
621
+ result = @doc.sign(:io, handler: :handler, write_options: :write_options, signature: :sig, opt: :key)
622
+ assert_equal(:added, result)
623
+ mock.verify
624
+ end
625
+ end
626
+
599
627
  describe "listener interface" do
600
628
  it "allows registering and dispatching messages" do
601
629
  args = []
@@ -182,7 +182,7 @@ describe HexaPDF::Object do
182
182
  assert_match(/\[5, 0\].*value=5/, obj.inspect)
183
183
  end
184
184
 
185
- it "can be compared to another object or reference" do
185
+ it "can be compared to another object, reference or, if not indirect, a simple value" do
186
186
  obj = HexaPDF::Object.new(5, oid: 5)
187
187
 
188
188
  assert_equal(obj, HexaPDF::Object.new(obj))
@@ -192,6 +192,11 @@ describe HexaPDF::Object do
192
192
  refute_equal(obj, HexaPDF::Object.new(5, oid: 5, gen: 1))
193
193
 
194
194
  assert_equal(obj, HexaPDF::Reference.new(5, 0))
195
+
196
+ refute_equal(obj, 5)
197
+ obj.data.oid = 0
198
+ assert_equal(obj, 5)
199
+ assert_equal(obj, HexaPDF::Object.new(5))
195
200
  end
196
201
 
197
202
  it "works correctly as hash key, is interchangable in this regard with Reference objects" do
@@ -216,7 +221,7 @@ describe HexaPDF::Object do
216
221
  it "creates an independent object" do
217
222
  obj = HexaPDF::Object.new({a: "mystring", b: HexaPDF::Reference.new(1, 0), c: 5})
218
223
  copy = obj.deep_copy
219
- refute_equal(copy, obj)
224
+ refute_same(copy, obj)
220
225
  assert_equal(copy.value, obj.value)
221
226
  refute_same(copy.value[:a], obj.value[:a])
222
227
  end
@@ -338,6 +338,11 @@ describe HexaPDF::Parser do
338
338
  assert_equal(5, @parser.startxref_offset)
339
339
  end
340
340
 
341
+ it "handles the case of startxref and its value being on the same line" do
342
+ create_parser("startxref 5\n%%EOF")
343
+ assert_equal(5, @parser.startxref_offset)
344
+ end
345
+
341
346
  it "fails even in big files when nothing is found" do
342
347
  create_parser("\nhallo" * 5000)
343
348
  exp = assert_raises(HexaPDF::MalformedPDFError) { @parser.startxref_offset }
@@ -366,6 +371,13 @@ describe HexaPDF::Parser do
366
371
  exp = assert_raises(HexaPDF::MalformedPDFError) { @parser.startxref_offset }
367
372
  assert_match(/end-of-file marker not found/, exp.message)
368
373
  end
374
+
375
+ it "fails on strict parsing if the startxref is on the same line as its value" do
376
+ @document.config['parser.on_correctable_error'] = proc { true }
377
+ create_parser("startxref 5\n%%EOF")
378
+ exp = assert_raises(HexaPDF::MalformedPDFError) { @parser.startxref_offset }
379
+ assert_match(/startxref on same line/, exp.message)
380
+ end
369
381
  end
370
382
 
371
383
  describe "file_header_version" do
@@ -531,12 +543,14 @@ describe HexaPDF::Parser do
531
543
  xref_section, trailer = @parser.load_revision(@parser.startxref_offset)
532
544
  assert_equal({Test: 'now'}, trailer)
533
545
  assert(xref_section[1].in_use?)
546
+ refute(@parser.contains_xref_streams?)
534
547
  end
535
548
 
536
549
  it "works for a cross-reference stream" do
537
550
  xref_section, trailer = @parser.load_revision(212)
538
551
  assert_equal({Size: 2}, trailer)
539
552
  assert(xref_section[1].in_use?)
553
+ assert(@parser.contains_xref_streams?)
540
554
  end
541
555
 
542
556
  it "fails if another object is found instead of a cross-reference stream" do
@@ -50,13 +50,6 @@ describe HexaPDF::Rectangle do
50
50
  assert_equal(12, rect.top)
51
51
  end
52
52
 
53
- it "allows comparison to arrays" do
54
- rect = HexaPDF::Rectangle.new([0, 1, 2, 5])
55
- assert(rect == [0, 1, 2, 5])
56
- rect.oid = 5
57
- refute(rect == [0, 1, 2, 5])
58
- end
59
-
60
53
  describe "validation" do
61
54
  it "ensures that it is a correct PDF rectangle" do
62
55
  doc = HexaPDF::Document.new
@@ -2,9 +2,10 @@
2
2
 
3
3
  require 'test_helper'
4
4
  require 'hexapdf/revision'
5
- require 'hexapdf/object'
5
+ require 'hexapdf/dictionary'
6
6
  require 'hexapdf/reference'
7
7
  require 'hexapdf/xref_section'
8
+ require 'hexapdf/type/catalog'
8
9
  require 'stringio'
9
10
 
10
11
  describe HexaPDF::Revision do
@@ -12,6 +13,10 @@ describe HexaPDF::Revision do
12
13
  @xref_section = HexaPDF::XRefSection.new
13
14
  @xref_section.add_in_use_entry(2, 0, 5000)
14
15
  @xref_section.add_free_entry(3, 0)
16
+ @xref_section.add_in_use_entry(4, 0, 1000)
17
+ @xref_section.add_in_use_entry(5, 0, 1000)
18
+ @xref_section.add_in_use_entry(6, 0, 5000)
19
+ @xref_section.add_in_use_entry(7, 0, 5000)
15
20
  @obj = HexaPDF::Object.new(:val, oid: 1, gen: 0)
16
21
  @ref = HexaPDF::Reference.new(1, 0)
17
22
 
@@ -19,7 +24,13 @@ describe HexaPDF::Revision do
19
24
  if entry.type == :free
20
25
  HexaPDF::Object.new(nil, oid: entry.oid, gen: entry.gen)
21
26
  else
22
- HexaPDF::Object.new(:Test, oid: entry.oid, gen: entry.gen)
27
+ case entry.oid
28
+ when 4 then HexaPDF::Dictionary.new({Type: :XRef}, oid: entry.oid, gen: entry.gen)
29
+ when 5 then HexaPDF::Dictionary.new({Type: :ObjStm}, oid: entry.oid, gen: entry.gen)
30
+ when 7 then HexaPDF::Type::Catalog.new({Type: :Catalog}, oid: entry.oid, gen: entry.gen,
31
+ document: self)
32
+ else HexaPDF::Object.new(:Test, oid: entry.oid, gen: entry.gen)
33
+ end
23
34
  end
24
35
  end
25
36
  @rev = HexaPDF::Revision.new({}, xref_section: @xref_section, loader: @loader)
@@ -36,10 +47,10 @@ describe HexaPDF::Revision do
36
47
  end
37
48
 
38
49
  it "returns the next free object number" do
39
- assert_equal(4, @rev.next_free_oid)
40
- @obj.oid = 4
50
+ assert_equal(8, @rev.next_free_oid)
51
+ @obj.oid = 8
41
52
  @rev.add(@obj)
42
- assert_equal(5, @rev.next_free_oid)
53
+ assert_equal(9, @rev.next_free_oid)
43
54
  end
44
55
 
45
56
  describe "add" do
@@ -151,15 +162,14 @@ describe HexaPDF::Revision do
151
162
  assert(@rev.object(@ref).null?)
152
163
  assert(@obj.null?)
153
164
  assert_raises(HexaPDF::Error) { @obj.document }
165
+ assert_same(@obj.data, @rev.object(@ref).data)
154
166
  end
155
167
  end
156
168
 
157
169
  describe "object iteration" do
158
170
  it "iterates over all objects via each" do
159
171
  @rev.add(@obj)
160
- obj2 = @rev.object(2)
161
- obj3 = @rev.object(3)
162
- assert_equal([@obj, obj2, obj3], @rev.each.to_a)
172
+ assert_equal([@obj, *(2..7).map {|i| @rev.object(i) }], @rev.each.to_a)
163
173
  end
164
174
 
165
175
  it "iterates only over loaded objects" do
@@ -178,11 +188,31 @@ describe HexaPDF::Revision do
178
188
  refute(rev.object?(@ref))
179
189
  end
180
190
 
181
- it "can iterate over all modified objects" do
182
- obj = @rev.object(2)
183
- assert_equal([], @rev.each_modified_object.to_a)
184
- obj.value = :Other
185
- @rev.add(@obj)
186
- assert_equal([obj, @obj], @rev.each_modified_object.to_a)
191
+ describe "each_modified_object" do
192
+ it "returns modified objects" do
193
+ obj = @rev.object(2)
194
+ obj.value = :Other
195
+ @rev.add(@obj)
196
+ deleted = @rev.object(6)
197
+ @rev.delete(6)
198
+ assert_equal([obj, @obj, deleted], @rev.each_modified_object.to_a)
199
+ end
200
+
201
+ it "ignores object and xref streams that were deleted" do
202
+ @rev.delete(4)
203
+ @rev.delete(5)
204
+ assert_equal([], @rev.each_modified_object.to_a)
205
+ end
206
+
207
+ it "doesn't return non-modified objects" do
208
+ @rev.object(2)
209
+ assert_equal([], @rev.each_modified_object.to_a)
210
+ end
211
+
212
+ it "doesn't return objects that have modified values just because of reading" do
213
+ obj = @rev.object(7)
214
+ obj.delete(:Type)
215
+ assert_equal([], @rev.each_modified_object.to_a)
216
+ end
187
217
  end
188
218
  end
@@ -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.0)>>
43
+ <</Producer(HexaPDF version 0.20.0)>>
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.0)>>
75
+ <</Producer(HexaPDF version 0.20.0)>>
76
76
  endobj
77
77
  2 0 obj
78
78
  <</Length 10>>stream
@@ -94,7 +94,8 @@ describe HexaPDF::Writer do
94
94
  document = HexaPDF::Document.new(io: input_io)
95
95
  document.trailer.info[:Producer] = "unknown"
96
96
  output_io = StringIO.new(''.force_encoding(Encoding::BINARY))
97
- HexaPDF::Writer.write(document, output_io)
97
+ xref_section = HexaPDF::Writer.write(document, output_io)
98
+ assert_kind_of(HexaPDF::XRefSection, xref_section)
98
99
  assert_equal(input_io.string, output_io.string)
99
100
  end
100
101
 
@@ -103,21 +104,50 @@ describe HexaPDF::Writer do
103
104
  assert_document_conversion(@compressed_input_io)
104
105
  end
105
106
 
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)
107
+ describe "write_incremental" do
108
+ it "writes a document in incremental mode" do
109
+ doc = HexaPDF::Document.new(io: @std_input_io)
110
+ doc.pages.add
111
+ output_io = StringIO.new
112
+ HexaPDF::Writer.write(doc, output_io, incremental: true)
113
+ assert_equal(output_io.string[0, @std_input_io.string.length], @std_input_io.string)
114
+ doc = HexaPDF::Document.new(io: output_io)
115
+ assert_equal(4, doc.revisions.size)
116
+ assert_equal(2, doc.revisions.current.each.to_a.size)
117
+ end
118
+
119
+ it "uses an xref stream if the document already contains at least one" do
120
+ doc = HexaPDF::Document.new(io: @compressed_input_io)
121
+ doc.pages.add
122
+ output_io = StringIO.new
123
+ HexaPDF::Writer.write(doc, output_io, incremental: true)
124
+ refute_match(/^trailer/, output_io.string)
125
+ end
115
126
  end
116
127
 
117
- it "raises an error if no xref stream is in a revision but object streams are" do
128
+ it "creates an xref stream if no xref stream is in a revision but object streams are" do
118
129
  document = HexaPDF::Document.new
119
130
  document.add({Type: :ObjStm})
120
- assert_raises(HexaPDF::Error) { HexaPDF::Writer.new(document, StringIO.new).write }
131
+ HexaPDF::Writer.new(document, StringIO.new).write
132
+ assert(:XRef, document.object(2).type)
133
+ end
134
+
135
+ it "creates an xref stream if a previous revision had one" do
136
+ document = HexaPDF::Document.new
137
+ document.pages.add
138
+ document.revisions.add
139
+ document.pages.add
140
+ document.add({Type: :ObjStm})
141
+ document.revisions.add
142
+ document.pages.add
143
+ io = StringIO.new
144
+ HexaPDF::Writer.new(document, io).write
145
+
146
+ document = HexaPDF::Document.new(io: io)
147
+ assert_equal(3, document.revisions.count)
148
+ assert(document.revisions[0].none? {|obj| obj.type == :XRef })
149
+ assert(document.revisions[1].one? {|obj| obj.type == :XRef })
150
+ assert(document.revisions[2].one? {|obj| obj.type == :XRef })
121
151
  end
122
152
 
123
153
  it "raises an error if the class is misused and an xref section contains invalid entries" do
@@ -155,6 +155,16 @@ describe HexaPDF::Type::AcroForm::Field do
155
155
  assert_equal(5, widget[:X])
156
156
  end
157
157
 
158
+ it "sets the print flag on the widget" do
159
+ widget = @field.create_widget(@page, X: 5)
160
+ assert_equal([:print], widget.flags)
161
+ end
162
+
163
+ it "associates the page with the widget" do
164
+ widget = @field.create_widget(@page, X: 5)
165
+ assert_same(@page, widget[:P])
166
+ end
167
+
158
168
  it "adds the new widget to the given page's annotations" do
159
169
  widget = @field.create_widget(@page)
160
170
  assert_equal([widget], @page[:Annots].value)
@@ -179,7 +189,7 @@ describe HexaPDF::Type::AcroForm::Field do
179
189
  refute_same(widget1, kids[0])
180
190
  assert_same(widget2, kids[1])
181
191
  assert_nil(@field[:Rect])
182
- assert_equal({Rect: [1, 2, 3, 4], Type: :Annot, Subtype: :Widget, Parent: @field},
192
+ assert_equal({Rect: [1, 2, 3, 4], Type: :Annot, Subtype: :Widget, Parent: @field, F: 4, P: @page},
183
193
  kids[0].value)
184
194
  assert_equal([2, 1, 4, 3], kids[1][:Rect].value)
185
195
 
@@ -226,6 +226,11 @@ describe HexaPDF::Type::AcroForm::Form do
226
226
  assert(field.flagged?(:multi_select))
227
227
  applies_variable_text_properties(:create_list_box)
228
228
  end
229
+
230
+ it "creates a signature field" do
231
+ field = @acro_form.create_signature_field("field")
232
+ assert_equal(:signature_field, field.concrete_field_type)
233
+ end
229
234
  end
230
235
 
231
236
  it "returns the default resources" do
@@ -0,0 +1,71 @@
1
+ require 'openssl'
2
+
3
+ module TestHelper
4
+
5
+ class Certificates
6
+
7
+ def ca_key
8
+ @ca_key ||= OpenSSL::PKey::RSA.new(512)
9
+ end
10
+
11
+ def ca_certificate
12
+ @ca_cert ||=
13
+ begin
14
+ ca_name = OpenSSL::X509::Name.parse('/C=AT/O=HexaPDF/CN=HexaPDF Test Root CA')
15
+
16
+ ca_cert = OpenSSL::X509::Certificate.new
17
+ ca_cert.serial = 0
18
+ ca_cert.version = 2
19
+ ca_cert.not_before = Time.now - 86400
20
+ ca_cert.not_after = Time.now + 86400
21
+ ca_cert.public_key = ca_key.public_key
22
+ ca_cert.subject = ca_name
23
+ ca_cert.issuer = ca_name
24
+
25
+ extension_factory = OpenSSL::X509::ExtensionFactory.new
26
+ extension_factory.subject_certificate = ca_cert
27
+ extension_factory.issuer_certificate = ca_cert
28
+ ca_cert.add_extension(extension_factory.create_extension('subjectKeyIdentifier', 'hash'))
29
+ ca_cert.add_extension(extension_factory.create_extension('basicConstraints', 'CA:TRUE', true))
30
+ ca_cert.add_extension(extension_factory.create_extension('keyUsage', 'cRLSign,keyCertSign', true))
31
+ ca_cert.sign(ca_key, OpenSSL::Digest.new('SHA1'))
32
+
33
+ ca_cert
34
+ end
35
+ end
36
+
37
+ def signer_key
38
+ @signer_key ||= OpenSSL::PKey::RSA.new(512)
39
+ end
40
+
41
+ def signer_certificate
42
+ @signer_certificate ||=
43
+ begin
44
+ name = OpenSSL::X509::Name.parse('/CN=signer/DC=gettalong')
45
+
46
+ signer_cert = OpenSSL::X509::Certificate.new
47
+ signer_cert.serial = 2
48
+ signer_cert.version = 2
49
+ signer_cert.not_before = Time.now - 86400
50
+ signer_cert.not_after = Time.now + 86400
51
+ signer_cert.public_key = signer_key.public_key
52
+ signer_cert.subject = name
53
+ signer_cert.issuer = ca_certificate.subject
54
+
55
+ extension_factory = OpenSSL::X509::ExtensionFactory.new
56
+ extension_factory.subject_certificate = signer_cert
57
+ extension_factory.issuer_certificate = ca_certificate
58
+ signer_cert.add_extension(extension_factory.create_extension('subjectKeyIdentifier', 'hash'))
59
+ signer_cert.add_extension(extension_factory.create_extension('basicConstraints', 'CA:FALSE'))
60
+ signer_cert.add_extension(extension_factory.create_extension('keyUsage', 'digitalSignature'))
61
+ signer_cert.sign(ca_key, OpenSSL::Digest.new('SHA1'))
62
+
63
+ signer_cert
64
+ end
65
+ end
66
+
67
+ end
68
+
69
+ end
70
+
71
+ CERTIFICATES = TestHelper::Certificates.new
@@ -0,0 +1,99 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require 'test_helper'
4
+ require_relative 'common'
5
+ require 'hexapdf/type/signature'
6
+ require 'ostruct'
7
+
8
+ describe HexaPDF::Type::Signature::AdbePkcs7Detached do
9
+ before do
10
+ @data = 'Some data'
11
+ @dict = OpenStruct.new
12
+ @pkcs7 = OpenSSL::PKCS7.sign(CERTIFICATES.signer_certificate, CERTIFICATES.signer_key,
13
+ @data, [CERTIFICATES.ca_certificate],
14
+ OpenSSL::PKCS7::DETACHED)
15
+ @dict.contents = @pkcs7.to_der
16
+ @dict.signed_data = @data
17
+ @handler = HexaPDF::Type::Signature::AdbePkcs7Detached.new(@dict)
18
+ end
19
+
20
+ it "returns the signer name" do
21
+ assert_equal("signer", @handler.signer_name)
22
+ end
23
+
24
+ it "returns the signing time" do
25
+ assert_equal(@pkcs7.signers.first.signed_time, @handler.signing_time)
26
+ end
27
+
28
+ it "returns the certificate chain" do
29
+ assert_equal([CERTIFICATES.signer_certificate, CERTIFICATES.ca_certificate],
30
+ @handler.certificate_chain)
31
+ end
32
+
33
+ it "returns the signer certificate" do
34
+ assert_equal(CERTIFICATES.signer_certificate, @handler.signer_certificate)
35
+ end
36
+
37
+ it "allows access to the signer information" do
38
+ info = @handler.signer_info
39
+ assert(info)
40
+ assert_equal(2, info.serial)
41
+ assert_equal(CERTIFICATES.signer_certificate.issuer, info.issuer)
42
+ end
43
+
44
+ describe "verify" do
45
+ before do
46
+ @store = OpenSSL::X509::Store.new
47
+ @store.add_cert(CERTIFICATES.ca_certificate)
48
+ end
49
+
50
+ it "logs an error if there are no certificates" do
51
+ def @handler.certificate_chain; []; end
52
+ result = @handler.verify(@store)
53
+ assert_equal(1, result.messages.size)
54
+ assert_equal(:error, result.messages.first.type)
55
+ assert_match(/No certificates/, result.messages.first.content)
56
+ end
57
+
58
+ it "logs an error if there is more than one signer" do
59
+ @pkcs7.add_signer(OpenSSL::PKCS7::SignerInfo.new(CERTIFICATES.signer_certificate,
60
+ CERTIFICATES.signer_key, 'SHA1'))
61
+ @dict.contents = @pkcs7.to_der
62
+ @handler = HexaPDF::Type::Signature::AdbePkcs7Detached.new(@dict)
63
+ result = @handler.verify(@store)
64
+ assert_equal(2, result.messages.size)
65
+ assert_equal(:error, result.messages.first.type)
66
+ assert_match(/Exactly one signer needed/, result.messages.first.content)
67
+ end
68
+
69
+ it "logs an error if the signer certificate is not found" do
70
+ def @handler.signer_certificate; nil end
71
+ result = @handler.verify(@store)
72
+ assert_equal(1, result.messages.size)
73
+ assert_equal(:error, result.messages.first.type)
74
+ assert_match(/Signer.*not found/, result.messages.first.content)
75
+ end
76
+
77
+ it "logs an error if the signer certificate is not usable for digital signatures" do
78
+ @pkcs7 = OpenSSL::PKCS7.sign(CERTIFICATES.ca_certificate, CERTIFICATES.ca_key,
79
+ @data, [CERTIFICATES.ca_certificate],
80
+ OpenSSL::PKCS7::DETACHED)
81
+ @dict.contents = @pkcs7.to_der
82
+ @handler = HexaPDF::Type::Signature::AdbePkcs7Detached.new(@dict)
83
+ result = @handler.verify(@store)
84
+ assert_equal(:error, result.messages.first.type)
85
+ assert_match(/key usage is missing 'Digital Signature'/, result.messages.first.content)
86
+ end
87
+
88
+ it "verifies the signature itself" do
89
+ result = @handler.verify(@store)
90
+ assert_equal(:info, result.messages.last.type)
91
+ assert_match(/Signature valid/, result.messages.last.content)
92
+
93
+ @dict.signed_data = 'other data'
94
+ result = @handler.verify(@store)
95
+ assert_equal(:error, result.messages.last.type)
96
+ assert_match(/Signature verification failed/, result.messages.last.content)
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,66 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require 'test_helper'
4
+ require_relative 'common'
5
+ require 'hexapdf/type/signature'
6
+ require 'ostruct'
7
+
8
+ describe HexaPDF::Type::Signature::AdbeX509RsaSha1 do
9
+ before do
10
+ @data = 'Some data'
11
+ @dict = OpenStruct.new
12
+ @dict.signed_data = @data
13
+ encoded_data = CERTIFICATES.signer_key.sign(OpenSSL::Digest.new('SHA1'), @data)
14
+ @dict.contents = OpenSSL::ASN1::OctetString.new(encoded_data).to_der
15
+ @dict.Cert = [CERTIFICATES.signer_certificate.to_der]
16
+ def @dict.key?(*); true; end
17
+ @handler = HexaPDF::Type::Signature::AdbeX509RsaSha1.new(@dict)
18
+ end
19
+
20
+ it "returns the certificate chain" do
21
+ assert_equal([CERTIFICATES.signer_certificate], @handler.certificate_chain)
22
+
23
+ @dict.singleton_class.undef_method(:key?)
24
+ def @dict.key?(*); false; end
25
+ assert_equal([], @handler.certificate_chain)
26
+ end
27
+
28
+ it "returns the signer certificate" do
29
+ assert_equal(CERTIFICATES.signer_certificate, @handler.signer_certificate)
30
+ end
31
+
32
+ describe "verify" do
33
+ before do
34
+ @store = OpenSSL::X509::Store.new
35
+ @store.set_default_paths
36
+ @store.purpose = OpenSSL::X509::PURPOSE_SMIME_SIGN
37
+ end
38
+
39
+ it "logs an error if there are no certificates" do
40
+ def @handler.certificate_chain; []; end
41
+ result = @handler.verify(@store)
42
+ assert_equal(1, result.messages.size)
43
+ assert_equal(:error, result.messages.first.type)
44
+ assert_match(/No certificates/, result.messages.first.content)
45
+ end
46
+
47
+ it "logs an error if signature contents is not of the expected type" do
48
+ @dict.contents = OpenSSL::ASN1::Boolean.new(true).to_der
49
+ result = @handler.verify(@store)
50
+ assert_equal(1, result.messages.size)
51
+ assert_equal(:error, result.messages.first.type)
52
+ assert_match(/signature object invalid/, result.messages.first.content)
53
+ end
54
+
55
+ it "verifies the signature itself" do
56
+ result = @handler.verify(@store)
57
+ assert_equal(:info, result.messages.last.type)
58
+ assert_match(/Signature valid/, result.messages.last.content)
59
+
60
+ @dict.signed_data = 'other data'
61
+ result = @handler.verify(@store)
62
+ assert_equal(:error, result.messages.last.type)
63
+ assert_match(/Signature verification failed/, result.messages.last.content)
64
+ end
65
+ end
66
+ end