hexapdf 0.20.0 → 0.20.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +42 -0
- data/data/hexapdf/encoding/glyphlist.txt +4283 -4282
- data/data/hexapdf/encoding/zapfdingbats.txt +203 -202
- data/lib/hexapdf/cli/form.rb +9 -1
- data/lib/hexapdf/encryption/security_handler.rb +5 -1
- data/lib/hexapdf/font/encoding/glyph_list.rb +5 -6
- data/lib/hexapdf/font/type1_wrapper.rb +14 -15
- data/lib/hexapdf/task/dereference.rb +12 -4
- data/lib/hexapdf/task/optimize.rb +3 -3
- data/lib/hexapdf/type/annotation.rb +1 -1
- data/lib/hexapdf/type/signature/adbe_pkcs7_detached.rb +1 -5
- data/lib/hexapdf/type/signature/adbe_x509_rsa_sha1.rb +1 -5
- data/lib/hexapdf/type/signature/handler.rb +39 -11
- data/lib/hexapdf/version.rb +1 -1
- data/lib/hexapdf/writer.rb +11 -3
- data/test/hexapdf/encryption/test_security_handler.rb +11 -3
- data/test/hexapdf/font/test_type1_wrapper.rb +4 -0
- data/test/hexapdf/task/test_optimize.rb +4 -1
- data/test/hexapdf/test_writer.rb +13 -2
- data/test/hexapdf/type/signature/test_handler.rb +33 -7
- data/test/hexapdf/type/test_annotation.rb +7 -1
- metadata +3 -3
@@ -78,6 +78,45 @@ module HexaPDF
|
|
78
78
|
raise "Needs to be implemented by specific handlers"
|
79
79
|
end
|
80
80
|
|
81
|
+
# Verifies general signature properties and prepares the provided OpenSSL::X509::Store
|
82
|
+
# object for use by concrete implementations.
|
83
|
+
#
|
84
|
+
# Needs to be called by specific handlers.
|
85
|
+
def verify(store, allow_self_signed: false)
|
86
|
+
result = VerificationResult.new
|
87
|
+
check_certified_signature(result)
|
88
|
+
verify_signing_time(result)
|
89
|
+
store.verify_callback =
|
90
|
+
store_verification_callback(result, allow_self_signed: allow_self_signed)
|
91
|
+
result
|
92
|
+
end
|
93
|
+
|
94
|
+
protected
|
95
|
+
|
96
|
+
# Verifies that the signing time was within the validity period of the signer certificate.
|
97
|
+
def verify_signing_time(result)
|
98
|
+
time = signing_time
|
99
|
+
cert = signer_certificate
|
100
|
+
if time && cert && (time < cert.not_before || time > cert.not_after)
|
101
|
+
result.log(:error, "Signer certificate not valid at signing time")
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
DOCMDP_PERMS_MESSAGE_MAP = { # :nodoc:
|
106
|
+
1 => "No changes allowed",
|
107
|
+
2 => "Form filling and signing allowed",
|
108
|
+
3 => "Form filling, signing and annotation manipulation allowed",
|
109
|
+
}
|
110
|
+
|
111
|
+
# Sets an informational message on +result+ whether the signature is a certified signature.
|
112
|
+
def check_certified_signature(result)
|
113
|
+
sigref = signature_dict[:Reference]&.find {|ref| ref[:TransformMethod] == :DocMDP }
|
114
|
+
if sigref && signature_dict.document.catalog[:Perms]&.[](:DocMDP) == signature_dict
|
115
|
+
perms = sigref[:TransformParams]&.[](:P) || 2
|
116
|
+
result.log(:info, "Certified signature (#{DOCMDP_PERMS_MESSAGE_MAP[perms]})")
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
81
120
|
# Returns the block that should be used as the OpenSSL::X509::Store verification callback.
|
82
121
|
#
|
83
122
|
# +result+:: The VerificationResult object that should be updated if problems are found.
|
@@ -94,17 +133,6 @@ module HexaPDF
|
|
94
133
|
end
|
95
134
|
end
|
96
135
|
|
97
|
-
protected
|
98
|
-
|
99
|
-
# Verifies that the signing time was within the validity period of the signer certificate.
|
100
|
-
def verify_signing_time(result)
|
101
|
-
time = signing_time
|
102
|
-
cert = signer_certificate
|
103
|
-
if time && (time < cert.not_before || time > cert.not_after)
|
104
|
-
result.log(:error, "Signer certificate not valid at signing time")
|
105
|
-
end
|
106
|
-
end
|
107
|
-
|
108
136
|
end
|
109
137
|
|
110
138
|
end
|
data/lib/hexapdf/version.rb
CHANGED
data/lib/hexapdf/writer.rb
CHANGED
@@ -90,12 +90,20 @@ module HexaPDF
|
|
90
90
|
#
|
91
91
|
# For this method to work the document must have been created from an existing file.
|
92
92
|
def write_incremental
|
93
|
-
@document.revisions.parser
|
94
|
-
|
93
|
+
parser = @document.revisions.parser
|
94
|
+
|
95
|
+
_, orig_trailer = parser.load_revision(parser.startxref_offset)
|
96
|
+
orig_trailer = @document.wrap(orig_trailer, type: :XXTrailer)
|
97
|
+
if @document.revisions.current.trailer[:Encrypt]&.value != orig_trailer[:Encrypt]&.value
|
98
|
+
raise HexaPDF::Error, "Used encryption cannot be modified when doing incremental writing"
|
99
|
+
end
|
100
|
+
|
101
|
+
parser.io.seek(0, IO::SEEK_SET)
|
102
|
+
IO.copy_stream(parser.io, @io)
|
95
103
|
@io << "\n"
|
96
104
|
|
97
105
|
@rev_size = @document.revisions.current.next_free_oid
|
98
|
-
@use_xref_streams =
|
106
|
+
@use_xref_streams = parser.contains_xref_streams?
|
99
107
|
|
100
108
|
revision = Revision.new(@document.revisions.current.trailer)
|
101
109
|
@document.revisions.each do |rev|
|
@@ -294,9 +294,17 @@ describe HexaPDF::Encryption::SecurityHandler do
|
|
294
294
|
assert_equal('string', obj.stream)
|
295
295
|
end
|
296
296
|
|
297
|
-
it "doesn't decrypt a document's Encrypt
|
298
|
-
@document
|
299
|
-
|
297
|
+
it "doesn't decrypt a document's Encrypt dictionaries" do
|
298
|
+
@document = HexaPDF::Document.new
|
299
|
+
@document.trailer[:Encrypt] = @document.add({Key: "Something"})
|
300
|
+
@document.revisions.add
|
301
|
+
@document.trailer[:Encrypt] = @document.add({Key: "Otherthing"})
|
302
|
+
@handler = TestHandler.new(@document)
|
303
|
+
|
304
|
+
assert_equal("Something",
|
305
|
+
@handler.decrypt(@document.revisions[0].trailer[:Encrypt])[:Key])
|
306
|
+
assert_equal("Otherthing",
|
307
|
+
@handler.decrypt(@document.revisions[1].trailer[:Encrypt])[:Key])
|
300
308
|
end
|
301
309
|
|
302
310
|
it "defers handling encryption to a Crypt filter is specified" do
|
@@ -103,5 +103,9 @@ describe HexaPDF::Font::Type1Wrapper do
|
|
103
103
|
it "sets the circular reference" do
|
104
104
|
assert_same(@times_wrapper, @times_wrapper.pdf_object.font_wrapper)
|
105
105
|
end
|
106
|
+
|
107
|
+
it "makes sure that the PDF dictionaries are indirect" do
|
108
|
+
assert(@times_wrapper.pdf_object.indirect?)
|
109
|
+
end
|
106
110
|
end
|
107
111
|
end
|
@@ -168,12 +168,14 @@ describe HexaPDF::Task::Optimize do
|
|
168
168
|
page1.resources[:XObject][:test] = @doc.add({})
|
169
169
|
page1.resources[:XObject][:used_on_page2] = @doc.add({})
|
170
170
|
page1.resources[:XObject][:unused] = @doc.add({})
|
171
|
-
page1.contents = "/test Do"
|
171
|
+
page1.contents = "/test Do /InvalidRef Do"
|
172
172
|
page2 = @doc.pages.add
|
173
173
|
page2.resources[:XObject] = {}
|
174
174
|
page2.resources[:XObject][:used_on2] = page1.resources[:XObject][:used_on_page2]
|
175
175
|
page2.resources[:XObject][:also_unused] = page1.resources[:XObject][:unused]
|
176
176
|
page2.contents = "/used_on2 Do"
|
177
|
+
page3 = @doc.pages.add
|
178
|
+
page3.contents = "/unused Do "
|
177
179
|
|
178
180
|
@doc.task(:optimize, prune_page_resources: true, compress_pages: compress_pages)
|
179
181
|
|
@@ -182,6 +184,7 @@ describe HexaPDF::Task::Optimize do
|
|
182
184
|
refute(page1.resources[:XObject].key?(:unused))
|
183
185
|
assert(page2.resources[:XObject].key?(:used_on2))
|
184
186
|
refute(page2.resources[:XObject].key?(:also_unused))
|
187
|
+
assert_equal("/unused Do#{compress_pages ? "\n" : ' '}", page3.contents)
|
185
188
|
end
|
186
189
|
end
|
187
190
|
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.20.
|
43
|
+
<</Producer(HexaPDF version 0.20.4)>>
|
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.20.
|
75
|
+
<</Producer(HexaPDF version 0.20.4)>>
|
76
76
|
endobj
|
77
77
|
2 0 obj
|
78
78
|
<</Length 10>>stream
|
@@ -123,6 +123,17 @@ describe HexaPDF::Writer do
|
|
123
123
|
HexaPDF::Writer.write(doc, output_io, incremental: true)
|
124
124
|
refute_match(/^trailer/, output_io.string)
|
125
125
|
end
|
126
|
+
|
127
|
+
it "raises an error if the used encryption was changed" do
|
128
|
+
io = StringIO.new
|
129
|
+
doc = HexaPDF::Document.new
|
130
|
+
doc.encrypt
|
131
|
+
doc.write(io)
|
132
|
+
|
133
|
+
doc = HexaPDF::Document.new(io: io)
|
134
|
+
doc.encrypt(owner_password: 'test')
|
135
|
+
assert_raises(HexaPDF::Error) { doc.write('notused', incremental: true) }
|
136
|
+
end
|
126
137
|
end
|
127
138
|
|
128
139
|
it "creates an xref stream if no xref stream is in a revision but object streams are" do
|
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
require 'test_helper'
|
4
4
|
require 'hexapdf/type/signature'
|
5
|
+
require 'hexapdf/document'
|
5
6
|
require 'time'
|
6
7
|
require 'ostruct'
|
7
8
|
|
@@ -10,6 +11,7 @@ describe HexaPDF::Type::Signature::Handler do
|
|
10
11
|
@time = Time.parse("2021-11-14 7:00")
|
11
12
|
@dict = {Name: "handler", M: @time}
|
12
13
|
@handler = HexaPDF::Type::Signature::Handler.new(@dict)
|
14
|
+
@result = HexaPDF::Type::Signature::VerificationResult.new
|
13
15
|
end
|
14
16
|
|
15
17
|
it "returns the signer name" do
|
@@ -30,7 +32,6 @@ describe HexaPDF::Type::Signature::Handler do
|
|
30
32
|
|
31
33
|
describe "store_verification_callback" do
|
32
34
|
before do
|
33
|
-
@result = HexaPDF::Type::Signature::VerificationResult.new
|
34
35
|
@context = OpenStruct.new
|
35
36
|
end
|
36
37
|
|
@@ -40,7 +41,7 @@ describe HexaPDF::Type::Signature::Handler do
|
|
40
41
|
[true, false].each do |allow_self_signed|
|
41
42
|
@result.messages.clear
|
42
43
|
@context.error = error
|
43
|
-
@handler.store_verification_callback
|
44
|
+
@handler.send(:store_verification_callback, @result, allow_self_signed: allow_self_signed).
|
44
45
|
call(false, @context)
|
45
46
|
assert_equal(1, @result.messages.size)
|
46
47
|
assert_match(/self-signed certificate/i, @result.messages[0].content)
|
@@ -51,26 +52,51 @@ describe HexaPDF::Type::Signature::Handler do
|
|
51
52
|
end
|
52
53
|
|
53
54
|
it "verifies the signing time" do
|
54
|
-
result = HexaPDF::Type::Signature::VerificationResult.new
|
55
55
|
[
|
56
56
|
[true, '6:00', '8:00'],
|
57
57
|
[false, '7:30', '8:00'],
|
58
58
|
[false, '5:00', '6:00'],
|
59
59
|
].each do |success, not_before, not_after|
|
60
|
-
result.messages.clear
|
60
|
+
@result.messages.clear
|
61
61
|
@handler.define_singleton_method(:signer_certificate) do
|
62
62
|
OpenStruct.new.tap do |struct|
|
63
63
|
struct.not_before = Time.parse("2021-11-14 #{not_before}")
|
64
64
|
struct.not_after = Time.parse("2021-11-14 #{not_after}")
|
65
65
|
end
|
66
66
|
end
|
67
|
-
@handler.send(:verify_signing_time, result)
|
67
|
+
@handler.send(:verify_signing_time, @result)
|
68
68
|
if success
|
69
|
-
assert(result.messages.empty?)
|
69
|
+
assert(@result.messages.empty?)
|
70
70
|
else
|
71
|
-
assert_equal(1, result.messages.size)
|
71
|
+
assert_equal(1, @result.messages.size)
|
72
72
|
end
|
73
73
|
@handler.singleton_class.remove_method(:signer_certificate)
|
74
74
|
end
|
75
75
|
end
|
76
|
+
|
77
|
+
describe "check_certified_signature" do
|
78
|
+
before do
|
79
|
+
@dict = HexaPDF::Document.new.wrap({Type: :Sig})
|
80
|
+
@handler.instance_variable_set(:@signature_dict, @dict)
|
81
|
+
end
|
82
|
+
|
83
|
+
it "logs nothing if there is no signature reference dictionary" do
|
84
|
+
@handler.send(:check_certified_signature, @result)
|
85
|
+
assert(@result.messages.empty?)
|
86
|
+
end
|
87
|
+
|
88
|
+
it "logs nothing if the global DocMDP permissions entry doesn't point to the signature" do
|
89
|
+
@dict[:Reference] = [{TransformMethod: :DocMDP}]
|
90
|
+
@handler.send(:check_certified_signature, @result)
|
91
|
+
assert(@result.messages.empty?)
|
92
|
+
end
|
93
|
+
|
94
|
+
it "logs a message if the signature is a certified one" do
|
95
|
+
@dict[:Reference] = [{TransformMethod: :DocMDP}]
|
96
|
+
@dict.document.catalog[:Perms] = {DocMDP: @dict}
|
97
|
+
@handler.send(:check_certified_signature, @result)
|
98
|
+
assert_equal(1, @result.messages.size)
|
99
|
+
assert_match(/certified signature/i, @result.messages[0].content)
|
100
|
+
end
|
101
|
+
end
|
76
102
|
end
|
@@ -77,7 +77,13 @@ describe HexaPDF::Type::Annotation do
|
|
77
77
|
stream[:BBox] = [1, 2, 3, 4]
|
78
78
|
appearance = @annot.appearance
|
79
79
|
assert_same(stream.data, appearance.data)
|
80
|
-
|
80
|
+
assert_kind_of(HexaPDF::Type::Form, appearance)
|
81
|
+
|
82
|
+
stream[:Type] = :XObject
|
83
|
+
stream[:Subtype] = :Form
|
84
|
+
appearance = @annot.appearance
|
85
|
+
assert_same(stream.data, appearance.data)
|
86
|
+
assert_kind_of(HexaPDF::Type::Form, appearance)
|
81
87
|
|
82
88
|
@annot[:AP][:N] = {X: {}}
|
83
89
|
assert_nil(@annot.appearance)
|
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.20.
|
4
|
+
version: 0.20.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Thomas Leitner
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2022-01-26 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: cmdparse
|
@@ -671,7 +671,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
671
671
|
- !ruby/object:Gem::Version
|
672
672
|
version: '0'
|
673
673
|
requirements: []
|
674
|
-
rubygems_version: 3.
|
674
|
+
rubygems_version: 3.3.3
|
675
675
|
signing_key:
|
676
676
|
specification_version: 4
|
677
677
|
summary: HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
|