pdfrb 0.8.6 → 0.8.7
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/lib/pdfrb/cli.rb +9 -3
- data/lib/pdfrb/digital_signature.rb +8 -7
- data/lib/pdfrb/linearization/writer.rb +20 -5
- data/lib/pdfrb/serializer.rb +40 -6
- data/lib/pdfrb/version.rb +1 -1
- data/lib/pdfrb/writer.rb +33 -12
- data/lib/pdfrb.rb +0 -2
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b0edf5439bfe359af4b900aeb846047b168d741271eb78a7f78315ca5b0bba55
|
|
4
|
+
data.tar.gz: 0b30e0edb04dcc2a4d26a493ba782379a8a864904eafc72f07c0c49ca4a25e57
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: bd9802dee13e72edb052c80bd4fea095eade14c95b0fe8cb31277c0a9c36de9f9c202d99bbc3a485911dc5035d6253589b3f50358f0d2b789f4756c19d211e72
|
|
7
|
+
data.tar.gz: c44636603bba32db00de362646b378238fcb884008b085627c1e19d66e899472d9c1a2539ffffd3eb076a58788afb3abec2eaecfb9b44274960e025d31c7ed34
|
data/lib/pdfrb/cli.rb
CHANGED
|
@@ -334,10 +334,16 @@ module Pdfrb
|
|
|
334
334
|
|
|
335
335
|
# Set up the handler so Writer can encrypt per-object.
|
|
336
336
|
handler = Pdfrb::Encryption::StandardSecurityHandler.new(
|
|
337
|
-
|
|
338
|
-
|
|
337
|
+
{
|
|
338
|
+
Encrypt: { V: v, R: r, Length: length, P: perms,
|
|
339
|
+
O: o_entry, U: u_entry },
|
|
340
|
+
ID: [id0.b, id0.b],
|
|
341
|
+
}
|
|
339
342
|
)
|
|
340
|
-
handler.verify_user_password(user_pw)
|
|
343
|
+
unless handler.verify_user_password?(user_pw)
|
|
344
|
+
raise Pdfrb::EncryptionError, "failed to derive encryption key"
|
|
345
|
+
end
|
|
346
|
+
|
|
341
347
|
doc.config["encryption.handler"] = handler
|
|
342
348
|
doc.config["encryption.password"] = user_pw
|
|
343
349
|
end
|
|
@@ -13,16 +13,17 @@ module Pdfrb
|
|
|
13
13
|
autoload :TimestampHandler, "pdfrb/digital_signature/timestamp_handler"
|
|
14
14
|
autoload :TimestampClient, "pdfrb/digital_signature/timestamp_client"
|
|
15
15
|
|
|
16
|
-
HANDLERS = {
|
|
17
|
-
"adbe.pkcs7.detached": CmsHandler,
|
|
18
|
-
"adbe.pkcs7.sha1": CmsHandler,
|
|
19
|
-
"adbe.revision": TimestampHandler,
|
|
20
|
-
}.freeze
|
|
21
|
-
|
|
22
16
|
module_function
|
|
23
17
|
|
|
18
|
+
# Resolve the handler class for +sub_filter+. Constant references
|
|
19
|
+
# live inside the case so the autoloads above defer loading the
|
|
20
|
+
# handlers (and their OpenSSL surface) until an actual
|
|
21
|
+
# sign/verify call needs them.
|
|
24
22
|
def handler_for(sub_filter)
|
|
25
|
-
|
|
23
|
+
case sub_filter.to_sym
|
|
24
|
+
when :"adbe.pkcs7.detached", :"adbe.pkcs7.sha1" then CmsHandler
|
|
25
|
+
when :"adbe.revision" then TimestampHandler
|
|
26
|
+
end
|
|
26
27
|
end
|
|
27
28
|
|
|
28
29
|
def sign(document, cert:, key:, **)
|
|
@@ -30,7 +30,7 @@ module Pdfrb
|
|
|
30
30
|
|
|
31
31
|
def initialize(document)
|
|
32
32
|
@document = document
|
|
33
|
-
@serializer = Pdfrb::
|
|
33
|
+
@serializer = Pdfrb::Writer.serializer_for(document)
|
|
34
34
|
end
|
|
35
35
|
|
|
36
36
|
# @param io [IO] target output.
|
|
@@ -70,7 +70,8 @@ module Pdfrb
|
|
|
70
70
|
|
|
71
71
|
ordered.each do |obj|
|
|
72
72
|
offsets[obj.oid] = temp.pos
|
|
73
|
-
temp << @serializer.serialize_indirect(obj
|
|
73
|
+
temp << @serializer.serialize_indirect(obj,
|
|
74
|
+
skip_string_encryption: encryption_dict?(obj))
|
|
74
75
|
end
|
|
75
76
|
first_page_end = temp.pos if first_page_objects.any?
|
|
76
77
|
|
|
@@ -79,7 +80,6 @@ module Pdfrb
|
|
|
79
80
|
hint_compressed = ::Zlib::Deflate.deflate(hint_data)
|
|
80
81
|
hint_offset = temp.pos
|
|
81
82
|
hint_dict = @serializer.serialize(
|
|
82
|
-
Type: :XRef,
|
|
83
83
|
S: HintStream::DEFAULT_ITEM_BITS,
|
|
84
84
|
Filter: :FlateDecode,
|
|
85
85
|
Length: hint_compressed.bytesize
|
|
@@ -132,7 +132,8 @@ module Pdfrb
|
|
|
132
132
|
buf << lin_padded
|
|
133
133
|
|
|
134
134
|
layout.ordered_objects.each do |obj|
|
|
135
|
-
buf << @serializer.serialize_indirect(obj
|
|
135
|
+
buf << @serializer.serialize_indirect(obj,
|
|
136
|
+
skip_string_encryption: encryption_dict?(obj))
|
|
136
137
|
end
|
|
137
138
|
|
|
138
139
|
buf << "#{layout.hint_oid} 0 obj\n"
|
|
@@ -243,7 +244,16 @@ module Pdfrb
|
|
|
243
244
|
end
|
|
244
245
|
size = [@document.next_oid || 1, 2].max
|
|
245
246
|
|
|
246
|
-
|
|
247
|
+
trailer_hash = {}
|
|
248
|
+
(@document.trailer || {}).each do |k, v|
|
|
249
|
+
next if Pdfrb::Writer::TRAILER_OVERRIDDEN_KEYS.include?(k)
|
|
250
|
+
|
|
251
|
+
trailer_hash[k] = v
|
|
252
|
+
end
|
|
253
|
+
trailer_hash[:Size] = size
|
|
254
|
+
trailer_hash[:Root] = root_ref if root_ref
|
|
255
|
+
|
|
256
|
+
trailer_str = @serializer.serialize(trailer_hash)
|
|
247
257
|
"trailer\n#{trailer_str}\nstartxref\n#{xref_offset}\n%%EOF\n"
|
|
248
258
|
end
|
|
249
259
|
|
|
@@ -251,6 +261,11 @@ module Pdfrb
|
|
|
251
261
|
"%PDF-1.7\n%\xE2\xE3\xCF\xD3\n"
|
|
252
262
|
end
|
|
253
263
|
|
|
264
|
+
# The /Encrypt dictionary's own strings must stay in cleartext.
|
|
265
|
+
def encryption_dict?(obj)
|
|
266
|
+
(@document.trailer || {})[:Encrypt]&.oid == obj.oid
|
|
267
|
+
end
|
|
268
|
+
|
|
254
269
|
def write_non_linearized(io)
|
|
255
270
|
Pdfrb::Writer.write(@document, io)
|
|
256
271
|
end
|
data/lib/pdfrb/serializer.rb
CHANGED
|
@@ -50,21 +50,28 @@ module Pdfrb
|
|
|
50
50
|
end
|
|
51
51
|
|
|
52
52
|
# Serialise an indirect object's body, including `oid gen obj`
|
|
53
|
-
# header and (for streams) the stream payload.
|
|
54
|
-
|
|
53
|
+
# header and (for streams) the stream payload. When an encrypter
|
|
54
|
+
# is set, all strings in the object's dictionaries and the stream
|
|
55
|
+
# payload are encrypted with the per-object key (s7.6.1) — pass
|
|
56
|
+
# +skip_string_encryption:+ for the /Encrypt dictionary itself,
|
|
57
|
+
# whose entries must stay readable.
|
|
58
|
+
def serialize_indirect(obj, skip_string_encryption: false)
|
|
55
59
|
raise ArgumentError, "not indirect: #{obj.inspect}" unless obj.indirect?
|
|
56
60
|
|
|
61
|
+
encrypt_strings = !skip_string_encryption && encrypter
|
|
57
62
|
buffer = +""
|
|
58
63
|
buffer << "#{obj.oid} #{obj.gen} obj\n"
|
|
59
64
|
case obj
|
|
60
65
|
when Pdfrb::Model::Cos::Stream
|
|
61
|
-
|
|
66
|
+
dict_value = encrypt_strings ? encrypt_value_strings(obj.value, obj.oid, obj.gen) : obj.value
|
|
67
|
+
dict, payload = emit_stream(obj, dict_value)
|
|
62
68
|
buffer << dict
|
|
63
69
|
buffer << "\nstream\n"
|
|
64
70
|
buffer << (encrypter ? encrypter.encrypt(payload, obj.oid, obj.gen) : payload)
|
|
65
71
|
buffer << "\nendstream\n"
|
|
66
72
|
else
|
|
67
|
-
|
|
73
|
+
value = encrypt_strings ? encrypt_value_strings(obj.value, obj.oid, obj.gen) : obj.value
|
|
74
|
+
buffer << serialize(value)
|
|
68
75
|
buffer << "\n"
|
|
69
76
|
end
|
|
70
77
|
buffer << "endobj\n"
|
|
@@ -73,12 +80,39 @@ module Pdfrb
|
|
|
73
80
|
|
|
74
81
|
private
|
|
75
82
|
|
|
83
|
+
# Deep-transform +value+ so every literal string carries the
|
|
84
|
+
# per-object ciphertext (s7.6.1: strings AND streams encrypt).
|
|
85
|
+
def encrypt_value_strings(value, oid, gen)
|
|
86
|
+
case value
|
|
87
|
+
when ::Hash
|
|
88
|
+
value.each_with_object({}) do |(k, v), h|
|
|
89
|
+
h[k] = encrypt_value_strings(v, oid, gen)
|
|
90
|
+
end
|
|
91
|
+
when ::Array
|
|
92
|
+
value.map { |v| encrypt_value_strings(v, oid, gen) }
|
|
93
|
+
when Pdfrb::Model::PdfArray
|
|
94
|
+
Pdfrb::Model::PdfArray.new(value.map { |v| encrypt_value_strings(v, oid, gen) })
|
|
95
|
+
when Pdfrb::Model::Object
|
|
96
|
+
Pdfrb::Model::Object.new(encrypt_value_strings(value.value, oid, gen),
|
|
97
|
+
oid: value.oid, gen: value.gen)
|
|
98
|
+
when ::String
|
|
99
|
+
bytes = if value.encoding == Encoding::UTF_8
|
|
100
|
+
Pdfrb::Model::Cos::StringEncoding.encode_text(value)
|
|
101
|
+
else
|
|
102
|
+
value.dup.force_encoding(Encoding::BINARY)
|
|
103
|
+
end
|
|
104
|
+
encrypter.encrypt(bytes, oid, gen)
|
|
105
|
+
else
|
|
106
|
+
value
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
76
110
|
# Build the dict body + payload for a stream object, applying
|
|
77
111
|
# FlateDecode compression when enabled and the stream is large
|
|
78
112
|
# enough to benefit. Returns [dict_bytes, payload_bytes].
|
|
79
|
-
def emit_stream(stream_obj)
|
|
113
|
+
def emit_stream(stream_obj, dict_value = stream_obj.value)
|
|
80
114
|
payload = stream_obj.stream
|
|
81
|
-
hash =
|
|
115
|
+
hash = dict_value.dup
|
|
82
116
|
existing_filter = hash[:Filter]
|
|
83
117
|
|
|
84
118
|
if should_compress?(stream_obj, payload, existing_filter)
|
data/lib/pdfrb/version.rb
CHANGED
data/lib/pdfrb/writer.rb
CHANGED
|
@@ -17,24 +17,37 @@ module Pdfrb
|
|
|
17
17
|
class Writer
|
|
18
18
|
DEFAULT_VERSION = "1.4"
|
|
19
19
|
|
|
20
|
+
# Trailer keys each writer recomputes itself; existing values
|
|
21
|
+
# for these are never carried through from the parsed trailer.
|
|
22
|
+
TRAILER_OVERRIDDEN_KEYS = %i[Size Root Prev XRefStm].freeze
|
|
23
|
+
|
|
20
24
|
attr_reader :document, :io, :serializer
|
|
21
25
|
|
|
22
26
|
def initialize(document, io)
|
|
23
27
|
@document = document
|
|
24
28
|
@io = io
|
|
25
|
-
@serializer =
|
|
29
|
+
@serializer = Writer.serializer_for(document)
|
|
30
|
+
@xref_offsets = {}
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Build a Serializer configured for +document+: stream compression
|
|
34
|
+
# from config, and an encrypter derived from the trailer /Encrypt.
|
|
35
|
+
# Raises EncryptionError when the document is encrypted but the
|
|
36
|
+
# configured password does not open it — writing must never
|
|
37
|
+
# silently fall back to plaintext.
|
|
38
|
+
def self.serializer_for(document)
|
|
39
|
+
Serializer.new(
|
|
26
40
|
compress_streams: document.config["writer.compress_streams"],
|
|
27
41
|
compress_min_size: document.config["writer.compress_min_size"],
|
|
28
|
-
**
|
|
42
|
+
**serializer_encrypter_opts_for(document)
|
|
29
43
|
)
|
|
30
|
-
@xref_offsets = {}
|
|
31
44
|
end
|
|
32
45
|
|
|
33
46
|
# Build the encrypter option for the Serializer from the document's
|
|
34
47
|
# /Encrypt dict. If the document isn't encrypted, returns empty hash.
|
|
35
48
|
# The SecurityHandler's encrypt_data method becomes the Serializer's
|
|
36
49
|
# encrypter, which encrypts string/stream payloads per-object.
|
|
37
|
-
def
|
|
50
|
+
def self.serializer_encrypter_opts_for(document)
|
|
38
51
|
# Use a pre-configured handler if the document provides one
|
|
39
52
|
# (e.g., set by the encryption CLI).
|
|
40
53
|
handler = document.config["encryption.handler"]
|
|
@@ -50,15 +63,21 @@ module Pdfrb
|
|
|
50
63
|
return {} unless encrypt_dict
|
|
51
64
|
|
|
52
65
|
handler = Pdfrb::Encryption::StandardSecurityHandler.new(
|
|
53
|
-
Encrypt: encrypt_dict.value,
|
|
54
|
-
ID: trailer[:ID]
|
|
66
|
+
{ Encrypt: encrypt_dict.value, ID: trailer[:ID] }
|
|
55
67
|
)
|
|
56
68
|
password = document.config["encryption.password"] || ""
|
|
57
|
-
handler.verify_user_password(password)
|
|
69
|
+
unless handler.verify_user_password?(password)
|
|
70
|
+
raise Pdfrb::EncryptionError,
|
|
71
|
+
"cannot write encrypted document: password does not verify"
|
|
72
|
+
end
|
|
58
73
|
|
|
59
74
|
{ encrypter: handler }
|
|
60
|
-
|
|
61
|
-
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# The /Encrypt dictionary's own strings (U, O, UE, OE, Perms) must
|
|
78
|
+
# stay in cleartext — they carry the password hashes.
|
|
79
|
+
def encryption_dict?(obj)
|
|
80
|
+
(document.trailer || {})[:Encrypt]&.oid == obj.oid
|
|
62
81
|
end
|
|
63
82
|
|
|
64
83
|
def self.write(document, io)
|
|
@@ -84,7 +103,8 @@ module Pdfrb
|
|
|
84
103
|
next if packed.key?(obj.oid)
|
|
85
104
|
|
|
86
105
|
@xref_offsets[obj.oid] = @io.pos
|
|
87
|
-
@io << @serializer.serialize_indirect(obj
|
|
106
|
+
@io << @serializer.serialize_indirect(obj,
|
|
107
|
+
skip_string_encryption: encryption_dict?(obj))
|
|
88
108
|
end
|
|
89
109
|
|
|
90
110
|
xref_pos = if use_stream
|
|
@@ -118,7 +138,8 @@ module Pdfrb
|
|
|
118
138
|
next unless obj.indirect?
|
|
119
139
|
|
|
120
140
|
@xref_offsets[obj.oid] = @io.pos
|
|
121
|
-
@io << @serializer.serialize_indirect(obj
|
|
141
|
+
@io << @serializer.serialize_indirect(obj,
|
|
142
|
+
skip_string_encryption: encryption_dict?(obj))
|
|
122
143
|
end
|
|
123
144
|
xref_pos = write_xref(prev: previous_xref_offset)
|
|
124
145
|
write_trailer(xref_pos, prev: previous_xref_offset)
|
|
@@ -293,7 +314,7 @@ module Pdfrb
|
|
|
293
314
|
existing = document.trailer || {}
|
|
294
315
|
trailer_hash = {}
|
|
295
316
|
existing.each do |k, v|
|
|
296
|
-
next if
|
|
317
|
+
next if TRAILER_OVERRIDDEN_KEYS.include?(k)
|
|
297
318
|
|
|
298
319
|
trailer_hash[k] = v
|
|
299
320
|
end
|
data/lib/pdfrb.rb
CHANGED
|
@@ -47,8 +47,6 @@ module Pdfrb
|
|
|
47
47
|
autoload :Serializer, "pdfrb/serializer"
|
|
48
48
|
autoload :Writer, "pdfrb/writer"
|
|
49
49
|
autoload :Importer, "pdfrb/importer"
|
|
50
|
-
autoload :Revision, "pdfrb/revision"
|
|
51
|
-
autoload :Revisions, "pdfrb/revisions"
|
|
52
50
|
autoload :XrefSection, "pdfrb/xref_section"
|
|
53
51
|
autoload :Document, "pdfrb/document"
|
|
54
52
|
|