hexapdf 0.19.0 → 0.20.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +69 -0
- data/data/hexapdf/cert/demo_cert.rb +22 -0
- data/data/hexapdf/cert/root-ca.crt +119 -0
- data/data/hexapdf/cert/signing.crt +125 -0
- data/data/hexapdf/cert/signing.key +52 -0
- data/data/hexapdf/cert/sub-ca.crt +125 -0
- data/lib/hexapdf/cli/info.rb +21 -1
- data/lib/hexapdf/configuration.rb +26 -0
- data/lib/hexapdf/content/graphics_state.rb +24 -5
- data/lib/hexapdf/content/processor.rb +1 -1
- data/lib/hexapdf/document/signatures.rb +327 -0
- data/lib/hexapdf/document.rb +26 -0
- data/lib/hexapdf/encryption/standard_security_handler.rb +1 -2
- data/lib/hexapdf/importer.rb +1 -1
- data/lib/hexapdf/layout/style.rb +2 -1
- data/lib/hexapdf/object.rb +5 -3
- data/lib/hexapdf/parser.rb +21 -9
- data/lib/hexapdf/rectangle.rb +0 -6
- data/lib/hexapdf/revision.rb +13 -6
- data/lib/hexapdf/type/acro_form/appearance_generator.rb +2 -4
- data/lib/hexapdf/type/acro_form/field.rb +2 -0
- data/lib/hexapdf/type/acro_form/form.rb +9 -1
- data/lib/hexapdf/type/annotation.rb +36 -3
- data/lib/hexapdf/type/font.rb +5 -0
- data/lib/hexapdf/type/font_simple.rb +1 -1
- data/lib/hexapdf/type/font_type3.rb +20 -0
- data/lib/hexapdf/type/object_stream.rb +3 -1
- data/lib/hexapdf/type/signature/adbe_pkcs7_detached.rb +125 -0
- data/lib/hexapdf/type/signature/adbe_x509_rsa_sha1.rb +99 -0
- data/lib/hexapdf/type/signature/handler.rb +112 -0
- data/lib/hexapdf/type/signature/verification_result.rb +92 -0
- data/lib/hexapdf/type/signature.rb +236 -0
- data/lib/hexapdf/type.rb +1 -0
- data/lib/hexapdf/version.rb +1 -1
- data/lib/hexapdf/writer.rb +24 -10
- data/test/hexapdf/content/test_graphics_state.rb +9 -1
- data/test/hexapdf/content/test_operator.rb +8 -3
- data/test/hexapdf/content/test_processor.rb +1 -1
- data/test/hexapdf/document/test_signatures.rb +225 -0
- data/test/hexapdf/encryption/test_standard_security_handler.rb +8 -6
- data/test/hexapdf/layout/test_style.rb +11 -0
- data/test/hexapdf/test_document.rb +28 -0
- data/test/hexapdf/test_object.rb +7 -2
- data/test/hexapdf/test_parser.rb +14 -0
- data/test/hexapdf/test_rectangle.rb +0 -7
- data/test/hexapdf/test_revision.rb +44 -14
- data/test/hexapdf/test_writer.rb +44 -14
- data/test/hexapdf/type/acro_form/test_field.rb +11 -1
- data/test/hexapdf/type/acro_form/test_form.rb +5 -0
- data/test/hexapdf/type/signature/common.rb +71 -0
- data/test/hexapdf/type/signature/test_adbe_pkcs7_detached.rb +99 -0
- data/test/hexapdf/type/signature/test_adbe_x509_rsa_sha1.rb +66 -0
- data/test/hexapdf/type/signature/test_handler.rb +76 -0
- data/test/hexapdf/type/signature/test_verification_result.rb +47 -0
- data/test/hexapdf/type/test_annotation.rb +40 -2
- data/test/hexapdf/type/test_font.rb +4 -0
- data/test/hexapdf/type/test_font_simple.rb +5 -5
- data/test/hexapdf/type/test_font_type3.rb +16 -1
- data/test/hexapdf/type/test_object_stream.rb +9 -0
- data/test/hexapdf/type/test_signature.rb +131 -0
- metadata +21 -33
- data/test/data/cert/create.sh +0 -171
- data/test/data/cert/root-ca/certs/84E66B6F4C359E741C0AFA014790DF39.pem +0 -119
- data/test/data/cert/root-ca/certs/84E66B6F4C359E741C0AFA014790DF3A.pem +0 -125
- data/test/data/cert/root-ca/db/crlnumber +0 -1
- data/test/data/cert/root-ca/db/index +0 -2
- data/test/data/cert/root-ca/db/index.attr +0 -1
- data/test/data/cert/root-ca/db/index.attr.old +0 -1
- data/test/data/cert/root-ca/db/index.old +0 -1
- data/test/data/cert/root-ca/db/serial +0 -1
- data/test/data/cert/root-ca/db/serial.old +0 -1
- data/test/data/cert/root-ca/private/root-ca.key +0 -52
- data/test/data/cert/root-ca/root-ca.conf +0 -65
- data/test/data/cert/root-ca/root-ca.crt +0 -119
- data/test/data/cert/root-ca/root-ca.csr +0 -28
- data/test/data/cert/signature-1-pkcs7-detached.pdf +0 -182
- data/test/data/cert/sub-ca/certs/453FF080E3EDCD6A388D5368DFC320D9.pem +0 -125
- data/test/data/cert/sub-ca/db/crlnumber +0 -1
- data/test/data/cert/sub-ca/db/index +0 -1
- data/test/data/cert/sub-ca/db/index.attr +0 -1
- data/test/data/cert/sub-ca/db/index.old +0 -0
- data/test/data/cert/sub-ca/db/serial +0 -1
- data/test/data/cert/sub-ca/db/serial.old +0 -1
- data/test/data/cert/sub-ca/private/signing.key +0 -52
- data/test/data/cert/sub-ca/private/sub-ca.key +0 -52
- data/test/data/cert/sub-ca/signing.crt +0 -125
- data/test/data/cert/sub-ca/signing.csr +0 -28
- data/test/data/cert/sub-ca/signing.p12 +0 -0
- data/test/data/cert/sub-ca/sub-ca.conf +0 -65
- data/test/data/cert/sub-ca/sub-ca.crt +0 -125
- 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 = []
|
data/test/hexapdf/test_object.rb
CHANGED
@@ -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
|
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
|
-
|
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
|
data/test/hexapdf/test_parser.rb
CHANGED
@@ -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/
|
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
|
-
|
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(
|
40
|
-
@obj.oid =
|
50
|
+
assert_equal(8, @rev.next_free_oid)
|
51
|
+
@obj.oid = 8
|
41
52
|
@rev.add(@obj)
|
42
|
-
assert_equal(
|
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
|
-
|
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
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
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
|
data/test/hexapdf/test_writer.rb
CHANGED
@@ -40,7 +40,7 @@ describe HexaPDF::Writer do
|
|
40
40
|
219
|
41
41
|
%%EOF
|
42
42
|
3 0 obj
|
43
|
-
<</Producer(HexaPDF version 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.
|
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
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
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 "
|
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
|
-
|
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
|